Open maht0rz opened 1 year ago
Additional discussion:
Started by reading Transferable
. Sounds reasonable! I'd suggest to tweak one aspect:
// from // zkApp -> issue a child account update to decrease its own balance, which can be proven token.transfer({ from, amount });
This sounds like the zkApp would issue a separate account update instead of using this.self
for decreasing the balance. To enable using this.self
, I would propose the following flexible interface:
token.transfer(options: { from: PublicKey | AccountUpdate | SmartContract, amount: UInt64 })
if an account update is passed, then we decrease the balance on it. if a SmartContract
is passed, then we call .self
on it to get its account update and decrease the balance on that. so, in a zkApp we could use:
token.transfer({ from: this, amount })
EDIT: everything I wrote applies to to
and receive
permissions as well of course
Started by reading
Transferable
. Sounds reasonable! I'd suggest to tweak one aspect:// from // zkApp -> issue a child account update to decrease its own balance, which can be proven token.transfer({ from, amount });
This sounds like the zkApp would issue a separate account update instead of using
this.self
for decreasing the balance. To enable usingthis.self
, I would propose the following flexible interface:token.transfer(options: { from: PublicKey | AccountUpdate | SmartContract, amount: UInt64 })
if an account update is passed, then we decrease the balance on it. if a
SmartContract
is passed, then we call.self
on it to get its account update and decrease the balance on that. so, in a zkApp we could use:token.transfer({ from: this, amount })
Thanks gregor i had an implementation earlier where i've passed SmartContract
as from/to
, and its definitely better since there's less AUs in the end. I'll update the interface and implementation accordingly. I think this may help with designing the transfer approval as well - but not much.
After discussing a bit more with @maht0rz, I think the token owner interface for transfers (Transferable
) is sufficient and handles all cases in the only way that's feasible.
It will need to be augmented with the right coding patterns in third party contracts that interact with multiple token holders which can either have proof or non-proof permissions for send/receive. Example: https://github.com/stove-labs/mip-token-standard/blob/develop/packages/token/test/ThirdParty.ts#L23
It's unfortunate that this complexity is pushed upwards but I don't think there's a different way that can handle arbitrary token holder accounts.
From my perspective, what this is mainly blocked on at this point is snarkyjs' current lack of general-purpose approval methods (see https://github.com/o1-labs/snarkyjs/issues/706, and more even advanced approval methods using recursion would be ideal)
@iam-dev brought up the question of configurability and/or composability of token standard features. I've attempted to implement the individual features in a composable manner, but decided not to pursue it further due to issues with inheritance in SnarkyJS SmartContracts. You can find the attempts and the resulting mixins in an older commit of this repository: https://github.com/stove-labs/mip-token-standard/tree/825d2195264b0156776afb1f1bbd5db585ece044/packages/token/src/mixins
Other than that, you could cherry pick only the features you want from the current monolithic implementation by removing the unwanted methods and their metadata from the Token class, which should be fairly straightforward to do.
Did any thinking go into not building the token standard using custom tokens
? I can see the benefit of coming up with a standard that composes on top of what's already available natively, but the token account model enforced by custom tokens
introduces significant problems:
As mentioned in the proposal, when using custom tokens
we have to fund an additional account in order to transfer tokens to a new user. This significantly deviates with how tokens work on other chains. For non-fungiables, custom tokens
create a more costly and cumbersome minting experience. For fungiables, custom tokens
make the ability to airdrop tokens non-trivial. It seems that the end result is that this token standard will have a harder time scaling to more users than a token standard not based on the native custom tokens.
@ycryptx aidroping would incur storage costs if the account ledger is tracked in e.g. a merkle tree anyways, this is handled by the protocol with an account creation fee instead. You can always build your airdrop on a claim-basis, where the end user would have to pay to claim the token (rather than the airdrop issuer).
Anyways the token standard described here is a fungible one, so it does not aim to address the NFT airdrop concerns you've mentioned.
If we wanted to build a token standard with non-built-in tokens, there'd be a whole another plethora of issues to deal with - e.g. concurrency.
This Issue contains a WIP token standard definition for the Mina L1, built with snarkyjs and based on the currently available smart contract features such as custom tokens. As a result of this discussion, we plan to propose a fully featured MIP.
Prerequisites
Reference implementation
The reference implementation of the features highlighted below is available as part of this repository, under
/packages/token/src
(develop branch). Tests can be found under/packages/token/test
Features
The token standard is designed in a modular fashion, also thanks to leveraging snarkyjs contract interoperability. As long as the contracts involved in the token standard 'contract suite' follow the predefined interfaces, the interoperability aspects of the token standard will be maintained. This allows for rich integrations with third party smart contracts, without the need to upgrade the token contracts themselves.
Token (owner) and Token Account
Mina L1 offers built-in custom tokens, where every account that holds a balance of a token is essentially a child account of the token contract. If Alice was a MINA account and we wanted to transfer our custom token to Alice's address, we'd have to deploy/fund an additional account under the token contract itself. This additional token account would be deployed using a
tokenId
of the parent token (owner) contract.Transferable
Transfers are an essential feature of every token standard, however in the case of snarkyjs and its account update based smart contracts, a simple
transfer()
method would not be sufficient. Each Mina L1 account can contain different set of permissions forreceive
andsend
, therefore the token contract cannot make any assumptions about the authorization required for bothfrom
andto
accounts. Due to the aforementioned reasons, theTransferable
interface is split into multiple methods, that allow the token contract and anyone interacting with it to issue account updates in a layout that suits their use case.Interface
Usage
Deciding which method to call is based on the underlying permissions of both the
from
andto
accounts respectively.All the available interface methods are wrapped under
transfer(...)
, which decides which underlying implementation to call based on the parameters provided.Here's a breakdown of what parameters should be used, depending on the account permission (send/receive proof/signature/none).
Transfer between to 'normal' accounts:
Withdraw from zkApp:
Transfer between two zkApps:
Approvable
Account updates to Token Accounts always require an approval from the Token (owner). In practice this means that any smart-contract call to a token account will need authorize it's own account updates by e.g. proof or signature, and in addition these account updates will have to go through an approval from the Token (owner) contract.
A practical example would be a zkApp that holds a balance of a custom token. If you want to withdraw, which means manipulate the state of the token account in any way via an account update - this account update will need additional validation/approval from the Token (owner) contract.
This approval logic is built-in into zkApps out of the box, and custom tokens rely on it completely.
Interface
Usage
Transfer between to 'normal' accounts:
No approval required, as long as the account updates are issued and proven from the Token (owner) contract itself.
Withdraw from zkApp:
Transfer between two zkApps:
Same principles as in the prior case apply here, but the
fromAccountUpdate
has to be issued from the respective token account as well, in order to have the appropriate proof authorization.Adminable (Mintable, Burnable, Pausable, Upgradable)
Adminable interfaces consist of multiple individual interfaces, such as: Mintable, Burnable, Pausable and Upgradable. Each of these interfaces can be implemented on its own, to allow for cases where certain tokens might not be e.g. mintable at all.
Interface
Viewable
The Viewable interface provides a set of non-method functions on the Token (owner) contract, which can be used to inspect state of the Token (owner) contract itself, or its Token accounts. Each viewable function offers an option to enable certain assertions, to support a case where the view function may be used in a third party smart contract.
Interface
Usage
Hookable
The Hookable interface provide a non-invasive way to extend the behavior of the Token's implementation. Hooks can be used to intercept certain features, such as transfers or admin-ish actions. Hooks are implemented as a standalone contract, which is called by the Token (owner) contract during certain points of execution. The hooks contract is referenced by an address in the Token (owner) contract itself.
Interface