Skip to content

Commit

Permalink
feat: 馃幐 Add procedure to manually execute instruction
Browse files Browse the repository at this point in the history
  • Loading branch information
prashantasdeveloper committed Jan 31, 2023
1 parent 17a824b commit cb12ebc
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/api/entities/Instruction/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import BigNumber from 'bignumber.js';

import { executeManualInstruction } from '~/api/procedures/executeManualInstruction';
import {
Asset,
Context,
Expand Down Expand Up @@ -129,6 +130,14 @@ export class Instruction extends Entity<UniqueIdentifiers, string> {
},
context
);

this.executeManually = createProcedureMethod(
{
getProcedureAndArgs: () => [executeManualInstruction, { id }],
voidArgs: true,
},
context
);
}

/**
Expand Down Expand Up @@ -487,6 +496,11 @@ export class Instruction extends Entity<UniqueIdentifiers, string> {
*/
public reschedule: NoArgsProcedureMethod<Instruction>;

/**
* Executes an Instruction of type `SettleManual`
*/
public executeManually: NoArgsProcedureMethod<Instruction>;

/**
* @hidden
* Retrieve Instruction status event from middleware
Expand Down
174 changes: 174 additions & 0 deletions src/api/procedures/executeManualInstruction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { PolymeshPrimitivesIdentityIdPortfolioId } from '@polkadot/types/lookup';
import BigNumber from 'bignumber.js';
import P from 'bluebird';

import { assertInstructionValidForManualExecution } from '~/api/procedures/utils';
import { Instruction, PolymeshError, Procedure } from '~/internal';
import {
DefaultPortfolio,
ErrorCode,
ExecuteManualInstructionParams,
InstructionDetails,
Leg,
NumberedPortfolio,
TxTags,
} from '~/types';
import { ExtrinsicParams, ProcedureAuthorization, TransactionSpec } from '~/types/internal';
import {
bigNumberToU32,
bigNumberToU64,
portfolioIdToMeshPortfolioId,
portfolioLikeToPortfolioId,
u64ToBigNumber,
} from '~/utils/conversion';

export interface Storage {
portfolios: (DefaultPortfolio | NumberedPortfolio)[];
totalLegAmount: BigNumber;
instructionDetails: InstructionDetails;
signer: string;
}

/**
* @hidden
*/
export async function prepareExecuteManualInstruction(
this: Procedure<ExecuteManualInstructionParams, Instruction, Storage>,
args: ExecuteManualInstructionParams
): Promise<
TransactionSpec<Instruction, ExtrinsicParams<'settlementTx', 'executeManualInstruction'>>
> {
const {
context: {
polymeshApi: {
tx: { settlement: settlementTx },
query: { settlement },
},
},
context,
storage: { portfolios, totalLegAmount, instructionDetails, signer },
} = this;

const { id } = args;

const instruction = new Instruction({ id }, context);

await assertInstructionValidForManualExecution(instructionDetails, context);

if (!portfolios.length) {
const {
owner: { did: ownerDid },
} = await instructionDetails.venue.details();

if (ownerDid !== signer) {
throw new PolymeshError({
code: ErrorCode.UnmetPrerequisite,
message: 'The signing Identity is not involved in this Instruction',
});
}
}

const rawInstructionId = bigNumberToU64(id, context);
const rawPortfolioIds: PolymeshPrimitivesIdentityIdPortfolioId[] = portfolios.map(portfolio =>
portfolioIdToMeshPortfolioId(portfolioLikeToPortfolioId(portfolio), context)
);

const pendingAffirmationsCount = await settlement.instructionAffirmsPending(rawInstructionId);

if (!u64ToBigNumber(pendingAffirmationsCount).isZero()) {
throw new PolymeshError({
code: ErrorCode.UnmetPrerequisite,
message: 'Instruction still requires has some pending affirmations',
data: {
pendingAffirmationsCount,
},
});
}

return {
transaction: settlementTx.executeManualInstruction,
resolver: instruction,
feeMultiplier: totalLegAmount,
args: [
rawInstructionId,
bigNumberToU32(totalLegAmount, context),
rawPortfolioIds.length ? rawPortfolioIds[0] : null,
],
};
}

/**
* @hidden
*/
export async function getAuthorization(
this: Procedure<ExecuteManualInstructionParams, Instruction, Storage>
): Promise<ProcedureAuthorization> {
const {
storage: { portfolios },
} = this;

return {
permissions: {
portfolios,
transactions: [TxTags.settlement.ExecuteManualInstruction],
assets: [],
},
};
}

/**
* @hidden
*/
export async function prepareStorage(
this: Procedure<ExecuteManualInstructionParams, Instruction, Storage>,
{ id }: ExecuteManualInstructionParams
): Promise<Storage> {
const { context } = this;

const instruction = new Instruction({ id }, context);

const [{ data: legs }, { did }, details] = await Promise.all([
instruction.getLegs(),
context.getSigningIdentity(),
instruction.details(),
]);

const portfolios = await P.reduce<Leg, (DefaultPortfolio | NumberedPortfolio)[]>(
legs,
async (custodiedPortfolios, { from, to }) => {
const [fromIsCustodied, toIsCustodied] = await Promise.all([
from.isCustodiedBy({ identity: did }),
to.isCustodiedBy({ identity: did }),
]);

let res = [...custodiedPortfolios];

if (fromIsCustodied) {
res = [...res, from];
}

if (toIsCustodied) {
res = [...res, to];
}

return res;
},
[]
);

return {
portfolios,
totalLegAmount: new BigNumber(legs.length),
instructionDetails: details,
signer: did,
};
}

/**
* @hidden
*/
export const executeManualInstruction = (): Procedure<
ExecuteManualInstructionParams,
Instruction,
Storage
> => new Procedure(prepareExecuteManualInstruction, getAuthorization, prepareStorage);
4 changes: 4 additions & 0 deletions src/api/procedures/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ export interface ModifyInstructionAffirmationParams {
operation: InstructionAffirmationOperation;
}

export interface ExecuteManualInstructionParams {
id: BigNumber;
}

export interface CreateVenueParams {
description: string;
type: VenueType;
Expand Down
38 changes: 38 additions & 0 deletions src/api/procedures/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
GenericAuthorizationData,
InputCondition,
InputTaxWithholding,
InstructionDetails,
InstructionStatus,
InstructionType,
PermissionedAccount,
Expand Down Expand Up @@ -76,6 +77,43 @@ export async function assertInstructionValid(
}
}

/**
* @hidden
*/
export async function assertInstructionValidForManualExecution(
details: InstructionDetails,
context: Context
): Promise<void> {
const { status, type } = details;

if (status === InstructionStatus.Executed) {
throw new PolymeshError({
code: ErrorCode.NoDataChange,
message: 'Instruction has already been executed',
});
}

if (type !== InstructionType.SettleManual) {
throw new PolymeshError({
code: ErrorCode.UnmetPrerequisite,
message: `You cannot execute settlement of type ${type}`,
});
}

const latestBlock = await context.getLatestBlock();
const { endAfterBlock } = details;
if (latestBlock.lte(endAfterBlock)) {
throw new PolymeshError({
code: ErrorCode.UnmetPrerequisite,
message: 'The Instruction cannot be executed until the specified end after block',
data: {
currentBlock: latestBlock,
endAfterBlock,
},
});
}
}

/**
* @hidden
*/
Expand Down
1 change: 1 addition & 0 deletions src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export {
ConsumeJoinOrRotateAuthorizationParams,
} from '~/api/procedures/consumeJoinOrRotateAuthorization';
export { addInstruction } from '~/api/procedures/addInstruction';
export { executeManualInstruction } from '~/api/procedures/executeManualInstruction';
export {
consumeAuthorizationRequests,
ConsumeAuthorizationRequestsParams,
Expand Down

0 comments on commit cb12ebc

Please sign in to comment.