@massalabs/multisig-contract 0.0.2-dev.20260414091108 → 0.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/README.md CHANGED
@@ -74,6 +74,46 @@ npm run propose:add-member -- <multisig-address> <new-member-address>
74
74
 
75
75
  The script uses the same `.env` configuration as deployment and prints the submitted operation id plus the final events emitted by the contract.
76
76
 
77
+ ## Create a revoke-member proposal
78
+
79
+ The repository also includes a helper script to submit a multisig proposal that removes an existing owner.
80
+
81
+ ```shell
82
+ npm run propose:revoke-member -- <multisig-address> <member-address>
83
+ ```
84
+
85
+ The script uses the same `.env` configuration as deployment and prints the submitted operation id plus the final events emitted by the contract.
86
+
87
+ ## Create a replace-member proposal
88
+
89
+ The repository also includes a helper script to submit a multisig proposal that replaces an existing owner with a new one.
90
+
91
+ ```shell
92
+ npm run propose:replace-member -- <multisig-address> <current-member-address> <new-member-address>
93
+ ```
94
+
95
+ The script uses the same `.env` configuration as deployment and prints the submitted operation id plus the final events emitted by the contract.
96
+
97
+ ## Create a threshold proposal
98
+
99
+ The repository also includes a helper script to submit a multisig proposal that updates the required approval threshold.
100
+
101
+ ```shell
102
+ npm run propose:threshold -- <multisig-address> <threshold>
103
+ ```
104
+
105
+ The script uses the same `.env` configuration as deployment and prints the submitted operation id plus the final events emitted by the contract.
106
+
107
+ ## Create an execution-delay proposal
108
+
109
+ The repository also includes a helper script to submit a multisig proposal that updates the execution delay.
110
+
111
+ ```shell
112
+ npm run propose:execution-delay -- <multisig-address> <execution-delay>
113
+ ```
114
+
115
+ The script uses the same `.env` configuration as deployment and prints the submitted operation id plus the final events emitted by the contract.
116
+
77
117
  ## Approve a proposal
78
118
 
79
119
  The repository also includes a helper script to approve an existing multisig proposal by id.
@@ -3,8 +3,16 @@ import {
3
3
  approve,
4
4
  constructor,
5
5
  getTransactions,
6
+ execute,
7
+ revoke,
6
8
  } from '../contracts/Multisig';
7
- import { Storage, mockAdminContext, Address } from '@massalabs/massa-as-sdk';
9
+ import {
10
+ Storage,
11
+ mockAdminContext,
12
+ Address,
13
+ balanceOf,
14
+ } from '@massalabs/massa-as-sdk';
15
+ import { mockBalance } from '@massalabs/massa-as-sdk/assembly/vm-mock';
8
16
  import {
9
17
  Args,
10
18
  u64ToBytes,
@@ -306,137 +314,88 @@ describe('Multisig contract tests', () => {
306
314
  expect(transaction.data).toStrictEqual(new Args().add(42).serialize());
307
315
  });
308
316
 
309
- // TODO: can't test transferCoins & call operation on massa for now
310
- // // operation 2 is validated, let's execute it
311
- // test('execute transaction operation with success', () => {
312
- // let destinationBalance = Coins.balanceOf(destination);
313
- // let contractBalance = Coins.balanceOf(contractAddr);
314
- // let initDestinationBalance = destinationBalance;
315
- // let initContractBalance = contractBalance;
316
-
317
- // switchUser(owners[1]);
318
- // generateEvent(
319
- // createEvent('BALANCES BEFORE', [
320
- // initDestinationBalance.toString(),
321
- // initContractBalance.toString(),
322
- // ]),
323
- // );
324
-
325
- // expect(() => {
326
- // execute(new Args().add(u64(2)).serialize());
327
- // }).not.toThrow();
328
-
329
- // // retrieve the operation and check that it is marked as executed
330
- // let transaction = retrieveOperation(i32(2));
331
- // expect(transaction.executed).toBe(true);
332
-
333
- // destinationBalance = Coins.balanceOf(destination);
334
- // contractBalance = Coins.balanceOf(contractAddr);
335
- // generateEvent(
336
- // createEvent('BALANCES AFTER', [
337
- // destinationBalance.toString(),
338
- // contractBalance.toString(),
339
- // ]),
340
- // );
341
-
342
- // // check that the transfer has been done
343
- // expect(destinationBalance).toBe(initDestinationBalance + 15000);
344
- // expect(contractBalance + 15000).toBe(initContractBalance);
345
- // });
346
-
347
- // // operation 1 is not validated, let's try to execute it
348
- // test('execute transaction operation with failure', () => {
349
- // let destinationBalance = Coins.balanceOf(destination);
350
- // let contractBalance = Coins.balanceOf(contractAddr);
351
- // let initDestinationBalance = destinationBalance;
352
- // let initContractBalance = contractBalance;
353
-
354
- // switchUser(owners[1]);
355
- // generateEvent(
356
- // createEvent('BALANCES BEFORE', [
357
- // initDestinationBalance.toString(),
358
- // initContractBalance.toString(),
359
- // ]),
360
- // );
361
-
362
- // expect(() => {
363
- // execute(new Args().add(u64(1)).serialize());
364
- // }).toThrow();
365
-
366
- // // the operation is not supposed to be deleted
367
- // expect(() => {
368
- // retrieveOperation(i32(1));
369
- // }).not.toThrow();
370
-
371
- // destinationBalance = Coins.balanceOf(destination);
372
- // contractBalance = Coins.balanceOf(contractAddr);
373
- // generateEvent(
374
- // createEvent('BALANCES AFTER', [
375
- // destinationBalance.toString(),
376
- // contractBalance.toString(),
377
- // ]),
378
- // );
379
-
380
- // // check that the transfer has not been done
381
- // expect(destinationBalance).toBe(initDestinationBalance);
382
- // expect(contractBalance).toBe(initContractBalance);
383
- // });
384
-
385
- // // operation 3 is validated by owners[1] & owners[2].
386
- // // now owners[1] will revoke it and we will try to execute it.
387
- // test('revoke operation', () => {
388
- // let operationListLenght = bytesToSerializableObjectArray<Transaction>(
389
- // getTransactions([]),
390
- // ).unwrap().length;
391
-
392
- // let destinationBalance = Coins.balanceOf(destination);
393
- // let contractBalance = Coins.balanceOf(contractAddr);
394
- // let initDestinationBalance = destinationBalance;
395
- // let initContractBalance = contractBalance;
396
-
397
- // switchUser(owners[1]);
398
- // expect(() => {
399
- // revoke(new Args().add(u64(3)).serialize());
400
- // }).not.toThrow();
401
-
402
- // switchUser(deployerAddress);
403
- // generateEvent(
404
- // createEvent('BALANCES BEFORE', [
405
- // initDestinationBalance.toString(),
406
- // initContractBalance.toString(),
407
- // ]),
408
- // );
409
-
410
- // expect(() => {
411
- // execute(new Args().add(u64(3)).serialize());
412
- // }).toThrow();
413
-
414
- // // the operation should not have been deleted
415
- // let operationList = bytesToSerializableObjectArray<Transaction>(
416
- // getTransactions([]),
417
- // ).unwrap();
418
- // expect(operationList.length).toBe(operationListLenght);
419
-
420
- // // retrieve the operation in its current state in Storage
421
- // let operation = retrieveOperation(i32(3));
422
-
423
- // expect(operation.to).toBe(new Address(destination));
424
- // expect(operation.value).toBe(u64(15000));
425
- // expect(operation.executed).toBe(false);
426
-
427
- // destinationBalance = Coins.balanceOf(destination);
428
- // contractBalance = Coins.balanceOf(contractAddr);
429
- // generateEvent(
430
- // createEvent('BALANCES AFTER', [
431
- // destinationBalance.toString(),
432
- // contractBalance.toString(),
433
- // ]),
434
- // );
435
-
436
- // // check that the transfer has not been done
437
- // expect(destinationBalance).toBe(initDestinationBalance);
438
- // expect(contractBalance).toBe(initContractBalance);
439
- // });
317
+ // operation 2 is validated, let's execute it
318
+ test('execute transaction operation with success', () => {
319
+ // Fund the multisig so transferCoins in execute() has balance to move.
320
+ // The multisig has two validated 15000-coin transactions (op 2 and op 3)
321
+ // but only op 2 will actually be executed in this test, so 15000 is enough.
322
+ mockBalance(contractAddr, u64(15000));
323
+
324
+ const initDestinationBalance = balanceOf(destination);
325
+ const initContractBalance = balanceOf(contractAddr);
326
+
327
+ switchUser(owners[1]);
328
+ execute(new Args().add(u64(2)).serialize());
329
+
330
+ // op 2 is marked as executed
331
+ const transaction = retrieveOperation(i32(2));
332
+ expect(transaction.executed).toBe(true);
333
+
334
+ // the coins were actually transferred from the multisig to the destination
335
+ expect(balanceOf(destination)).toBe(initDestinationBalance + u64(15000));
336
+ expect(balanceOf(contractAddr) + u64(15000)).toBe(initContractBalance);
337
+ });
338
+
339
+ // operation 1 is not validated (only 1/2 approvals), let's try to execute it
340
+ test('execute transaction operation with failure', () => {
341
+ const initDestinationBalance = balanceOf(destination);
342
+ const initContractBalance = balanceOf(contractAddr);
343
+
344
+ switchUser(owners[1]);
345
+
346
+ // execute must revert because op 1 doesn't meet the required threshold
347
+ expect(() => {
348
+ execute(new Args().add(u64(1)).serialize());
349
+ }).toThrow();
350
+
351
+ // the operation is still in storage
352
+ expect(() => {
353
+ retrieveOperation(i32(1));
354
+ }).not.toThrow();
355
+
356
+ // no funds moved
357
+ expect(balanceOf(destination)).toBe(initDestinationBalance);
358
+ expect(balanceOf(contractAddr)).toBe(initContractBalance);
359
+ });
360
+
361
+ // operation 3 is validated by owners[1] & owners[2].
362
+ // now owners[1] will revoke it and we will try to execute it.
363
+ test('revoke operation', () => {
364
+ const initOperationListLength = bytesToSerializableObjectArray<Transaction>(
365
+ getTransactions([]),
366
+ ).unwrap().length;
367
+
368
+ const initDestinationBalance = balanceOf(destination);
369
+ const initContractBalance = balanceOf(contractAddr);
370
+
371
+ // owners[1] revokes his approval on op 3. op 3 now has only 1 approval,
372
+ // which is below the required threshold.
373
+ switchUser(owners[1]);
374
+ revoke(new Args().add(u64(3)).serialize());
375
+
376
+ // the approval threshold is no longer met, so execute must revert.
377
+ expect(() => {
378
+ execute(new Args().add(u64(3)).serialize());
379
+ }).toThrow();
380
+
381
+ // the operation list is untouched (nothing gets deleted)
382
+ const operationList = bytesToSerializableObjectArray<Transaction>(
383
+ getTransactions([]),
384
+ ).unwrap();
385
+ expect(operationList.length).toBe(initOperationListLength);
386
+
387
+ // op 3 is still present in storage, not executed, and the revocation
388
+ // reset its validation timestamp back to 0.
389
+ const operation = retrieveOperation(i32(3));
390
+ expect(operation.to).toBe(new Address(destination));
391
+ expect(operation.value).toBe(u64(15000));
392
+ expect(operation.executed).toBe(false);
393
+ expect(operation.timestamp).toBe(u64(0));
394
+
395
+ // no funds moved
396
+ expect(balanceOf(destination)).toBe(initDestinationBalance);
397
+ expect(balanceOf(contractAddr)).toBe(initContractBalance);
398
+ });
440
399
 
441
400
  test('check operation list', () => {
442
401
  let operationList = bytesToSerializableObjectArray<Transaction>(