ERC-5202 블루프린트 컨트랙트 형식 (Blueprint contract format)
Final · 기타 · 2022-06-23 작성
컨트랙트를 만들어 내는 배포 코드(initcode)를 체인 위에 '블루프린트' 컨트랙트로 저장할 때 쓰는 표준 바이트코드 형식을 정합니다. 앞에 붙는 식별자 덕분에 직접 실행을 막고 도구가 쉽게 알아볼 수 있습니다.
왜 필요한가
컨트랙트를 만들어 내는 배포 코드(initcode)를 체인 위에 저장해 두면, 배포용 컨트랙트가 그 코드를 복사(EXTCODECOPY)한 뒤 생성 명령(CREATE, CREATE2)을 불러 새 컨트랙트를 만들면 되므로 배포용 컨트랙트의 크기를 줄일 수 있습니다. 그런데 정해진 형식이 없으면 두 가지 문제가 생깁니다. 첫째, 외부 도구와 인덱서가 어떤 컨트랙트가 일반 실행용인지 배포 코드 저장용인지 구분하기 어렵습니다. 바이트코드의 패턴을 추측해 찾는 방식은 정확성과 유지보수 모두에 문제가 있습니다. 둘째, 배포 코드를 바이트 그대로 저장하면 누구나 그 컨트랙트를 직접 호출해 배포 코드를 일반 코드처럼 실행할 수 있습니다. 저장소에 값을 쓰거나 외부 컨트랙트를 부르는 부작용이 생길 여지가 있습니다. 코드 안에 자기 삭제 명령(SELFDESTRUCT)이 있으면 블루프린트 자체가 지워져 그것에 의존하는 배포용 컨트랙트가 더는 동작하지 못할 수 있습니다.
심층 설명
프리앰블 기본 구조
블루프린트 컨트랙트는 반드시 0xFE71<version bits><length encoding bits> 프리앰블로 시작해야 합니다. 세 번째 바이트의 상위 6비트가 버전, 하위 2비트가 길이 인코딩입니다. 버전은 0(0b000000)에서 시작해 1씩 올라갑니다. 길이 인코딩 비트가 0b11이면 예약된 값으로, 세 번째 바이트를 연속 바이트(continuation byte)로 간주합니다. 즉 버전을 여러 바이트로 적어야 하는 경우를 위한 표시인데, 멀티바이트 버전의 구체적 인코딩은 이후 ERC로 미뤄 두었습니다. 또 블루프린트는 반드시 1바이트 이상의 initcode를 담아야 하며 빈 initcode는 흔한 실수를 막기 위해 금지됩니다.
데이터 구간과 길이 인코딩
버전 바이트와 initcode 사이에는 임의의 바이트(데이터나 코드)를 넣을 수 있습니다. 이 경우 프리앰블은 0xFE71<version bits><length encoding bits><length bytes><data> 형태가 됩니다. <length encoding bits>는 0에서 2 사이의 수로 <length bytes>가 몇 바이트인지 나타내고 <length bytes>는 데이터 길이를 빅엔디언으로 적은 값입니다. EIP-170이 컨트랙트 크기를 24KB로 제한하므로 길이를 적는 데 2바이트면 충분합니다. 그래서 쓸 일이 없는 0b11 값을 연속 시퀀스 표시로 예약할 수 있었습니다. 인덱서는 이 길이 정보를 보고 데이터 구간을 건너뛰면 됩니다.
왜 0xFE71로 시작하는가
블루프린트를 직접 CALL하는 것은 잘못된 사용이므로, 프리앰블은 INVALID(0xfe) 명령으로 시작해 STOP(0x00) 같은 부드러운 종료 대신 예외적 중단으로 실행을 끝냅니다. 0xFE로 시작하는 다른 컨트랙트와 구분하기 위해 매직 바이트를 하나 더 두는데, 0x71은 바이트 문자열 "blueprint"의 keccak256 해시 마지막 바이트(keccak256(b"blueprint")[-1])에서 임의로 고른 값입니다. 가스와 저장 공간을 아끼기 위해 프리앰블은 최대한 짧게 설계했습니다.
initcode 길이는 적지 않음
프리앰블에는 initcode 자체의 길이가 기본적으로 들어가지 않습니다. 공간을 차지하는 데다, 컨트랙트 전체 크기는 EXTCODESIZE로 쉽게 알 수 있어 프리앰블과 데이터 구간 길이를 빼면 initcode 길이를 바로 구할 수 있기 때문입니다. 참조 구현 parse_blueprint_preamble 함수는 이 규칙대로 바이트코드를 (버전, 데이터 구간, initcode)로 분해하고 형식에 맞지 않으면 예외를 냅니다.
예시로 보는 형식
데이터 구간이 없고 initcode가 STOP 한 바이트뿐인 가장 단순한 블루프린트는 0xFE710000입니다. 데이터로 0xFF를 7번 담은 예는 0xFE710107FFFFFFFFFFFFFF00인데, 구분해서 보면 0xFE71|01|07|FFFFFFFFFFFFFF|00입니다. 0x01은 버전 0에 길이 비트 1을 더한 값이고 0x07은 데이터 길이입니다. 데이터가 256바이트면 길이 비트가 2가 되어 0xFE71|02|0100|FF...FF|00처럼 길이를 2바이트(0x0100)로 적습니다.
EOF와의 관계
이더리움 객체 형식(EOF)에 initcode용 섹션 종류(3)를 추가하면 블루프린트를 지정하는 또 다른 방법이 될 수 있습니다. 하지만 EOF는 아직 EVM에 들어 있지 않고 이 표준은 EVM 변경 없이 지금 바로 블루프린트를 표준화하려는 것입니다. 나중에 섹션 종류 3이 EOF 명세에 들어가고 EOF가 EVM의 일부가 되면, EOF 검증이 이 ERC보다 훨씬 강한 보장을 제공하므로 이 ERC는 폐기된 것으로 간주합니다. 이 제안의 상태는 Final로 확정되어 있습니다.
쓸 때 주의할 점
- 우연히 같은 접두사로 시작하는 기존 컨트랙트: 이미 체인 위에 이 표준의 접두사와 똑같이 시작하는 컨트랙트가 있을 수 있습니다. 다만 인덱서는 소스 코드를 컴파일한 뒤 프리앰블을 앞에 붙여 검증하는 방식으로 쓸 것으로 보기 때문에 심각한 위험으로 보지 않습니다. 2022년 7월 8일 기준으로 이더리움 메인넷에 0xFE71로 시작하는 바이트코드를 가진 컨트랙트는 없습니다.