체인의정석

Dapp 속도 최적화 시키는데 필수! Multicall 불러와서 상호작용하기 본문

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

Dapp 속도 최적화 시키는데 필수! Multicall 불러와서 상호작용하기

체인의정석 2023. 1. 3. 10:42
728x90
반응형

dapp을 만들때 병목이 되는 경우는 어떤 경우일까?

바로 블록체인에 call을 해오는 부분이다.

만약 하나의 api를 만드는데 블록체이에다가 여러번 call을 해야 하는 상황이라면?

당연히 서비스의 속도는 느려질 수 밖에 없다.

 

이에 따라서 사용해야 하는 부분이 바로 multicall이다.

 

multicall 컨트렉트는 체인별로 하나씩만 있으면 누구나 와서 사용할 수 있다.

그냥 call에 대한 값을 순서대로 받아와서 처리해 주기 때문이다.

 

각 서비스 별로 multicall을 가져오기도 하는데 아무거나 사용하면된다.

어차피 조회를 모아서 해주는 것이니 문제 없는것이다.

 

https://github.com/makerdao/multicall/blob/master/src/Multicall.sol

 

GitHub - makerdao/multicall: Multicall: Aggregate multiple constant function call results into one

Multicall: Aggregate multiple constant function call results into one - GitHub - makerdao/multicall: Multicall: Aggregate multiple constant function call results into one

github.com

예시로 makerdao의 multicall을 가져와서 보겠다.

 

// SPDX-License-Identifier: MIT


pragma solidity >=0.5.0;
pragma experimental ABIEncoderV2;

/// @title Multicall - Aggregate results from multiple read-only function calls
/// @author Michael Elliot <mike@makerdao.com>
/// @author Joshua Levine <joshua@makerdao.com>
/// @author Nick Johnson <arachnid@notdot.net>

contract Multicall {
    struct Call {
        address target;
        bytes callData;
    }
    function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {
        blockNumber = block.number;
        returnData = new bytes[](calls.length);
        for(uint256 i = 0; i < calls.length; i++) {
            (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
            require(success);
            returnData[i] = ret;
        }
    }
    // Helper functions
    function getEthBalance(address addr) public view returns (uint256 balance) {
        balance = addr.balance;
    }
    function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
        blockHash = blockhash(blockNumber);
    }
    function getLastBlockHash() public view returns (bytes32 blockHash) {
        blockHash = blockhash(block.number - 1);
    }
    function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
        timestamp = block.timestamp;
    }
    function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
        difficulty = block.difficulty;
    }
    function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
        gaslimit = block.gaslimit;
    }
    function getCurrentBlockCoinbase() public view returns (address coinbase) {
        coinbase = block.coinbase;
    }
}

보는 것 처럼 그냥 구조체로 call과 target 을 만들어서 어느 주소에 어떤 call을 보낼지를 먼저 정의해 주고 난 후에

구조체 배열을 배열을 받으면 반복문을 돌면서 aggregate를 써서 해당 target에 call bytecode를 넣어주는 것이다.

 

이런 식으로 바이트코드 형태로 받아서 처리를 시켜주는 예시로는 멀티시그 지갑이 있는데 web3 나 ethers나 각각 바이트코드로 Input을 바꾸어주는 함수들이 있다.

 

예를들어 web3에서는

blockchainUtil.web3.eth.abi.encodeParameters("string", ["ETH"]).substr(2)

이런식으로 하면 0x를 제외한 나머지 값들이 들어가게 된다.

 

어차피 트랜잭션 입장에서는 data를 채워서 보내주는 것이므로

(bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);

이런식으로 call을 보내주게 되면 그냥 해당 주소에 call이 가버리는 것이다.

 

근데 결과 값을 보자 bytes형태가 return 된다.

 

따라서 응답 값으로 받은 결과 값을 다시 데이터로 바꾸어주는 과정 까지 거쳐야 한다.

숫자의 경우라면

//소수점 처리가 안된 상황
((hexToBigNum("바이트코드로 표시된 숫자")).toString()
//소수첨 처리를 한 상황 다만 큰 숫자의 경우에는 e18과 같이 exponential 형태가 나올 수 있다.
((hexToBigNum("바이트코드로 표시된 숫자")).toString() / 10 ** 18)
//이를 막아주기 위해서 noExponents를 만들어 준다.
((hexToBigNum("바이트코드로 표시된 숫자")).toString() / 10 ** 18).noExponents()

이런식으로 바꾸어 주어야 기본 단위로 표기된 계산이 가능해 진다.

noExponents의 식은 아래와 같다.

http://www.java2s.com/ref/javascript/javascript-number-noexponents.html

 

Javascript Number noExponents()

 

www.java2s.com

아무튼 이런식으로 숫자화까지 시켜서 리턴 값을 보여주게 되면 효율적인 api 작성이 가능하다.

 

그리고 여기서 팁!

multicall의 경우 오류가 나게 될 경우 어디서 오류가 나게 될지도 모르고 call 하나만 오류가나도 서비스 자체가 먹통이 될 수 있다.

따라서 예외처리 부분에서 multicall이 오류가 나는 경우 콜을 일일히 하나씩 다해주는 작업도 추가해주는 것이 좋다.

 

(이에 따라 엄청난 길이의 함수가 탄생하였다.)

 

또한 자체적으로 아카이브 노드를 구현중인 경우 call에 블록넘버를 가져와서 call 할 있기 때문에 같은 시점에서의 데이터를 call해올 때 하나의 멀티콜로 묶을 수 있다.

 

예를 들어 1주일전, 1일전 데이터들이 요구되는 경우 2개의 멀티콜 안에 모든 콜을 쏴주면 되고

1일전, 1주일전, 1달전 데이터들이 요구되는 경우 3개의 멀티콜 안에 모든 콜을 쏴주면 된다.

 

728x90
반응형
Comments