Directly mutate stable memory of an external canister

Hello!

I’m having a following use case:

  • there’re 2 canisters: root and child
  • root is a controller of child
  • I do not own source code of the child canister but I do have a good idea about it’s stable memory layout
  1. Is there -anything- that can be done in order to directly mutate the stable memory of child canister from the root?(child doesn’t have a public update call to mutate needed stable structure)
  2. Can child canister be somehow proxified with LOW cost(I could redeploy, say by directly embedding it’s binary WASM code inside a proxy canister with the purpose of modifying a call of a single method but then… there’s no way to carry out the stable memory with the WASM heh)?
  3. Can root always get an up-to-date binary WASM code of the child and if so, how?

I welcome any ideas obviously

  1. No. It would be a gigantic foot-gun, so I don’t think this would ever be added to the spec. A canister is responsible for it’s memory layout and should not be required to account for random interference.
  2. I don’t quite understand what you’re trying to do
  3. No. You can’t fetch the running code. If you install new WASM via root (basically just let it proxy the install_code call), then it could always keep a copy around for later use

Overall it sounds like you’re working on a too low level. What is the problem you are trying to solve?

2 Likes

let’s assume

  • there’s a generic controller canister that controls multiple icrc1 ledgers
  • those ledgers are being passed by 3rd parties by changing the controller(permissionless)
  • minting account can point to an arbitrary principal ID

I wanna simplify this migration process and implement a generic way to reset the minting account of a running icrc1 ledger. Aka if someone changes the controller the minting account would be ‘fixed’ automatically

For beginning I just wanted to implement a public update call on this generic controller canister that would reset the minting account of icrc1 ledger it controls.

Later on if this is possible I’d like it to be generalized so that in order to pass control of icrc1 ledger it’s just necessary to change the controller everything else is done automagically

Maybe I’m just getting at it from a wrong angle :slight_smile:

Another thing that could help with several other issues with icrc1 ledgers is being able to perform actions from controller’s side the same way as when it’s coming from a ‘controlled’ canister… aka if controller somehow could impersonate the controlled canister

It should be safu from permissions side(because you already control said canister).
But I guess there’s nothing even close to this? :slight_smile:

This is very problematic and basically breaks everything if you don’t make any extra assumptions. If you say you have a random ICRC-1 compliant ledger and can’t make assumptions about what code exactly it’s running, then there is no way to update the minting account (or other similar data) safely. You can’t assume where in memory this is saved (not even stable vs main memory), you can’t even assume that there is a minting account at all (the return type on icrc1_minting_account is opt Account, not Account).

Now, assuming every ledger that you work with is a copy of this one. The proper way to do what you want to is either get the functionality into this implementation (I can ping some of the people you want to talk to if you want me to) or fork it and add it there. Then you can upgrade to a version that supports what you want to do safely since this impl is backwards compatible.

1 Like

I was more looking for a ‘reflective’ solution. Aka if something looks like a duck, it’s likely a duck. And I want to change a single narrow concept of how this duck behaves - override memory that is being used when icrc1_minting_account is called.

Yea, I’m aware of the forking solution. It’s the obvious one but I was looking if such kind of migration can be simplified and if so what is the way of such simplification

I think you should consider that a dead end.

  • you don’t have access to the WASM
  • you’d need to build or port a WASM runtime into your canister
  • you’d need to instrumentalize the runtime to figure out what memory was accessed
  • you need to figure out the right heuristics to swap out the right memory
  • you need a way to swap out stable or main memory of a different canister, which you don’t have

IMO forking is much less painful. Or you can collaborate with the team to get the upgrade args in place so that you get the functionality out-of-the-box

1 Like

What is the reason behind not allowing controller to download an up-to-date WASM of a canister it controls? Seems like an arbitrary and weird restriction

If I could fetch the WASM it at least would be a theoretically -feasible- task :slight_smile:
Albeit a very complicated one, but I imagine such techniques as you just described could be used for a broader scope of tasks

Having an up-to-date WASM opens a lot of doors… and could make for interesting use cases

Reflection always is the best solution for solving the wrong problem. ;)

1 Like

This is just educated guessing, but I would imagine it’s so that there’s less data to store. ICP instrumentalizes WASM before it is deployed. It injects e.g. functions to calculate how many instructions were run. If you discard the original WASM you can save ~50% data.

1 Like