Multicall을 사용하여 효율적인 contract 호출
개시하다
이번에는 Contract를 효율적으로 호출하는 Multicall과 그 사용 방법을 소개하겠습니다.
Multicall aggregates results from multiple contract constant function calls.
간단하게 어떻게 말했는지 소개하자면 멀티플렉스 자체는 하나의 구조기로서 멀티플렉스에 콜하고 싶은 정보를 전달하고 contract에서 지정한address,function에 호출하여 결과를 종합하여 반환한다.
이것을 이용하면 프레임에서 전단과 Bot을 효과적으로 호출할 수 있을 뿐만 아니라나는 즉시 사용법부터 소개하고 싶다.
시용하다
이번에는 JavaScript 기반 환경을 활용하여 주로 다음과 같은 도구를 사용합니다.
이미지
멀티콜(그리고Contract 자체)과 협업하여 호출할 수 있는 코드라는 인상을 간단하게 주기 위해 먼저 코드의 이미지로 절차를 설명하고 싶습니다.
// 1. Multicall Contract と連携するための Contract Instance を生成
const multicall = new ethers.Contract(
"address", // multicall contract の address
ABI, // Application Binary Interface
ethers.provider // 実際の ethereum network と通信するための Provider
)
// 2. Multicall#aggregate を呼び出すための引数を作成
const inputs = [ ... ]
// 3. Multicall#aggregate を呼び出し、実行結果を受け取る
const result = await multicall.callStatic.aggregate(inputs);
// 4. 3 で取得したデータから指定した function の実行結果を出力
console.log(result[1][N])
논리의 중심 부분은 상술한 이미지이다.여기
Multicall#aggregate
에서 교부된 매개 변수의 생성을 고려하면 더욱 복잡해질 것이다.이번에 Curve.fipool의 3pool을 이용해서 Multicall을 실제로 사용하고 싶어요.
이 3pool에서 많은 사용자들이 Stable coin, USDC/USDT/DAI
deposit
인데 실제로 3pool의Contract는 이런 token을 가지고 있다.이 3 pool을 user로 사용하고 multicall을 이용하여 각각의 Token 보유량을 확인해 보세요.
!
커브 파이낸스에 대한 자세한 설명은 없지만 일본어로 해설된 글도 있으니 궁금하신 분들은 한번 보세요.
https://academy.binance.com/ja/articles/what-is-curve-finance-in-defi
https://jp.cointelegraph.com/news/what-is-curve-finance
Multicall을 사용하지 않는 경우
Multicall을 사용하지 않고 호출하려면, 콜 각 영패 구조기
#balanceOf(address)
가 필요합니다.이미지는 다음과 같다. ethereum 네트워크를 3차례 호출한다.
const ERC20_ABI = jsonfile.readFileSync("./abis/ERC20.json")
const TOKENS = {
USDC: {
address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
decimals: 6
},
USDT: {
address: "0xdac17f958d2ee523a2206206994597c13d831ec7",
decimals: 6
},
DAI: {
address: "0x6b175474e89094c44da98b954eedeac495271d0f",
decimals: 18
}
}
const USER = "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7" // Curve.fi: DAI/USDC/USDT Pool
task("call:direct", "call:direct").setAction(async ({}, hre: HardhatRuntimeEnvironment) => {
const { ethers } = hre
const usdc = new ethers.Contract(
TOKENS.USDC.address,
new ethers.utils.Interface(ERC20_ABI),
ethers.provider
)
const usdt = new ethers.Contract(
TOKENS.USDT.address,
new ethers.utils.Interface(ERC20_ABI),
ethers.provider
)
const dai = new ethers.Contract(
TOKENS.DAI.address,
new ethers.utils.Interface(ERC20_ABI),
ethers.provider
)
const results = await Promise.all([
usdc.balanceOf(USER), // 1回
usdt.balanceOf(USER), // 2回
dai.balanceOf(USER), // 3回
])
console.log(...)
})
같은 코드로 세 개의 Contract Instance를 생성하는 것이 좋습니다(자주 있기 때문에)Promise.all
병렬 호출을 사용하여 ethereum network에 RPC request를 3회 실행합니다.사용하는 쪽과 부르는 쪽은 모두 효율이 낮으니 멀티콜을 이용해 효율화를 시도해 보자.
멀티컬로 한번 볼게요.
갑작스럽지만 완성판 코드는 다음과 같습니다. 개별 댓글에 설명이 추가되었으니 참고하시기 바랍니다.
import keccak256 from "keccak256"
import { task } from "hardhat/config"
import { HardhatRuntimeEnvironment } from "hardhat/types"
import jsonfile from "jsonfile"
import { BigNumber } from "ethers"
import { ERC20__factory, Multicall__factory } from "../../libs/contracts/__generated__"
const multicall_abi = jsonfile.readFileSync("./abis/Multicall.json") // Multicall の Contract のABI (etherscan などで取得可能です)
const MULTICALL_ADDRESS = "0xeefba1e63905ef1d7acba5a8513c70307c1ce441" // Multicall Contract の address
// 今回利用する token (USDC/USDT/DAI) の address, decimals
const TOKENS = {
USDC: {
address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
decimals: 6
},
USDT: {
address: "0xdac17f958d2ee523a2206206994597c13d831ec7",
decimals: 6
},
DAI: {
address: "0x6b175474e89094c44da98b954eedeac495271d0f",
decimals: 18
}
}
// Curve.fi: DAI/USDC/USDT Pool の address
const ADDRESS = "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7"
/**
* use multicall by only ethersjs
* - support only mainnet
*/
task("multicall", "multicall").setAction(async ({}, hre: HardhatRuntimeEnvironment) => {
const { ethers } = hre
const multicall = new ethers.Contract(
MULTICALL_ADDRESS,
new ethers.utils.Interface(multicall_abi),
ethers.provider
)
// Multicall#aggregate に渡す callData の作成
const selector = keccak256("balanceOf(address)").toString('hex').substr(0, 8) // #balanceOf(address) の method id 作成
const param = ADDRESS.substring(2).padStart(64, "0") // 呼び出す function の引数生成: input を zero padding し 32byte のデータに加工する
const inputs = [
{
target: TOKENS.USDC.address, // USDC
callData: `0x${selector}${param}`
},
{
target: TOKENS.USDT.address, // USDT
callData: `0x${selector}${param}`
},
{
target: TOKENS.DAI.address, // DAI
callData: `0x${selector}${param}`
},
]
const result = await multicall.callStatic.aggregate(inputs);
for (const [index, key] of Object.keys(TOKENS).entries()) {
const balanceOf = ethers.utils.formatUnits( // 表示向けに、token の decimals 分 shift させる
BigNumber.from(result[1][index]), // Multicall#aggregate 実行結果から、arguments order を考慮して、該当するデータを取得する
TOKENS[key as keyof typeof TOKENS].decimals
)
console.log(`${key} ... ${balanceOf}`)
}
})
상기 코드를 실행하면 다음과 같은 실행 결과를 얻을 수 있으며 각token의 저장량을 확인할 수 있다.$ npx hardhat multicall --network mainnet
USDC ... 1085091186.161086
USDT ... 822667786.055421
DAI ... 1252359115.58683885475104325
Self Q&A
Q. 당신은 무엇을 하고 있습니까?
0x${selector}${param}
이 필요합니다.나는 스마트 구조기를 사용하는 입금 시스템을 전력으로 이해했다
Q. 멀티컬은 어디서나 사용할 수 있나요?
repository에서 보듯이testnet도deploy로 사용할 수 있다
Multicall.js
그런 말이 있는 것 같아요.Multicall with Type chain 사용
멀티컬을 사용하는 방법을 설명했지만 사은품으로 typechain을 사용하면 더 쉽게 불러낼 수 있다는 것을 소개해드리고 싶습니다.
typechain이란 ABI에서Contract와 연합하여 TypeScript 코드를 자동으로 생성하는integration tool입니다.
hardhat plugin
,truffle plugin
등은 자신의 ethereum 개발 환경,test,deploy script 등을 넣었기 때문에 기술하기 쉬워 개인에게 필수적인 프로젝트이다.Typechain을 사용하여 코드를 자동으로 생성할 때는 다음과 같습니다.
task("multicall-with-typechain", "multicall-with-typechain").setAction(async ({}, hre: HardhatRuntimeEnvironment) => {
const { ethers } = hre
const multicall = Multicall__factory.connect(
MULTICALL_ADDRESS,
ethers.provider
)
const _interface = ERC20__factory.createInterface()
const callData = [
{
target: TOKENS.USDC.address, // USDC
callData: _interface.encodeFunctionData("balanceOf", [ADDRESS])
},
{
target: TOKENS.USDT.address, // USDT
callData: _interface.encodeFunctionData("balanceOf", [ADDRESS])
},
{
target: TOKENS.DAI.address, // DAI
callData: _interface.encodeFunctionData("balanceOf", [ADDRESS])
},
]
const result = await multicall.callStatic.aggregate(callData)
for (const [index, key] of Object.keys(TOKENS).entries()) {
console.log(
ethers.utils.formatUnits(
_interface.decodeFunctionResult(
"balanceOf",
result.returnData[index]
)[0],
TOKENS[key as keyof typeof TOKENS].decimals)
)
}
})
Typechain을 사용하지 않은 모드와 비교이것은 매우 간단하고 다른 프로그램 라이브러리가 필요하지 않다는 것을 모두가 알고 싶다.
keccak256
를 활용해 지정된 byte 수를 재단하기 때문에 그런 부분의 설치를 생략할 수 있어 스텝을 보다 쉽게 처리할 수 있다.끝말
키포인트 토픽이지만 타입 체인을 비롯한 Dapps 개발에서 생산성이 매우 높기 때문에 이번에 소개합니다.
최대한 필요한 정보만 가능한 한 소개한 만큼 관심 있는 사람이 실제로 접해보면 좋을 것 같다.
(지난번 업데이트부터 상당한 시간이 흘렀지만) 기술지식 공유로 기사를 낼 수 있어서 다행이다.앞으로도 웹3에 관심 있는 사람들을 위해 뭔가를 할 수 있을 것 같습니다.
끝까지 읽어주셔서 감사합니다!🙇
참고 자료
Reference
이 문제에 관하여(Multicall을 사용하여 효율적인 contract 호출), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/linnefromice/articles/use-multicall텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)