Skip to content

Commit cd21bd3

Browse files
polymath-ericprashantasdeveloper
authored andcommitted
test: 💍 add REST API multi sig tests
1 parent ec1c188 commit cd21bd3

File tree

12 files changed

+293
-3
lines changed

12 files changed

+293
-3
lines changed

.vscode/settings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88
"custodied",
99
"permissioned",
1010
"PIA's",
11+
"polkadot",
1112
"Polymesh",
1213
"polymeshassociation",
13-
"Polyx"
14+
"Polyx",
15+
"ttsc",
16+
"ttypescript",
17+
"zerollup"
1418
]
1519
}

envs/6.3.0.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ CHAIN_IMAGE=polymeshassociation/polymesh:6.3.0-staging-debian
22
SUBQUERY_INDEXER_IMAGE=polymeshassociation/polymesh-subquery:v15.1.0-alpha.2
33

44
SUBQUERY_QUERY_IMAGE=onfinality/subql-query:v2.11.0
5-
REST_IMAGE=polymeshassociation/polymesh-rest-api:v5.5.0-alpha.1
5+
REST_IMAGE=polymeshassociation/polymesh-rest-api:v5.5.0-alpha.6
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { BigNumber } from '@polymeshassociation/polymesh-sdk';
2+
3+
import { assertTagPresent, assertTagsPresent } from '~/assertions';
4+
import { TestFactory } from '~/helpers';
5+
import { RestClient } from '~/rest';
6+
import { ProcessMode } from '~/rest/common';
7+
import { Identity } from '~/rest/identities/interfaces';
8+
import { joinCreatorParams } from '~/rest/multiSig';
9+
import { portfolioParams } from '~/rest/portfolios';
10+
import { VaultKey } from '~/vault';
11+
12+
const handles = ['creator'];
13+
const signerOne = 'signerOne';
14+
const signerTwo = 'signerTwo';
15+
16+
let factory: TestFactory;
17+
18+
describe('MultiSig', () => {
19+
let restClient: RestClient;
20+
let signer: string;
21+
let creator: Identity;
22+
let keyOne: VaultKey;
23+
let keyTwo: VaultKey;
24+
25+
let multiSigAddress: string;
26+
let proposalId: string;
27+
28+
beforeAll(async () => {
29+
factory = await TestFactory.create({ handles });
30+
({ restClient } = factory);
31+
creator = factory.getSignerIdentity(handles[0]);
32+
33+
const signerOneKey = factory.prefixNonce(signerOne);
34+
const signerTwoKey = factory.prefixNonce(signerTwo);
35+
36+
[keyOne, keyTwo] = await Promise.all([
37+
factory.vaultClient.createKey(signerOneKey),
38+
factory.vaultClient.createKey(signerTwoKey),
39+
]);
40+
41+
signer = creator.signer;
42+
});
43+
44+
afterAll(async () => {
45+
await factory.close();
46+
});
47+
48+
it('should create a multiSig', async () => {
49+
const requiredSignatures = new BigNumber(2);
50+
const result = await restClient.multiSig.create(
51+
requiredSignatures,
52+
[keyOne.address, keyTwo.address],
53+
{
54+
options: { processMode: ProcessMode.Submit, signer },
55+
}
56+
);
57+
58+
expect(result).toEqual(assertTagPresent(expect, 'multiSig.createMultisig'));
59+
60+
expect(result.multiSigAddress).toBeDefined();
61+
62+
multiSigAddress = result.multiSigAddress;
63+
});
64+
65+
it('should join the multiSig to the creator', async () => {
66+
const params = joinCreatorParams({ options: { processMode: ProcessMode.Submit, signer } });
67+
68+
const result = await restClient.multiSig.joinCreator(multiSigAddress, params);
69+
70+
expect(result).toEqual(assertTagsPresent(expect, 'multiSig.makeMultisigSecondary'));
71+
});
72+
73+
it('should allow signers to join the MultiSig', async () => {
74+
const [accountOne, accountTwo] = await Promise.all([
75+
factory.polymeshSdk.accountManagement.getAccount({ address: keyOne.address }),
76+
factory.polymeshSdk.accountManagement.getAccount({ address: keyTwo.address }),
77+
]);
78+
79+
const [[accountOneAuth], [accountTwoAuth]] = await Promise.all([
80+
accountOne.authorizations.getReceived(),
81+
accountTwo.authorizations.getReceived(),
82+
]);
83+
84+
const [resultOne, resultTwo] = await Promise.all([
85+
restClient.identities.acceptAuthorization(accountOneAuth.authId.toString(), {
86+
options: { processMode: ProcessMode.Submit, signer: keyOne.signer },
87+
}),
88+
89+
restClient.identities.acceptAuthorization(accountTwoAuth.authId.toString(), {
90+
options: { processMode: ProcessMode.Submit, signer: keyTwo.signer },
91+
}),
92+
]);
93+
94+
expect(resultOne).toEqual(assertTagPresent(expect, 'multiSig.acceptMultisigSignerAsKey'));
95+
expect(resultTwo).toEqual(assertTagPresent(expect, 'multiSig.acceptMultisigSignerAsKey'));
96+
});
97+
98+
it('should allow a multiSig signer to submit a proposal', async () => {
99+
const params = portfolioParams('multiSigPortfolio', {
100+
options: { processMode: ProcessMode.Submit, signer: keyOne.signer },
101+
});
102+
103+
const result = await restClient.portfolios.createPortfolio(params);
104+
105+
expect(result.proposal).toBeDefined();
106+
expect(result.proposal?.multiSigAddress).toEqual(multiSigAddress);
107+
expect(result.proposal?.id).toBeDefined();
108+
109+
proposalId = result.proposal?.id as string;
110+
});
111+
112+
it('should fetch proposal details', async () => {
113+
const proposalDetails = await restClient.multiSig.getProposalDetails(
114+
multiSigAddress,
115+
proposalId
116+
);
117+
118+
expect(proposalDetails).toBeDefined();
119+
});
120+
121+
it('should allow a signer to approve a proposal', async () => {
122+
const result = await restClient.multiSig.approveProposal(multiSigAddress, proposalId, {
123+
options: { processMode: ProcessMode.Submit, signer: keyTwo.signer },
124+
});
125+
126+
expect(result).toEqual(assertTagPresent(expect, 'multiSig.approveAsKey'));
127+
});
128+
129+
it('should allow a signer to reject a proposal', async () => {
130+
const proposalParams = portfolioParams('rejectPortfolio', {
131+
options: { processMode: ProcessMode.Submit, signer: keyOne.signer },
132+
});
133+
134+
const proposalResult = await restClient.portfolios.createPortfolio(proposalParams);
135+
136+
expect(proposalResult.proposal).toBeDefined();
137+
138+
const result = await restClient.multiSig.rejectProposal(
139+
multiSigAddress,
140+
proposalResult.proposal?.id as string,
141+
{
142+
options: { processMode: ProcessMode.Submit, signer: keyTwo.signer },
143+
}
144+
);
145+
146+
expect(result).toEqual(assertTagPresent(expect, 'multiSig.rejectAsKey'));
147+
});
148+
});

src/assertions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,13 @@ export const assertTagPresent = (expect: jest.Expect, tag: string): jest.Expect
77
]),
88
});
99
};
10+
11+
export const assertTagsPresent = (expect: jest.Expect, tag: string): jest.Expect => {
12+
return expect.objectContaining({
13+
transactions: expect.arrayContaining([
14+
expect.objectContaining({
15+
transactionTags: expect.arrayContaining([tag]),
16+
}),
17+
]),
18+
});
19+
};

src/rest/client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Claims } from '~/rest/claims/client';
55
import { TxBase } from '~/rest/common';
66
import { Compliance } from '~/rest/compliance';
77
import { Identities } from '~/rest/identities';
8+
import { MultiSig } from '~/rest/multiSig';
89
import { Network } from '~/rest/network';
910
import { Nfts } from '~/rest/nfts';
1011
import { Portfolios } from '~/rest/portfolios';
@@ -22,6 +23,7 @@ export class RestClient {
2223
public tickerReservations: TickerReservations;
2324
public portfolios: Portfolios;
2425
public claims: Claims;
26+
public multiSig: MultiSig;
2527
public network: Network;
2628

2729
constructor(public baseUrl: string) {
@@ -34,6 +36,7 @@ export class RestClient {
3436
this.tickerReservations = new TickerReservations(this);
3537
this.portfolios = new Portfolios(this);
3638
this.claims = new Claims(this);
39+
this.multiSig = new MultiSig(this);
3740
this.network = new Network(this);
3841
}
3942

src/rest/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ interface BatchResult {
3838

3939
export type RestSuccessResult = Record<string, unknown> & {
4040
transactions: SingleResult[] | BatchResult[];
41+
proposal?: { multiSigAddress: string; id: string };
4142
};
4243

4344
export interface RestErrorResult {

src/rest/multiSig/client.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { BigNumber } from '@polymeshassociation/polymesh-sdk';
2+
3+
import { RestClient } from '~/rest';
4+
import { TxBase } from '~/rest/common';
5+
import { PostResult } from '~/rest/interfaces';
6+
import { joinCreatorParams, modifyMultiSigParams } from '~/rest/multiSig';
7+
8+
export class MultiSig {
9+
constructor(private client: RestClient) {}
10+
11+
public async getProposal(did: string): Promise<unknown> {
12+
return this.client.get(`/identities/${did}/pending-instructions`);
13+
}
14+
15+
public async create(
16+
requiredSignatures: BigNumber,
17+
signers: string[],
18+
params: TxBase
19+
): Promise<{ multiSigAddress: string } & PostResult> {
20+
return this.client.post('/multi-sigs/create', { ...params, requiredSignatures, signers });
21+
}
22+
23+
public async joinCreator(
24+
multiSigAddress: string,
25+
params: ReturnType<typeof joinCreatorParams>
26+
): Promise<PostResult> {
27+
return this.client.post(`/multi-sigs/${multiSigAddress}/join-creator`, params);
28+
}
29+
30+
public async modify(
31+
multiSigAddress: string,
32+
requiredSignatures: BigNumber,
33+
signers: string[],
34+
params: ReturnType<typeof modifyMultiSigParams>
35+
): Promise<PostResult> {
36+
return this.client.post(`/multi-sigs/${multiSigAddress}/modify`, {
37+
...params,
38+
signers,
39+
requiredSignatures,
40+
});
41+
}
42+
43+
public async getProposalDetails(multiSigAddress: string, proposalId: string): Promise<unknown> {
44+
return this.client.get(`/multi-sigs/${multiSigAddress}/proposals/${proposalId}`);
45+
}
46+
47+
public async approveProposal(
48+
multiSigAddress: string,
49+
proposalId: string,
50+
params: TxBase
51+
): Promise<PostResult> {
52+
return this.client.post(`/multi-sigs/${multiSigAddress}/proposals/${proposalId}/approve`, {
53+
...params,
54+
});
55+
}
56+
57+
public async rejectProposal(
58+
multiSigAddress: string,
59+
proposalId: string,
60+
params: TxBase
61+
): Promise<PostResult> {
62+
return this.client.post(`/multi-sigs/${multiSigAddress}/proposals/${proposalId}/reject`, {
63+
...params,
64+
});
65+
}
66+
}

src/rest/multiSig/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './client'
2+
export * from './params'

src/rest/multiSig/params.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { TxBase, TxExtras } from '~/rest/common';
2+
3+
export const createMultiSigParams = (
4+
requiredSignatures: number,
5+
signers: string[],
6+
base: TxBase,
7+
extras: TxExtras = {}
8+
) =>
9+
({
10+
requiredSignatures: requiredSignatures.toString(),
11+
signers,
12+
...extras,
13+
...base,
14+
} as const);
15+
16+
export const joinCreatorParams = (base: TxBase, extras: TxExtras = {}) =>
17+
({
18+
asPrimary: false,
19+
permissions: { transactions: null, portfolios: null, assets: null },
20+
...extras,
21+
...base,
22+
} as const);
23+
24+
export const modifyMultiSigParams = (
25+
requiredSignatures: number,
26+
signers: string[],
27+
base: TxBase,
28+
extras: TxExtras = {}
29+
) =>
30+
({
31+
signers,
32+
requiredSignatures: requiredSignatures.toString(),
33+
...extras,
34+
...base,
35+
} as const);

src/rest/network/client.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import { RestClient } from '~/rest/client';
2+
import { PostResult } from '~/rest/interfaces';
3+
import { transferPolyxParams } from '~/rest/network';
24

35
export class Network {
4-
constructor(private client: RestClient) {}
6+
constructor(private readonly client: RestClient) {}
57

68
public async getMiddlewareMetadata(): Promise<unknown> {
79
return this.client.get('/network/middleware-metadata');
810
}
11+
12+
public async transferPolyx(params: ReturnType<typeof transferPolyxParams>): Promise<PostResult> {
13+
return this.client.post('/accounts/transfer', params);
14+
}
915
}

src/rest/network/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './client';
2+
export * from './params';

src/rest/network/params.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { TxBase, TxExtras } from '~/rest/common';
2+
3+
export const transferPolyxParams = (
4+
to: string,
5+
amount: string,
6+
base: TxBase,
7+
extras: TxExtras = {}
8+
) =>
9+
({
10+
to,
11+
amount,
12+
...extras,
13+
...base,
14+
} as const);

0 commit comments

Comments
 (0)