@hyperlane-xyz/sdk 16.0.0 → 16.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/block-explorer/etherscan.d.ts +1 -12
- package/dist/block-explorer/etherscan.d.ts.map +1 -1
- package/dist/block-explorer/etherscan.js +14 -2
- package/dist/block-explorer/etherscan.js.map +1 -1
- package/dist/block-explorer/utils.d.ts +3 -0
- package/dist/block-explorer/utils.d.ts.map +1 -0
- package/dist/block-explorer/utils.js +16 -0
- package/dist/block-explorer/utils.js.map +1 -0
- package/dist/consts/testChains.d.ts +2 -0
- package/dist/consts/testChains.d.ts.map +1 -1
- package/dist/consts/testChains.js +36 -1
- package/dist/consts/testChains.js.map +1 -1
- package/dist/contracts/contracts.d.ts +9 -1
- package/dist/contracts/contracts.d.ts.map +1 -1
- package/dist/contracts/contracts.js +15 -1
- package/dist/contracts/contracts.js.map +1 -1
- package/dist/deploy/verify/ContractVerifier.d.ts.map +1 -1
- package/dist/deploy/verify/ContractVerifier.js +16 -5
- package/dist/deploy/verify/ContractVerifier.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata/ChainMetadataManager.d.ts +10 -0
- package/dist/metadata/ChainMetadataManager.d.ts.map +1 -1
- package/dist/metadata/ChainMetadataManager.js +33 -2
- package/dist/metadata/ChainMetadataManager.js.map +1 -1
- package/dist/metadata/blockExplorer.d.ts.map +1 -1
- package/dist/metadata/blockExplorer.js +27 -1
- package/dist/metadata/blockExplorer.js.map +1 -1
- package/dist/metadata/blockExplorer.test.js +176 -1
- package/dist/metadata/blockExplorer.test.js.map +1 -1
- package/dist/providers/transactions/submitter/ethersV5/EV5TimelockSubmitter.d.ts.map +1 -1
- package/dist/providers/transactions/submitter/ethersV5/EV5TimelockSubmitter.js +3 -3
- package/dist/providers/transactions/submitter/ethersV5/EV5TimelockSubmitter.js.map +1 -1
- package/dist/rpc/evm/EvmEventLogsReader.d.ts +79 -0
- package/dist/rpc/evm/EvmEventLogsReader.d.ts.map +1 -0
- package/dist/rpc/evm/EvmEventLogsReader.hardhat-test.d.ts +2 -0
- package/dist/rpc/evm/EvmEventLogsReader.hardhat-test.d.ts.map +1 -0
- package/dist/rpc/evm/EvmEventLogsReader.hardhat-test.js +279 -0
- package/dist/rpc/evm/EvmEventLogsReader.hardhat-test.js.map +1 -0
- package/dist/rpc/evm/EvmEventLogsReader.js +107 -0
- package/dist/rpc/evm/EvmEventLogsReader.js.map +1 -0
- package/dist/rpc/evm/types.d.ts +11 -0
- package/dist/rpc/evm/types.d.ts.map +1 -0
- package/dist/rpc/evm/types.js +2 -0
- package/dist/rpc/evm/types.js.map +1 -0
- package/dist/rpc/evm/utils.d.ts +18 -0
- package/dist/rpc/evm/utils.d.ts.map +1 -0
- package/dist/rpc/evm/utils.hardhat-test.d.ts +2 -0
- package/dist/rpc/evm/utils.hardhat-test.d.ts.map +1 -0
- package/dist/rpc/evm/utils.hardhat-test.js +144 -0
- package/dist/rpc/evm/utils.hardhat-test.js.map +1 -0
- package/dist/rpc/evm/utils.js +67 -0
- package/dist/rpc/evm/utils.js.map +1 -0
- package/dist/timelock/evm/EvmTimelockReader.d.ts +30 -0
- package/dist/timelock/evm/EvmTimelockReader.d.ts.map +1 -0
- package/dist/timelock/evm/EvmTimelockReader.hardhat-test.d.ts +2 -0
- package/dist/timelock/evm/EvmTimelockReader.hardhat-test.d.ts.map +1 -0
- package/dist/timelock/evm/EvmTimelockReader.hardhat-test.js +649 -0
- package/dist/timelock/evm/EvmTimelockReader.hardhat-test.js.map +1 -0
- package/dist/timelock/evm/EvmTimelockReader.js +173 -0
- package/dist/timelock/evm/EvmTimelockReader.js.map +1 -0
- package/dist/timelock/evm/utils.d.ts +4 -0
- package/dist/timelock/evm/utils.d.ts.map +1 -0
- package/dist/timelock/evm/utils.js +11 -0
- package/dist/timelock/evm/utils.js.map +1 -0
- package/dist/timelock/types.d.ts +11 -0
- package/dist/timelock/types.d.ts.map +1 -1
- package/dist/timelock/types.js.map +1 -1
- package/dist/token/checker.js +2 -2
- package/dist/token/checker.js.map +1 -1
- package/dist/token/types.d.ts +1 -1
- package/dist/token/xerc20.d.ts.map +1 -1
- package/dist/token/xerc20.js +3 -35
- package/dist/token/xerc20.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
import chai, { expect } from 'chai';
|
|
2
|
+
import chaiAsPromised from 'chai-as-promised';
|
|
3
|
+
import { ethers } from 'ethers';
|
|
4
|
+
import hre from 'hardhat';
|
|
5
|
+
import { TimelockController__factory } from '@hyperlane-xyz/core';
|
|
6
|
+
import { assert, deepCopy, normalizeAddressEvm } from '@hyperlane-xyz/utils';
|
|
7
|
+
import { KNOWN_BASE_TIMELOCK_CONTRACT, TestChainName, baseTestChain, test1, } from '../../consts/testChains.js';
|
|
8
|
+
import { ZBytes32String } from '../../metadata/customZodTypes.js';
|
|
9
|
+
import { MultiProvider } from '../../providers/MultiProvider.js';
|
|
10
|
+
import { randomAddress } from '../../test/testUtils.js';
|
|
11
|
+
import { EvmTimelockDeployer } from './EvmTimelockDeployer.js';
|
|
12
|
+
import { EvmTimelockReader } from './EvmTimelockReader.js';
|
|
13
|
+
import { EMPTY_BYTES_32 } from './constants.js';
|
|
14
|
+
chai.use(chaiAsPromised);
|
|
15
|
+
describe(EvmTimelockReader.name, () => {
|
|
16
|
+
let contractOwner;
|
|
17
|
+
let proposer;
|
|
18
|
+
let executor;
|
|
19
|
+
let providerChainTest1;
|
|
20
|
+
let multiProvider;
|
|
21
|
+
let timelockDeployer;
|
|
22
|
+
let timelockReader;
|
|
23
|
+
let timelockAddress;
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
[contractOwner, proposer, executor] = await hre.ethers.getSigners();
|
|
26
|
+
assert(contractOwner.provider, 'Provider should be available');
|
|
27
|
+
providerChainTest1 = contractOwner.provider;
|
|
28
|
+
// Initialize MultiProvider with test chain
|
|
29
|
+
const testChain1Clone = deepCopy(test1);
|
|
30
|
+
testChain1Clone.blockExplorers = [];
|
|
31
|
+
multiProvider = new MultiProvider({
|
|
32
|
+
[TestChainName.test1]: testChain1Clone,
|
|
33
|
+
});
|
|
34
|
+
multiProvider.setProvider(TestChainName.test1, providerChainTest1);
|
|
35
|
+
multiProvider.setSharedSigner(contractOwner);
|
|
36
|
+
// Deploy timelock contract
|
|
37
|
+
timelockDeployer = new EvmTimelockDeployer(multiProvider);
|
|
38
|
+
});
|
|
39
|
+
async function deployTestTimelock() {
|
|
40
|
+
const config = {
|
|
41
|
+
minimumDelay: 0,
|
|
42
|
+
proposers: [proposer.address],
|
|
43
|
+
executors: [executor.address],
|
|
44
|
+
admin: contractOwner.address,
|
|
45
|
+
};
|
|
46
|
+
const { TimelockController } = await timelockDeployer.deployContracts(TestChainName.test1, config);
|
|
47
|
+
timelockAddress = TimelockController.address;
|
|
48
|
+
assert(TimelockController.deployTransaction.blockNumber, 'Expected the Timelock deployment block number to be defined');
|
|
49
|
+
return TimelockController;
|
|
50
|
+
}
|
|
51
|
+
describe(EvmTimelockReader.fromConfig.name, () => {
|
|
52
|
+
beforeEach(async () => {
|
|
53
|
+
await deployTestTimelock();
|
|
54
|
+
});
|
|
55
|
+
it('should initialize EvmTimelockReader using fromConfig', async () => {
|
|
56
|
+
const reader = EvmTimelockReader.fromConfig({
|
|
57
|
+
chain: TestChainName.test1,
|
|
58
|
+
timelockAddress,
|
|
59
|
+
multiProvider,
|
|
60
|
+
});
|
|
61
|
+
expect(reader).to.be.instanceOf(EvmTimelockReader);
|
|
62
|
+
expect(reader['timelockInstance'].address).to.equal(timelockAddress);
|
|
63
|
+
expect(reader['chain']).to.equal(TestChainName.test1);
|
|
64
|
+
});
|
|
65
|
+
it('should create reader with valid timelock address', async () => {
|
|
66
|
+
expect(() => {
|
|
67
|
+
EvmTimelockReader.fromConfig({
|
|
68
|
+
chain: TestChainName.test1,
|
|
69
|
+
timelockAddress,
|
|
70
|
+
multiProvider,
|
|
71
|
+
});
|
|
72
|
+
}).to.not.throw();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe(`${EvmTimelockReader.name} (RPC)`, () => {
|
|
76
|
+
beforeEach(async () => {
|
|
77
|
+
await deployTestTimelock();
|
|
78
|
+
timelockReader = EvmTimelockReader.fromConfig({
|
|
79
|
+
chain: TestChainName.test1,
|
|
80
|
+
timelockAddress,
|
|
81
|
+
multiProvider,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe(`${EvmTimelockReader.prototype.getScheduledOperations.name}`, () => {
|
|
85
|
+
it('should return empty object when no transactions are scheduled', async () => {
|
|
86
|
+
const scheduledTxs = await timelockReader.getScheduledOperations();
|
|
87
|
+
expect(scheduledTxs).to.deep.equal({});
|
|
88
|
+
});
|
|
89
|
+
const scheduleTestCases = [
|
|
90
|
+
{
|
|
91
|
+
title: 'should retrieve single scheduled transaction correctly',
|
|
92
|
+
timelockTx: {
|
|
93
|
+
data: [
|
|
94
|
+
{
|
|
95
|
+
to: randomAddress(),
|
|
96
|
+
value: ethers.utils.parseEther('1'),
|
|
97
|
+
data: '0x1234',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
delay: 0,
|
|
101
|
+
predecessor: EMPTY_BYTES_32,
|
|
102
|
+
salt: ethers.utils.formatBytes32String('test-salt'),
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
title: 'should handle multiple scheduled transactions in a batch',
|
|
107
|
+
timelockTx: {
|
|
108
|
+
data: [
|
|
109
|
+
{
|
|
110
|
+
to: randomAddress(),
|
|
111
|
+
value: ethers.utils.parseEther('1'),
|
|
112
|
+
data: '0x1234',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
to: randomAddress(),
|
|
116
|
+
value: ethers.utils.parseEther('2'),
|
|
117
|
+
data: '0x5678',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
delay: 0,
|
|
121
|
+
predecessor: EMPTY_BYTES_32,
|
|
122
|
+
salt: ethers.utils.formatBytes32String('batch-salt'),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
title: 'should handle transactions with no salt (using EMPTY_BYTES_32)',
|
|
127
|
+
timelockTx: {
|
|
128
|
+
data: [
|
|
129
|
+
{
|
|
130
|
+
to: randomAddress(),
|
|
131
|
+
value: ethers.BigNumber.from(0),
|
|
132
|
+
data: '0x',
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
delay: 0,
|
|
136
|
+
predecessor: EMPTY_BYTES_32,
|
|
137
|
+
salt: EMPTY_BYTES_32,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
for (const { title, timelockTx } of scheduleTestCases) {
|
|
142
|
+
it(title, async () => {
|
|
143
|
+
const proposerTimelock = TimelockController__factory.connect(timelockAddress, proposer);
|
|
144
|
+
const targets = timelockTx.data.map((tx) => tx.to);
|
|
145
|
+
const values = timelockTx.data.map((tx) => tx.value ?? '0');
|
|
146
|
+
const dataArray = timelockTx.data.map((tx) => tx.data);
|
|
147
|
+
const scheduleTx = await proposerTimelock.scheduleBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt, timelockTx.delay);
|
|
148
|
+
await scheduleTx.wait();
|
|
149
|
+
const scheduledTxs = await timelockReader.getScheduledOperations();
|
|
150
|
+
const txIds = Object.keys(scheduledTxs);
|
|
151
|
+
expect(txIds).to.have.length(1);
|
|
152
|
+
const scheduledTx = scheduledTxs[txIds[0]];
|
|
153
|
+
expect(scheduledTx.data).to.have.length(timelockTx.data.length);
|
|
154
|
+
for (let i = 0; i < timelockTx.data.length; i++) {
|
|
155
|
+
expect(normalizeAddressEvm(scheduledTx.data[i].to)).to.equal(normalizeAddressEvm(timelockTx.data[i].to));
|
|
156
|
+
assert(scheduledTx.data[i].value, 'Expected value to be defined when reading from Timelock');
|
|
157
|
+
expect(scheduledTx.data[i].value?.toString()).to.equal(timelockTx.data[i].value?.toString() ?? '0');
|
|
158
|
+
expect(scheduledTx.data[i].data).to.equal(timelockTx.data[i].data);
|
|
159
|
+
}
|
|
160
|
+
expect(scheduledTx.delay).to.equal(timelockTx.delay);
|
|
161
|
+
expect(scheduledTx.predecessor).to.equal(timelockTx.predecessor);
|
|
162
|
+
expect(scheduledTx.salt).to.equal(timelockTx.salt);
|
|
163
|
+
expect(scheduledTx.id).to.equal(txIds[0]);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
describe(`${EvmTimelockReader.prototype.getCancelledOperationIds.name}`, () => {
|
|
168
|
+
it('should return empty set when no transactions are cancelled', async () => {
|
|
169
|
+
const cancelledIds = await timelockReader.getCancelledOperationIds();
|
|
170
|
+
expect(cancelledIds.size).to.equal(0);
|
|
171
|
+
});
|
|
172
|
+
const cancelTestCases = [
|
|
173
|
+
{
|
|
174
|
+
title: 'should retrieve single cancelled operation ID correctly',
|
|
175
|
+
timelockTxs: [
|
|
176
|
+
{
|
|
177
|
+
data: [
|
|
178
|
+
{
|
|
179
|
+
to: randomAddress(),
|
|
180
|
+
value: ethers.BigNumber.from(0),
|
|
181
|
+
data: '0x1234',
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
delay: 0,
|
|
185
|
+
predecessor: EMPTY_BYTES_32,
|
|
186
|
+
salt: ethers.utils.formatBytes32String('cancel-test'),
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
title: 'should handle multiple cancelled operations',
|
|
192
|
+
timelockTxs: [
|
|
193
|
+
{
|
|
194
|
+
data: [
|
|
195
|
+
{
|
|
196
|
+
to: randomAddress(),
|
|
197
|
+
value: ethers.BigNumber.from(0),
|
|
198
|
+
data: '0x1234',
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
delay: 0,
|
|
202
|
+
predecessor: EMPTY_BYTES_32,
|
|
203
|
+
salt: ethers.utils.formatBytes32String('cancel-1'),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
data: [
|
|
207
|
+
{
|
|
208
|
+
to: randomAddress(),
|
|
209
|
+
value: ethers.BigNumber.from(0),
|
|
210
|
+
data: '0x5678',
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
delay: 0,
|
|
214
|
+
predecessor: EMPTY_BYTES_32,
|
|
215
|
+
salt: ethers.utils.formatBytes32String('cancel-2'),
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
for (const { title, timelockTxs } of cancelTestCases) {
|
|
221
|
+
it(title, async () => {
|
|
222
|
+
const proposerTimelock = TimelockController__factory.connect(timelockAddress, proposer);
|
|
223
|
+
const operationIds = [];
|
|
224
|
+
// Schedule and cancel operations
|
|
225
|
+
for (const timelockTx of timelockTxs) {
|
|
226
|
+
const targets = timelockTx.data.map((tx) => tx.to);
|
|
227
|
+
const values = timelockTx.data.map((tx) => tx.value ?? '0');
|
|
228
|
+
const dataArray = timelockTx.data.map((tx) => tx.data);
|
|
229
|
+
// Schedule
|
|
230
|
+
const scheduleTx = await proposerTimelock.scheduleBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt, timelockTx.delay);
|
|
231
|
+
await scheduleTx.wait();
|
|
232
|
+
// Get operation ID
|
|
233
|
+
const operationId = await proposerTimelock.hashOperationBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt);
|
|
234
|
+
operationIds.push(operationId);
|
|
235
|
+
// Cancel
|
|
236
|
+
const cancelTx = await proposerTimelock.cancel(operationId);
|
|
237
|
+
await cancelTx.wait();
|
|
238
|
+
}
|
|
239
|
+
const cancelledIds = await timelockReader.getCancelledOperationIds();
|
|
240
|
+
expect(cancelledIds.size).to.equal(timelockTxs.length);
|
|
241
|
+
for (const operationId of operationIds) {
|
|
242
|
+
expect(cancelledIds.has(operationId)).to.be.true;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
describe(`${EvmTimelockReader.prototype.getExecutedOperationIds.name}`, () => {
|
|
248
|
+
it('should return empty set when no transactions are executed', async () => {
|
|
249
|
+
const executedIds = await timelockReader.getExecutedOperationIds();
|
|
250
|
+
expect(executedIds.size).to.equal(0);
|
|
251
|
+
});
|
|
252
|
+
const executeTestCases = [
|
|
253
|
+
{
|
|
254
|
+
title: 'should retrieve single executed operation ID correctly',
|
|
255
|
+
timelockTxs: [
|
|
256
|
+
{
|
|
257
|
+
data: [
|
|
258
|
+
{
|
|
259
|
+
to: randomAddress(),
|
|
260
|
+
value: ethers.BigNumber.from(0),
|
|
261
|
+
data: '0x',
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
delay: 0,
|
|
265
|
+
predecessor: EMPTY_BYTES_32,
|
|
266
|
+
salt: ethers.utils.formatBytes32String('execute-test'),
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
title: 'should handle multiple executed operations',
|
|
272
|
+
timelockTxs: [
|
|
273
|
+
{
|
|
274
|
+
data: [
|
|
275
|
+
{
|
|
276
|
+
to: randomAddress(),
|
|
277
|
+
value: ethers.BigNumber.from(0),
|
|
278
|
+
data: '0x',
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
delay: 0,
|
|
282
|
+
predecessor: EMPTY_BYTES_32,
|
|
283
|
+
salt: ethers.utils.formatBytes32String('execute-1'),
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
data: [
|
|
287
|
+
{
|
|
288
|
+
to: randomAddress(),
|
|
289
|
+
value: ethers.BigNumber.from(0),
|
|
290
|
+
data: '0x',
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
delay: 0,
|
|
294
|
+
predecessor: EMPTY_BYTES_32,
|
|
295
|
+
salt: ethers.utils.formatBytes32String('execute-2'),
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
];
|
|
300
|
+
for (const { title, timelockTxs } of executeTestCases) {
|
|
301
|
+
it(title, async () => {
|
|
302
|
+
const proposerTimelock = TimelockController__factory.connect(timelockAddress, proposer);
|
|
303
|
+
const executorTimelock = TimelockController__factory.connect(timelockAddress, executor);
|
|
304
|
+
const operationIds = [];
|
|
305
|
+
// Schedule and execute operations
|
|
306
|
+
for (const timelockTx of timelockTxs) {
|
|
307
|
+
const targets = timelockTx.data.map((tx) => tx.to);
|
|
308
|
+
const values = timelockTx.data.map((tx) => tx.value ?? '0');
|
|
309
|
+
const dataArray = timelockTx.data.map((tx) => tx.data);
|
|
310
|
+
// Schedule
|
|
311
|
+
const scheduleTx = await proposerTimelock.scheduleBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt, timelockTx.delay);
|
|
312
|
+
await scheduleTx.wait();
|
|
313
|
+
// Get operation ID
|
|
314
|
+
const operationId = await proposerTimelock.hashOperationBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt);
|
|
315
|
+
operationIds.push(operationId);
|
|
316
|
+
// Execute
|
|
317
|
+
const executeTx = await executorTimelock.executeBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt);
|
|
318
|
+
await executeTx.wait();
|
|
319
|
+
}
|
|
320
|
+
const executedIds = await timelockReader.getExecutedOperationIds();
|
|
321
|
+
expect(executedIds.size).to.equal(timelockTxs.length);
|
|
322
|
+
for (const operationId of operationIds) {
|
|
323
|
+
expect(executedIds.has(operationId)).to.be.true;
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
describe(`${EvmTimelockReader.prototype.getReadyOperationIds.name}`, () => {
|
|
329
|
+
it('should return empty set for empty input', async () => {
|
|
330
|
+
const readyIds = await timelockReader.getReadyOperationIds([]);
|
|
331
|
+
expect(readyIds.size).to.equal(0);
|
|
332
|
+
});
|
|
333
|
+
const readyTestCases = [
|
|
334
|
+
{
|
|
335
|
+
title: 'should return ready operations correctly (no delay)',
|
|
336
|
+
timelockTxs: [
|
|
337
|
+
{
|
|
338
|
+
data: [
|
|
339
|
+
{
|
|
340
|
+
to: randomAddress(),
|
|
341
|
+
value: ethers.BigNumber.from(0),
|
|
342
|
+
data: '0x',
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
delay: 0,
|
|
346
|
+
predecessor: EMPTY_BYTES_32,
|
|
347
|
+
salt: ethers.utils.formatBytes32String('ready-test'),
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
expectedReadyCount: 1,
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
title: 'should filter out non-ready operations (with delay)',
|
|
354
|
+
timelockTxs: [
|
|
355
|
+
{
|
|
356
|
+
data: [
|
|
357
|
+
{
|
|
358
|
+
to: randomAddress(),
|
|
359
|
+
value: ethers.BigNumber.from(0),
|
|
360
|
+
data: '0x',
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
delay: 3600,
|
|
364
|
+
predecessor: EMPTY_BYTES_32,
|
|
365
|
+
salt: ethers.utils.formatBytes32String('not-ready'),
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
expectedReadyCount: 0,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
title: 'should handle mixed ready and non-ready operations',
|
|
372
|
+
timelockTxs: [
|
|
373
|
+
{
|
|
374
|
+
data: [
|
|
375
|
+
{
|
|
376
|
+
to: randomAddress(),
|
|
377
|
+
value: ethers.BigNumber.from(0),
|
|
378
|
+
data: '0x',
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
delay: 0,
|
|
382
|
+
predecessor: EMPTY_BYTES_32,
|
|
383
|
+
salt: ethers.utils.formatBytes32String('ready-1'),
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
data: [
|
|
387
|
+
{
|
|
388
|
+
to: randomAddress(),
|
|
389
|
+
value: ethers.BigNumber.from(0),
|
|
390
|
+
data: '0x',
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
delay: 3600,
|
|
394
|
+
predecessor: EMPTY_BYTES_32,
|
|
395
|
+
salt: ethers.utils.formatBytes32String('not-ready-1'),
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
expectedReadyCount: 1,
|
|
399
|
+
},
|
|
400
|
+
];
|
|
401
|
+
for (const { title, timelockTxs, expectedReadyCount } of readyTestCases) {
|
|
402
|
+
it(title, async () => {
|
|
403
|
+
const proposerTimelock = TimelockController__factory.connect(timelockAddress, proposer);
|
|
404
|
+
const operationIds = [];
|
|
405
|
+
// Schedule operations
|
|
406
|
+
for (const timelockTx of timelockTxs) {
|
|
407
|
+
const targets = timelockTx.data.map((tx) => tx.to);
|
|
408
|
+
const values = timelockTx.data.map((tx) => tx.value ?? '0');
|
|
409
|
+
const dataArray = timelockTx.data.map((tx) => tx.data);
|
|
410
|
+
// Schedule
|
|
411
|
+
const scheduleTx = await proposerTimelock.scheduleBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt, timelockTx.delay);
|
|
412
|
+
await scheduleTx.wait();
|
|
413
|
+
// Get operation ID
|
|
414
|
+
const operationId = await proposerTimelock.hashOperationBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt);
|
|
415
|
+
operationIds.push(operationId);
|
|
416
|
+
}
|
|
417
|
+
const readyIds = await timelockReader.getReadyOperationIds(operationIds);
|
|
418
|
+
expect(readyIds.size).to.equal(expectedReadyCount);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
describe(`${EvmTimelockReader.prototype.getScheduledExecutableTransactions.name}`, () => {
|
|
423
|
+
it('should return empty object when no executable transactions exist', async () => {
|
|
424
|
+
const executableTxs = await timelockReader.getScheduledExecutableTransactions();
|
|
425
|
+
expect(executableTxs).to.deep.equal({});
|
|
426
|
+
});
|
|
427
|
+
const executableTestCases = [
|
|
428
|
+
{
|
|
429
|
+
title: 'should return scheduled executable transactions',
|
|
430
|
+
scheduledTxs: [
|
|
431
|
+
{
|
|
432
|
+
data: [
|
|
433
|
+
{
|
|
434
|
+
to: randomAddress(),
|
|
435
|
+
value: ethers.BigNumber.from(0),
|
|
436
|
+
data: '0x1234',
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
delay: 0,
|
|
440
|
+
predecessor: EMPTY_BYTES_32,
|
|
441
|
+
salt: ethers.utils.formatBytes32String('executable'),
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
expectedExecutableCount: 1,
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
title: 'should exclude cancelled transactions from executable list',
|
|
448
|
+
scheduledTxs: [
|
|
449
|
+
{
|
|
450
|
+
data: [
|
|
451
|
+
{
|
|
452
|
+
to: randomAddress(),
|
|
453
|
+
value: ethers.BigNumber.from(0),
|
|
454
|
+
data: '0x1234',
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
delay: 0,
|
|
458
|
+
predecessor: EMPTY_BYTES_32,
|
|
459
|
+
salt: ethers.utils.formatBytes32String('cancelled'),
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
cancelledTxIndexes: [0],
|
|
463
|
+
expectedExecutableCount: 0,
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
title: 'should exclude executed transactions from executable list',
|
|
467
|
+
scheduledTxs: [
|
|
468
|
+
{
|
|
469
|
+
data: [
|
|
470
|
+
{
|
|
471
|
+
to: randomAddress(),
|
|
472
|
+
value: ethers.BigNumber.from(0),
|
|
473
|
+
data: '0x',
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
delay: 0,
|
|
477
|
+
predecessor: EMPTY_BYTES_32,
|
|
478
|
+
salt: ethers.utils.formatBytes32String('executed'),
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
executedTxIndexes: [0],
|
|
482
|
+
expectedExecutableCount: 0,
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
title: 'should handle mixed scheduled, cancelled, and executed transactions',
|
|
486
|
+
scheduledTxs: [
|
|
487
|
+
{
|
|
488
|
+
data: [
|
|
489
|
+
{
|
|
490
|
+
to: randomAddress(),
|
|
491
|
+
value: ethers.BigNumber.from(0),
|
|
492
|
+
data: '0x1234',
|
|
493
|
+
},
|
|
494
|
+
],
|
|
495
|
+
delay: 0,
|
|
496
|
+
predecessor: EMPTY_BYTES_32,
|
|
497
|
+
salt: ethers.utils.formatBytes32String('executable-1'),
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
data: [
|
|
501
|
+
{
|
|
502
|
+
to: randomAddress(),
|
|
503
|
+
value: ethers.BigNumber.from(0),
|
|
504
|
+
data: '0x5678',
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
delay: 0,
|
|
508
|
+
predecessor: EMPTY_BYTES_32,
|
|
509
|
+
salt: ethers.utils.formatBytes32String('cancelled-1'),
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
data: [
|
|
513
|
+
{
|
|
514
|
+
to: randomAddress(),
|
|
515
|
+
value: ethers.BigNumber.from(0),
|
|
516
|
+
data: '0x',
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
delay: 0,
|
|
520
|
+
predecessor: EMPTY_BYTES_32,
|
|
521
|
+
salt: ethers.utils.formatBytes32String('executed-1'),
|
|
522
|
+
},
|
|
523
|
+
],
|
|
524
|
+
cancelledTxIndexes: [1],
|
|
525
|
+
executedTxIndexes: [2],
|
|
526
|
+
expectedExecutableCount: 1,
|
|
527
|
+
},
|
|
528
|
+
];
|
|
529
|
+
for (const { title, scheduledTxs, cancelledTxIndexes, executedTxIndexes, expectedExecutableCount, } of executableTestCases) {
|
|
530
|
+
it(title, async () => {
|
|
531
|
+
const proposerTimelock = TimelockController__factory.connect(timelockAddress, proposer);
|
|
532
|
+
const executorTimelock = TimelockController__factory.connect(timelockAddress, executor);
|
|
533
|
+
const operationIds = [];
|
|
534
|
+
// Schedule all transactions
|
|
535
|
+
for (const timelockTx of scheduledTxs) {
|
|
536
|
+
const targets = timelockTx.data.map((tx) => tx.to);
|
|
537
|
+
const values = timelockTx.data.map((tx) => tx.value ?? '0');
|
|
538
|
+
const dataArray = timelockTx.data.map((tx) => tx.data);
|
|
539
|
+
// Schedule
|
|
540
|
+
const scheduleTx = await proposerTimelock.scheduleBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt, timelockTx.delay);
|
|
541
|
+
await scheduleTx.wait();
|
|
542
|
+
// Get operation ID
|
|
543
|
+
const operationId = await proposerTimelock.hashOperationBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt);
|
|
544
|
+
operationIds.push(operationId);
|
|
545
|
+
}
|
|
546
|
+
// Cancel specific transactions
|
|
547
|
+
if (cancelledTxIndexes) {
|
|
548
|
+
for (const index of cancelledTxIndexes) {
|
|
549
|
+
const cancelTx = await proposerTimelock.cancel(operationIds[index]);
|
|
550
|
+
await cancelTx.wait();
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
// Execute specific transactions
|
|
554
|
+
if (executedTxIndexes) {
|
|
555
|
+
for (const index of executedTxIndexes) {
|
|
556
|
+
const timelockTx = scheduledTxs[index];
|
|
557
|
+
const targets = timelockTx.data.map((tx) => tx.to);
|
|
558
|
+
const values = timelockTx.data.map((tx) => tx.value ?? '0');
|
|
559
|
+
const dataArray = timelockTx.data.map((tx) => tx.data);
|
|
560
|
+
const executeTx = await executorTimelock.executeBatch(targets, values, dataArray, timelockTx.predecessor, timelockTx.salt);
|
|
561
|
+
await executeTx.wait();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const executableTxs = await timelockReader.getScheduledExecutableTransactions();
|
|
565
|
+
const txIds = Object.keys(executableTxs);
|
|
566
|
+
expect(txIds).to.have.length(expectedExecutableCount);
|
|
567
|
+
// Verify structure of executable transactions
|
|
568
|
+
for (const [txId, executableTx] of Object.entries(executableTxs)) {
|
|
569
|
+
expect(executableTx.id).to.equal(txId);
|
|
570
|
+
expect(executableTx.data).to.be.an('array');
|
|
571
|
+
expect(executableTx.data.length).to.be.greaterThan(0);
|
|
572
|
+
expect(executableTx.encodedExecuteTransaction).to.be.a('string');
|
|
573
|
+
expect(executableTx.encodedExecuteTransaction.length).to.be.greaterThan(0);
|
|
574
|
+
expect(executableTx.delay).to.be.a('number');
|
|
575
|
+
expect(executableTx.predecessor).to.be.a('string');
|
|
576
|
+
expect(executableTx.salt).to.be.a('string');
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
describe(`${EvmTimelockReader.name} (Block Explorer)`, () => {
|
|
583
|
+
let reader;
|
|
584
|
+
let multiProvider;
|
|
585
|
+
beforeEach(async () => {
|
|
586
|
+
multiProvider = new MultiProvider({
|
|
587
|
+
base: baseTestChain,
|
|
588
|
+
});
|
|
589
|
+
reader = EvmTimelockReader.fromConfig({
|
|
590
|
+
chain: baseTestChain.name,
|
|
591
|
+
timelockAddress: KNOWN_BASE_TIMELOCK_CONTRACT,
|
|
592
|
+
multiProvider,
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
describe(`${EvmTimelockReader.prototype.getScheduledOperations.name}`, () => {
|
|
596
|
+
it('should retrieve scheduled transactions from block explorer API', async () => {
|
|
597
|
+
const scheduledTxs = await reader.getScheduledOperations();
|
|
598
|
+
// Should find some scheduled transactions on this timelock
|
|
599
|
+
expect(Object.keys(scheduledTxs).length).to.be.greaterThan(0);
|
|
600
|
+
// Validate structure of returned transactions
|
|
601
|
+
for (const [txId, tx] of Object.entries(scheduledTxs)) {
|
|
602
|
+
expect(ZBytes32String.safeParse(txId).success).to.be.true;
|
|
603
|
+
expect(tx.id).to.equal(txId);
|
|
604
|
+
expect(tx.data.length).to.be.greaterThan(0);
|
|
605
|
+
expect(tx.delay).not.to.be.undefined;
|
|
606
|
+
expect(ZBytes32String.safeParse(tx.predecessor).success).to.be.true;
|
|
607
|
+
expect(ZBytes32String.safeParse(tx.salt).success).to.be.true;
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
describe(`${EvmTimelockReader.prototype.getCancelledOperationIds.name}`, () => {
|
|
612
|
+
it('should retrieve cancelled operation IDs from block explorer API', async () => {
|
|
613
|
+
const cancelledIds = await reader.getCancelledOperationIds();
|
|
614
|
+
expect(cancelledIds).to.be.instanceOf(Set);
|
|
615
|
+
for (const id of cancelledIds) {
|
|
616
|
+
expect(ZBytes32String.safeParse(id).success).to.be.true;
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
describe(`${EvmTimelockReader.prototype.getExecutedOperationIds.name}`, () => {
|
|
621
|
+
it('should retrieve executed operation IDs from block explorer API', async () => {
|
|
622
|
+
const executedIds = await reader.getExecutedOperationIds();
|
|
623
|
+
// Should find some executed transactions on this timelock
|
|
624
|
+
expect(executedIds.size).to.be.greaterThan(0);
|
|
625
|
+
for (const id of executedIds) {
|
|
626
|
+
expect(ZBytes32String.safeParse(id).success).to.be.true;
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
describe(`${EvmTimelockReader.prototype.getScheduledExecutableTransactions.name}`, () => {
|
|
631
|
+
it('should retrieve scheduled executable transactions from block explorer API', async () => {
|
|
632
|
+
const executableTxs = await reader.getScheduledExecutableTransactions();
|
|
633
|
+
for (const [txId, executableTx] of Object.entries(executableTxs)) {
|
|
634
|
+
expect(executableTx.id).to.equal(txId);
|
|
635
|
+
expect(ZBytes32String.safeParse(txId).success).to.be.true;
|
|
636
|
+
expect(executableTx.data.length).to.be.greaterThan(0);
|
|
637
|
+
expect(executableTx.encodedExecuteTransaction).to.be.a('string');
|
|
638
|
+
expect(executableTx.encodedExecuteTransaction.length).to.be.greaterThan(0);
|
|
639
|
+
expect(executableTx.delay).not.to.be.undefined;
|
|
640
|
+
expect(ZBytes32String.safeParse(executableTx.predecessor).success).to
|
|
641
|
+
.be.true;
|
|
642
|
+
expect(ZBytes32String.safeParse(executableTx.salt).success).to.be
|
|
643
|
+
.true;
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
//# sourceMappingURL=EvmTimelockReader.hardhat-test.js.map
|