-
Notifications
You must be signed in to change notification settings - Fork 334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CIP-0113? | Programmable tokens #444
base: master
Are you sure you want to change the base?
Conversation
Please note that at this moment this is only a draft and may contain errors in the logic. If you spot any or have suggestions on how to improve the logic contributions are more than welcome! |
Question: |
@matiwinnetou How it works on EVM compatible chains is that the wallet stores locally a list of common tokens / users can manually add token addresses to track. Try installing Metamask and interacting with milkomeda.muesliswap.com |
@matiwinnetou the solution proposed emulates the way tokens are used on EVM chains in many aspects As @nielstron said the user of a wallet needs to manually add the contract to track Wallets can find the user balance by the NFT associated with the payment credentials. |
I see, thanks, this helps a lot. So I guess it would be of course also possible to have some registry for those tokens so wallets could use it. |
thanks @michele-nuzzi - I just edited title to be specific & put the Draft status into the review stage itself; feel free to change back when you feel the drafting process is complete: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request Also I'm adding a link to the readable proposal into the original comment & please keep updated if it changes path 🙏 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I've noted elsewhere, a system as described in this CIP is inherently centralised (but trustless) and thus can't support the throughput it would on account-model based ledgers.
A much simpler system which likely serves your needs is a system where the owner is noted in the token name of a token. Let's assume it's a PKH for simplicity, but in the future we want scripts + datums as owners too.
You then only own a token if you 1) actually hold it and 2) the token name includes your PKH. The token name can also contain extra information through hashing the PHK along with token-specific information. If necessary, a UTXO can be created to ensure the preimage is stored on Cardano itself. Then sending a token to someone would entail burning then minting it again. In this process, arbitrary checks can be done through the minting policy.
Implement royalties is trivial, and would be done by including the author name in the token name too.
Implementing freezing for USD stablecoins would include checking a centralised Merkle tree of frozen addresses and checking that yours is not included.
As for supporting scripts, I think just allowing a script + datum to be used as owner might be enough.
Also, unrelated to the idea itself, I would appreciate it if the CIP was better checked for grammatical/formatting errors/typos, etc.
@michaelpj might be interested |
@L-as Thank you for helping with the CIP. I like a lot the system you propose but from a first read I believe is missing an equivalent for the Regarding throughput, it is only limited to the phase of the creation of a new account; while I understand it is a bottleneck it is an operation performed only once and it should not cause too many problems. Once an account is created it remembers its own state and multiple accounts can be used in parallel. I recognize my writing is not the best and I'll try to put more effort into it. |
@michele-nuzzi That is somewhat true, however, you could implement it by freezing the old tokens (ref inputs) and minting new ones, but this might not be any better than what you describe. How common is There is also the issue of complexity, the scheme described in this CIP is not simple, and also requires more effort from wallet developers to show the information. I'm not sure which scheme is better when taking that into account. |
From what I can tell It is true that for this first draft, it might be tricky to implement something like freezing; I was thinking to add a second field to the const AccountManagerDatum = pstruct({
Account: {
amount: int,
state: data
},
/* ... */
}); and then make sure that the the account manager can have other redeemers after the standard one so that maybe a Regarding wallet implementation, it shouldn't be any harder than the current resolution of ADAHandles. |
I would say to avoid the ERC-20 approve function at all costs. It has plagued the Ethereum eco-system and even they are standardizing a new key signature method called permit to try to correct this terrible mistake. I'd recommend instead signatories be checked for transferFrom methods instead of the far outdated approve. I have not had the time to fully go through this. But I just want to warn against the mistake of implementing approve. |
@MicroProofs If that's the case then there is no difference from the |
@michele-nuzzi |
With those two removed is there any reason not to go for the token approach? |
@L-as There would still be the problem of additional (trusted) data for script execution.
As you said implementing logic like freezing requires some external contract; this external contract would not be a standard, so someone else that wants to move around ERC20-like tokens would (and should) not know of this additional requirement |
@michele-nuzzi It would always be the case that transferring tokens can fail. It should not be part of the interface in the first place. For wallets to support it seemlessly, you could have a helper untrusted script associated with the MP that tells you what you need to add to fix it. That would have to be the case even with your system to support arbitrary behaviour and keep the interface simple. |
I don't particularly have an opinion here and I'm not currently planning to review this. If this is a thing that people want then they can create it. I think it may not perform well (as @L-as says), and it sacrifices essentially all of the benefits of native tokens on Cardano. A few thoughts:
|
After putting it in Draft stage I've been watching the feedback expecting that it will produce a Rationale and other CIP prerequisites to be added before @michele-nuzzi brings it into the "Ready for review" stage. I'm not recommending anything specifically yet in case a broader approach suggests presentation as a CPS instead. The discussion has been productive so far even without a mature proposal yet. |
@michaelpj me and @L-as have had a very productive discussion regarding performances, the only bottle neck is the Merkle tree which is there to guarantee the uniqueness of an account. This constraint is not really useful, there is no harm in having multiple accounts with the same credentials so it will be removed before the final CIP. The implementation will only require the account manager. |
Added |
@michele-nuzzi I'm happy to see Mermaid come in here & I'll keep an eye on Mermaid support on derived web sites that carry CIPs: p.s. if your last commits make your PR ready for review before Tuesday please post here so we can include it in our CIP meeting agenda again (p.p.s. that would also take it out of |
no @rphair, not yet ready, will tag you when is good |
@rphair this should give a good description of the CIP. I will wait for some feedback before proceeding. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @michele-nuzzi, looks like the narrative behind your spec changes is in 50e6215 - but will you also be able to come to the next CIP meeting (Tuesday) to discuss your progress & publicly review it again after your updates? We can also try to agree then upon what it will take to merge this.
If you can be here, let us now, or we'll reschedule the community review for a future meeting: https://hackmd.io/@cip-editors/97
@rphair I would love to, does it overlap with the plutus working group call? |
@michele-nuzzi I don't know; I doubt it since Plutus experts like @MicroProofs have attended the CIP meeting often. The CIP meeting date & time are as posted above and the meeting lasts 1 hour. |
Co-authored-by: Ryan <[email protected]>
I have changed the title because since the last changes the standard uses actual CNTs and not only numbers in datums. The previous title was confusing other community members. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My impression from community review in the CIP meeting today is this continues to show feasibility for community adoption, with generality as well as a broad range of specific applications.
<!-- The rationale fleshes out the specification by describing what motivated the design and what led to particular design decisions. It SHOULD describe alternate designs considered and related work. The rationale SHOULD provide evidence of consensus within the community and discuss significant objections or concerns raised during the discussion. | ||
|
||
It MUST also explain how the proposal affects the backward compatibility of existing solutions when applicable. If the proposal responds to a CPS, the 'Rationale' section SHOULD explain how it addresses the CPS, and answer any questions that the CPS poses for potential solutions. | ||
--> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<!-- The rationale fleshes out the specification by describing what motivated the design and what led to particular design decisions. It SHOULD describe alternate designs considered and related work. The rationale SHOULD provide evidence of consensus within the community and discuss significant objections or concerns raised during the discussion. | |
It MUST also explain how the proposal affects the backward compatibility of existing solutions when applicable. If the proposal responds to a CPS, the 'Rationale' section SHOULD explain how it addresses the CPS, and answer any questions that the CPS poses for potential solutions. | |
--> |
(artefact from template comment scaffold)
## Path to Active | ||
|
||
### Acceptance Criteria | ||
<!-- Describes what are the acceptance criteria whereby a proposal becomes 'Active' --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<!-- Describes what are the acceptance criteria whereby a proposal becomes 'Active' --> |
(artefact from template comment scaffold)
- independent transaction creation with `Transfer` redeemers | ||
|
||
### Implementation Plan | ||
<!-- A plan to meet those criteria. Or `N/A` if NOT applicable. --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<!-- A plan to meet those criteria. Or `N/A` if NOT applicable. --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate you clarifying some things for me. Here are some thoughts based on my (hopefully) correct understanding of this CIP.
This CIP uses the term "state" a lot in both variable names and the prose, but I do not think this is the proper use of the term "state". I believe this contributed to my initial confusion. According to wikipedia:
In information technology and computer science, a system is described as stateful if it is designed to remember preceding events or user interactions; the remembered information is called the state of the system.
-- emphasis mine
AFAIU this CIP is not remembering preceding events, and therefore, there is no state.
### High level idea | ||
|
||
The core idea of the implementation is to emulate the ERC20 standard; where tokens are entries in a map with addresses (or credentials in our case) as key and integers (the balances) as value. ([see the OpenZeppelin implementation for reference](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/9b3710465583284b8c4c5d2245749246bb2e0094/contracts/token/ERC20/ERC20.sol#L16)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this section is no longer accurate, I would appreciate it if you could update it. It was a huge contributor to my confusion over this CIP.
> | ||
> this would allow for a more powerful model than the account based equivalent but implies higher execution costs | ||
> | ||
> with the goal of keeping the standard simple we allow only a single sender |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this restriction still present or is this also outdated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, only a single sender allowed so far, but open to discussions
1) a `stateManager` contract | ||
- with minting policy used to proof the validity of account's state utxos | ||
- spending validator defining the rules to update the states |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about changing the name of the stateManager
to controlManager
. IMO, the state manager isn't managing "state" (ie, when assets are transferred it isn't updating anything). AFAIU it is just controlling whether the account has the proper permissions to spend the UTxOs. Calling it stateManager
also contributed to my confusion.
I would also argue these aren't "state utxos", but rather "control utxos" because they control what the account can do with its utxos. The spending validator then defines the rules to update the controls.
2) a `transferManager` parametrized with the `stateManager` hash | ||
- having minting policy for the tokens to be locked in the spending validator | ||
- spending validator for the validation of single utxos (often just forwarding to the withdraw 0) | ||
- certificate validator to allow registering the stake credentials (always succeed MAY be used) and specify the rules for de-registration (always fail MAY be used, but some logic based on the user state is RECOMMENDED) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
De-registering the tansferManager
seems like a good way to freeze all utxos. This feature may not always be desired, but perhaps it is worth mentioning. DApps can decide for themselves if they want this feature.
Also, why would the user state be part of the logic for de-registering? I thought de-registering impacts the whole DApp since it would disable the withdraw 0 observer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes you are right, de-registering should not be based on the user state
const Account = pstruct({ | ||
Account: { | ||
credentials: PCredential.type, | ||
state: data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I'd argue this isn't "state" but rather the controls/permissions for this account. There is no "remembering" occurring here.
The `Transfer` transaction allows the transfer of programmable tokens from one account to another within the `transferManager` contract. This transaction involves the following components: | ||
|
||
- **transferManagerObserver**: The contract that validates the inputs and ensures the correctness of the transfer. | ||
- **stateManagerContract**: The contract that manages the state of the accounts involved in the transfer. | ||
- **transferManagerContract**: The contract that handles the actual transfer of tokens. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the stateManagerContract should be included in this list because it doesn't seem like it is actually involved. Its utxos are only referenced which means the contract is not executed. It being included here is why I initially thought the state manager utxos were normal inputs instead of reference inputs.
|
||
The validation is performed in the minting policy. | ||
|
||
The minting policy MUST succeed if only one asset is created under the policy respecting the naming convention explained below, and MAY succeed if other assets are created as long as their name is NOT of length 32. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does the length matter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to avoid potential conflict with the resulting hash
very remote but better avoid
should not be a very useful feature anyway
|
||
Main differences were in the proposed: | ||
- [use of sorted merkle trees to prove uniqueness](https://github.com/cardano-foundation/CIPs/pull/444/commits/525ce39a89bde1ddb62e126e347828e3bf0feb58#diff-370b6563a47be474523d4f4dbfdf120c567c3c0135752afb61dc16c9a2de8d74R72) of an account during creation; | ||
- account credentials as asset name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was the account credential abandoned for the name? If the account credential was used, users could trivially check their account statuses since they know the token name to lookup. The current naming scheme makes it very difficult for users to lookup their account statuses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using the credentials for the name could not guarantee the unique association of an account to a set of utxos
a user could create as many accounts with the same name, with different states (or control data, or config, whatever you like) which could represent a security vulnerability from the perspective of the spending logic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, the transferManager
is parameterized by the stateManager
. I haven't fully thought this through, but what if it wasn't and instead the stateManager
hash was in the transferManager
datum? It could be something like:
accountManager
- a (new) minting policy where the token name is the user's credential. This enables the DApp entity to see all registered credentials. ThisaccountManager
does not control any state; it just enables using the list of associated tokens as a list of registered users.stateManager
- the currentstateManager
except it is parameterized by theaccountManager
hash and the user's credential. So all users get their ownstateManager
minting policy for the programmable token. The DApp entity can see the credential in the registered list (tokens foraccountManager
) and can then lookup all associatedstateManager
tokens for that credential. Minting newstate
tokens requires referencing theaccount
token for that credential.transferManager
- the currenttransferManager
except it is not parameterized by anything. Instead, thestateManager
hash is also included in thetransferManager
datum. TheaccountManager
hash could also go in the datum. This contract does not mint the programmable tokens. Instead, they programmable tokens will be deposited into it.
This approach could make the transferManager
general purpose so each user only needs one address for all programmable tokens. This approach also makes it easy for users to check their statuses since all tokens minted with a given stateManager
(parameterized with their credential) belongs to them. In other words, they can find all of their state UTxOs using just their credential. Thoughts?
EDIT: The accountManager
may not be necessary since you likely already know the credential you want to freeze.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach could make the transferManager general purpose so each user only needs one address for all programmable tokens
I find it hard to believe that many tokens will adhere to the same spending rules
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it hard to believe that many tokens will adhere to the same spending rules
I don't think they will, but the differences could possibly be encoded into the state UTxOs. Currently, the lack of generality is a deal breaker IMO. Do you expect users to have a separate address for every stablecoin, every NFT collection, every goverment issued DID, etc? Do you expect frontends to actually add support for possibly an infinite number of addresses? I don't. You might get a few addresses to be supported, but that means the frontends effectively act as gatekeepers for which tokens can be programmable since unsupported tokens effectively don't exist. Perhaps there can't be a universal transferManager
, but perhaps 2 or 3 different transferManagers
can cover most use cases.
- with minting policy used to proof the validity of account's state utxos | ||
- spending validator defining the rules to update the states | ||
2) a `transferManager` parametrized with the `stateManager` hash | ||
- having minting policy for the tokens to be locked in the spending validator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be made more general? Having the transferManager
also mint the tokens means users will have a separate address for every programmable token. This can quickly become a nightmare for frontends to integrate. It would be better if there was a single transferManager
that could be used with all programmable tokens.
Programmable tokens don't necessarily need to be minted by the transferManager
. Instead, they can be deposited into the transferManager
by the entity minting the token. For example, a user selling NFTs can ensure that all NFTs sold go to a transferManager
address.
There may also be use cases where a token should be allowed to leave the transferManager
. For example, lets say there is an art NFT that has been collecting royalties for a while and now the royalty owners would like to release the NFT from the royalty requirement (there may be legal/tax benefits to this in the future). This approach doesn't seem to have a "release" mechanism.
A "release" mechanism could also be useful for upgrading the transferManager
. If a new version comes out, users can possibly transfer their assets from transferManager
v1 to transferManager
v2 (depending on the control utxos). The current design seems to prevent upgrades like this. If this CIP actually does support upgrades, it definitely needs to mention how upgrades are possible. Upgrades don't seem to be mentioned anywhere.
Only **one (1)** utxo from the `transferManager` is allowed to be spent in a burn transaction. | ||
|
||
If a user desires to burn a different amount than the one currently present they should break the operation in 2 different transactions: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why only one utxo per transaction? This seems unnecessarily restrictive. Why can't multiple utxos be spent? The entire balance from each utxo spent can be burned. The observer can tell which utxos are being used for burning based on the redeemers used to spend them.
the standard does not impose real rules of what the "state" is used for from the "Transfer" tx perspective it acts kinda like the "Read" monad, aka. a configuration. This configuration could be changed (by the user or some other party according to the specific implementation) so it "could" act as a state The same way the datum attached to an utxo does not necessarily remember previous stuff, but it could and often does, that is why it is used as state. I believe using |
This CIP proposes a solution to CPS-3? (Smart Tokens) implementable already with V2 Plutus contracts.
If adopted as a standard it would allow to emulate the behavior of account-based ledgers tokens on Cardano.
(rendered proposal in branch)