Skip to content

Commit

Permalink
feat: remove ethers-multicall and embed native multicall3
Browse files Browse the repository at this point in the history
  • Loading branch information
koraykoska committed Jan 5, 2024
1 parent 27e858f commit 504d89d
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 49 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
"@uniswap/swap-router-contracts": "^1.2.1",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-staker": "1.0.2",
"ethers-multicall": "^0.2.3",
"tiny-invariant": "^1.1.0",
"tiny-warning": "^1.0.3"
},
Expand Down
16 changes: 16 additions & 0 deletions src/__tests__/stubs/calls/callResults.json

Large diffs are not rendered by default.

126 changes: 115 additions & 11 deletions src/utils/rpcPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { Token } from '@uniswap/sdk-core'
import { FeeAmount, Tick } from '../'
import { ethers } from 'ethers'
import poolAbi from '@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json'
import { Contract, Provider } from 'ethers-multicall'
import { AbiCoder } from '@ethersproject/abi'
import { keccak256 } from '@ethersproject/keccak256'
import { toUtf8Bytes } from '@ethersproject/strings'
import { BytesLike } from '@ethersproject/bytes'

export interface PoolData {
address: string
Expand Down Expand Up @@ -62,19 +65,17 @@ export abstract class RPCPool {
startWord: number,
endWord: number
): Promise<number[]> {
const multicallProvider = new Provider(provider)
await multicallProvider.init()
const poolContract = new Contract(poolAddress, poolAbi.abi)
const poolContract = new ethers.Contract(poolAddress, poolAbi.abi)

const calls: any[] = []
const wordPosIndices: number[] = []

for (let i = startWord; i <= endWord; i++) {
wordPosIndices.push(i)
calls.push(poolContract.tickBitmap(i))
calls.push(this.makeMulticallFunction(poolContract, 'tickBitmap')(i))
}

const results: bigint[] = (await multicallProvider.all(calls)).map((ethersResponse: any) => {
const results: bigint[] = (await this.multicall(calls, provider)).map((ethersResponse: any) => {
return BigInt(ethersResponse.toString())
})

Expand Down Expand Up @@ -104,17 +105,15 @@ export abstract class RPCPool {
poolAddress: string,
tickIndices: number[]
): Promise<Tick[]> {
const multicallProvider = new Provider(provider)
await multicallProvider.init()
const poolContract = new Contract(poolAddress, poolAbi.abi)
const poolContract = new ethers.Contract(poolAddress, poolAbi.abi)

const calls: any[] = []

for (const index of tickIndices) {
calls.push(poolContract.ticks(index))
calls.push(this.makeMulticallFunction(poolContract, 'ticks')(index))
}

const results = await multicallProvider.all(calls)
const results = await this.multicall(calls, provider)
const allTicks: Tick[] = []

for (let i = 0; i < tickIndices.length; i++) {
Expand All @@ -129,4 +128,109 @@ export abstract class RPCPool {
}
return allTicks
}

// Helpers for multicall

private static makeMulticallFunction(contract: ethers.Contract, name: string): (...params: any[]) => ContractCall {
return (...params: any[]) => {
const { address } = contract
const { inputs } =
contract.interface.functions[name] || Object.values(contract.interface.functions).find((f) => f.name === name)
const { outputs } =
contract.interface.functions[name] || Object.values(contract.interface.functions).find((f) => f.name === name)
return {
contract: {
address: address,
},
name: name,
inputs: inputs || [],
outputs: outputs || [],
params: params,
}
}
}

private static multicallGetFunctionSignature(name: string, inputs: ethers.utils.ParamType[]): string {
const types = []
for (const input of inputs) {
if (input.type === 'tuple') {
const tupleString = this.multicallGetFunctionSignature('', input.components)
types.push(tupleString)
continue
}
if (input.type === 'tuple[]') {
const tupleString = this.multicallGetFunctionSignature('', input.components)
const arrayString = `${tupleString}[]`
types.push(arrayString)
continue
}
types.push(input.type)
}
const typeString = types.join(',')
const functionSignature = `${name}(${typeString})`
return functionSignature
}

private static makeMulticallCallData(name: string, inputs: ethers.utils.ParamType[], params: any[]) {
const functionSignature = this.multicallGetFunctionSignature(name, inputs)
const functionHash = keccak256(toUtf8Bytes(functionSignature))
const functionData = functionHash.substring(2, 10)
const abiCoder = new AbiCoder()
const argumentString = abiCoder.encode(inputs, params)
const argumentData = argumentString.substring(2)
const inputData = `0x${functionData}${argumentData}`
return inputData
}

private static fromMulticallResult(outputs: ethers.utils.ParamType[], data: BytesLike) {
const abiCoder = new AbiCoder()
const params = abiCoder.decode(outputs, data)
return params
}

private static async multicall<T extends any[] = any[]>(
calls: ContractCall[],
provider: ethers.providers.Provider
): Promise<T> {
// Multicall3 address deployed on 100+ chains. Code cannot be changed and
// nothing else can be deployed on this address on any chain.
// So if a chain ever doesn't have the deployment yet, the function will
// throw. Which is what we want.
// https://github.com/mds1/multicall
const multicallAddress = '0xcA11bde05977b3631167028862bE2a173976CA11'
const multicallAbi = [
'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)',
]

const multicall = new ethers.Contract(multicallAddress, multicallAbi, provider)
const callRequests = calls.map((call) => {
const callData = this.makeMulticallCallData(call.name, call.inputs, call.params)
return {
target: call.contract.address,
allowFailure: false,
callData: callData,
}
})
const response = await multicall.callStatic.aggregate3(callRequests)
const callCount = calls.length
const callResult = [] as unknown as T
for (let i = 0; i < callCount; i++) {
const outputs = calls[i].outputs
const returnData = response[i].returnData
const params = this.fromMulticallResult(outputs, returnData)
const result = outputs.length === 1 ? params[0] : params
callResult.push(result)
}
return callResult
}
}

interface ContractCall {
contract: {
address: string
}
name: string
inputs: ethers.utils.ParamType[]
outputs: ethers.utils.ParamType[]
params: any[]
}
67 changes: 30 additions & 37 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@
integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==

"@babel/core@^7.11.6", "@babel/core@^7.12.3":
version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.6.tgz#8be77cd77c55baadcc1eae1c33df90ab6d2151d4"
integrity sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==
version "7.23.7"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.7.tgz#4d8016e06a14b5f92530a13ed0561730b5c6483f"
integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.23.5"
"@babel/generator" "^7.23.6"
"@babel/helper-compilation-targets" "^7.23.6"
"@babel/helper-module-transforms" "^7.23.3"
"@babel/helpers" "^7.23.6"
"@babel/helpers" "^7.23.7"
"@babel/parser" "^7.23.6"
"@babel/template" "^7.22.15"
"@babel/traverse" "^7.23.6"
"@babel/traverse" "^7.23.7"
"@babel/types" "^7.23.6"
convert-source-map "^2.0.0"
debug "^4.1.0"
Expand Down Expand Up @@ -137,13 +137,13 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307"
integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==

"@babel/helpers@^7.23.6":
version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.6.tgz#d03af2ee5fb34691eec0cda90f5ecbb4d4da145a"
integrity sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==
"@babel/helpers@^7.23.7":
version "7.23.7"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.7.tgz#eb543c36f81da2873e47b76ee032343ac83bba60"
integrity sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==
dependencies:
"@babel/template" "^7.22.15"
"@babel/traverse" "^7.23.6"
"@babel/traverse" "^7.23.7"
"@babel/types" "^7.23.6"

"@babel/highlight@^7.23.4":
Expand Down Expand Up @@ -267,10 +267,10 @@
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"

"@babel/traverse@^7.23.6":
version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5"
integrity sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==
"@babel/traverse@^7.23.7":
version "7.23.7"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305"
integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==
dependencies:
"@babel/code-frame" "^7.23.5"
"@babel/generator" "^7.23.6"
Expand Down Expand Up @@ -949,9 +949,9 @@
"@babel/types" "^7.0.0"

"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
version "7.20.4"
resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.4.tgz#ec2c06fed6549df8bc0eb4615b683749a4a92e1b"
integrity sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.5.tgz#7b7502be0aa80cc4ef22978846b983edaafcd4dd"
integrity sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==
dependencies:
"@babel/types" "^7.20.7"

Expand Down Expand Up @@ -1006,9 +1006,9 @@
parse5 "^7.0.0"

"@types/node@*":
version "20.10.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2"
integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==
version "20.10.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.6.tgz#a3ec84c22965802bf763da55b2394424f22bfbb5"
integrity sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==
dependencies:
undici-types "~5.26.4"

Expand Down Expand Up @@ -1124,9 +1124,9 @@ acorn-walk@^8.0.2:
integrity sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==

acorn@^8.1.0, acorn@^8.8.1:
version "8.11.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b"
integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==
version "8.11.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==

[email protected]:
version "3.0.0"
Expand Down Expand Up @@ -1356,9 +1356,9 @@ camelcase@^6.2.0:
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==

caniuse-lite@^1.0.30001565:
version "1.0.30001571"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001571.tgz#4182e93d696ff42930f4af7eba515ddeb57917ac"
integrity sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ==
version "1.0.30001574"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001574.tgz#fb4f1359c77f6af942510493672e1ec7ec80230c"
integrity sha512-BtYEK4r/iHt/txm81KBudCUcTy7t+s9emrIaHqjYurQ10x71zJ5VQ9x1dYPcz/b+pKSp4y/v1xSI67A+LzpNyg==

chalk@^2.0.1, chalk@^2.4.2:
version "2.4.2"
Expand Down Expand Up @@ -1580,9 +1580,9 @@ dotenv@^14.2.0:
integrity sha512-vwEppIphpFdvaMCaHfCEv9IgwcxMljMw2TnAQBB4VWPvzXQLTb82jwmdOKzlEVUL3gNFT4l4TPKO+Bn+sqcrVQ==

electron-to-chromium@^1.4.601:
version "1.4.616"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz#4bddbc2c76e1e9dbf449ecd5da3d8119826ea4fb"
integrity sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==
version "1.4.622"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.622.tgz#925d8b2264abbcbe264a9a6290d97b9e5a1af205"
integrity sha512-GZ47DEy0Gm2Z8RVG092CkFvX7SdotG57c4YZOe8W8qD4rOmk3plgeNmiLVRHP/Liqj1wRiY3uUUod9vb9hnxZA==

[email protected]:
version "6.5.4"
Expand Down Expand Up @@ -1660,14 +1660,7 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==

ethers-multicall@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/ethers-multicall/-/ethers-multicall-0.2.3.tgz#872b5ad7d6b5d4d7f2960c33bf36bd46d034ac41"
integrity sha512-RaWQuLy+HzeKOibptlc9RZ6j7bT1H6VnkdAKTHiLx2t/lpyfS2ckXHdQhhRbCaXNc1iu6CgoisgMejxKHg84tg==
dependencies:
ethers "^5.0.0"

ethers@^5.0.0, ethers@^5.7.2:
ethers@^5.7.2:
version "5.7.2"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e"
integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==
Expand Down

0 comments on commit 504d89d

Please sign in to comment.