[TIL] Part4 - DAY70 Project - 가스비, storage활용법, VRF 실습

박동우·2023년 7월 4일

블록체인 스쿨 3기

목록 보기
66/72

[TIL] 2023-07-04 DAY70


Project

GIT 주소
https://github.com/dowoo303/SOLIDITY
https://github.com/dowoo303/hardhat


오늘 배운것들

  1. 가스비 아끼는법
  2. memory와 storage
  3. VRF 활용

각종 팁

  1. 가스비를 이해하려면 OP코드를 어느정도 이해하는 것이 도움이 된다.

회고




💻 Solidity 코딩

✅ 가스비 효율

  1. uint 상태변수에 0에서 1로 더할 때가 가장 가스비가 많이 든다.
    -> 초기 uint 상태변수 값을 신경써주는 것이 좋음(만약 상태변수가 1로 시작하면 많이 안듦)
    -> 지역변수를 활용하여 상태변수에 값을 꽂아주는 것이 제일 저렴하다.

  2. 반면 뺄셈에서 상태변수가 1 -> 0으로 갈 때는 가스비가 싸다.

  3. 기본적으로 memory보다는 calldata가 더 저렴하다.
    calldata는 초기값이 있는게 더 싸지만, memory는 초기값이 없는게 더 싸다
    가격 싼 순서: calldata(초기값 존재) -> calldata(초기값 없음) -> memory(초기값 없음) -> clladata(초기값 존재)

  4. visibility에 따른 가스비 차이는 미미하다.



✅ memory와 storage 차이

storage를 사용하면 원본을 바꾸면 쳐다보는 애도 변하고, 쳐다보는 애를 바꿔도 원본이 바뀐다.
-> storage 쓰는이유: 쳐다보는 애의 값을 보여주는걸로 원본의 값을 보여주되 변경은 불가하게(private) 만들어쓰기 위함(보안)

가스비 측면에서는 storage를 사용하지않고 그냥 원래 변수 값에 넣는 것이 저렴하다.


🟥 코드

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract MEMORY_STORAGE {
    struct AA {
        uint a;
        string b;
    }

    AA public aa;

    function setAA(uint _a, string calldata _b) public {
        aa.a = _a;
        aa.b = _b;
    }

    function setAA_memory(uint _a, string calldata _b) public pure returns(AA memory){
        AA memory _aa;
        _aa.a = _a;
        _aa.b = _b;
        return (_aa);
    }

    function setAA_memory2(uint _a, string calldata _b) public view returns(AA memory, AA memory){
        AA memory _aa = aa;
        _aa.a = _a;
        _aa.b = _b;
        return (_aa, aa);
    }

    // 위와 다르게 aa 값 또한 변한다
    function setAA_storage(uint _a, string calldata _b) public returns(AA memory, AA memory) {
        AA storage _aa = aa;
        _aa.a = _a;
        _aa.b = _b;
        return (_aa, aa);
    }

    // _aa 또한 storage를 사용하면 변한다
    function setAA_storage2(uint _a, string calldata _b) public returns(AA memory, AA memory) {
        AA storage _aa = aa;
        aa.a = _a;
        aa.b = _b;
        return (_aa, aa);
    }
}


https://vrf.chain.link/goerli

  • 홈페이지에서 작업 순서
    위 홈페이지에서 LINK 코인을 받기 -> Subscription 생성 -> 컨트랙트 주소 입력 -> 리믹스 코드 실행

  • 코드 실행순서
    리믹스에서 requestRandomWords 함수 실행 후 getRequestStatus에 결과값 넣고 함수 실행 후 조금 기다리면 fullfilled가 true로 바뀌고 랜덤 난수가 생성된다.

  • 원리
    requestRandomWords으로 난수 생성 후 getRequestStatus에서 확인 및 이 값을 이용하여 필요한 함수에 넣어서 사용(ex)주사위 게임, 99게임 등등)


🟥 코드

// SPDX-License-Identifier: MIT
// An example of a consumer contract that relies on a subscription for funding.
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";

contract VRFv2Consumer is VRFConsumerBaseV2, ConfirmedOwner {
    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(uint256 requestId, uint256[] randomWords);

    struct RequestStatus {
        bool fulfilled; // whether the request has been successfully fulfilled
        bool exists; // whether a requestId exists
        uint256[] randomWords;
    }
    mapping(uint256 => RequestStatus) public s_requests; /* requestId --> requestStatus */
    VRFCoordinatorV2Interface COORDINATOR;

    uint64 s_subscriptionId;

    // past requests Id.
    uint256[] public requestIds;
    uint256 public lastRequestId;

    bytes32 keyHash = 0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15;

    uint32 callbackGasLimit = 100000;
    uint16 requestConfirmations = 3;
    uint32 numWords = 2;

    constructor(uint64 subscriptionId) VRFConsumerBaseV2(0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D) ConfirmedOwner(msg.sender) {
        COORDINATOR = VRFCoordinatorV2Interface(0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D);
        s_subscriptionId = subscriptionId;
    }


    // 실제 random 값을 뱉어내는 함수
    function requestRandomWords() external onlyOwner returns (uint256 requestId) {
        requestId = COORDINATOR.requestRandomWords(
            keyHash,
            s_subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            randomWords: new uint256[](0),
            exists: true,
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        return requestId;
    }

    // 꼭 구현되어야 하는 vrf callback function
    function fulfillRandomWords(uint256 _requestId,uint256[] memory _randomWords) internal override {
        require(s_requests[_requestId].exists, "request not found");
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        emit RequestFulfilled(_requestId, _randomWords);
    }

    // 랜덤 값을 requestID로 받아오는 함수
    function getRequestStatus(uint256 _requestId) external view returns (bool fulfilled, uint256[] memory randomWords) {
        require(s_requests[_requestId].exists, "request not found");
        RequestStatus memory request = s_requests[_requestId];
        return (request.fulfilled, request.randomWords);
    }

    // 1. 0부터 99까지 임의의 숫자, 위의 getRequestStatus를 응용한 함수
    function zerto99(uint256 _requestId) external view returns (bool fulfilled, uint256[] memory randomWords) {
        require(s_requests[_requestId].exists, "request not found");
        RequestStatus memory request = s_requests[_requestId];
        uint a = request.randomWords[0];
        uint[] memory b = divideNumber(a);
        return (request.fulfilled, b);
    }

    // 2. 주사위(1~6), 위의 getRequestStatus를 응용한 함수
    function rollingDice(uint256 _requestId) external view returns (bool, uint256) {
        require(s_requests[_requestId].exists, "request not found");
        RequestStatus memory request = s_requests[_requestId];
        uint a = request.randomWords[0];
        uint b = a%6+1;
        return (request.fulfilled, b); 
        /* uint32 numWords = 2; => 1로 수정, 
        위에 있는 uint a = request.randomWords[0];를 uint a = request.randomWords; 
        */
    }

    function getLength(uint _n) public pure returns(uint) {
        if(_n==0) {
            return 1;
        }
        uint a;
        while(_n >= 10**a) {
            a++;
        }
        return a;
    }

    function divideNumber(uint _n) public pure returns(uint[] memory) {
        uint[] memory b = new uint[]((getLength(_n)+1)/2);

        uint i=0;
        while(_n !=0) {
            b[i++] = _n%100;
            _n = _n/100;
        }
        return (b);
    }

}

profile
HELLO!

0개의 댓글