Skip to content
This repository was archived by the owner on Aug 12, 2023. It is now read-only.

Commit 3433f71

Browse files
committed
refactor abi mapper for graceful options
1 parent fccea41 commit 3433f71

File tree

7 files changed

+249
-5
lines changed

7 files changed

+249
-5
lines changed

src/AbiMapper.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { Interface } from '@ethersproject/abi';
2+
import { multicall1, multicall2, multicall3 } from './abis';
3+
import { Options } from './models';
4+
import { networks } from './networks';
5+
import { Address, Mapping, Network } from './types';
6+
7+
// Checks if the provided address is a valid multicall address in our network definitions
8+
const constructWithAddress = (address: Address): Mapping => {
9+
let mapping: Mapping = {
10+
found: false,
11+
address: networks['1']['multicall3'],
12+
network: 1,
13+
interface: new Interface(multicall3),
14+
abi: multicall3,
15+
};
16+
17+
// Iterate over the networks
18+
for (const network in networks) {
19+
// If we have a match, return the network
20+
const networkObject: Mapping = networkToMapping(
21+
parseInt(network),
22+
networks[network],
23+
address
24+
);
25+
if (networkObject.found) {
26+
mapping = networkObject;
27+
break;
28+
}
29+
}
30+
31+
return mapping;
32+
};
33+
34+
// Attempts to construct a mapping from a network object with the provided multicall address
35+
const networkToMapping = (
36+
network: Network,
37+
networkObject: object,
38+
address: Address
39+
): Mapping => {
40+
let mapping: Mapping = {
41+
found: false,
42+
address: networks['1']['multicall3'],
43+
network: 1,
44+
interface: new Interface(multicall3),
45+
abi: multicall3,
46+
};
47+
48+
if (networkObject['multicall3'].toLowerCase() == address.toLowerCase()) {
49+
mapping = {
50+
found: true,
51+
address: networkObject['multicall3'],
52+
network,
53+
interface: new Interface(multicall3),
54+
abi: multicall3,
55+
};
56+
} else if (
57+
networkObject['multicall2'].toLowerCase() == address.toLowerCase()
58+
) {
59+
mapping = {
60+
found: true,
61+
address: networkObject['multicall2'],
62+
network,
63+
interface: new Interface(multicall2),
64+
abi: multicall2,
65+
};
66+
} else if (
67+
networkObject['multicall'].toLowerCase() == address.toLowerCase()
68+
) {
69+
mapping = {
70+
found: true,
71+
address: networks[network.toString()]['multicall'],
72+
network,
73+
interface: new Interface(multicall1),
74+
abi: multicall1,
75+
};
76+
}
77+
78+
return mapping;
79+
};
80+
81+
// Maps Multicall address string to an Interface generated from our stored ABIs
82+
// Network defaults to 1 (mainnet) if multicall address isn't found
83+
// If an inconsistency is found between the network and multicall address string,
84+
// the multicall address is first checked, then we will default to the multicall
85+
// instance of the provided network
86+
const abiMap = (options?: Options): Mapping => {
87+
// Craft the default mapping
88+
let mapping: Mapping = {
89+
found: false,
90+
address: networks['1']['multicall3'],
91+
network: 1,
92+
interface: new Interface(multicall3),
93+
abi: multicall3,
94+
};
95+
96+
// If we have options
97+
if (options) {
98+
// Deconstruct our option parameters
99+
const { multicall: address, network } = options;
100+
101+
if (network) {
102+
const networkObject: object = networks[network.toString()];
103+
104+
// If we have a network, let's check if we have a multicall address
105+
if (networkObject) {
106+
// Try to get multicall with address
107+
const foundMulticall = networkToMapping(
108+
network,
109+
networkObject,
110+
address || '0x0' // if no address, 0x0 shouldn't work
111+
);
112+
if (foundMulticall.found) {
113+
mapping = foundMulticall;
114+
} else {
115+
// The network didn't contain the expected multicall address
116+
// Check if networks contains the address
117+
const validatedNetwork: Mapping = constructWithAddress(
118+
address || '0x0'
119+
);
120+
121+
// If the address isn't found, we can use the network
122+
if (!validatedNetwork.found) {
123+
// Grab the most up to date multicall address
124+
if (networkObject['multicall3']) {
125+
mapping = {
126+
found: true,
127+
address: networkObject['multicall3'],
128+
network,
129+
interface: new Interface(multicall3),
130+
abi: multicall3,
131+
};
132+
} else if (networkObject['multicall2']) {
133+
mapping = {
134+
found: true,
135+
address: networkObject['multicall2'],
136+
network,
137+
interface: new Interface(multicall2),
138+
abi: multicall2,
139+
};
140+
} else if (networkObject['multicall1']) {
141+
mapping = {
142+
found: true,
143+
address: networkObject['multicall1'],
144+
network,
145+
interface: new Interface(multicall1),
146+
abi: multicall1,
147+
};
148+
} else {
149+
// We have to use the default since none of the network multicalls are defined
150+
// This should really never happen
151+
}
152+
} else {
153+
// Otherwise, use the address
154+
mapping = validatedNetwork;
155+
}
156+
}
157+
} else {
158+
// No network, try to find multicall address and use it
159+
mapping = constructWithAddress(address || '0x0');
160+
}
161+
} else {
162+
// No network, try to find multicall address and use it
163+
mapping = constructWithAddress(address || '0x0');
164+
}
165+
}
166+
167+
// Return the constructed mapping
168+
return mapping;
169+
};
170+
171+
export default abiMap;
172+
export { Mapping, abiMap, constructWithAddress, networkToMapping };

src/Multicall.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import {
66
AggregatedCall,
77
AggregateFullResponse,
88
ContractCall,
9+
Options,
910
} from './models';
10-
11-
type Address = string;
11+
import abiMap, { Mapping } from 'AbiMapper';
12+
import { Address, Network } from './types';
1213

1314
// Multicall - A library for calling multiple contracts in aggregate
1415
export class Multicall {
@@ -21,7 +22,7 @@ export class Multicall {
2122
constructor(options?: {
2223
address?: Address;
2324
provider?: ethers.providers.Provider | ethers.Signer;
24-
network?: number;
25+
network?: Network;
2526
}) {
2627
// Extract the network or default to 1
2728
this.chainId = options && options.network ? options.network : 1;
@@ -251,13 +252,14 @@ export class Multicall {
251252
}
252253

253254
public static async call(
254-
calls: ContractCall[] | ContractCall
255+
calls: ContractCall[] | ContractCall,
256+
options?: Options
255257
): Promise<AggregateFullResponse> {
256258
// Encode the calls
257259
const encoded: AggregatedCall[] = Multicall.encode(calls);
258260

259261
// Craft default configuration
260-
const abi: object = multicall3;
262+
const abi: Mapping = abiMap(options);
261263
const multicall3Address: string = networks['1']['multicall3'];
262264
const provider: ethers.providers.Provider | ethers.Signer =
263265
ethers.getDefaultProvider();

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export { Multicall } from './Multicall';
2+
export * from './types';
3+
export * from './AbiMapper';

src/models/Options.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ethers } from 'ethers';
2+
3+
export interface Options {
4+
abi?: any[];
5+
multicall?: string;
6+
provider?: ethers.providers.Provider | ethers.Signer;
7+
network?: number;
8+
}

src/models/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { AggregateFullResponse } from './AggregateFullResponse';
44
import { AggregatedCall } from './AggregatedCall';
55
import { ContractCall } from './ContractCall';
66
import { SingleCall } from './SingleCall';
7+
import { Options } from './Options';
78

89
// Re-exports
910
export {
@@ -12,5 +13,6 @@ export {
1213
AggregatedCall,
1314
ContractCall,
1415
SingleCall,
16+
Options,
1517
};
1618
export default {};

src/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Interface } from 'ethers/lib/utils';
2+
3+
export type Network = number;
4+
export type Address = string;
5+
6+
export interface Mapping {
7+
found: boolean;
8+
address: Address;
9+
network: Network;
10+
interface: Interface;
11+
abi: object;
12+
}

tests/AbiMapper.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { multicall1, multicall2, multicall3 } from '../src/abis';
2+
import { networks } from '../src/networks';
3+
import { Address, Mapping } from '../src/types';
4+
import { constructWithAddress } from '../src';
5+
6+
describe('Constructs Mapping with Address', () => {
7+
it('Non-existent address returns default mapping', () => {
8+
const zero: Address = '0x0000000000000000000000000000000000000000';
9+
const mapping: Mapping = constructWithAddress(zero);
10+
11+
expect(mapping.found).toBe(false);
12+
expect(mapping.address).toEqual(networks['1']['multicall3']);
13+
expect(mapping.network).toEqual(1);
14+
expect(mapping.abi).toEqual(multicall3);
15+
});
16+
17+
it('Existing Multicall 3 properly construct mappings', () => {
18+
const goerliMulticall3: Address = networks['5']['multicall3'];
19+
const mapping: Mapping = constructWithAddress(goerliMulticall3);
20+
21+
expect(mapping.found).toBe(true);
22+
expect(mapping.address).toEqual(goerliMulticall3);
23+
expect(mapping.network).toEqual(5);
24+
expect(mapping.abi).toEqual(multicall3);
25+
});
26+
27+
it('Existing Multicall 2 properly construct mappings', () => {
28+
const rinkebyMulticall2: Address = networks['4']['multicall2'];
29+
const mapping: Mapping = constructWithAddress(rinkebyMulticall2);
30+
31+
expect(mapping.found).toBe(true);
32+
expect(mapping.address).toEqual(rinkebyMulticall2);
33+
expect(mapping.network).toEqual(4);
34+
expect(mapping.abi).toEqual(multicall2);
35+
});
36+
37+
it('Existing Multicall 1 properly construct mappings', () => {
38+
const ropstenMulticall1: Address = networks['3']['multicall1'];
39+
const mapping: Mapping = constructWithAddress(ropstenMulticall1);
40+
41+
expect(mapping.found).toBe(true);
42+
expect(mapping.address).toEqual(ropstenMulticall1);
43+
expect(mapping.network).toEqual(3);
44+
expect(mapping.abi).toEqual(multicall1);
45+
});
46+
});

0 commit comments

Comments
 (0)