diff --git a/CIP-0113/README.md b/CIP-0113/README.md new file mode 100644 index 0000000000..3ca54a0820 --- /dev/null +++ b/CIP-0113/README.md @@ -0,0 +1,359 @@ +--- +CIP: 113 +Title: Programmable token-like assets +Category: Tokens +Status: Proposed +Authors: + - Michele Nuzzi + - Matteo Coppola + - Philip DiSarro +Implementors: [] +Discussions: + - https://github.com/cardano-foundation/CIPs/pull/444 + - https://github.com/cardano-foundation/CIPs/pull/944 +Created: 2023-01-14 +License: CC-BY-4.0 +--- + +## Abstract + +This CIP proposes a standard for programmable tokens. + +We use the term "programmable tokens" to describe the family of tokens that require +the successful execution of a script in order to change owner. + +## Motivation: why is this CIP necessary? + +This CIP proposes a solution at the Cardano Problem Statement 3 +([CPS-0003](https://github.com/cardano-foundation/CIPs/pull/947)). + +If adopted it would allow to introduce the programmability over the transfer of tokens +(programmable tokens) and their lifecycle. + +The solution proposed includes (answering to the open questions of CPS-0003): + +> 1) How to support wallets to start supporting validators? + +With the use of standard smart wallets, that the user can derive deterministically, +and an on-chain registry of programmable tokens. Any new programmable token that is +registered in the on-chain registry is immediately supported by the smart wallet. + +> 2) How would wallets know how to interact with these tokens? - smart contract registry? + +The requirements for a valid transaction are described in this standard. + +From an indipendent party implementing the standard perspective, +it will be clear how and where to find the necessary reference inputs +to satisfy the smart wallet that handles the programmable tokens. + +> 3) Is there a possibility to have a transition period, so users won't have their funds blocked until the wallets start supporting smart tokens? + +Programmable tokens are normal native tokens in a smart wallet. + +There is no need for a transition period, because there will be no change from the ledger persepective. + +> 4) Can this be achieved without a hard fork? + +Yes. + +> 5) How to make validator scripts generic enough without impacting costs significantly? + +Validator scripts will be standard "withdraw 0" contracts. +The impact on costs is strictly dependend on the specific implementation. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL +NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and +"OPTIONAL" in this document are to be interpreted as described in +[RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119). + +The term "user" is used to indicate, interchangeably: +- "public key hash" credentials +- "script" credentials (smart contracts) + +The term "creator" is used to indicate a user that creates a new programmable token. +The term "admin" is used to indicate a user that can execute priviledged actions on a certain programmable token without explicit permission of the users. +The term "issuer" is used to indicate a user that can mint and/or burn a certain programmable token. + +The term "policy" indicates a Cardano native token (CNT) policy, which is the hash of the script that can mint such token. +The term "unit" indicates the concatenation of a token policy and a token name to uniquely represent a certain CNT. + +The following terminology represents all the on-chain components of the standard: +- `tokenRegistry`: on-chain registry where anyone can register new programmable tokens. It's a smart contract that implements the linked list pattern. The contract ensures the list is always sorted by the entry's policy. It is made of: + - `registryNode`: a node (or entry) of the tokenRegistry is a UTxO representing a specific programmable token + - `registrySpendScript`: Spend script where all the registryNodes exist + - `registryMintingPolicy`: Minting script of NFTs that represent valid registryNodes +- `programmableLogicBase`: the unique Spend script that always holds all existing programmable tokens +- `transferLogicScript`: a token-specific Withdraw script that implements the custom transfer logic for such programmable token +- `thirdPartyTransferLogicScript`: a token-specific Withdraw script that defines admin actions on such programmable token +- `issuancePolicy`: a Minting script that mints programmable token. While this script code is one for all possible programmable tokens, its deployment is token-specific because issuancePolicy is parametrized by the hash of an issuanceLogicScript +- `issuanceLogicScript`: a token-specific Withdraw script that impelments the custom minting/burning logic for such programmable token +- `globalState`: an optional token-specific unique UTxO whose datum contains global information regarding the token (if it's frozen, if transfers are paused, etc.) + +The term "substandard" indicates a specific implementation of all the on-chain components that guarantees a certain behaviour and a consistent way to build the transactions. Any programmable token must belong to a certain substandard, otherwise wallets and dApps don't know how to manage them. + +The term "smart wallet" indicates all the UTxOs living inside the programmableLogicBase script and belonging to a certain user. +To know a user's smart wallet address check the dedicated following section. + +### High level flow + +The creator wants to release a new programmable token. + +The tokenRegistry and the programmableLogicBase are already deployed on-chain. + +The creator writes a new transferLogicScript where he defines the rules to transfer the new token. + +Then he writes a new thirdPartyTransferLogicScript where he defines who are the admins and what the actions they can do without permission of any other user. + +Then he writes a new issuanceLogicScript where he defines who can mint and burn the new token. + +Then he deploys a new issuancePolicy instance parametrized by the new issuanceLogicScript. + +Finally, the creator adds a registryNode to the tokenRegistry with the hashes of all the above scripts and with additional required +information. The registration cannot happen if the policy has been already registered or if the issuancePolicy is wrong. + +During or after the registration process, the creator can mint the new programmable token. +This new token is enforced to be sent in the programmableLogicBase script. + +Off-chain, any user can deterministically derive their smart wallet (as explained in the next section). + +When an user wants to transfer some of his programmable tokens, for each token a proof of registration (or non registration) is required: if the policy is present in the tokenRegistry then the associated transferLogicScript MUST be running in the same transaction; if the policy is not present in the tokenRegistry, the token is treated as a normal, non programmable, CNT and can always leave the programmableLogicBase script. + +In any moment for each token, admins can execute privileged actions as defined in thirdPartyTransferLogicScript. + +In any moment for each token, issuers can mint more token or burn existing ones that are held by them. + +### User's smart wallet address derivation + +The smart wallet address has the following form: +(fixedPaymentCredentials, userDefinedStakeCredentials) + +where: +- fixedPaymentCredentials never change and they are the credentials obtained from the hash of programmableLogicBase +- userDefinedStakeCredentials are different for each user. +The smart wallet logic is the same for every user. If the user is a wallet, he MAY choose to user either their payment credentials or their stake credentials. We suggest to use his payment credentials to allow anyone to derive indipendently the address of the user. +If the user is a smart contract, then it's the payment credentials of that contract. + +In other words, a smart wallet is the set of UTxOs that live in the programmableLogicBase script and that have a specific stake credential that identifies the owner. + +### TokenRegistry entry datum + +Every entry in the tokenRegistry MUST have attached an inline datum following this standard type: + +```ts +type RegistryNode { + tokenPolicy: ByteArray, + nextTokenPolicy: ByteArray, + transferLogicScript: ByteArray, + userStateManagerHash: ByteArray, + globalStateUnit: ByteArray, + thirdPartyTransferLogicScript: ByteArray +} +``` + +This datum is enforced by the execution of the tokenRegistry at registration. + +The ordering of the fields MUST be respected. + +#### `tokenPolicy` + +MUST be a bytestring of length 28. This field is also the key of the entry (registryNode) +and the token name of the NFT minted via registryMintingPolicy and that is included in the entry UTxO. + +#### `nextTokenPolicy` + +MUST be a bytestring of length 28. + +As the tokenRegistry is a ordered linked list, it's the next key (a tokenPolicy) in the tokenRegistry in lexicographic order. + +#### `transferLogicScript` + +MUST be a bytestring of length 28. + +Represents the hash of the "Withdraw 0" script to be included in a transfer transaction for validation. + +#### `userStateManagerHash` + +MUST be a bytestring of either length 0 or length 28. + +It represents the optional hash of the "Withdraw 0" script that manages the state for each user. + +If the value has length 28, when creating a transfer transaction one or more reference inputs MUST be +included depending on the sub-standard (see below). + +If the value has length 0, when creating a transfer transaction no reference input representing the user state is expected. + +#### `globalStateUnit` + +MUST be a bytestring of either length 0 or length between 28 inclusive and 60 exclusive. + +It represents the optional unit of an NFT that is contained in a UTxO with the global state as inline datum. + +If the value has length 0, it means there is no global state for this programmable token. +For this reason, when creating a transfer transaction no reference input is expected to represent the global state. + +If the bytestring has length greater than or equal to 28, +the value represents the concatenation of a token policy (of length 28) +and a token name (of length between 0 and 32). +In this case, when creating a transfer transaction a reference input having an NFT matching the unit MUST be included. + +#### `thirdPartyTransferLogicScript` + +MUST be a bytestring of either length 0 or length 28. + +It represents the optional hash of the "Withdraw 0" script that defines who is admin (third party) +and the admin actions on the programmable token. + +If the value has length 0, the programmable token does NOT allow admins to transfer programmable tokens on the users' behalf. + +If the value has length 28, an admin can transfer users' tokens creating a transaction that executes this script. + +### Programmable token registration + +To create a new programmable token, it must be properly registered in the tokenRegistry. +The on-chain validation won't allow the creator to cheat or deviate from the enforced rules. + +Recalling that the tokenRegistry is an ordered linked list sorted by the `tokenPolicy` field, the transaction to +register the token has the following requirements: +1) The registryNode preceding the policy of the new token, called here prev_node, MUST be spent +2) The output of the transaction MUST include: + - prev_node with the value and the datum unchanged except for the field `nextTokenPolicy` that is now set to the new token `tokenPolicy` + - a new registryNode representing the new token with the proper registryDatum fields, in particular `nextTokenPolicy` + is set to the input value of prev_node `nextTokenPolicy` +3) The transaction MUST include the registration certificate of `transferLogicScript` +4) The new registryNode `globalStateUnit` MAY be an empty string if the logic of +the programmable token does not require a global state; +otherwhise it MUST be a bytestring of length between +28 inclusive and 60 (28 + 32) exclusive. +5) The new registryNode SHOULD have the minimum amount of lovelaces that the protocol allows, +and MUST include a newly minted NFT, under the `registryMintingPolicy`, +and the programmable token policy as NFT name. +6) Both the outputs MUST NOT have any reference script. +7) Both the outputs address MUST **only** have payment credentials. +8) The new token `issuancePolicy` MUST be an instance of the official script parametrized by a custom `issuanceLogicScript` + +### Transfer +**TODO This section must be updated** + +In order to spend an utxo holding programmable tokens, +a standard redeemer must be passed to the smart wallet: + +```ts +type TransferRedeemer { + Transfer { + registryNodes: List + } + ThirdParty { + registryNodes: List + } +} +``` + +Depending on the constructor used, for each programmable token, either the `transferManager` or the `thirdPartyActionRules` contract will be used respectively. + +Independently of the constructor, the first field of the redeemer MUST include a list of indexes to be used on the reference inputs field. + +The list MUST include one index for each non-lovelace token policy, programmable and not, present on the utxo's value. + +The list order must match the lexicographic ordering of the value. + +For example, the index at position 0 indicates the index of the reference input coming from the registry +that indicates the presence (or the absence) of the first policy of the input value. + +(for reference, a typescript implementation of the lexicographic ordering can be found +[here](https://github.com/HarmonicLabs/uint8array-utils/blob/c1788bf351de24b961b84bfc849ee59bd3e9e720/src/utils/index.ts#L8-L27)) + +### Implementing programmable tokens in DeFi protocols +**TODO This section must be updated** + +### Implementing programmable tokens in wallets and dApps +**TODO This section must be updated** + +#### Existing substandards + +**TODO This section must be updated** + +In order to validate a transfer, transfer managers MAY need to read informations about the state of the users involved in the transaction. + +However, depending on the implementation, said state MAY be managed in different ways. + +Some examples of state management may be: + +- no state +- one state per user involved in the spending, to be queried by NFT. +- one state per user involved in the transfer (both inputs and outputs), to be queried by NFT. +- a single reference input representing the root of a merkleized state. + +And many more state managements are possible, depending on the specific implementation. + +For this reason, we make explicit the need for sub standards + +## Rationale: how does this CIP achieve its goals? +The current CIP version, informally called V3, is the result of several iterations to create the best standard for programmable tokens. +This standard safely extends the functionality of tokens on Cardano, in a scalable way and without disruptions, leveraging CNTs that live +forever in a single smart contract. + +The proposal does not affect backward compatibilty being the first proposing a standard for programmability over transfers. + +Existing native tokens are not conflicting with the standard, instead, native tokes are used in this specification for various purposes. + +### History of the proposed standard +The [first proposed implementation](https://github.com/cardano-foundation/CIPs/pull/444/commits/525ce39a89bde1ddb62e126e347828e3bf0feb58) (which we could informally refer as v0) was quite different by the one shown in this document + +Main differences were: +- [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 + +This path was abandoned due to the logaritmic cost of creation of accounts, on top of the complexity. + +Other crucial difference with the first proposed implementation was in the `accountManager` redeemers; +which included definitions for `TransferFrom`, `Approve` and `RevokeApproval` redeemers, aiming to emulate ERC20's methods of `transferFrom` and `approve`; + +After [important feedback by the community](https://github.com/cardano-foundation/CIPs/pull/444#issuecomment-1399356241), +it was noted that such methods would not only have been superfluous, but also dangerous, and are hence removed in this specification. + +After a first round of community feedback, a +[reviewed standard was proposed](https://github.com/cardano-foundation/CIPs/pull/444/commits/f45867d6651f94ba53503833098d550326913a0f) +(which we could informally refer to as v1). +[This first revision even had a PoC implementation](https://github.com/HarmonicLabs/erc20-like/commit/0730362175a27cee7cec18386f1c368d8c29fbb8), +but after further feedback from the community it was noted that the need to spend an UTxO on the receiving side could cause UTxO contention +in the moment two or more parties would have wanted to send a programmable token to the same receiver at the same time. + +After "v1", another improved standard was proposed, informally referred to as "v2". +v2 proposed a standard interface for programmable tokens, based on the exsistence of 2 contracts: the `stateManager` and the `transferManager`. + +By the v2 standard, each user must "register" to the policy by creating an utxo on the `stateManager` so that they could +spend programmable tokens using the `transferManager` + +This soft requirement for registration was not well received from the community, and for this reason we are now at the "v3" version of the standard. + +The specification proposed in this file addresses all the previous concerns. + +## Path to Active + +### Acceptance Criteria + + +- having at least one instance of the smart contracts described on: + - mainnet + - preview testnet + - preprod testnet +- having at least 2 different wallets integrating programmable asset functionalities, mainly: + - displayning balance of a specified programmable assets + - independent transaction creation with `Transfer` redeemers + +### Implementation Plan +- [ ] Issuance of at-least one smart token via the proposed standard on the following networks: + - [ ] 1. Preview testnet + - [ ] 2. Mainnet +- [ ] End-to-end tests of programmable token logic. +- [ ] Finally, a widely adopted wallet that can read and display programmable token balances to users and allow the user to conduct transfers of such tokens. + +### Implementation Plan +- [ ] Implement the contracts detailed in the specification. +- [ ] Implement the offchain code required to query programmable token balances and construct transactions to transfer such tokens. + +## Copyright + +This CIP is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/legalcode). diff --git a/CIP-0113/deprecated/v0.md b/CIP-0113/deprecated/v0.md new file mode 100644 index 0000000000..c164f6d30f --- /dev/null +++ b/CIP-0113/deprecated/v0.md @@ -0,0 +1,753 @@ +--- +CIP: ? +Title: meta-assets (ERC20-like assets) +Category: Tokens +Status: Proposed +Authors: + - Michele Nuzzi + - Harmonic Laboratories +Implementors: [] +Discussions: + - https://github.com/cardano-foundation/cips/pulls/? +Created: 2023-01-14 +License: CC-BY-4.0 +--- + + + +# CIP-XXXX: meta-assets (ERC20-like assets) + + +## Abstract + +This CIP proposes a standard that if adopted would allow the same level of programmability of other ecosistem at the price of the toke true ownership. + +This is achieved imitating the way the account model works laveraging the UTxO structure adopted by Cardano. + +## Motivation: why is this CIP necessary? + + +This CIP proposes a solution at the Cardano Problem Statement 3 ([CPS-0003](https://github.com/cardano-foundation/CIPs/pull/382/files?short_path=5a0dba0#diff-5a0dba075d998658d72169818d839f4c63cf105e4d6c3fe81e46b20d5fd3dc8f)). + +If adopted it would allow to introduce the programmability over the transfer of tokens (meta-tokens) and their lifecycle. + +The solution proposed includes (answering to the open questions of CPS-0003): + +1 and 2) very much like accoun based models, wallets supporting this standard will require to know the address of the smart contract (validator) +3) the soultion can coexsist with exsiting the native tokens +4) the implementaiton is possible withou hardfork since Vasil +5) optimized implementations should not take significant computation, especially on transfers. + +## Specification + + +In the specifiaction we'll use the haskell data type `Data`: +```hs +data Data + = Constr Integer [Data] + | Map [( Data, Data )] + | List [Data] + | I Integer + | B ByteString +``` + +and we'll use the [plu-ts syntax for structs definition](https://www.harmoniclabs.tech/plu-ts-docs/language/values/structs.html) as an abstraction over the `Data` type. + +The core idea of the implementation is to remember all public key hashes that have (or have had) a balance in the meta-asset in question in a distributed sorted merkle tree. + +The idea is really similar to the [Distributed Map by Marcin Bugaj described in the Plutonmicon repository](https://github.com/Plutonomicon/plutonomicon/blob/main/DistributedMap.md) but instead uses sorted merkle trees to allow logaritmic complexity (instead of linear) when proving that a public key hash `pkh` is part of the set of registered "accounts" and it needs to be sorted to prove that a `pkh` is _not_ part of the same set. + +The idea of sorted merkle trees has first beed introduced in [Zero-Knowledge Sets](https://people.csail.mit.edu/silvio/Selected%20Scientific%20Papers/Zero%20Knowledge/Zero-Knowledge_Sets.pdf) by Silvio Micali Michael Rabin and Joe Killian and it is here adapted to relax the constraint of the fixed set so that new public key hashes can be added. +> **NOTE**: to allow new values insertion the tree will not be balaced nor complete; it is therefore possible to have multiple different hashes for the same ordered set based on the configuration of the tree, however that should not cause any problems as our only goal is jsut to prove a given element is or is not present. + +> **NOTE**: The relaxed tree we are using implies more information is revealed during a proof, see the verification of a public key hash missing in the `NewAccount` implementation + +The implementation requires: + +- 2 (very similar) minting policies that will be used to mint an NFT as proof that the datum present on an NFT has been obtained as result of a smart contract execution (and hence is valid) +> **NOTE**: due to the two minting policies being similar some implementation may prefer to unify the two policies in a single one and run that with different redeemers + +- 2 validators + - one that handles the distributed sorted merkle tree (`DSMerkleTree` contract) + - one that handles the "accounts" (`AccountManager` contract) + +### standard data types used + +We'll need to use some data types as defined in the script context; + +in particular we need to the fine the types for a transacntion output reference: + +```ts +const PTxId = pstruct({ + PTxId: { txId: bs } +}); + +const PTxOutRef = pstruct({ + PTxOutRef: { + id: PTxId.type, + index: int + } +}); +``` +which are equivalent to the data: +```hs +-- PTxId +Constr 0 [ B txId ] + +--- PTxOutRef +Constr 0 [ PTxId, I index ] +``` + +and for (payment) credentials: +```ts +const PCredential = pstruct({ + PPubKeyCredential: { pkh: bs }, + PScriptCredential: { valHash: bs }, +}); +``` +which translates to the data: +```hs +-- PPubKeyCredential +Constr 0 [ B pkh ] + +-- PScriptCredential +Constr 1 [ B valHash ] +``` + +## Minting policy 1 + +> minting poilicy to be used for the distributed merkle tree contract's utxos + +redeemers: +```ts +const TreeMintingPolicyRdmr = pstruct({ + NewNode: {}, + NewLeaf: {}, + MakeRoot: {} +}) +``` +equivalent to the data: +```hs +-- NewNode +Constr 0 [] + +-- NewLeaf +Constr 1 [] + +-- MakeRoot +Constr 2 [] +``` + +### NewNode + +- mint: + - 1 NFT with asset name equal to the data hash of the TxOutRef used as input +- outputs: + - output to the distributed merkle tree validator + +#### validation logic + +- the transaction MUST mint a value that MUST: + - have an entry for the CurrencySymbol of this minting policy + - have a single asset name entry (MUST fail otherwise) of which asset name is equal to `sha2_256( serialiseData( TxOutRef ) )` + - have quantity of 1 +- the minted token MUST go to the (hard coded) distributed merkle tree validator with `Node` datum + +### NewLeaf + +- mint: + - 1 NFT with asset name equal to the new key hash added +- outputs: + - output to the distributed merkle tree validator + +#### validation logic + +- the transaction MUST mint a value that MUST: + - have an entry for the CurrencySymbol of this minting policy + - have a single asset name entry (MUST fail otherwise) of which asset name is equal to the new key hash added + - have quantity of 1 +- the minted token MUST go to the (hard coded) distributed merkle tree validator with `Leaf` datum + +### MakeRoot + +- mint: + - 1 NFT with asset name equal to the data hash of an **hard coded** TxOutRef +- outputs: + - output to the distributed merkle tree validator + +#### validation logic + +- the transaction MUST mint a value that MUST: + - have an entry for the CurrencySymbol of this minting policy + - have a single asset name entry (MUST fail otherwise) of which asset name is equal to `sha2_256( serialiseData( TxOutRef ) )` where `TxOutRef` is hard coded in the minting policy + - have quantity of 1 +- the minted token MUST go to the (hard coded) distributed merkle tree validator with `Node` datum + +## Minting policy 2 + +> minting poilicy to be used for the account manager contract's utxos + +redeemers: +```ts +const AccountMintingPolicyRdmr = pstruct({ + NewAccount: {}, + MintAllowedSpender: {}, + BurnAllowedSpender: {} +}) +``` + +### encoding payment credentials + +at the moment of writing there exsits two types of credentials: + +1) public keys +2) scripts + +of which the `blake2b_244` hash is used on-chain + +since the payment credentials are data that once specified is not meant to change ever it is stored as NFT asset name (instead of a field of a datum that needs to be checked not to change each time) + +however asset names are just bytestrings and do not allow for choiches. + +for this reason we need a way to remember which type of credential a 28-length hash is. + +We do so by adding an header byte at the start of the bytestring so that the byte `0` impies the following 28 bytes are a public key hash, and the byte `1` implies the following 28 bytes are a script hash. + +so that +```hs +PubKeyCredential pkh +``` +becomes +``` +00 + pkh +``` + +and +```hs +ScriptCredential scriptHash +``` +becomes +``` +01 + scriptHash +``` + +> by encoding the credentials as asset names it should also be easier to query the balance corresponding to a given hash using the exsitsting off chain tools. + +### NewAccount + +- mint: + - 1 NFT with asset name equal to the payment credentials +- outputs: + - output to the account manager validator + +#### validation logic + +- txInfo redeemers field MUST: + - include an entry with `Spending` purpose and utxo that belongs to an hard coded distributed sorted merkle tree contract + - have redeemer `NewAccount` (constructor index == 0) +- mint value MUST: + - have an entry for this currency symbol + - have a single entry with asset name equal to the new credentials added + - have quantity of 1 +- output going to the account manager validator MUST: + - include the minted NFT correct datum is checked by the account manager spent with `NewAccount` redemeer) + +### MintAllowedSpender + +- mint: + - 1 NFT with asset name equal to the concatenation of teh original owner payment credentials and the allowed spender payment credentials ( 29 + 29 length bytestring ) +- outputs: + - output to the account manager validator + +#### validation logic + +- txInfo redeemers field MUST: + - include an entry with `Spending` purpose and utxo that belongs to an hard coded account manager contract + - have redeemer `Approve` (constructor index == 3) +- mint value MUST: + - have an entry for this currency symbol + - have a single entry with asset name equal to the concatenation of teh original owner payment credentials and the allowed spender payment credentials ( 29 + 29 length bytestring ) + - have quantity of 1 +- output going to the account manager validator MUST: + - include the minted NFT (correct datum is checked by the account manager spent with `Approve` redemeer) + +### BurnAllowedSpender + +- inputs: + - account manager contract input having the NFT to burn +- mint: + - (burn) 1 NFT with asset name equal to the payment credentials + +#### validation logic + +- txInfo redeemers field MUST: + - include an entry with `Spending` purpose and utxo that belongs to an hard coded account manager contract + - have redeemer `RevokeApproval` (constructor index == 4) +- mint value MUST: + - have an entry for this currency symbol + - have a single entry with asset name of length 58 ( 29 + 29 ) + - have quantity less than 0 + +## DSMerkleTree contract + +The redeemers accepted by the merkle tree validators are: +```ts +const DSMerkleTreeRdmr = pstruct({ + NewAccount: { + credsBs: bs // pub key hash or validator hash; encoded as described above (29-length bytestring) + }, + UpdateNode: {} +}); +``` + +which translates to the following `Data`s: +```hs +-- NewAccount +Constr 0 [ B credsBs ] + +-- UpdateNode +Constr 1 [] +``` + +The datums (that define the different types of node of the merkle tree) are: +```ts +const DSMerkleTreeDatum = pstruct({ + // Node includes the Root + Node: { + hash: bs, + rootAssetName: bs, // reference to the root NFT assetName + leftNodeAssetName: bs, // reference to left node NFT + rightNodeAssetName: bs,// reference to right node NFT + leftBranchMax : bs, // max key of the lowest keys branch + rightBranchMin: bs, // min key of the highest keys branch + leftBranchWeight: int, + rightBranchWeight: int, + }, + Leaf: { + credsBs: bs, // value + rootAssetName: bs, // reference to the root NFT assetName + } +}); +``` + +which translates to the following `Data`s: +```hs +-- Node +Constr 0 [ + B hash, + B rootAssetName, + B leftNodeAssetName, + B rightNodeAssetName, + B leftBranchMax, + B rightBranchMin +] + +-- Leaf +Constr 1 [ + B credsBs, + B rootAssetName +] +``` + +### proving a `credsBs` is not included on-chain + +> this can be done by any validator (in parallel using reference inputs) as the inputs required are read-only. + +- data required + - `credsBs` as the key hash to proof not to be included + - `merkleTreeRootHash` identifying teh merkle tree in which the `pkh` is not present +- reference inputs: + - all utxos from the root down to the node that has `leftBranchMax <= pkh` and `pkh <= rightBranchMin` + +#### validation logic + +- assert that `root.hash == merkleTreeRootHash` +- loop starting form `root` as current node: + - assert that the left hash and the right hash concatenated and hashed are equal to `hash` extracted from the current node + - if `pkh` is less than `leftBranchMax` + - loop over the left sub-tree + - else if `rightBranchMin` is less than `pkh` + - loop over the right sub-tree + - else if `leftBranchMax == pkh` or `rightBranchMin == pkh` + - fail + - else + - success (`leftBranchMax <= pkh <= rightBranchMin`, pkh not present in none of the branches) + +### UpdateNode + +- spending input is a node + +#### validation logic + +- tx has an input (root) which MUST: + - have a value containing an NFT with: + - the same currency symbol of this one + - asset name equal to `rootAssetName` of the datum on this node + - quantity equal to 1 + - be spent with `NewAccount` redeemer +- the NFT is present in an output going back to the same validator, the output MUST: + - NOT have any other assets from the same currecy symbol other than its own + - NOT change the `rootAssetName` field in the datum + +### NewAccount + +the process adds 1 `Node` and 1 `Leaf` nodes + +- spending input is the root +- inputs: + - all nodes that needs to be updated spent with `UpdateNode` redeemer +- outputs: + - one output per each node spent with `UpdateNode` + - the updated root (with the same rules of the `UpdateNode` redeemer) + - the new `Leaf` + - the new `Node` having the new `Leaf` and its sibling as left and right nodes (in order) + - a new UTxO at the accoun manager contract with datum `Account` and `amount` field equal to 0 + +#### validation logic + +- proof that the `pkh` to add is not already included in the merkle tree; if succeeds remember the last node checked else the contract fails the transaction. +- given the last node checked in the proof, the node must be updated such that: + - a new `Node` is added as root of the branch with lower weight, this MUST: + - have the new `Leaf` node with the added `pkh` as inner child + - have the old branch as outer child + - the `leftBranchMax` or `rightBranchMin` is updated to the new `pkh` according to the branch actually updated +- starting from the last added node to the root, check that the output have the correct hash for each node + +## AccountManager contract + +The datums (that define the different types of node of the merkle tree) are: +```ts +const AccounManagerDatum = pstruct({ + Account: { + // credentials: bs, // stored as asset name + amount: int + }, + AllowedSpender: { + // originalOwner: bs, // stored as first 29 bytes in the asset name + // spender: bs, // stored as second 29 bytes in the asset name + remainingAmount: int + } +}); +``` + +which translates to the following `Data`s: +```hs +-- Account +Constr 0 [ I amount ] + +-- AllowedSpender +Constr 1 [ + B originalOwner, + B spender, + I remainingAmount +] +``` + +The redeemers accepted by the account managers validators are: +```ts +const AccounManagerRdmr = pstruct({ + Mint: { // or Burn if amount is negative + account: PCredential.type, + amount: int + }, + Transfer: { + to: PCredential.type, + amount: int + }, + TransferFrom: { + from: PCredential.type, + to: PCredential.type, + amount: int + }, + Approve: { + spender: PCredential, + maxAmount: int + }, + RevokeApproval: {}, + Receive: {}, + UseApprovedSpender: {} +}); +``` + +which translates to the following `Data`s: +```hs +-- Mint +Constr 0 [ + PCredential, + I amount +] + +-- Transfer +Constr 1 [ + PCredential, + I amount +] + +-- TransferFrom +Constr 2 [ + PCredential, + PCredential, + I amount +] + +-- Approve +Constr 3 [ + PCredential, + I maxAmount +] + +-- RevokeApproval +Constr 4 [] + +-- Receive +Constr 5 [] + +-- UseApprovedSpender +Constr 6 [] +``` + +### extract typed credentials from an account NFT asset name + +- extract the head and the tail from the bytestring asset name + - if the head is 0 the tail represents a public key hash + - else if the head is 1 the tail represents a validator hash + - else the validator SHOULD fail + +### Mint + +- inputs: + - input with: (input being validated) + - `Account` datum + - NFT's asset name matching the one in the redeemer +- outputs: + - output with the same native assets value of the input + +#### validation logic + +- the input MUST: + - have an `Account` datum + - have a value containing the NFT with the asset name matching the `PCredential`s specified in the `Mint` redeemer + - if the `PCredential`s are of a validator the tx inputs MUST include an input from that validator hash +- the output to the spender MUST: + - have an `Account` datum of which: + - the `amount` field is equal to the `amount` field from the spender input's datum added to the `amount` field of the redeemer + - the validation MUST fail if the the result of the addition is negative + - have a native assets value that MUST: + - have an entry for the NFT currency symbol + - have a single entry (MUST fail otherwhise) with the same asset name of the input being validated + - have quantity of 1 + - go back to the account manager validator + +### Transfer + +- inputs: + - the spender input (input being vaildated) + - (optional) input of a the validator with hash matching the one in the credetials if the spender's credentials are not a public key hash + - the receiver input (with `Receive` redeemer) +- outputs + - output with the spender account + - output with the receiver account + +#### validation logic + +- the spender input MUST: + - have an `Account` datum + - have a value containing the NFT with the asset name matching the `PCredential`s of the spender + - if the `PCredential`s of the utxo are of a validator the tx inputs MUST include an input from that validator hash +- the receiver input MUST: + - be spent with the `Receive` redeemer + - have a value containing the NFT with the asset name matching the `PCredential`s of the receiver (specified in the redeemer) +- check the NFT asset name against the spender input credentials as explained above + - assert that requred signers from the tx infos MUST include a key equal to the tail if the credentials are a public key hash + - assert that at least one of the inputs that are not from the account manager contract MUST be from the validator of which hash is equal to the tail if the credentials are a validator hash +- check the NFT asset name against the receiver input credentials as explained above + - fail if it doesn't match +- the output to the spender MUST: + - have an `Account` datum of which: + - the `amount` field is equal to the `amount` field from the spender input's datum minus the `amount` field of the redeemer + - the validation MUST fail if the input amount is less than the spending amount + - and the value that MUST: + - have an entry for the NFT currency symbol + - have a single entry (MUST fail otherwhise) with the same asset name of the spender input + - have quantity of 1 + - go back to the account manager validator +- the output to the receiver MUST: + - have an `Account` datum of which: + - the `amount` field is equal to the `amount` field from the spender input's datum plus the `amount` field of the redeemer + - and the value that MUST: + - have an entry for the NFT currency symbol + - have a single entry (MUST fail otherwhise) with the same asset name of the receiver input + - have quantity of 1 + - go back to the account manager validator + +### TransferFrom + +- inputs: + - the spender input (input being vaildated) + - (optional) allowed spender input (with `UseApprovedSpender` redeemer) + - (optional) input of a the validator if the spender credentials are not a public key hash + - the receiver input (with `Receive` redeemer) +- outputs + - output with the spender account + - (optional) allowed spender output ( if present between the inputs ) + - output with the receiver account + +#### validation logic + +- the spender input MUST: + - have an `Account` datum + - have a value containing the NFT with the asset name matching the `PCredential`s of the spender + - if the `PCredential`s of the utxo are of a validator the tx inputs MUST include an input from that validator +- the receiver input MUST: + - be spent with the `Receive` redeemer + - have a value containing the NFT with the asset name matching the `PCredential`s of the receiver (specified in the redeemer) +- check the NFT asset name against the spender input credentials as explained above + - assert that requred signers from the tx infos MUST include a key equal to the tail if the credentials are a public key hash + - assert that at least one of the inputs that are not from the account manager contract MUST be from the validator of which hash is equal to the tail if the credentials are a validator hash +- the output to the spender MUST: + - have an `Account` datum of which: + - the `amount` field is equal to the `amount` field from the spender input's datum minus the `amount` field of the redeemer + - the validation MUST fail if the input amount is less than the spending amount + - and the value that MUST: + - have an entry for the NFT currency symbol + - have a single entry (MUST fail otherwhise) with the same asset name of the spender input + - have quantity of 1 + - go back to the account manager validatorv +- the output to the receiver MUST: + - have an `Account` datum of which: + - the `amount` field is equal to the `amount` field from the spender input's datum plus the `amount` field of the redeemer + - and the value that MUST: + - have an entry for the NFT currency symbol + - have a single entry (MUST fail otherwhise) with the same asset name of the receiver input + - have quantity of 1 + - go back to the account manager validator + +### Approve + +- inputs: + - input having an `Account` datum (input being validated) +- mint: + - an NFT with the credentials of the original owner and the credentials of the allowed spender as asset name +- outputs: + - output with preserving the approver account + - output with with `AllowedSpender` datum + +#### validation logic + +- the input being validated MUST: + - have an `Account` datum + - have a value containing the NFT with the asset name matching the `PCredential`s of the spender + - if the `PCredential`s of the utxo are of a validator the tx inputs MUST include an input from that validator hash +- the output preserving the approver account MUST: + - have the **exact same** datum as the validation input (`(builtin equalsData)` can be used for the purpose) + - have the **exact same** NFT from the input + - go back to the accoun manager validator +- the tx MUST mint an NFT with asset name equal to the concatenation of the input account asset name and the `spender` credentials (redeemer field) in the form of asset name as explained in the second minting poilicy +- the output with `AllowedSpender` datum MUST: + - have an `AllowedSpender` datum with: + - `remainingAmount` field that MUST be greather than 0 AND equal to the `maxAmount` field of the redeemer + - have a value with the NFT minted in this transaction + +### RevokeApproval + +- inputs: + - input having an `AllowedSpender` datum (input being validated) +- mint: + - (burn) an NFT with the credentials of the original owner and the credentials of the allowed spender as asset name + +#### validation logic + +- the input being validated MUST: + - have an `AllowedSpender` datum + - have a value containing the NFT with the asset name of length 58 + - extract the original owner credentials (first 29 bytes of the assetname) and make sure that it either signed the transaction if pkh or an input is included if a validator Hash +- the mint field MUST: + - have an entry for the validating input NFT + - have an entry for the asset name of the validating input NFT + - have quantity less than 0 + +### Receive + +- inputs: + - the receiver input (input being validated) +- outputs: + - output with the same native asset value of the input + +#### validation logic + +- the receiver input MUST: + - have an `Account` datum + - be used with an other input from the same account manager validator spent either with redeemer `Transfer` or with redeemer `TransferFrom` (using the `redeemers` field from the tx infos) + - have a value that MUST: + - have an entry for the NFT currency symbol +- the output MUST: + - have an `Account` datum with: + - an `amount` field samller or equal than the `amount` field present on the validating input's datum + - have value that MUST: + - have an entry for the NFT currency symbol + - have a single entry (MUST fail otherwhise) with the same asset name of the receiver input + - have quantity of 1 + +### UseApprovedSpender + +- inputs: + - the receiver input (input being validated) +- outputs: + - output with the same native asset value of the input + +#### validation logic + +- the receiver input MUST: + - have an `AllowedSpender` datum + - be used with an other input from the same account manager validator spent with redeemer `TransferFrom` (using the `redeemers` field from the tx infos) + - have a value that MUST: + - have an entry for the NFT currency symbol +- the output MUST: + - have an `AllowedSpender` datum with: + - an `amount` field greather or equal than the `amount` field present on the validating input's datum + - exact same `originalOwner` field as in the input being validated + - exact same `spender` field as in the input being validated + - have value that MUST: + - have an entry for the NFT currency symbol + - have a single entry (MUST fail otherwhise) with the same asset name of the receiver input + - have quantity of 1 + +## Rationale: how does this CIP achieve its goals? + + +## Path to Active + +### Acceptance Criteria + + +- having at least one instance of the smart contracts described on: + - mainnet + - preview testnet + - preprod testnet +- having at least 2 different wallets integrating meta asset functionalities, mainly: + - displayning balance of a specified meta asset if the user provides the address of the respecive account manager contract + - transaction creation with `Transfer`, `Approve` and `RevokeApproval` redeemers + +### Implementation Plan + + +## Copyright + + +[CC-BY-4.0]: https://creativecommons.org/licenses/by/4.0/legalcode +[Apache-2.0]: http://www.apache.org/licenses/LICENSE-2.0 \ No newline at end of file diff --git a/CIP-0113/deprecated/v1.md b/CIP-0113/deprecated/v1.md new file mode 100644 index 0000000000..34c8404e40 --- /dev/null +++ b/CIP-0113/deprecated/v1.md @@ -0,0 +1,520 @@ +--- +CIP: 113 +Title: Programmable token-like assets +Category: Tokens +Status: Proposed +Authors: + - Michele Nuzzi + - Harmonic Laboratories + - Matteo Coppola +Implementors: [] +Discussions: + - https://github.com/cardano-foundation/CIPs/pull/444 +Created: 2023-01-14 +License: CC-BY-4.0 +--- + +## Abstract +This CIP proposes a standard that if adopted would allow the same level of programmability of other ecosistems at the price of the token true ownership. + +This is achieved imitating the way the account model works laveraging the UTxO structure adopted by Cardano. + +## Motivation: why is this CIP necessary? + +This CIP proposes a solution at the Cardano Problem Statement 3 ([CPS-0003](https://github.com/cardano-foundation/CIPs/pull/382/files?short_path=5a0dba0#diff-5a0dba075d998658d72169818d839f4c63cf105e4d6c3fe81e46b20d5fd3dc8f)). + +If adopted it would allow to introduce the programmability over the transfer of tokens (meta-tokens) and their lifecycle. + +The solution proposed includes (answering to the open questions of CPS-0003): + +1 and 2) very much like account based models, wallets supporting this standard will require to know the address of the smart contract (validator) + +3) the solution can co-exist with the existing native tokens + +4) the implementation is possible without hardfork since Vasil + +5) optimized implementations SHOULD NOT take significant computation, especially on transfers. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL +NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and +"OPTIONAL" in this document are to be interpreted as described in +[RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119). + +In the specification we'll use the haskell data type `Data`: +```hs +data Data + = Constr Integer [Data] + | Map [( Data, Data )] + | List [Data] + | I Integer + | B ByteString +``` + +and we'll use the [plu-ts syntax for structs definition](https://pluts.harmoniclabs.tech/docs/onchain/Values/Structs/definition#pstruct) as an abstraction over the `Data` type. + +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)); + +Unlike the ERC20 standard; this CIP: + +- allows for multiple entries with the same key (same credentials can be used for multiple accounts) +- DOES NOT include an equivalent of the `transferFrom` method; if needed it can be added by a specific implementation but it won't be considered part of the standard. + +> **NOTE** +> +> the UTxO model allows for multiple transfers in one transaction +> +> 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 interoperable and easy to understand and implement +> in this first implementation we restricts transfers from a single account to a single account +> +> if necessary; this restriction might be dropped in a future version of the CIP + + +The implementation requires + +- a parameterized minting policy to validate the creation of new accounts (in this CIP also referred as `accountFactory`) +- a spending validator to manage the accounts (in this CIP also referred as `accountManager`) + +### standard data types used + +We'll need to use some data types as defined in the script context; + +```ts +const PTxId = pstruct({ + PTxId: { txId: bs } +}); + +const PTxOutRef = pstruct({ + PTxOutRef: { + id: PTxId.type, + index: int + } +}); + +const PCredential = pstruct({ + PPubKeyCredential: { pkh: bs }, + PScriptCredential: { valHash: bs }, +}); + +const PCurrencySymbol = palias( bs ); +``` +which are equivalent to the data: +```hs +-- PTxId +Constr 0 [ B txId ] + +--- PTxOutRef +Constr 0 [ PTxId, I index ] + +-- PPubKeyCredential +Constr 0 [ B pkh ] + +-- PScriptCredential +Constr 1 [ B valHash ] + +-- PCurrencySymbol +B currencySym +``` + +### `accountFactory` (minting policy) + +The `accountFactory` contract is responsabile of validating the creation of new accounts. + +### validation logic + +For the creation of the account to be considered valid (the minting policy suceeds) +it MUST result in a single asset minted going to an address with payment credentials equalst to an hard-coded (parametrized) `accountManager` contract, +and it is RECOMMENDED to have stake credentials equals to the stake cretentials of the user (if any) that SHOULD be inferred by the input utxo chosen to derive the name of the asset minted. + +The choice to preserve the stake credentials is done to facilitate the query of the user's accounts in the offchain. + +the standard redeemer for the `accountFactory` MUST have at least one constructor (with index `0`) with a single field being an integer, +used to determine which input is intended to be of the user. + +the minimal definition would then be: +```ts +const AccountFactoryRdmr = pstruct({ + New: { inputIdx: int } +}); +``` + +the input at the index given by the redeemer is used to deterime `userUtxoRef` (the utxo ref of the input) and `userUtxo` (the resolved input) + +It is then RECOMMENDED to include a second constructor for the redeemer meant to signal the burning of an asset under that policy, +so a more complete definition would be +```ts +const AccountFactoryRdmr = pstruct({ + New: { inputIdx: int }, // Mint + Delete: {} // Burn, not required by standard +}); +``` + +the data needed from the `ScriptContext` includes the following fields: + +- `inputs` +- `output` +- `mint` + +used as follows: + +- all the transaction inputs MUST NOT include any tokens having the same currency symbol of the `accountFactory` minting policy; +optionally a specific implementation MAY require a fixed number of inputs (eg. a single input) for performance reasons. + +- when minting assets, only a single token under the policy MUST be minted (indipendently from the number of inputs) +- the only asset minted MUST have an unique asset name (we suggest using the output of `[(builtin sha2_256) [(builtin serialiseData) userUtxoRef]]` where `userUtxoRef` is the utxo reference (or `PTxOutRef`) of the very first input of the transaction) + +- the asset minted MUST be included in an output of the transaction being validated going to the address as specified above (MUST have payment credentials of the the hard-coded `accountManager` validator, and SHOULD have stake credtentials of the user if any). + +- the output going to the `accountManager` validator MUST implement the following checks (in addition to the presence of the minted asset): + - the address' payment credential MUST have `ScriptCredential` constructor (constructor index `1`) and validator hash MUST equal the hard coded `accountManager` hash + - the value attached to the UTxO MUST only have 2 entries (ADA and minted token) to prevent DoS by token spam. + - the output datum MUST: + - be constructed using the `InlineDatum` consturctor (consturctor index `2`) + - have datum value with the [`Account` structure (explained below)](#Account) with the following fields: + - amount: `0` + - currencySym: currency symbol of the `accountFactory` minting policy (own currency symbol) + - credentials: the `userUtxo` address payment credentials (both public key hash and validator hash supported) + - state: no restrictions (up to the specific implementaiton). + +### `accountManager` (spending validator) + +The `accountManager` contract is responsabile of managing exsiting accounts and their balances. + +This is done using some standard redeemers and optionally some implementation-specfic ones. + +### `Account` + +The `Account` data type is used as the `accountManager` datum; and is defined as follows: + +```ts +const Account = pstruct({ + Account: { + amount: int, + credentials: PCredential.type, + currencySym: PCurrencySymbol.type, + state: data + } +}); +``` + +> **NOTE:** +> +> the `state` field is indicated here as `data`, meaning that anything that is data-like can be used; +> the standard redeemers will only check for not to change; +> +> for the `state` field to be changed, some implementation-specific redeemer MAY be added +> +> example; this is considered a valid datum definition by the standard: +> +> ```ts +> // implementation-specific state +> const FreezeableAccountState = pstruct({ +> Ok: {}, // Constr index 0; free to spend +> Frozen: {} // Constr index 1; frozen +> }); +> +> const FreezeableAccount = pstruct({ +> Account: { +> amount: int, +> credentials: PCredential.type, +> currencySym: PCurrencySymbol.type, +> state: FreezeableAccountState.type +> } +> }); +> ``` + +### `AccountManagerRedeemer` + +The `AccountManagerRedeemer` is used to comunicate the contract the intention with which the utxo is being spent. + +It MUST define 4 standard redeemers; none of which is meant to manipulate the state of the spending account. + +for this reason a specific implementation will likely have more than 4 possible redeemers that will NOT be considered standard +(eg. a wallet implementing an interface SHOULD NOT depend on the exsistence of these additional redeemers). + +The minimal `AccountManagerRedeemer` is: + +```ts +const AccountManagerRedeemer = pstruct({ + Mint: { // or Burn if `amount` is negative + amount: int + }, + Transfer: { + to: PCredential.type, + amount: int + }, + Receive: {}, + ForwardCompatibility: {} +}); +``` + +The validation logic is different for each redeemer. + +#### Common operations and values + +Before proceeding with the redeemers validation logic here are some common operations and values between some of the redeemers. + +##### `ownUtxoRef` + +the `accountManager` contract is meant to be used only as spending validator. + +As such we can extract the utxo being spent from the `ScriptPurpose` when constructed with the `Spending` constructor and fail for the rest. + +```ts +const ownUtxoRef = plet( + pmatch( ctx.purpose ) + .onSpending(({ utxoRef }) => utxoRef) + ._( _ => perror( PTxOutRef.type ) ) +); +``` + +##### `validatingInput` + +is the input with `utxoRef` field equivalent to `ownUtxoRef` + +```ts +const validatingInput = plet( + pmatch( + tx.inputs.find( i => i.utxoRef.eq( ownUtxoRef ) ) + ) + .onJust(({ val }) => val.resolved ) + .onNothing(_ => perror( PTxOut.type ) ) +); +``` + +##### `ownCreds` + +from the `validatingInput`: + +```ts +validatingInput.resolved.address.credential +``` + +##### `ownValue` + +from the `validatingInput`: + +```ts +validatingInput.resolved.value +``` + +##### `isOwnOutput` + +given an output; we recongize the output as "own" if the attached credentials are equivalent to `ownCreds` +and the attached value includes an entry for the `currencySym` field in the specified in the datum (making sure the same `accountFactory` was used). + +> **NOTE:** +> +> We compare only the payment credentials; NOT the entire address +> +> outputs that are under different stake credentials are meant only to facilitate the offchain queries, but still considered as "own" + +```ts +const isOwnOutput = plet( + pfn([ PTxOut.type ], bool ) + ( out => + out.address.credential.eq( ownCreds ) + // a single account manager contract might handle multiple tokens + .and( + out.value.some( ({ fst: policy }) => policy.eq( account.currencySym ) ) + ) + ) +); +``` + +##### `isOwnInput` + +an input is "own" if the resolved field is "own"; + +```ts +const isOwnInput = plet( + pfn([ PTxInInfo.type ], bool ) + ( input => isOwnOutput.$( input.resolved ) ) +); +``` + +##### `isOwnCurrencySym` + +given an asset policy we might want to know if it is the one being validated. + +this is done by comparing it with the one specified in the datum field + +```ts +const isOwnCurrSym = plet( account.currencySym.peq ); +``` + +##### `outIncludesNFT` + +given a transaction output is useful to check if the value includes the NFT generated from the `accountFactory`; + +this is done by checking that at least one of the attached value's entry satisfies `isOwnCurrencySym` for the policy. + +```ts + const outIncludesNFT = plet( + pfn([ PTxOut.type ], bool ) + ( out => out.value.some( entry => isOwnCurrSym.$( entry.fst ) ) ) +); +``` + +##### `ownOuts` + +transaction outputs filtered by `isOwnOutput`; + +##### `ownInputs` + +transaction inputs filtered by `isOwnInput`; + +##### updating the `Account` datum + +Between all the standard redeemers it MUST be checked that the datum fields `credentials`, `currencySym` and `state` remain unchanged compared to the current datum. + +the `amount` field MAY change according to the purpose of the redeemer. + +This MAY NOT be true for any additional implementation-specific redeemer. + +#### `Mint` + +the contract being called using the `Mint` redeemer MUST succeed only if the following conditions are met: + +> **NOTE** +> +> we use `mint.amount` to describe the value of the `amount` field included in the `Mint` redeemer +> and `account.amount` to describe the value of the `amount` field included in the input `Account` datum. + +- there MUST be only a single input is from the `accountManager` validator (aka. `ownInputs.length.eq( 1 )`); + +- the minted value in the transaction (`ctx.tx.mint`) MUST NOT include any entry with `PCurrencySymbol` +equivalent to the one specified in the `currencySym` field of the `Account` datum (aka. no accounts are created) + +```ts +const noAccountsCreated = pisEmpty.$( + tx.mint.filter( isOwnAssetEntry ) +); +``` + +- the input value coming from the `accountManager` MUST include an entry with `PCurrencySymbol` +equivalent to the one specified in the `currencySym` field of the `Account` datum. + +- to prevent DoS by token spamming the output going back to the `accountManager` MUST have at most length 2. + +- there MUST be a single output going back to the `accountManager` contract with the following properties + - it MUST have an entry for the `PCurrencySymbol` specified in the `currencySym` field of the `Account` (aka. NFT is preserved) + - the output datum MUST be constructed using the `InlineDatum` constructor + - the datum fields `credentials`, `currencySym` and `state` MUST be unchanged compared to the current datum. + - the datum field `account.amount` MUST change based on `mint.amount` as described below: + - if the `mint.amount` is positive the output datum MUST + be equal to the sum of `account.datum` and `mint.datum` + - if the `amount` field included in the `Mint` redeemer is negative (aka. we are burning) + the contract MUST FAIL if the sum of `mint.amount` and `account.amount` is strictly less than `0` (aka. burning more assets than how much the `Account` holds); + otherwhise it MUST check for the output datum `amount` field to be equal to the result of the sum + +#### `Transfer` + +The `Transfer` redeemer is used to pay an other account in the same account manager. + +When used as redeemer the contract checks for the following conditions to be true; + +- `ownInputs` to be of lenght `2` + +- `ownOuts` to be of lenght `2` + +- to prevent DoS by tokne spam, every `ownOut` value must have length of `2` + +- the receiver input (the `ownInput` with different `utxoRef` of the `validatingInput`) +being spent with `Receive` redeemer + +- the receiver credentials present in the receiver datum (`receiverAccount.credentials`) MUST be equal to the `transfer.to` credentials present in the redeemer +(this allows for other potential contracts in the same transaction to only need to check the `Transfer` redeemer in order to understand who are the sender and the receiver) + +- the sender input (`validatingInput`) MUST have the NFT according to `account.currencySym` (the input is valid) + +- the output with the sender's credentials MUST preserve the NFT + +- the sender account fields `credentials`, `currencySym` and `state` are unchanged between sender input datum and sender output datum + +- the receiver account fields `credentials`, `currencySym` and `state` are unchanged between receiver input datum and receiver output datum + +- the input sender's amount field is greather or equal than the redeemer amount (`transfer.amount`) + +- the output sender's amount field is equal to the input one minus `transfer.amount` + +- the output receiver's amount field is equal to the input one plus `transfer.amount` + +- the sender singned the transaction (included in `ctx.tx.signatories` if a `PPubKeyHash` or included a script input if a `PValidatorHash`) + +any additional check can be made based on the `account.state` (implementation specific) + +#### `Receive` + +- `ownInputs` to be of lenght `2` + +- the `validatingInput` includes the NFT under the datum's `currencySym` field + +- the sender input (the `ownInput` with different `utxoRef` of the `validatingInput`) +being spent with `Transfer` redeemer + +- in the sender input `Transfer` redeemer the `amount` field is strictly grather than 0 + +> **NOTE** +> +> the sender input being an `ownInputs` element and being spent with `Transfer` redeemer +> implies that the transfer calculation is running in that validation +> +> hence we don't need to perform the same calculation here + +#### `ForwardCompatibility` + +For the current version this redeemer SHOULD always fail. + +The main purpose of the redeemer is to avoid breaking compatibilities for addtional implementation specific redeemers + +## Rationale: how does this CIP achieve its goals? + + +The [first proposed implementation](https://github.com/cardano-foundation/CIPs/pull/444/commits/525ce39a89bde1ddb62e126e347828e3bf0feb58) was quite different by the one shown in this document + +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 + +this path was abandoned due to the logaritmic cost of creation of accounts, on top of the complexity. + +Other crucial difference with the first proposed implementation was in the `accountManager` redeemers; +which included definitions for `TransferFrom`, `Approve` and `RevokeApproval` redeemers, aiming to emulate ERC20's methods of `transferFrom` and `approve`; + +after [important feedback by the community](https://github.com/cardano-foundation/CIPs/pull/444#issuecomment-1399356241), it was noted that such methods would not only have been superfluous, but also dangerous, and are hence removed in this specification. + +The proposal does not affect backward compatibilty being the first proposing a standard for programmability over transfers; + +exsisting native tokens are not conflicting for the standard, and instead are making it possible; +it would be infact much harder to prove the validity of an utxo without a native token on it. + +## Path to Active + +### Acceptance Criteria + + +- having at least one instance of the smart contracts described on: + - mainnet + - preview testnet + - preprod testnet +- having at least 2 different wallets integrating meta asset functionalities, mainly: + - displayning balance of a specified meta asset if the user provides the address of the respecive account manager contract + - transaction creation with `Transfer` redeemers + +### Implementation Plan + + +- [x] [PoC implementation](https://github.com/HarmonicLabs/erc20-like) +- [x] [showcase transactions](https://github.com/HarmonicLabs/erc20-like) +- [ ] wallet implementation + +## Copyright + +This CIP is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/legalcode). \ No newline at end of file diff --git a/CIP-0113/deprecated/v2.md b/CIP-0113/deprecated/v2.md new file mode 100644 index 0000000000..fb3072eab8 --- /dev/null +++ b/CIP-0113/deprecated/v2.md @@ -0,0 +1,578 @@ +--- +CIP: 113 +Title: Programmable token-like assets +Category: Tokens +Status: Proposed +Authors: + - Michele Nuzzi + - Harmonic Laboratories + - Matteo Coppola +Implementors: [] +Discussions: + - https://github.com/cardano-foundation/CIPs/pull/444 +Created: 2023-01-14 +License: CC-BY-4.0 +--- + +## Abstract +This CIP proposes a standard that if adopted would allow the same level of programmability of other ecosistems at the price of the token true ownership. + +This is achieved imitating the way the account model works laveraging the UTxO structure adopted by Cardano. + +## Motivation: why is this CIP necessary? + +This CIP proposes a solution at the Cardano Problem Statement 3 ([CPS-0003](https://github.com/cardano-foundation/CIPs/pull/382/files?short_path=5a0dba0#diff-5a0dba075d998658d72169818d839f4c63cf105e4d6c3fe81e46b20d5fd3dc8f)). + +If adopted it would allow to introduce the programmability over the transfer of tokens (meta-tokens) and their lifecycle. + +The solution proposed includes (answering to the open questions of CPS-0003): + +1) very much like account based models, wallets supporting this standard will require to know the address of the smart contract (validator) + +2) the standard defines what is expected for normal transfer transaction, so that wallets can independently construct transactions + +3) the solution can co-exist with the existing native tokens + +4) the implementation is possible without hardfork since Vasil + +5) optimized implementations SHOULD NOT take significant computation, especially on transfers. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL +NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and +"OPTIONAL" in this document are to be interpreted as described in +[RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119). + +In the specification we'll use the haskell data type `Data`: +```hs +data Data + = Constr Integer [Data] + | Map [( Data, Data )] + | List [Data] + | I Integer + | B ByteString +``` + +we'll use the [plu-ts syntax for structs definition](https://pluts.harmoniclabs.tech/onchain/Values/Structs/definition#pstruct) as an abstraction over the `Data` type. + +finally, we'll use [`mermaid`](https://mermaid.js.org/) to help with the visualization of transactions and flow of informations. + +In order to do so we introduce here a legend + +### Legend + +```mermaid +flowchart TD + + subgraph relations + direction LR + + a[ ] -.-> b[ ] + c[ ] <-.-> d[ ] + + style a fill:#FFFFFF00, stroke:#FFFFFF00; + style b fill:#FFFFFF00, stroke:#FFFFFF00; + style c fill:#FFFFFF00, stroke:#FFFFFF00; + style d fill:#FFFFFF00, stroke:#FFFFFF00; + + end + style relations fill:#FFFFFF00, stroke:#FFFFFF00; + + subgraph entities + direction LR + + A[speding contract] + pkh((pkh signer)) + end + style entities fill:#FFFFFF00, stroke:#FFFFFF00; + + subgraph scripts + direction LR + + mint[(minting policy)] + obs([withdraw 0 observer]) + end + style scripts fill:#FFFFFF00, stroke:#FFFFFF00; + + + subgraph utxos + direction LR + + subgraph input/output + direction LR + inStart[ ] --o inStop[ ] + + style inStart fill:#FFFFFF00, stroke:#FFFFFF00; + style inStop fill:#FFFFFF00, stroke:#FFFFFF00; + end + + subgraph ref_input + direction LR + refInStart[ ]-.-orefInStop[ ] + + style refInStart fill:#FFFFFF00, stroke:#FFFFFF00; + style refInStop fill:#FFFFFF00, stroke:#FFFFFF00; + end + + subgraph minted_asset + direction LR + mntInStart[ ] -.- mntInStop[ ] + + style mntInStart fill:#FFFFFF00, stroke:#FFFFFF00; + style mntInStop fill:#FFFFFF00, stroke:#FFFFFF00; + end + + subgraph burned_asset + direction LR + brnInStart[ ] -.-x brnInStop[ ] + + style brnInStart fill:#FFFFFF00, stroke:#FFFFFF00; + style brnInStop fill:#FFFFFF00, stroke:#FFFFFF00; + end + + style input/output fill:#FFFFFF00, stroke:#FFFFFF00; + style ref_input fill:#FFFFFF00, stroke:#FFFFFF00; + style minted_asset fill:#FFFFFF00, stroke:#FFFFFF00; + style burned_asset fill:#FFFFFF00, stroke:#FFFFFF00; + end + style utxos fill:#FFFFFF00, stroke:#FFFFFF00; + + + subgraph transactions + direction LR + + subgraph Transaction + .[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + end + + subgraph transaction + direction LR + start[ ] --> stop[ ] + + style start fill:#FFFFFF00, stroke:#FFFFFF00; + style stop fill:#FFFFFF00, stroke:#FFFFFF00; + end + style transaction fill:#FFFFFF00, stroke:#FFFFFF00; + + end + style transactions fill:#FFFFFF00, stroke:#FFFFFF00; + + + +``` + +### 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)); + +Unlike the ERC20 standard; this CIP: + +- allows for multiple entries with the same key (same credentials can be used for multiple accounts, though not particularly useful) +- DOES NOT include an equivalent of the `transferFrom` method; if needed it can be added by a specific implementation but it won't be considered part of the standard. +- allows for multiple inputs (from the same sender, scripts and multisigs MAY also be used) and multiple outputs (possibly many distinct reveivers) + +> **NOTE: Multiple Inputs** +> +> the UTxO model allows for multiple transfers in one transaction +> +> 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 +> +> we MUST however account for multiple inputs from the same sender due to +> the creation of new UTxOs in receiving transactions. + +### Design + +with the introduction of [CIP-69](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0069) in Plutus V3 the number of contracts required are only 2, covering different purposes. + +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 +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) + - withdraw validator to validate the spending of multiple utxos. + + +```mermaid +flowchart LR + subgraph transferManager + transferManagerPolicy[(transfer manager policy)] + transferManagerContract[transfer manager] + transferManagerObserver([transfer manager observer]) + + transferManagerPolicy -. mints CNTs .-> transferManagerContract + + transferManagerContract <-. validates many inputs .-> transferManagerObserver + + transferManagerContract -- transfer --> transferManagerContract + + end + + subgraph stateManager + stateManagerPolicy[(state manager policy)] + stateManagerContract[state manager] + + stateManagerPolicy -. mints validity NFTs .-> stateManagerContract + + stateManagerContract -- mutates state --> stateManagerContract + end + + stateManager -. hash .-> transferManager +``` + +an external contract that needs to validate the transfer of a programmable token should be able to get all the necessary informations about the transfer by looking for the redeemer with withdraw purpose and `transferManager` stake credentials. + + +### Datums and Redeemers + +#### `Account` + +The `Account` data type is used as the `stateManager` datum; and is defined as follows: + +```ts +const Account = pstruct({ + Account: { + credentials: PCredential.type, + state: data + } +}); +``` + +The only rule when spending an utxo with `Account` datum is that the NFT present on the utxo +stays in the same contract. + +The standard does not impose any rules on the redeemer to spend the utxo, +as updating the state is implementation-specific. + +#### `TransferManagerDatum` + +The `TransferManagerDatum` data type is used as the `transferManager` utxo datum; and is defined as follows: + +```ts +const TransferManagerDatum = pstruct({ + TransferManagerDatum: { + userStateTokenName: PTokenName.type, // alias of bytestring + state: PMaybe( data ).type // wallet may use `Nothing` constructor + } +}); +``` + +the purpose of `userStateTokenName` is to prevent the user from creating a new account at the `stateManager` with same credentials and use it for this same utxo. + +This makes sure only one `Account` is valid for a given utxo, even if possibly many `Account`s may exsist for the same credentials. + +A specific implementation MAY additionaly implement other checks to ensure unique accounts, such as centralized actors, or merkle-patricia trees. + +#### `TransferRedeemer` + +redeemer to be used in the withdraw 0 contract +when spending (possibly many) utxos from the `transferManager` contract. + +> NOTE: +> +> there is no standard datum for the `transferManager` since the utxos might not have a datum at all +> +> + +```ts +const TransferOutput = pstruct({ + TransferOutput: { + credential: PCredential.type, + amount: int, + stateIndex: int + } +}); + +const TransferRedeemer = pstruct({ + Transfer: { + totInputAmt: int, + outputs: list( TransferOutput.type ), + stateIndex: int + firstOutputIndex: int, + // inputIndicies: list( int ), // filter + } +}); +``` + +#### `SingleTransferRedeemer` + +redeemer to be used on a single utxo of the `transferManager`; + +```ts +const SingleTransferRedeemer = pstruct({ + Transfer: {}, + Burn: {}, +}); +``` + + +### Transactions + +#### New Account Generation Transaction + +```mermaid +flowchart LR + + stateManagerPolicy[(state manager policy)] + + subgraph transaction + .[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + end + + stateManagerContract[state manager] + + stateManagerPolicy -. validity NFTs .-> transaction + --o stateManagerContract +``` + +The purpose of the "New Account generation" transaction is to create a new account for a user and validate the initial state of an account. + +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. + +The name of the asset MUST be derived from the first input's utxo reference **spent** by the minting transaction. + +in particular MUST be the result of applying the serialised ([`serialiseData CIP`](https://github.com/cardano-foundation/CIPs/blob/125ac054179074d832927ec9f5ff53f46098be4a/CIP-0042/README.md) and [opinionated standard serialization in Haskell](https://github.com/IntersectMBO/plutus/blob/1f31e640e8a258185db01fa899da63f9018c0e85/plutus-core/plutus-core/src/PlutusCore/Data.hs#L108)) utxo reference according to the [standard plutus v3 onchain type definition](https://github.com/IntersectMBO/plutus/blob/ed3799004eaf2040e7f978d6ab83209c2bac8157/plutus-ledger-api/src/PlutusLedgerApi/V3/Tx.hs#L66) (where `txId` is an alias of a bytestring, and NOT a bytestring wrapped in a constr 0 like previous plutus versions) to the builtin `sha3_256`. + +In textual uplc: + +```uplc +(lam utxoRefData + [ + (builtin sha3_256) + [ + (builtin serialiseData) + utxoRefData + ] + ] +) +``` + +**at all times** only **one (1)** NFT under `stateManager` policy MUST be present on a `stateManager` utxo. + +The transaction MUST have one output going to the `stateManager` having correct `Account` **inline** datum. + +by correct `Account` datum is implied the data is a constructor with index 0, first field being the credentials of the user, and second field being the state associated. + +Additional checks regarding the state are left to the implementation. + +#### Account State Update + +```mermaid +flowchart LR + + subgraph transaction + .[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + end + + stateManagerContract[state manager] + stateManager[state manager] + + stateManager --o transaction + --o stateManagerContract +``` + +The purpose of this transaction is to modify the state of an account. + +The only two rules regarding this transaction are: + +1) The NFT present in the input MUST be preserved in the output going back to the `stateManager` +2) the credentials (first field of the datum) associated to an NFT MUST be preserved for that NFT (ie. the output having the nft has `Account` datum with first field equal to the first field of the input datum). + +#### Minting Tokens + +```mermaid +flowchart LR + + subgraph transaction + .[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + end + + stateManager[state manager] + transferManagerPolicy[(transfer manager policy)] + transferManagerContract[transfer manager] + + stateManager -. receiver account state .-o transaction + + transferManagerPolicy -. mints CNTs .-> transaction + -- minted CNTs --o transferManagerContract + +``` + +The "Minting Tokens" transaction allows to create new programmable tokens. + +Programmable tokens are normal CNTs that are only present on `transferManager` +contract with same hash as their own policy. + +If, by incorrect implementation of the standard, these tokens are found outside the respective transfer manager, they are no longer to be considered as programmable tokens. + +The transaction MUST have an output going to the `transferManager` contract with the correct [`TransferManagerDatum`](#TransferManagerDatum) **inline** datum. + +The first field of the datum MUST be the name of the NFT present on the reference input coming from the state manager. + +Additional, implementation-specific, arbitrary logic (eg. capped supply, or one-time mints, etc. ) MAY be added. + +#### Burning Tokens + +```mermaid +flowchart LR + + subgraph transaction + .[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + end + + stateManager[state manager] + transferManagerContract[transfer manager] + + stateManager -. sender account state .-o transaction + + transferManagerContract -- Burn Redeemer --o transaction -..-x a[ ] + + style a fill:#FFFFFF00, stroke:#FFFFFF00; +``` + +If an utxo in the `transferManager` is spent with `Burn` redeemer, the **entire** amount of the programmable token on that utxo MUST be burnt. + +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: + +one `Transfer` transaction to create the utxo with that amount. + +one `Burn` transaction spending the desired utxo. + +Additional, implementation-specific, arbitrary logic MAY be added. + +#### Transfer + +```mermaid +flowchart LR + transferManagerObserver([transfer manager observer]) + stateManagerContract[state manager] + transferManagerContract[transfer manager] + + sender((sender)) + + A[transfer manager] + B[transfer manager] + same[transfer manager] + + subgraph transaction + direction LR + .[ ] + _[ ] + z[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + style _ fill:#FFFFFF00, stroke:#FFFFFF00; + style z fill:#FFFFFF00, stroke:#FFFFFF00; + end + + transferManagerObserver -. validates inputs .-> transaction + + stateManagerContract -. sender account state .-o transaction + stateManagerContract -. receiver A account state .-o transaction + stateManagerContract -. receiver B account state .-o transaction + + transferManagerContract --o transaction + transferManagerContract -- possibly --o transaction + transferManagerContract -- many --o transaction + transferManagerContract -- inputs --o transaction + transferManagerContract --o transaction + + sender -. signs .-> transaction + + transaction -- stake creds A --o A + transaction -- stake creds B --o B + transaction -- change (if needed) --o same +``` + + +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. + +The purpose of this transaction is to transfer tokens from the sender's account to one or more receiver accounts. The transaction MUST ensure that the transfer is valid according to the states of each account involved if necessary. + +The transaction MUST call the `transferManagerObserver` contract with the `TransferRedeemer`. External contracts should look for this redeemer to obtain information regarding the transfer. + +Additionally, any UTxOs spent from the `transferManager` contract MUST be spent with the `SingleTransferRedeemer` with `Transfer` constructor (index `0`). + +The transaction MUST include: + +1. One or more inputs from the sender's account in the `transferManager` with `SingleTransferRedeemer` for each UTxO spent. +2. Outputs to the receives' accounts in the `transferManager` as specified in the `TransferRedeemer` of the transfer manager observer. +3. The `TransferRedeemer` for the `transferManager` (as observer) contract. +4. one `Account` reference input for each of the parties involved (sender + receivers). + +The information in the `TransferRedeemer` MUST be validated by the observer. + +That includes that the sum of the programmable tokens coming from the `transferManager` is stirctly equal to `totInputAmt`. + +There is one output for each element of the 2nd field (`outputs`) starting at the index specified by the 4th field (`firstOutputIndex`), in the same order. + +Each of the outputs going to the `transferManager` MUST have `TransferManagerDatum` **inline** datum, and MUST make sure the first field of the datum matches the token name of the NFT present on the respective reference input `Account`. + + +## Rationale: how does this CIP achieve its goals? + + +The [first proposed implementation](https://github.com/cardano-foundation/CIPs/pull/444/commits/525ce39a89bde1ddb62e126e347828e3bf0feb58) (which we could informally refer as v0) was quite different by the one shown in this document + +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 + +this path was abandoned due to the logaritmic cost of creation of accounts, on top of the complexity. + +Other crucial difference with the first proposed implementation was in the `accountManager` redeemers; +which included definitions for `TransferFrom`, `Approve` and `RevokeApproval` redeemers, aiming to emulate ERC20's methods of `transferFrom` and `approve`; + +after [important feedback by the community](https://github.com/cardano-foundation/CIPs/pull/444#issuecomment-1399356241), it was noted that such methods would not only have been superfluous, but also dangerous, and are hence removed in this specification. + +After a first round of community feedback, a [reviewed stadard was proposed](https://github.com/cardano-foundation/CIPs/pull/444/commits/f45867d6651f94ba53503833098d550326913a0f) (which we could informally refer to as v1). +[This first revision even had a PoC implementation](https://github.com/HarmonicLabs/erc20-like/commit/0730362175a27cee7cec18386f1c368d8c29fbb8), but after further feedback from the community it was noted that the need to spend an utxo on the receiving side could cause UTxO contention in the moment two or more parties would have wanted to send a programmable token to the same receiver at the same time. + +The specification proposed in this file addresses all the previous concerns. + +The proposal does not affect backward compatibilty being the first proposing a standard for programmability over transfers; + +exsisting native tokens are not conflicting for the standard, instead, native tokes are used in this specification for various purposes. + +## Path to Active + +### Acceptance Criteria + + +- having at least one instance of the smart contracts described on: + - mainnet + - preview testnet + - preprod testnet +- having at least 2 different wallets integrating meta asset functionalities, mainly: + - displayning balance of a specified meta asset if the user provides the address of the respecive account manager contract + - independent transaction creation with `Transfer` redeemers + +### Implementation Plan + + +- [ ] [PoC implementation](https://github.com/HarmonicLabs/erc20-like) +- [ ] [showcase transactions](https://github.com/HarmonicLabs/erc20-like) +- [ ] wallet implementation + +## Copyright + +This CIP is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/legalcode). \ No newline at end of file