@hyperlane-xyz/deploy-sdk 3.0.1 → 4.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.
Files changed (47) hide show
  1. package/dist/core/core-artifact-reader.d.ts +67 -0
  2. package/dist/core/core-artifact-reader.d.ts.map +1 -0
  3. package/dist/core/core-artifact-reader.js +117 -0
  4. package/dist/core/core-artifact-reader.js.map +1 -0
  5. package/dist/core/core-artifact-reader.test.d.ts +2 -0
  6. package/dist/core/core-artifact-reader.test.d.ts.map +1 -0
  7. package/dist/core/core-artifact-reader.test.js +300 -0
  8. package/dist/core/core-artifact-reader.test.js.map +1 -0
  9. package/dist/core/core-writer.d.ts +42 -0
  10. package/dist/core/core-writer.d.ts.map +1 -0
  11. package/dist/core/core-writer.js +230 -0
  12. package/dist/core/core-writer.js.map +1 -0
  13. package/dist/core/core-writer.test.d.ts +2 -0
  14. package/dist/core/core-writer.test.d.ts.map +1 -0
  15. package/dist/core/core-writer.test.js +980 -0
  16. package/dist/core/core-writer.test.js.map +1 -0
  17. package/dist/hook/hook-reader.d.ts +5 -2
  18. package/dist/hook/hook-reader.d.ts.map +1 -1
  19. package/dist/hook/hook-reader.js +4 -3
  20. package/dist/hook/hook-reader.js.map +1 -1
  21. package/dist/hook/hook-writer.d.ts.map +1 -1
  22. package/dist/hook/hook-writer.js +2 -1
  23. package/dist/hook/hook-writer.js.map +1 -1
  24. package/dist/index.d.ts +7 -8
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +7 -8
  27. package/dist/index.js.map +1 -1
  28. package/dist/ism/generic-ism.d.ts.map +1 -1
  29. package/dist/ism/generic-ism.js +3 -40
  30. package/dist/ism/generic-ism.js.map +1 -1
  31. package/dist/warp/warp-reader.d.ts +0 -2
  32. package/dist/warp/warp-reader.d.ts.map +1 -1
  33. package/dist/warp/warp-reader.js +7 -5
  34. package/dist/warp/warp-reader.js.map +1 -1
  35. package/package.json +10 -10
  36. package/dist/AltVMCoreModule.d.ts +0 -94
  37. package/dist/AltVMCoreModule.d.ts.map +0 -1
  38. package/dist/AltVMCoreModule.js +0 -368
  39. package/dist/AltVMCoreModule.js.map +0 -1
  40. package/dist/AltVMCoreReader.d.ts +0 -18
  41. package/dist/AltVMCoreReader.d.ts.map +0 -1
  42. package/dist/AltVMCoreReader.js +0 -35
  43. package/dist/AltVMCoreReader.js.map +0 -1
  44. package/dist/core-module.d.ts +0 -5
  45. package/dist/core-module.d.ts.map +0 -1
  46. package/dist/core-module.js +0 -28
  47. package/dist/core-module.js.map +0 -1
@@ -0,0 +1,980 @@
1
+ import { expect } from 'chai';
2
+ import sinon from 'sinon';
3
+ import { MockSigner, hasProtocol, registerProtocol, } from '@hyperlane-xyz/provider-sdk';
4
+ import { ArtifactState, } from '@hyperlane-xyz/provider-sdk/artifact';
5
+ import { ZERO_ADDRESS_HEX_32 } from '@hyperlane-xyz/utils';
6
+ import { CoreWriter } from './core-writer.js';
7
+ // Test protocol
8
+ const TestProtocol = 'test-core-writer';
9
+ let mockIsmArtifactManager;
10
+ let mockHookArtifactManager;
11
+ let mockProtocolProvider;
12
+ if (!hasProtocol(TestProtocol)) {
13
+ registerProtocol(TestProtocol, () => mockProtocolProvider);
14
+ }
15
+ describe('CoreWriter', () => {
16
+ const mockMailboxAddress = '0xMAILBOX';
17
+ const mockIsmAddress = '0xISM';
18
+ const mockNewIsmAddress = '0xNEWISM';
19
+ const mockDefaultHookAddress = '0xDEFAULTHOOK';
20
+ const mockRequiredHookAddress = '0xREQUIREDHOOK';
21
+ const mockValidatorAnnounceAddress = '0xVA';
22
+ const mockOwner = '0xOWNER';
23
+ const mockSignerAddress = '0xSIGNER';
24
+ const mockDomainId = 1;
25
+ const chainName = 'test-chain';
26
+ let createMailboxWriterStub;
27
+ let createVAWriterStub;
28
+ let getSignerAddressStub;
29
+ let sendAndConfirmTxStub;
30
+ let mailboxArtifactManager;
31
+ let validatorAnnounceArtifactManager;
32
+ let signer;
33
+ let chainLookup;
34
+ let chainMetadata;
35
+ let coreWriter;
36
+ const mockReceipt = {
37
+ transactionHash: '0xHASH',
38
+ blockNumber: 123,
39
+ };
40
+ const mockIsm = {
41
+ artifactState: ArtifactState.DEPLOYED,
42
+ config: {
43
+ type: 'merkleRootMultisigIsm',
44
+ validators: ['0xVALIDATOR1'],
45
+ threshold: 1,
46
+ },
47
+ deployed: { address: mockIsmAddress },
48
+ };
49
+ const mockDefaultHook = {
50
+ artifactState: ArtifactState.DEPLOYED,
51
+ config: {
52
+ type: 'merkleTreeHook',
53
+ },
54
+ deployed: { address: mockDefaultHookAddress },
55
+ };
56
+ const mockRequiredHook = {
57
+ artifactState: ArtifactState.DEPLOYED,
58
+ config: {
59
+ type: 'interchainGasPaymaster',
60
+ owner: mockOwner,
61
+ beneficiary: '0xBENEFICIARY',
62
+ oracleKey: '0xORACLE',
63
+ overhead: {},
64
+ oracleConfig: {},
65
+ },
66
+ deployed: { address: mockRequiredHookAddress },
67
+ };
68
+ beforeEach(async () => {
69
+ mockIsmArtifactManager = {
70
+ readIsm: sinon.stub(),
71
+ createReader: sinon.stub().returns({ read: sinon.stub() }),
72
+ createWriter: sinon.stub().returns({
73
+ create: sinon.stub(),
74
+ update: sinon.stub(),
75
+ read: sinon.stub(),
76
+ }),
77
+ };
78
+ mockHookArtifactManager = {
79
+ readHook: sinon.stub(),
80
+ createReader: sinon.stub().returns({ read: sinon.stub() }),
81
+ createWriter: sinon.stub().returns({
82
+ create: sinon.stub(),
83
+ update: sinon.stub(),
84
+ read: sinon.stub(),
85
+ }),
86
+ };
87
+ mockProtocolProvider = {
88
+ createProvider: sinon.stub(),
89
+ createSigner: sinon.stub(),
90
+ createSubmitter: sinon.stub(),
91
+ createIsmArtifactManager: sinon.stub(),
92
+ createHookArtifactManager: sinon.stub().returns(mockHookArtifactManager),
93
+ createMailboxArtifactManager: sinon.stub(),
94
+ createValidatorAnnounceArtifactManager: sinon.stub(),
95
+ getMinGas: sinon.stub(),
96
+ createWarpArtifactManager: sinon.stub(),
97
+ };
98
+ mockProtocolProvider.createIsmArtifactManager = sinon
99
+ .stub()
100
+ .returns(mockIsmArtifactManager);
101
+ signer = await MockSigner.connectWithSigner();
102
+ getSignerAddressStub = sinon.stub().returns(mockSignerAddress);
103
+ sinon.stub(signer, 'getSignerAddress').callsFake(getSignerAddressStub);
104
+ sendAndConfirmTxStub = sinon
105
+ .stub()
106
+ .resolves(mockReceipt);
107
+ sinon
108
+ .stub(signer, 'sendAndConfirmTransaction')
109
+ .callsFake(sendAndConfirmTxStub);
110
+ const mailboxCreateStub = sinon
111
+ .stub()
112
+ .resolves([
113
+ {
114
+ artifactState: ArtifactState.DEPLOYED,
115
+ config: {
116
+ owner: mockSignerAddress,
117
+ defaultIsm: {
118
+ artifactState: ArtifactState.DEPLOYED,
119
+ config: mockIsm.config,
120
+ deployed: mockIsm.deployed,
121
+ },
122
+ defaultHook: {
123
+ artifactState: ArtifactState.UNDERIVED,
124
+ deployed: { address: ZERO_ADDRESS_HEX_32 },
125
+ },
126
+ requiredHook: {
127
+ artifactState: ArtifactState.UNDERIVED,
128
+ deployed: { address: ZERO_ADDRESS_HEX_32 },
129
+ },
130
+ },
131
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
132
+ },
133
+ [mockReceipt],
134
+ ]);
135
+ const mailboxUpdateStub = sinon
136
+ .stub()
137
+ .resolves([]);
138
+ const mockMailboxWriter = {
139
+ create: mailboxCreateStub,
140
+ update: mailboxUpdateStub,
141
+ read: sinon.stub(),
142
+ };
143
+ createMailboxWriterStub = sinon.stub().returns(mockMailboxWriter);
144
+ mailboxArtifactManager = {
145
+ readMailbox: sinon.stub(),
146
+ createReader: sinon.stub(),
147
+ createWriter: createMailboxWriterStub,
148
+ };
149
+ const vaCreateStub = sinon
150
+ .stub()
151
+ .resolves([
152
+ {
153
+ artifactState: ArtifactState.DEPLOYED,
154
+ config: { mailboxAddress: mockMailboxAddress },
155
+ deployed: { address: mockValidatorAnnounceAddress },
156
+ },
157
+ [mockReceipt],
158
+ ]);
159
+ const mockVAWriter = {
160
+ create: vaCreateStub,
161
+ update: sinon.stub(),
162
+ read: sinon.stub(),
163
+ };
164
+ createVAWriterStub = sinon.stub().returns(mockVAWriter);
165
+ validatorAnnounceArtifactManager = {
166
+ readValidatorAnnounce: sinon.stub(),
167
+ createReader: sinon.stub(),
168
+ createWriter: createVAWriterStub,
169
+ };
170
+ chainMetadata = {
171
+ name: chainName,
172
+ domainId: mockDomainId,
173
+ protocol: TestProtocol,
174
+ chainId: mockDomainId,
175
+ };
176
+ chainLookup = {
177
+ getChainMetadata: sinon
178
+ .stub()
179
+ .returns(chainMetadata),
180
+ getChainName: sinon.stub().returns(chainName),
181
+ getDomainId: sinon
182
+ .stub()
183
+ .returns(mockDomainId),
184
+ getKnownChainNames: sinon.stub().returns([chainName]),
185
+ };
186
+ coreWriter = new CoreWriter(mailboxArtifactManager, validatorAnnounceArtifactManager, chainMetadata, chainLookup, signer);
187
+ });
188
+ afterEach(() => {
189
+ sinon.restore();
190
+ });
191
+ describe('create', () => {
192
+ it('should deploy mailbox with NEW ISM and NEW hooks', async () => {
193
+ // ARRANGE
194
+ const artifact = {
195
+ artifactState: ArtifactState.NEW,
196
+ config: {
197
+ owner: mockOwner,
198
+ defaultIsm: {
199
+ artifactState: ArtifactState.NEW,
200
+ config: mockIsm.config,
201
+ },
202
+ defaultHook: {
203
+ artifactState: ArtifactState.NEW,
204
+ config: mockDefaultHook.config,
205
+ },
206
+ requiredHook: {
207
+ artifactState: ArtifactState.NEW,
208
+ config: mockRequiredHook.config,
209
+ },
210
+ },
211
+ };
212
+ const ismCreateStub = sinon
213
+ .stub()
214
+ .callsFake(async () => [mockIsm, [mockReceipt]]);
215
+ const mockIsmWriter = {
216
+ create: ismCreateStub,
217
+ update: sinon.stub(),
218
+ read: sinon.stub(),
219
+ };
220
+ Object.defineProperty(coreWriter, 'ismWriter', {
221
+ value: mockIsmWriter,
222
+ writable: true,
223
+ });
224
+ const hookCreateStub = sinon
225
+ .stub()
226
+ .onFirstCall()
227
+ .callsFake(async () => [mockDefaultHook, [mockReceipt]])
228
+ .onSecondCall()
229
+ .callsFake(async () => [mockRequiredHook, [mockReceipt]]);
230
+ const mockHookWriter = {
231
+ create: hookCreateStub,
232
+ update: sinon.stub(),
233
+ read: sinon.stub(),
234
+ };
235
+ mockHookArtifactManager.createWriter = sinon
236
+ .stub()
237
+ .returns(mockHookWriter);
238
+ // ACT
239
+ const [result, receipts] = await coreWriter.create(artifact);
240
+ // ASSERT
241
+ expect(result.mailbox.deployed.address).to.equal(mockMailboxAddress);
242
+ expect(result.mailbox.config.owner).to.equal(mockOwner);
243
+ expect(result.validatorAnnounce).to.not.be.null;
244
+ expect(result.validatorAnnounce?.deployed.address).to.equal(mockValidatorAnnounceAddress);
245
+ expect(receipts.length).to.be.greaterThan(0);
246
+ sinon.assert.calledOnce(ismCreateStub);
247
+ sinon.assert.calledTwice(hookCreateStub);
248
+ sinon.assert.calledWith(createMailboxWriterStub, 'mailbox', signer);
249
+ sinon.assert.calledWith(createVAWriterStub, 'validatorAnnounce', signer);
250
+ });
251
+ it('should use existing ISM when DEPLOYED', async () => {
252
+ // ARRANGE
253
+ const artifact = {
254
+ artifactState: ArtifactState.NEW,
255
+ config: {
256
+ owner: mockOwner,
257
+ defaultIsm: mockIsm,
258
+ defaultHook: mockDefaultHook,
259
+ requiredHook: mockRequiredHook,
260
+ },
261
+ };
262
+ const ismCreateStub = sinon.stub();
263
+ const mockIsmWriter = {
264
+ create: ismCreateStub,
265
+ update: sinon.stub(),
266
+ read: sinon.stub(),
267
+ };
268
+ Object.defineProperty(coreWriter, 'ismWriter', {
269
+ value: mockIsmWriter,
270
+ writable: true,
271
+ });
272
+ // ACT
273
+ await coreWriter.create(artifact);
274
+ // ASSERT
275
+ sinon.assert.notCalled(ismCreateStub);
276
+ });
277
+ it('should handle protocol without validator announce support', async () => {
278
+ // ARRANGE
279
+ const coreWriterNoVA = new CoreWriter(mailboxArtifactManager, null, chainMetadata, chainLookup, signer);
280
+ const artifact = {
281
+ artifactState: ArtifactState.NEW,
282
+ config: {
283
+ owner: mockOwner,
284
+ defaultIsm: mockIsm,
285
+ defaultHook: mockDefaultHook,
286
+ requiredHook: mockRequiredHook,
287
+ },
288
+ };
289
+ const mockIsmWriter = {
290
+ create: sinon.stub(),
291
+ update: sinon.stub(),
292
+ read: sinon.stub(),
293
+ };
294
+ Object.defineProperty(coreWriterNoVA, 'ismWriter', {
295
+ value: mockIsmWriter,
296
+ writable: true,
297
+ });
298
+ // ACT
299
+ const [result] = await coreWriterNoVA.create(artifact);
300
+ // ASSERT
301
+ expect(result.validatorAnnounce).to.be.null;
302
+ });
303
+ it('should create mailbox with signer as initial owner', async () => {
304
+ // ARRANGE
305
+ const artifact = {
306
+ artifactState: ArtifactState.NEW,
307
+ config: {
308
+ owner: mockOwner,
309
+ defaultIsm: mockIsm,
310
+ defaultHook: mockDefaultHook,
311
+ requiredHook: mockRequiredHook,
312
+ },
313
+ };
314
+ const mockIsmWriter = {
315
+ create: sinon.stub(),
316
+ update: sinon.stub(),
317
+ read: sinon.stub(),
318
+ };
319
+ Object.defineProperty(coreWriter, 'ismWriter', {
320
+ value: mockIsmWriter,
321
+ writable: true,
322
+ });
323
+ // ACT
324
+ await coreWriter.create(artifact);
325
+ // ASSERT
326
+ const mailboxWriter = createMailboxWriterStub.returnValues[0];
327
+ const mailboxCreateStub = mailboxWriter.create;
328
+ const initialConfig = mailboxCreateStub.firstCall.args[0].config;
329
+ expect(initialConfig.owner).to.equal(mockSignerAddress);
330
+ expect(initialConfig.owner).to.not.equal(mockOwner);
331
+ });
332
+ it('should create mailbox with zero-address hooks initially', async () => {
333
+ // ARRANGE
334
+ const artifact = {
335
+ artifactState: ArtifactState.NEW,
336
+ config: {
337
+ owner: mockOwner,
338
+ defaultIsm: mockIsm,
339
+ defaultHook: {
340
+ artifactState: ArtifactState.NEW,
341
+ config: mockDefaultHook.config,
342
+ },
343
+ requiredHook: {
344
+ artifactState: ArtifactState.NEW,
345
+ config: mockRequiredHook.config,
346
+ },
347
+ },
348
+ };
349
+ const mockIsmWriter = {
350
+ create: sinon.stub(),
351
+ update: sinon.stub(),
352
+ read: sinon.stub(),
353
+ };
354
+ Object.defineProperty(coreWriter, 'ismWriter', {
355
+ value: mockIsmWriter,
356
+ writable: true,
357
+ });
358
+ const mockHookWriter = {
359
+ create: sinon
360
+ .stub()
361
+ .onFirstCall()
362
+ .callsFake(async () => [mockDefaultHook, [mockReceipt]])
363
+ .onSecondCall()
364
+ .callsFake(async () => [mockRequiredHook, [mockReceipt]]),
365
+ update: sinon.stub(),
366
+ read: sinon.stub(),
367
+ };
368
+ mockHookArtifactManager.createWriter = sinon
369
+ .stub()
370
+ .returns(mockHookWriter);
371
+ // ACT
372
+ await coreWriter.create(artifact);
373
+ // ASSERT
374
+ const mailboxWriter = createMailboxWriterStub.returnValues[0];
375
+ const mailboxCreateStub = mailboxWriter.create;
376
+ const initialConfig = mailboxCreateStub.firstCall.args[0].config;
377
+ expect(initialConfig.defaultHook.deployed.address).to.equal(ZERO_ADDRESS_HEX_32);
378
+ expect(initialConfig.requiredHook.deployed.address).to.equal(ZERO_ADDRESS_HEX_32);
379
+ expect(initialConfig.defaultHook.artifactState).to.equal(ArtifactState.UNDERIVED);
380
+ });
381
+ it('should update mailbox with hooks and owner after deployment', async () => {
382
+ // ARRANGE
383
+ const artifact = {
384
+ artifactState: ArtifactState.NEW,
385
+ config: {
386
+ owner: mockOwner,
387
+ defaultIsm: mockIsm,
388
+ defaultHook: mockDefaultHook,
389
+ requiredHook: mockRequiredHook,
390
+ },
391
+ };
392
+ const mockIsmWriter = {
393
+ create: sinon.stub(),
394
+ update: sinon.stub(),
395
+ read: sinon.stub(),
396
+ };
397
+ Object.defineProperty(coreWriter, 'ismWriter', {
398
+ value: mockIsmWriter,
399
+ writable: true,
400
+ });
401
+ // ACT
402
+ await coreWriter.create(artifact);
403
+ // ASSERT
404
+ const mailboxWriter = createMailboxWriterStub.returnValues[0];
405
+ const mailboxUpdateStub = mailboxWriter.update;
406
+ sinon.assert.calledOnce(mailboxUpdateStub);
407
+ const updatedArtifact = mailboxUpdateStub.firstCall.args[0];
408
+ expect(updatedArtifact.config.owner).to.equal(mockOwner);
409
+ expect(updatedArtifact.config.defaultHook.deployed.address).to.equal(mockDefaultHookAddress);
410
+ expect(updatedArtifact.config.requiredHook.deployed.address).to.equal(mockRequiredHookAddress);
411
+ });
412
+ it('should execute update transactions via signer', async () => {
413
+ // ARRANGE
414
+ const mockUpdateTx = {
415
+ to: mockMailboxAddress,
416
+ data: '0xUPDATE',
417
+ annotation: 'Update hooks',
418
+ };
419
+ const mailboxCreateStub = sinon.stub().resolves([
420
+ {
421
+ artifactState: ArtifactState.DEPLOYED,
422
+ config: {
423
+ owner: mockSignerAddress,
424
+ defaultIsm: {
425
+ artifactState: ArtifactState.DEPLOYED,
426
+ config: mockIsm.config,
427
+ deployed: mockIsm.deployed,
428
+ },
429
+ defaultHook: {
430
+ artifactState: ArtifactState.UNDERIVED,
431
+ deployed: { address: ZERO_ADDRESS_HEX_32 },
432
+ },
433
+ requiredHook: {
434
+ artifactState: ArtifactState.UNDERIVED,
435
+ deployed: { address: ZERO_ADDRESS_HEX_32 },
436
+ },
437
+ },
438
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
439
+ },
440
+ [mockReceipt],
441
+ ]);
442
+ const mailboxUpdateStub = sinon.stub().resolves([mockUpdateTx]);
443
+ const mockMailboxWriter = {
444
+ create: mailboxCreateStub,
445
+ update: mailboxUpdateStub,
446
+ read: sinon.stub(),
447
+ };
448
+ mailboxArtifactManager.createWriter = sinon
449
+ .stub()
450
+ .returns(mockMailboxWriter);
451
+ const artifact = {
452
+ artifactState: ArtifactState.NEW,
453
+ config: {
454
+ owner: mockOwner,
455
+ defaultIsm: mockIsm,
456
+ defaultHook: mockDefaultHook,
457
+ requiredHook: mockRequiredHook,
458
+ },
459
+ };
460
+ const mockIsmWriter = {
461
+ create: sinon.stub(),
462
+ update: sinon.stub(),
463
+ read: sinon.stub(),
464
+ };
465
+ Object.defineProperty(coreWriter, 'ismWriter', {
466
+ value: mockIsmWriter,
467
+ writable: true,
468
+ });
469
+ // ACT
470
+ await coreWriter.create(artifact);
471
+ // ASSERT
472
+ sinon.assert.calledWith(sendAndConfirmTxStub, mockUpdateTx);
473
+ });
474
+ it('should collect receipts from all steps', async () => {
475
+ // ARRANGE
476
+ const artifact = {
477
+ artifactState: ArtifactState.NEW,
478
+ config: {
479
+ owner: mockOwner,
480
+ defaultIsm: {
481
+ artifactState: ArtifactState.NEW,
482
+ config: mockIsm.config,
483
+ },
484
+ defaultHook: mockDefaultHook,
485
+ requiredHook: mockRequiredHook,
486
+ },
487
+ };
488
+ const mockIsmWriter = {
489
+ create: sinon
490
+ .stub()
491
+ .callsFake(async () => [mockIsm, [mockReceipt, mockReceipt]]),
492
+ update: sinon.stub(),
493
+ read: sinon.stub(),
494
+ };
495
+ Object.defineProperty(coreWriter, 'ismWriter', {
496
+ value: mockIsmWriter,
497
+ writable: true,
498
+ });
499
+ // ACT
500
+ const [, receipts] = await coreWriter.create(artifact);
501
+ // ASSERT
502
+ expect(receipts.length).to.be.greaterThan(0);
503
+ expect(receipts.length).to.be.at.least(4);
504
+ });
505
+ it('should propagate ISM deployment errors', async () => {
506
+ // ARRANGE
507
+ const error = new Error('ISM deployment failed');
508
+ const artifact = {
509
+ artifactState: ArtifactState.NEW,
510
+ config: {
511
+ owner: mockOwner,
512
+ defaultIsm: {
513
+ artifactState: ArtifactState.NEW,
514
+ config: mockIsm.config,
515
+ },
516
+ defaultHook: mockDefaultHook,
517
+ requiredHook: mockRequiredHook,
518
+ },
519
+ };
520
+ const mockIsmWriter = {
521
+ create: sinon.stub().rejects(error),
522
+ update: sinon.stub(),
523
+ read: sinon.stub(),
524
+ };
525
+ Object.defineProperty(coreWriter, 'ismWriter', {
526
+ value: mockIsmWriter,
527
+ writable: true,
528
+ });
529
+ // ACT & ASSERT
530
+ await expect(coreWriter.create(artifact)).to.be.rejectedWith('ISM deployment failed');
531
+ });
532
+ it('should propagate mailbox creation errors', async () => {
533
+ // ARRANGE
534
+ const error = new Error('Mailbox creation failed');
535
+ const mockMailboxWriter = {
536
+ create: sinon.stub().rejects(error),
537
+ update: sinon.stub(),
538
+ read: sinon.stub(),
539
+ };
540
+ mailboxArtifactManager.createWriter = sinon
541
+ .stub()
542
+ .returns(mockMailboxWriter);
543
+ const artifact = {
544
+ artifactState: ArtifactState.NEW,
545
+ config: {
546
+ owner: mockOwner,
547
+ defaultIsm: mockIsm,
548
+ defaultHook: mockDefaultHook,
549
+ requiredHook: mockRequiredHook,
550
+ },
551
+ };
552
+ const mockIsmWriter = {
553
+ create: sinon.stub(),
554
+ update: sinon.stub(),
555
+ read: sinon.stub(),
556
+ };
557
+ Object.defineProperty(coreWriter, 'ismWriter', {
558
+ value: mockIsmWriter,
559
+ writable: true,
560
+ });
561
+ // ACT & ASSERT
562
+ await expect(coreWriter.create(artifact)).to.be.rejectedWith('Mailbox creation failed');
563
+ });
564
+ });
565
+ describe('update', () => {
566
+ it('should handle ISM type change by deploying new ISM', async () => {
567
+ // ARRANGE
568
+ const currentMailbox = {
569
+ artifactState: ArtifactState.DEPLOYED,
570
+ config: {
571
+ owner: mockOwner,
572
+ defaultIsm: mockIsm,
573
+ defaultHook: mockDefaultHook,
574
+ requiredHook: mockRequiredHook,
575
+ },
576
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
577
+ };
578
+ sinon.stub(coreWriter, 'read').resolves(currentMailbox);
579
+ const newIsm = {
580
+ artifactState: ArtifactState.DEPLOYED,
581
+ config: {
582
+ type: 'messageIdMultisigIsm',
583
+ validators: ['0xVALIDATOR2'],
584
+ threshold: 1,
585
+ },
586
+ deployed: { address: mockNewIsmAddress },
587
+ };
588
+ const expectedArtifact = {
589
+ artifactState: ArtifactState.DEPLOYED,
590
+ config: {
591
+ owner: mockOwner,
592
+ defaultIsm: {
593
+ artifactState: ArtifactState.NEW,
594
+ config: newIsm.config,
595
+ },
596
+ defaultHook: mockDefaultHook,
597
+ requiredHook: mockRequiredHook,
598
+ },
599
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
600
+ };
601
+ const ismCreateStub = sinon
602
+ .stub()
603
+ .callsFake(async () => [newIsm, [mockReceipt]]);
604
+ const mockIsmWriter = {
605
+ create: ismCreateStub,
606
+ update: sinon.stub(),
607
+ read: sinon.stub(),
608
+ };
609
+ mockIsmArtifactManager.createWriter = sinon.stub().returns(mockIsmWriter);
610
+ const mockHookWriter = {
611
+ create: sinon.stub(),
612
+ update: sinon.stub().resolves([]),
613
+ read: sinon.stub(),
614
+ };
615
+ mockHookArtifactManager.createWriter = sinon
616
+ .stub()
617
+ .returns(mockHookWriter);
618
+ // ACT
619
+ await coreWriter.update(expectedArtifact);
620
+ // ASSERT
621
+ sinon.assert.calledOnce(ismCreateStub);
622
+ const createArg = ismCreateStub.firstCall.args[0];
623
+ expect(createArg.artifactState).to.equal(ArtifactState.NEW);
624
+ expect(createArg.config.type).to.equal('messageIdMultisigIsm');
625
+ });
626
+ it('should handle UNDERIVED ISM by using address as-is', async () => {
627
+ // ARRANGE
628
+ const currentMailbox = {
629
+ artifactState: ArtifactState.DEPLOYED,
630
+ config: {
631
+ owner: mockOwner,
632
+ defaultIsm: mockIsm,
633
+ defaultHook: mockDefaultHook,
634
+ requiredHook: mockRequiredHook,
635
+ },
636
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
637
+ };
638
+ sinon.stub(coreWriter, 'read').resolves(currentMailbox);
639
+ const expectedArtifact = {
640
+ artifactState: ArtifactState.DEPLOYED,
641
+ config: {
642
+ owner: mockOwner,
643
+ defaultIsm: {
644
+ artifactState: ArtifactState.UNDERIVED,
645
+ deployed: { address: mockIsmAddress },
646
+ },
647
+ defaultHook: mockDefaultHook,
648
+ requiredHook: mockRequiredHook,
649
+ },
650
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
651
+ };
652
+ const ismCreateStub = sinon.stub();
653
+ const ismUpdateStub = sinon.stub();
654
+ const mockIsmWriter = {
655
+ create: ismCreateStub,
656
+ update: ismUpdateStub,
657
+ read: sinon.stub(),
658
+ };
659
+ Object.defineProperty(coreWriter, 'ismWriter', {
660
+ value: mockIsmWriter,
661
+ writable: true,
662
+ });
663
+ const mockHookWriter = {
664
+ create: sinon.stub(),
665
+ update: sinon.stub().resolves([]),
666
+ read: sinon.stub(),
667
+ };
668
+ mockHookArtifactManager.createWriter = sinon
669
+ .stub()
670
+ .returns(mockHookWriter);
671
+ // ACT
672
+ await coreWriter.update(expectedArtifact);
673
+ // ASSERT
674
+ sinon.assert.notCalled(ismCreateStub);
675
+ sinon.assert.notCalled(ismUpdateStub);
676
+ });
677
+ it('should return empty array when no updates needed', async () => {
678
+ // ARRANGE
679
+ const currentMailbox = {
680
+ artifactState: ArtifactState.DEPLOYED,
681
+ config: {
682
+ owner: mockOwner,
683
+ defaultIsm: mockIsm,
684
+ defaultHook: mockDefaultHook,
685
+ requiredHook: mockRequiredHook,
686
+ },
687
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
688
+ };
689
+ sinon.stub(coreWriter, 'read').resolves(currentMailbox);
690
+ const expectedArtifact = {
691
+ artifactState: ArtifactState.DEPLOYED,
692
+ config: currentMailbox.config,
693
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
694
+ };
695
+ const mockIsmWriter = {
696
+ create: sinon.stub(),
697
+ update: sinon.stub().resolves([]),
698
+ read: sinon.stub(),
699
+ };
700
+ Object.defineProperty(coreWriter, 'ismWriter', {
701
+ value: mockIsmWriter,
702
+ writable: true,
703
+ });
704
+ const mockHookWriter = {
705
+ create: sinon.stub(),
706
+ update: sinon.stub().resolves([]),
707
+ read: sinon.stub(),
708
+ };
709
+ mockHookArtifactManager.createWriter = sinon
710
+ .stub()
711
+ .returns(mockHookWriter);
712
+ // ACT
713
+ const result = await coreWriter.update(expectedArtifact);
714
+ // ASSERT
715
+ expect(result).to.be.an('array').with.lengthOf(0);
716
+ });
717
+ it('should deploy NEW ISM when current is zero-address UNDERIVED', async () => {
718
+ // ARRANGE
719
+ const currentMailbox = {
720
+ artifactState: ArtifactState.DEPLOYED,
721
+ config: {
722
+ owner: mockOwner,
723
+ defaultIsm: {
724
+ artifactState: ArtifactState.UNDERIVED,
725
+ deployed: { address: ZERO_ADDRESS_HEX_32 },
726
+ },
727
+ defaultHook: mockDefaultHook,
728
+ requiredHook: mockRequiredHook,
729
+ },
730
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
731
+ };
732
+ sinon.stub(coreWriter, 'read').resolves(currentMailbox);
733
+ const newIsm = {
734
+ artifactState: ArtifactState.DEPLOYED,
735
+ config: {
736
+ type: 'merkleRootMultisigIsm',
737
+ validators: ['0xVALIDATOR1'],
738
+ threshold: 1,
739
+ },
740
+ deployed: { address: mockNewIsmAddress },
741
+ };
742
+ const expectedArtifact = {
743
+ artifactState: ArtifactState.DEPLOYED,
744
+ config: {
745
+ owner: mockOwner,
746
+ defaultIsm: {
747
+ artifactState: ArtifactState.NEW,
748
+ config: newIsm.config,
749
+ },
750
+ defaultHook: mockDefaultHook,
751
+ requiredHook: mockRequiredHook,
752
+ },
753
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
754
+ };
755
+ const ismCreateStub = sinon
756
+ .stub()
757
+ .callsFake(async () => [newIsm, [mockReceipt]]);
758
+ const mockIsmWriter = {
759
+ create: ismCreateStub,
760
+ update: sinon.stub(),
761
+ read: sinon.stub(),
762
+ };
763
+ Object.defineProperty(coreWriter, 'ismWriter', {
764
+ value: mockIsmWriter,
765
+ writable: true,
766
+ });
767
+ const mockHookWriter = {
768
+ create: sinon.stub(),
769
+ update: sinon.stub().resolves([]),
770
+ read: sinon.stub(),
771
+ };
772
+ mockHookArtifactManager.createWriter = sinon
773
+ .stub()
774
+ .returns(mockHookWriter);
775
+ // ACT
776
+ const result = await coreWriter.update(expectedArtifact);
777
+ // ASSERT
778
+ sinon.assert.calledOnce(ismCreateStub);
779
+ expect(result).to.be.an('array');
780
+ });
781
+ it('should assert artifacts are expanded before updating', async () => {
782
+ // ARRANGE
783
+ const currentMailbox = {
784
+ artifactState: ArtifactState.DEPLOYED,
785
+ config: {
786
+ owner: mockOwner,
787
+ defaultIsm: {
788
+ artifactState: ArtifactState.UNDERIVED,
789
+ deployed: { address: mockIsmAddress },
790
+ },
791
+ defaultHook: mockDefaultHook,
792
+ requiredHook: mockRequiredHook,
793
+ },
794
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
795
+ };
796
+ sinon.stub(coreWriter, 'read').resolves(currentMailbox);
797
+ const expectedArtifact = {
798
+ artifactState: ArtifactState.DEPLOYED,
799
+ config: {
800
+ owner: mockOwner,
801
+ defaultIsm: mockIsm,
802
+ defaultHook: mockDefaultHook,
803
+ requiredHook: mockRequiredHook,
804
+ },
805
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
806
+ };
807
+ // ACT & ASSERT
808
+ await expect(coreWriter.update(expectedArtifact)).to.be.rejectedWith('Expected defaultIsm to be DEPLOYED or UNDERIVED with zero address');
809
+ });
810
+ it('should deploy NEW hook during update', async () => {
811
+ // ARRANGE
812
+ const currentMailbox = {
813
+ artifactState: ArtifactState.DEPLOYED,
814
+ config: {
815
+ owner: mockOwner,
816
+ defaultIsm: mockIsm,
817
+ defaultHook: {
818
+ artifactState: ArtifactState.UNDERIVED,
819
+ deployed: { address: ZERO_ADDRESS_HEX_32 },
820
+ },
821
+ requiredHook: mockRequiredHook,
822
+ },
823
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
824
+ };
825
+ sinon.stub(coreWriter, 'read').resolves(currentMailbox);
826
+ const newDefaultHook = {
827
+ artifactState: ArtifactState.DEPLOYED,
828
+ config: { type: 'merkleTreeHook' },
829
+ deployed: { address: mockDefaultHookAddress },
830
+ };
831
+ const expectedArtifact = {
832
+ artifactState: ArtifactState.DEPLOYED,
833
+ config: {
834
+ owner: mockOwner,
835
+ defaultIsm: mockIsm,
836
+ defaultHook: {
837
+ artifactState: ArtifactState.NEW,
838
+ config: newDefaultHook.config,
839
+ },
840
+ requiredHook: mockRequiredHook,
841
+ },
842
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
843
+ };
844
+ const mockIsmWriter = {
845
+ create: sinon.stub(),
846
+ update: sinon.stub().resolves([]),
847
+ read: sinon.stub(),
848
+ };
849
+ Object.defineProperty(coreWriter, 'ismWriter', {
850
+ value: mockIsmWriter,
851
+ writable: true,
852
+ });
853
+ const hookCreateStub = sinon
854
+ .stub()
855
+ .callsFake(async () => [newDefaultHook, [mockReceipt]]);
856
+ const mockHookWriter = {
857
+ create: hookCreateStub,
858
+ update: sinon.stub().resolves([]),
859
+ read: sinon.stub(),
860
+ };
861
+ mockHookArtifactManager.createWriter = sinon
862
+ .stub()
863
+ .returns(mockHookWriter);
864
+ // ACT
865
+ await coreWriter.update(expectedArtifact);
866
+ // ASSERT
867
+ sinon.assert.calledOnce(hookCreateStub);
868
+ });
869
+ it('should handle owner change', async () => {
870
+ // ARRANGE
871
+ const newOwner = '0xNEWOWNER';
872
+ const currentMailbox = {
873
+ artifactState: ArtifactState.DEPLOYED,
874
+ config: {
875
+ owner: mockOwner,
876
+ defaultIsm: mockIsm,
877
+ defaultHook: mockDefaultHook,
878
+ requiredHook: mockRequiredHook,
879
+ },
880
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
881
+ };
882
+ sinon.stub(coreWriter, 'read').resolves(currentMailbox);
883
+ const expectedArtifact = {
884
+ artifactState: ArtifactState.DEPLOYED,
885
+ config: {
886
+ owner: newOwner,
887
+ defaultIsm: mockIsm,
888
+ defaultHook: mockDefaultHook,
889
+ requiredHook: mockRequiredHook,
890
+ },
891
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
892
+ };
893
+ const mockIsmWriter = {
894
+ create: sinon.stub(),
895
+ update: sinon.stub().resolves([]),
896
+ read: sinon.stub(),
897
+ };
898
+ Object.defineProperty(coreWriter, 'ismWriter', {
899
+ value: mockIsmWriter,
900
+ writable: true,
901
+ });
902
+ const mockHookWriter = {
903
+ create: sinon.stub(),
904
+ update: sinon.stub().resolves([]),
905
+ read: sinon.stub(),
906
+ };
907
+ mockHookArtifactManager.createWriter = sinon
908
+ .stub()
909
+ .returns(mockHookWriter);
910
+ // ACT
911
+ await coreWriter.update(expectedArtifact);
912
+ // ASSERT
913
+ const mailboxWriter = createMailboxWriterStub.returnValues[0];
914
+ const updatedArtifact = mailboxWriter.update.firstCall.args[0];
915
+ expect(updatedArtifact.config.owner).to.equal(newOwner);
916
+ });
917
+ it('should wire correct addresses into mailbox update', async () => {
918
+ // ARRANGE
919
+ const newIsm = {
920
+ artifactState: ArtifactState.DEPLOYED,
921
+ config: {
922
+ type: 'messageIdMultisigIsm',
923
+ validators: ['0xVALIDATOR2'],
924
+ threshold: 1,
925
+ },
926
+ deployed: { address: mockNewIsmAddress },
927
+ };
928
+ const currentMailbox = {
929
+ artifactState: ArtifactState.DEPLOYED,
930
+ config: {
931
+ owner: mockOwner,
932
+ defaultIsm: mockIsm,
933
+ defaultHook: mockDefaultHook,
934
+ requiredHook: mockRequiredHook,
935
+ },
936
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
937
+ };
938
+ sinon.stub(coreWriter, 'read').resolves(currentMailbox);
939
+ const expectedArtifact = {
940
+ artifactState: ArtifactState.DEPLOYED,
941
+ config: {
942
+ owner: mockOwner,
943
+ defaultIsm: {
944
+ artifactState: ArtifactState.NEW,
945
+ config: newIsm.config,
946
+ },
947
+ defaultHook: mockDefaultHook,
948
+ requiredHook: mockRequiredHook,
949
+ },
950
+ deployed: { address: mockMailboxAddress, domainId: mockDomainId },
951
+ };
952
+ const ismCreateStub = sinon
953
+ .stub()
954
+ .callsFake(async () => [newIsm, [mockReceipt]]);
955
+ const mockIsmWriter = {
956
+ create: ismCreateStub,
957
+ update: sinon.stub(),
958
+ read: sinon.stub(),
959
+ };
960
+ mockIsmArtifactManager.createWriter = sinon.stub().returns(mockIsmWriter);
961
+ const mockHookWriter = {
962
+ create: sinon.stub(),
963
+ update: sinon.stub().resolves([]),
964
+ read: sinon.stub(),
965
+ };
966
+ mockHookArtifactManager.createWriter = sinon
967
+ .stub()
968
+ .returns(mockHookWriter);
969
+ // ACT
970
+ await coreWriter.update(expectedArtifact);
971
+ // ASSERT
972
+ const mailboxWriter = createMailboxWriterStub.returnValues[0];
973
+ const updatedArtifact = mailboxWriter.update.firstCall.args[0];
974
+ expect(updatedArtifact.config.defaultIsm.deployed.address).to.equal(mockNewIsmAddress);
975
+ expect(updatedArtifact.config.defaultHook.deployed.address).to.equal(mockDefaultHookAddress);
976
+ expect(updatedArtifact.config.requiredHook.deployed.address).to.equal(mockRequiredHookAddress);
977
+ });
978
+ });
979
+ });
980
+ //# sourceMappingURL=core-writer.test.js.map