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 <amatei@umaproject.org> * Fix linter issue Signed-off-by: amateima <amatei@umaproject.org> * Revert client to old implementation Signed-off-by: amateima <amatei@umaproject.org> * Add Multicall2 separate client Signed-off-by: amateima <amatei@umaproject.org> * Implement changes suggested in review Signed-off-by: amateima <amatei@umaproject.org> * Improve tests Signed-off-by: amateima <amatei@umaproject.org> * Improve typescript integration Signed-off-by: amateima <amatei@umaproject.org> * Make contract abstract Signed-off-by: amateima <amatei@umaproject.org>
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy