‘마스터링 이더리움’ 세미나 5-1 – 7장. 스마트 컨트랙트와 솔리디티

이번 세미나에서는 ‘7장. 스마트 컨트랙트와 솔리디티’를 다룹니다. 솔리디티를 전반적으로 다루기 때문에 분량이 꽤 많이 됩니다. 한 번에 다 읽어 내려고 하면 지칠 수 있고, 실습을 제대로 해 보지 않을 수 있습니다. 하루에 조금씩 진도를 빼기 위한 계획을 세우고 실천하는 것이 중요한 장입니다.

 

아래 요약한 내용을 이해하고 기억하도록 합니다.

  • 이더리움에는 두 가지 유형의 계정이 있다.
    • 외부 소유 계정(EOA), 컨트랙트 계정
    • 두 가지 계정 모두 이더리움 주소로 식별
  • 외부 소유 계정
    • 이더리움 플랫폼의 외부에 있는 지갑 애플리케이션 같은 소프트웨어를 통해 사용자가 제어한다.
    • 개인키로 생성되고 서명된 거래에 의해 제어된다.
    • 관련 코드나 데이터 저장소가 없는 간단한 계정이다.
  • 컨트랙트 계정
    • 이더리움 가상 머신에 의해 실행되는 프로그램 코드(일반적으로 ‘스마트 컨트랙트’라고 함)가 제어한다.
    • 컨트랙트 계정은 관련 코드와 데이터 저장소를 모두 가진다.
    • 컨트랙트 계정은 개인키를 갖지 않으므로 스마트 컨트랙트에 규정된 미리 결정된 방식으로 ‘스스로 제어’한다.

스마트 컨트랙트란 무엇인가?

아래 인용한 본문 내용을 다시 한 번 주의 깊게 읽어 봅니다. 스마트 컨트랙트가 무엇인지 본문 내용에 충실하게 설명할 수 있도록 합니다. 특히 ‘불변적인’과 ‘결정론적으로’의 의미를 분명히 이해하고 설명할 수 있어야 합니다.

이더리움의 컨텍스트에서 이더리움 스마트 컨트랙트는 스마트하지도 않고 법적 컨트랙트도 아니기 때문에 다소 잘못된 이름임에도 이미 확고하게 자리를 잡았다.

이 책에서는 ‘스마트 컨트랙트’라는 용어를 불변적인(immutable) 컴퓨터 프로그램을 지칭하는데, 이 프로그램은 이더리움 네트워크 프로토콜(즉, 탈중앙화된 이더리움 월드 컴퓨터)의 일부인 이더리움 가상 머신의 컨텍스트상에서 결정론적으로(deterministically) 작동한다.

 

스마트 컨트랙트는 컴퓨터 프로그램입니다. 스마트 컨트랙트 코드는 일단 배포되면 변경할 수 없습니다. 불변적입니다.

스마트 컨트랙트는 불변적이어야 하는데 인간이 하는 일에 완벽이란 달성하기 어려운 목표이고 소프트웨어 개발에서는 특히 그렇습니다. 현실적인 이유로 업그레이드 가능한 방법이 요구되고, 업그레이드 가능한 스마트 컨트랙트 작성 방법들이 제시되고 있습니다. 지금 시점에서는 무조건적으로 ‘스마트 컨트랙트는 불변적이다’라고 말하기는 어렵습니다.

스마트 컨트랙트는 입력이 같으면 출력이 같은 함수처럼 결정론적입니다. 어떤 노드에서 실행하더라도 스마트 컨트랙트를 실행한 트랜잭션 컨텍스트와 실행 시점에서의 이더리움 블록체인 상태가 같으면 실행 결과는 같습니다.

스마트 컨트랙트는 매우 제한된 실행 컨텍스트(EVM 컨텍스트)에서 동작합니다. 스마트 컨트랙트의 상태와 스마트 컨트랙트를 실행한 트랜잭션 컨텍스트와 가장 최근의 블록들에 대한 일부 정보에만 접근할 수 있습니다.

모든 이더리움 노드들은 각각이 로컬 인스턴스로 EVM을 실행하지만, 결정론적으로 스마트 컨트랙트를 실행하기 때문에 전체가 단일 월드 컴퓨터 처럼 작동합니다.

스마트 컨트랙트의 생명주기

아래 요약한 내용을 분명하게 이해하고 기억하도록 합니다.

  • 스마트 컨트랙트는 솔리디티 같은 고급 언어로 작성, EVM에서 실행되기 위해서는 저수준의 바이트코드로 컴파일되어야 한다.
  • 컴파일된 스마트 컨트랙트는 컨트랙트 생성 트랜잭션을 사용해서 이더리움 플랫폼에 배포된다.
    • 트랜잭션의 수신 주소는 컨트랙트 생성 주소인 0x0이어야 한다.
  • 컨트랙트가 생성되면 컨트랙트에는 고유한 이더리움 주소가 할당된다. 스마트 컨트랙트는 이 주소로 식별된다. 이 주소로 이더를 보내거나 컨트랙트 실행을 요청할 수 있다.
  • 스마트 컨트랙트는 트랜잭션에 의해 호출된 경우에만 실행된다. 컨트랙트는 자체적으로 또는 백그라운드에서 실행되지 않는다. 스마트 컨트랙트는 체인의 일부분으로 트랜잭션에 의해 직접 또는 간접적으로 호출되기 전까지 대기 상태에 놓여 있다. 스마트 컨트랙트는 병렬적으로 실행되지 않는다.
  • 트랜잭션은 원자성을 갖는다. 전체 실행이 성공적이지 않으면 실패로 처리된다. 실패한 트랜잭션은 여전히 시도된 것으로 기록되며, 실행을 위해 소비된 가스에 해당하는 이더 또한 차감된다.
  • 컨트랙트 코드는 변경할 수 없으나 삭제 할 수 있다. 컨트랙트를 삭제하면 해당 주소에서 코드와 내부 상태(스토리지)를 제거하고 빈 계정으로 남김으로 자원을 반환하는 효과가 있다. 컨트랙트를 삭제하려면 SELFDESTRUCT라는 EVM 연산코드를 실행해야 한다. SELFDESTRUCT 기능은 컨트랙트 작성자가 해당 기능을 프로그래밍한 경우에만 사용할 수 있다. 컨트랙트를 삭제하면 가스 환불이 일어난다.

이더리움 고급 언어의 소개

스마트 컨트랙트를 작성하는데에는 부작용(side-effect)이 없는 선언형 프로그래밍 언어가 더 적당합니다. 하지만 많은 수의 개발자들이 명령형 프로그래밍 언어를 사용하고 있는 현실을 무시하기는 쉽지 않습니다. 현실을 반영하듯이  대표적인 이더리움 스마트 컨트랙트 프로그래밍 언어인 솔리디티 또한 명령형 프로그래밍 언어입니다.

솔리디티로 스마트 컨트랙트 생성

솔리디티는 프로그래밍 언어입니다. 다른 프로그래밍 언어(+ 개발환경)와 마찬가지고 기본적인 개념을 익히고 실제적인 경험을 쌓아야만 제대로된 프로그램을 작성할 수 있습니다.  대충 훑어보기식으로는 안 됩니다.

솔리디티는 블록체인 이라는 탈중앙화된 환경에서 가치 이동을 다뤄야 하는 프로그래밍 언어입니다. 단순히 프로그래밍 경험 만을 의지해서 무턱대고 덤벼들어서도 안 됩니다. 블록체인에 대한 기본적인 이해 및 탈중앙화된 환경에서 가치 이동이 갖는 의미와 이를 프로그래밍 하고 실행하는 의미를 분명하게 이해해야 합니다.

 

아래 요약한 내용들을 읽고 다시 한 번 내용을 정리합니다.

 

솔리디티의 버전 선택

아래 요약한 내용을 다시 한 번 확인합니다.

  • 솔리디티의 버전은  Major.Minor.Patch와 같이 시맨틱 버저닝에 따라 작성한다.
    • The “major” number is incremented for major and backward-incompatible changes, the “minor” number is incremented as backward-compatible features are added in between major releases, and the “patch” number is incremented for backward-compatible bug fixes.
  • 프로젝트 초기 개발을 위한 메이저 번호는 0이다.  프로젝트 초기 개발 단계에서 메이저 번호는 변경하지 않고 마이너와 패치 번호만 바뀐다. 실제로 솔리디티는 마이너를 메이저 버전인 것처럼 처리하고 패치 번호를 마이너 버전인 것처럼 처리한다. 따라서 0.4.24에서 4를 메이저 버전으로 24를 마이너 버전으로 간주한다.

 

개발 환경

개발환경은 중요합니다. 개발환경에 대한 자세한 사항은 다음 세미나인 ‘이더리움 댑 개발’에서 자세히 다루겠습니다. 이번 세미나에서는 리믹스를 사용하는 수준으로 진행하겠습니다.

 

단순한 솔리디티 프로그램 작성

예제 7-1을 리믹스에서 직접 작성하고 컴파일 해 봅니다.

이더리움 컨트랙트 ABI

아래 인용한 본문 내용을 다시 한 번 확인합니다. 일부 내용은 내용의 맛을 살리기 위해 원문을 인용했습니다.

애플리케이션이 컨트랙트와 상호작용하는 데 필요한 것은 ABI와 컨트랙트 주소다.

An ABI(Application Binary Interface) defines how data structures and functions are accessed in machine code. The ABI is thus the primary way of encoding and decoding data into and out of machine code.

In Ethereum, the ABI is used to encode contract calls for the EVM and to read data out of transactions. The purpose of an ABI is to define the functions in the contract that can be invoked and describe how each function will accept arguments and return its result.

A contract’s ABI is specified as a JSON array of function descriptions and events. A function description is a JSON object with fields type, name, inputs, outputs, constant, and payable. An event description object has fields type, name, inputs, and anonymous.

 

솔리디티 컴파일러 및 언어 버전 선택

솔리디티는 빠르게 진화하는 언어로 예상치 못한 방식으로 변경될 수 있습니다. 따라서 컨트랙트를 작성한 솔리디티 버전과 컴파일러 버전이 맞춰줘야 합니다. pragma라고 하는 컴파일러 지시문을 사용해서 컨트랙트 작성시에 솔리디티 버전을 명시해야 합니다.

‘pragma solidity ^0.4.19’와 같이 버전 앞에 ‘^’를 사용할 수 있는데, 이것의 의미는 마이너 버전(버전의 마지막 숫자) 간에는 컴파일을 허용한다는 것을 나타냅니다. 앞의 예에서 컴파일러는 0.4.20은 허용하지만 0.5.0은 허용하지 않습니다.

솔리디티로 프로그래밍 하기

솔리디티는 실험적 언어로 매우 빠르게 버전 업 되고 있습니다. 본문은 0.4.x 버전을 사용하고 있는데 0.5.x 버전에서 큰 폭의 변화가 있었고, 현재 버전은 0.8.x 버전입니다.

 

본문 내용을 빠르게 읽으면서 어떤 내용이 있는지를 확인하고, 실제적인 학습은 최신 공식 문서 내용에 따라 장기적인 학습 계획을 세우고 실천해 나가도록 합니다.

 

다른 프로그래밍 언어들과 비교해 봤을 때 솔리디티가 갖는 특별한 점들에 주의합니다. 아래 언급한 부분들을 다시 한 번 확인해 봅니다.

  • 데이터 타입
    • 주소 타입, 매핑 타입
  • 다양한 단위를 표현하는 리터럴 제공
    • 시간 단위
      • seconds, minutes, hours, days, seconds
    • 이더 단위
      • ether, wei
  • 사전 정의된 글로벌 변수 및 함수
    • msg
      • 메시지 객체
        • 컨트랙트 실행을 시작한 트랜잭션 호출(EOA 발신) 또는 메시지 호출(컨트랙트 발신)
      • msg.sender
        • 컨트랙트 호출을 시작한 주소
          • 컨트랙트도 다른 컨트랙트를 호출할 수 있기 때문에 EOA 주소나 컨트랙트 주소가 될 수 있다.
          • 컨트랙트에서 다른 컨트랙트를 호출할 때마다 msg의 모든 속성 값이 새 발신자의 정보를 반영하도록 변경된다는 점에 주의해야 한다. 원래 msg 컨텍스트 내에서 다른 컨트랙트/라이브러리의 코드를 실행하는 delegatecall 함수는 예외다.
      • msg.value
        • 컨트랙트 호출과 함께 전송된 이더의 값(웨이)
      • msg.gas
        • 남은 가스양, 0.4.21에서는 gasleft로 대체
      • msg.data
        • 데이터 페이로드
      • msg.sig
        • 함수 선택자인 데이터 페이로드의 처음 4바이트
    • tx
      • 트랜잭션 객체
      • tx.gasprice
        • 트랜잭션을 호출하는 데 필요한 가스 가격
      • tx.origin
        • 원래 EOA 주소, 안전하지 않다.
    • block
      • 블록 객체
      • block.blockhash(blockNumber)
      • bock.coinbase
        • 채굴자 주소
      • block.difficulty
      • block.gaslimit
      • block.number
      • block.timestamp
    • address
      • 입력으로 전달되거나 컨트랙트 객체에서 형변환되는 주소 객체
      • address.balance
      • address.transfer(amount), address.send(amount)
        • transfer는 오류가 발생하면 예외를 발생시키고, send는 false를 리턴한다.
      • address.call(payload), address.callcode(payload)
        • 오류가 발생하면 false를 리턴한다. call은 안전하지 않고, callcode는 사용에 주의를 요한다.
      • address.delegatecall()
        • callcode(payload)
    • 내장 함수
      • addmod, mulmod – 모듈로 연산
      • keccak256, sha256, sha3, ripemd160 – 해시 함수
      • ecrecover
        • 서명에서 주소를 복구
      • selfdestruct(recipient_address)
        • 컨트랙트 삭제, 해당 주소로 이더를 환불해 준다.
    • this
      • 현재 실행 중인 컨트랙트 계정 주소
  • 컨트랙트 정의
    • contract, interface, library
      • library는 delegatecall을 사용하여 한 번만 배포되고 다른 컨트랙트에서 사용되기 위한 컨트랙트이다.
  • 함수
      • 컨트랙트 마다 한 개의 함수는 이름이 없이 정의될 수 있다. 폴백 함수라 부르고, 함수 이름이 명시되지 않을 때 호출된다. 인수도 없고 반환 값도 없다.
      • external은 명시적으로 키워드 this가 앞에 붙지 않는 한 컨트랙트 내에서 호출할 수 없다. internal은 컨트랙트 내에서와 파생 컨트랙트에서만 접근할 수 있다.
      • 상태를 변경하지 않는다는 것을 view로 명시한다.
      • 스토리지에서 변수를 읽거나 쓰지 않는다는 것을 pure로 명시한다.
      • 입금을 받을 수 있음을 payable로 명시한다.
  • 컨트랙트 생성자 및 selfdestruct
    • 생성자는 컨트랙트가 생성될 때 실행된다. v0.4.22 부터는 컨트랙트 이름 대신 constructor라는 키워드를 사용
  • 함수 modifier
    • 파이썬의 데코레이터에 해당합니다.
  • 컨트랙트 상속
    • is 키워드 사용, 다중 상속 지원
  • assert, require
    • 조건을 평가하고 조건이 거짓이면 에러로 실행을 중지한다.
    • 통상적으로 assert는 결과가 참일 것으로 예상될 때 사용한다. require는 입력값이 설정한 조건의 기댓값에 맞는지 테스트할 때 사용한다.
  • 이벤트
    • When a transaction completes (successfully or not), it produces a transaction receipt. The transaction receipt contains log entries that provide information about the actions that occurred during the execution of the transaction. Events are the Solidity high-level objects that are used to construct these logs.
    • 이벤트는 라이트 클라이언트와 댑에서 유용한데, 특정한 이벤트가 일어나는지 감시해서 사용자 인터페이스에 반영하거나, 해당 컨트랙트상의 이벤트에 대응되는 변화를 애플리케이션의 상태에도 반영되도록 할 수 있기 때문이다.
    • 이벤트 객체는 인수들을 취할 수 있는데, 이것은 시리얼라이즈되어서 블록체인상의 트랜잭션 로그에 기록된다. 인수 앞에 indexed 키워드를 붙여서 애플리케이션에서 검색하거나 필터링할 수 있는, 인덱싱된 테이블 값으로 만들 수 있다.
    • 트랜잭션 로그에 이벤트 데이터를 집어넣기 위해서(이벤트를 발생시키기 위해서) emit 키워드를 사용한다.
  • 이벤트 받기
    • web3.js는 트랜잭션 로그를 포함하는 데이터 구조를 제공한다.
  • 다른 컨트랙트 호출
    • 여러분의 컨트랙트에서 다른 컨트랙트를 호출하거나 다른 컨트랙트에서 여러분의 컨트랙트를 호출할 수 있다. 위험은 다른 컨트랙트에 대해 잘 모른다는 사실에서 발생한다.
    • 다른 컨트랙트를 호출할 때는 new 키워드를 사용한다.
      • 인스턴스 생성 시 선택적으로 이더를 전송할 수 있다.
        • _faucet = (new Faucet).value(0.5 ether)();
    • 주소를 컨트랙트 생성자의 인자로 전달해서 기존의 컨트랙트 인스턴스를 구할 수 있다.
      • _faucet = Faucet(addr);
    • 저수준 함수를 사용해서 다른 컨트랙트를 호출할 수 있다 – delegatecall.

 

가스 고려사항

아래 요약한 내용을 읽어 봅니다.

  • 이더리움은 가스로 트랜잭션의 최대 계산량을 제한한다.
  • 가스 한도를 초과하면 다음과 같은 일들이 일어난다.
    • 가스 부족 예외 발생
    • 실행 전의 컨트랙트 상태 복원(복구)
    • 실행을 위해 사용된 가스는 수수료로 간주되어 환불되지 않음
  • 가스 비용을 최소화하기 위한 지침들
    • 동적 크기 배열은 피한다.
      • Any loop through a dynamically sized array where a function performs operations on each element or searches for a particular element introduces the risk of using too much gas.
      • 실제로 컨트랙트가 원하는 결과를 찾기 전에 혹은 모든 요소에 적용하기 전에 가스가 소진될지도 모른다. 그러면 아무런 결과 없이 시간과 이더를 낭비하는 셈이다.
    • 다른 컨트랙트 호출 피하기
      • 잘 테스트되지 않고 광범위하게 사용되지 않는 라이브러리는 사용을 피해라.
      • 다음과 같이 가스 비용을 추정할 수 있다. 대부분의 경우 상당히 정확한 예상치를 제공한다.
  • 메인넷에 컨트랙트를 배포할 때는 예상치 못한 가스 비용이 들어가는 경우가 발생하지 않도록 개발의 일부로 함수의 가스 비용을 평가하는 것이 좋다.

 

About the Author
(주)뉴테크프라임 대표 김현남입니다. 저에 대해 좀 더 알기를 원하시는 분은 아래 링크를 참조하세요. http://www.umlcert.com/kimhn/

Leave a Reply

*