Contents
Solidity - 기본Solidity - 기본
- private : 관례상 함수 이름을 _로 시작한다
- view: 함수가 상태를 변경하지 않는 경우, view를 붙인다.
- pure: 함수가 앱에서 어떤 데이터도 접근하지 않는다.
- keccak256: sha3의 한 버전인 내장 해시 함수
keccak256("aaaab"); //6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5 keccak256("aaaac"); //b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
- 이벤트: 컨트랙트가 블록체인 상에서 앱의 액션이 발생했을때 행동을 취함
- mapping (map)
- 솔리디티에서 구조화된 데이터를 저장하는 또다른 방법
- 기본적으로 key-value 구조로, 데이터를 저장하고 검색하는데 이용
*// 금융 앱용으로, 유저의 계좌 잔액을 보유하는 uint를 저장한다:* mapping (address => uint) public accountBalance; *// 혹은 userID로 유저 이름을 저장/검색하는 데 매핑을 쓸 수도 있다* mapping (uint => string) userIdToName;
- msg.sender
- 솔리디티에는 모든 함수에서 이용 가능한 특정 전역 변수들이 있다.
- msg.sender는 현재 스마트 컨트랙트를 호출한 사람의 주소를 가리킨다.
- mapping과 msg.sender 이용 예시
mapping (address => uint) favoriteNumber; function setMyNumber(uint _myNumber) public { // `msg.sender`에 대해 `_myNumber`가 저장되도록 `favoriteNumber` 매핑을 업데이트한다 ` favoriteNumber[msg.sender] = _myNumber; // ^ 데이터를 저장하는 구문은 배열로 데이터를 저장할 떄와 동일하다 } function whatIsMyNumber() public view returns (uint) { // sender의 주소에 저장된 값을 불러온다 // sender가 `setMyNumber`을 아직 호출하지 않았다면 반환값은 `0`이 될 것이다 return favoriteNumber[msg.sender]; }
- require
- 특정 조건이 참이 아닐 때 함수가 에러 메세지를 발생하고 실행을 멈추게 된다.
function sayHiToVitalik(string _name) public returns (string) { // _name이 "Vitalik"인지 비교한다. 참이 아닐 경우 에러 메시지를 발생하고 함수를 벗어난다 // (참고: 솔리디티는 고유의 스트링 비교 기능을 가지고 있지 않기 때문에 // 스트링의 keccak256 해시값을 비교하여 스트링 값이 같은지 판단한다) require(keccak256(_name) == keccak256("Vitalik")); // 참이면 함수 실행을 진행한다: return "Hi!"; }
- 상속
contract Doge { function catchphrase() public returns (string) { return "So Wow CryptoDoge"; } } contract BabyDoge is Doge { function anotherCatchphrase() public returns (string) { return "Such Moon BabyDoge"; } }
- Storage vs Memory
- 솔리디티에는 변수를 저장할 수 있는 공간이 storage와 memory 두 가지가 있다.
- Storage
- 블록체인 상에 영구적으로 저장되는 변수
- Memory
- 임시적으로 저장되는 변수로, 컨트랙트 함수에 대한 외부 호출들이 일어나는 사이 지워짐
- 대부분의 경우 솔리디티가 알아서 처리해 주지만, 함수 내에 구조체와 배열을 처리할 때는 사용할 때가 있다.
contract SandwichFactory { struct Sandwich { string name; string status; } Sandwich[] sandwiches; function eatSandwich(uint _index) public { // Sandwich mySandwich = sandwiches[_index]; // ^ 꽤 간단해 보이나, 솔리디티는 여기서 // `storage`나 `memory`를 명시적으로 선언해야 한다는 경고 메시지를 발생한다. // 그러므로 `storage` 키워드를 활용하여 다음과 같이 선언해야 한다: Sandwich storage mySandwich = sandwiches[_index]; // ...이 경우, `mySandwich`는 저장된 `sandwiches[_index]`를 가리키는 포인터이다. // 그리고 mySandwich.status = "Eaten!"; // ...이 코드는 블록체인 상에서 `sandwiches[_index]`을 영구적으로 변경한다. // 단순히 복사를 하고자 한다면 `memory`를 이용하면 된다: Sandwich memory anotherSandwich = sandwiches[_index + 1]; // ...이 경우, `anotherSandwich`는 단순히 메모리에 데이터를 복사하는 것이 된다. // 그리고 anotherSandwich.status = "Eaten!"; // ...이 코드는 임시 변수인 `anotherSandwich`를 변경하는 것으로 // `sandwiches[_index + 1]`에는 아무런 영향을 끼치지 않는다. 그러나 다음과 같이 코드를 작성할 수 있다: sandwiches[_index + 1] = anotherSandwich; // ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다. } }
- internal, external
- internal, external을 선언하는 구문은 private, public을 선언하는 구문과 동일하다.
- internal
- 함수가 정의된 컨트랙트를 상속하는 컨트랙트에서도 접근이 가능하다.
- external
- 함수가 컨트랙트 바깥에서만 호출될 수 있고 컨트랙트 내의 다른 함수에 의해 호출될 수는 없다
- 솔리디티는 다수의 반환값을 처리할 수 있다.
function multipleReturns() internal returns(uint a, uint b, uint c) { return (1, 2, 3); } function processMultipleReturns() external { uint a; uint b; uint c; // 다음과 같이 다수 값을 할당한다: (a, b, c) = multipleReturns(); } // 혹은 단 하나의 값에만 관심이 있을 경우: function getLastReturnValue() external { uint c; // 다른 필드는 빈칸으로 놓기만 하면 된다: (,,c) = multipleReturns(); }
- 컨트랙트의 불변성
- 불변성
- 이더리움 컨트랙트에 배포하고 나면, 컨트랙트는 변하지 않는다(컨트랙트를 수정하거나 업데이트 할 수 없다)
- 컨트랙트에 배포한 최초의 코드는 항상 블록체인에 영구적으로 존재한다
- OpenZeppelin
- OpenZeppelin은, 안전하고 커뮤니티에서 검증받은 스마트 컨트랙트 라이브러리이다.
- OpenZeppelin의
Ownable
컨트랙트
/** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; } }
- 함수 제어자로, 제어자는 다른 함수들에 대한 접근을 제어하기 위해 사용되는 일종의 유사 함수이다.
- 보통 함수 실행 전의 요구사항 충족 여부를 확인하는데 사용된다.
- 함수 제어자는, 직접 호출할 수 없다.
onlyOwner()
의 경우 오직 컨트랙트 소유자만 해당 함수를 실행할 수 있도록 하였다._;
키워드Ownable
를 상속한 MyContract에서, onlyOwner()을 extend 하여 사용한다고 할때
contract MyContract is Ownable { event LaughManiacally(string laughter); // 아래 `onlyOwner`의 사용 방법을 잘 보게: function likeABoss() external onlyOwner { LaughManiacally("Muahahahaha"); } }
likeABoss
→ onlyOwner
의 _;
부분을 lifeABoss 함수로 되돌아가 해당 코드를 실행하게 된다.- 가스
- DApp의 함수를 실행할 때마다 가스라고 불리는 화폐를 지불하여야 한다.
- 각각의 연산은 소모되는 가스 비용이 있고, 그 연산을 수행하는데에 소모되는 컴퓨팅 자원의 양이 이 비용을 결정한다.
- 따라서, 이더리움에서 코드 최적화는 다른 프로그래밍 언어들에 비해 훨씬 더 중요하다.
- 구조체 안에서는 가능한 작은 크기의 정수 타입을 쓰는것이 좋다.
- 동일한 데이터 타입은 하나로 묶어놓는 것이 저장 공간을 최소화 시킨다.
uint c; uint32 a; uint32 b
; 가uint32 a; uint c; uint32 b;
보다 효율적
Share article