diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a55913ec..16125141 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- feat: Add support for effective target canister ID in management canister calls. + ### Changed - chore: Removes warning that users found unhelpful, when a message originates from other sources than the identity provider in `AuthClient` during authentication. diff --git a/packages/agent/src/actor.ts b/packages/agent/src/actor.ts index 27833dd3..0bda279d 100644 --- a/packages/agent/src/actor.ts +++ b/packages/agent/src/actor.ts @@ -658,7 +658,7 @@ export type ManagementCanisterRecord = _SERVICE; export function getManagementCanister(config: CallConfig): ActorSubclass { function transform( _methodName: string, - args: Record & { canister_id: string }[], + args: Record & { canister_id: string; target_canister?: unknown }[], ) { if (config.effectiveCanisterId) { return { effectiveCanisterId: Principal.from(config.effectiveCanisterId) }; @@ -668,6 +668,9 @@ export function getManagementCanister(config: CallConfig): ActorSubclass { + const identity = new AnonymousIdentity(); + + // Response generated by calling a locally deployed replica of the management canister, cloned using fetchCloner + const mockResponse = { + headers: [ + ['access-control-allow-origin', '*'], + ['content-length', '761'], + ['content-type', 'application/cbor'], + ['date', 'Tue, 22 Oct 2024 22:19:07 GMT'], + ], + ok: true, + status: 200, + statusText: 'OK', + body: 'd9d9f7a266737461747573677265706c6965646b63657274696669636174655902d7d9d9f7a26474726565830183018204582012dbb02955bd3e2987bbba491230b2bb4a593feb02b5bb2d08f5f861afa9cec28301820458202b60693266aeec370be9f54508af493f4dd740086476054c862fe5af17ab15c183024e726571756573745f73746174757383018301820458204bebdfa0327978bfb109f0e14b35e8d368bb62114628ae547386162e9ee3dad883025820cf1cd57f39dfbb40ca1c816c71407c8b1b2edfb5a632676c7917dc4aa8641c5283018302457265706c7982035901684449444c0a6c0b9cb1fa2568b2ceef2f01c0cff2717d9cbab69c0202ffdb81f7037d8daacd94087de3f9f5d90805e8fc8cec0908b0e4d2970a7d81cfaef40a0984aaa89e0f7d6b038da4879b047ff496e4910b7fffdba5db0e7f6d036c020004017d6d7b6c089cb1fa2568c0cff2717dd7e09b90020680ad988a047dedd9c8c90707f8e287cc0c7ddeebb5a90e7da882acc60f7d6d686b02d7e09b90027fa981ceb7067f6c04c1f8dc83037d83cac6e9057da1d0b8af0a7d8fd0cfd00f7d6e040100011100001945cd0f5904e6ce2e5ac91900fb0102809a9e01010100b9f384b5ff59d4b88c01b9f384b5ff59011100001945cd0f5904e6ce2e5ac91900fb01809a9e0103010104010a80000000001000000101011d9a1e6bf09022ffccbffa69fc8e083bb02d5079d48b3640c086b9bfb10280a0e5b9c291010000000000000000aab36e0120fe9be8c73192343439c983d14da2f37a593269285d310fd2c11d4970e5f16eb6008302467374617475738203477265706c69656482045820daeffcc5dadc3aca94e0dc470e429ad4e3bc08517b5776f6a71e7e6982883bef8301820458208e6c6a7c4ba444475de4f4cd2d6df9501873d3290693060faf92e6dc528ee08083024474696d65820349a88dd3f9dacbb98018697369676e6174757265583088040a8228ef3f428c61918c5fb356e74b2ab07aa19f960edbb1fdfcbbc115e35f2e6c3f33b5cf4752799619e67e2b22', + now: 1729635546372, + }; + + // Mock the fetch implementation, resolving a pre-calculated response + const mockFetch: jest.Mock = jest.fn(() => { + return Promise.resolve({ + ...mockResponse, + body: fromHex(mockResponse.body), + arrayBuffer: async () => fromHex(mockResponse.body), + }); + }); + + // Mock time so certificates can be accurately decoded + jest.useFakeTimers(); + jest.setSystemTime(mockResponse.now); + + // Pass in rootKey from replica (used because test was written using local replica) + const agent = await HttpAgent.createSync({ + identity, + fetch: mockFetch, + host: 'http://localhost:4943', + rootKey: fromHex( + '308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c050302010361008be882f1985cccb53fd551571a42818014835ed8f8a27767669b67dd4a836eb0d62b327e3368a80615b0e4f472c73f7917c036dc9317dcb64b319a1efa43dd7c656225c061de359db6fdf7033ac1bff24c944c145e46ebdce2093680b6209a13', + ), + }); + + // Use management canister call + const management = getManagementCanister({ agent }); + + // Important - override nonce when making request to ensure reproducible result + (Actor.agentOf(management) as HttpAgent).addTransform('update', async args => { + args.body.nonce = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]) as Nonce; + return args; + }); + + const spy = jest.spyOn(Actor.agentOf(management) as HttpAgent, 'call'); + + const target_canister = Principal.from('bkyz2-fmaaa-aaaaa-qaaaq-cai'); + + await management.install_chunked_code({ + arg: new Uint8Array([1, 2, 3]), + wasm_module_hash: new Uint8Array([4, 5, 6]), + mode: { install: null }, + chunk_hashes_list: [], + target_canister, + store_canister: [], + sender_canister_version: [], + }); + + expect(spy).toHaveBeenCalledWith( + Principal.fromHex(''), + expect.objectContaining({ + effectiveCanisterId: target_canister, + }), + ); +}); + /** * Test utility to clone a fetch response for mocking purposes with the agent * @param request - RequestInfo