DIP20 Community Proposal
Today, we (PsychedelicDAO) are opening up a conversation with the greater community to align ourselves on the DIP20 token standard, a battle tested implementation of ERC20 for the Internet Computer.
Community Asks
Over the past two weeks, we’ve been chatting with various community members seeking input on how we can improve DIP20 to further align with the IC community, while still keeping its simple to use core relatively the same.
The most common asks were:
- Namespaced Method Signatures
- Notify Interface
- Subaccount Support
As a result, we are creating a proposal for implementing some of these asks, and to open the floor to input from the community at large.
Improvement Proposal
Let’s go over each of the community asks, outline what they would entail, and give our thoughts about implementing them.
Namespaced Method Signatures
What: Adding a suffix or prefix to all DIP20 methods to make them unique. This allows a single canister to implement multiple standards into its public API.
ex: Instead of transferFrom
, the method would be named transfer_from_dip20
Our Thoughts: We are for adding namespacing to DIP20 as it enables services to have maximum interoperability.
The difficult part of adding namespacing to DIP20 is that it will inevitable cause a mismatch between some services and tokens during the upgrade process. Some tokens might upgrade while services might try to call the old non-namespaced functions, and vice versa.
Our plan to avoid as much mismatch as possible is to:
- Update our documentation & communicate with the community that DIP20 and canisters adhering to it will be changing to namespaced signatures.
- Create a legacy wrapper that tokens can implement so that services still making deprecated function calls are routed to the new namespaced functions.
We would love to hear if the community has other ways that we could alleviate these growing pains.
Notify Interface
What: DIP20 currently implements approve
/ transferFrom
, an interface where users approve a balance that services can spend on their behalf. Notify is similar, using transferNotify
a user or service would specify the destination they’d like to transfer their tokens to and a destination that they’d like to notify about this transfer. These two interfaces have similar purposes and for that reason are generally not implemented at the same time.
Our thoughts: We are against adding notify to DIP20. The asynchronous nature of the IC brings up some concerning attack vectors in notify. For example, you could spam transfers with a notify to a canister that purposefully takes a long time to respond, effectively DDOSing the canister.
Additionally, if notify fails then services have effectively lost track of users funds, unless they are running expensive tracking mechanisms like a heartbeat. This does not occur with approve
/ transferFrom
as tokens always stay in a user’s balance until spent by the service.
In conclusion, we are not against the concept of notify, but dont think that anyone (ourselves included) have found the right implementation for it on the IC yet, and therefore are going to stick with approve
/ transferFrom
for the time being. We are happy to be proved wrong, if anyone has implementations that are simple, cost effective, and void of attack vectors, we would be glad to hear them!
Subaccount Support
What: Subaccounts (Account IDs) are secondary identifiers on the IC where your principal ID (or canister ID) is hashed with an array of bits to produce a unique and arguably private identifier. DIP20 currently operates using principal ID’s only – this change would allow identification support for Account IDs everywhere in the standard (eg: ledger, transfer calls, etc).
This feature is mainly being asked for in order to cut down the magnitude of potential attack vectors in token canister code. The example given by community member @skilesare is that services holding tokens on behalf of users would be able to hold each unique user’s balance in a separate balance by creating new sub-accounts derived from the services canister ID. Thus, an attacker would only be able to access up to the max value of a single account, rather than overflowing to the balance of the entire service.
Our Thoughts: We have mixed thoughts regarding subaccounts.
Our critiques of sub-accounts are as follows:
-
The concepts of ‘Subaccounts’ on the IC can be a bit confusing for outsiders – on most chains subaccounts are heuristically derived key pairs, while on the IC they are not but are sometimes passed off as secure/anonymous identities.
-
Client side code can become a bit cumbersome when you need to understand the cryptography behind calculating sha224 hash of a principal ID + account bits.
-
Ledger bloat from increasing the amount of accounts required from each service.
Our Proposal: Right now, subaccounts are being derived at the application level. We propose adding a protocol level mechanism for canisters to derive more IDs (with or without the need to deploy more canisters), similar to the way that new principals can be derived and managed by wallets with BIP44 & BIP32.
We propose this is done by allowing a canister to make inter-canister calls with different principal IDs. Let’s say canister A is making a call to canister B, when canister B runs ic::caller() on A, we could get different Principal IDs for canister A.
To do so we propose introducing a new IC system api called ic0.call_set_derivation
in the WASM runtime, which could be used by canister A, to indicate it wants to make the call to canister B using its different account. The api call will become a member of the already existing IC system api calls:
ic0.call_new : // U Ry Rt H
( callee_src : i32,
callee_size : i32,
name_src : i32,
name_size : i32,
reply_fun : i32,
reply_env : i32,
reject_fun : i32,
reject_env : i32
) -> ();
ic0.call_on_cleanup : (fun : i32, env : i32) -> (); // U Ry Rt H
ic0.call_data_append : (src : i32, size : i32) -> (); // U Ry Rt H
ic0.call_cycles_add : (amount : i64) -> (); // U Ry Rt H
ic0.call_cycles_add128 : (amount_high : i64, amount_low: i64) -> (); // U Ry Rt H
ic0.call_perform : () -> ( err_code : i32 ); // U Ry Rt H
This would be easy for developers to understand from other ecosystems, leave us with only a single ID to deal with, clean up client side code, and be very useful for canister based wallets once we have tECDSA.
That’s all for today We look forward to see some thoughtful community discussion – critiques and questions are welcomed and encouraged! We’ll be continuing this discussion in Psychedelic TownHall 02 today @ 1pm, you can get notified or join here.