Skip to content

Commit

Permalink
test: 💍 add REST API multi sig tests
Browse files Browse the repository at this point in the history
  • Loading branch information
polymath-eric committed Aug 22, 2024
1 parent 4f820b8 commit 6ad2a99
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 1 deletion.
2 changes: 1 addition & 1 deletion envs/6.3.0.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ CHAIN_IMAGE=polymeshassociation/polymesh:6.3.0-staging-debian
SUBQUERY_INDEXER_IMAGE=polymeshassociation/polymesh-subquery:v15.1.0-alpha.2

SUBQUERY_QUERY_IMAGE=onfinality/subql-query:v2.11.0
REST_IMAGE=polymeshassociation/polymesh-rest-api:v5.5.0-alpha.1
REST_IMAGE=polymeshassociation/polymesh-rest-api:v5.5.0-alpha.6
148 changes: 148 additions & 0 deletions src/__tests__/rest/accountManagement/multiSigs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { BigNumber } from '@polymeshassociation/polymesh-sdk';

import { assertTagPresent, assertTagsPresent } from '~/assertions';
import { TestFactory } from '~/helpers';
import { RestClient } from '~/rest';
import { ProcessMode } from '~/rest/common';
import { Identity } from '~/rest/identities/interfaces';
import { joinCreatorParams } from '~/rest/multiSig';
import { portfolioParams } from '~/rest/portfolios';
import { VaultKey } from '~/vault';

const handles = ['creator'];
const signerOne = 'signerOne';
const signerTwo = 'signerTwo';

let factory: TestFactory;

describe('MultiSig', () => {
let restClient: RestClient;
let signer: string;
let creator: Identity;
let keyOne: VaultKey;
let keyTwo: VaultKey;

let multiSigAddress: string;
let proposalId: string;

beforeAll(async () => {
factory = await TestFactory.create({ handles });
({ restClient } = factory);
creator = factory.getSignerIdentity(handles[0]);

const signerOneKey = factory.prefixNonce(signerOne);
const signerTwoKey = factory.prefixNonce(signerTwo);

[keyOne, keyTwo] = await Promise.all([
factory.vaultClient.createKey(signerOneKey),
factory.vaultClient.createKey(signerTwoKey),
]);

signer = creator.signer;
});

afterAll(async () => {
await factory.close();
});

it('should create a multiSig', async () => {
const requiredSignatures = new BigNumber(2);
const result = await restClient.multiSig.create(
requiredSignatures,
[keyOne.address, keyTwo.address],
{
options: { processMode: ProcessMode.Submit, signer },
}
);

expect(result).toEqual(assertTagPresent(expect, 'multiSig.createMultisig'));

expect(result.multiSigAddress).toBeDefined();

multiSigAddress = result.multiSigAddress;
});

it('should join the multiSig to the creator', async () => {
const params = joinCreatorParams({ options: { processMode: ProcessMode.Submit, signer } });

const result = await restClient.multiSig.joinCreator(multiSigAddress, params);

expect(result).toEqual(assertTagsPresent(expect, 'multiSig.makeMultisigSecondary'));
});

it('should allow signers to join the MultiSig', async () => {
const [accountOne, accountTwo] = await Promise.all([
factory.polymeshSdk.accountManagement.getAccount({ address: keyOne.address }),
factory.polymeshSdk.accountManagement.getAccount({ address: keyTwo.address }),
]);

const [[accountOneAuth], [accountTwoAuth]] = await Promise.all([
accountOne.authorizations.getReceived(),
accountTwo.authorizations.getReceived(),
]);

const [resultOne, resultTwo] = await Promise.all([
restClient.identities.acceptAuthorization(accountOneAuth.authId.toString(), {
options: { processMode: ProcessMode.Submit, signer: keyOne.signer },
}),

restClient.identities.acceptAuthorization(accountTwoAuth.authId.toString(), {
options: { processMode: ProcessMode.Submit, signer: keyTwo.signer },
}),
]);

expect(resultOne).toEqual(assertTagPresent(expect, 'multiSig.acceptMultisigSignerAsKey'));
expect(resultTwo).toEqual(assertTagPresent(expect, 'multiSig.acceptMultisigSignerAsKey'));
});

it('should allow a multiSig signer to submit a proposal', async () => {
const params = portfolioParams('multiSigPortfolio', {
options: { processMode: ProcessMode.Submit, signer: keyOne.signer },
});

const result = await restClient.portfolios.createPortfolio(params);

expect(result.proposal).toBeDefined();
expect(result.proposal?.multiSigAddress).toEqual(multiSigAddress);
expect(result.proposal?.id).toBeDefined();

proposalId = result.proposal?.id as string;
});

it('should fetch proposal details', async () => {
const proposalDetails = await restClient.multiSig.getProposalDetails(
multiSigAddress,
proposalId
);

expect(proposalDetails).toBeDefined();
});

it('should allow a signer to approve a proposal', async () => {
const result = await restClient.multiSig.approveProposal(multiSigAddress, proposalId, {
options: { processMode: ProcessMode.Submit, signer: keyTwo.signer },
});

expect(result).toEqual(assertTagPresent(expect, 'multiSig.approveAsKey'));
});

it('should allow a signer to reject a proposal', async () => {
const proposalParams = portfolioParams('rejectPortfolio', {
options: { processMode: ProcessMode.Submit, signer: keyOne.signer },
});

const proposalResult = await restClient.portfolios.createPortfolio(proposalParams);

expect(proposalResult.proposal).toBeDefined();

const result = await restClient.multiSig.rejectProposal(
multiSigAddress,
proposalResult.proposal?.id as string,
{
options: { processMode: ProcessMode.Submit, signer: keyTwo.signer },
}
);

expect(result).toEqual(assertTagPresent(expect, 'multiSig.rejectAsKey'));
});
});
10 changes: 10 additions & 0 deletions src/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@ export const assertTagPresent = (expect: jest.Expect, tag: string): jest.Expect
]),
});
};

export const assertTagsPresent = (expect: jest.Expect, tag: string): jest.Expect => {
return expect.objectContaining({
transactions: expect.arrayContaining([
expect.objectContaining({
transactionTags: expect.arrayContaining([tag]),
}),
]),
});
};
6 changes: 6 additions & 0 deletions src/rest/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Claims } from '~/rest/claims/client';
import { TxBase } from '~/rest/common';
import { Compliance } from '~/rest/compliance';
import { Identities } from '~/rest/identities';
import { MultiSig } from '~/rest/multiSig';
import { Network } from '~/rest/network';
import { Nfts } from '~/rest/nfts';
import { Portfolios } from '~/rest/portfolios';
import { Settlements } from '~/rest/settlements';
Expand All @@ -21,6 +23,8 @@ export class RestClient {
public tickerReservations: TickerReservations;
public portfolios: Portfolios;
public claims: Claims;
public multiSig: MultiSig;
public network: Network;

constructor(public baseUrl: string) {
this.assets = new Assets(this);
Expand All @@ -32,6 +36,8 @@ export class RestClient {
this.tickerReservations = new TickerReservations(this);
this.portfolios = new Portfolios(this);
this.claims = new Claims(this);
this.multiSig = new MultiSig(this);
this.network = new Network(this);
}

public async get<T = unknown>(path: string): Promise<T> {
Expand Down
1 change: 1 addition & 0 deletions src/rest/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface BatchResult {

export interface RestSuccessResult {
transactions: SingleResult[] | BatchResult[];
proposal?: { multiSigAddress: string; id: string };
}

export interface RestErrorResult {
Expand Down
66 changes: 66 additions & 0 deletions src/rest/multiSig/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { BigNumber } from '@polymeshassociation/polymesh-sdk';

import { RestClient } from '~/rest';
import { TxBase } from '~/rest/common';
import { PostResult } from '~/rest/interfaces';
import { joinCreatorParams, modifyMultiSigParams } from '~/rest/multiSig';

export class MultiSig {
constructor(private client: RestClient) {}

public async getProposal(did: string): Promise<unknown> {
return this.client.get(`/identities/${did}/pending-instructions`);
}

public async create(
requiredSignatures: BigNumber,
signers: string[],
params: TxBase
): Promise<{ multiSigAddress: string } & PostResult> {
return this.client.post('/multi-sigs/create', { ...params, requiredSignatures, signers });
}

public async joinCreator(
multiSigAddress: string,
params: ReturnType<typeof joinCreatorParams>
): Promise<PostResult> {
return this.client.post(`/multi-sigs/${multiSigAddress}/join-creator`, params);
}

public async modify(
multiSigAddress: string,
requiredSignatures: BigNumber,
signers: string[],
params: ReturnType<typeof modifyMultiSigParams>
): Promise<PostResult> {
return this.client.post(`/multi-sigs/${multiSigAddress}/modify`, {
...params,
signers,
requiredSignatures,
});
}

public async getProposalDetails(multiSigAddress: string, proposalId: string): Promise<unknown> {
return this.client.get(`/multi-sigs/${multiSigAddress}/proposals/${proposalId}`);
}

public async approveProposal(
multiSigAddress: string,
proposalId: string,
params: TxBase
): Promise<PostResult> {
return this.client.post(`/multi-sigs/${multiSigAddress}/proposals/${proposalId}/approve`, {
...params,
});
}

public async rejectProposal(
multiSigAddress: string,
proposalId: string,
params: TxBase
): Promise<PostResult> {
return this.client.post(`/multi-sigs/${multiSigAddress}/proposals/${proposalId}/reject`, {
...params,
});
}
}
2 changes: 2 additions & 0 deletions src/rest/multiSig/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './client'
export * from './params'
35 changes: 35 additions & 0 deletions src/rest/multiSig/params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { TxBase, TxExtras } from '~/rest/common';

export const createMultiSigParams = (
requiredSignatures: number,
signers: string[],
base: TxBase,
extras: TxExtras = {}
) =>
({
requiredSignatures: requiredSignatures.toString(),
signers,
...extras,
...base,
} as const);

export const joinCreatorParams = (base: TxBase, extras: TxExtras = {}) =>
({
asPrimary: false,
permissions: { transactions: null, portfolios: null, assets: null },
...extras,
...base,
} as const);

export const modifyMultiSigParams = (
requiredSignatures: number,
signers: string[],
base: TxBase,
extras: TxExtras = {}
) =>
({
signers,
requiredSignatures: requiredSignatures.toString(),
...extras,
...base,
} as const);
11 changes: 11 additions & 0 deletions src/rest/network/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { RestClient } from '~/rest/client';
import { PostResult } from '~/rest/interfaces';
import { transferPolyxParams } from '~/rest/network';

export class Network {
constructor(private client: RestClient) {}

public async transferPolyx(params: ReturnType<typeof transferPolyxParams>): Promise<PostResult> {
return this.client.post('/accounts/transfer', params);
}
}
2 changes: 2 additions & 0 deletions src/rest/network/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './client';
export * from './params';
14 changes: 14 additions & 0 deletions src/rest/network/params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { TxBase, TxExtras } from '~/rest/common';

export const transferPolyxParams = (
to: string,
amount: string,
base: TxBase,
extras: TxExtras = {}
) =>
({
to,
amount,
...extras,
...base,
} as const);

0 comments on commit 6ad2a99

Please sign in to comment.