There are different implementations of multicall:
- https://github.com/Vectorized/multicaller
- https://github.com/mds1/multicall
- https://github.com/AmazingAng/WTF-Solidity/tree/main/55_MultiCall
In the following section, we will use Multicaller
as an example to illustrate the process.
The main idea of Multicaller
is to aggregate multiple contract function calls into a single one. It's usually to batch contract reads from off-chain apps. However, it could also be used to batch contract writes.
Multiple Contract Reads
import { defaultAbiCoder } from "ethers/lib/utils"
class Liquidator {
async fetchIsLiquidatableResults(
marketId: number,
positions: Position[],
) {
const price = await this.pythService.fetchPythOraclePrice(marketId)
const targets = new Array(positions.length).fill(this.exchange.address)
const data = positions.map(position =>
this.exchange.interface.encodeFunctionData("isLiquidatable", [
marketId,
position.account,
price,
]),
)
const values = new Array(accountMarkets.length).fill(0)
return await this.multicaller.callStatic.aggregate(targets, data, values)
}
async start() {
const positions = await this.fetchPositions(marketId)
const results = await this.fetchIsLiquidatableResults(marketId, positions)
for (const [i, result] of results.entries()) {
const isLiquidatable = defaultAbiCoder.decode(["bool"], result)[0]
const position = positions[i]
console.log(`${position.account} isLiquidatable: ${isLiquidatable}`)
}
}
}
ref:
https://github.com/Vectorized/multicaller/blob/main/API.md#aggregate
Multiple Contract Writes
It requires the target contract is compatible with Multicaller
if the target contract needs to read msg.sender
.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import { LibMulticaller } from "multicaller/LibMulticaller.sol";
contract MulticallerSenderCompatible {
function _sender() internal view virtual returns (address) {
return LibMulticaller.sender();
}
}
contract Exchange is MulticallerSenderCompatible {
function openPosition(OpenPositionParams calldata params) external returns (int256, int256) {
address taker = _sender();
return _openPositionFor(taker, params);
}
}
class Bot {
async openPosition() {
const targets = [
this.oracleAdapter.address,
this.exchange.address,
]
const data = [
this.oracleAdapter.interface.encodeFunctionData("updatePrice", [priceId, priceData]),
this.exchange.interface.encodeFunctionData("openPosition", [params]),
]
const values = [
BigNumber.from(0),
BigNumber.from(0),
]
// update oracle price first, then open position
const tx = await this.multicaller.connect(taker).aggregateWithSender(targets, data, values)
await tx.wait()
}
}
ref:
https://github.com/Vectorized/multicaller/blob/main/API.md#aggregatewithsender