Skip to content

Commit

Permalink
feat: Add methods multisig.{asMulti, approveAsMulti, cancelAsMulti} (#52
Browse files Browse the repository at this point in the history
)

* feat: Add methods multisig.{asMulti, approveAsMulti, cancelAsMulti}

* Update docs comments

* Update packages/txwrapper-substrate/src/methods/multisig/types.ts

Co-authored-by: Tarik Gul <[email protected]>

* Update test text

* Apply suggestions from code review

Co-authored-by: David <[email protected]>

* Address half of Davids's feedback'

* Update Timepoint docs

* Improve store call boolean

Co-authored-by: Tarik Gul <[email protected]>
Co-authored-by: David <[email protected]>
  • Loading branch information
3 people authored Feb 15, 2021
1 parent 5ee2d7d commit 6476a74
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 12 deletions.
1 change: 1 addition & 0 deletions packages/txwrapper-polkadot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const methods = {
balances: substrateMethods.balances,
utility: substrateMethods.utility,
proxy: substrateMethods.proxy,
multisig: substrateMethods.multisig,
};

// Re-export all of txwrapper-core so users have access to utilities, construct functions,
Expand Down
1 change: 1 addition & 0 deletions packages/txwrapper-substrate/src/methods/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Name exports to create namespaces that map to pallets
export * as balances from './balances';
export * as democracy from './democracy';
export * as multisig from './multisig';
export * as proxy from './proxy';
export * as staking from './staking';
export * as utility from './utility';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
itHasCorrectBaseTxInfo,
POLKADOT_25_TEST_OPTIONS,
TEST_BASE_TX_INFO,
} from '@substrate/txwrapper-core';

import { TEST_METHOD_ARGS } from '../../test-helpers';
import { approveAsMulti } from './approveAsMulti';

describe('multisig::approveAsMulti', () => {
it('should work', () => {
const unsigned = approveAsMulti(
TEST_METHOD_ARGS.multisig.approveAsMulti,
TEST_BASE_TX_INFO,
POLKADOT_25_TEST_OPTIONS
);

itHasCorrectBaseTxInfo(unsigned);

expect(unsigned.method).toBe(
'0x1e0202000cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22017b000000030000003078303530303330363732313231316435343034626439646138386530323034f6ffffffffff3f01'
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
Args,
BaseTxInfo,
defineMethod,
OptionsWithMeta,
UnsignedTransaction,
} from '@substrate/txwrapper-core';

import { Timepoint } from './types';

export interface MultiSigApproveAsMulti extends Args {
/**
* The total number of approvals required for this dispatch before it is executed.
*/
threshold: number | string;
/**
* The accounts (other than the sender) who can approve this call.
* May not be empty.
*/
otherSignatories: string[];
/**
* If this is the first approval, then this must be `null`. If it is not the first
* approval, then it must be the timepoint (block number and transaction index) of the first
* approving transaction.
*/
maybeTimepoint: null | Timepoint;
/**
* The hash of the call to be executed.
*/
callHash: string;
/**
* Maximium weight the call being approved may consume.
*/
maxWeight: string | number;
}

/**
* Register approval for a dispatch to be made from a deterministic composite account (i.e. multisig
* account) if approved by a total of `threshold - 1` of `other_signatories`.
*
* Payment: `DepositBase` will be reserved if this is the first approval, plus
* `threshold` times `DepositFactor`. It is returned once this dispatch happens or
* is cancelled.
*
* NOTE: If this is the final approval, you must use `as_multi` instead.
*
* @param args - Arguments specific to this method.
* @param info - Information required to construct the transaction.
* @param options - Registry and metadata used for constructing the method.
*/
export function approveAsMulti(
args: MultiSigApproveAsMulti,
info: BaseTxInfo,
options: OptionsWithMeta
): UnsignedTransaction {
return defineMethod(
{
method: {
args,
name: 'approveAsMulti',
pallet: 'multisig',
},
...info,
},
options
);
}
48 changes: 48 additions & 0 deletions packages/txwrapper-substrate/src/methods/multisig/asMulti.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
itHasCorrectBaseTxInfo,
POLKADOT_25_TEST_OPTIONS,
TEST_BASE_TX_INFO,
} from '@substrate/txwrapper-core';

import { TEST_METHOD_ARGS } from '../../test-helpers';
import { transferKeepAlive } from '../balances/transferKeepAlive';
import { asMulti } from './asMulti';

describe('multisig::asMulti', () => {
it('should work', () => {
const unsigned = asMulti(
TEST_METHOD_ARGS.multisig.asMulti,
TEST_BASE_TX_INFO,
POLKADOT_25_TEST_OPTIONS
);

itHasCorrectBaseTxInfo(unsigned);

expect(unsigned.method).toBe(
'0x1e0102000cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22017b00000003000000a80500306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc200f00a0be1c44839900f6ffffffffff3f01'
);
});

it('should accept another txwrapper method as a `call`', () => {
const unsignedBalancesTransferKeepAlive = transferKeepAlive(
TEST_METHOD_ARGS.balances.transferKeepAlive,
TEST_BASE_TX_INFO,
POLKADOT_25_TEST_OPTIONS
);

const unsignedAsMulti = asMulti(
{
...TEST_METHOD_ARGS.multisig.asMulti,
call: unsignedBalancesTransferKeepAlive.method,
},
TEST_BASE_TX_INFO,
POLKADOT_25_TEST_OPTIONS
);

itHasCorrectBaseTxInfo(unsignedAsMulti);

expect(unsignedAsMulti.method).toBe(
'0x1e0102000cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22017b000000030000008c060396074594cccf1cd185fa8a72ceaeefd86648f8d45514f3ce33c31bdd07e4655d3000f6ffffffffff3f01'
);
});
});
62 changes: 62 additions & 0 deletions packages/txwrapper-substrate/src/methods/multisig/asMulti.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
BaseTxInfo,
defineMethod,
OptionsWithMeta,
UnsignedTransaction,
} from '@substrate/txwrapper-core';

import { MultiSigApproveAsMulti } from './approveAsMulti';

export interface MultisigAsMulti
extends Omit<MultiSigApproveAsMulti, 'callHash'> {
/**
* The call to be executed as a SCALE encoded hex string.
*/
call: string;
/**
* Wether or not to store the call in the pallet storage item `Calls`. Storing the call
* is normally only useful if this is the first approval, threshold > 1, and you
* want the call stored on chain so others can see. The call will always be
* removed from storage once the call is executed.
*/
storeCall: boolean;
}

/**
* Register approval for a dispatch to be made from a deterministic composite account if
* approved by a total of `threshold - 1` of `other_signatories`.
*
* If there are enough, then dispatch the call.
*
* Payment: `DepositBase` will be reserved if this is the first approval, plus
* `threshold` times `DepositFactor`. It is returned once this dispatch happens or
* is cancelled.
*
* NOTE: Unless this is the final approval, you will generally want to use
* `approve_as_multi` instead, since it only requires a hash of the call.
*
* Result is equivalent to the dispatched result if `threshold` is exactly `1`. Otherwise
* on success, result is `Ok` and the result from the interior call, if it was executed,
* may be found in the deposited `MultisigExecuted` event.
*
* @param args - Arguments specific to this method.
* @param info - Information required to construct the transaction.
* @param options - Registry and metadata used for constructing the method.
*/
export function asMulti(
args: MultisigAsMulti,
info: BaseTxInfo,
options: OptionsWithMeta
): UnsignedTransaction {
return defineMethod(
{
method: {
args,
name: 'asMulti',
pallet: 'multisig',
},
...info,
},
options
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
itHasCorrectBaseTxInfo,
POLKADOT_25_TEST_OPTIONS,
TEST_BASE_TX_INFO,
} from '@substrate/txwrapper-core';

import { TEST_METHOD_ARGS } from '../../test-helpers';
import { cancelAsMulti } from './cancelAsMulti';

describe('multisig::cancelAsMulti', () => {
it('should work', () => {
const unsigned = cancelAsMulti(
TEST_METHOD_ARGS.multisig.cancelAsMulti,
TEST_BASE_TX_INFO,
POLKADOT_25_TEST_OPTIONS
);

itHasCorrectBaseTxInfo(unsigned);

expect(unsigned.method).toBe(
'0x1e0302000cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe227b000000030000003078303530303330363732313231316435343034626439646138386530323034'
);
});
});
48 changes: 48 additions & 0 deletions packages/txwrapper-substrate/src/methods/multisig/cancelAsMulti.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
BaseTxInfo,
defineMethod,
OptionsWithMeta,
UnsignedTransaction,
} from '@substrate/txwrapper-core';

import { MultiSigApproveAsMulti } from './approveAsMulti';
import { Timepoint } from './types';

export interface MultisigCancelAsMulti
extends Omit<MultiSigApproveAsMulti, 'maxWeight' | 'maybeTimepoint'> {
/**
* The timepoint (block number and transaction index) of the first approval
* transaction for this dispatch.
*/
timepoint: Timepoint;
/**
* The hash of the call to be executed.
*/
callHash: string;
}

/**
* Cancel a pre-existing, on-going multisig transaction. Any deposit reserved previously
* for this operation will be unreserved on success.
*
* @param args - Arguments specific to this method.
* @param info - Information required to construct the transaction.
* @param options - Registry and metadata used for constructing the method.
*/
export function cancelAsMulti(
args: MultisigCancelAsMulti,
info: BaseTxInfo,
options: OptionsWithMeta
): UnsignedTransaction {
return defineMethod(
{
method: {
args,
name: 'cancelAsMulti',
pallet: 'multisig',
},
...info,
},
options
);
}
3 changes: 3 additions & 0 deletions packages/txwrapper-substrate/src/methods/multisig/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './approveAsMulti';
export * from './asMulti';
export * from './cancelAsMulti';
15 changes: 15 additions & 0 deletions packages/txwrapper-substrate/src/methods/multisig/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* A global extrinsic index, formed as the extrinsic index within a block, together with that
* block's height. This allows a transaction in which a multisig operation of a particular
* multisig account was created to be uniquely identified.
*/
export type Timepoint = {
/**
* The height of the chain at the point in time.
*/
height: number | string;
/**
* The index of the extrinsic in the block it was executed in.
*/
index: number | string;
};
45 changes: 45 additions & 0 deletions packages/txwrapper-substrate/src/test-helpers/TEST_METHOD_ARGS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,49 @@ export const TEST_METHOD_ARGS = {
target: 'Fr4NzY1udSFFLzb2R3qxVQkwz9cZraWkyfH4h3mVVk7BK7P', // seed "//Charlie"
},
},
multisig: {
cancelAsMulti: {
threshold: 2,
otherSignatories: [
'15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5', // seed "//Alice"
'14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3', // seed "//Bob",
'14Gjs1TD93gnwEBfDMHoCgsuf1s2TVKUP6Z1qKmAZnZ8cW5q', // seed "//Charlie"
],
timepoint: {
height: 123,
index: 3,
},
callHash: '0x0500306721211d5404bd9da88e02043',
},
approveAsMulti: {
threshold: 2,
otherSignatories: [
'15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5', // seed "//Alice"
'14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3', // seed "//Bob",
'14Gjs1TD93gnwEBfDMHoCgsuf1s2TVKUP6Z1qKmAZnZ8cW5q', // seed "//Charlie"
],
maybeTimepoint: {
height: 123,
index: 3,
},
callHash: '0x0500306721211d5404bd9da88e02043',
maxWeight: '90071992547409910',
},
asMulti: {
threshold: 2,
otherSignatories: [
'15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5', // seed "//Alice"
'14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3', // seed "//Bob",
'14Gjs1TD93gnwEBfDMHoCgsuf1s2TVKUP6Z1qKmAZnZ8cW5q', // seed "//Charlie"
],
maybeTimepoint: {
height: 123,
index: 3,
},
call:
'0x0500306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc200f00a0be1c448399',
storeCall: false,
maxWeight: '90071992547409910',
},
},
};
Loading

0 comments on commit 6476a74

Please sign in to comment.