Under-Optimized Smart Contracts Devour Your Money

얼마 전에 흥미로운 논문을 보게 되었다. https://arxiv.org/abs/1703.03994

논문의 제목은 이 글의 제목과 같은데, 간단히 요약하자면 컨트랙트를 코딩하고 EVM에서 동작하도록 컴파일 될 때 개스 사용이 최적화 되지 않고 있다는 이야기다. 즉, 코딩을 어떻게 하느냐에 따라 개스 소모가 달라지고, 이런 부분은 실제 Solidity로 개발을 하는 개발자는 확인을 하기가 어려우니 컴파일러의 최적화 기능을 강화해야 한다라는 것이다. 5페이지짜리 짧은 페이퍼이고, 매우 Plain English로 쓰여 있으니 부담없이 읽을 만 하다.

Ethereum의 스마트 컨트랙트에 친숙하지 않은 분을 위해 약간의 부연 설명을 하자면,  Ethereum에서는 스마트 컨트랙트를 (현재) Solidity로 작성하며, 이는 컴파일되어 EVM(Ethereum Virtual Machine)코드로 변환된다. 실행 시 컨트랙트는 Miner의 EVM에서 동작하게 되며, 이 때 gas를 사용하게 된다. 즉, 누군가의 컴퓨팅 자원을 사용하니 댓가를 지불해야 한다는 것으로 쉽게 이해할 수 있겠다.

또한 gas는 DoS 어택을 막는 용도로도 중요한 역할을 한다. 간단히 예를 들어 컨트랙트에 무한 루프를 넣고 무거운 동작을 반복하게 만든다면 노드의 실행에 장애가 생길 수 있는데, 각 Operation에 비용을 지불하게 함으로써 이러한 공격을 막게 된다. (만약 해커가 만수르라면??)

EVM의 동작에는 소모되는 gas가 설정되어 있는데, 이는 Yellow Paper의 Appendix G에서 확인할 수 있다. 해당 표는 아래와 같다.

스크린샷 2017-04-12 11.02.31

대충 살펴 보면 굉장히 싼 오퍼레이션이 있는 반면에, 매우 비싼 것들이 존재한다. 대개 스토리지를 사용하거나, 계정을 생성하는 것들이 그렇다. 그렇다면 개발할 때에도 가급적 gas를 적게 사용하도록 만들어야 할 것이다. (버튼 한번 누를 때마다 1,000원씩 날아간다면 무서워서 쓰겠나..)

다시 논문의 내용으로 돌아오면, 저자들은 2가지 분류의 7가지 정도의 개스를 낭비하는 패턴을 찾아 냈다. 그리고 이런 부분은, EVM의 동작을 깊이 이해하지 못하는 개발자들이 파악하기 어렵기 때문에 컴파일러에서 처리를 해 주어야 한다고 이야기 하고 있다.

첫 번째 분류인 Useless Code에서는 두 가지 패턴을 이야기 하고 있는데, dead code와 opaque predicate이다.

스크린샷 2017-04-12 11.12.35.png

어찌 보면 이 부분은 간단하다. 왼쪽의 패턴 1에서는 4행의 코드는 실행될 일이 없다. x는 5보다 크기 때문에, 이 값을 제곱한 값이 20보다 작을 일은 없기 때문이다. 우측 패턴의 경우는 불필요한 조건문이 들어간 경우다. 3행은 의미가 없다. 하지만 gas를 사용한다.

두 번째 분류는 Loop Related Patterns인데, 이는 좀 더 심각하다. 말 그대로 루프를 통해 반복되기 때문에 불필요한 비용 낭비가 급격히 올라갈 수 있기 때문이다.

스크린샷 2017-04-12 11.16.52

패턴 3은 비싼 오퍼레이션이 루프에 들어가 있는 경우다. sum은 스토리지에 보관되는 변수인데, SLOAD를 통해 스택에 올라가고, SSTORE를 통해 값을 저장한다. SLOAD는 200 gas를, SSTORE는 20,000 gas(!)를 사용한다. 값 비싼 오퍼레이션은 루프 밖으로 빼야 하고, 임시적으로 사용되는 값들은 memory 를 사용하는 편이 싸게 먹힌다.

패턴 4는 루프의 결과가 항상 상수가 나오는 경우다. 3,4행의 결과로 sum은 항상 5050이 된다. 즉 return sum; 보다는 return 5050; 으로 하고 루프를 없애는게 낫다.

스크린샷 2017-04-12 11.17.37

패턴 5는 동일한 역할을 하는 루프라면 루프를 합치라는 이야기다. 4,6행에서 시작하는 두 개의 루프는 동일한 횟수만큼 반복하는데, 하나의 루프로 합쳐서 오퍼레이션의 수와 bytecode 사이즈를 줄이라는 이야기다.

패턴6은 반복되는 연산을 빼라는 것이다. 6행에 보면 sum + x + y가 있는데, 여기서 x + y는 루프 안에 있을 필요가 없다. x + y를 반복 계산하기 위해 SLOAD가 반복 호출되는 결과를 가져 온다.

스크린샷 2017-04-12 11.17.47

패턴 7은 비교문의 결과를 컴파일 시에 알 수는 없지만, 루프 내에서 항상 동일한 결과를 가질 경우 루프 바깥으로 빼라는 것이다. 3행의 x > 0 는, x 값에 따라 참 또는 거짓이겠지만 루프를 반복할 대 항상 동일한 값을 가지게 된다. 이런 경우 루프를 들어가기 전에 미리 조건을 검사하는 것이 유리하다.

사실 이러한 패턴들은 어찌 보면 상당히 사소한 것들처럼 보이고, 누가 저렇게 할까 싶은 것도 있기는 하다. 하지만 사소하기 때문에 의외의 엄청난 결과를 가져올 위험성도 내포하고 있다.

저자들은 GASPER라는 툴을 만들어 2016년 11월 5일 까지 Ethereum 네트워크에 등록된 모든 컨트랙트를 전수 검사를 했다. 전체 수는 566,907이지만 코드가 없는 것, 중복된 것 등을 모두 제외한 4,669 컨트랙트를 대상으로 실시했는데 상당히 놀라운 결과가 나왔다.

스크린샷 2017-04-12 11.37.16.png

패턴 1, 2 (불필요한 코드)가 전체 컨트랙트의 93.5%, 90.1% 에서 발견되었고, 루프 내부에 비싼 오퍼레이션을 포함하는 경우도 전체의 80%에 달했다. 이 세 가지 모두를 포함하고 있는 경우는 71.7%.

즉 이런 낭비를 만드는 것이 매우 쉽다는 것을 의미하는 결과라고 본다. 특히, 그 동안 서버의 스펙이 높아져 가면서 사실 코드 자체의 최적화에 대한 고민은 상대적으로 덜 중요해져 왔다고 생각하는데, Solidity로 개발할 때는 (최소한 당분간은) 주의가 필요하다.

특히 아직 표준 라이브러리가 명확하게 구성되어 있지 않고, 데이터의 보관을 외부 DB등에 사요하는 것에 익숙해져 있는 경우가 많은데 Solidity에서는 사용 불가능하기 때문에 이런 낭비에 대해 좀 더 고민할 필요가 있다고 하겠다.

마지막으로 http://dapps.ethercasts.com/  을 살펴 보다가 발견한 것인데, 아마도 베트남 사람으로 보이는 친구가 출/퇴근 펀치 카드를 간단히 구현한 것이 있었다.  재미 있는 것은 개략적인 사용료인데, gas price = 0.00000002 Ether, 1 Ether = $40으로 가정했을 때의 월 사용료가 $343.2에 달한다는 것이다. (관리자 10명, 직원 100명, 직원은 하루에 두 번 카드를 체킹한다는 가정)

아직은 막 시작된 분야이기 때문에 비즈니스 모델을 고민하기에 이른 감이 없지 않지만, 저렇게 비싼 비용을 지불하고라도 사용할만한 이익을 제공하기가 쉽지는 않아 보인다.

 

 

이더리움 설계 근거

원문: https://github.com/ethereum/wiki/wiki/Design-Rationale

이더리움의 디자인 원칙 문서를 요약해 봅니다. 위키에 기술된 내용인데, 상당히 깊은 부분의 내용들도 포함되어 있습니다. 내용을 모두 번역하기는 양이 많아서, 적당히 중요한 부분들만 정리하였습니다.

Principles

  1. Sandwitch complexity model: 이더리움의 하부 레벨 아키텍처는 가능한 단순하게 만들고, 인터페이스를 가능한한 쉽게 만든다. 복잡도가 발생할 수 없는 부분은 “미들 레이어”에 배치하며 이 부분은 코어 컨센서스 영역은 아니지만 엔드유저에게 보이지도 않는다. 여기 해당하는 것은 고수준 컴파일러, 인자 직렬화, 스토리지의 데이터 스트럭처 모델, leveldb 스토리지 인터페이스와 wire protocol 등.
  2. Freedom: 사용자는 이더리움 프로토콜을 어떤 용도로 사용하든 제약을 받지 않는다. 사용의 목적에 따라 특정한 이더리움 컨트랙트나 트랜잭션을 차별하려 하지 않는다.
  3. Generalization: 프로토콜 피처나 opcode는 최대한 로우레벨 컨셉들을 구현함. 이를 통해 다양한 형태로 재구성/활용될 수 있도록 하기 위해.
  4. We have No Features: 매우 흔히 사용되는 하이레벨 유스 케이스라도 프로토콜의 본질적인 기능으로 추가하지는 않는다. 꼭 필요하다면 컨트랙트 내부의 서브 프로토콜로 만들면 된다.

Blockchain-level protocol

Account based

비트코인은 UTXO를, 이더리움은 Account를 사용한다.

UTXO는 다음의 장점을 가진다.

  1. 높은 수준의 프라이버시: 각각의 트랜잭션마다 다른 주소를 사용하게 되면, 추적하기 매우 어렵다. 통화인 경우 이는 잘 적용되나 Dapp의 경우 이는 문제를 가진다. 사용자의 상태 정보를 추적해야 하는 경우가 많기 때문
  2. 확장성 패러다임으로의 가능성: UTXO는 이론적으로 특정한 확장성 패러다임에 잘 맞는다. 오직 특정 코인의 소유자만이 소유권에 대한 머클 트리를 관리하기 때문에, 소유자를 포함한 모든이가 그 데이터를 잊기로 한다면, 소유자에게만 피해가 간다.

Accounts는 다음의 장점을 가진다

  1. 공간 절약: UTXO 모델에서 300바이트를 사용할 때 Account 모델은 30바이트만 사용한다. 현실적으로는 이렇게 큰 차이가 나진 않는데, account 역시 파트리샤 트리에 저장되기 때문이다. 또한 트랜잭션 역시 더 작은데 이는 각각의 트랜잭션이 오직 하나의 리퍼런스와 시그너처를 필요로 하기 때문이다.
  2. 더 나은 대체성: 특정 코인의 소스에 대한 블록체인 레벨의 개념이 없기 때문에 코인의 출처에 따라 레드리스트/블랙리스트를 적용하기 어렵다
  3. 단순성: 스크립트를 작성하고 이해하기 쉽다
  4. 경량 클라이언트 참조: 경량 클라이언트는 상태 트리를 읽음으로써 어느 시점에서든 계정에 관계된 모든 정보를 읽을 수 있다.

어카운트 모델의 약점은 리플레이 어택을 막기 위해 모든 트랜잭션이 반드시 논스(nonce)를 가져야 하고, 마지막으로 사용된 논스보다 1큰 값이 아니면 트랜잭션을 받아 들이지 않는다는 점이다. 이 것은 더 이상 사용되지 않는 계정도 어카운트 스테이트에서 제거될 수 없다는 것을 의미한다.

Merkle Patricia Tree 링크

이더리움의 기본 데이터 구조. 모든 어카운트의 상태와 트랜잭션, 영수증들을 각각의 블록에 저장한다. 머클 트리와 패트리샤 트리를 결합한 것으로 각각의 엘리먼트들을 이용하여 아래와 같은 속성들을 가지는 구조를 생성한다.

  1. 루트 해시에 유일하게 매핑되는 고유의 키-밸류 쌍.
  2. 키-밸류 쌍의 추가, 수정, 삭제는 대수적인 (logarithmic) 시간 내에 처리됨. 즉 최대 소요 시간이 일정한 한도 내에서 유지된다

이는 모든 상태 트리에 대해 효율적이고 쉽게 갱신할 수 있는 “fingerprint”를 제공할 수 있게 한다. 이더리움 MPT는 여기에 기술되어 있다.

MPT는 아래와 같은 설계 결정사항을 포함한다.

  1. 두 가지 클래스의 노드들. kv 노드와 diverge 노드. kv 노드는 64단계의 트리를 탐색할 필요 없이 해당 노드로 가는 “shortcut”으로 동작한다. 이는 효율성을 높인다
  2. Diverge노드는 hexary이고 binary가 아님: 이 것은 lookup 효율성을 높이기 위해 결정되었음. 하지만 최적의 상태는 아닌 것이 확인되어 1.1에서는 재구성할 예정.
  3. 빈 값과 non-membership간의 차이 없음: 단순성을 위한 결정. 예를 들어 잔고에 값이 설정되지 않았을 경우 0으로 간주. 이더리움의 기본값에서 잘 동작하지만 일반성을 희생시키는 것은 사실.
  4. 종단 노드와 비종단 노드의 구분: 기술적으로 종단 노드임을 표시하는 플래그는 불필요함. 이더리움의 모든 트리는 고정된 키 길이를 가지기 때문. 하지만 일반성을 높이기 위해 추가되었고, 이더리움의 MPT 구현이 그대로 다른 암호화 프로토콜에도 적용되기를 기대함.
  5. “안전한 트리”의 키로서 sha3(k)를 사용: 상태와 어카운트 저장소 트리에 사용됨.

RLP (Recursive length prefix)

이더리움의 메인 직렬화 포맷. 블록, 트랜잭션, 어카운트 상태, 와이어 프로토콜 메시지등 모든 부분에 사용됨. 매우 최소화된 직렬화 포맷으로 유일한 목적은 중첩된(nested) 바이트 어레이를 저장하는 것임. protobuf, BSON등과는 다리 RLP는 어떠한 데이터 타입도 정의하지 않음.

구조를 중첩된 배열의 형태로 저장하고, 각 배열의 의미를 결정하는 것은 프로토콜에게 맡김. 키/밸류 맵 역시 명시적으로 지원되지 않음. RLP를 사용하는 이유는

  1. 구현의 단순성
  2. 바이트 단위의 확실한 일관성 확보

많은 언어에서 키/밸류 맵은 명시적인 순서를 가지지 않고, 부동 소수점 포맷은 많은 특별한 경우를 가지고 있음. 이로 인해 동일한 데이터가 다른 인코딩으로 만들어지고 결과적으로 다른 해시를 가지게 될 가능성이 있음. 프로토콜을 직접 만듦으로써 이러한 목표들에 맞게 설계되었는지를 확실히 할 수 있음

Compression Algorithm

와이어 프로토콜과 데이터베이스 모두 데이터를 저장할 대 커스텀 압축 알고리듬을 사용함. 이 알고리듬은 run-length-encoding zero로. 0이 아닌 다른 값들은 그대로 내버려 둠. (sha3(”)와 같은 일부 예외 제외)

Trie Usage

이더리움 블록체인의 각 블럭 헤더는 세 트리에 대한 포인터를 가진다.

  1. 상태트리 : 블록 액세스 후의 전체 상태 표시
  2. 트랜잭션 트리: 인덱스를 키로 사용하는 블록의 모든 트랜잭션 표시 (key 0: 첫번째 트랜잭션, key1: 그 다음 트랜잭션..)
  3. 영수증 트리: 각 트랜잭션에 해당하는 영수증 표시. 트랜잭션의 영수증(receipt)은 RLP 인코딩 된 자료구조이다.

[ medstate, gas_used, logbloom, logs]

  • medstate: 트랜잭션을 처리한 후 상태 트리의 루트
  • gas_used: 트랜잭션 처리가 끝난 후 사용된 개스의 양
  • logs: [address, [topic1, topic2 ..], data] 형태를 가지는 아이템들의 리스트. 트랜잭션의 실행중 LOG0 .. LOG4 opcode로 생성되는 로그. address는 컨트랙트의 주소. topic은 최대 4개까지의 32바이트 값이며, 데이터는 임의 길이의 바이트 배열임
  • logbloom: 트랜잭션에 포함된 모든 로그의 주소와 토픽으로 구성된 블룸 필터

블록 헤더에는 또한 블룸이 있는데, 이는 블록에 포함된 트랜잭션들의 모든 블룸을 OR 연산한 것이다. 이 구조의 목적은 이더리움 프로토콜을 경량 클라이언트에 대해 가능한 많은 부분에서 친화적으로 만들기 위함이다.

Uncle Incentivization

“Gredy Heaviest Observed Subtree” (GHOST) 프로토콜은 Yonatan Sompolinsky와 Aviv Zohar가 2013년 겨울에 발표한 것으로 더 빠른 블록 시간을 방해하는 문제들을 풀기 위한 시도이다. 현재 블록체인의 빠른 승인 시간은 높은 stale rate로 인해 보안성이 낮아진다는 문제가 있다. 왜냐하면 블록이 네트워크를 통해 전파되기 위해서는 일정 시간이 필요하기 때문이다. 예를 들어 마이너 A가 블록을 채굴하고, 이 블록이 전파되기 전에 마이너 B가 블록을 채굴한 경우, 마이너 B의 블록은 버려지고(stale) 이는 네트워크의 안정성에 기여하지 못한다. 또한 이는 중앙화 이슈가 있는데, 만약 마이너 A가 마이닝 풀이며 30%의 해시 파워를 가지고 있고, 마이너 B가 10%의 해시 파워를 가진다면, A는 전체 시간 중 70%의 시간에서 stale 블록을 생성할 가능성이 있지만 (30%, 자신이 생성한 블록은 즉시 전파될 것이므로) B는 90%의 시간동안 리스크를 가지게 된다.

그러므로 블록 인터벌이 높은 stale rate를 가지게 될 만큼 짧다면 A는 실질적으로 단순히 그 규모로 인해 훨씬 효율적이 될 수 있다. 이런 이유로 블록을 빠르게 생성하는 블록체인은 많은 해시 파워를 가진 한 마이닝 풀이 마이닝 프로세스에 있어서 실질적인 통제력을 가지게 될 수 있다.

GHOST는 stale block을 어느 체인이 가장 긴 가를 결정하는 계산에 포함시킴으로써 첫 번째 이슈를 풀고 있다. 즉 블록의 부모와 그 조상들 뿐만아니라, 블록 조상의 stale decendants도 포함시키는 것 (Ethereum에서는 “uncle”이라 부름)

중앙화 이슈의 경우 이더리움에서는 다른 전략을 적용하는데, 블록 보상을 stale 에도 지급한다. stale block은 기본 보상의 7/8 (87.5%)를 지급하고, stale block을 포함하는 nephew는 1/32(3.125%)를 포함 포상금으로 지급한다.  트랜잭션 수수료는 uncles나 nephews에게 지급되지 않는다.

이더리움에서는 stale block으로 7번째 세대까지만 포함된다. 무한히 늘릴 경우 계산이 너무 복잡해지고, 마이너로 하여금 메인 체인에서 채굴할 동기를 없애기 때문이다. 시뮬레이션 결과 7번째 레벨로 제한하는 것이 지나치게 큰 부정적 결과 없이 원하는 결과를 끌어낼 수 있는 것으로 확인되었다.

블록 시간 알고리즘에 대한 결정은 다음의 내용을 포함한다.

  • 12초의 블록 시간: 12초는 네트워크 레이턴시보다는 충분히 크면서 가능한 빠른 인터벌 시간이다
  • 7 블록 조상 제한: 이 것은 블록 히스토리를 일정 수의 블록이 지난 후에 “잊을 수 있도록” 하기 위함이다. 7 블럭은 원하는 효과를 얻기에 충분하다.
  • 1 블록 자손 제한: 단순성 목표를 위한 설계.
  • Uncle 유효성 요구사항: Uncle들은 유효한 헤더를 가지고 있어야 함.
  • 보상 분배 : Uncle의 경우 기본 보상의 7/8. Nephew의 경우 1/32. 둘 다 트랜잭션 수수료는 없음

Difficulty Update Algorithm

diff(genesis) = 2^32

diff(block) = diff.block.parent + floor(diff.block.parent / 1024) *
    1 if block.timestamp - block.parent.timestamp < 9 else     -1 if block.timestamp - block.parent.timestamp >= 9

설계 목표

  1. 빠른 업데이트: 블록간의 시간은 해시파워에 따라 빠르게 업데이트 되어야 함
  2. 낮은 변동성: 난이도는 해시파워가 균등할 경우 급격히 바뀌지 않아야 함
  3. 단순성: 구현이 상대적으로 단순한 알고리즘이어야 함
  4. 낮은 메모리: 알고리즘은 몇 블럭 이상의 이력에 의존해서는 안 되며, 가급적 적은 “메모리 변수”를 포함해야 함
  5. Non-exploitability: 알고리즘은 마이너로 하여금 타임스탬프를 조작하거나 마이닝 풀이 수익 극대화를 위해 반복적으로 해시 파워를 추가하고 제거하는 일을 장려해서는 안 됨

Gas and Fees

비트코인에서의 모든 트랜잭션은 거의 비슷하고, 네트워크상의 비용이 하나의  단위로 계산될 수 있는 반면에, 이더리움에서의 트랜잭션은 훨씬 복잡함. 그렇기 때문에 트랜잭션 수수료 시스템은 다양한 요소들을 고려해야 함.  특히 중요한 것은 이더리움의 프로그래밍 언어가 튜링 컴플리트하다는 점인데, 그렇기 때문에 하나의 트랜잭션의 네트워크 대역, 스토리지, 연산 사용량은 달라질 수 있음. 또한 무한 루프를 통한 DoS 공격을 막는 것 역시 중요한 목표임

기본 메커니즘

  • 모든 트랜잭션은 사용할 의사가 있는 “gas”의 양(startgas)과 단위 gas당 지불할 가격(“gasprice”)을 반드시 지정해야 함. 실행이 시작될 때 startgas * gasprice 만큼의 이더가 트랜잭션 발신자의 계좌에서 삭제됨
  • 데이터베이스를 읽고 쓰고, 메시지를 보내고, 가상머신에서 실행되는 모든 연산 단계와 같은 트랜잭션 실행 중의 모든 오퍼레이션은 일정량의 gas를 사용함
  • 트랜잭션이 완전히 종료되었는데 지정된 한도보다 적은 gas를 사용한다면, 남은 gas(gas_rem )는 트랜잭션 종료와 함께 트랜잭션 발신자에게 반환됨. 그리고 블록의 마이너는 (startgas -gas_rem) * gasprice 만큼을 보상으로 방게 됨
  • 만약 트랜잭션 실행 도중에 gas가 떨어지게 되면 모든 실행은 원상복귀됨. 하지만 트랜잭션 자체는 그럼에도 불구하고 유효하며 그 결과로 전체 startgas * gasprice만큼의 이더는 마이너에게 지급됨
  • 컨트랙트가 다른 컨트랙트에 메시지를 보내는 경우, 이러한 sub excution을 위해 gas limit을 설정할 수 있음. 만약 sub-execution 실행 중 gas가 떨어지면 sub-execution은 원상복원되지만 gas는 그래도 소비됨

gas cost 관련

  • 21000 gas는 모든 트랜잭션의 기본 요금. 이 값은 서명에서 발신자 주소를 복원하는 elliptic curve 연산과 트랜잭션의 저장을 위한 디스크와 네트워크 비용임
  • 트랜잭션은 무제한의 데이터를 가질 수 있음. 데이터에 대한 비용은 값이 0인 바이트의 경우 4 gas, 그렇지 않은 경우 68gas.
  • SSTORE 연산 (값을 계정의 스토리지에 보관)은
    • 0인 값을 0이 아닌 값으로 변경할 때 20000gas
    • 0에서 0으로, 또는 0이 아닌 값에서 0이 아닌 값으로 변경시 5000gas
    • 0이 아닌 값에서 0으로 변경시 5000gas
    • 성공적으로 트랜잭션 실행이 종료되면 20000gas 환불 (환불은 사용한 전체 gas의 50% 캡을 가지고 있음. 트랜잭션의 전체 소모 gas가 30,000이면 15,000이 환불 한계)
  • 컨트랙트가 제공하는 데이터의 메시지에는 과금 없음
  • 메모리는 무한히 확장 가능한 배열임. 하지만 32바이트당 1gas가 소모됨
  • 인자의 값에 따라 연산 시간이 크게 달라지는 opcode의 경우 (예: EXP) 인자의 값에 따라 소모되는 gas가 달라짐

Virtual Machine

Ethereum Virtual Machine은 트랜잭션 코드가 실행되는 엔진임. 그리고 이더리움을 다른 시스템과 차별화하는 요소이기도 함.

설계 목표

  • 단순성: 가급적 적은, 그리고 가급적 로우레벨 opcodes, 가급적 적은 데이터 타입
  • 전체적인 확정성: VM 스펙의 어느 부분에도 모호한 부분이 없어야 함. 또한 연산 스텝에 대한 정확한 개념이 조재해야 함. 이 스텝들로 개스 소모를 계산하게 됨
  • 공간 절약: EVM 어셈블리는 가능한 컴팩트 해야 함 (예: 기본 C 프로그램의 4000byte 기본 사이즈는 받아 들일 수 없음)
  • 특화 기능: 20바이트 주소 처리, 32바이트 값을 가지는 암호화 처리 능력, 커스텀 암호화에 사용되는 모듈러 연산, 블록과 트랜잭션 데이터 읽기, 상태 정보 확인 등
  • 단순한 보안: gas cost 모델에 대응하기 쉬워야 함. 이는 VM을 non-exploitable하게 만듦
  • 최적화 용이성: 최적화를 적용하기 쉬워야 함. JIT 컴파일되거나 다른 속도를 향상시키 버전의 VM이 만들어 질 수 있도록.

주요 설계 결정

  • 임시/영구 스토리지 구분: 임시 스토리지와 영구 스토리지의 구분이 존재함. 임시 스토리지에 저장된 값은 해당 인스턴스 안에서만 유효. 영구 스토리지는 해당 컨트랙트 전체에 유효.
  • 스택/메모리 모델: 세 가지 유형의 computational state를 가짐. Stack/ Memory / Storage.
  • 32바이트의 워드 크기: 비트코인의 경우 제한이 없음. 4, 8바이트 워드 등은 너무 작아 비효율적임. 32바이트는 많은 크립토 구현에서 사용되는 값들을 보관하기에 충분함. 반면 제한이 없는 경우 안전한 gas model을 만들기가 어려움
  • 독자적인 VM을 개발: JAva, Lisp dialect, Lua 등을 사용할 수도 있겠으나 독자적인 VM을 만들기로 결정함. 그 이유는 다음과 같음
    • 요구되는 VM spec이 다른 것들에 비해 훨씬 단순함
    • VM을 요구사항에 맞게 좀 더 특화시킬 수 있음. 예: 32byte word size
    • 복잡한 외부 dependency를 없앨 수 있음. dependency가 많아지면 설치가 매우 어려워짐
  • 가변적인 메모리 크기
  • 스택 크기에 제한 없음
  • 1024 call depth 제한
  • 타입 없음: 단순성을 위해. 대신 signed, unsigned에 대응하는 별도의 opcode 제공 (예: DIV, SDIV, MOD, SMOD)