Skip to content

Commit

Permalink
feat: extract ic-mgmt call transform (#774)
Browse files Browse the repository at this point in the history
# Motivation

It’s literally impossible to test the internal `transform` function used
within the static `create` function of the `ic-mgmt` class. However, as
we need to address an issue to support WASM installation with chunks
(#773), we must extend it. That’s why this PR extracts the function into
a module, allowing us to provide tests for the mapping.

# Notes

We delegate the responsibility of calling the `transform` function to
`agent-js`, as it is used internally there and as we are "just"
implementing the interface exposed by agent-js. While it is not
thoroughly covered at the moment in the agent, I submitted a PR
[#954](dfinity/agent-js#954) today that adds at
least one assertion.

No particular entry in CHANGELOG. This is an internal refactoring.

# Changes

- Extract internal `transform` function to a utility.
- JSdocs and tests.
- Improve types of the function (expected interface and return type)

---------

Signed-off-by: David Dal Busco <[email protected]>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
peterpeterparker and github-actions[bot] authored Nov 27, 2024
1 parent 2b06baa commit 41871fa
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 34 deletions.
36 changes: 18 additions & 18 deletions packages/ic-management/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Create a new canister
| ---------------- | ------------------------------------------------------------------------------------- |
| `createCanister` | `({ settings, senderCanisterVersion, }?: CreateCanisterParams) => Promise<Principal>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L82)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L67)

##### :gear: updateSettings

Expand All @@ -104,7 +104,7 @@ Update canister settings
| ---------------- | ------------------------------------------------------------------------------------------- |
| `updateSettings` | `({ canisterId, senderCanisterVersion, settings, }: UpdateSettingsParams) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L105)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L90)

##### :gear: installCode

Expand All @@ -114,7 +114,7 @@ Install code to a canister
| ------------- | -------------------------------------------------------------------------------------------------- |
| `installCode` | `({ canisterId, wasmModule, senderCanisterVersion, ...rest }: InstallCodeParams) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L130)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L115)

##### :gear: uploadChunk

Expand All @@ -129,7 +129,7 @@ Parameters:
- `params.canisterId`: The canister in which the chunks will be stored.
- `params.chunk`: A chunk of Wasm module.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L156)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L141)

##### :gear: clearChunkStore

Expand All @@ -143,7 +143,7 @@ Parameters:

- `params.canisterId`: The canister in which the chunks are stored.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L176)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L161)

##### :gear: storedChunks

Expand All @@ -157,7 +157,7 @@ Parameters:

- `params.canisterId`: The canister in which the chunks are stored.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L195)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L180)

##### :gear: installChunkedCode

Expand All @@ -177,7 +177,7 @@ Parameters:
- `params.storeCanisterId`: Specifies the canister in whose chunk storage the chunks are stored (this parameter defaults to target_canister if not specified).
- `params.wasmModuleHash`: The Wasm module hash as hex string. Used to check that the SHA-256 hash of wasm_module is equal to the wasm_module_hash parameter and can calls install_code with parameters.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L220)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L205)

##### :gear: uninstallCode

Expand All @@ -187,7 +187,7 @@ Uninstall code from a canister
| --------------- | -------------------------------------------------------------------------------- |
| `uninstallCode` | `({ canisterId, senderCanisterVersion, }: UninstallCodeParams) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L251)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L236)

##### :gear: startCanister

Expand All @@ -197,7 +197,7 @@ Start a canister
| --------------- | ------------------------------------------ |
| `startCanister` | `(canisterId: Principal) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L269)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L254)

##### :gear: stopCanister

Expand All @@ -207,7 +207,7 @@ Stop a canister
| -------------- | ------------------------------------------ |
| `stopCanister` | `(canisterId: Principal) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L281)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L266)

##### :gear: canisterStatus

Expand All @@ -217,7 +217,7 @@ Get canister details (memory size, status, etc.)
| ---------------- | ------------------------------------------------------------ |
| `canisterStatus` | `(canisterId: Principal) => Promise<canister_status_result>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L292)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L277)

##### :gear: deleteCanister

Expand All @@ -227,7 +227,7 @@ Deletes a canister
| ---------------- | ------------------------------------------ |
| `deleteCanister` | `(canisterId: Principal) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L306)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L291)

##### :gear: provisionalCreateCanisterWithCycles

Expand All @@ -237,7 +237,7 @@ Creates a canister. Only available on development instances.
| ------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `provisionalCreateCanisterWithCycles` | `({ settings, amount, canisterId, }?: ProvisionalCreateCanisterWithCyclesParams) => Promise<Principal>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L321)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L306)

##### :gear: fetchCanisterLogs

Expand All @@ -247,7 +247,7 @@ Given a canister ID as input, this method returns a vector of logs of that canis
| ------------------- | ---------------------------------------------------------------- |
| `fetchCanisterLogs` | `(canisterId: Principal) => Promise<fetch_canister_logs_result>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L344)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L329)

##### :gear: takeCanisterSnapshot

Expand All @@ -265,7 +265,7 @@ Parameters:
Can be provided as a `string` or a `Uint8Array`.
If not provided, a new snapshot will be created.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L370)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L355)

##### :gear: listCanisterSnapshots

Expand All @@ -280,7 +280,7 @@ Parameters:
- `params`: - Parameters for the listing operation.
- `params.canisterId`: - The ID of the canister for which snapshots will be listed.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L399)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L384)

##### :gear: loadCanisterSnapshot

Expand All @@ -297,7 +297,7 @@ Parameters:
- `params.snapshotId`: - The ID of the snapshot to load.
- `params.senderCanisterVersion`: - The optional sender canister version. If provided, its value must be equal to ic0.canister_version.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L425)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L410)

##### :gear: deleteCanisterSnapshot

Expand All @@ -313,7 +313,7 @@ Parameters:
- `params.canisterId`: - The ID of the canister for which the snapshot will be deleted.
- `params.snapshotId`: - The ID of the snapshot to delete.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L456)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L441)

<!-- TSDOC_END -->

Expand Down
17 changes: 1 addition & 16 deletions packages/ic-management/src/ic-management.canister.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { CallConfig } from "@dfinity/agent";
import { Principal } from "@dfinity/principal";
import {
createServices,
Expand Down Expand Up @@ -34,28 +33,14 @@ import type {
FetchCanisterLogsResponse,
} from "./types/ic-management.responses";
import { mapSnapshotId } from "./utils/ic-management.utils";
import { transform } from "./utils/transform.utils";

export class ICManagementCanister {
private constructor(private readonly service: IcManagementService) {
this.service = service;
}

public static create(options: ICManagementCanisterOptions) {
// Source getManagementCanister in agent-js.
// Allow usage of the ICManagementCanister wrapper locally.
const transform = (
_methodName: string,
args: unknown[],
_callConfig: CallConfig,
) => {
const first = args[0] as { canister_id: string };
let effectiveCanisterId = Principal.fromHex("");
if (first && typeof first === "object" && first.canister_id) {
effectiveCanisterId = Principal.from(first.canister_id as unknown);
}
return { effectiveCanisterId };
};

const { service } = createServices<IcManagementService>({
options: {
...options,
Expand Down
74 changes: 74 additions & 0 deletions packages/ic-management/src/utils/transform.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { CallConfig } from "@dfinity/agent";
import { Principal } from "@dfinity/principal";
import { mockCanisterId } from "../ic-management.mock";
import { transform } from "./transform.utils";

describe("transform", () => {
it("should map the effectiveCanisterId when a valid canister_id is provided as principal in the request", () => {
const methodName = "someMethod";
const args = [{ canister_id: mockCanisterId }];
const callConfig: CallConfig = {};

const result = transform(methodName, args, callConfig);

expect(result).toEqual({
effectiveCanisterId: mockCanisterId,
});
});

it("should map the effectiveCanisterId when a valid canister_id is provided as string in the request", () => {
const methodName = "someMethod";
const args = [{ canister_id: mockCanisterId.toText() }];
const callConfig: CallConfig = {};

const result = transform(methodName, args, callConfig);

expect(result).toEqual({
effectiveCanisterId: mockCanisterId,
});
});

it("should return effectiveCanisterId aaaaa-aa when args is empty", () => {
const methodName = "someMethod";
const args: unknown[] = [];
const callConfig: CallConfig = {};

const result = transform(methodName, args, callConfig);

expect(result).toEqual({
effectiveCanisterId: Principal.fromHex(""),
});
});

it("should return effectiveCanisterId aaaaa-aa when canister_id is missing in the first argument", () => {
const methodName = "someMethod";
const args = [{}];
const callConfig: CallConfig = {};

const result = transform(methodName, args, callConfig);

expect(result).toEqual({
effectiveCanisterId: Principal.fromHex(""),
});
});

it("should return effectiveCanisterId aaaaa-aa when the first argument is not an object", () => {
const methodName = "someMethod";
const args = [42];
const callConfig: CallConfig = {};

const result = transform(methodName, args, callConfig);

expect(result).toEqual({
effectiveCanisterId: Principal.fromHex(""),
});
});

it("should throw an error if canister_id is provided in the request but is not a valid principal or representation", () => {
const methodName = "someMethod";
const args = [{ canister_id: 12345 }];
const callConfig: CallConfig = {};

expect(() => transform(methodName, args, callConfig)).toThrow();
});
});
30 changes: 30 additions & 0 deletions packages/ic-management/src/utils/transform.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { ActorConfig, CallConfig } from "@dfinity/agent";
import { Principal } from "@dfinity/principal";

type CallTransform = Required<ActorConfig>["callTransform"];

type QueryTransform = Required<ActorConfig>["queryTransform"];

/**
* Transformer function for service creation with `callTransform` or `queryTransform`.
*
* This function maps the `effective_canister_id` for calls to the Management Canister (`aaaaa-aa`).
*
* Original source `getManagementCanister` in agent-js.
*
* Providing a transformer is required to determine the effective_canister_id when the request is an update call to the Management Canister (aaaaa-aa).
*
* @link https://internetcomputer.org/docs/current/references/ic-interface-spec/#http-effective-canister-id
**/
export const transform: CallTransform | QueryTransform = (
_methodName: string,
args: unknown[],
_callConfig: CallConfig,
): { effectiveCanisterId: Principal } => {
const first = args[0] as { canister_id: string };
let effectiveCanisterId = Principal.fromHex("");
if (first && typeof first === "object" && first.canister_id) {
effectiveCanisterId = Principal.from(first.canister_id as unknown);
}
return { effectiveCanisterId };
};

0 comments on commit 41871fa

Please sign in to comment.