Skip to content

Commit

Permalink
Expose memoToNeuronSubaccount and memoToNeuronAccountIdentifier
Browse files Browse the repository at this point in the history
  • Loading branch information
dskloetd committed Nov 1, 2024
1 parent 9168785 commit 84ab7a2
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 38 deletions.
44 changes: 6 additions & 38 deletions packages/nns/src/governance.canister.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import type { ActorSubclass, Agent } from "@dfinity/agent";
import { Actor } from "@dfinity/agent";
import type { LedgerCanister } from "@dfinity/ledger-icp";
import {
AccountIdentifier,
SubAccount,
checkAccountId,
} from "@dfinity/ledger-icp";
import { AccountIdentifier, checkAccountId } from "@dfinity/ledger-icp";
import type { Principal } from "@dfinity/principal";
import {
arrayOfNumberToUint8Array,
asciiStringToByteArray,
assertPercentageNumber,
createServices,
fromNullable,
isNullish,
nonNullish,
uint8ArrayToBigInt,
} from "@dfinity/utils";
import { sha256 } from "@noble/hashes/sha256";
import randomBytes from "randombytes";
import type {
Command_1,
Expand Down Expand Up @@ -90,6 +83,7 @@ import type {
ProposalId,
ProposalInfo,
} from "./types/governance_converters";
import { memoToNeuronAccountIdentifier } from "./utils/neurons.utils";

export class GovernanceCanister {
private constructor(
Expand Down Expand Up @@ -269,10 +263,10 @@ export class GovernanceCanister {

const nonceBytes = new Uint8Array(randomBytes(8));
const nonce = uint8ArrayToBigInt(nonceBytes);
const toSubAccount = this.buildNeuronStakeSubAccount(nonceBytes, principal);
const accountIdentifier = AccountIdentifier.fromPrincipal({
principal: this.canisterId,
subAccount: toSubAccount,
const accountIdentifier = memoToNeuronAccountIdentifier({
controller: principal,
memo: nonce,
governanceCanisterId: this.canisterId,
});

// Send amount to the ledger.
Expand Down Expand Up @@ -870,32 +864,6 @@ export class GovernanceCanister {
);
};

private buildNeuronStakeSubAccount = (
nonce: Uint8Array,
principal: Principal,
): SubAccount => {
return SubAccount.fromBytes(
this.getNeuronStakeSubAccountBytes(nonce, principal),
) as SubAccount;
};

private getNeuronStakeSubAccountBytes = (
nonce: Uint8Array,
principal: Principal,
): Uint8Array => {
const padding = asciiStringToByteArray("neuron-stake");
const shaObj = sha256.create();
shaObj.update(
arrayOfNumberToUint8Array([
0x0c,
...padding,
...principal.toUint8Array(),
...nonce,
]),
);
return shaObj.digest();
};

private getGovernanceService(certified: boolean): GovernanceService {
return certified ? this.certifiedService : this.service;
}
Expand Down
42 changes: 42 additions & 0 deletions packages/nns/src/utils/neurons.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Principal } from "@dfinity/principal";
import { uint8ArrayToHexString } from "@dfinity/utils";
import { Vote } from "../enums/governance.enums";
import { NeuronInfo, ProposalInfo } from "../types/governance_converters";
import {
ineligibleNeurons,
memoToNeuronAccountIdentifier,
memoToNeuronSubaccount,
votableNeurons,
votedNeurons,
} from "./neurons.utils";
Expand Down Expand Up @@ -202,4 +206,42 @@ describe("neurons-utils", () => {
expect(voted).toEqual([eligibleNeuronsData[1], eligibleNeuronsData[2]]);
});
});

describe("memoToNeuronSubaccount", () => {
it("should calculate the neuron subaccount", () => {
// It's not that useful to do the same hashing here so we just use some
// hardcoded values for a "change detector" test.
const controller = Principal.fromText("6czrj-7isge-rrema");
const memo = 123123123n;

expect(
uint8ArrayToHexString(
memoToNeuronSubaccount({ controller, memo }).toUint8Array(),
),
).toEqual(
"21c6b6f1c9be307a956fd950687037203c6ceb3557ae5cc49b52010d4497b2d3",
);
});
});

describe("memoToNeuronAccountIdentifier", () => {
it("should calculate the neuron account identifier", () => {
// It's not that useful to do the same hashing here so we just use some
// hardcoded values for a "change detector" test.
const governanceCanisterId = Principal.fromText(
"rrkah-fqaaa-aaaaa-aaaaq-cai",
);
const controller = Principal.fromText("jl4mq-2sfmr-lekya");
const memo = 456456456n;
expect(
memoToNeuronAccountIdentifier({
controller,
memo,
governanceCanisterId,
}).toHex(),
).toEqual(
"2a8523d6488286c937f25a855cc74e640da7115824eb6c78d89d503a37e6b3d8",
);
});
});
});
45 changes: 45 additions & 0 deletions packages/nns/src/utils/neurons.utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { AccountIdentifier, SubAccount } from "@dfinity/ledger-icp";
import type { Principal } from "@dfinity/principal";
import {
arrayOfNumberToUint8Array,
asciiStringToByteArray,
bigIntToUint8Array,
} from "@dfinity/utils";
import { sha256 } from "@noble/hashes/sha256";
import { Vote } from "../enums/governance.enums";
import type {
Ballot,
Expand Down Expand Up @@ -89,3 +97,40 @@ export const votedNeurons = ({
(neuron: NeuronInfo) =>
getNeuronVoteForProposal({ proposal, neuron }) !== Vote.Unspecified,
);

export const memoToNeuronSubaccount = ({
controller,
memo,
}: {
controller: Principal;
memo: bigint;
}): SubAccount => {
const padding = asciiStringToByteArray("neuron-stake");
const shaObj = sha256.create();
shaObj.update(
arrayOfNumberToUint8Array([
padding.length,
...padding,
...controller.toUint8Array(),
...bigIntToUint8Array(memo),
]),
);

return SubAccount.fromBytes(shaObj.digest()) as SubAccount;
};

export const memoToNeuronAccountIdentifier = ({
controller,
memo,
governanceCanisterId,
}: {
controller: Principal;
memo: bigint;
governanceCanisterId: Principal;
}): AccountIdentifier => {
const subAccount = memoToNeuronSubaccount({ controller, memo });
return AccountIdentifier.fromPrincipal({
principal: governanceCanisterId,
subAccount,
});
};

0 comments on commit 84ab7a2

Please sign in to comment.