본문으로 바로가기

스마트 컨트랙트는 특정 계약을 스스로 수립, 검증, 이행하기 위한 컴퓨터 프로토콜입니다. 이더리움에서 스마트 컨트랙트는 이더리움의 상태를 변경할수 있는 프로그램 코드로서 블록에 포함되어 각 노드에 전파되고, EVM에서 작동되어 상태전이를 발생시킵니다. 즉 스마트 컨트랙트는 블록헤더의 데이타뿐만 아니라 특정 값이나 발신자 및 수신되는 메시지의 데이타를 조작하는 등 이더리움의 상태변화와 데이타 저장등이 가능한 프로그램 코드입니다.


스마트 컨트랙트는 1996년 닉 자보( Nick Szabo : 컴퓨터과학자, 법학자, 암호학자)란 분에 의해서 개념이 정립되었습니다. 


“a set of promises, specified in digital form, including protocols within which the parties perform on these promises.” 

  (Szabo, Smart Contracts: Building Blocks for Digital Markets, 1996) : 

  - 조건이 충족되면 사람의 개입 없이 자동으로 실행되는 ‘자동화된 거래규약’



닉 자보는 스마트 컨트랙트의 개념을 자동판매기에 비유하여 설명하였습니다.

"자동판매기는 합의 원리를 물리적 하드웨어에 포함시킨 장치로서, 여러가지 규칙에 의해서 작동됩니다. 예를 들어, 자동판매기에 1달러를 넣으면  한병의 물을 제공하는 '계약'이 있다고 가정할때, 1달러는 넣으면 물한병이 나와야 합니다. 1달러를 넣었는데 물 한병이 나오지 않는다면 그것은 합의를 위반하는것이며, 1달러를 넣지 않았는데 물 한병이 나오면 그것또한 합의를 위반하는 것입니다."

이와같이 닉 자보는 " 3자를 거치지 않는 자동화된 계약에 의한 합의"라는 개념을 최초로 정립하였습니다.



위와같은 스마트 컨트랙트의 개념을 이더리움에서는 더욱 발전시켜  다양한 형태의 응용 컨트랙트를 개발할수 있도록 발전시켰으며, 이를 통해 이더리움은 단순한 암호화폐를 벗어나 다양한 분야에 적용할수 있는 블록체인 컴퓨팅 플랫폼으로 발전하게 되었습니다.


비트코인의 경우에도 스크립트라는 단순한 형태의 프로그램 코드를 지원합니다. 비트코인은 트랜잭션을 생성할때, UTXO의 잠금스크립트를 자신의 지갑에서 생성한 해제 스크립트를 사용하여 푸는 과정에서 하기와같은 스크립트를 사용합니다. 스크립트는 스택 구조를 사용하여 계산되어지며, 최종적으로 잠금스크립트의 sig값과 PubK 값이 매칭되는지 확인하여 UTXO의 사용여부를 검증합니다. 이와같이 비트코인에서의 스크립트는 트랜잭션의 유효성을 검증하는 단순한 용도로만 사용되어집니다.



이더리움에서 스마트 컨트랙트는 새로운 스마트 컨트랙트를 생성하거나, 특정 스마트 컨트랙트상의 함수를 실행하거나, 이더를 전송하는 방식중의 하나로 실행이 됩니다. 또한 사용자 어카운트(EOA)에 의해서 발생한 트랜잭션이나 다른 컨트랙트에 의해서만 실행됩니다. 

이더리움의  스마트 컨트랙트에서는 무한반복같은 악의적인 코드를 막고 데이타의 무결성를 지키기 위해 모든 트랜잭션을 실행할때 해당실행비용을 지급하도록 규정하고 있으며, 이와같은 모든 트랜잭션의 기본실행비용은 21,000 가스입니다. 이와같은 비용에는 발송자 어카툰트 주소에 대한 ECDSA를 위한 비용과 트랜잭션 저장을 위한 스토리지 비용, 네트워크 대역폭 비용이 포함됩니다. 이와같이 스마트 컨트랙트 실행시 비용을 지불하도록 정의하여, 의도적인 디도스 공격과 같은 무한실행과 같은 악의적인 의도를 방지할수 있습니다.


스마트 컨트랙트간의 호출은 메시지라는 특별한 구조체를 사용하여 호출됩니다. 메시지는 외부 어카운트(EOA)가 아니라 컨트랙트 어카운트(CA)에 의해서만 생성되며, 함수 호출시에 다른 컨트랙트로 전달됩니다. 메시지는 트랜잭션과는 달리 EVM 내부에서만 존재하기 떄문에 가스비용이 발생하지 않습니다. 


다음은 메시지의 구조입니다. 메시지는 EVM내에서 컨트랙트를 실행하기 위해서 Call, CallCode, DelegateCall, StaticCall 등이 호출될때에 생성됩니다. 이들 Call 코드들은 공통적으로 컨트랙트 주소를 매개변수로 전달받아 이를 실행하고 처리합니다.


type Message struct {
to *common.Address
from common.Address
nonce uint64
amount *big.Int
gasLimit uint64
gasPrice *big.Int
data []byte
checkNonce bool
}


to : 메시지 수신처

from : 메시지 발신처

nonce : 거래 실행시 수행되도록 허용된 최대 트랜잭션 수행횟수

amount : 메시지와 함께 전달되는 이더(wei 단위)

gasLimit : 트랜잭션 수행시 소비될 총 가스량에 대한 추정치

gasPrice : 가스가격

data : 매개변수 전달시 사용되는 데이타 필드(Optional)


다음과 같이 Call, CallCode, DelegateCall 이 호출될때에 메시지가 만들어집니다.

Call, CallCode, DelegateCall 함수호출은 서로간에 차이점이 있으며, 이 차이에 대해서는 이후에 다시 정리하도록 하겠습니다.


contract D {
uint public n;
address public sender;
function callSetN(address _e, uint _n) {
_e.call(bytes4(sha3("setN(uint256)")), _n); // E's storage is set, D is not modified
}
function callcodeSetN(address _e, uint _n) {
_e.callcode(bytes4(sha3("setN(uint256)")), _n); // D's storage is set, E is not modified
}
function delegatecallSetN(address _e, uint _n) {
_e.delegatecall(bytes4(sha3("setN(uint256)")), _n); // D's storage is set, E is not modified
}
}
contract E {
uint public n;
address public sender;
function setN(uint _n) {
n = _n;
sender = msg.sender;
// msg.sender is D if invoked by D's callcodeSetN. None of E's storage is updated
// msg.sender is C if invoked by C.foo(). None of E's storage is updated
// the value of "this" is D, when invoked by either D's callcodeSetN or C.foo()
}
}
contract C {
function foo(D _d, E _e, uint _n) {
_d.delegatecallSetN(_e, _n);
}
}


이더리움에서 스마트 컨트랙트는 Solidity 언어로 프로그래밍되어집니다. solidity 언어로 프로그래밍된 스마트 컨트랙트는 컴파일러(solc)에 의해 바이트 코드로 컴파일되고, 컴파일된 바이트코드는 블록에 포함되어, EVM(Ethereum  Virtual Machine)에 의해 실행됩니다.



EVM(Ethereum Virtual Machine)은 이더리움 스마트 컨트랙트의 바이트 코드를 실행하는 32바이트 스택 기반의 실행환경으로 스택의 최대 크기는 1024입니다. 이더리움의 각 노드는 EVM을 포함하고 있으며, EVM을 통해 바이트 코드를 OP코드로 변환하고 스택기반으로 각각의 OP코드를 실행합니다.


EVM은 다음과 같은 구조를 가지고 있습니다.

EVM은 휘발성, 비휘발성 메모리로 구성되어 있으며, 여기에 바이트 배열 형태로 스택의 항목들을 저장합니다. 


비휘발성 (non-volatile)

  - storage : 상태(state)가 저장

  - code : 스마트 컨트랙트의 컴파일된 바이트 코드가 저장

volatile (휘발성) 

  - stack : OP 코드를 실행하기위한 스택영역

  - args : 컨트랙트 호출시에 넘어오는 인자를 저장

  - memory : word 단위로 아이템을 저장하는 바이트 배열



EVM은 바이트코드를 내부 OP 코드로 재해석합니다. 즉, solidity 언어로 개발된 스마트 컨트랙트의 컴파일된 바이트 코드는 EVM에서 OP 코드로 치환하여 실행됩니다.

"1+2"를 계산하는 바이트 코드를 가지고 EVM의 동작을 알아보도록 하겠습니다.

1+2를 계산하는 코드의 바이트코드는 "6001600201" 입니다. 이 바이트코드를 치환하여 OP 코드로 분리하면 "0x60, 0x01, 0x60, 0x02, 0x01" 이 됩니다.

위에 링크한 OP 코드표를 참고로 하면, 0x60은 PUSH OPCODE를 의미하고, 0x01, 0x02는 값, 1, 2를 의미하고, 마지막 0x01은 ADD OPCODE를 위미합니다. 즉 EVM의 스택에 1,2를 Push하고 Add 연산을 수행하라는 의미입니다.


evm 도구를 사용하여 실제 바이트코드가 OP코드로 치환되어 실행되는 과정을 살펴보도록 하겠습니다. 

다음과 같이 evm 도구를 사용하여 위의 바이트코드를 실행할경우 다음과 같은 과정을 통해서 결과값 3이 스택에 넣어지는것을 확인할수 있습니다. 또한 OP코드표상으로 PUSH, ADD 연산의 Gas 비용은 3임을 확인할수 있고, 하기와와 같이 해당 연산이 수행시에 3 Gas가 사용됨을 확인할수 있습니다.



마지막으로 EVM의 특징을 정리해보면 다음과 같습니다.


1. 임시저장소영구저장소를 구분하여, 임시저장소에 저장한 값은 해당 인스턴스에서만 유효하고, 영구저장소에 저장한 값은 해당 컨트랙트 전체에 유효합니다.

2. EVM에서 바이트 코드를 실행하기 위해서는 다음의 3가지 요소가 필요합니다.

   - 컨테이너에 값을 Push, Pop하기 위한 스택

   - 바이트 배열을 담을수 있는 메모리

   - 영속적으로 값을 저장하기 위한 저장소. 현재 저장소로는 레벨DB를 사용하고 있습니다.

3. 4,8바이트 워드단위는 크기가 너무작아, 32바이트 워드 단위를 지원합니다.

4. 32바이트 워드크기 등 이더리움에서 요구하는  VM기능과 명세를 지원하기 위해 단순화된 자체 VM을 개발하였습니다.

5. 메모리 크기가 가변적이고 스택의 크기에 제한이 없습니다.

6. 반복호출횟수를 1024로 제한하였습니다.