‘이더리움 댑 개발’ 세미나 14. 솔리디티 공식 문서(버전 0.8.x) – Additional Material
Solidity v0.5.0 Breaking Changes
Semantic Only Changes
- 부호 있는 오른쪽 시프트는 0으로 반올림하는 대신 음의 무한대로 반올림 합니다.
- do while 루프에 continue문을 사용할 수 있습니다.
- call, delegatecall, staticcall은 주어진 단일 bytes 매개변수에 대해서 더 이상 pad하지 않습니다.
- pure와 view 함수는 EVM이 Byzantium 이상이면 CALL 대신 STATICCALL 연산자를 사용해 호출됩니다.
- EVM 수준에서 상태 변경을 허용하지 않습니다.
- ABI 인코더는 calldata로 부터 byte arrays와 strings을 적당하게 pad합니다.
- unpadded 인코딩을 위해서는 abi.encodePacked를 사용합니다.
- ABI 디코더는 calldata가 너무 짧거나 경계를 벗어난 경우 함수 시작부분에서 또는 abi.decode에서 revert 됩니다.
- Note that dirty higher order bits are still simply ignored.
- 외부 함수 호출에 모든 가용한 가스를 forward합니다.
- starting from Tangerine Whistle.
Semantic and Syntactic Changes
- call(), delegatecall(), staticcall(), keccak256(), sha256(), ripemd160()는 하나의 bytes 인자만을 받는다.
- 인자는 pad되지 않는다.
- call()를 call(“”)로, call(signature, a, b, c)를 call(abi.encodeWithSignature(signature, a, b, c))로 변경합니다.
- the last one only works for value types.
- keccak256(a, b, c)를 keccak256(abi.encodePacked(a, b, c))로 변경합니다.
- Even though it is not a breaking change, it is suggested that developers change x.call(bytes4(keccak256(“f(uint256)”)), a, b) to x.call(abi.encodeWithSignature(“f(uint256)”, a, b)).
- call, delegatecall, staticcall은 (bool, bytes memory)를 리턴합니다.
- bool success = otherContract.call(“f”)를 (bool success, bytes memory data) = otherContract.call(“f”)와 같이 변경합니다.
- 함수 로컬 변수에 대해 C99-style scoping rules을 적용한다.
- 변수들은 같은 스코프 또는 내포 스코프에서만 선언된 후에 사용할 수 있습니다.
- for 루프의 초기화 블록에 선언된 변수들은 루프내 어디에서나 접근할 수 있습니다.
Explicitness Requirements
- 모든 함수는 가시성을 명시적으로 작성해야 합니다.
- 변수도 명시적으로 가시성을 작성하도록 합니다.
- 구조체, 배열, 매핑 타입 변수는 명시적으로 데이터 위치를 작성해야 한다. 함수 매개변수와 리턴에 대해서도 마찬가지입니다.
- external 함수의 매개변수는 데이터 위치를 calldata로 명시해야 합니다.
- 컨트랙트 타입은 주소 타입의 멤버를 포함하지 않기 때문에 주소 타입 멤버를 사용해야 하는 경우 명시적으로 주소 타입으로 형변환해야 합니다.
- 예) c가 컨트랙트 라면 transfer 함수를 호출하려면 먼저 address(c)와 같이 먼저 주소 타입으로 변경한 후 address(c).transfer(…)와 같이 사용해야 합니다.
- 상속 관계를 갖는 컨트랙트가 아니라면 명시적 형변환이 허용되지 않습니다. 상속 관계를 갖지 않더라도 호환 가능한 경우에는 먼저 주소 타입으로 형변환해야 합니다.
- 예) A, B가 상속 관계를 갖지 않는 컨트랙트 타입이지만 호환 가능하다면 A(address(b))와 같이 할 수 있습니다.
- 주소 타입은 address 타입과 address payable로 구분됩니다. address payable 타입에 대해서만 transfer 함수가 제공됩니다.
- address payable은 직접적으로 address로 형변환 가능하지만 그 반대는 허용되지 않습니다. address를 address payable로 형변환하는 것은 uint160을 통해 가능합니다.
- 크기가 다른 bytesX와 uintY 사이의 형변환은 허용되지 않습니다. 형변환을 위해서는 먼저 크기를 맞추어야 합니다.
- 예) bytes4 a를 uint64로 형변환하려면 먼저 bytes8로 형변환해야 합니다.
- payable 함수가 아닌 경우 msg.value를 사용할 수 없습니다.
Deprecated Elements
Command Line and JSON Interfaces
- –formal이 제거되었습니다.
- A new formal verification module, the SMTChecker, is enabled via pragma experimental SMTChecker;.
- –julia는 –yul로 변경되었습니다.
- –clone-bin과 –combined-json 제거되었습니다.
- 빈 prefix로 remappings 허용하지 않습니다.
- JSON AST 필드 constant와 payable이 제거되었습니다. 해당 정보는 stateMutability 필드를 사용해 나타냅니다.
- JSON AST 필드 FunctionDefinition의 isConstructor이 kind 필드로 대체되었습니다.
- kind 값으로 “constructor”, “fallback”, “function”이 가능합니다.
- In unlinked binary hex files, library address placeholders are now the first 36 hex characters of the keccak256 hash of the fully qualified library name, surrounded by $…$. Previously, just the fully qualified library name was used. This reduces the chances of collisions, especially when long paths are used. Binary files now also contain a list of mappings from these placeholders to the fully qualified names.
Constructors
- constructor 키워드를 사용해서 정의해야 합니다.
- 가로 없이 base 생성자를 호출하는 것은 허용되지 않습니다.
- Specifying base constructor arguments multiple times in the same inheritance hierarchy is now disallowed.
- 잘못된 인자 갯수로 생성자 호출이 허용되지 않습니다.
- If you only want to specify an inheritance relation without giving arguments, do not provide parentheses at all.
Functions
- callcode 함수는 허용되지 않습니다. delegatecall을 사용합니다. 인라인 어셈블리를 통해서는 사용이 가능합니다.
- suicide는 허용되지 않습니다. selfdestruct를 사용합니다.
- sha3는 허용되지 않습니다. keccak256를 사용합니다.
- throw는 허용되지 않습니다. revert, require, assert를 사용합니다.
Conversions
- 십진수 리터럴에서 bytesXX로의 형 변환은 허용되지 않습니다.
- 크기가 다른 16진수 리터럴에서 bytesXX 형변환은 허용되지 않습니다.
Literals and Suffixes
- years는 사용하지 않습니다.
- 뒤에 숫자가 없는 .는 허용되지 않습니다.
- 16진수 숫자와 단위 구분자는 함께 사용할 수 없습니다.
- 예) 0x1e wei
- 0X는 허용되지 않습니다. 0x만 사용합니다.
Variables
- 빈 구조체는 허용되지 않습니다.
- var 키워드는 사용되지 않습니다.
- 구성 요소 갯수가 다른 튜플들 사이의 할당은 허용되지 않습니다.
- 컴파일 타임 상수가 아닌 상수는 사용하지 않습니다.
- 변수 객수가 다른 다중 변수 선언은 허용되지 않습니다.
- 초기화되지 않은 스토리지 변수 사용은 허용되지 않습니다.
- 빈 튜플은 허용되지 않습니다.
- 변수 및 구조체에서 순환 의존성을 감지하는 것은 재귀합니다.
- call()를 call(“”)로, call(signature, a, b, c)를 call(abi.encodeWithSignature(signature, a, b, c))로 변경합니다.
- the last one only works for value types.
- keccak256(a, b, c)를 keccak256(abi.encodePacked(a, b, c))로 변경합니다.
- Even though it is not a breaking change, it is suggested that developers change x.call(bytes4(keccak256(“f(uint256)”)), a, b) to x.call(abi.encodeWithSignature(“f(uint256)”, a, b)).
- call, delegatecall, staticcall은 (bool, bytes memory)를 리턴합니다.
- bool success = otherContract.call(“f”)를 (bool success, bytes memory data) = otherContract.call(“f”)와 같이 변경합니다.
- 함수 로컬 변수에 대해 C99-style scoping rules을 적용한다.
- 변수들은 같은 스코프 또는 내포 스코프에서만 선언된 후에 사용할 수 있습니다.
- for 루프의 초기화 블록에 선언된 변수들은 루프내 어디에서나 접근할 수 있습니다.
Explicitness Requirements
- 모든 함수는 가시성을 명시적으로 작성해야 합니다.
- 변수도 명시적으로 가시성을 작성하도록 합니다.
- 구조체, 배열, 매핑 타입 변수는 명시적으로 데이터 위치를 작성해야 한다. 함수 매개변수와 리턴에 대해서도 마찬가지입니다.
- external 함수의 매개변수는 데이터 위치를 calldata로 명시해야 합니다.
- 컨트랙트 타입은 주소 타입의 멤버를 포함하지 않기 때문에 주소 타입 멤버를 사용해야 하는 경우 명시적으로 주소 타입으로 형변환해야 합니다.
- 예) c가 컨트랙트 라면 transfer 함수를 호출하려면 먼저 address(c)와 같이 먼저 주소 타입으로 변경한 후 address(c).transfer(…)와 같이 사용해야 합니다.
- 상속 관계를 갖는 컨트랙트가 아니라면 명시적 형변환이 허용되지 않습니다. 상속 관계를 갖지 않더라도 호환 가능한 경우에는 먼저 주소 타입으로 형변환해야 합니다.
- 예) A, B가 상속 관계를 갖지 않는 컨트랙트 타입이지만 호환 가능하다면 A(address(b))와 같이 할 수 있습니다.
- 주소 타입은 address 타입과 address payable로 구분됩니다. address payable 타입에 대해서만 transfer 함수가 제공됩니다.
- address payable은 직접적으로 address로 형변환 가능하지만 그 반대는 허용되지 않습니다. address를 address payable로 형변환하는 것은 uint160을 통해 가능합니다.
- 크기가 다른 bytesX와 uintY 사이의 형변환은 허용되지 않습니다. 형변환을 위해서는 먼저 크기를 맞추어야 합니다.
- 예) bytes4 a를 uint64로 형변환하려면 먼저 bytes8로 형변환해야 합니다.
- payable 함수가 아닌 경우 msg.value를 사용할 수 없습니다.
Deprecated Elements
- Command Line and JSON Interfaces
- –formal이 제거되었습니다.
- A new formal verification module, the SMTChecker, is enabled via pragma experimental SMTChecker;.
- –julia는 –yul로 변경되었습니다.
- –clone-bin과 –combined-json 제거되었습니다.
- 빈 prefix로 remappings 허용하지 않습니다.
- JSON AST 필드 constant와 payable이 제거되었습니다. 해당 정보는 stateMutability 필드를 사용해 나타냅니다.
- JSON AST 필드 FunctionDefinition의 isConstructor이 kind 필드로 대체되었습니다.
- kind 값으로 “constructor”, “fallback”, “function”이 가능합니다.
- In unlinked binary hex files, library address placeholders are now the first 36 hex characters of the keccak256 hash of the fully qualified library name, surrounded by $…$. Previously, just the fully qualified library name was used. This reduces the chances of collisions, especially when long paths are used. Binary files now also contain a list of mappings from these placeholders to the fully qualified names.
Constructors
- constructor 키워드를 사용해서 정의해야 합니다.
- 가로 없이 base 생성자를 호출하는 것은 허용되지 않습니다.
- Specifying base constructor arguments multiple times in the same inheritance hierarchy is now disallowed.
- 잘못된 인자 갯수로 생성자 호출이 허용되지 않습니다.
- If you only want to specify an inheritance relation without giving arguments, do not provide parentheses at all.
Functions
- callcode 함수는 허용되지 않습니다. delegatecall을 사용합니다. 인라인 어셈블리를 통해서는 사용이 가능합니다.
- suicide는 허용되지 않습니다. selfdestruct를 사용합니다.
- sha3는 허용되지 않습니다. keccak256를 사용합니다.
- throw는 허용되지 않습니다. revert, require, assert를 사용합니다.
Conversions
- 십진수 리터럴에서 bytesXX로의 형 변환은 허용되지 않습니다.
- 크기가 다른 16진수 리터럴에서 bytesXX 형변환은 허용되지 않습니다.
Literals and Suffixes
- years는 사용하지 않습니다.
- 뒤에 숫자가 없는 .는 허용되지 않습니다.
- 16진수 숫자와 단위 구분자는 함께 사용할 수 없습니다.
- 예) 0x1e wei
- 0X는 허용되지 않습니다. 0x만 사용합니다.
Variables
- 빈 구조체는 허용되지 않습니다.
- var 키워드는 사용되지 않습니다.
- 구성 요소 갯수가 다른 튜플들 사이의 할당은 허용되지 않습니다.
- 컴파일 타임 상수가 아닌 상수는 사용하지 않습니다.
- 변수 객수가 다른 다중 변수 선언은 허용되지 않습니다.
- 초기화되지 않은 스토리지 변수 사용은 허용되지 않습니다.
- 빈 튜플은 허용되지 않습니다.
- 변수 및 구조체에서 순환 의존성을 감지하는 것은 재귀에서 256으로 제한됩니다.
- 길이가 0인 고정크기 배열은 허용되지 않는다.
Syntax
- 함수에 사용되는 constant는 더 이상 사용할 수 없습니다.
- 불린 표현식에는 산술 연산을 사용할 수 없습니다.
- unary + 연산자는 허용되지 않습니다.
- 리터럴은 명시적인 타입으로 형변화 없이 abi.encodePacked에 사용될 수 없습니다.
- 하나 이상의 리턴 값을 갖는 함수에 대해서 빈 리턴문은 허용되지 않습니다.
- The “loose assembly” syntax is now disallowed entirely, that is, jump labels, jumps and non-functional instructions cannot be used anymore. Use the new while, switch and if constructs instead.
- 구현 없는 함수는 modifier를 사용할 수 없습니다.
- 이름 있는 리턴 값을 가진 함수 타입은 허용되지 않습니다.
- Single statement variable declarations inside if/while/for bodies that are not blocks are now disallowed.
- 새로운 키워드
- calldata, constructor.
- 새로 예약된 키워드들
- alias, apply, auto, copyof, define, immutable, implements, macro, mutable, override, partial, promise, reference, sealed, sizeof, supports, typedef, unchecked.
Interoperability With Older Contracts
이전 버전으로 작성되어 배포된 컨트랙트에 대해 0.5.0 버전으로 작성된 인터페이스는 호환 가능하다.
- Note that we did not declare anotherOldFunction to be view, despite it being declared constant in the original contract. This is due to the fact that starting with Solidity v0.5.0 staticcall is used to call view functions. Prior to v0.5.0 the constant keyword was not enforced, so calling a function declared constant with staticcall may still revert, since the constant function may still attempt to modify storage. Consequently, when defining an interface for older contracts, you should only use view in place of constant in case you are absolutely sure that the function will work with staticcall.
Solidity v0.6.0 Breaking Changes
Changes the Compiler Might not Warn About
- The resulting type of an exponentiation is the type of the base. It used to be the smallest type that can hold both the type of the base and the type of the exponent, as with symmetric operations. Additionally, signed types are allowed for the base of the exponentiation.
Explicitness Requirements
- 함수는 인터페이스에 정의되어 있거나 virtual인 것에 대해서만 재정의할 수 있습니다. 재정의를 위해 override 키워드를 사용합니다.
- 다중 상속을 지원하기 때문에 다수의 base에 같은 signature로 작성된 경우에는 재정의 대상이 되는 상위 컨트랙트를 명시해야 한다.
- override(Base1, Base2)
- 다중 상속을 지원하기 때문에 다수의 base에 같은 signature로 작성된 경우에는 재정의 대상이 되는 상위 컨트랙트를 명시해야 한다.
- 배열 길이에 대한 접근은 항상 읽기 전용입니다. 스토리지 배열에 대해서도 마찬가지 입니다. 길이 값을 변경함으로 배열 크기를 조정할 수 없다는 것입니다.
- Use push(), push(value) or pop() instead, or assign a full array, which will of course overwrite the existing content. The reason behind this is to prevent storage collisions of gigantic storage arrays.
- 추상 컨트랙트에는 abstract 키워드를 사용한다. 컴파일시에 bytecode를 생성하지 않는다.
- Libraries have to implement all their functions, not only the internal ones.
- The names of variables declared in inline assembly may no longer end in _slot or _offset.
- 인라인 어셈블리 변수 선언은 더 이상 인라인 어셈블리 블록 외부에 선언된 것을 숨기지 않습니다. If the name contains a dot, its prefix up to the dot may not conflict with any declaration outside the inline assembly block.
- 상태 변수 shadowing은 허용되지 않는다.
- A derived contract can only declare a state variable x, if there is no visible state variable with the same name in any of its bases.
Semantic and Syntactic Changes
- Conversions from external function types to address are now disallowed. Instead external function types have a member called address, similar to the existing selector member.
- 동적 스토리지 배열의 push 함수는 아무것도 리턴하지 않습니다.
- fallback 함수는 fallback 키워드를 사용해서 정의하는 fallback 함수와 receive 키워드로 정의되는 receive 함수로 분리되었습니다.
- If present, the receive ether function is called whenever the call data is empty (whether or not ether is received). This function is implicitly payable.
- The new fallback function is called when no other function matches (if the receive ether function does not exist then this includes calls with empty call data). You can make this function payable or not. If it is not payable then transactions not matching any other function which send value will revert. You should only need to implement the new fallback function if you are following an upgrade or proxy pattern.
New Features
- The try/catch statement allows you to react on failed external calls.
- 구조체와 열거형은 파일 수준에서 선언될 수 있습니다.
- 배열 슬라이스가 calldata 배열에 사용될 수 있습니다.
- 예) abi.decode(msg.data[4:], (uint, uint)) is a low-level way to decode the function call payload.
- Natspec supports multiple return parameters in developer documentation, enforcing the same naming check as @param.
- Yul and Inline Assembly have a new statement called leave that exits the current function.
- address에서 address payable로 형 변환은 payable(x)로 가능합니다.
Interface Changes
- New Error Reporter
- A new error reporter was introduced, which aims at producing more accessible error messages on the command line. It is enabled by default, but passing –old-reporter falls back to the the deprecated old error reporter.
- Metadata Hash Options
- The compiler now appends the IPFS hash of the metadata file to the end of the bytecode by default (for details, see documentation on contract metadata). Before 0.6.0, the compiler appended the Swarm hash by default, and in order to still support this behaviour, the new command line option –metadata-hash was introduced. It allows you to select the hash to be produced and appended, by passing either ipfs or swarm as value to the –metadata-hash command line option. Passing the value none completely removes the hash.
- These changes can also be used via the Standard JSON Interface and effect the metadata JSON generated by the compiler.
- The recommended way to read the metadata is to read the last two bytes to determine the length of the CBOR encoding and perform a proper decoding on that data block as explained in the metadata section.
Yul Optimizer
Together with the legacy bytecode optimizer, the Yul optimizer is now enabled by default when you call the compiler with –optimize. It can be disabled by calling the compiler with –no-optimize-yul. This mostly affects code that uses ABI coder v2.
C API Changes
The client code that uses the C API of libsolc is now in control of the memory used by the compiler. To make this change consistent, solidity_free was renamed to solidity_reset, the functions solidity_alloc and solidity_free were added and solidity_compile now returns a string that must be explicitly freed via solidity_free().
How to update your code
- Change address(f) to f.address for f being of external function type.
- Replace function () external [payable] { … } by either receive() external payable { … }, fallback() external [payable] { … } or both. Prefer using a receive function only, whenever possible.
- Change uint length = array.push(value) to array.push(value);. The new length can be accessed via array.length.
- Change array.length++ to array.push() to increase, and use pop() to decrease the length of a storage array.
- For every named return parameter in a function’s @dev documentation define a @return entry which contains the parameter’s name as the first word. E.g. if you have function f() defined like function f() public returns (uint value) and a @dev annotating it, document its return parameters like so: @return value The return value.. You can mix named and un-named return parameters documentation so long as the notices are in the order they appear in the tuple return type.
- Choose unique identifiers for variable declarations in inline assembly that do not conflict with declarations outside the inline assembly block.
- Add virtual to every non-interface function you intend to override. Add virtual to all functions without implementation outside interfaces. For single inheritance, add override to every overriding function. For multiple inheritance, add override(A, B, ..), where you list all contracts that define the overridden function in the parentheses. When multiple bases define the same function, the inheriting contract must override all conflicting functions.
Solidity v0.7.0 Breaking Changes
Silent Changes of the Semantics
- Exponentiation and shifts of literals by non-literals (e.g. 1 << x or 2 ** x) will always use either the type uint256 (for non-negative literals) or int256 (for negative literals) to perform the operation. Previously, the operation was performed in the type of the shift amount / the exponent which can be misleading.
Changes to the Syntax
- 외부 함수와 컨트랙트 생성 호출에서, 이더와 가스는 x.f{gas: 10000, value: 2 ether}(arg1, arg2)와 같이 명시합니다.
- 글로벌 변수는 now는 deprecated 되었고, 대신 block.timestamp를 사용해야 합니다.
- NatSpec comments on variables are only allowed for public state variables and not for local or internal variables.
- gwei는 키워드로 사용됩니다.
- 문자열 리터럴은 escape sequences와 프린트 가능한 ASCII 문자열만 포함합니다.
- Unicode string literals are supported now to accommodate valid UTF-8 sequences. They are identified with the unicode prefix: unicode”Hello 😃”.
- State Mutability: The state mutability of functions can now be restricted during inheritance: Functions with default state mutability can be overridden by pure and view functions while view functions can be overridden by pure functions. At the same time, public state variables are considered view and even pure if they are constants.
- Inline Assembly
- Disallow . in user-defined function and variable names in inline assembly. It is still valid if you use Solidity in Yul-only mode.
- Slot and offset of storage pointer variable x are accessed via x.slot and x.offset instead of x_slot and x_offset.
Removal of Unused or Unsafe Features
- 매핑은 스토리지에서만 사용됩니다.
- Functions and Events
- 생성자에는 가시성이 필요 없습니다.
- Type Checker: Disallow virtual for library functions: Since libraries cannot be inherited from, library functions should not be virtual.
- Multiple events with the same name and parameter types in the same inheritance hierarchy are disallowed.
- using A for B only affects the contract it is mentioned in. Previously, the effect was inherited. Now, you have to repeat the using statement in all derived contracts that make use of the feature.
- Expressions
- Shifts by signed types are disallowed. Previously, shifts by negative amounts were allowed, but reverted at runtime.
- The finney and szabo denominations are removed. They are rarely used and do not make the actual amount readily visible. Instead, explicit values like 1e20 or the very common gwei can be used.
- Declarations
- var는 더이상 사용할 수 없습니다.
Interface Changes
- JSON AST: Mark hex string literals with kind: “hexString”.
- JSON AST: Members with value null are removed from JSON output.
- NatSpec: Constructors and functions have consistent userdoc output.
Solidity v0.8.0 Breaking Changes
Silent Changes of the Semantics
- 산술 연산은 언더플로어나 오버플로어시에 revert 됩니다. unchecked { … }를 사용하면 체크하지 않습니다.
- 오픈제플린의 SafeMath와 같은 라이브러리를 사용하지 않고 직접적으로 산술 연산을 해도 됩니다.
- ABI coder v2가 기본이 되었습니다. 이전 버전을 사용하려면 pragma abicoder v1;와 같이 선언해 줘야 합니다.
- 지수는 오른쪽에서 부터 적용됩니다.
- 예) a**b**c는 a**(b**c)와 같습니다. 이전 버전에서는 (a**b)**c와 같이 적용되었습니다.
- Failing assertions and other internal checks like division by zero or arithmetic overflow do not use the invalid opcode but instead the revert opcode. More specifically, they will use error data equal to a function call to Panic(uint256) with an error code specific to the circumstances. This will save gas on errors while it still allows static analysis tools to distinguish these situations from a revert on invalid input, like a failing require.
- If a byte array in storage is accessed whose length is encoded incorrectly, a panic is caused. A contract cannot get into this situation unless inline assembly is used to modify the raw representation of storage byte arrays.
- If constants are used in array length expressions, previous versions of Solidity would use arbitrary precision in all branches of the evaluation tree. Now, if constant variables are used as intermediate expressions, their values will be properly rounded in the same way as when they are used in run-time expressions.
- bytes1의 별칭으로 사용되던 byte 타입이 제거되었습니다.
New Restrictions
- There are new restrictions related to explicit conversions of literals. The previous behaviour in the following cases was likely ambiguous:
- Explicit conversions from negative literals and literals larger than type(uint160).max to address are disallowed.
- Explicit conversions between literals and an integer type T are only allowed if the literal lies between type(T).min and type(T).max. In particular, replace usages of uint(-1) with type(uint).max.
- Explicit conversions between literals and enums are only allowed if the literal can represent a value in the enum.
- Explicit conversions between literals and address type (e.g. address(literal)) have the type address instead of address payable. One can get a payable address type by using an explicit conversion, i.e., payable(literal).
- 주소 리터럴은 address 타입입니다.
- There are new restrictions on explicit type conversions. The conversion is only allowed when there is at most one change in sign, width or type-category (int, address, bytesNN, etc.). To perform multiple changes, use multiple conversions.
- address(uint) and uint(address): converting both type-category and width. Replace this by address(uint160(uint)) and uint(uint160(address)) respectively.
- payable(uint160), payable(bytes20) and payable(integer-literal): converting both type-category and state-mutability. Replace this by payable(address(uint160)), payable(address(bytes20)) and payable(address(integer-literal)) respectively. Note that payable(0) is valid and is an exception to the rule.
- int80(bytes10) and bytes10(int80): converting both type-category and sign. Replace this by int80(uint80(bytes10)) and bytes10(uint80(int80) respectively.
- Contract(uint): converting both type-category and width. Replace this by Contract(address(uint160(uint))).Let us use the notation T(S) to denote the explicit conversion T(x), where, T and S are types, and x is any arbitrary variable of type S. An example of such a disallowed conversion would be uint16(int8) since it changes both width (8 bits to 16 bits) and sign (signed integer to unsigned integer). In order to do the conversion, one has to go through an intermediate type. In the previous example, this would be uint16(uint8(int8)) or uint16(int16(int8)). Note that the two ways to convert will produce different results e.g., for -1. The following are some examples of conversions that are disallowed by this rule.
- These conversions were disallowed to avoid ambiguity. For example, in the expression uint16 x = uint16(int8(-1)), the value of x would depend on whether the sign or the width conversion was applied first.
- Function call options can only be given once, i.e. c.f{gas: 10000}{value: 1}() is invalid and has to be changed to c.f{gas: 10000, value: 1}().
- The global functions log0, log1, log2, log3 and log4 have been removed.
- These are low-level functions that were largely unused. Their behaviour can be accessed from inline assembly.
- enum definitions cannot contain more than 256 members. This will make it safe to assume that the underlying type in the ABI is always uint8.
- Declarations with the name this, super and _ are disallowed, with the exception of public functions and events. The exception is to make it possible to declare interfaces of contracts implemented in languages other than Solidity that do permit such function names.
- Remove support for the \b, \f, and \v escape sequences in code. They can still be inserted via hexadecimal escapes, e.g. \x08, \x0c, and \x0b, respectively.
- The global variables tx.origin and msg.sender have the type address instead of address payable. One can convert them into address payable by using an explicit conversion, i.e., payable(tx.origin) or payable(msg.sender).
- This change was done since the compiler cannot determine whether or not these addresses are payable or not, so it now requires an explicit conversion to make this requirement visible.
- Explicit conversion into address type always returns a non-payable address type. In particular, the following explicit conversions have the type address instead of address payable:
- address(u) where u is a variable of type uint160. One can convert u into the type address payable by using two explicit conversions, i.e., payable(address(u)).
- address(b) where b is a variable of type bytes20. One can convert b into the type address payable by using two explicit conversions, i.e., payable(address(b)).
- address(c) where c is a contract. Previously, the return type of this conversion depended on whether the contract can receive Ether (either by having a receive function or a payable fallback function). The conversion payable(c) has the type address payable and is only allowed when the contract c can receive Ether. In general, one can always convert c into the type address payable by using the following explicit conversion: payable(address(c)). Note that address(this) falls under the same category as address(c) and the same rules apply for it.
- The chainid builtin in inline assembly is now considered view instead of pure.
- address(uint) and uint(address): converting both type-category and width. Replace this by address(uint160(uint)) and uint(uint160(address)) respectively.
Interface Changes
- The output of –combined-json has changed: JSON fields abi, devdoc, userdoc and storage-layout are sub-objects now. Before 0.8.0 they used to be serialised as strings.
- The “legacy AST” has been removed (–ast-json on the commandline interface and legacyAST for standard JSON). Use the “compact AST” (–ast-compact–json resp. AST) as replacement.
- The old error reporter (–old-reporter) has been removed.
How to update your code
- Add intermediate explicit type conversions if required.
- Combine c.f{gas: 10000}{value: 1}() to c.f{gas: 10000, value: 1}().
- Change msg.sender.transfer(x) to payable(msg.sender).transfer(x) or use a stored variable of address payable type.
- Use inline assembly as a replacement for log0, …, log4.
Security Considerations
Pitfalls
- Private Information and Randomness
- private으로 설정했다고 해서 그 정보를 볼 수 없는 것은 아니다. 가시성은 다른 컨트랙트 코드에서 해당 컨트랙트 코드에 대한 접근성을 제어하는 것 뿐입니다.
- 온체인에서 보안에 안전한 랜덤 수를 만드는 것은 쉽지 않다. 랜덤 수 생성에 채굴자가 어느 정도 영향을 미칠 수 있습니다.
- 외부 컨트랙트 함수 호출
- 직접 작성해서 배포한 컨트랙트가 아닌 외부 컨트랙트를 사용하는 것은 어떤 이유로든 위험합니다.
- 호출 함수 내에 상태 변경 로직이 있는 경우 주의를 기울입니다. 특히 자산 상태를 변경하는 것이 있다면 더욱 주의를 기울입니다.
- 외부 컨트랙트를 호출해야만 한다면
- 컨트랙트 작성자를 신뢰할 수 있는지 판단해야 합니다.
- 배포된 컨트랙트 소스 코드 확인이 가능한지 확인합니다.
- 컨트랙트 소스 코드 감사 여부를 확인하고 감사 보고서를 확인합니다.
- 감사 받지 않은 컨트랙트라면 보안 위험성이 없는지 꼼꼼하게 소스 코드를 감사합니다.
- 업그레이드 가능성에 대해서 확인합니다.
- 업그레이드 가능하다면 어떤 절차에 따라서 어느 범위에서 업그레이드가 가능한지 확인합니다.
- Re-Entrancy
- 외부 컨트랙트 함수를 호출할 때 발생할 수 있는 위험입니다.
- 호출된 컨트랙트 함수에서 상호작용이 완료되기 전에 호출한 컨트랙트 함수를 다시 호출해서 재진입하는 경우 발생할 수 있습니다.
- 상호작용이 상태 변경 전에 일어나는 경우 상태가 변경되지 않은 상태에서 상호작용을 반복합니다.
- 재진입을 막기 위해서 Checks-Effects-Interactions 패턴을 사용한다.
- 상태 변경을 먼저 하고 상호작용(외부 컨트랙트 함수 호출)을 나중에 합니다.
- Gas Limit and Loops
- 반복 횟수가 예측되지 않는 경우 가스 한도에 주의해야 한다.
- Sending and Receiving Ether
- 누구나 특정 계정으로 이더를 보낼 수 있다. 컨트랙트 계정의 경우 이더를 받는 함수의 로직을 통해 전송을 막을 수는 있다. 하지만 selfdestruct와 같은 방식으로 이더를 전송하는 것은 막을 수 없다.
- payable 함수를 호출하지 않고 이더를 전송하면 receive 함수가 실행된다. receive 함수가 컨트랙트에 작성되지 않았다면 fallback 함수가 실행된다. fallback 함수도 없다면 예외가 발생한다. 이들 함수는 2300 가스만 전달되어 실행되기 때문에 스토리지를 변경하지 못한다.
- 다른 컨트랙트에 이더를 전송하기 위해 send, transfer, call 함수를 사용할 수 있다. transfer는 예외가 발생할 경우 revert하지만 send나 call은 false를 리턴한다. call은 남은 가스를 모두 사용해서 실행할 수 있고, transfer는 2300가스만을 사용함에 주의한다.
- 컨트랙트에서 특정 계정으로 이더를 보내야 한다면 직접 전송이 아닌 사용자의 요청에 의해 withdraw하는 방법이 가능한지를 알아본다. 가능하다면 직접 전송보다는 withdraw를 사용하도록 한다.
- Call Stack Depth
- 최대 콜 스택 크기는 1024로 제한된다는 점에 주의합니다.
- tx.origin
- 권한을 체크하기 위해 msg.sender를 사용한다. tx.origin은 결코 사용해서는 안 된다.
- Two’s Complement / Underflows / Overflows
- 솔리디티 8.0부터는 오버플로어와 언더플로어에 대해서 checked 모드와 unchecked 모드를 지원한다.
- 기본이 checked이기 때문에 오버플로어나 언더플로어 조건이 되었을 때 오버플로어나 언더플로어 예외가 발생합니다.
- 솔리디티 8.0부터는 정수의 산술 연산을 위해 SafeMath와 같은 라이브러리를 사용하지 않아도 됩니다.
- unchecked { … }와 같이 unchecked 모드를 활성화할 수 있다.
- 기본이 checked이기 때문에 오버플로어나 언더플로어 조건이 되었을 때 오버플로어나 언더플로어 예외가 발생합니다.
- In general, read about the limits of two’s complement representation, which even has some more special edge cases for signed numbers.
- Try to use require to limit the size of inputs to a reasonable range and use the SMT checker to find potential overflows.
- 솔리디티 8.0부터는 오버플로어와 언더플로어에 대해서 checked 모드와 unchecked 모드를 지원한다.
- Clearing Mappings
- mapping은 스토리지에만 저장되는 키-값 데이터 구조이다.
- does not keep track of the keys that were assigned a non-zero value.
- Because of that, cleaning a mapping without extra information about the written keys is not possible.
- If a mapping is used as the base type of a dynamic storage array, deleting or popping the array will have no effect over the mapping elements. The same happens, for example, if a mapping is used as the type of a member field of a struct that is the base type of a dynamic storage array. The mapping is also ignored in assignments of structs or arrays containing a mapping.
- If your mapping information must be deleted, consider using a library similar to iterable mapping, allowing you to traverse the keys and delete their values in the appropriate mapping.
- does not keep track of the keys that were assigned a non-zero value.
- mapping은 스토리지에만 저장되는 키-값 데이터 구조이다.
- Types that do not occupy the full 32 bytes might contain “dirty higher order bits”. This is especially important if you access msg.data – it poses a malleability risk: You can craft transactions that call a function f(uint8 x) with a raw byte argument of 0xff000001 and with 0x00000001. Both are fed to the contract and both will look like the number 1 as far as x is concerned, but msg.data will be different, so if you use keccak256(msg.data) for anything, you will get different results.
Recommendations
- Take Warnings Seriously
- 컨트랙트 배포 조건에 컴파일러 경고도 없어야 함을 명시합니다.
- 컴파일러는 가능한 최신 버전을 사용하도록 한다.
- Restrict the Amount of Ether
- 컨트랙트에 저장되는 이더나 토큰 수량에 제약하도록 한다.
- Keep it Small and Modular
- 컨트랙트는 가능한 작고 쉽게 이해할 수 있도록 해야 한다. 관련 없는 기능은 다른 컨트랙트나 라이브러리로 분리해서 모듈화한다.
- 로컬 변수 갯수를 제한하거나 함수 길이를 제한하도록 한다.
- 함수의 의도와 코드가 하는 일을 문서화하라.
- Use the Checks-Effects-Interactions Pattern
- Most functions will first perform some checks (who called the function, are the arguments in range, did they send enough Ether, does the person have tokens, etc.). These checks should be done first.
- As the second step, if all checks passed, effects to the state variables of the current contract should be made. Interaction with other contracts should be the very last step in any function.
- Early contracts delayed some effects and waited for external function calls to return in a non-error state. This is often a serious mistake because of the re-entrancy problem explained above.
- Note that, also, calls to known contracts might in turn cause calls to unknown contracts, so it is probably better to just always apply this pattern.
- 컨트랙트는 가능한 작고 쉽게 이해할 수 있도록 해야 한다. 관련 없는 기능은 다른 컨트랙트나 라이브러리로 분리해서 모듈화한다.
- Include a Fail-Safe Mode
- While making your system fully decentralised will remove any intermediary, it might be a good idea, especially for new code, to include some kind of fail-safe mechanism:
- You can add a function in your smart contract that performs some self-checks like “Has any Ether leaked?”, “Is the sum of the tokens equal to the balance of the contract?” or similar things. Keep in mind that you cannot use too much gas for that, so help through off-chain computations might be needed there.
- If the self-check fails, the contract automatically switches into some kind of “failsafe” mode, which, for example, disables most of the features, hands over control to a fixed and trusted third party or just converts the contract into a simple “give me back my money” contract.
- While making your system fully decentralised will remove any intermediary, it might be a good idea, especially for new code, to include some kind of fail-safe mechanism:
- Ask for Peer Review
- The more people examine a piece of code, the more issues are found. Asking people to review your code also helps as a cross-check to find out whether your code is easy to understand – a very important criterion for good smart contracts.
- 테스트 주도 개발을 하도록 합니다.
- 단위 테스트와 인수 테스트를 자동화합니다.
- 인수 테스트가 명세 역할을 하도록 하고 인수 테스트가 formal verification이 되도록 합니다.
- 단위 테스트와 인수 테스트를 자동화합니다.
Formal Verification
Yul
Yul (previously also called JULIA or IULIA) is an intermediate language that can be compiled to bytecode for different backends.
Support for EVM 1.0, EVM 1.5 and Ewasm is planned, and it is designed to be a usable common denominator of all three platforms. It can already be used in stand-alone mode and for “inline assembly” inside Solidity and there is an experimental implementation of the Solidity compiler that uses Yul as an intermediate language. Yul is a good target for high-level optimisation stages that can benefit all target platforms equally.
Style Guide
This guide is intended to provide coding conventions for writing solidity code.
The structure and many of the recommendations within this style guide were taken from python’s pep8 style guide.
The goal of this guide is not to be the right way or the best way to write solidity code. The goal of this guide is consistency. A quote from python’s pep8 captures this concept well.