스마트 컨트랙트와 EVM: 블록체인이 세계의 컴퓨터로 나아가는 여정
DSRV Research는 더 많은 사람이 Web3를 이해하고 참여하는 데 기여하기 위해, 블록체인과 관련된 지식을 연재합니다.
Disclaimer: 이 글은 정보 전달을 위한 목적으로 작성되었으며, 특정 프로젝트에 대한 투자 권고, 법률적 자문 등 목적으로 하지 않습니다. 모든 투자의 책임은 개인에게 있으며, 이로 발생된 결과에 대해 어떤 부분에서도 DSRV는 책임을 지지 않습니다. 본문이 포괄하는 내용들은 특정 자산에 대한 투자를 추천하는 것이 아니며, 언제나 본문의 내용만을 통한 의사결정은 지양하시길 바랍니다.
블록체인 혹은 암호화폐를 생각했을 때 가장 먼저 떠오르는 단어가 무엇이신가요? 많은 사람들에게 이는 아마 ‘비트코인’일 것입니다. ‘비트코인이 디지털 금인가?’라는 논쟁은 오랫동안 이어져 온 화두입니다. 비트코인이 금에 자주 비유되는 이유는 아마 비트코인이 가지는 희소성과, 또 비트코인이 가치의 저장 수단이라는 이유 때문일 텐데요. 최초의 탈중앙화된 전자화폐 시스템으로 등장한 비트코인도 오늘날에는 사람들이 사용하는 통화의 역할 보다는 가치를 저장하는 수단이라는 점이 더욱 주목받고 있는 것 같습니다.
비트코인으로부터 시작되었지만, 오늘날 우리가 접하는 블록체인과 암호화폐는 훨씬 더 다채로운 모습을 띠고 있습니다. 우리는 블록체인들 위에서 DeFi(Decentralized Finance) 금융상품, NFT, 최근 화두인 메타버스까지 다양한 DApp(Decentralized App, 탈중앙 어플리케이션)을 경험하고 있습니다. 아래의 그림을 통해 이더리움의 DeFi 생태계만 보더라도 대출, 거래소, 보험, 파생상품 등 다양한 종류의 Dapp들이 존재하고 있음을 알 수 있습니다.
이와 같은 것들이 비트코인 네트워크에서도 가능할까요? 비트코인에서도 코드를 작성할 수 있기는 하지만 이는 단순한 수준의 조건적인 자산전송 기능에 그칩니다.[1] 비트코인 네트워크에서는 위와 같은 어플리케이션을 만들 수 없지만, 그렇기에 비트코인은 더욱 견고하고 ‘디지털 금’과 같은 입지를 다질 수 있기도 합니다.
하지만 이더리움의 창립자인 비탈릭 부테린은 비트코인의 제한적인 사용성에 한계를 느끼고, 더욱 다양한 기능과 프로그램을 실행할 수 있는 ‘세계의 컴퓨터’로서의 블록체인을 목표로 이더리움을 만들었습니다.[2] 이더리움은 ‘스마트 컨트랙트(Smart Contract)’라는 블록체인에서 동작할 수 있는 프로그램을 통해 이를 가능하게 합니다. 그래서 우리는 블록체인을 오늘날의 모습으로 만든 이더리움이라는 대표적인 사례를 통해 스마트 컨트랙트가 어떻게 존재하는지, 그리고 어떻게 실행되는 지를 살펴보고자 합니다.
블록체인에는 트랜잭션(Transaction)이라고 하는 거래기록과 트랜잭션을 통해 변화하는 상태(State)가 있습니다. 예를 들어 ‘Alice가 Bob에게 1ETH의 토큰을 전송합니다.'라는 거래가 바로 트랜잭션, Alice의 지갑 잔액이 바로 상태이며, 1ETH 였던 Alice의 지갑 잔액이 토큰을 전송하여 0ETH로 변화하는 것이 상태가 변화하는 것입니다. 따라서 블록체인은 트랜잭션을 모두가 공유하고 이러한 기록들에 따라 모두가 동일한 전체 상태(World State)를 유지하기 때문에 상태머신이라고 불리기도 합니다.
비트코인의 상태라는 것은 UTXO라는 형태로 저장되는데 이는 1BTC라는 하나의 동전에 소유주의 이름을 적어 저장하는 것과 비슷합니다. Alice가 하나의 동전을 Bob에게 전송하여 사용하면, Alice의 동전을 녹여 새로운 Bob의 이름이 적힌 동전을 만드는 것입니다. 하지만 이러한 방식으로 상태를 저장하는 비트코인은 동전을 사용하거나 안 하거나 두 가지 상태만을 사용할 수 있습니다. [3]
이더리움은 비트코인과 달리 계정(Account)으로 상태를 저장합니다. 계정이란 동전에 소유주를 적는 것보다 은행 계좌의 잔고의 숫자를 적는 방식과 유사하다고 이해할 수 있습니다. 이더리움의 계정의 상태(Account State)에는 ETH잔액 뿐만 아니라 코드를 적을 수도 있고, 코드가 관리하는 또 다른 상태를 기록할 수도 있습니다. 예를 들어, NFT를 만드는 경우 계정의 상태에 NFT를 만들고 관리하는 코드와 NFT의 소유자 정보 및 발행 번호 등의 상태를 저장할 수 있습니다. 이와 같이 이더리움의 ‘스마트 컨트랙트’란 계정의 상태에 여러가지 기능들의 코드를 저장하여 실행하는 프로그램을 말하며, 스마트 컨트랙트를 실행하면 계정의 상태가 변경됩니다.
위에서 스마트 컨트랙트를 계정의 상태에 저장한다고 하였는데, 그렇다면 무슨 계정에 어떻게 저장되는 것일까요? 이더리움에는 두 가지의 계정이 존재합니다. 하나는 우리가 흔히 지갑을 통해 사용하고 있는 사용자 계정(Externally Owned Account, EOA)입니다. 사용자 계정은 개인이 소유하고 있는 계정으로, 사용자는 자신만 알고 있는 비밀번호와 같은 ‘개인키’로 서명하여 트랜잭션을 생성할 수 있습니다. 사용자 계정의 상태에는 성공적으로 전송한 트랜잭션의 개수인 논스(nonce)와 ETH 잔액(balance)이 적혀있습니다.
또 하나는 스마트 컨트랙트의 코드가 저장되어 있는 컨트랙트 계정(Contract Account, CA)입니다. 컨트랙트 계정은 논스와 ETH 잔액에 더하여 스마트 컨트랙트에 관련된 상태 데이터들과 이를 실행하기 위한 코드를 담고 있습니다. 이와 같은 데이터들은 컨트랙트 계정에는 code hash와 storage hash로 적혀져 있는데요. 컨트랙트 계정은 긴 데이터를 계정의 상태에 일일이 적기보다는, 이를 일정한 길이의 해시값으로 만들어 실제로 데이터가 저장되어 있는 장소를 쉽게 찾아갈 수 있는 주소와 같이 사용하게 됩니다. code hash는 스마트 컨트랙트 코드의 해시값이며 이를 이용하여 실제 실행코드가 저장되어 있는 위치를 찾을 수 있습니다. 뿐만 아니라 storage hash는 컨트랙트가 관리하는 상태 데이터가 저장되어 있는 스토리지의 해시값으로, 스토리지의 실제 위치를 나타내 줍니다.
❓ 용어정리: 암호화 해시함수(hash function)
임의의 길이의 데이터를 고정된 길이로 변환하는 함수로, 함수를 거친 데이터의 결과값을 해시라고 합니다. 해시함수는 결과값으로부터 입력값을 유추할 수 없으며 극히 일부분만 변화하더라도 결과값이 완전히 달라진다는 특징을 가지고 있습니다. 따라서 긴 코드나, 많은 데이터를 요약해서 표현할 수 있으며, 요약본인 해시값을 원본 데이터가 저장되어 있는 장소를 찾는 주소와 같이 사용하기도 합니다.
예시를 통해 한번 살펴볼까요? 우리가 스마트 컨트랙트로 X라는 토큰을 발행한다고 가정해 봅시다. 먼저 X토큰을 발행 및 관리하기 위한 스마트 컨트랙트 코드를 이더리움에 배포하게 되면 새로운 컨트랙트 계정이 생성됩니다. 계정의 code hash는 배포한 코드의 해시값이 적혀있어, 토큰을 발행하는 트랜잭션을 보내면 code hash에 나타난 주소에 저장되어 있는 실제 코드에서 ‘X라는 토큰을 발행하라' 라는 발행 기능을 나타내는 코드의 조각이 실행될 것입니다. storage hash는 발행된 토큰의 개수 및 누가 몇개의 X 토큰을 가지고 있는지 등의 정보가 적힐 스토리지를 가리키고 있을 것입니다.
이더리움의 ETH같은 체인 고유의 토큰은 사용자 계정의 상태에 그 잔액을 바로 적을 수 있지만, X토큰과 같이 스마트 컨트랙트를 통해 생성되는 토큰들은 토큰을 관장하는 컨트랙트 계정의 스토리지에 그 잔액이 적히게 됩니다. 우리가 사용하는 지갑에 ETH뿐만 아니라 다양한 토큰들의 잔액들이 표시될 수 있는 것도 지갑이 해당 토큰의 컨트랙트 계정 주소를 추가하여 스토리지를 읽어오기 때문입니다. 스마트 컨트랙트가 어떻게 블록체인에 존재하고 있는지 조금은 감이 잡히셨나요?
스마트 컨트랙트에 코드가 저장되어 있기는 하지만, 컨트랙트 계정은 혼자서 코드를 실행시킬 수 없습니다. 스마트 컨트랙트는 누군가가 실행을 해주어야 조건에 따라 동작하는 프로그램이니까요. 따라서 사용자의 계정이 개인키로 서명하여 해당 코드를 통해 상태를 변경하겠다는 트랜잭션을 생성하여야 합니다. 사용자의 계정이 트랜잭션을 생성하여 컨트랙트 계정에 메세지를 보내면 메세지를 받은 컨트랙트 계정은 코드를 실행한 후 사용자 계정에 메세지를 보내거나, 또 다른 컨트랙트 계정에 메세지를 보내 또 다른 코드를 실행하게 할 수 있습니다.
❓ 용어정리: 트랜잭션과 메세지
트랜잭션은 사용자 계정이 서명한 데이터로 블록체인의 각 블록에 기록됩니다. 트랜잭션은 새로운 컨트랙트 계정을 생성하거나 메세지를 전달하는 두 가지 종류가 있습니다. 메세지는 두 계정 간에 전달되는 데이터 및 ETH의 양으로 사용자 계정의 서명(트랜잭션) 혹은 컨트랙트 계정의 내부적인 호출을 통해 생성 및 전달될 수 있습니다.[4]
이번에도 예시를 통해 살펴보겠습니다. 이전에 배포했던 X토큰의 코드를 통해 X토큰을 한번 전송해볼까요? 우리가 배포한 코드에는 토큰을 발행하는 기능뿐만 아니라, 잔액을 확인하는 기능, 토큰을 전송하는 기능 등 여러가지 기능들이 포함되어 있습니다. X토큰을 10개 가지고 있던 Alice가 Bob에게 X토큰 5개를 보내고자 하는 상황을 살펴봅시다. Alice가 X토큰을 전송하기 위해서는 수신인을 Bob의 주소로 적는 것이 아니라 X토큰의 컨트랙트의 계정의 주소를 수신인으로 하여 트랜잭션을 전송해야 합니다. 컨트랙트 계정의 코드에는 다양한 기능들이 적혀있기 때문에 그중 어떤 기능을 사용할 것인지도 명시해 주어야 합니다. 토큰을 전송하는 기능의 이름이 transfer라고 한다면 Alice는 transfer(Bob, 5)와 같이 전송기능을 통해 Bob에게 5개의 X토큰을 보낸다는 내용을 같이 적어 트랜잭션을 생성합니다. 트랜잭션을 통해 컨트랙트 계정에 있는 transfer
코드가 실행되면 스토리지에 있는 Alice와 Bob의 X토큰의 잔액 상태가 업데이트됩니다.
여기까지 잘 따라오셨다면 한단계 더 나아가 볼까요? 이번에는 X토큰을 스테이킹(Staking, 예치)하는 경우를 살펴봅시다. 이는 위와 달리 사용자 계정이 컨트랙트 계정을 부르고, 컨트랙트 계정이 또 다른 컨트랙트 계정을 다시 호출하게 되는 경우입니다. 이제 Alice는 X토큰 10개를 스테이킹하고 스테이킹을 했다는 증표인 X’토큰을 10개 받으려고 합니다. Alice는 스테이킹 코드를 가진 컨트랙트 계정(이하 스테이킹 계정)의 주소에
stake(10)이라는 정보를 적어 트랜잭션을 보냅니다. Alice의 메세지를 받은 스테이킹 계정은 X토큰의 컨트랙트 계정이 아니기 때문에 다시 X토큰의 컨트랙트 계정에게 Alice의 주소에서 스테이킹 계정의 주소로 10개의 X토큰을 보내라는 메세지를 보내야 합니다. 스테이킹 계정은 X토큰의 컨트랙트 계정에 transferFrom(Alice address, Staking address, 10)이라는 메시지를 보내서 Alice의 X토큰 잔액을 10만큼 차감하고 스테이킹 계정의 주소에 10을 추가합니다. 그리고 스테이킹 계정은 새로운 X’토큰을 스테이킹을 했다는 증표로 발행하기 위하여 X’토큰의 컨트랙트 계정에 10개의 X’을 발행하라는 mint(Alice address, 10) 메세지를 보내 Alice X’잔액에 주소에 10을 추가합니다. 컨트랙트는 이와같이 사용자 계정에게 불려질 수도 있고, 다른 컨트랙트에게 불려질 수도 있습니다. 하지만 컨트랙트가 다른 컨트랙트를 부르려면 항상 사용자 계정에서 트랜잭션을 보내주어야 시작될 수 있습니다.
우리는 지금까지 스마트 컨트랙트의 코드 어디에 저장되고, 어떠한 방식으로 실행되는지 그 과정을 알아보았습니다. 하지만 코드의 ‘실행’이라는 일련의 과정에는 핵심적인 플레이어가 하나 더 있습니다. ‘이 코드를 실행해 주세요’라고 요청했을 때, 누군가는 그 요청을 받아 실제로 코드를 돌리고 상태를 업데이트 시켜주어야 합니다. 이더리움이 스마트 컨트랙트라는 프로그램을 실행할 수 있는 플랫폼으로 기능할 수 있는 가장 핵심적인 이유는 ‘EVM(Ethereum Virtual Machine, 이더리움 가상머신)’이라는 가상의 컴퓨터가 있기 때문입니다. EVM은 이더리움이 정의한 규칙에 따라 스마트 컨트랙트 코드를 실행하고, 그 결과로 변화된 상태를 업데이트하는 작업을 수행해 줍니다. 또한 EVM은 위와 같은 과정에서 코드 실행이 성공적으로 완료됐을 경우에만 상태를 변경하여 스마트 컨트랙트가 직접적으로 블록체인에 영향을 미치지 않도록 보호하는 샌드박스(보호 영역)와 같이 역할합니다.[5]
EVM이 존재하는 또 하나의 이유는 블록체인이 분산 네트워크에 참여하고 있는 노드들이 동일한 전체 상태에 대한 합의를 이루어야 하는 시스템이기 때문입니다. 동일한 코드를 실행하였는데 각자 다른 환경에서 이를 실행하는 노드들이 모두 다른 결과값을 얻는다면 우리는 하나의 상태에 대한 합의를 이룰 수 없을 것입니다. 따라서 이더리움 네트워크의 모든 노드들은 동일한 코드에 대해 공통적인 결과를 도출하기 위해서 EVM을 실행시키고 있습니다. 노드들은 블록에 담긴 트랜잭션과 스마트 컨트랙트를 각자의 EVM에서 실행시켜 자신이 유지하고 있는 블록체인의 전체 상태를 다른 노드들과 동일하게 유지할 수 있습니다.
하지만 EVM을 사용하는 것은 공짜가 아닙니다. 우리가 EVM을 사용하기 위해서는 그에 대한 대가로 ‘가스’라는 비용을 지불해야하고 여기에는 매우 중요한 이유가 있습니다. 이더리움은 복잡한 스마트 컨트랙트를 수행할 수 있지만 이것이 언제 종료될지, 얼마나 오랫동안 실행될지를 예측할 수는 없습니다. EVM에서 무한히 실행되는 프로그램을 실행시키게 된다면 해당 프로그램은 EVM의 리소스를 점유할 것이고 우리는 다른 프로그램을 실행시키기 위해 영원히 기다려야 합니다. 누군가는 이를 악용해서 이더리움을 마비시키기 위한 공격을 할지도 모릅니다. 따라서 이더리움은 가스를 통해 프로그램이 사용할 수 있는 자원의 양을 제한하여 이를 방지합니다. 실행되는 연산마다 가스가 소모되기 때문에 무한히 연산을 실행시키기 위해서는 막대한 양의 가스를 지불해야 합니다. 가스는 이더리움의 마비를 방지하는 것뿐만 아니라 EVM이라는 가상의 컴퓨터를 실행시키고 있는 노드들에 대한 보상으로서 주어지게 됩니다.
이번에는 EVM의 구조를 통해 EVM이 동작하는 방식을 살펴볼까요? EVM은 대표적으로 ROM, 스택, 메모리, 스토리지라는 공간에 접근할 수 있습니다. 이더리움의 코드들은 EVM이 실행할 수 있는 명령어들로, 이를 실행하기 위해서는 코드들을 ROM(읽기 전용 메모리)에 로드해야 합니다. 실행되는 명령어 코드들은 스택이라는 공간을 활용한 연산으로 종류에 따라 스택에 값을 저장하거나 불러오는 등의 연산을 수행합니다. 스택은 제한된 크기의 공간이기 때문에 복잡한 프로그램을 실행하기 위해서는 메모리라는 추가 저장공간을 활용하여 데이터를 저장하거나 다른 컨트랙트에 전달해야 합니다. 스토리지는 일시적인 저장공간인 스택과 메모리와 달리 블록체인에 영구적으로 저장되어야 하는 데이터를 저장합니다.
� DSRV’s Tip: 이더리움에 저장되는 코드
우리가 스마트 컨트랙트를 사람이 이해할 수 있는 고급언어(High-level language)로 작성하였다고 하더라도, EVM이라는 컴퓨터가 이를 실행하게 하기 위해서는 EVM이 이해할 수 있는 명령어로 이를 해석해 주어야 합니다. 따라서 이더리움 위의 코드는 EVM이 실행할 수 있는 명령어들의 집합이며 이는 EVM이 읽을 수 있는 형태인 바이트코드(bytecode)로 저장되어 있습니다.
그럼 이제 EVM이 동작하는 과정을 따라가 봅시다.
먼저 EVM이 트랜잭션을 전달받으면, EVM은 트랜잭션이 호출한 컨트랙트 계정의 스마트 컨트랙트를 실행하기 위한 상태로 세팅됩니다. EVM의 스택 및 메모리는 비워지며, 스토리지에는 컨트랙트 계정의 스토리지의 주소가 로드되고, 실행시킬 코드들을 컨트랙트 계정에서 ROM으로 가져옵니다.
이후 가져온 코드에 따라 연산의 실행이 진행되는데, EVM은 연산을 실행할 때마다 사용자 계정이 공급한 가스(Gas Available)가 필요한 만큼 남아있는지 확인하고 가스를 차감하여 연산을 실행합니다.
이때 실행하는 연산마다 필요한 가스의 양이 다릅니다. 일시적인 데이터를 변경하는 것보다 영구적인 데이터를 변경하는 것의 리스크가 더 크기 때문에 스택과 메모리만을 사용하는 연산보다 스토리지에 접근해야 하는 경우 더욱 많은 가스가 들도록 책정되어 있습니다. 또한 EVM이 또 다른 컨트랙트 계정을 내부적으로 호출하는 경우에도 리소스를 많이 소모하게 되기 때문에 많은 가스가 소모됩니다. 컨트랙트를 호출한 사용자 계정이 공급한 가스의 양이 필요한 가스보다 부족하면 연산의 실행이 즉시 중단되고 변경되었던 상태가 원래대로 돌아갑니다. 가스가 고갈되어 트랜잭션이 실패하더라도 그전까지 수행한 연산에 대해서는 가스를 지불해야 하며 잔여 가스가 있다면 이는 사용자 계정에 환불됩니다.
코드의 실행이 성공적으로 완료되는 경우 결과값은 스토리지에 저장하게 되는데 이것이 바로 컨트랙트 계정의 상태를 업데이트하는 것입니다.
이더리움이 EVM을 통해 이처럼 다양한 스마트 컨트랙트를 실행시킬 수 있는 이유는 이더리움이 ‘튜링 완전성(Turing Completeness)’에 근접한 시스템이기 때문입니다. 튜링 완전한 시스템이라는 말이 굉장히 생소하게 들리실텐데요. 아주 간단하게 이해하자면 튜링 완전하다는 것은, 마치 사람에게처럼 컴퓨터에게 충분한 시간과 메모리를 주고 문제를 풀게하면 아무리 복잡한 문제라도 풀 수 있게 된다는 의미입니다. 따라서 튜링 완전한 시스템은 인간이 사고하는 것처럼 아주 작은 단위별로 문제를 풀고 필요에 따라 반복과 조건을 통해 결과물을 낼 수 있어야 합니다.
이더리움과 비트코인은 바로 이 튜링 완전성에서 가장 큰 차이점을 보여주고 있습니다. 튜링 완전성은 때때로 답을 찾기 위해 무한히 실행되는 프로그램의 특징을 의미하기도 하는데요. 비트코인은 탈중앙된 지불시스템으로써 누군가 복잡하게 반복되는 프로그램을 실행하여 네트워크가 마비되는 위험을 배제하기 위해 튜링 완전성을 포기하였습니다. 하지만 이더리움은 위에서 이야기하였듯, 프로그램을 실행시키는데 필요한 리소스를 사전에 정의하고 비용을 지불하도록 하는 가스를 도입함으로써 복잡한 스마트 컨트랙트, 다시 말하자면 어떤 프로그램이든 실행시킬 수 있는 컴퓨터로 자리매김하게 된 것입니다.
복잡한 스마트 컨트랙트를 실행시킬 수 있는 환경은 블록체인 및 암호화폐에 단순히 전송하거나 가치를 저장하는 것 이상의 사용성을 가져다 주었습니다. 이더리움 뿐만 아니라 다양한 블록체인들이 스마트 컨트랙트를 지원하는 DApp을 위한 플랫폼으로 등장하게 되었습니다. 우리는 평소에 DApp을 통해 많은 스마트 컨트랙트들을 사용하고 있지만 이가 어떠한 과정으로 작동하는지, 그리고 어떠한 의의를 가지는지에 대해 생각해 볼 기회는 많지 않습니다. 이번 글을 통해 우리가 그동안 트랜잭션을 보내고 ‘완료!’가 뜨기까지의 ‘Behind the scene’을 엿본 것 같은 재미를 느끼셨기를, 그리고 이더리움이 가져온 한걸음의 발전이 블록체인 씬에 어떤 변화를 가져왔는지를 생각해볼 계기가 되셨기를 바라며 글을 마칩니다. 감사합니다.
Author
Youngbin Park of DSRV, Research Associate (@bin0_0bin)
Reviewed by
Owen Hwang of DSRV, Research Manager (@journeywith_eth)
Seokjoong Yoon of DSRV, Research Associate (@imlearning_eth)
스마트 컨트랙트란 블록체인에 코드를 저장하여 실행시킬수 있는 프로그램을 말합니다.
EVM(Ethereum Virtual Machine)은 스마트 컨트랙트를 실행하고, 실행의 결과로 모든 노드가 동일하게 상태를 변경할 수 있도록 합니다.
EVM에서 실행되는 연산에 대해서는 ‘가스’라는 비용을 지불하여야 하며, 이는 복잡한 프로그램이 이더리움의 리소스를 독점적으로 사용하여 네트워크를 마비시키는 것을 방지합니다.
References
[1] Bitcoin Developer Docs , Contracts
[2] The mind behind the world computer: Ethereum’s Vitalik Buterin by Cointelegraph
[3] Ethereum Whitepaper, Introduction to bitcoin and existing concepts, Scripting by Vitalik Buterin
[4] Ethereum YellowPaper, Appendix A. Terminology by Gavin Wood
[5] Mastering Ethereum, Chap 13. EVM이란 무엇인가?