Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sns disburse maturity function #395

Merged
merged 12 commits into from
Aug 9, 2023
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
{
"name": "@dfinity/sns",
"path": "./packages/sns/dist/index.js",
"limit": "14 kB",
"limit": "16 kB",
"ignore": [
"@dfinity/agent",
"@dfinity/candid",
Expand Down
160 changes: 93 additions & 67 deletions packages/sns/README.md

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions packages/sns/src/converters/governance.converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type {
SnsIncreaseDissolveDelayParams,
SnsListProposalsParams,
SnsNeuronAutoStakeMaturityParams,
SnsNeuronDisburseMaturityParams,
SnsNeuronPermissionsParams,
SnsNeuronStakeMaturityParams,
SnsRegisterVoteParams,
Expand Down Expand Up @@ -178,6 +179,23 @@ export const toStakeMaturityRequest = ({
},
});

export const toDisburseMaturityRequest = ({
neuronId,
percentageToDisburse,
toAccount,
}: SnsNeuronDisburseMaturityParams): ManageNeuron =>
toManageNeuronCommand({
neuronId,
command: {
DisburseMaturity: {
// currently there is a main account only support
to_account:
toAccount === undefined ? [] : toNullable(toCandidAccount(toAccount)),
percentage_to_disburse: percentageToDisburse,
},
},
});

export const toAutoStakeMaturityNeuronRequest = ({
neuronId,
autoStake: requested_setting_for_auto_stake_maturity,
Expand Down
76 changes: 76 additions & 0 deletions packages/sns/src/governance.canister.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
import { rootCanisterIdMock } from "./mocks/sns.mock";
import {
SnsDisburseNeuronParams,
SnsNeuronDisburseMaturityParams,
SnsRegisterVoteParams,
SnsSplitNeuronParams,
} from "./types/governance.params";
Expand Down Expand Up @@ -1136,6 +1137,81 @@ describe("Governance canister", () => {
});
});

describe("disburseMaturity", () => {
const toAccount = {
owner: Principal.fromText("aaaaa-aa"),
subaccount: arrayOfNumberToUint8Array([0, 0, 1]),
};
const params: SnsNeuronDisburseMaturityParams = {
neuronId: {
id: arrayOfNumberToUint8Array([1, 2, 3]),
},
percentageToDisburse: 50,
toAccount,
};

it("should disburse maturity of the neuron", async () => {
const request: ManageNeuron = {
subaccount: params.neuronId.id,
command: [
{
DisburseMaturity: {
to_account: [toCandidAccount(toAccount)],
percentage_to_disburse: params.percentageToDisburse,
},
},
],
};

const service = mock<ActorSubclass<SnsGovernanceService>>();
service.manage_neuron.mockResolvedValue({
command: [{ DisburseMaturity: { amount_disbursed_e8s: BigInt(0) } }],
});

const canister = SnsGovernanceCanister.create({
canisterId: rootCanisterIdMock,
certifiedServiceOverride: service,
});

await canister.disburseMaturity(params);

expect(service.manage_neuron).toBeCalled();
expect(service.manage_neuron).toBeCalledWith(request);
});

it("throws error if percentage not valid", () => {
const service = mock<ActorSubclass<SnsGovernanceService>>();

const canister = SnsGovernanceCanister.create({
canisterId: rootCanisterIdMock,
certifiedServiceOverride: service,
});

const call = () =>
canister.disburseMaturity({
...params,
percentageToDisburse: 500,
});

expect(call).rejects.toThrow(InvalidPercentageError);
expect(service.manage_neuron).not.toBeCalled();
});

it("should raise an error", async () => {
const service = mock<ActorSubclass<SnsGovernanceService>>();
service.manage_neuron.mockResolvedValue(mockErrorCommand);

const canister = SnsGovernanceCanister.create({
canisterId: rootCanisterIdMock,
certifiedServiceOverride: service,
});
const call = () => canister.disburseMaturity(params);

expect(call).rejects.toThrowError(SnsGovernanceError);
expect(service.manage_neuron).toBeCalled();
});
});

describe("autoStakeMaturity", () => {
const testAutoStakeMaturitySuccess = async (
requested_setting_for_auto_stake_maturity: boolean,
Expand Down
19 changes: 19 additions & 0 deletions packages/sns/src/governance.canister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
toAddPermissionsRequest,
toAutoStakeMaturityNeuronRequest,
toClaimOrRefreshRequest,
toDisburseMaturityRequest,
toDisburseNeuronRequest,
toFollowRequest,
toIncreaseDissolveDelayRequest,
Expand All @@ -49,6 +50,7 @@ import type {
SnsListNeuronsParams,
SnsListProposalsParams,
SnsNeuronAutoStakeMaturityParams,
SnsNeuronDisburseMaturityParams,
SnsNeuronPermissionsParams,
SnsNeuronStakeMaturityParams,
SnsRegisterVoteParams,
Expand Down Expand Up @@ -289,6 +291,23 @@ export class SnsGovernanceCanister extends Canister<SnsGovernanceService> {
await this.manageNeuron(request);
};

/**
* Disburse the maturity of a neuron.
*
* @param {neuronId: NeuronId; toAccount?: IcrcAccount; percentageToDisburse: number; } params
* @param {IcrcAccount} toAccount. Account to disburse maturity.
* @param {NeuronId} neuronId The id of the neuron for which to disburse the maturity
* @param {number} percentageToDisburse What percentage of the available maturity to disburse.
*/
disburseMaturity = async (
params: SnsNeuronDisburseMaturityParams,
): Promise<void> => {
assertPercentageNumber(params.percentageToDisburse);

const request: ManageNeuron = toDisburseMaturityRequest(params);
await this.manageNeuron(request);
};

/**
* Changes auto-stake maturity for a Neuron.
*
Expand Down
21 changes: 21 additions & 0 deletions packages/sns/src/sns.wrapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,27 @@ describe("SnsWrapper", () => {
});
});

it("should call disburseMaturity", async () => {
const neuronId = {
id: arrayOfNumberToUint8Array([1, 2, 3]),
};
const toAccount = {
owner: Principal.fromText("aaaaa-aa"),
subaccount: arrayOfNumberToUint8Array([0, 0, 1]),
};
const percentageToDisburse = 50;
await snsWrapper.disburseMaturity({
neuronId,
percentageToDisburse,
toAccount,
});
expect(mockGovernanceCanister.disburseMaturity).toHaveBeenCalledWith({
neuronId,
percentageToDisburse,
toAccount,
});
});

it("should call autoStakeMaturity", async () => {
const neuronId = {
id: arrayOfNumberToUint8Array([1, 2, 3]),
Expand Down
5 changes: 5 additions & 0 deletions packages/sns/src/sns.wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import type {
SnsListNeuronsParams,
SnsListProposalsParams,
SnsNeuronAutoStakeMaturityParams,
SnsNeuronDisburseMaturityParams,
SnsNeuronPermissionsParams,
SnsNeuronStakeMaturityParams,
SnsRegisterVoteParams,
Expand Down Expand Up @@ -437,6 +438,10 @@ export class SnsWrapper {
stakeMaturity = (params: SnsNeuronStakeMaturityParams): Promise<void> =>
this.governance.stakeMaturity(params);

// Always certified
disburseMaturity = (params: SnsNeuronDisburseMaturityParams): Promise<void> =>
this.governance.disburseMaturity(params);

// Always certified
autoStakeMaturity = (
params: SnsNeuronAutoStakeMaturityParams,
Expand Down
9 changes: 9 additions & 0 deletions packages/sns/src/types/governance.params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ export interface SnsNeuronStakeMaturityParams
percentageToStake?: number;
}

/**
* The parameters to disburse maturity of a neuron
*/
export interface SnsNeuronDisburseMaturityParams
extends SnsNeuronManagementParams {
toAccount?: IcrcAccount;
percentageToDisburse: number;
}

/**
* The parameters to toggle auto stake maturity of a neuron
*/
Expand Down
Loading