Solidity v0.6.0 Breaking Changes
Solidity v0.6.0 Breaking Changes
이 절은 Solidity 버전 0.6.0에서 도입된 주요 breaking change와, 그 변경의 이유, 그리고 영향을 받는 코드를 업데이트하는 방법을 강조해요. 전체 목록은 릴리스 changelog를 확인해요. v0.6.0에서는 virtual/override 키워드 도입, fallback 함수의 receive/fallback 분리, try/catch 도입 같은 중요한 변경이 있었어요.
출처: 문서
본문
이 절은 Solidity 버전 0.6.0에서 도입된 주요 breaking change와 그 뒤의 이유, 그리고 영향을 받는 코드를 업데이트하는 방법을 강조해요. 전체 목록은 릴리스 changelog를 확인해요.
컴파일러가 경고하지 않을 수 있는 변경 (Changes the Compiler Might not Warn About)
이 절은 컴파일러가 알려주지 않고 코드의 동작이 바뀔 수 있는 변경을 나열해요.
- 지수화의 결과 타입은 이제 기본(base)의 타입이에요. 예전에는 대칭 연산처럼 기본 타입과 지수 타입 둘 다 담을 수 있는 가장 작은 타입이었어요. 게다가 이제 지수화의 기본으로 부호 있는 타입이 허용돼요.
명시성 요구사항 (Explicitness Requirements)
이 절은 코드가 이제 더 명시적이어야 하지만 의미는 바뀌지 않는 변경을 나열해요. 대부분의 주제에 대해 컴파일러가 제안을 제공할 거예요.
- 함수는 이제
virtual키워드로 표시되거나 인터페이스에서 정의된 경우에만 오버라이드될 수 있어요. 인터페이스 밖의 구현 없는 함수는virtual로 표시해야 해요. 함수나 수정자를 오버라이드할 때는 새 키워드override를 사용해야 해요. 여러 병렬 기본에 정의된 함수나 수정자를 오버라이드할 때는 키워드 뒤 괄호에 모든 기본을 나열해야 해요:override(Base1, Base2)처럼요. - 배열의
length에 대한 멤버 접근은 이제 항상 읽기 전용이에요. 스토리지 배열에도 마찬가지예요. 더 이상length에 새 값을 할당해 스토리지 배열의 크기를 조정할 수 없어요. 대신push(),push(value)또는pop()을 사용하거나 전체 배열을 할당해요(물론 기존 내용을 덮어쓰겠죠). 그 이유는 거대한 스토리지 배열의 스토리지 충돌을 막기 위해서예요. - 새 키워드
abstract를 사용해 컨트랙트를 추상으로 표시할 수 있어요. 컨트랙트가 모든 함수를 구현하지 않으면 이 키워드를 사용해야 해요. 추상 컨트랙트는new연산자로 생성할 수 없고, 컴파일 중에 바이트코드를 생성할 수 없어요. - 라이브러리는 모든 함수를 구현해야 해요. 내부 함수만이 아니라요.
- 인라인 어셈블리에서 선언된 변수의 이름은 더 이상
_slot이나_offset으로 끝날 수 없어요. - 인라인 어셈블리의 변수 선언은 더 이상 인라인 어셈블리 블록 밖의 어떤 선언도 섀도잉할 수 없어요. 이름에 점이 포함되면 점까지의 접두사가 인라인 어셈블리 블록 밖의 어떤 선언과도 충돌해서는 안 돼요.
- 인라인 어셈블리에서 인자를 받지 않는 opcode는 이제 독립 식별자 대신 "내장 함수(built-in functions)"로 표현돼요. 그래서
gas는 이제gas()예요. - 상태 변수 섀도잉이 이제 허용되지 않아요. 파생 컨트랙트는 기본 중 어떤 것에도 같은 이름의 보이는 상태 변수가 없을 때만 상태 변수
x를 선언할 수 있어요.
의미·문법 변경 (Semantic and Syntactic Changes)
이 절은 코드를 수정해야 하고 그 후에 다른 일을 하는 변경을 나열해요.
- 외부 함수 타입에서
address로의 변환이 이제 허용되지 않아요. 대신 외부 함수 타입은 기존의selector멤버와 비슷한address라는 멤버를 가져요. - 동적 스토리지 배열의
push(value)함수는 더 이상 새 길이를 반환하지 않아요(아무것도 반환하지 않음). - 일반적으로 "fallback 함수"라고 불리는 이름 없는 함수가 두 개로 분리됐어요.
fallback키워드로 정의되는 새 fallback 함수와receive키워드로 정의되는 receive ether 함수로요. present할 때, call 데이터가 비어 있으면(ether를 받는지 여부와 무관하게) receive ether 함수가 호출돼요. 이 함수는 암시적으로payable이에요. 새 fallback 함수는 다른 어떤 함수도 일치하지 않을 때 호출돼요(receive ether 함수가 없으면 빈 call 데이터를 가진 호출도 포함). 이 함수를payable로 만들 수도 있고 아닐 수도 있어요.payable이 아니면 다른 어떤 함수와도 일치하지 않는 값을 보내는 트랜잭션은 revert해요. 업그레이드나 프록시 패턴을 따르는 경우가 아니라면 새 fallback 함수를 구현할 필요는 없어요.
새 기능 (New Features)
이 절은 Solidity 0.6.0 이전에는 불가능했거나 더 어려웠던 것들을 나열해요.
try/catch문을 사용하면 실패한 외부 호출에 반응할 수 있어요.struct와enum타입을 파일 수준에서 선언할 수 있어요.- 배열 슬라이스를 calldata 배열에 사용할 수 있어요. 예를 들어
abi.decode(msg.data[4:], (uint, uint))는 함수 호출 페이로드를 디코딩하는 저수준 방법이에요. - Natspec이 개발자 문서의 여러 반환 매개변수를 지원하며,
@param과 같은 이름 검사를 강제해요. - Yul과 인라인 어셈블리에 현재 함수를 종료하는
leave라는 새 문이 있어요. address에서address payable로의 변환이 이제payable(x)을 통해 가능해요. 여기서x는address타입이어야 해요.
인터페이스 변경 (Interface Changes)
이 절은 언어 자체와 무관하지만 컴파일러의 인터페이스에 영향을 주는 변경을 나열해요. 명령줄에서 컴파일러를 사용하는 방식, 프로그래밍 인터페이스 사용 방식, 또는 생성된 출력 분석에 영향을 줄 수 있어요.
새 에러 리포터 (New Error Reporter)
더 접근하기 쉬운 명령줄 에러 메시지를 만드는 것을 목표로 하는 새 에러 리포터가 도입됐어요. 기본으로 활성화되지만, --old-reporter를 전달하면 비권장된 옛 에러 리포터로 폴백해요.
메타데이터 해시 옵션 (Metadata Hash Options)
컴파일러는 이제 기본으로 메타데이터 파일의 IPFS 해시를 바이트코드 끝에 추가해요(자세한 내용은 컨트랙트 메타데이터 문서 참고). 0.6.0 이전에는 컴파일러가 기본으로 Swarm 해시를 추가했고, 이 동작을 계속 지원하기 위해 새 명령줄 옵션 --metadata-hash가 도입됐어요. --metadata-hash 명령줄 옵션의 값으로 ipfs 또는 swarm을 전달해 생성·추가할 해시를 선택할 수 있어요. none 값을 전달하면 해시를 완전히 제거해요. 이러한 변경은 표준 JSON 인터페이스를 통해서도 사용할 수 있고 컴파일러가 생성하는 메타데이터 JSON에 영향을 줘요. 메타데이터를 읽는 권장 방법은 마지막 두 바이트를 읽어 CBOR 인코딩의 길이를 결정하고, 메타데이터 절에서 설명한 대로 그 데이터 블록에 대해 적절한 디코딩을 수행하는 것이에요.
Yul 최적화 프로그램 (Yul Optimizer)
레거시 바이트코드 최적화 프로그램과 함께, --optimize로 컴파일러를 호출하면 Yul 최적화 프로그램이 이제 기본으로 활성화돼요. --no-optimize-yul로 컴파일러를 호출해 비활성화할 수 있어요. 이는 주로 ABI coder v2를 사용하는 코드에 영향을 줘요.
C API 변경 (C API Changes)
libsolc의 C API를 사용하는 클라이언트 코드가 이제 컴파일러가 사용하는 메모리를 제어해요. 이 변경을 일관되게 만들기 위해, solidity_free는 solidity_reset으로 개명됐고, solidity_alloc와 solidity_free 함수가 추가됐으며, solidity_compile은 이제 solidity_free()로 명시적으로 해제해야 하는 문자열을 반환해요.
코드를 업데이트하는 방법 (How to update your code)
이 절은 각 breaking change에 대해 이전 코드를 업데이트하는 방법을 상세히 설명해요.
f가 외부 함수 타입일 때address(f)를f.address로 바꿔요.function () external [payable] { ... }를receive() external payable { ... },fallback() external [payable] { ... }또는 둘 다로 교체해요. 가능하면 receive 함수만 사용하는 걸 선호해요.uint length = array.push(value)를array.push(value);로 바꿔요. 새 길이는array.length로 접근할 수 있어요.- 스토리지 배열의 길이를 늘리려면
array.length++를array.push()로 바꾸고, 줄이려면pop()을 사용해요. - 함수의
@dev문서에서 모든 이름 있는 반환 매개변수에 대해 첫 단어로 매개변수 이름을 포함하는@return항목을 정의해요. 예를 들어function f() public returns (uint value)처럼 정의된 함수f()와 그것을 주석 처리하는@dev가 있다면, 반환 매개변수를@return value The return value.처럼 문서화해요. 공지 사항이 튜플 반환 타입에 나타나는 순서대로 있는 한, 이름 있는 반환 매개변수와 이름 없는 반환 매개변수 문서를 섞을 수 있어요. - 인라인 어셈블리의 변수 선언에 대해 인라인 어셈블리 블록 밖의 선언과 충돌하지 않는 고유 식별자를 선택해요.
- 오버라이드하려는 모든 비-인터페이스 함수에
virtual을 추가해요. 인터페이스 밖의 모든 구현 없는 함수에virtual을 추가해요. 단일 상속의 경우 모든 오버라이딩 함수에override를 추가해요. 다중 상속의 경우override(A, B, ..)을 추가하고, 괄호 안에 오버라이드된 함수를 정의하는 모든 컨트랙트를 나열해요. 여러 기본이 같은 함수를 정의하면, 상속 컨트랙트는 모든 충돌하는 함수를 오버라이드해야 해요. - 인라인 어셈블리에서 인자를 달리 받지 않는 모든 opcode에
()을 추가해요. 예를 들어pc를pc()로,gas를gas()로 바꿔요.