마스터링 이더리움(Mastering Ethereum) 7장 - 스마트 컨트랙트와 솔리디티

JinJinJJara·2021년 1월 15일
0

Mastering_Ethereum

목록 보기
3/11

스크립토 6기 하진원입니다.
스크립토 방학 스터디로 마스터링 이더리움 공부하고 있습니다.

솔리디티 버전 0.4.19로 작성

정의

이더리움 네트워크 프로토콜의 일부인 EVM 컨텍스트 상에서 결정론적으로 작동하는 불변적 컴퓨터 프로그램

  • 불변의 컴퓨터 프로그램 : ‘컨트랙트’ 단어가 주는 일반적인 느낌과 다르게 컴퓨터 프로그램이고 일반적인 소프트웨어와 다르게 배포되면 변경될 수 없다.
  • 결정론적 : 스마트 컨트랙트를 실행한 결과물을 모두에게 동일하게 하기 위해 실행 시작한 트랜잭션의 컨텍스트와 실행 당시의 이더리움 블록체인의 상태가 같다는 전제.

생성

솔리디티로 작성 -> 바이트코드로 컴파일 -> 컨트랙트 생성 트랜잭션 통해 이더리움 플랫폼에 배포

사용

트랜잭션으로 호출된 경우 실행. 자체적으로, 백그라운드에서 실행되지 않는다.
컨트랙트가 성공적으로 종료된 경우에만 글로벌 상태의 모든 변경사항이 기록되고 전체가 실행.
컨트랙트 코드는 변경할 수 없지만 삭제할 수 있다. SELFDESTRUCT EVM 코드를 통해 삭제 가능하지만 이를 위해서는 컨트랙트를 프로그래밍할 때 해당 코드를 내장시켜놓아야 하고 그렇지 않은 경우에는 삭제할 수 없다.

이더리움 고급언어

바이트코드로 프로그래밍하는 것은 힘들기 때문에 대부분 고급언어 사용.
특수한 환경에서 특수한 기능을 사용하기 때문에 스마트 컨트랙트 언어를 만듦!
스마트 컨트랙트에서 버그는 실제 비용이 발생하기 때문에 버그 없는 것이 중요.
대부분 솔리디티 사용!

바이트코드 예시

608060405260043610601f5760003560e01c80632e1a7d4d146022576020565b5b005b348015602d57600080fd5b50605760048036036020811015604257600080fd5b81019080803590602001909291905050506059565b005b6509184e72a000811115606b57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f1935050505015801560b0573d6000803e3d6000fd5b505056fea26469706673582212208c8365e595efb620418e18a3e1188eadd7a670c691bc208cbeb4f8a1120dd2fa64736f6c63430007040033

ABI

정의

데이터 구조와 함수가 어떻게 기계 코드에서 사용되는지 방법 정의.
EVM에서 컨트랙트 호출을 인코딩, 트랜잭션에서 데이터를 읽는 데 사용.

특성

ABI는 함수설명, 이벤트의 JSON 배열로 지정된다.
ABI를 통해 해당 컨트랙트에 접근하려는 어플리케이션은 올바른 인수, 인수 유형을 사용하여 트랜잭션을 생성할 수 있다.
애플리케이션이 컨트랙트와 상호작용할 때 ABI와 컨트랙트가 배포된 주소가 필요하다.

데이터 타입

매핑

키-값 쌍에 대한 조회
Mapping(KEY_TYPE => VALUE_TYPE)

시간 단위

단위들을 seconds의 배수로 변환하여 사용

이더 단위

Wei, finney, Szabo, ether을 wei의 배수로 변환하여 사용

require(withdraw_amount <= 100000000000000000)
require(withdraw_amount <= 0.1ether)

외에도 다양한 데이터 타입이 있다.

사전 정의된 글로벌 변수

msg

컨트랙트 실행을 시작한 트랜잭션 혹은 메시지 호출.

  • msg.sender
    발신자의 주소
  • msg.value
    전송된 이더의 값
  • msg.data
    호출의 데이터 페이로드(전송되는 데이터)
  • msg.sig
    데이터 페이로드의 처음 4바이트

tx

트랜잭션 관련 정보에 접근하는 방법 제공

  • tx.gasprice
    트랜잭션 호출하는데 필요한 가스 가격
  • tx.origin
    트랜잭션에 대한 원래 EOA의 주소

block

블록에 대한 정보

  • block.coinbase
    블록 보상 수취인의 주소
  • block.difficulty
    블록 작업증명의 난이도
  • block.number
    블록 번호
  • block.timestamp
    채굴자가 블록에 넣은 타임스탬프

address

주소에 대한 정보

  • address.balance
    주소의 잔액
  • address.transfer(amount)
    주소로 전송. 오류 발생시 예외 발생.
    주로 send보다 transfer을 사용한다!
  • address.send(amount)
    주소로 전송. 오류 발생시 false 반환.
    (transfer을 주로 사용!)

함수

컨트랙트 내에서 EOA 트랜잭션이나 다른 컨트랙트에 의해 호출될 수 있는 함수.

function FunctionName([parameters]) {public|private|internal|external} [pure|constant|view|payable] [modifiers] [returns (return types)]
  1. FunctionName
    함수 이름.
    이름 없이 정의될 수 있는 함수는 fallback 함수라고 부르고 다른 함수 이름이 없을 때 호출되며 인수가 없고 반환할 수도 없다.

  2. parameters
    인수

  3. 키워드 세트1 (public, private, internal, external) -> 호출 방법, 조건에 영향

  • public : 공개함수. 다른 컨트랙트, 트랜잭션에서 호출 가능
  • external : 외부 함수. 키워드 this가 붙지 않으면 컨트랙트 내에서 호출할 수 없음.
  • internal : 내부 함수. 컨트랙트 내에서만 접근 가능. 다른 컨트랙트, 트랜잭션에서 호출할 수 없고 파생된 컨트랙트에서는 호출 가능
  • private : 비공개 함수. 파생된 컨트랙트에서도 호출할 수 없다.
  1. 키워드 세트2 (pure, constant, view, payable)
    -> 함수의 동작에 영향
  • constant or view : 상태를 변경하지 않음
  • pure : 순수 함수. 인수에 대해서만 작동하고 데이터 반환.
  • payable : payable 선언에 따라 입금 여부 판별.

컨트랙트 종료

컨트랙트 생성자

컨트랙트 생성될 때 생성자 함수가 있는 경우 이를 실행하여 컨트랙트 상태를 초기화.
constructor 키워드를 통해서 지정

pragma ^0.4.22
contract MEContract {
	constructor () {
    }
}

컨트랙트 소멸자

SELFDESTRUCT EVM 코드를 통해

selfdestruct(address recipient);

생성자, 소멸자 사용 예시

pragma solidity ^0.4.22;

contract Faucet {
    address owner;

    constructor() {
        owner = msg.sender;
    }

    function withdraw(uint256 withdraw_amount) public {
        require(withdraw_amount <= 10000000000000);

 	msg.sender.transfer(withdraw_amount);
    }


    function() external payable {}
    
    function destroy() public {
    	require(msg.sender == owner);
    	selfdestruct(owner);
    }

}

생성자 효과 : 생성자가 woner에 저장한 동일한 주소를 호출하면 컨트랙트는 파괴되고 잔액을 owner 주소로 돌려줌

파괴자 효과 : owner 주소가 destroy 함수를 호출하면 컨트랙트 파괴

함수 변경자(modifier)

컨트랙트 내에서 함수에 적용되어야 할 조건 생성하기 위해 사용

modifier onlyOwner {
    require(msg.sender == owner);
    _;
}

msg.sender == owner 통해서 컨트랙트의 owner만 실행가능하게 함.

_; 부분에 수정된 코드 삽입.

사용예시

function destroy() public onlyOwner {
     selfdestruct(owner);
}

컨트랙트 상속

컨트랙트에 기능을 추가, 확장하기 위해 상속 지원.

contract child is parent1, parent2{
~~~
}
  • 단순하고 일반적인 컨트랙트로 시작하여 전문화된 컨트랙트로 기능을 상속하여 확장하는 형식으로 자주 사용.
  • 이에 따라 컨트랙트를 더 단순하게 만들고 특정 기능들에 더 중점을 둘 수 있게 해준다.

에러 처리(assert, require, revert)

에러로 컨트랙트가 중지될 때는 모든 상태를 원래대로 되돌리는 것이 필요하다.

assert & require

assert와 require는 조건을 평가하고 조건이 거짓이면 실행을 중지시키는 방식으로 동작.

assert는 필요한 내적 조건들이 만족되는지 테스트

require는 입력값이 설정한 조건의 기댓값에 맞는지 테스트할 때 사용.
요구되는 조건 설정한 후 만족되지 않을 경우 에러를 발생시켜 나머지 부분이 실행되지 않게 한다.

require(msg.sender == owner, 'Only the contract owner can call this function')

revert

revert는 컨트랙트의 실행을 중지하고 모든 변경 상태를 되돌린다.

비명시적 에러

출금 요청을 만족시킬 이더가 불충분한 경우와 같이 명시적으로 정의하지 않더라도 발생하는 에러가 있다.
이 경우에도 에러 메시지를 분명하게 제공하여 확인이 가능하게 하는 것이 좋다.

이벤트

트랜잭션 완료될 때 발행하는 트랜잭션 영수증에 기록할 트랜잭션 실행 동안 발생했던 행위에 관한 정보를 제공하는 객체.

이벤트가 일어나는 여부를 감시할 수 있게 해주기 때문에 DApp에 유용.

이벤트 객체는 인수를 취할 수 있고 이는 indexed라는 키워드를 붙여서 검색, 필터링 가능한 인덱싱된 테이블의 값으로 만들 수 있다.

결과코드

pragma solidity ^0.4.22;

contract owned {
    address owner;

    // 컨트랙트 생성자: woner 설정
    constructor() {
        owner = msg.sender;
    }
}

contract mortal is owned {
    //컨트랙트 소멸자
    function destroy() public onlyOwner {
        selfdestruct(owner);
    }
}

contract Faucet is mortal {
    event withdrawal(address indexed to, uint256 amount);
    event Deposit(address indexed from, uint256 amount);

    // Give out ether to anyone who asks for it
    function withdraw(uint256 withdraw_amount) public {
        // Limit withdrawl amount
        require(withdraw_amount <= 0.1 ether);
        require(
            this.balance >= withdraw_amount,
            "Insufficient balance in faucet for withdrawal request"
        );
        // Send amount to address that requested it
        // transfer function transfers ether to the sender of msg.
        msg.sender.transfer(withdraw_amount);
        emit Withdrawal(msg.sender, withdraw_amount);
    }

    // Accept any incoming amount
    // default function for tx triggered the contract that doesn't have declared function in the contract or doesn't contain data.
    function() public payable {
        emit Deposit(msg.sender, msg.value);
    }
}

다른 컨트랙트 호출

가스 고려

앞의 장에서 다루었듯이 트랜잭션이 가스 한계를 초과하면 복귀하고 실행하는데 사용된 가스는 반환되지 않는다. 따라서 컨트랙트에 사용되는 가스 비용을 최소화하는 것이 중요하다.

동적 크기 배열 피하기

함수에서 동적 크기 배열을 통한 루프는 많은 가스를 사용한다.

다른 컨트랙트 호출 피하기

다른 컨트랙트의 함수가 얼만큼의 가스를 사용할지 정확히 알 수 없다.

가스 비용의 추정

컨트랙트에 필요한 가스를 추정할 수 있다.

var contract = web3.eth.contract(abi).at(address);
var gasEstimate = contract.myAweSomeMethod.estimateGas(arg1, arg2, {from: account});

gasEstimate는 컨트랙트 실행에 필요한 가스의 예상치를 알려준다.

profile
갈팡질팡 공부하는 중입니다

0개의 댓글