Enable Canisters to Hold ICP

Update on enabling canisters to transfer ICP

TL;DR: The Foundation will resubmit the proposal to upgrade the ledger and enable canisters to transfer ICP. The Foundation will vote for adopting the proposal.

Yesterday, voting on proposal 30496 to upgrade the Ledger and enable canisters to transfer ICP saw 18.9% votes for adoption and less than 0.1% for rejection with only half an hour to the deadline. Unfortunately, a technical issue in application subnet “pljlw” made it risky to perform the upgrade to the Ledger canister at the same time. Important to note, the issue with subnet “pljlw” is unrelated to the ICP transferability upgrade. Fortunately, the subnet is back to running normally. As promised, the Foundation is resubmitting the upgrade proposal to update the Ledger canister and enable ICP transferability. Given the majority of the community was in favour of adoption, this time, the Foundation will vote in favor of the upgrade proposal.

24 Likes

I am confused why this has to do so like this, which sounds like a waste of time. You said that “the issue with subnet ‘pljlw’ is unrelated to the ICP transferability upgrade.” Why cannot jut let the ICP transferability upgrade? As per this saying, if one day there are millions of subsets and if one subset has this kind of issue and we will not be able to do any of this kind of upgrade things? Is my understanding right?

Currently, converting ICP to Cycles to top-up a canister triggers a call-tree involving the ledger and the canister to be topped up. If the canister to be topped up is on an unresponsive subnetwork, then the call-tree may not terminate and the ledger cannot be upgraded (since atm, to safely upgrade a canister the canister needs to be stopped).
We will remove this dependency (either by eliminating the need to stop the canister, or via different flows for ICP conversion to cycles so that the dependency is removed). So, upgrading the ledger will be completely independent of the behavior of other canisters/subnetworks.

8 Likes

Here is the resubmitted proposal.
Adopted.
Upgraded.
Canisters are now able to transfer ICP!

23 Likes

I wonder is the phase of the nns ICP wallet when we accidentally delete it, although we still save the phase, but we can’t backup the wallet, is there any way to recreate it?

Trying to use the new interface… what am I missing?

❯ dfx canister --network ic call "ryjl3-tyaaa-aaaaa-aaaba-cai" account_balance "(record { \"account\" = blob \"\a8\137Tw\ca\0d\04KeY2r(6P\d2<\0a\c0\ac\bcnH\ca\d5\e6\fc\"})"

The Replica returned an error: code 5, message: "Canister ryjl3-tyaaa-aaaaa-aaaba-cai trapped explicitly: Panicked at 'Deserialization Failed: "Fail to decode argument 0 from table0 to record { account : vec nat8 }"', /builds/dfinity/ic/rs/rust_canisters/dfn_core/src/endpoint.rs:34:41"

Using the exact same interface (pulled from the proposal’s github link) on a local test canister works just fine:

❯ dfx canister call legends account_balance "(record { \"account\" = blob \"\a8\137Tw\ca\0d\04KeY2r(6P\d2<\0a\c0\ac\bcnH\ca\d5\e6\fc\"})"

(record { e8s = 0 : nat64 })

If you have accidentally deleted the recovery phrase then you can simply create a new recovery phrase which will be different from the phrase you had before. Is your question if you can somehow re-install the old recovery phrase into your internet identity instead of getting a new, different one?

how can i create a new phase once i have checked it in the approved devices section nns please guide me

I got exactly the same error as @jorgenbuilder

The Replica returned an error: code 4, message: "IC0503: Canister ryjl3-tyaaa-aaaaa-aaaba-cai trapped explicitly: Panicked at 'Deserialization Failed: "Fail to decode argument 0 from table0 to record { account : vec nat8 }"', /builds/dfinity/ic/rs/rust_canisters/dfn_core/src/endpoint.rs:34:41"

Context: a canister that tries to get its own balance.
You can find the code here.

EDIT: it is now fixed, the account id hash was missing.

4 Likes

@quint , @jorgenbuilder

I believe the problems you’re facing with ledger interface are caused by the fact that you’re trying to use canister principals as ledger account identifiers. Principals and ledger account identifiers are different identifiers (even though the ledger account id is derived from the principal of the owner and a subaccount):

$ dfx identity get-principal 
sp3em-jkiyw-tospm-2huim-jor4p-et4s7-ay35f-q7tnm-hi4k2-pyicb-xae

$ dfx ledger account-id
8af54f1fa09faeca18d294e0787346264f9f1d6189ed20ff14f029a160b787e8

I know this is very confusing, but it’s how the ledger was originally designed, and there is some reasoning behind this.

We’re planning to release a few support libraries and examples demonstrating how to correctly talk to the Ledger, a formal specification for the Ledger canister, and a reference implementation in Motoko. This will happen in the next few weeks. I’ll also give a community conversation talk on these topics.

If you’re already eager to start building things, you can use the following draft PRs as your point of departure:

11 Likes

Thanks for the response! Just to clarify, the example above uses account ids.

1 Like

Thanks for the response! Just to clarify, the example above uses account ids.

Not really:

    public type AccountIdentifier = Blob;

    public func balance() : async Ledger.ICP {
        await ledger.account_balance({
            // same as Blob.fromArray(Blob.fromPrincipal(Principal.fromActor(this), null)); 
            account = Blob.fromArray(AccountIdentifier.fromPrincipal(Principal.fromActor(this), null));
        });
    };  

This code converts a Principal into a blob, not computes an account identifier from a principal.
For the reference, this is the proper way to compute account identifier from a principal: Draft: a canister that transfers funds using ICP Ledger API by roman-kashitsyn · Pull Request #123 · dfinity/examples · GitHub

  public func accountIdentifier(principal: Principal, subaccount: Subaccount) : AccountIdentifier {
    let hash = SHA224.Digest();
    hash.write([0x0A]);
    hash.write(Blob.toArray(Text.encodeUtf8("account-id")));
    hash.write(Blob.toArray(Principal.toBlob(principal)));
    hash.write(Blob.toArray(subaccount));
    let hashSum = hash.sum();
    let crc32Bytes = beBytes(CRC32.ofArray(hashSum));
    Blob.fromArray(Array.append(crc32Bytes, hashSum))
  };

The default subaccount is 32 zero bytes.

4 Likes

It uses this package to convert a principal to an account ids, not Blob, am I missing something?..

How are these the same?

// same as Blob.fromArray(Blob.fromPrincipal(Principal.fromActor(this), null));
account = Blob.fromArray(AccountIdentifier.fromPrincipal(Principal.fromActor(this), null));

3 Likes

Ah, thanks for the clarification, I missed the fact that an external package is used. Unfortunately, this package doesn’t include the extra 4 bytes (CRC32 of the hash) into the account identifier as indicated in the ledger candid interface docs. So the ledger canister fails to decode a 32 byte array from the message.

10 Likes

This fixed it. :sweat_smile: I guess I need to read my own documentation
Thanks a lot @roman-kashitsyn!

7 Likes

Is this the official ledger canister on mainnet?

https://k7gat-daaaa-aaaae-qaahq-cai.ic0.app/listing/nns-ledger-10244/ryjl3-tyaaa-aaaaa-aaaba-cai

(The id and interface differs from what the rust code uses but I was able to query the balance of my canister after updating them.)

3 Likes

Is this the official ledger canister on mainnet?

Thanks a lot for spotting the issue, @hhanh00! I fixed the issue in this commit.

The id and interface differs from what the rust code uses but I was able to query the balance of my canister after updating them.

That’s a very good point!
The canister provides multiple interfaces for various use-cases, and the .did file returned by __get_candid_interface_tmp_hack endpoint still describes the old interface. Since the methods exposed by the old interface weren’t designed for general public use, and mixing two interfaces together will be very confusing, we can change the interface endpoint to return the new .did file in the next release of the ledger canister.

5 Likes

Are we able to interact with a local ledger canister with “fake ICP” through this API to test ICP transferring with a local replica?

so what do i need to do to generate a new phase code on my old nns wallet address i need to back it up on my laptop now i’m praying my phone doesn’t break :frowning:

Go to https://identity.ic0.app/ on your phone
Type in your identity anchor number
Authenticate with your phone
Click on “Add recovery key” or “Add recovery mechanism” (whatever appears)
Click on “Seed phrase”
Copy the whole thing including the identity anchor number in the beginning and write it down