Skip to content

Commit

Permalink
Merge branch 'main' into feat/ledger-icrc-2
Browse files Browse the repository at this point in the history
  • Loading branch information
peterpeterparker authored Sep 18, 2023
2 parents 92e9338 + 75a3ff4 commit 0c1679d
Show file tree
Hide file tree
Showing 15 changed files with 287 additions and 357 deletions.
2 changes: 1 addition & 1 deletion ADMIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Add an empty test file `index.spec.ts` to make sure the command will not fail be
```javaScript
import * as lib from "./index";

describe("rosetta-client", () => {
describe("my-lib", () => {
it("is implemented", () => {
expect(lib).toEqual({});
});
Expand Down
13 changes: 0 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 1 addition & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"packages/sns",
"packages/cmc",
"packages/ckbtc",
"packages/rosetta-client",
"packages/ic-management"
],
"scripts": {
Expand All @@ -21,7 +20,7 @@
"protoc": "bash ./scripts/update_proto.sh",
"test": "jest",
"test-all": "npm ci && npm run build --workspace=packages/utils && npm run build --workspace=packages/ledger && npm run test --workspaces",
"docs": "node scripts/docs.js && prettier --write packages/nns/README.md packages/sns/README.md packages/cmc/README.md packages/utils/README.md packages/ledger/README.md packages/ckbtc/README.md packages/rosetta-client/README.md packages/ic-management/README.md",
"docs": "node scripts/docs.js && prettier --write packages/nns/README.md packages/sns/README.md packages/cmc/README.md packages/utils/README.md packages/ledger/README.md packages/ckbtc/README.md packages/ic-management/README.md",
"build": "npm run build --workspaces",
"size": "size-limit --json",
"update:agent": "./scripts/update-agent"
Expand Down Expand Up @@ -133,12 +132,6 @@
"@dfinity/principal"
]
},
{
"name": "@dfinity/rosetta-client",
"path": "./packages/rosetta-client/dist/index.js",
"limit": "1 kB",
"ignore": []
},
{
"name": "@dfinity/ic-management",
"path": "./packages/ic-management/dist/index.js",
Expand Down
71 changes: 40 additions & 31 deletions packages/nns/README.md

Large diffs are not rendered by default.

199 changes: 166 additions & 33 deletions packages/nns/src/governance.canister.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type Agent,
type RequestId,
} from "@dfinity/agent";
import type { IcrcLedgerCanister } from "@dfinity/ledger";
import {
ManageNeuronResponse as PbManageNeuronResponse,
NeuronId as PbNeuronId,
Expand Down Expand Up @@ -82,7 +83,6 @@ describe("GovernanceCanister", () => {
jest.clearAllMocks();
});

// TODO: Take out tests from "listKnownNeurons" describe
describe("GovernanceCanister.listKnownNeurons", () => {
it("populates all KnownNeuron fields correctly", async () => {
const response: ListKnownNeuronsResponse = {
Expand Down Expand Up @@ -163,7 +163,9 @@ describe("GovernanceCanister", () => {

expect(res.map((n) => Number(n.id))).toEqual([100, 200, 300, 400]);
});
});

describe("GovernanceCanister.stakeNeuron", () => {
it("creates new neuron successfully", async () => {
const neuronId = BigInt(10);
const serviceResponse: ManageNeuronResponse = {
Expand Down Expand Up @@ -288,52 +290,181 @@ describe("GovernanceCanister", () => {
new InsufficientAmountError(BigInt(10_000_000)),
);
});
});

describe("GovernanceCanister.stakeNeuronIcrc1", () => {
it("creates new neuron successfully", async () => {
const neuronId = BigInt(10);
const serviceResponse: ManageNeuronResponse = {
command: [
{ ClaimOrRefresh: { refreshed_neuron_id: [{ id: neuronId }] } },
],
};
const service = mock<ActorSubclass<GovernanceService>>();
service.manage_neuron.mockResolvedValue(serviceResponse);

describe("listNeurons", () => {
it("list user neurons", async () => {
const service = mock<ActorSubclass<GovernanceService>>();
service.list_neurons.mockResolvedValue(mockListNeuronsResponse);
const mockLedger = mock<IcrcLedgerCanister>();
mockLedger.transfer.mockImplementation(
jest.fn().mockResolvedValue(BigInt(1)),
);

const governance = GovernanceCanister.create({
certifiedServiceOverride: service,
serviceOverride: service,
});
const neurons = await governance.listNeurons({
certified: true,
});
expect(service.list_neurons).toBeCalled();
expect(neurons.length).toBe(1);
const governance = GovernanceCanister.create({
certifiedServiceOverride: service,
});
const response = await governance.stakeNeuronIcrc1({
stake: BigInt(100_000_000),
principal: new AnonymousIdentity().getPrincipal(),
icrcLedgerCanister: mockLedger,
});

it("list Hardware Wallet neurons", async () => {
const agent = mock<Agent>();
agent.call.mockResolvedValue(agentCallSuccessfulResponse);
expect(mockLedger.transfer).toBeCalled();
expect(service.manage_neuron).toBeCalled();
expect(response).toEqual(neuronId);
});

const governance = GovernanceCanister.create({
agent,
hardwareWallet: true,
});
await governance.listNeurons({ certified: true });
expect(agent.call).toBeCalled();
expect(spyPollForResponse).toBeCalled();
it("stakeNeuron passes fee to the ledger transfer", async () => {
const neuronId = BigInt(10);
const serviceResponse: ManageNeuronResponse = {
command: [
{ ClaimOrRefresh: { refreshed_neuron_id: [{ id: neuronId }] } },
],
};
const service = mock<ActorSubclass<GovernanceService>>();
service.manage_neuron.mockResolvedValue(serviceResponse);

const mockLedger = mock<IcrcLedgerCanister>();
mockLedger.transfer.mockImplementation(
jest.fn().mockResolvedValue(BigInt(1)),
);
const fee = BigInt(10_000);

const governance = GovernanceCanister.create({
certifiedServiceOverride: service,
});
const response = await governance.stakeNeuronIcrc1({
stake: BigInt(100_000_000),
principal: new AnonymousIdentity().getPrincipal(),
icrcLedgerCanister: mockLedger,
fee,
});

expect(mockLedger.transfer).toBeCalledWith(
expect.objectContaining({ fee }),
);
});

it("creates new neuron from subaccount successfully", async () => {
const neuronId = BigInt(10);
const serviceResponse: ManageNeuronResponse = {
command: [
{ ClaimOrRefresh: { refreshed_neuron_id: [{ id: neuronId }] } },
],
};
const service = mock<ActorSubclass<GovernanceService>>();
service.manage_neuron.mockResolvedValue(serviceResponse);

const mockLedger = mock<IcrcLedgerCanister>();
mockLedger.transfer.mockImplementation(
jest.fn().mockResolvedValue(BigInt(1)),
);

const governance = GovernanceCanister.create({
certifiedServiceOverride: service,
});
const response = await governance.stakeNeuronIcrc1({
stake: BigInt(100_000_000),
principal: new AnonymousIdentity().getPrincipal(),
icrcLedgerCanister: mockLedger,
fromSubAccount: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1,
],
});

expect(mockLedger.transfer).toBeCalled();
expect(service.manage_neuron).toBeCalled();
expect(response).toEqual(neuronId);
});

it("returns insufficient amount errors", async () => {
const neuronId = BigInt(1);
const clainNeuronResponse: ClaimOrRefreshNeuronFromAccountResponse = {
result: [{ NeuronId: { id: neuronId } }],
};
const service = mock<ActorSubclass<GovernanceService>>();
service.claim_or_refresh_neuron_from_account.mockResolvedValue(
clainNeuronResponse,
);

it("should not support query neurons with hardware wallet (only update calls)", async () => {
const agent = mock<Agent>();
agent.call.mockResolvedValue(agentCallSuccessfulResponse);
const mockLedger = mock<IcrcLedgerCanister>();
mockLedger.transfer.mockImplementation(jest.fn());

const governance = GovernanceCanister.create({
certifiedServiceOverride: service,
});

const governance = GovernanceCanister.create({
agent,
hardwareWallet: true,
const call = async () =>
await governance.stakeNeuronIcrc1({
stake: BigInt(10_000_000),
principal: new AnonymousIdentity().getPrincipal(),
icrcLedgerCanister: mockLedger,
});

const call = async () =>
await governance.listNeurons({ certified: false });
expect(mockLedger.transfer).not.toBeCalled();
expect(service.claim_or_refresh_neuron_from_account).not.toBeCalled();

await expect(call).rejects.toThrow(new FeatureNotSupportedError());
await expect(call).rejects.toThrow(
new InsufficientAmountError(BigInt(10_000_000)),
);
});
});

describe("GovernanceCanister.listNeurons", () => {
it("list user neurons", async () => {
const service = mock<ActorSubclass<GovernanceService>>();
service.list_neurons.mockResolvedValue(mockListNeuronsResponse);

const governance = GovernanceCanister.create({
certifiedServiceOverride: service,
serviceOverride: service,
});
const neurons = await governance.listNeurons({
certified: true,
});
expect(service.list_neurons).toBeCalled();
expect(neurons.length).toBe(1);
});

it("list Hardware Wallet neurons", async () => {
const agent = mock<Agent>();
agent.call.mockResolvedValue(agentCallSuccessfulResponse);

const governance = GovernanceCanister.create({
agent,
hardwareWallet: true,
});
await governance.listNeurons({ certified: true });
expect(agent.call).toBeCalled();
expect(spyPollForResponse).toBeCalled();
});

it("should not support query neurons with hardware wallet (only update calls)", async () => {
const agent = mock<Agent>();
agent.call.mockResolvedValue(agentCallSuccessfulResponse);

const governance = GovernanceCanister.create({
agent,
hardwareWallet: true,
});

const call = async () =>
await governance.listNeurons({ certified: false });

await expect(call).rejects.toThrow(new FeatureNotSupportedError());
});
});

describe("GovernanceCanister.registerVote", () => {
it("registers vote successfully", async () => {
const serviceResponse: ManageNeuronResponse = {
command: [{ RegisterVote: {} }],
Expand Down Expand Up @@ -392,7 +523,9 @@ describe("GovernanceCanister", () => {
new GovernanceError(unexpectedGovernanceError),
);
});
});

describe("GovernanceCanister.getNeuron", () => {
it("should fetch and convert a neuron", async () => {
const service = mock<ActorSubclass<GovernanceService>>();
const governance = GovernanceCanister.create({
Expand Down
Loading

0 comments on commit 0c1679d

Please sign in to comment.