[Design Pattern] Behavioral Patterns

zzase·2022년 9월 10일
0

Solidity

목록 보기
6/6
post-thumbnail

Behavioral Patterns

Guard Check

예상하지 못한 로직이 동작하지 않게 예외처리 진행

3가지 조건처리문을 활용한다.

  • require()
    조건 체크가 한줄로 간단할 경우
    exception이 충분히 발생할 수 있는 경우
    함수 arguments 체크시에 많이 사용됨

  • assert()
    조건 체크가 한줄로 간단할 경우
    exceoption이 절대 발생하면 안되는 경우
    최종 체크시에 많이 사용 되기에 함수 뒷부분에서 주로 사용
    revert 발생시 gas를 돌려주지 않는다.

  • revert()
    조건 체크가 한줄 이상으로 복잡하거나 조건을 적기 어려운 경우
    무조건 revert 발생

ex.

contract GuardCheck {
    
    function donate(address addr) payable public {
        require(addr != address(0));
        require(msg.value != 0);
        uint balanceBeforeTransfer = this.balance;
        uint transferAmount;
        
        if (addr.balance == 0) {
            transferAmount = msg.value;
        } else if (addr.balance < msg.sender.balance) {
            transferAmount = msg.value / 2;
        } else {
            revert();
        }
        
        addr.transfer(transferAmount);
        assert(this.balance == balanceBeforeTransfer - transferAmount);      
    }
}

State Machine

stage 설정, stage에 따른 로직 실행, stage 전환을 지원

solidity에서 stage는 주로 enum을 사용해서 나타낸다.
그리고 stage에 따른 로직 실행을 위해 함수 실행전 stage를 체크할때
modifier(Access Control Pattern) 혹은 require(Guard Check Pattern)을 주로 사용한다.

ex.

// This code has not been professionally audited, therefore I cannot make any promises about
// safety or correctness. Use at own risk.
contract StateMachine {
    
    enum Stages {
        AcceptingBlindBids,
        RevealBids,
        WinnerDetermined,
        Finished
    }

    Stages public stage = Stages.AcceptingBlindBids;

    uint public creationTime = now;

    modifier atStage(Stages _stage) {
        require(stage == _stage);
        _;
    }
    
    modifier transitionAfter() {
        _;
        nextStage();
    }
    
    modifier timedTransitions() {
        if (stage == Stages.AcceptingBlindBids && now >= creationTime + 6 days) {
            nextStage();
        }
        if (stage == Stages.RevealBids && now >= creationTime + 10 days) {
            nextStage();
        }
        _;
    }

    function bid() public payable timedTransitions atStage(Stages.AcceptingBlindBids) {
        // Implement biding here
    }

    function reveal() public timedTransitions atStage(Stages.RevealBids) {
        // Implement reveal of bids here
    }

    function claimGoods() public timedTransitions atStage(Stages.WinnerDetermined) transitionAfter {
        // Implement handling of goods here
    }

    function cleanup() public atStage(Stages.Finished) {
        // Implement cleanup of auction here
    }
    
    function nextStage() internal {
        stage = Stages(uint(stage) + 1);
    }
}

Oracle

스마트 컨트랙트에서 블록체인 외부 정보를 사용

블록체인 외부의 정보이기 때문에 블록체인에서 제공되는 신뢰성을 보장받을 수 없다.
따라서 믿을 수 있는 외부 데이터 저장소에서 정보를 사용해야 하는데, 주로 영국 회사인 Oraclize 서비스를 사용한다.

해당 패턴을 사용할 때 다음 두가지 method는 반드시 구현되어야 한다.

  1. 오라클 컨트랙트에 query를 보내는 methd
    return값으로 오라클 컨트랙트가 제공한 id를 받는다.

  2. 오라클이 호출하는 call back function
    오라클이 1번에서 받은 id에 매칭되는 결과 값을 돌려줄 때 사용된다.

ex.

import "github.com/oraclize/ethereum-api/oraclizeAPI.sol";

contract OracleExample is usingOraclize {

    string public EURUSD;

    function updatePrice() public payable {
        if (oraclize_getPrice("URL") > this.balance) {
            //Handle out of funds error
        } else {
            oraclize_query("URL", "json(http://api.fixer.io/latest?symbols=USD).rates.USD");
        }
    }
    
    function __callback(bytes32 myid, string result) public {
        require(msg.sender == oraclize_cbAddress());
        EURUSD = result;
    }
}

Randomness

random값을 사용하기 위한 패턴

이더리움은 결정론적 튜링 기계 (순차적으로 처리) 이기에 consensus가 이루어지려면 random 함수에 사용되는 seed는 모든 시점에 모두가 동일해야한다.
게다가 모두에게 데이터가 공개되는 블록체인 특성 상 secure source of entropy를 설정하는 것이 어렵다.

초기에는 block timestamp를 seed로 사용했으나 block timestamp는 miner에 의해 영향을 받고 악용할 수 있기 때문에 deprecated 되었다.

이를 해결하기 위해 제안된 방법은 다음과 같다

  1. block hash를 사용하는 방법
    가장 최근을 포함한 기존의 block hash를 사용하면 악용가능성이 있어 미래의 block hash값과 trusted party가 제공하는 seed를 합쳐서 random number를 만든다.

  2. Oracle을 사용하는 방법
    신뢰기관에게 random value 생성을 위임

  3. 블록체인상의 여러가지 정보를 조합해서 사용하는 방법 (Collaborative PRNG)
    현재는 개발 중단

ex.

contract Randomness {

    bytes32 sealedSeed;
    bool seedSet = false;
    bool betsClosed = false;
    uint storedBlockNumber;
    address trustedParty = 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF;

    function setSealedSeed(bytes32 _sealedSeed) public {
        require(!seedSet);
        require (msg.sender == trustedParty);
        betsClosed = true;
        sealedSeed = _sealedSeed;
        storedBlockNumber = block.number + 1;
        seedSet = true;
    }

    function bet() public {
        require(!betsClosed);
        // Make bets here
    }

    function reveal(bytes32 _seed) public {
        require(seedSet);
        require(betMade);
        require(storedBlockNumber < block.number);
        require(keccak256(msg.sender, _seed) == sealedSeed);
        uint random = uint(keccak256(_seed, blockhash(storedBlockNumber)));
        // Insert logic for usage of random number here;
        seedSet = false;
        betsClosed = false;
    }
}
profile
블록체인 백엔드 개발자

0개의 댓글