Skip to content

Commit

Permalink
feat: 馃幐 get historical multisig proposals
Browse files Browse the repository at this point in the history
  • Loading branch information
sansan committed Apr 30, 2024
1 parent 0733151 commit ea3be3d
Show file tree
Hide file tree
Showing 6 changed files with 559 additions and 1 deletion.
44 changes: 43 additions & 1 deletion src/api/entities/Account/MultiSig/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import BigNumber from 'bignumber.js';

import { UniqueIdentifiers } from '~/api/entities/Account';
import { HistoricalMultiSigProposal } from '~/api/entities/HistoricalMultiSigProposal';
import { MultiSigProposal } from '~/api/entities/MultiSigProposal';
import { Account, Context, Identity, modifyMultiSig, PolymeshError } from '~/internal';
import { multiSigProposalsQuery } from '~/middleware/queries';
import { Query } from '~/middleware/types';
import {
ErrorCode,
ModifyMultiSigParams,
MultiSigDetails,
ProcedureMethod,
ProposalStatus,
ResultSet,
} from '~/types';
import { Ensured } from '~/types/utils';
import {
addressToKey,
identityIdToString,
Expand All @@ -20,7 +25,7 @@ import {
stringToAccountId,
u64ToBigNumber,
} from '~/utils/conversion';
import { createProcedureMethod, optionize } from '~/utils/internal';
import { calculateNextKey, createProcedureMethod, optionize } from '~/utils/internal';

/**
* Represents a MultiSig Account. A MultiSig Account is composed of one or more signing Accounts. In order to submit a transaction, a specific amount of those signing Accounts must approve it first
Expand Down Expand Up @@ -143,6 +148,43 @@ export class MultiSig extends Account {
return proposals.filter((_, index) => statuses[index] === ProposalStatus.Active);
}

/**
* Return all { @link api/entities/HistoricalMultiSigProposal!HistoricalMultiSigProposal } for this MultiSig Account
*/
public async getHistoricalProposals(opts: {
size?: BigNumber;
start?: BigNumber;
}): Promise<ResultSet<HistoricalMultiSigProposal>> {
const {
context: { queryMiddleware },
context,
address,
} = this;
const { size, start } = opts;

const {
data: {
multiSigProposals: { nodes, totalCount },
},
} = await queryMiddleware<Ensured<Query, 'multiSigProposals'>>(
multiSigProposalsQuery(address, size, start)
);

const data = nodes.map(
proposal => new HistoricalMultiSigProposal({ id: proposal.id }, proposal, context)
);

const count = new BigNumber(totalCount);

const next = calculateNextKey(count, data.length, start);

return {
data,
next,
count,
};
}

/**
* Returns the Identity of the MultiSig creator. This Identity can add or remove signers directly without creating a MultiSigProposal first.
*/
Expand Down
54 changes: 54 additions & 0 deletions src/api/entities/Account/__tests__/MultiSig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js';
import { when } from 'jest-when';

import { Account, Context, MultiSig, PolymeshError, PolymeshTransaction } from '~/internal';
import { multiSigProposalsQuery } from '~/middleware/queries';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import {
createMockAccountId,
Expand Down Expand Up @@ -195,6 +196,59 @@ describe('MultiSig class', () => {
});
});

describe('method: getHistoricalProposals', () => {
const mockHistoricalMultisig = {
id: 'someId',
proposalId: 1,
multisigId: address,
approvalCount: 1,
rejectionCount: 1,
creator: {
did: 'somedid',
},
status: 'Pending',
createdBlockId: '1',
updatedBlockId: '1',
datetime: new Date().toISOString(),
};

const multiSigProposalsResponse = {
totalCount: 2,
nodes: [mockHistoricalMultisig],
};

it('should get proposals', async () => {
dsMockUtils.createApolloQueryMock(
multiSigProposalsQuery(address, new BigNumber(1), new BigNumber(0)),
{
multiSigProposals: multiSigProposalsResponse,
}
);

const result = await multiSig.getHistoricalProposals({
size: new BigNumber(1),
start: new BigNumber(0),
});

const { data, next, count } = result;

expect(next).toEqual(new BigNumber(1));
expect(count).toEqual(new BigNumber(2));
expect(data.length).toEqual(1);
expect(data[0].creatorDid).toEqual(mockHistoricalMultisig.creator.did);
});

it('should return an empty array if no proposals are pending', async () => {
dsMockUtils.createQueryMock('multiSig', 'proposals', {
entries: [],
});

const result = await multiSig.getProposals();

expect(result).toEqual([]);
});
});

describe('method: getCreator', () => {
it('should return the Identity of the creator of the MultiSig', async () => {
const expectedDid = 'abc';
Expand Down
177 changes: 177 additions & 0 deletions src/api/entities/HistoricalMultiSigProposal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import BigNumber from 'bignumber.js';

import { Context, Entity } from '~/internal';
import { multiSigProposalVotesQuery } from '~/middleware/queries';
import { Query } from '~/middleware/types';
import { MultiSigProposalVote, SignerType } from '~/types';
import { Ensured } from '~/types/utils';
import { middlewareEventDetailsToEventIdentifier, signerValueToSigner } from '~/utils/conversion';

export type MiddlewareMultiSigProposal = {
id: string;
proposalId: number;
multisigId: string;
approvalCount: number;
rejectionCount: number;
creator?: {
did: string;
};
status: string;
createdBlockId: string;
updatedBlockId: string;
datetime: Date;
};

type MiddlewareMultiSigProposalUniqueIdentifiers = Pick<MiddlewareMultiSigProposal, 'id'>;

export interface HumanReadable {
id: string;
proposalId: number;
multisigId: string;
approvalCount: number;
rejectionCount: number;
creatorDid?: string;
status: string;
createdBlockId: string;
updatedBlockId: string;
datetime: string;
}

enum MultiSigStatusEnum {
Pending = 'Pending',
Success = 'Success',
Failed = 'Failed',
}

/**
* Represents historical MultiSigProposal that no longer exists on chain
*/
export class HistoricalMultiSigProposal extends Entity<
MiddlewareMultiSigProposalUniqueIdentifiers,
HumanReadable
> {
public id: string;
public proposalId: BigNumber;
public multisigId: string;
public approvalCount: BigNumber;
public rejectionCount: BigNumber;
public creatorDid?: string;
public status: MultiSigStatusEnum;
public createdBlockId: string;
public updatedBlockId: string;
public datetime: Date;

/**
* @hidden
*/
public constructor(
identifiers: MiddlewareMultiSigProposalUniqueIdentifiers,
proposal: MiddlewareMultiSigProposal,
context: Context
) {
const { id } = identifiers;

const {
proposalId,
multisigId,
approvalCount,
rejectionCount,
creator,
status,
createdBlockId,
updatedBlockId,
datetime,
} = proposal;
super({ id }, context);

this.id = id;
this.proposalId = new BigNumber(proposalId);
this.multisigId = multisigId;
this.approvalCount = new BigNumber(approvalCount);
this.rejectionCount = new BigNumber(rejectionCount);
this.creatorDid = creator?.did;
this.status = status as MultiSigStatusEnum;
this.createdBlockId = createdBlockId;
this.updatedBlockId = updatedBlockId;
this.datetime = datetime;
}

/**
* @hidden
* Check if a value is of type {@link MiddlewareMultiSigProposalUniqueIdentifiers}
*/
public static override isUniqueIdentifiers(
identifier: unknown
): identifier is MiddlewareMultiSigProposalUniqueIdentifiers {
const { id } = identifier as MiddlewareMultiSigProposalUniqueIdentifiers;

return typeof id === 'string';
}

/**
* @hidden
*/
public exists(): Promise<boolean> {
return Promise.resolve(false);
}

/**
* Determine whether this Entity is the same as another one
*/
public override isEqual(entity: HistoricalMultiSigProposal): boolean {
return entity.id === this.id;
}

/**
* Get the MultiSigProposalVotes associated with this MultiSigProposal
*/
public async getVotes(): Promise<MultiSigProposalVote[]> {
const {
context: { queryMiddleware },
id: proposalId,
context,
} = this;

const {
data: {
multiSigProposalVotes: { nodes: signerVotes },
},
} = await queryMiddleware<Ensured<Query, 'multiSigProposalVotes'>>(
multiSigProposalVotesQuery({
proposalId,
})
);

return signerVotes.map(signerVote => {
const { signer, action, createdBlock, eventIdx } = signerVote;

const { signerType, signerValue } = signer!;
return {
signer: signerValueToSigner(
{ type: signerType as unknown as SignerType, value: signerValue },
context
),
action: action!,
...middlewareEventDetailsToEventIdentifier(createdBlock!, eventIdx),
};
});
}

/**
* Return HistoricalMultiSigProposal static data
*/
public toHuman(): HumanReadable {
return {
id: this.id,
proposalId: this.proposalId.toNumber(),
multisigId: this.multisigId,
approvalCount: this.approvalCount.toNumber(),
rejectionCount: this.rejectionCount.toNumber(),
creatorDid: this.creatorDid,
status: this.status,
createdBlockId: this.createdBlockId,
updatedBlockId: this.updatedBlockId,
datetime: this.datetime.toISOString(),
};
}
}

0 comments on commit ea3be3d

Please sign in to comment.