‘이더리움 댑 개발’ 세미나 12. 솔리디티 공식 문서(버전 0.8.x) – Language Description 2
이번 세미나는 솔리디티 공식 문서(버전 0.8.x)의 Language Description에서 다음과 같은 부분을 다룹니다.
- Structure of a Contract
- Contracts
- Inline Assembly
Contracts
컨트랙트는 객체지향 언어의 클래스에 해당합니다. 컨트랙트는 상태 변수, 함수, function modifier, 이벤트, 구조체, 열거형 선언을 포함할 수 있습니다. 컨트랙트는 다른 컨트랙트를 상속할 수 있습니다. 특별한 종류의 컨트랙트인 라이브러리와 인터페이스가 있습니다.
- 상태 변수
- 멤버 변수(속성)에 해당합니다.
- 상태 변수는 storage에 영속적으로 저장된다는 점에서 속성과 다릅니다.
- 멤버 변수(속성)에 해당합니다.
- 함수
- 멤버 함수(메소드)에 해당합니다.
- 일반적으로 컨트랙트 내에 정의되지만 컨트랙트 밖에서도 정의될 수 있습니다.
- external 가시성(visibility)를 지원합니다.
- 함수 외부에서만 접근 가능(호출 가능한) 합니다.
- 다수의 타입 값들을 리턴할 수 있습니다.
- 함수 modifiers
- modifiers는 선언적인 방식으로 함수의 수행 방법을 제약하기 위해 사용합니다. 오버로딩은 가능하지 않고 오버라이딩(재정의)만 가능합니다.
- 이벤트
- 이벤트는 EVM 로깅 기능과 편리하게 인터페이스 할 수 있도록 합니다.
- 구조체
- 구조체는 다수의 변수들을 그루핑할 수 있는 사용자 정의 타입입니다.
- 열거형
- 열거형은 상수 값들의 제한된 집합을 가진 사용자 정의 타입입니다.
- 다른 컨트랙트 함수를 호출하는 것은 EVM 함수 호출입니다.
- 별도의 EVM에서 독립적으로 수행되는 함수 호출로 실행됩니다.
- 컨텍스트가 스위칭 된다는 것
- 별도의 EVM에서 독립적으로 수행되는 함수 호출로 실행됩니다.
- 이더리움에는 타임 스케줄링에 의한 트리거 개념이 없습니다.
- 누군가 외부에서 이에 대한 함수 호출을 실행해야 합니다.
Creating Contracts
- 컨트랙트는 컨트랙트 생성 트랜잭션에 의해 또는 다른 컨트랙트 내에서 생성될 수 있습니다.
- 컨트랙트가 생성될 때 생성자가 실행됩니다.
- 생성자는 한 번만 실행되고, 선택적으로 작성될 수 있지만 하나만 가능합니다. 재정의할 수 없습니다.
- 생성자는 함수 이름이 없이 constructor라는 키워드를 사용해서 정의합니다.
- 생성자가 실행된 후에 컨트랙트의 최종 코드가 생성되고, 이 코드가 블록체인에 저장됩니다.
- 코드에는 public, external 함수들과 이들 함수들의 호출을 통해 도달할 수 있는 함수들이 포함합니다.
- 최종 코드에는 생성자 코드와 생성자에서만 호출되는 함수는 포함되지 않습니다.
- Internally, constructor arguments are passed ABI encoded after the code of the contract itself.
- 0.7.x 부터는 생성자에 가시성을 직접 작성하지 않도록 하고 있습니다. 컴파일러가 상황에 맞춰 public이나 internal로 작성합니다.
- 기본이 public
- 추상 컨트랙트의 경우 internal이 되어야 하는데
- 추상 컨트랙트의 경우 abstract로 선언해야 하고
- 컴파일러는 abstract 컨트랙트에 대해 internal로 작성
- 추상 컨트랙트의 경우 abstract로 선언해야 하고
- 컨트랙트를 배포할 때 생성자가 실행되기 때문에
- 컨트랙트 배포를 ‘실행 코드를 배포한다’로 생각하지 않아야 합니다.
- ‘컨트랙트의 인스턴스를 생성하고 그 인스턴스의 실행코드를 배포’한다고 생각해야 합니다.
Visibility and Getters
- visibility
- private, internal, public, external
- 컨트랙트 내부에서 external 함수를 호출할 때는 this를 사용해야 합니다.
- this.f()
- external 함수는 calldata로 부터 메모리로 복사되지 않기 때문에 큰 배열 데이터를 받아야 하는 경우 더 효율적일 수 있습니다.
- 컨트랙트 내부에서 public 함수를 호출하는 것은 외부에서 external 함수를 호출하는 것과 같은 방법으로 동작합니다.
- EVM 함수 호출(message call)에 해당합니다.
- 상태 변수는 external이 될 수 없습니다.
- 상태 변수가 public이면 컴파일러가 getter 함수를 자동 생성합니다.
- external 가시성을 가집니다.
- 상태 변수가 배열이나 매핑인 경우 단일 요소를 리턴하는 getter 함수를 자동 생성합니다.
- 인자로 인덱스가 전달됩니다.
- 가시성은 상태 변수의 타입 뒤에, 함수의 매개변수리스트와 returns 사이에 명시됩니다.
함수 Modifiers
- modifier는 이름 그대로 함수의 행위를 변경하기 위해 사용합니다.
- 대부분 함수 실행(실행 전, 실행 중, 실행 후)에 대한 제약 사항을 체크합니다.
- 상속 가능한 것으로, 상위 타입 컨트랙트에서 virtual로 선언된 경우 재정의할 수 있습니다.
- 함수에 다수의 modifier를 적용할 때는 공백-분리 리스트로 작성할 수 있습니다. 순서대로 실행됩니다.
- modifier를 정의할 때는 함수 본문이 어디서 실행되어야 하는지를 알려줘야 합니다. 함수 본문 실행 부분은 “_”로 작성합니다.
Constant and Immutable State Variables
- 상태 변수는 constant 또는 immutable로 선언할 수 있습니다.
- constant는 컴파일시에 값이 결정되고, immutable은 생성 시에 결정(생성자에서 설정)됩니다. 값이 결정되고 나면 둘 다 변경할 수 없습니다.
- constant는 파일 수준에서 정의할 수도 있습니다. 별도 파일에 constant만을 모아서 작성할 수 있다는 것입니다.
- Any expression that accesses storage, blockchain data or execution data or makes calls to external contracts is disallowed. Expressions that might have a side-effect on memory allocation are allowed, but those that might have a side-effect on other memory objects are not. The built-in functions keccak256, sha256, ripemd160, ecrecover, addmod and mulmod are allowed (even though, with the exception of keccak256, they do call external contracts).
- 컴파일러는 이들에 대해서 직접 값으로 대체하기 때문에 저장소를 할당할 필요가 없기 때문에 효율적입니다.
- 현재 값 타입과 string에만 사용할 수 있습니다. string은 constant만 가능합니다.
Functions
함수는 컨트랙트 내부 또는 외부에 정의될 수 있습니다.
- 컨트랙트 밖에 정의된 함수를 free functions라고 부릅니다.
- 암시적으로 항상 internal 가시성을 가지고, 컨트랙트에서 자유롭게 호출할 수 있습니다.
- 함수를 호출하는 컨트랙트 내에 이 함수 코드가 포함됩니다.
- 암시적으로 항상 internal 가시성을 가지고, 컨트랙트에서 자유롭게 호출할 수 있습니다.
- 함수는 입력으로 매개변수들을 갖고 출력으로 리턴 변수들을 갖습니다.
- returns 뒤에 리턴 변수들을 선언합니다.
- 변수 이름은 생략할 수 있습니다.
- 로컬 변수 처럼 초기화 되고 사용됩니다.
- return 문을 사용해서 직접적으로 리턴할 수도 있고, 리턴 변수 값을 설정해서 암시적으로 리턴할 수도 있습니다.
- returns 뒤에 리턴 변수들을 선언합니다.
- 함수 실행에 의해 상태 변경이 일어나지 않는 경우에는 view로 선언해야 합니다.
- 컴파일러의 EVM이 Byzantium 이상이면 view 함수에 대해서 STATICCALL이 호출됩니다.
- STATICCALL은 상태를 수정하지 않음을 보장합니다.
- 라이브러리 view 함수에 대해서는 DELEGATECALL이 사용되기 때문에 이 함수 호출이 상태를 수정하지 않는다는 것을 보장할 수는 없습니다.
- This should not impact security negatively because library code is usually known at compile-time and the static checker performs compile-time checks.
- 다음은 상태 변경을 일으키는 것들로 간주됩니다.
- Writing to state variables.
- Emitting events.
- Creating other contracts.
- Using selfdestruct.
- Sending Ether via calls.
- Calling any function not marked view or pure.
- Using low-level calls.
- Using inline assembly that contains certain opcodes.
- 컴파일러의 EVM이 Byzantium 이상이면 view 함수에 대해서 STATICCALL이 호출됩니다.
- 함수 실행에 의해 상태 변경이 일어나지도 않고, 상태 변수 값을 읽지도 않는다면 pure로 선언해야 합니다.
- 컴파일러의 EVM이 Byzantium이상이면 pure 함수에 대해서 STATICCALL이 호출됩니다.
- STATICCALL은 상태를 수정하지 않음 만을 보장합니다.
- 상태 값을 읽지 않는 다는 것은 보장하지 못합니다.
- STATICCALL은 상태를 수정하지 않음 만을 보장합니다.
- 다음과 같은 것들은 상태를 읽는 것들로 간주됩니다.
- Reading from state variables.
- Accessing address(this).balance or <address>.balance.
- Accessing any of the members of block, tx, msg (with the exception of msg.sig and msg.data).
- Calling any function not marked pure.
- Using inline assembly that contains certain opcodes.
- 컴파일러의 EVM이 Byzantium이상이면 pure 함수에 대해서 STATICCALL이 호출됩니다.
- 컨트랙트는 이더를 받는 함수 receive를 선택적으로 하나 만 가질 수 있습니다.
- receive() external payable { … }
- function 키워드를 사용하지 않습니다.
- external payable로 명시됩니다.
- 매개 변수를 갖지 않고 함수 명은 receive여야 합니다.
- 빈 calldata를 가지고 컨트랙트가 호출될 때 실행됩니다.
- send나 transfer로 이더를 보낼 때 이 함수가 실행됩니다.
- 2300 가스만 사용할 수 있기 때문에 기본적인 로깅을 제외한 다른 연산을 할 여력이 거의 없습니다. 다음과 같은 연산들은 2300가스 이상을 소모하도록 하는 것들입니다.
- Writing to storage
- Creating a contract
- Calling an external function which consumes a large amount of gas
- Sending Ether
- receive 함수가 없다면 payable fallback 함수가 있어야 이더를 받을 수 있습니다.
- receive 함수도 없고 payable fallback 함수도 없다면 이더를 받을 수 없습니다.
- A contract without a receive Ether function can receive Ether as a recipient of a coinbase transaction (aka miner block reward) or as a destination of a selfdestruct.
- receive() external payable { … }
- 컨트랙트는 fallback 함수를 선택적으로 하나 만 가질 수 있습니다.
- fallback 함수는 컨트랙트 함수에 없는 함수를 호출했을 때 실행되거나 컨트랙트에 receive 함수가 없는 데 data 없이 호출되었을 때 실행됩니다.
- fallback () external [payable]
- function 키워드도 없고 이름도 없습니다. external 가시성을 갖고 payable을 선택적으로 가질 수 있습니다.
- 이더를 받아야 한다면 payable을 명시합니다.
- function 키워드도 없고 이름도 없습니다. external 가시성을 갖고 payable을 선택적으로 가질 수 있습니다.
- 입력과 리턴이 있는 fallback 함수를 가질 수 있습니다.
- fallback (bytes calldata _input) external [payable] returns (bytes memory _output)
- virtual이 될 수 있고, 재정의 가능하고, 함수 modifier를 가질 수 있습니다.
- receive 함수와 같이 2300 가스 만을 사용할 수 있습니다.
- 이더를 받는 용도로는 receive를 사용하고, fallback 함수는 컨트랙트에 없는 함수를 호출할 경우에 사용하는 것을 권장합니다.
Inheritance
- 상속을 지원합니다.
- is 키워드를 사용합니다.
- 다중 상속이 가능합니다.
- 상위 컨트랙트 코드에 접근할 때는 컨트랙트 이름을 직접 사용하거나 super 키워드를 사용할 수 있습니다.
- 상위 컨트랙트에서 상속 받은 상태 변수와 같은 이름으로 상태 변수를 선언할 수 없습니다.
- State variable shadowing is considered as an error.
- 다형성을 지원하기 위해 virtual과 override 키워드를 지원합니다.
- 다중 상속이 가능하기 때문에 override의 대상이 여럿 될 수 있습니다.
- 예) function foo() public override(Base1, Base2) {}
- public 상태 변수의 경우 자동으로 getter가 생성되기 때문에 재정의를 명시할 수 있어야 합니다.
- 예) uint public override f;
- Function modifiers도 재정의 가능합니다.
- 생성자에 매개변수가 선언되어 있다면, 하위 컨트랙트에서 매개변수에 대한 인자를 전달해야 합니다.
- contract PriceFeed is , Named(“GoldFeed”) { … }
- constructor(uint _y) Base(_y * _y) {}
- 추상 컨트랙트의 경우 상위 컨트랙트의 인자를 전달하지 않아도 됩니다.
- 추상 컨트랙트는 이름 그대로 생성할 만큼 구체화 되어 있지 않기 때문에 직접적으로 생성할 수 없는 컨트랙트입니다. 대부분 추상 컨트랙트는 적어도 하나 이상의 구현되지 않은 함수를 갖습니다.
- The constructors will always be executed in the linearized order, regardless of the order in which their arguments are provided in the inheriting contract’s constructor.
Interfaces
- 인터페이스는 추상 컨트랙트와 비슷한데 다음과 같은 제약들이 추가됩니다.
- 다른 컨트랙트를 상속할 수 없습니다. 다른 인터페이스는 상속할 수 있습니다.
- 모든 선언된 함수는 external이어야 합니다.
- 생성자를 선언할 수 없습니다.
- 상태 변수를 선언할 수 없습니다.
- interface 키워드를 사용합니다.
- 컨트랙트가 인터페이스를 상속할 수 있습니다.
- 객체지향 언어에서는 인터페이스를 실현 또는 구현한다고 합니다.
- 인터페이스의 모든 함수는 암시적으로 virtual이기 때문에 컨트랙트에서 override로 재정의 해야 합니다.
Libraries
- 라이브러리는 재사용을 위한 것입니다.
- 공통의 기능을 배포해 두고 다수의 컨트랙트에서 재사용하는 것입니다.
- 배포된 기능을 재 사용하기 위해서
- DELEGATECALL을 사용합니다.
- stateless로 작성해야 합니다.
- destroy할 수 없습니다.
- 이더를 받을 수 없습니다.
- 컨트랙트가 라이브러리 코드에 의존할 때는 상위 컨트랙트와 유사하게 동작합니다.
- 컴파일될 때 라이브러리 코드들은 모두 라이브러리를 참조하는 컨트랙트에 포함되고, DELEGATECALL은 JUMP로 대체됩니다.
- 라이브러리 함수의 첫 번째 인자로 호출한 객체를 받도록 할 수 있습니다. 이것을 attach 한다고 합니다.
- 라이브러리 함수의 첫 번째 매개변수 이름은 일반적으로 self를 사용합니다.
-
12struct Data { mapping(uint => bool) flags; }library Set { function insert(Data storage self, uint value) public returns (bool) { ... }
-
- 컨트랙트에서 특정 타입에 라이브러리 함수를 attach하기 위해서 using for를 사용합니다.
- 예) using Set for Data;
- Data 타입에 Set 함수들을 attach
-
12345678contract C {using Set for Data;Data knownValues;function register(uint value) public {require(knownValues.insert(value));}}
-
- Data 타입에 Set 함수들을 attach
- 예) using Set for Data;
- 라이브러리 함수의 첫 번째 매개변수 이름은 일반적으로 self를 사용합니다.
Events
- 이벤트는 EVM 로깅 기능을 위한 것입니다.
- Solidity events give an abstraction on top of the EVM’s logging functionality.
- 애플리케이션은 이더리움 클라이언트의 RPC 인터페이스를 통해서 이벤트를 구독할 수 있습니다.
- 이벤트는 상속가능한 컨트랙트 멤버입니다.
- 이더리움 블록체인에는 특별한 데이터 구조로 트랜잭션 로그가 있는데, 이벤트가 호출될 때 인자들은 여기에 저장됩니다. 로그는 컨트랙트 주소와 연결되어 있고, 블록체인에 통합되며, 블록에 액세스할 수 있는 한 그대로 유지됩니다. 로그와 이벤트 데이터는 컨트랙트에서는 접근할 수 없습니다.
- 로그에 대한 머클 증명을 요청할 수 있습니다. 따라서 외부에서 증명을 제공하면, 로그가 블록체인 내에 실제 존재하는지를 확인할 수 있습니다.
- 이벤트 매개변수는 세 개까지 indexed를 명시할 수 있습니다. 매개변수가 indexed되면, 이벤트 발생 시에 전달된 인자는 로그의 데이터 부분 대신 topics라는 특별한 데이터 구조에 추가됩니다.
- indexed가 아닌 인자는 ABI-encoded되어 로그의 데이터 부분에 저장됩니다.
- 이벤트 시그니처 또한 topic이 됩니다.
- 3개의 인덱스 포함 topic은 4개까지 가능합니다.
- anonymous로 이벤트를 선언하면 이벤트 시그니처는 topic이 되지 않습니다.
- 배포와 호출에 더 싸다는 장점이 있습니다.
- 예) event Start(uint start, uint middle, uint end) anonymous;
- 인덱스를 갖는 인자로 배열(string이나 bytes 포함)이 사용되는 경우 topic으로 이것의 Keccak-256 해시 값이 저장됩니다.
- this is because a topic can only hold a single word (32 bytes).
- topics은 어떤 이벤트에 대해 일련의 블록들을 필터링 함으로 이벤트를 검색할 수 있도록 합니다. 컨트랙트 주소로도 이벤트를 필터링할 수 있습니다.
- For example, the code below uses the web3.js subscribe(“logs”) method to filter logs that match a topic with a certain address value:
-
123456789101112131415var options = {fromBlock: 0,address: web3.eth.defaultAccount,topics: ["0x0000000000000000000000000000000000000000000000000000000000000000", null, null]};web3.eth.subscribe('logs', options, function (error, result) {if (!error)console.log(result);}).on("data", function (log) {console.log(log);}).on("changed", function (log) {});
- Additional Resources for Understanding Events
Javascript documentation
Example usage of events
How to access them in js
-
- For example, the code below uses the web3.js subscribe(“logs”) method to filter logs that match a topic with a certain address value:
- EVM은 4개의 연산 코드(log0, log1, log2, log3, log4)를 갖습니다. 솔리디티는 이들 연산 코드에 해당하는 4개의 함수를 갖습니다.
- 각각의 함수는 ‘log 뒤의 숫자 + 1’만큼 byte32 타입의 매개변수를 갖습니다. 첫 번째 인자는 로그의 데이터 부분으로 사용되고 나머지 부분은 topics로 사용됩니다. 결국 log 뒤의 숫자는 topics 갯수를 나타냅니다. topics는 총 4개를 갖기 때문에 logo4까지 있어야 합니다.
- 첫 번째 topic은 이벤트 signature 해시입니다.
- 예) keccak256(“Deposit(address,bytes32,uint256)”)
- 첫 번째 topic은 이벤트 signature 해시입니다.
- 각각의 함수는 ‘log 뒤의 숫자 + 1’만큼 byte32 타입의 매개변수를 갖습니다. 첫 번째 인자는 로그의 데이터 부분으로 사용되고 나머지 부분은 topics로 사용됩니다. 결국 log 뒤의 숫자는 topics 갯수를 나타냅니다. topics는 총 4개를 갖기 때문에 logo4까지 있어야 합니다.