Solidity 바이너리란 무엇인가?
작성자 Alchemy
스마트 컨트랙트를 통해 Ethereum 블록체인에 게시되는 원시 데이터는 바이트코드, 즉 긴 16진수 문자열입니다. 개발자는 사람이 읽을 수 있는 Solidity 코드로 스마트 컨트랙트를 작성하고 읽지만, 실제로 블록체인에 게시되는 것은 그 텍스트가 아닙니다.
마찬가지로, 스마트 컨트랙트에 대한 모든 "호출", 즉 스마트 컨트랙트가 공개한 외부에서 볼 수 있는 함수에 대한 요청은 원시 바이트코드, 즉 "바이너리" 형태로 이루어집니다.
다음과 같은 (Solidity로 인코딩된) 구조를 가진 스마트 컨트랙트가 Ethereum mainnet에 업로드되었다고 합시다:
사용자가 함수 baz를 매개변수 69와 true로 호출하려 한다고 합시다.
이 요청이 실제로 바이트코드로 전송되면 다음과 같은 모습입니다:
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450...
읽기 꽤 어렵죠?
이 글에서는 Ethereum Virtual Machine이 모든 것을 바이트코드로 인코딩하는 이유를 살펴보고, ABI가 무엇이며 어떻게 사용하는지 알아보고, 바이트코드를 다시 사람이 읽을 수 있는 Solidity로 디컴파일하는 몇 가지 기본 도구를 소개합니다.
참고: 이 글의 예시는 공식 Solidity ABI 문서에서 가져온 것입니다.
Solidity는 왜 스마트 컨트랙트를 바이너리로 인코딩할까?
Ethereum 블록체인에 데이터를 저장하는 것은 매우 비용이 많이 들고, 업로드되는 모든 바이트는 블록체인의 모든 풀 노드에 복제되어야 하므로, Solidity 코드를 업로드하는 것보다 원시 바이트코드를 쓰고 읽는 것이 비용 효율적입니다.
사람이 읽을 수 있는 코드를 파싱하고 저장하는 것은 데이터 비용이 자릿수 단위로 더 커질 수 있는데, mainnet에서 이미 스마트 컨트랙트 하나에 수천 달러가 들 수 있다는 점을 고려하면 이는 문제가 됩니다.
Solidity ABI란 무엇이며, 왜 스마트 컨트랙트를 읽으려면 ABI가 필요할까?
스마트 컨트랙트는 게시될 때 Ethereum에 게시되기 전에 자동으로 바이트코드로 트랜스파일됩니다. 하지만 일단 네트워크에 게시된 후에는, 특정 개인이 해당 스마트 컨트랙트와 어떻게 상호작용해야 하는지 어떻게 알 수 있을까요? 긴 바이트코드 문자열을 보고 어떤 함수를 호출할 수 있는지 이해하는 것은 거의 불가능합니다.
Application Binary Interface, 즉 ABI가 그 해답입니다.
ABI는 특정 스마트 컨트랙트에 대해 어떤 호출이 가능하고 각 호출이 무엇을 반환하는지를 설명하는, 사람이 읽을 수 있는 공개 메서드 목록입니다.
ABI를 사용하면 스마트 컨트랙트 사용자는 바이트코드를 읽을 필요 없이, 호출을 바이트코드로 변환하여 스마트 컨트랙트와 상호작용할 수 있습니다.
ABI는 전통적인 Web2 아키텍처의 API(Application Programming Interface)와 매우 유사합니다. 하지만 주된 차이점은 Solidity ABI는 바이너리로 인코딩된 스마트 컨트랙트 내의 메서드에 사용자가 접근할 수 있게 해주는 반면, API는 온라인 서버 엔드포인트의 메서드에 사용자가 접근할 수 있게 해준다는 것입니다.
사람이 사용하고 읽도록 만들어졌기 때문에, 스마트 컨트랙트 개발자는 스마트 컨트랙트의 ABI를 블록체인에 게시하지 않습니다. 그렇게 하면 비용이 매우 많이 들기 때문입니다.
대신, ABI는 다음과 같은 방법으로 얻을 수 있습니다:
- 스마트 컨트랙트 개발자가 공개한, 컨트랙트의 공개 소스 코드를 통해 ABI를 생성할 수 있습니다.
- 스마트 컨트랙트가 Etherscan에서 검증된 경우, Etherscan의 컨트랙트 정보에서 얻을 수 있습니다.
- 스마트 컨트랙트 바이트코드로부터 ABI를 리버스 엔지니어링할 수 있습니다(권장하지 않음).
ABI는 일반적으로 Solidity 스마트 컨트랙트의 공개 함수 선언을 JSON 형식으로 인코딩하여 게시됩니다.
다음 스마트 컨트랙트의 함수 정의를 살펴보겠습니다:
이에 대응하는 JSON 인코딩은 다음과 같습니다:
Solidity의 호출 데이터 바이너리를 해석하는 방법
Solidity 바이너리를 직접 손으로 파싱해서 함수 호출을 역추적하는 것은 복잡하고 직관적이지 않으며 실수하기 쉬우므로 권장하지 않지만, 바이너리가 Solidity에서 어떻게 구성되는지 대략적으로 이해해두면 호출 데이터를 빠르게 훑어보거나 값을 다시 확인할 때 매우 유용합니다.
다음 섹션에서 이러한 변환 작업을 대부분 처리해주는 몇 가지 도구를 소개하겠습니다.
위의 예시를 다시 살펴보겠습니다.
사용자가 스마트 컨트랙트 내의 함수 baz를 매개변수 69와 true로 호출하려 한다고 합시다. 이 요청은 바이트코드로 다음과 같이 나타나며, 총 68바이트입니다:
0xcdcd77c0000000000000000000000000000000000000000000000000000000000000004500...
1. 호출 데이터의 처음 4바이트로 메서드 ID를 식별합니다.
이 경우 0xcdcd77c는 시그니처 baz(uint32,bool)의 ASCII 형태에 대한 Keccak 해시의 처음 4바이트를 구함으로써 메서드 baz를 식별합니다.
2. 다음 32바이트로 첫 번째 매개변수를 식별합니다
0x00000000000000000000000000000000000000000000000000000000000000045는 첫 번째 매개변수인 69를 식별하며, 이는 32바이트로 패딩된 uint32 값입니다. 패딩이란 실제 숫자 크기와 상관없이 전체 문자열이 (이 경우) 32바이트 길이가 되도록 0을 추가하는 것을 의미합니다.
0x00000000000000000000000000000000000000000000000000000000000000045
3. 마지막 32바이트로 두 번째 매개변수를 식별합니다**
두 번째 매개변수는 true로, 32바이트로 패딩된 bool 값입니다:
0x0000000000000000000000000000000000000000000000000000000000000001
동적 타입을 포함하는 매개변수의 경우 인코딩이 다소 다르게 보이는데, address, bool, uint32 같은 정적 타입은 제자리에서 인코딩되는 반면 동적 타입은 별도로 할당된 위치에 인코딩되기 때문입니다.
Solidity의 이벤트 데이터 바이너리는 어떻게 해석하나요?
이벤트는 스마트 컨트랙트가 메서드 호출을 실행할 때 게시하는 로그이며, 이벤트는 바이너리 데이터로 게시됩니다.
이벤트는 매개변수를 받을 수 있으며, 이는 이벤트가 무엇을 출력할지 지정하는 데 도움이 됩니다. 이러한 매개변수는 인덱싱될 수 있는데, 이는 해당 인덱싱된 매개변수를 필터로 사용해 이벤트를 검색할 수 있게 된다는 의미입니다. 이러한 인덱싱된 매개변수는 Solidity 용어로 토픽(topics)이라고도 불립니다!
대략적으로, Solidity 이벤트는 다음과 같은 구조를 따릅니다:
- address: 컨트랙트의 주소
- topics[n]: 0~4개의 토픽, 즉 인덱싱된 매개변수
- 임의 길이의 바이너리 데이터로, ABI에 따라 파싱할 수 있습니다.
Solidity 바이너리를 디컴파일하려면 어떤 도구를 사용해야 할까?
EtherVM Decompiler와 Panoramix decompiler를 비롯하여 Solidity 바이너리를 더 읽기 쉬운 형태로 되돌리는 데 도움이 되는 다양한 EVM 디컴파일러가 있습니다.
이러한 EVM 디컴파일러가 원본 소스 코드를 완벽하게 복원해주지는 않지만(바이너리 크기를 줄이기 위해 이름이나 다른 중요한 정보가 제거되었을 수 있습니다), 허용된 ABI 요청에 대해 대략적으로 이해할 수 있게 해줍니다.
관련 개요

블록체인 매직을 만드세요
Alchemy는 가장 강력한 Web3 개발자 제품 및 도구를 리소스, 커뮤니티, 그리고 전설적인 지원과 결합합니다.


