Skip to content

Commit bb31e8f

Browse files
authored
improve(sdk): add support for Multicall2 contract (#3325)
* Add support for Multipart2 contract Signed-off-by: amateima <[email protected]> * Fix linter issue Signed-off-by: amateima <[email protected]> * Revert client to old implementation Signed-off-by: amateima <[email protected]> * Add Multicall2 separate client Signed-off-by: amateima <[email protected]> * Implement changes suggested in review Signed-off-by: amateima <[email protected]> * Improve tests Signed-off-by: amateima <[email protected]> * Improve typescript integration Signed-off-by: amateima <[email protected]> * Make contract abstract Signed-off-by: amateima <[email protected]>
1 parent 2fbce13 commit bb31e8f

File tree

8 files changed

+138
-1
lines changed

8 files changed

+138
-1
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity >=0.5.0;
3+
4+
/**
5+
* @title interface for MakerDao's Multicall2 contract.
6+
* @dev This adds method to allow calls within the batch to fail
7+
* @dev Full implementation can be found here: https://github.com/makerdao/multicall/blob/16ec5e2859b3a4829ceed4ee1ef609e6e9a744ee/src/Multicall2.sol
8+
*/
9+
abstract contract Multicall2 {
10+
struct Call {
11+
address target;
12+
bytes callData;
13+
}
14+
struct Result {
15+
bool success;
16+
bytes returnData;
17+
}
18+
19+
function aggregate(Call[] memory calls) public virtual returns (uint256 blockNumber, bytes[] memory returnData);
20+
21+
function tryBlockAndAggregate(bool requireSuccess, Call[] memory calls)
22+
public
23+
virtual
24+
returns (
25+
uint256 blockNumber,
26+
bytes32 blockHash,
27+
Result[] memory returnData
28+
);
29+
}

packages/sdk/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@
4242
"highland": "^2.13.5"
4343
},
4444
"devDependencies": {
45-
"@types/highland": "^2.12.13",
4645
"@ethersproject/abi": "^5.4.0",
4746
"@ethersproject/abstract-provider": "^5.4.0",
4847
"@ethersproject/contracts": "^5.4.0",
4948
"@ethersproject/providers": "^5.4.2",
5049
"@size-limit/preset-small-lib": "^4.10.2",
50+
"@types/dotenv": "^8.2.0",
51+
"@types/highland": "^2.12.13",
5152
"size-limit": "^4.10.2",
5253
"tsdx": "^0.14.1",
5354
"tslib": "^2.2.0"

packages/sdk/src/clients/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export * as registry from "./registry";
22
export * as emp from "./emp";
33
export * as erc20 from "./erc20";
44
export * as multicall from "./multicall";
5+
export * as multicall2 from "./multicall2";
56
export * as lspCreator from "./lsp-creator";
67
export * as lsp from "./lsp";

packages/sdk/src/clients/multicall/client.e2e.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ describe("multicall", function () {
1818
assert.ok(client);
1919
assert.ok(empClient);
2020
});
21+
2122
test("multicall on emp", async function () {
2223
const calls = ["priceIdentifier", "tokenCurrency", "collateralCurrency"];
2324
const multicalls = calls.map((call: any) => {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# UMA Multicall2 Client
2+
3+
This client helps you batch multiple calls together in a single transaction. Useful also for
4+
reducing api calls to infura when reading many properties from contracts.
5+
6+
## Usage
7+
8+
```ts
9+
import { ethers } from "ethers"
10+
import * as uma from "@uma/sdk"
11+
12+
// assume you have a url injected from env
13+
const provider = new ethers.providers.WebSocketProvider(env.CUSTOM_NODE_URL)
14+
15+
// get the contract instance
16+
const client = uma.clients.multicall2.connect(address, provider)
17+
```
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import assert from "assert";
2+
import * as Client from "./client";
3+
import { ethers } from "ethers";
4+
import { emp } from "..";
5+
import dotenv from "dotenv";
6+
7+
dotenv.config();
8+
// multicall contract deployed to mainnet
9+
const address = "0x5ba1e12693dc8f9c48aad8770482f4739beed696";
10+
const empAddress = "0x4E3168Ea1082f3dda1694646B5EACdeb572009F1";
11+
// these require integration testing, skip for ci
12+
describe("multicall2", function () {
13+
let client: Client.Instance;
14+
let empClient: emp.Instance;
15+
16+
test("inits", function () {
17+
const provider = ethers.providers.getDefaultProvider(process.env.CUSTOM_NODE_URL);
18+
client = Client.connect(address, provider);
19+
empClient = emp.connect(empAddress, provider);
20+
assert.ok(client);
21+
assert.ok(empClient);
22+
});
23+
24+
test("multicall2 on emp", async function () {
25+
const calls = ["priceIdentifier", "tokenCurrency", "collateralCurrency"];
26+
const multicalls = calls.map((call: any) => {
27+
return {
28+
target: empAddress,
29+
callData: empClient.interface.encodeFunctionData(call),
30+
};
31+
});
32+
const response = await client.callStatic.aggregate(multicalls);
33+
const decoded = calls.map((call: any, i: number) => {
34+
const result = response.returnData[i];
35+
return empClient.interface.decodeFunctionResult(call, result);
36+
});
37+
assert.equal(decoded.length, calls.length);
38+
});
39+
40+
test("multicall2 on emp with no errors", async function () {
41+
const calls = ["priceIdentifier", "tokenCurrency", "collateralCurrency"];
42+
const multicalls = calls.map((call: any) => {
43+
return {
44+
target: empAddress,
45+
callData: empClient.interface.encodeFunctionData(call),
46+
};
47+
});
48+
const response = await client.callStatic.tryBlockAndAggregate(false, multicalls);
49+
const decoded = calls.map((call: any, i: number) => {
50+
const result = response.returnData[i].returnData;
51+
return empClient.interface.decodeFunctionResult(call, result);
52+
});
53+
assert.equal(decoded.length, calls.length);
54+
});
55+
56+
test("multicall2 on emp with errors", async function () {
57+
const calls = ["priceIdentifier", "tokenCurrency", "disputeBondPercentage"];
58+
const multicalls = calls.map((call) => ({
59+
target: empAddress,
60+
callData: empClient.interface.encodeFunctionData(call as any),
61+
}));
62+
const response = await client.callStatic.tryBlockAndAggregate(false, multicalls);
63+
const decoded: ethers.utils.Result[] = [];
64+
const failedCalls: string[] = [];
65+
66+
for (let i = 0; i < calls.length; i++) {
67+
const result = response.returnData[i].returnData;
68+
const call = calls[i];
69+
70+
if (response.returnData[i].success) {
71+
decoded.push(empClient.interface.decodeFunctionResult(call as any, result));
72+
} else {
73+
failedCalls.push(call);
74+
}
75+
}
76+
assert.ok(decoded.length === 2 && failedCalls.length == 1);
77+
});
78+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { EthersContracts } from "@uma/core";
2+
import type { SignerOrProvider } from "../..";
3+
4+
export type Instance = EthersContracts.Multicall2;
5+
const Factory = EthersContracts.Multicall2__factory;
6+
7+
export function connect(address: string, provider: SignerOrProvider): Instance {
8+
return Factory.connect(address, provider);
9+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./client";

0 commit comments

Comments
 (0)