@hyperlane-xyz/deploy-sdk 1.4.0 → 2.0.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/AltVMCoreModule.d.ts.map +1 -1
- package/dist/AltVMCoreModule.js +1 -2
- package/dist/AltVMCoreModule.js.map +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/warp/warp-reader.d.ts +51 -0
- package/dist/warp/warp-reader.d.ts.map +1 -0
- package/dist/warp/warp-reader.js +106 -0
- package/dist/warp/warp-reader.js.map +1 -0
- package/dist/warp/warp-writer.d.ts +66 -0
- package/dist/warp/warp-writer.d.ts.map +1 -0
- package/dist/warp/warp-writer.js +231 -0
- package/dist/warp/warp-writer.js.map +1 -0
- package/dist/warp/warp-writer.test.d.ts +2 -0
- package/dist/warp/warp-writer.test.d.ts.map +1 -0
- package/dist/warp/warp-writer.test.js +903 -0
- package/dist/warp/warp-writer.test.js.map +1 -0
- package/package.json +9 -9
- package/dist/AltVMWarpDeployer.d.ts +0 -14
- package/dist/AltVMWarpDeployer.d.ts.map +0 -1
- package/dist/AltVMWarpDeployer.js +0 -69
- package/dist/AltVMWarpDeployer.js.map +0 -1
- package/dist/AltVMWarpModule.d.ts +0 -96
- package/dist/AltVMWarpModule.d.ts.map +0 -1
- package/dist/AltVMWarpModule.js +0 -416
- package/dist/AltVMWarpModule.js.map +0 -1
- package/dist/AltVMWarpModule.test.d.ts +0 -2
- package/dist/AltVMWarpModule.test.d.ts.map +0 -1
- package/dist/AltVMWarpModule.test.js +0 -508
- package/dist/AltVMWarpModule.test.js.map +0 -1
- package/dist/AltVMWarpRouteReader.d.ts +0 -53
- package/dist/AltVMWarpRouteReader.d.ts.map +0 -1
- package/dist/AltVMWarpRouteReader.js +0 -168
- package/dist/AltVMWarpRouteReader.js.map +0 -1
- package/dist/ism/ism-config-utils.d.ts +0 -37
- package/dist/ism/ism-config-utils.d.ts.map +0 -1
- package/dist/ism/ism-config-utils.js +0 -72
- package/dist/ism/ism-config-utils.js.map +0 -1
- package/dist/warp-module.d.ts +0 -5
- package/dist/warp-module.d.ts.map +0 -1
- package/dist/warp-module.js +0 -28
- package/dist/warp-module.js.map +0 -1
|
@@ -0,0 +1,903 @@
|
|
|
1
|
+
import chai, { expect } from 'chai';
|
|
2
|
+
import chaiAsPromised from 'chai-as-promised';
|
|
3
|
+
import Sinon from 'sinon';
|
|
4
|
+
import { ArtifactState } from '@hyperlane-xyz/provider-sdk/artifact';
|
|
5
|
+
import { ProtocolType } from '@hyperlane-xyz/provider-sdk/protocol';
|
|
6
|
+
import { TokenType, } from '@hyperlane-xyz/provider-sdk/warp';
|
|
7
|
+
import { WarpTokenWriter } from './warp-writer.js';
|
|
8
|
+
chai.use(chaiAsPromised);
|
|
9
|
+
const TEST_CHAIN = 'test1';
|
|
10
|
+
const TEST_DOMAIN_ID = 1;
|
|
11
|
+
const REMOTE_DOMAIN_ID_1 = 1234;
|
|
12
|
+
const REMOTE_DOMAIN_ID_2 = 4321;
|
|
13
|
+
const REMOTE_DOMAIN_ID_3 = 5321;
|
|
14
|
+
const TOKEN_ADDRESS = '0x726f757465725f61707000000000000000000000000000010000000000000000';
|
|
15
|
+
const OWNER_ADDRESS = 'hyp1jq304cthpx0lwhpqzrdjrcza559ukyy3sc4dw5';
|
|
16
|
+
const MAILBOX_ADDRESS = '0x68797065726c616e650000000000000000000000000000000000000000000000';
|
|
17
|
+
const ISM_ADDRESS = '0x1234';
|
|
18
|
+
const HOOK_ADDRESS = '0x5678';
|
|
19
|
+
describe('WarpTokenWriter', () => {
|
|
20
|
+
let writer;
|
|
21
|
+
let mockArtifactManager;
|
|
22
|
+
let mockSigner;
|
|
23
|
+
let mockIsmWriter;
|
|
24
|
+
let mockHookWriter;
|
|
25
|
+
let mockChainLookup;
|
|
26
|
+
let readStub;
|
|
27
|
+
const actualConfig = {
|
|
28
|
+
type: TokenType.collateral,
|
|
29
|
+
owner: OWNER_ADDRESS,
|
|
30
|
+
mailbox: MAILBOX_ADDRESS,
|
|
31
|
+
token: 'uhyp',
|
|
32
|
+
remoteRouters: {
|
|
33
|
+
[REMOTE_DOMAIN_ID_1]: {
|
|
34
|
+
address: TOKEN_ADDRESS,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
destinationGas: {
|
|
38
|
+
[REMOTE_DOMAIN_ID_1]: '200000',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
const baseDeployedArtifact = {
|
|
42
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
43
|
+
config: actualConfig,
|
|
44
|
+
deployed: { address: TOKEN_ADDRESS },
|
|
45
|
+
};
|
|
46
|
+
const chainMetadata = {
|
|
47
|
+
name: TEST_CHAIN,
|
|
48
|
+
chainId: 1,
|
|
49
|
+
domainId: TEST_DOMAIN_ID,
|
|
50
|
+
protocol: ProtocolType.Ethereum,
|
|
51
|
+
rpcUrls: [{ http: 'http://localhost:8545' }],
|
|
52
|
+
};
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
// Create mock artifact manager
|
|
55
|
+
mockArtifactManager = {
|
|
56
|
+
readWarpToken: Sinon.stub(),
|
|
57
|
+
createWriter: Sinon.stub(),
|
|
58
|
+
supportsHookUpdates: Sinon.stub().returns(true),
|
|
59
|
+
};
|
|
60
|
+
// Create minimal mock signer
|
|
61
|
+
mockSigner = {
|
|
62
|
+
getSignerAddress: () => OWNER_ADDRESS,
|
|
63
|
+
};
|
|
64
|
+
// Create mock chain lookup
|
|
65
|
+
mockChainLookup = {
|
|
66
|
+
getChainMetadata: Sinon.stub().returns({
|
|
67
|
+
name: TEST_CHAIN,
|
|
68
|
+
domainId: TEST_DOMAIN_ID,
|
|
69
|
+
protocol: ProtocolType.Ethereum,
|
|
70
|
+
}),
|
|
71
|
+
getChainName: Sinon.stub().returns(TEST_CHAIN),
|
|
72
|
+
getDomainId: Sinon.stub().returns(TEST_DOMAIN_ID),
|
|
73
|
+
};
|
|
74
|
+
// Create mock ISM and Hook writers FIRST
|
|
75
|
+
mockIsmWriter = {
|
|
76
|
+
create: Sinon.stub(),
|
|
77
|
+
update: Sinon.stub(),
|
|
78
|
+
read: Sinon.stub(),
|
|
79
|
+
};
|
|
80
|
+
mockHookWriter = {
|
|
81
|
+
create: Sinon.stub(),
|
|
82
|
+
update: Sinon.stub(),
|
|
83
|
+
read: Sinon.stub(),
|
|
84
|
+
};
|
|
85
|
+
// Create writer instance - manually to bypass protocol provider
|
|
86
|
+
writer = Object.create(WarpTokenWriter.prototype);
|
|
87
|
+
Object.assign(writer, {
|
|
88
|
+
artifactManager: mockArtifactManager,
|
|
89
|
+
chainMetadata,
|
|
90
|
+
chainLookup: mockChainLookup,
|
|
91
|
+
signer: mockSigner,
|
|
92
|
+
ismWriter: mockIsmWriter,
|
|
93
|
+
hookWriterFactory: () => mockHookWriter,
|
|
94
|
+
});
|
|
95
|
+
// Default read stub - returns current config
|
|
96
|
+
readStub = Sinon.stub(writer, 'read').resolves(baseDeployedArtifact);
|
|
97
|
+
});
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
Sinon.restore();
|
|
100
|
+
});
|
|
101
|
+
describe('update() - Router Management', () => {
|
|
102
|
+
const createMockTx = (annotation) => ({
|
|
103
|
+
annotation,
|
|
104
|
+
to: TOKEN_ADDRESS,
|
|
105
|
+
data: '0x',
|
|
106
|
+
});
|
|
107
|
+
const routerTestCases = [
|
|
108
|
+
{
|
|
109
|
+
name: 'no updates needed if config is the same',
|
|
110
|
+
configOverrides: {},
|
|
111
|
+
expectedTxCount: 0,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'new remote router',
|
|
115
|
+
configOverrides: {
|
|
116
|
+
remoteRouters: {
|
|
117
|
+
...actualConfig.remoteRouters,
|
|
118
|
+
[REMOTE_DOMAIN_ID_2]: { address: '0xNEWROUTER' },
|
|
119
|
+
},
|
|
120
|
+
destinationGas: {
|
|
121
|
+
...actualConfig.destinationGas,
|
|
122
|
+
[REMOTE_DOMAIN_ID_2]: '300000',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
expectedTxCount: 1,
|
|
126
|
+
assertion: (txs) => {
|
|
127
|
+
expect(txs[0].annotation).to.include('router');
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'multiple new remote routers',
|
|
132
|
+
configOverrides: {
|
|
133
|
+
remoteRouters: {
|
|
134
|
+
...actualConfig.remoteRouters,
|
|
135
|
+
[REMOTE_DOMAIN_ID_2]: { address: '0xNEWROUTER1' },
|
|
136
|
+
[REMOTE_DOMAIN_ID_3]: { address: '0xNEWROUTER2' },
|
|
137
|
+
},
|
|
138
|
+
destinationGas: {
|
|
139
|
+
...actualConfig.destinationGas,
|
|
140
|
+
[REMOTE_DOMAIN_ID_2]: '300000',
|
|
141
|
+
[REMOTE_DOMAIN_ID_3]: '400000',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
expectedTxCount: 2,
|
|
145
|
+
assertion: (txs) => {
|
|
146
|
+
expect(txs).to.have.lengthOf(2);
|
|
147
|
+
txs.forEach((tx) => {
|
|
148
|
+
expect(tx.annotation).to.include('router');
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'remove existing remote router',
|
|
154
|
+
configOverrides: {
|
|
155
|
+
remoteRouters: {},
|
|
156
|
+
destinationGas: {},
|
|
157
|
+
},
|
|
158
|
+
expectedTxCount: 1,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'update existing router address',
|
|
162
|
+
configOverrides: {
|
|
163
|
+
remoteRouters: {
|
|
164
|
+
[REMOTE_DOMAIN_ID_1]: { address: '0xUPDATEDROUTER' },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
expectedTxCount: 2, // unenroll + enroll
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'update existing router gas',
|
|
171
|
+
configOverrides: {
|
|
172
|
+
destinationGas: {
|
|
173
|
+
[REMOTE_DOMAIN_ID_1]: '999999',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
expectedTxCount: 2, // unenroll + enroll with new gas
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'remove and add remote router at the same time',
|
|
180
|
+
configOverrides: {
|
|
181
|
+
remoteRouters: {
|
|
182
|
+
[REMOTE_DOMAIN_ID_2]: { address: '0xNEWROUTER' },
|
|
183
|
+
},
|
|
184
|
+
destinationGas: {
|
|
185
|
+
[REMOTE_DOMAIN_ID_2]: '300000',
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
expectedTxCount: 2, // remove old + add new
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
routerTestCases.forEach(({ name, configOverrides, expectedTxCount, assertion }) => {
|
|
192
|
+
it(name, async () => {
|
|
193
|
+
// Setup mock writer
|
|
194
|
+
const mockWriter = {
|
|
195
|
+
read: Sinon.stub(),
|
|
196
|
+
create: Sinon.stub(),
|
|
197
|
+
update: Sinon.stub().resolves(Array(expectedTxCount)
|
|
198
|
+
.fill(null)
|
|
199
|
+
.map((_, i) => createMockTx(`Update router ${i}`))),
|
|
200
|
+
};
|
|
201
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
202
|
+
// Execute update
|
|
203
|
+
const artifact = {
|
|
204
|
+
...baseDeployedArtifact,
|
|
205
|
+
config: { ...actualConfig, ...configOverrides },
|
|
206
|
+
};
|
|
207
|
+
const updateTxs = await writer.update(artifact);
|
|
208
|
+
// Assertions
|
|
209
|
+
expect(updateTxs).to.have.lengthOf(expectedTxCount);
|
|
210
|
+
if (assertion) {
|
|
211
|
+
assertion(updateTxs);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe('update() - Ownership Changes', () => {
|
|
217
|
+
it('should update ownership', async () => {
|
|
218
|
+
const newOwner = '0x9999999999999999999999999999999999999999';
|
|
219
|
+
const configWithNewOwner = {
|
|
220
|
+
...actualConfig,
|
|
221
|
+
owner: newOwner,
|
|
222
|
+
};
|
|
223
|
+
const mockWriter = {
|
|
224
|
+
read: Sinon.stub(),
|
|
225
|
+
create: Sinon.stub(),
|
|
226
|
+
update: Sinon.stub().resolves([
|
|
227
|
+
{
|
|
228
|
+
annotation: 'Transfer ownership',
|
|
229
|
+
to: TOKEN_ADDRESS,
|
|
230
|
+
data: '0x',
|
|
231
|
+
},
|
|
232
|
+
]),
|
|
233
|
+
};
|
|
234
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
235
|
+
const artifact = {
|
|
236
|
+
...baseDeployedArtifact,
|
|
237
|
+
config: configWithNewOwner,
|
|
238
|
+
};
|
|
239
|
+
const updateTxs = await writer.update(artifact);
|
|
240
|
+
expect(updateTxs).to.have.lengthOf(1);
|
|
241
|
+
expect(updateTxs[0].annotation).to.match(/ownership/i);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
describe('update() - ISM Updates', () => {
|
|
245
|
+
const createIsmConfig = (type, validators) => ({
|
|
246
|
+
type,
|
|
247
|
+
validators,
|
|
248
|
+
threshold: 1,
|
|
249
|
+
});
|
|
250
|
+
it('should deploy new ISM', async () => {
|
|
251
|
+
const newIsmConfig = createIsmConfig('messageIdMultisigIsm', [
|
|
252
|
+
'0xVALIDATOR',
|
|
253
|
+
]);
|
|
254
|
+
const configWithIsm = {
|
|
255
|
+
...actualConfig,
|
|
256
|
+
interchainSecurityModule: {
|
|
257
|
+
artifactState: ArtifactState.NEW,
|
|
258
|
+
config: newIsmConfig,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
// Mock ISM creation
|
|
262
|
+
const deployedIsm = {
|
|
263
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
264
|
+
config: newIsmConfig,
|
|
265
|
+
deployed: { address: ISM_ADDRESS },
|
|
266
|
+
};
|
|
267
|
+
mockIsmWriter.create.resolves([deployedIsm, []]);
|
|
268
|
+
const mockWriter = {
|
|
269
|
+
read: Sinon.stub(),
|
|
270
|
+
create: Sinon.stub(),
|
|
271
|
+
update: Sinon.stub().resolves([
|
|
272
|
+
{
|
|
273
|
+
annotation: 'Set ISM',
|
|
274
|
+
to: TOKEN_ADDRESS,
|
|
275
|
+
data: '0x',
|
|
276
|
+
},
|
|
277
|
+
]),
|
|
278
|
+
};
|
|
279
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
280
|
+
const artifact = {
|
|
281
|
+
...baseDeployedArtifact,
|
|
282
|
+
config: configWithIsm,
|
|
283
|
+
};
|
|
284
|
+
const updateTxs = await writer.update(artifact);
|
|
285
|
+
expect(mockIsmWriter.create.callCount).to.equal(1);
|
|
286
|
+
expect(updateTxs.length).to.be.greaterThan(0);
|
|
287
|
+
});
|
|
288
|
+
it('should update existing ISM in-place when type is unchanged', async () => {
|
|
289
|
+
const ismConfig = createIsmConfig('messageIdMultisigIsm', [
|
|
290
|
+
'0xVALIDATOR1',
|
|
291
|
+
]);
|
|
292
|
+
const currentArtifactWithIsm = {
|
|
293
|
+
...baseDeployedArtifact,
|
|
294
|
+
config: {
|
|
295
|
+
...actualConfig,
|
|
296
|
+
interchainSecurityModule: {
|
|
297
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
298
|
+
config: ismConfig,
|
|
299
|
+
deployed: { address: ISM_ADDRESS },
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
readStub.restore();
|
|
304
|
+
readStub = Sinon.stub(writer, 'read').resolves(currentArtifactWithIsm);
|
|
305
|
+
mockIsmWriter.update.resolves([]);
|
|
306
|
+
const mockWriter = {
|
|
307
|
+
read: Sinon.stub(),
|
|
308
|
+
create: Sinon.stub(),
|
|
309
|
+
update: Sinon.stub().resolves([]),
|
|
310
|
+
};
|
|
311
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
312
|
+
const artifact = {
|
|
313
|
+
...baseDeployedArtifact,
|
|
314
|
+
config: {
|
|
315
|
+
...actualConfig,
|
|
316
|
+
interchainSecurityModule: {
|
|
317
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
318
|
+
config: ismConfig,
|
|
319
|
+
deployed: { address: ISM_ADDRESS },
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
await writer.update(artifact);
|
|
324
|
+
expect(mockIsmWriter.create.callCount).to.equal(0);
|
|
325
|
+
expect(mockIsmWriter.update.callCount).to.equal(1);
|
|
326
|
+
});
|
|
327
|
+
it('should replace existing ISM when type changes', async () => {
|
|
328
|
+
// Setup current artifact with existing ISM
|
|
329
|
+
const currentIsmConfig = createIsmConfig('messageIdMultisigIsm', [
|
|
330
|
+
'0xVALIDATOR1',
|
|
331
|
+
]);
|
|
332
|
+
const currentArtifactWithIsm = {
|
|
333
|
+
...baseDeployedArtifact,
|
|
334
|
+
config: {
|
|
335
|
+
...actualConfig,
|
|
336
|
+
interchainSecurityModule: {
|
|
337
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
338
|
+
config: currentIsmConfig,
|
|
339
|
+
deployed: { address: ISM_ADDRESS },
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
readStub.restore();
|
|
344
|
+
readStub = Sinon.stub(writer, 'read').resolves(currentArtifactWithIsm);
|
|
345
|
+
// New ISM config
|
|
346
|
+
const newIsmConfig = {
|
|
347
|
+
type: 'merkleRootMultisigIsm',
|
|
348
|
+
validators: ['0xVALIDATOR2'],
|
|
349
|
+
threshold: 1,
|
|
350
|
+
};
|
|
351
|
+
const configWithNewIsm = {
|
|
352
|
+
...actualConfig,
|
|
353
|
+
interchainSecurityModule: {
|
|
354
|
+
artifactState: ArtifactState.NEW,
|
|
355
|
+
config: newIsmConfig,
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
// Mock ISM creation (new ISM type)
|
|
359
|
+
const newIsmAddress = '0x0000000000000000000000000000000000000004';
|
|
360
|
+
const deployedNewIsm = {
|
|
361
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
362
|
+
config: newIsmConfig,
|
|
363
|
+
deployed: { address: newIsmAddress },
|
|
364
|
+
};
|
|
365
|
+
mockIsmWriter.create.resolves([deployedNewIsm, []]);
|
|
366
|
+
const mockWriter = {
|
|
367
|
+
read: Sinon.stub(),
|
|
368
|
+
create: Sinon.stub(),
|
|
369
|
+
update: Sinon.stub().resolves([
|
|
370
|
+
{
|
|
371
|
+
annotation: 'Update ISM',
|
|
372
|
+
to: TOKEN_ADDRESS,
|
|
373
|
+
data: '0x',
|
|
374
|
+
},
|
|
375
|
+
]),
|
|
376
|
+
};
|
|
377
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
378
|
+
const artifact = {
|
|
379
|
+
...baseDeployedArtifact,
|
|
380
|
+
config: configWithNewIsm,
|
|
381
|
+
};
|
|
382
|
+
const updateTxs = await writer.update(artifact);
|
|
383
|
+
expect(mockIsmWriter.create.callCount).to.equal(1);
|
|
384
|
+
expect(updateTxs.length).to.be.greaterThan(0);
|
|
385
|
+
});
|
|
386
|
+
it('should treat omitted ISM and zero-address ISM equivalently', async () => {
|
|
387
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
388
|
+
const mockWriter = {
|
|
389
|
+
read: Sinon.stub(),
|
|
390
|
+
create: Sinon.stub(),
|
|
391
|
+
update: Sinon.stub().resolves([]),
|
|
392
|
+
};
|
|
393
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
394
|
+
// Case 1: no ISM
|
|
395
|
+
const artifactNoIsm = {
|
|
396
|
+
...baseDeployedArtifact,
|
|
397
|
+
config: { ...actualConfig, interchainSecurityModule: undefined },
|
|
398
|
+
};
|
|
399
|
+
await writer.update(artifactNoIsm);
|
|
400
|
+
const createCountAfterNoIsm = mockIsmWriter.create.callCount;
|
|
401
|
+
// Case 2: zero-address ISM (UNDERIVED — treated as pass-through)
|
|
402
|
+
const artifactZeroIsm = {
|
|
403
|
+
...baseDeployedArtifact,
|
|
404
|
+
config: {
|
|
405
|
+
...actualConfig,
|
|
406
|
+
interchainSecurityModule: {
|
|
407
|
+
artifactState: ArtifactState.UNDERIVED,
|
|
408
|
+
deployed: { address: ZERO_ADDRESS },
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
await writer.update(artifactZeroIsm);
|
|
413
|
+
// Neither case should trigger ISM creation
|
|
414
|
+
expect(mockIsmWriter.create.callCount).to.equal(createCountAfterNoIsm);
|
|
415
|
+
expect(mockIsmWriter.create.callCount).to.equal(0);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
describe('update() - Hook Updates', () => {
|
|
419
|
+
const merkleTreeHookConfig = {
|
|
420
|
+
type: 'merkleTreeHook',
|
|
421
|
+
};
|
|
422
|
+
it('should deploy new hook when none existed before', async () => {
|
|
423
|
+
const configWithHook = {
|
|
424
|
+
...actualConfig,
|
|
425
|
+
hook: {
|
|
426
|
+
artifactState: ArtifactState.NEW,
|
|
427
|
+
config: merkleTreeHookConfig,
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
const deployedHook = {
|
|
431
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
432
|
+
config: merkleTreeHookConfig,
|
|
433
|
+
deployed: { address: HOOK_ADDRESS },
|
|
434
|
+
};
|
|
435
|
+
mockHookWriter.create.resolves([deployedHook, []]);
|
|
436
|
+
const mockWriter = {
|
|
437
|
+
read: Sinon.stub(),
|
|
438
|
+
create: Sinon.stub(),
|
|
439
|
+
update: Sinon.stub().resolves([
|
|
440
|
+
{ annotation: 'Set hook', to: TOKEN_ADDRESS, data: '0x' },
|
|
441
|
+
]),
|
|
442
|
+
};
|
|
443
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
444
|
+
const artifact = {
|
|
445
|
+
...baseDeployedArtifact,
|
|
446
|
+
config: configWithHook,
|
|
447
|
+
};
|
|
448
|
+
const updateTxs = await writer.update(artifact);
|
|
449
|
+
expect(mockHookWriter.create.callCount).to.equal(1);
|
|
450
|
+
expect(updateTxs.length).to.be.greaterThan(0);
|
|
451
|
+
});
|
|
452
|
+
it('should skip hook deployment when hook is underived (address reference)', async () => {
|
|
453
|
+
const configWithUnderivedHook = {
|
|
454
|
+
...actualConfig,
|
|
455
|
+
hook: {
|
|
456
|
+
artifactState: ArtifactState.UNDERIVED,
|
|
457
|
+
deployed: { address: HOOK_ADDRESS },
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
const mockWriter = {
|
|
461
|
+
read: Sinon.stub(),
|
|
462
|
+
create: Sinon.stub(),
|
|
463
|
+
update: Sinon.stub().resolves([]),
|
|
464
|
+
};
|
|
465
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
466
|
+
const artifact = {
|
|
467
|
+
...baseDeployedArtifact,
|
|
468
|
+
config: configWithUnderivedHook,
|
|
469
|
+
};
|
|
470
|
+
await writer.update(artifact);
|
|
471
|
+
expect(mockHookWriter.create.called).to.be.false;
|
|
472
|
+
expect(mockHookWriter.update.called).to.be.false;
|
|
473
|
+
});
|
|
474
|
+
it('should skip hook deployment when protocol does not support hook updates', async () => {
|
|
475
|
+
mockArtifactManager.supportsHookUpdates.returns(false);
|
|
476
|
+
const configWithHook = {
|
|
477
|
+
...actualConfig,
|
|
478
|
+
hook: {
|
|
479
|
+
artifactState: ArtifactState.NEW,
|
|
480
|
+
config: merkleTreeHookConfig,
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
const mockWriter = {
|
|
484
|
+
read: Sinon.stub(),
|
|
485
|
+
create: Sinon.stub(),
|
|
486
|
+
update: Sinon.stub().resolves([]),
|
|
487
|
+
};
|
|
488
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
489
|
+
const artifact = {
|
|
490
|
+
...baseDeployedArtifact,
|
|
491
|
+
config: configWithHook,
|
|
492
|
+
};
|
|
493
|
+
await writer.update(artifact);
|
|
494
|
+
expect(mockHookWriter.create.called).to.be.false;
|
|
495
|
+
expect(mockHookWriter.update.called).to.be.false;
|
|
496
|
+
});
|
|
497
|
+
it('should handle hook + router updates in single call', async () => {
|
|
498
|
+
const configWithHookAndRouter = {
|
|
499
|
+
...actualConfig,
|
|
500
|
+
hook: {
|
|
501
|
+
artifactState: ArtifactState.NEW,
|
|
502
|
+
config: merkleTreeHookConfig,
|
|
503
|
+
},
|
|
504
|
+
remoteRouters: {
|
|
505
|
+
...actualConfig.remoteRouters,
|
|
506
|
+
[REMOTE_DOMAIN_ID_2]: { address: '0xNEWROUTER' },
|
|
507
|
+
},
|
|
508
|
+
destinationGas: {
|
|
509
|
+
...actualConfig.destinationGas,
|
|
510
|
+
[REMOTE_DOMAIN_ID_2]: '300000',
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
const deployedHook = {
|
|
514
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
515
|
+
config: merkleTreeHookConfig,
|
|
516
|
+
deployed: { address: HOOK_ADDRESS },
|
|
517
|
+
};
|
|
518
|
+
mockHookWriter.create.resolves([deployedHook, []]);
|
|
519
|
+
const mockWriter = {
|
|
520
|
+
read: Sinon.stub(),
|
|
521
|
+
create: Sinon.stub(),
|
|
522
|
+
update: Sinon.stub().resolves([
|
|
523
|
+
{ annotation: 'Set hook', to: TOKEN_ADDRESS, data: '0x' },
|
|
524
|
+
{ annotation: 'Enroll router', to: TOKEN_ADDRESS, data: '0x' },
|
|
525
|
+
]),
|
|
526
|
+
};
|
|
527
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
528
|
+
const artifact = {
|
|
529
|
+
...baseDeployedArtifact,
|
|
530
|
+
config: configWithHookAndRouter,
|
|
531
|
+
};
|
|
532
|
+
const updateTxs = await writer.update(artifact);
|
|
533
|
+
expect(mockHookWriter.create.callCount).to.equal(1);
|
|
534
|
+
expect(updateTxs.length).to.equal(2);
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
describe('update() - Validation', () => {
|
|
538
|
+
it('should reject changing token type', async () => {
|
|
539
|
+
// Current artifact is collateral
|
|
540
|
+
const currentArtifact = {
|
|
541
|
+
...baseDeployedArtifact,
|
|
542
|
+
config: {
|
|
543
|
+
...actualConfig,
|
|
544
|
+
type: TokenType.collateral,
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
readStub.restore();
|
|
548
|
+
readStub = Sinon.stub(writer, 'read').resolves(currentArtifact);
|
|
549
|
+
// Try to change to synthetic
|
|
550
|
+
const syntheticConfig = {
|
|
551
|
+
type: TokenType.synthetic,
|
|
552
|
+
owner: OWNER_ADDRESS,
|
|
553
|
+
mailbox: MAILBOX_ADDRESS,
|
|
554
|
+
name: 'Synthetic Token',
|
|
555
|
+
symbol: 'SYN',
|
|
556
|
+
decimals: 18,
|
|
557
|
+
remoteRouters: {},
|
|
558
|
+
destinationGas: {},
|
|
559
|
+
};
|
|
560
|
+
const artifact = {
|
|
561
|
+
...baseDeployedArtifact,
|
|
562
|
+
config: syntheticConfig,
|
|
563
|
+
};
|
|
564
|
+
await expect(writer.update(artifact)).to.be.rejectedWith(/Cannot change warp token type/);
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
describe('update() - Complex Scenarios', () => {
|
|
568
|
+
it('should handle ISM + router updates in single call', async () => {
|
|
569
|
+
const newIsmConfig = {
|
|
570
|
+
type: 'messageIdMultisigIsm',
|
|
571
|
+
validators: ['0xVALIDATOR'],
|
|
572
|
+
threshold: 1,
|
|
573
|
+
};
|
|
574
|
+
const configWithIsmAndRouter = {
|
|
575
|
+
...actualConfig,
|
|
576
|
+
interchainSecurityModule: {
|
|
577
|
+
artifactState: ArtifactState.NEW,
|
|
578
|
+
config: newIsmConfig,
|
|
579
|
+
},
|
|
580
|
+
remoteRouters: {
|
|
581
|
+
...actualConfig.remoteRouters,
|
|
582
|
+
[REMOTE_DOMAIN_ID_2]: { address: '0xNEWROUTER' },
|
|
583
|
+
},
|
|
584
|
+
destinationGas: {
|
|
585
|
+
...actualConfig.destinationGas,
|
|
586
|
+
[REMOTE_DOMAIN_ID_2]: '300000',
|
|
587
|
+
},
|
|
588
|
+
};
|
|
589
|
+
// Mock ISM creation
|
|
590
|
+
const deployedIsm = {
|
|
591
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
592
|
+
config: newIsmConfig,
|
|
593
|
+
deployed: { address: ISM_ADDRESS },
|
|
594
|
+
};
|
|
595
|
+
mockIsmWriter.create.resolves([deployedIsm, []]);
|
|
596
|
+
const mockWriter = {
|
|
597
|
+
read: Sinon.stub(),
|
|
598
|
+
create: Sinon.stub(),
|
|
599
|
+
update: Sinon.stub().resolves([
|
|
600
|
+
{
|
|
601
|
+
annotation: 'Set ISM',
|
|
602
|
+
to: TOKEN_ADDRESS,
|
|
603
|
+
data: '0x',
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
annotation: 'Enroll router',
|
|
607
|
+
to: TOKEN_ADDRESS,
|
|
608
|
+
data: '0x',
|
|
609
|
+
},
|
|
610
|
+
]),
|
|
611
|
+
};
|
|
612
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
613
|
+
const artifact = {
|
|
614
|
+
...baseDeployedArtifact,
|
|
615
|
+
config: configWithIsmAndRouter,
|
|
616
|
+
};
|
|
617
|
+
const updateTxs = await writer.update(artifact);
|
|
618
|
+
expect(mockIsmWriter.create.callCount).to.equal(1);
|
|
619
|
+
expect(updateTxs.length).to.be.greaterThan(1);
|
|
620
|
+
});
|
|
621
|
+
it('should handle ownership + ISM + router updates', async () => {
|
|
622
|
+
const newOwner = '0x9999999999999999999999999999999999999999';
|
|
623
|
+
const newIsmConfig = {
|
|
624
|
+
type: 'messageIdMultisigIsm',
|
|
625
|
+
validators: ['0xVALIDATOR'],
|
|
626
|
+
threshold: 1,
|
|
627
|
+
};
|
|
628
|
+
const complexConfig = {
|
|
629
|
+
...actualConfig,
|
|
630
|
+
owner: newOwner,
|
|
631
|
+
interchainSecurityModule: {
|
|
632
|
+
artifactState: ArtifactState.NEW,
|
|
633
|
+
config: newIsmConfig,
|
|
634
|
+
},
|
|
635
|
+
remoteRouters: {},
|
|
636
|
+
destinationGas: {},
|
|
637
|
+
};
|
|
638
|
+
// Mock ISM creation
|
|
639
|
+
const deployedIsm = {
|
|
640
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
641
|
+
config: newIsmConfig,
|
|
642
|
+
deployed: { address: ISM_ADDRESS },
|
|
643
|
+
};
|
|
644
|
+
mockIsmWriter.create.resolves([deployedIsm, []]);
|
|
645
|
+
const mockWriter = {
|
|
646
|
+
read: Sinon.stub(),
|
|
647
|
+
create: Sinon.stub(),
|
|
648
|
+
update: Sinon.stub().resolves([
|
|
649
|
+
{
|
|
650
|
+
annotation: 'Transfer ownership',
|
|
651
|
+
to: TOKEN_ADDRESS,
|
|
652
|
+
data: '0x',
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
annotation: 'Set ISM',
|
|
656
|
+
to: TOKEN_ADDRESS,
|
|
657
|
+
data: '0x',
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
annotation: 'Unenroll router',
|
|
661
|
+
to: TOKEN_ADDRESS,
|
|
662
|
+
data: '0x',
|
|
663
|
+
},
|
|
664
|
+
]),
|
|
665
|
+
};
|
|
666
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
667
|
+
const artifact = {
|
|
668
|
+
...baseDeployedArtifact,
|
|
669
|
+
config: complexConfig,
|
|
670
|
+
};
|
|
671
|
+
const updateTxs = await writer.update(artifact);
|
|
672
|
+
expect(mockIsmWriter.create.callCount).to.equal(1);
|
|
673
|
+
expect(updateTxs.length).to.equal(3);
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
describe('create()', () => {
|
|
677
|
+
it('should create warp token without ISM', async () => {
|
|
678
|
+
const mockWriter = {
|
|
679
|
+
read: Sinon.stub(),
|
|
680
|
+
create: Sinon.stub().resolves([
|
|
681
|
+
{
|
|
682
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
683
|
+
config: actualConfig,
|
|
684
|
+
deployed: { address: TOKEN_ADDRESS },
|
|
685
|
+
},
|
|
686
|
+
[],
|
|
687
|
+
]),
|
|
688
|
+
update: Sinon.stub(),
|
|
689
|
+
};
|
|
690
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
691
|
+
const artifact = {
|
|
692
|
+
artifactState: ArtifactState.NEW,
|
|
693
|
+
config: actualConfig,
|
|
694
|
+
};
|
|
695
|
+
const [deployed, receipts] = await writer.create(artifact);
|
|
696
|
+
expect(deployed.artifactState).to.equal(ArtifactState.DEPLOYED);
|
|
697
|
+
expect(deployed.deployed.address).to.equal(TOKEN_ADDRESS);
|
|
698
|
+
expect(receipts).to.be.an('array');
|
|
699
|
+
expect(mockWriter.create.callCount).to.equal(1);
|
|
700
|
+
});
|
|
701
|
+
it('should create warp token with new ISM', async () => {
|
|
702
|
+
const newIsmConfig = {
|
|
703
|
+
type: 'messageIdMultisigIsm',
|
|
704
|
+
validators: ['0xVALIDATOR'],
|
|
705
|
+
threshold: 1,
|
|
706
|
+
};
|
|
707
|
+
const configWithIsm = {
|
|
708
|
+
...actualConfig,
|
|
709
|
+
interchainSecurityModule: {
|
|
710
|
+
artifactState: ArtifactState.NEW,
|
|
711
|
+
config: newIsmConfig,
|
|
712
|
+
},
|
|
713
|
+
};
|
|
714
|
+
// Mock ISM creation
|
|
715
|
+
const deployedIsm = {
|
|
716
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
717
|
+
config: newIsmConfig,
|
|
718
|
+
deployed: { address: ISM_ADDRESS },
|
|
719
|
+
};
|
|
720
|
+
mockIsmWriter.create.resolves([deployedIsm, []]);
|
|
721
|
+
const mockWriter = {
|
|
722
|
+
read: Sinon.stub(),
|
|
723
|
+
create: Sinon.stub().resolves([
|
|
724
|
+
{
|
|
725
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
726
|
+
config: configWithIsm,
|
|
727
|
+
deployed: { address: TOKEN_ADDRESS },
|
|
728
|
+
},
|
|
729
|
+
[],
|
|
730
|
+
]),
|
|
731
|
+
update: Sinon.stub(),
|
|
732
|
+
};
|
|
733
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
734
|
+
const artifact = {
|
|
735
|
+
artifactState: ArtifactState.NEW,
|
|
736
|
+
config: configWithIsm,
|
|
737
|
+
};
|
|
738
|
+
const [deployed, receipts] = await writer.create(artifact);
|
|
739
|
+
expect(mockIsmWriter.create.callCount).to.equal(1);
|
|
740
|
+
expect(deployed.artifactState).to.equal(ArtifactState.DEPLOYED);
|
|
741
|
+
expect(deployed.deployed.address).to.equal(TOKEN_ADDRESS);
|
|
742
|
+
expect(receipts).to.be.an('array');
|
|
743
|
+
});
|
|
744
|
+
it('should create warp token with existing ISM', async () => {
|
|
745
|
+
const existingIsmConfig = {
|
|
746
|
+
type: 'messageIdMultisigIsm',
|
|
747
|
+
validators: ['0xVALIDATOR'],
|
|
748
|
+
threshold: 1,
|
|
749
|
+
};
|
|
750
|
+
const configWithExistingIsm = {
|
|
751
|
+
...actualConfig,
|
|
752
|
+
interchainSecurityModule: {
|
|
753
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
754
|
+
config: existingIsmConfig,
|
|
755
|
+
deployed: { address: ISM_ADDRESS },
|
|
756
|
+
},
|
|
757
|
+
};
|
|
758
|
+
const mockWriter = {
|
|
759
|
+
read: Sinon.stub(),
|
|
760
|
+
create: Sinon.stub().resolves([
|
|
761
|
+
{
|
|
762
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
763
|
+
config: configWithExistingIsm,
|
|
764
|
+
deployed: { address: TOKEN_ADDRESS },
|
|
765
|
+
},
|
|
766
|
+
[],
|
|
767
|
+
]),
|
|
768
|
+
update: Sinon.stub(),
|
|
769
|
+
};
|
|
770
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
771
|
+
const artifact = {
|
|
772
|
+
artifactState: ArtifactState.NEW,
|
|
773
|
+
config: configWithExistingIsm,
|
|
774
|
+
};
|
|
775
|
+
const [deployed, receipts] = await writer.create(artifact);
|
|
776
|
+
// Should not create new ISM
|
|
777
|
+
expect(mockIsmWriter.create.called).to.be.false;
|
|
778
|
+
expect(deployed.artifactState).to.equal(ArtifactState.DEPLOYED);
|
|
779
|
+
expect(deployed.deployed.address).to.equal(TOKEN_ADDRESS);
|
|
780
|
+
expect(receipts).to.be.an('array');
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
describe('create() - Hook', () => {
|
|
784
|
+
const merkleTreeHookConfig = {
|
|
785
|
+
type: 'merkleTreeHook',
|
|
786
|
+
};
|
|
787
|
+
it('should deploy hook before warp token when hook is new', async () => {
|
|
788
|
+
const configWithHook = {
|
|
789
|
+
...actualConfig,
|
|
790
|
+
hook: {
|
|
791
|
+
artifactState: ArtifactState.NEW,
|
|
792
|
+
config: merkleTreeHookConfig,
|
|
793
|
+
},
|
|
794
|
+
};
|
|
795
|
+
const hookReceipt = { transactionHash: '0xHOOKTX' };
|
|
796
|
+
const deployedHook = {
|
|
797
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
798
|
+
config: merkleTreeHookConfig,
|
|
799
|
+
deployed: { address: HOOK_ADDRESS },
|
|
800
|
+
};
|
|
801
|
+
mockHookWriter.create.resolves([deployedHook, [hookReceipt]]);
|
|
802
|
+
const mockWriter = {
|
|
803
|
+
read: Sinon.stub(),
|
|
804
|
+
create: Sinon.stub().resolves([
|
|
805
|
+
{
|
|
806
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
807
|
+
config: configWithHook,
|
|
808
|
+
deployed: { address: TOKEN_ADDRESS },
|
|
809
|
+
},
|
|
810
|
+
[],
|
|
811
|
+
]),
|
|
812
|
+
update: Sinon.stub(),
|
|
813
|
+
};
|
|
814
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
815
|
+
const artifact = {
|
|
816
|
+
artifactState: ArtifactState.NEW,
|
|
817
|
+
config: configWithHook,
|
|
818
|
+
};
|
|
819
|
+
const [deployed, receipts] = await writer.create(artifact);
|
|
820
|
+
expect(mockHookWriter.create.callCount).to.equal(1);
|
|
821
|
+
expect(receipts).to.include(hookReceipt);
|
|
822
|
+
expect(deployed.artifactState).to.equal(ArtifactState.DEPLOYED);
|
|
823
|
+
expect(deployed.deployed.address).to.equal(TOKEN_ADDRESS);
|
|
824
|
+
});
|
|
825
|
+
it('should reuse hook address when hook is already deployed', async () => {
|
|
826
|
+
const configWithDeployedHook = {
|
|
827
|
+
...actualConfig,
|
|
828
|
+
hook: {
|
|
829
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
830
|
+
config: merkleTreeHookConfig,
|
|
831
|
+
deployed: { address: HOOK_ADDRESS },
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
const mockWriter = {
|
|
835
|
+
read: Sinon.stub(),
|
|
836
|
+
create: Sinon.stub().resolves([
|
|
837
|
+
{
|
|
838
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
839
|
+
config: configWithDeployedHook,
|
|
840
|
+
deployed: { address: TOKEN_ADDRESS },
|
|
841
|
+
},
|
|
842
|
+
[],
|
|
843
|
+
]),
|
|
844
|
+
update: Sinon.stub(),
|
|
845
|
+
};
|
|
846
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
847
|
+
const artifact = {
|
|
848
|
+
artifactState: ArtifactState.NEW,
|
|
849
|
+
config: configWithDeployedHook,
|
|
850
|
+
};
|
|
851
|
+
const [deployed] = await writer.create(artifact);
|
|
852
|
+
expect(mockHookWriter.create.called).to.be.false;
|
|
853
|
+
expect(deployed.artifactState).to.equal(ArtifactState.DEPLOYED);
|
|
854
|
+
});
|
|
855
|
+
it('should skip hook deployment when protocol does not support hooks', async () => {
|
|
856
|
+
mockArtifactManager.supportsHookUpdates.returns(false);
|
|
857
|
+
const configWithHook = {
|
|
858
|
+
...actualConfig,
|
|
859
|
+
hook: {
|
|
860
|
+
artifactState: ArtifactState.NEW,
|
|
861
|
+
config: merkleTreeHookConfig,
|
|
862
|
+
},
|
|
863
|
+
};
|
|
864
|
+
const mockWriter = {
|
|
865
|
+
read: Sinon.stub(),
|
|
866
|
+
create: Sinon.stub().resolves([
|
|
867
|
+
{
|
|
868
|
+
artifactState: ArtifactState.DEPLOYED,
|
|
869
|
+
config: configWithHook,
|
|
870
|
+
deployed: { address: TOKEN_ADDRESS },
|
|
871
|
+
},
|
|
872
|
+
[],
|
|
873
|
+
]),
|
|
874
|
+
update: Sinon.stub(),
|
|
875
|
+
};
|
|
876
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
877
|
+
const artifact = {
|
|
878
|
+
artifactState: ArtifactState.NEW,
|
|
879
|
+
config: configWithHook,
|
|
880
|
+
};
|
|
881
|
+
const [deployed] = await writer.create(artifact);
|
|
882
|
+
expect(mockHookWriter.create.called).to.be.false;
|
|
883
|
+
expect(deployed.artifactState).to.equal(ArtifactState.DEPLOYED);
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
describe('update() - Idempotency', () => {
|
|
887
|
+
it('should return empty array when no changes needed', async () => {
|
|
888
|
+
const mockWriter = {
|
|
889
|
+
read: Sinon.stub(),
|
|
890
|
+
create: Sinon.stub(),
|
|
891
|
+
update: Sinon.stub().resolves([]),
|
|
892
|
+
};
|
|
893
|
+
mockArtifactManager.createWriter.returns(mockWriter);
|
|
894
|
+
const artifact = {
|
|
895
|
+
...baseDeployedArtifact,
|
|
896
|
+
config: actualConfig,
|
|
897
|
+
};
|
|
898
|
+
const updateTxs = await writer.update(artifact);
|
|
899
|
+
expect(updateTxs).to.be.an('array').that.is.empty;
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
//# sourceMappingURL=warp-writer.test.js.map
|