-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathmerkle-funder.ts
139 lines (122 loc) · 5.54 KB
/
merkle-funder.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import { LogOptions, getGasPrice, logger } from '@api3/airnode-utilities';
import { go } from '@api3/promise-utils';
import { ethers } from 'ethers';
import { computeMerkleFunderDepositoryAddress, decodeRevertString, estimateMulticallGasLimit } from './evm';
import buildMerkleTree from './merkle-tree';
import { ChainConfig } from './types';
import { MerkleFunder } from './contracts';
export const fundChainRecipients = async (
chainId: string,
chainConfig: Pick<ChainConfig, 'options' | 'merkleFunderDepositories'>,
merkleFunderContract: MerkleFunder,
logOptions: LogOptions
) => {
logger.info(`Processing ${chainConfig.merkleFunderDepositories.length} merkleFunderDepositories...`, logOptions);
let nonce: number | undefined = undefined;
for (const { owner, values } of chainConfig.merkleFunderDepositories) {
// Build merkle tree
const tree = buildMerkleTree(values);
logger.debug(`Merkle tree:\n${tree.render()}`, logOptions);
const merkleFunderDepositoryAddress = await computeMerkleFunderDepositoryAddress(
merkleFunderContract.address,
owner,
tree.root
);
const depositoryLogOptions = {
...logOptions,
meta: { ...logOptions.meta, DEPOSITORY: merkleFunderDepositoryAddress },
};
const getBlockNumberCalldata = merkleFunderContract.interface.encodeFunctionData('getBlockNumber');
const multicallCalldata = values.map(({ recipient, lowThreshold, highThreshold }, treeValueIndex) => {
logger.debug(`Testing funding of ${recipient}`, depositoryLogOptions);
logger.debug(JSON.stringify({ lowThreshold, highThreshold }, null, 2), depositoryLogOptions);
return {
recipient,
calldata: merkleFunderContract.interface.encodeFunctionData('fund', [
owner,
tree.root,
tree.getProof(treeValueIndex),
recipient,
ethers.utils.parseUnits(lowThreshold.value.toString(), lowThreshold.unit),
ethers.utils.parseUnits(highThreshold.value.toString(), highThreshold.unit),
]),
};
});
const tryStaticMulticallResult = await go(() =>
merkleFunderContract.callStatic.tryMulticall([
getBlockNumberCalldata,
...multicallCalldata.map((c) => c.calldata),
])
);
if (!tryStaticMulticallResult.success) {
logger.info(
`Failed to call merkleFunderContract.callStatic.tryMulticall: ${tryStaticMulticallResult.error.message}`,
depositoryLogOptions
);
continue;
}
const {
successes: [getBlockNumberSuccess, ...remainingSuccesses],
returndata: [getBlockNumberReturndata, ...remainingRetunrdata],
} = tryStaticMulticallResult.data;
// Get block number to use as argument when fetching the transaction count
if (!getBlockNumberSuccess) {
logger.info(
`Failed to fetch block number: ${decodeRevertString(getBlockNumberReturndata)}`,
depositoryLogOptions
);
continue;
}
const blockNumber = ethers.BigNumber.from(getBlockNumberReturndata);
logger.info(`Block number fetched while testing funding of recipients: ${blockNumber}`, depositoryLogOptions);
// Filter out calldata that failed to be sent
const successfulMulticallCalldata = (remainingSuccesses as boolean[]).reduce(
(acc: { recipient: string; calldata: string }[], success, index) => {
if (!success) {
const reason =
merkleFunderContract.interface.parseError(remainingRetunrdata[index])?.name ??
decodeRevertString(remainingRetunrdata[index]);
logger.info(
`Funding test of ${multicallCalldata[index].recipient} reverted with message: ${reason}`,
depositoryLogOptions
);
return acc;
}
logger.info(`Funding test of ${multicallCalldata[index].recipient} succeeded`, depositoryLogOptions);
return [...acc, multicallCalldata[index]];
},
[]
);
// Try to send the calldatas
// TODO: A potential improvement here is to batch these calls
if (successfulMulticallCalldata.length > 0) {
nonce =
nonce ??
(await merkleFunderContract.signer.getTransactionCount(
// HACK: Arbitrum returns the L1 block number so we need to fetch the L2 block number via provider RPC call
chainId === '42161' ? await merkleFunderContract.provider.getBlockNumber() : blockNumber.toNumber()
));
logger.info(`tryMulticall transaction nonce: ${nonce}`, depositoryLogOptions);
// Get the latest gas price
const [logs, gasTarget] = await getGasPrice(merkleFunderContract.provider, chainConfig.options);
logs.forEach((log) => logger.info(log.error ? log.error.message : log.message), depositoryLogOptions);
const calldatas = successfulMulticallCalldata.map((c) => c.calldata);
const gasLimit = await estimateMulticallGasLimit(merkleFunderContract, calldatas, gasTarget.gasLimit);
logger.debug(`Gas limit: ${gasLimit.toString()}`, logOptions);
// We still tryMulticall in case a recipient is funded by someone else in the meantime
const tryMulticallResult = await go(() =>
merkleFunderContract.tryMulticall(calldatas, { nonce, ...gasTarget, gasLimit })
);
if (!tryMulticallResult.success) {
logger.error(
`Failed to call merkleFunderContract.tryMulticall: ${tryMulticallResult.error.message}`,
tryMulticallResult.error,
depositoryLogOptions
);
continue;
}
logger.info(`Sent tx with hash ${tryMulticallResult.data.hash}`, depositoryLogOptions);
nonce++;
}
}
};