체인의정석

Chain link 서비스 분석 - chain link VRF 분석 본문

블록체인/퍼블릭 블록체인

Chain link 서비스 분석 - chain link VRF 분석

체인의정석 2022. 9. 13. 16:39
728x90
반응형

이번 프로젝트의 장기적인 로드맵에 있어서 chain link를 연동하는 부분을 고려중이기 때문에 한번 살펴 보았다.

먼저 체인링크의 활용 방안은 스마트컨트렉트 내부에서 url에 대한 요청을 보내서 거기서 받은 응답값을 통해서 활용이 가능한 것이였다.

 

chain link VRF

랜덤 변수를 뽑아낼 때는 최근에 나온 체인링크 VRF를 활용하면 되었는데 

https://docs.chain.link/docs/vrf/v2/best-practices/

 

VRF Best Practices | Chainlink Documentation

Best pracices for using Chainlink VRF.

docs.chain.link

일단 내부적으로는 랜덤 변수를 생성할 수 없기 때문에 외부에서 데이터를 가져와야 하는데

function fulfillRandomWords(
  uint256, /* requestId */
  uint256[] memory randomWords
) internal override {
  // Assuming only one random word was requested.
  s_randomRange = (randomWords[0] % 50) + 1;
}

요런식으로 랜덤한 숫자 하나만 가져오게 되면 이걸 통해서 다양한 수치를 랜덤으로 뽑아낼 수 있다.

 

만약 동시에 여러개의 VRF 요청을 하려는 경우 아래와 같이 매핑을 사용해서 ID별로 여러개의 랜덤 변수를 담을 수 있도록 하는 응답값에 대한 Mapping과 해당 id를 요청한 주소를 매핑으로 저장시키는 mapping을 각각 추가 시켜서 요청시에 Id 별로 따로따로 요청을 하는 함수를 만들고 응답값은 복수로 받아올 수 있도록 아이디 별로 응답값을 저장시키는 식으로 구현할 수 있다.

mapping(uint256 => uint256[]) public s_requestIdToRandomWords;
mapping(uint256 => address) public s_requestIdToAddress;
uint256 public s_requestId;

function requestRandomWords() external onlyOwner returns (uint256) {
  uint256 requestId = COORDINATOR.requestRandomWords(
    keyHash,
    s_subscriptionId,
    requestConfirmations,
    callbackGasLimit,
    numWords
  );
  s_requestIdToAddress[requestId] = msg.sender;

  // Store the latest requestId for this example.
  s_requestId = requestId;

  // Return the requestId to the requester.
  return requestId;
}

function fulfillRandomWords(
    uint256 requestId,
    uint256[] memory randomWords
  ) internal override {
  // You can return the value to the requester,
  // but this example simply stores it.
  s_requestIdToRandomWords[requestId] = randomWords;
}

request를 트래킹하면서 아이디별로 인덱스를 따로 만들고 싶다면 

mapping(uint256 => uint256) s_requestIdToRequestIndex;
mapping(uint256 => uint256[]) public s_requestIndexToRandomWords;
uint256 public requestCounter;

function requestRandomWords() external onlyOwner {
  uint256 requestId = COORDINATOR.requestRandomWords(
    keyHash,
    s_subscriptionId,
    requestConfirmations,
    callbackGasLimit,
    numWords
  );
  s_requestIdToRequestIndex[requestId] = requestCounter;
  requestCounter += 1;
}

function fulfillRandomWords(
    uint256 requestId,
    uint256[] memory randomWords
  ) internal override {
  uint256 requestNumber = s_requestIdToRequestIndex[requestId];
  s_requestIndexToRandomWords[requestNumber] = randomWords;
}

이런식으로 카운터 값을 주면서 주소값이 아닌 인덱스를 통한 관리도 가능하다.

 

마지막으로 예제에는 각각의 경로로 서로 다르게 랜덤 값을 관리하는 코드가 있엇는데

// SPDX-License-Identifier: MIT
// An example of a consumer contract that relies on a subscription for funding.
// It shows how to setup multiple execution paths for handling a response.
pragma solidity ^0.8.7;

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

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

contract VRFv2MultiplePaths is VRFConsumerBaseV2 {
    VRFCoordinatorV2Interface COORDINATOR;

    // Your subscription ID.
    uint64 s_subscriptionId;

    // Goerli coordinator. For other networks,
    // see https://docs.chain.link/docs/vrf/v2/supported-networks/#configurations
    address vrfCoordinator =  0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D;

    // The gas lane to use, which specifies the maximum gas price to bump to.
    // For a list of available gas lanes on each network,
    // see https://docs.chain.link/docs/vrf/v2/supported-networks/#configurations
    bytes32 keyHash = 0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15 View in Tenderly ;

    uint32 callbackGasLimit = 100_000;

    // The default is 3, but you can set this higher.
    uint16 requestConfirmations = 3;

    // For this example, retrieve 1 random value in one request.
    // Cannot exceed VRFCoordinatorV2.MAX_NUM_WORDS.
    uint32 numWords = 1;

    enum Variable {
        A,
        B,
        C
    }

    uint256 public variableA;
    uint256 public variableB;
    uint256 public variableC;

    mapping(uint256 => Variable) public requests;

    // events
    event FulfilledA(uint256 requestId, uint256 value);
    event FulfilledB(uint256 requestId, uint256 value);
    event FulfilledC(uint256 requestId, uint256 value);

    constructor(uint64 subscriptionId) VRFConsumerBaseV2(vrfCoordinator) {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        s_subscriptionId = subscriptionId;
    }

    function updateVariable(uint256 input) public {
        uint256 requestId = COORDINATOR.requestRandomWords(
            keyHash,
            s_subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );

        if (input % 2 == 0) {
            requests[requestId] = Variable.A;
        } else if (input % 3 == 0) {
            requests[requestId] = Variable.B;
        } else {
            requests[requestId] = Variable.C;
        }
    }

    function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
        Variable variable = requests[requestId];
        if (variable == Variable.A) {
            fulfillA(requestId, randomWords[0]);
        } else if (variable == Variable.B) {
            fulfillB(requestId, randomWords[0]);
        } else if (variable == Variable.C) {
            fulfillC(requestId, randomWords[0]);
        }
    }

    function fulfillA(uint256 requestId, uint256 randomWord) private {
        // execution path A
        variableA = randomWord;
        emit FulfilledA(requestId, randomWord);
    }

    function fulfillB(uint256 requestId, uint256 randomWord) private {
        // execution path B
        variableB = randomWord;
        emit FulfilledB(requestId, randomWord);
    }

    function fulfillC(uint256 requestId, uint256 randomWord) private {
        // execution path C
        variableC = randomWord;
        emit FulfilledC(requestId, randomWord);
    }
}

결국

VRFCoordinatorV2Interface

 해당 인터페이스를 통하여 체인링크의 스마트 컨트렉트에 접근을 하는 것이고 해당 컨트렉트에다가 트랜잭션을 직접 호출하면 응답값을 체인링크가 주는 형태이다. 위의 예시코드는 체인링크를 사용할 경우 해당 응답값을 받아와서 처리하는 체인링크의 고객인 개발자가 참고하고 구현할 코드이다.

 

 

지원 네트워크의 경우 evm 계열의 네트워크가 사용되는 것 같다.

https://docs.chain.link/docs/vrf/v2/supported-networks/

 

Supported Networks | Chainlink Documentation

 

docs.chain.link

들어가면 이런식으로 해당 체인에서의 링크 토큰 주소 및 gwei key hash가 나온다. Key hash의 경우 주석을 살펴보니 사용할 최대 가스치인것 같다.

가장 많은 유저가 사용하는 것이 바로 requeset Random Wokrds 였는데

여기서 subId의 경우 구독 아이디로서 체인링크에서 돈을 내는 계정의 아이디라고 볼 수 있다. 따로 페이지에서 충전시켜 놓고 서비스를 구동하는 것을 이런식으로 스마트컨트렉트에서는 아이디만 받아와서 사용하는 구조를 취하고 있다.

 

그리고 응답값에 대한 gas limit도 걸어두고 받고 있는데 응답값이 큰 경우 가스비도 많이 발생하기 때문인것 같다. numWords같은 경우는 받을 랜덤 변수의 개수라고 볼 수 있다.

 

결국 서비스에서는 랜덤변수를 던져주고 여기에 대해 등록하거나 관리하는 부분을 컨트렉트가 해주게 된다.

 

 

728x90
반응형
Comments