diff --git a/CIP-0113/README.md b/CIP-0113/README.md new file mode 100644 index 0000000000..1921dca4ee --- /dev/null +++ b/CIP-0113/README.md @@ -0,0 +1,465 @@ +--- +CIP: 113 +Title: Programmable token-like assets +Category: Tokens +Status: Proposed +Version: 3.0 +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. + +## Specification Versions + +**Current Version: 3.0** (This document) + +This specification has evolved through community feedback: +- **Version 0** (deprecated) - Initial proposal with Merkle tree account uniqueness, included Approve/TransferFrom patterns +- **Version 1** (deprecated) - Removed Merkle trees, but had UTxO contention issues on receiver side +- **Version 2** (deprecated) - Introduced stateManager/transferManager pattern with user registration requirement +- **Version 3** (current) - Removed registration requirement, simplified to transferLogicScript pattern + +Previous specification versions are available in the [deprecated/](./deprecated/) directory. + +For detailed evolution history, see the [Rationale](#rationale-how-does-this-cip-achieve-its-goals) section. + +## 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). + +### General Terms + +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 privileged 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. + +### Layer 1: Registry Components + +These components form the infrastructure layer shared by ALL programmable tokens. They are deployed once and used by all tokens: + +- `registry`: The on-chain registry where anyone can register new programmable tokens. It implements a sorted linked list pattern where entries are ordered by token policy. The registry ensures only properly configured programmable tokens can be registered. + - `RegistryNode`: A node (or entry) in the registry. Each RegistryNode is a UTxO representing a specific programmable token with an inline datum containing all the token's configuration. + - `registrySpendScript`: The Spend script that controls all RegistryNode UTxOs. It enforces the linked list invariants during insertions and modifications. + - `registryMintingPolicy`: The Minting script for NFTs that mark valid RegistryNodes. Each RegistryNode contains a unique NFT minted by this policy, using the token's policy as the NFT name. + +### Layer 2: Standard Components + +These components form the common validation infrastructure shared by ALL programmable tokens: + +- `programmableLogicBase`: The unique Spend script that holds all existing programmable tokens. All programmable tokens live at addresses with this script as the payment credential. This script acts as a gatekeeper, delegating actual validation to the programmableLogicGlobal stake validator. +- `programmableLogicGlobal`: The Stake validator that performs the actual validation logic for transfers and third-party actions. It is invoked via the withdraw-zero pattern when programmable tokens are spent from the programmableLogicBase script. +- `smart wallet`: The set of UTxOs living inside the programmableLogicBase script that belong to a specific user. Ownership is determined by the stake credential attached to the UTxOs, not the payment credential (which is always programmableLogicBase). + +### Layer 3: Substandard Components + +These components are token-specific and define the custom behavior of each programmable token. They are deployed per token and implement the "substandard": + +- `transferLogicScript`: A token-specific Withdraw-0 script that implements the custom transfer logic for user-initiated transfers. This script validates whether a transfer is allowed based on the token's rules (e.g., whitelist checks, transfer limits, compliance rules). +- `thirdPartyTransferLogicScript`: A token-specific Withdraw-0 script that defines admin/third-party actions on the programmable token. This includes privileged operations like seizure, forced transfers, or emergency actions that can be executed without explicit user permission. +- `issuanceLogicScript`: A token-specific Withdraw-0 script that implements the custom minting/burning logic for the programmable token. It defines who can mint new tokens, under what conditions, and the burning rules. +- `issuanceMintingPolicy`: The Minting script that mints/burns programmable tokens. While the script code is shared across all programmable tokens, each deployment is token-specific because the policy is parameterized by the hash of the token's specific issuanceLogicScript. +- `globalState`: An optional token-specific unique UTxO whose datum contains global information regarding the token (e.g., if it's frozen, if transfers are paused, total supply, etc.). Not all tokens require a global state. +- `globalStateUnit`: The unit (policy + token name) of the NFT contained in the globalState UTxO. This NFT uniquely identifies the global state for a specific programmable token. + +The term "substandard" indicates a specific implementation of all the token-specific components (Layer 3) that guarantees a certain behaviour and a consistent way to build transactions. Any programmable token MUST belong to a certain substandard, otherwise wallets and dApps don't know how to properly interact with them. + +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 registry (along with registrySpendScript and registryMintingPolicy), programmableLogicBase, and programmableLogicGlobal are already deployed on-chain as the shared infrastructure (Layers 1 and 2). + +The creator writes a new transferLogicScript where they define the rules to transfer the new token (e.g., whitelist checks, transfer limits). + +(Optional) Then they write a new thirdPartyTransferLogicScript where they define who are the admins and what actions they can do without permission of any other user (e.g., seizure, forced transfers). + +Then they write a new issuanceLogicScript where they define who can mint and burn the new token. + +Then they deploy a new issuanceMintingPolicy instance parameterized by the hash of the new issuanceLogicScript. + +Finally, the creator adds a RegistryNode to the registry with the hashes of all the above scripts and with additional required information. The registration cannot happen if the policy has already been registered or if the issuanceMintingPolicy is wrong. + +During or after the registration process, the creator can mint the new programmable token. This new token is enforced to be sent to the programmableLogicBase script. + +Off-chain, any user can deterministically derive their smart wallet address (as explained in the next section). + +When a user wants to transfer some of their programmable tokens, the programmableLogicBase script delegates validation to the programmableLogicGlobal stake validator (via withdraw-zero pattern). For each token, a proof of registration (or non-registration) is required: if the policy is present in the registry then the associated transferLogicScript MUST be executed in the same transaction; if the policy is not present in the registry, the token is treated as a normal, non-programmable CNT and can always leave the programmableLogicBase script. + +At any moment, for each token, admins can execute privileged actions as defined in the thirdPartyTransferLogicScript. + +At any moment, for each token, issuers can mint more tokens 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 independently 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. + +### RegistryNode datum + +Every entry in the registry MUST have attached an inline datum following this standard type: + +```ts +type RegistryNode { + key: ByteArray, + next: ByteArray, + transfer_logic_script: Credential, + third_party_transfer_logic_script: Credential, + global_state_cs: ByteArray +} +``` + +This datum is enforced by the execution of the registrySpendScript at registration. + +The ordering of the fields MUST be respected. + +#### `key` + +MUST be a bytestring of length 28. This field is also the key of the RegistryNode +and the token name of the NFT minted via registryMintingPolicy and that is included in the RegistryNode UTxO. +This represents the currency symbol (policy) of the programmable token being registered. + +#### `next` + +MUST be a bytestring of length 28. + +As the registry is an ordered linked list, this is the next key (a tokenPolicy) in the registry in lexicographic order. + +#### `transfer_logic_script` + +MUST be a Credential (either PubKeyCredential or ScriptCredential with a 28-byte hash). + +Represents the credential of the "Withdraw 0" script to be executed in a transfer transaction for validation. +This script implements the custom logic for user-initiated transfers. + +#### `third_party_transfer_logic_script` + +MUST be a Credential (either PubKeyCredential or ScriptCredential with a 28-byte hash). + +Represents the credential of the "Withdraw 0" script that defines who is an admin (third party) +and the admin actions on the programmable token. + +This script is used for privileged actions (such as seizure or forced transfers) that can be executed without user permission. + +#### `global_state_cs` + +MUST be a bytestring of either length 0 or length 28. + +It represents the optional currency symbol (policy ID) of an NFT that marks a UTxO containing 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 28, it represents the policy ID of the NFT that uniquely identifies the global state UTxO. +In this case, when creating a transfer transaction a reference input containing an NFT with this policy MUST be included. + +### Programmable token registration + +To create a new programmable token, it must be properly registered in the registry. +The on-chain validation won't allow the creator to cheat or deviate from the enforced rules. + +Recalling that the registry is an ordered linked list sorted by the `key` 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 `next` that is now set to the new token `key` + - a new RegistryNode representing the new token with the proper RegistryNode datum fields, in particular `next` + is set to the input value of prev_node `next` +3) The transaction MUST include the registration certificate of `transfer_logic_script` +4) The new RegistryNode `global_state_cs` MAY be an empty bytestring if the logic of +the programmable token does not require a global state; +otherwise it MUST be a bytestring of length 28. +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`, +with 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 `issuanceMintingPolicy` MUST be an instance of the official script parameterized by a custom `issuanceLogicScript` + +### Transfer + +In order to spend a UTxO holding programmable tokens from the programmableLogicBase script, +the programmableLogicGlobal stake validator MUST be invoked via the withdraw-zero pattern. +The redeemer passed to the programmableLogicGlobal stake validator follows this standard type: + +```ts +type ProgrammableLogicGlobalRedeemer { + TransferAct { + registryProofs: List + } + ThirdPartyAct { + seize_input_idx: Int, + seize_output_idx: Int, + registry_node_idx: Int + } +} + +type RegistryProof { + TokenExists { + node_idx: Int + } + TokenDoesNotExist { + node_idx: Int + } +} +``` + +#### Architecture: Delegation Pattern + +The programmableLogicBase script is a lightweight gatekeeper that: +1. Accepts any redeemer (of type `Data`) +2. Verifies that the programmableLogicGlobal stake validator is invoked in the transaction +3. Delegates all validation logic to the programmableLogicGlobal stake validator + +The programmableLogicGlobal stake validator performs the actual validation when invoked via withdraw-zero. + +#### TransferAct Constructor + +The `TransferAct` constructor is used when the owner of the programmable tokens wants to transfer them. +This represents the standard transfer flow initiated by the token owner. + +When using this constructor: +1. The `registryProofs` field MUST contain a list of proofs, one for each non-lovelace token policy present in any spent UTxO from programmableLogicBase +2. For each proof of type `TokenExists`: + - The corresponding RegistryNode MUST be included as a reference input at the specified index (`node_idx`) + - The token's `transfer_logic_script` MUST be executed in the transaction (via withdraw-zero) +3. For each proof of type `TokenDoesNotExist`: + - The covering RegistryNode MUST be included as a reference input at the specified index (`node_idx`) + - The token is treated as a normal CNT and can be transferred without additional validation + +#### ThirdPartyAct Constructor + +The `ThirdPartyAct` constructor is used when an admin (third party) wants to execute privileged actions on programmable tokens +without the explicit permission of the token owner. This is specifically designed for **seizure operations** where tokens are removed from a user's smart wallet. + +When using this constructor: +1. `seize_input_idx`: The index (in the transaction inputs) of the UTxO from programmableLogicBase being seized +2. `seize_output_idx`: The index (in the transaction outputs) of the resulting UTxO after seizure +3. `registry_node_idx`: The index (in reference inputs) of the RegistryNode for the token being seized + +**Validation Requirements:** +- Only ONE input from programmableLogicBase is allowed (the seized input at `seize_input_idx`) +- The RegistryNode at `registry_node_idx` MUST exist and contain the token's configuration +- The token's `third_party_transfer_logic_script` MUST be executed in the transaction (via withdraw-zero) +- The output at `seize_output_idx` MUST: + - Have the same address as the seized input + - Have the same datum as the seized input + - Contain the original value MINUS the seized tokens +- At least some tokens MUST be removed (to prevent DoS attacks) +- The authorization check for the stake credential is bypassed (admins don't need user permission) + +#### RegistryProof Requirements + +The `registryProofs` list MUST satisfy the following requirements: + +1. **Completeness**: The list MUST include one proof for each distinct non-lovelace token policy present in any UTxO being spent from programmableLogicBase +2. **Ordering**: The list order MUST match the lexicographic ordering of the token policies. + For example, if spending UTxOs contains policies A, B, and C (in lexicographic order), + then `registryProofs[0]` is for policy A, `registryProofs[1]` for policy B, and `registryProofs[2]` for policy C +3. **Correctness**: Each proof must accurately represent the registration status of the corresponding token + +(For reference, a TypeScript implementation of lexicographic ordering can be found +[here](https://github.com/HarmonicLabs/uint8array-utils/blob/c1788bf351de24b961b84bfc849ee59bd3e9e720/src/utils/index.ts#L8-L27)) + +#### Proving Registration Status + +**TokenExists Proof**: To prove a token policy IS registered in the registry, include the RegistryNode with that exact `key` matching the token policy as a reference input. + +**TokenDoesNotExist Proof**: To prove a token policy is NOT registered in the registry, include as a reference input the "covering node" - the RegistryNode whose `key` is the largest value that is still less than the token being proven absent, and whose `next` is greater than the token being proven absent. + +For example: +- If the registry contains policies [A, C, E] and you want to prove policy D is not registered +- You must include the RegistryNode for policy C as a reference input (at `node_idx`) +- Because C < D < E (lexicographically), this proves D is not in the registry + +#### programmableLogicGlobal Validation + +The programmableLogicGlobal stake validator performs the following validation: + +1. **Authorization Check** (TransferAct only): For each spent UTxO from programmableLogicBase: + - If the stake credential is a public key hash, verify the transaction is signed by that key + - If the stake credential is a script hash, verify that script is executed in the transaction + - Exception: ThirdPartyAct bypasses this check (admins don't need user permission) + +2. **Proof Validation** (TransferAct only): For each RegistryProof: + - Verify the referenced RegistryNode exists at the specified index in reference inputs + - For TokenExists: verify the RegistryNode's `key` matches the policy being validated + - For TokenDoesNotExist: verify the covering node relationship (prev.key < unregistered < prev.next) + +3. **Logic Script Execution**: For each registered programmable token: + - TransferAct: verify the token's `transfer_logic_script` is executed (appears in withdrawals) + - ThirdPartyAct: verify the token's `third_party_transfer_logic_script` is executed + +4. **Output Validation**: + - TransferAct: Verify all programmable tokens in outputs remain at programmableLogicBase addresses with valid stake credentials + - ThirdPartyAct: Verify the output at `seize_output_idx` matches requirements (same address, datum, value minus seized tokens) + +#### Additional Reference Inputs + +Depending on the substandard and the specific programmable token implementation, additional reference inputs MAY be required: + +1. **Global State**: If the RegistryNode's `global_state_cs` field is non-empty, a reference input containing an NFT with that policy MUST be included +2. **User State**: Depending on the substandard implementation, one or more reference inputs representing user state MAY be required. The exact requirements depend on the substandard (see "Existing substandards" section) + +#### Security Considerations for Transfers + +- The programmableLogicBase/programmableLogicGlobal split ensures all programmable tokens can only be spent if proper validation occurs +- The RegistryProof mechanism prevents bypassing transfer restrictions by claiming a token is unregistered when it actually is registered +- Third-party actions provide a mechanism for compliance (seizure, freezes) while maintaining clear on-chain definitions of admin powers +- The authorization check ensures users maintain control over their tokens (except when third-party actions are explicitly defined) +- ThirdPartyAct is limited to single-input operations to prevent DoS attacks and ensure predictable seizure behavior + +### 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 specification (Version 3.0) 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) (Version 0) 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) +(Version 1). +[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 Version 1, another improved standard was proposed (Version 2). +Version 2 proposed a standard interface for programmable tokens, based on the exsistence of 2 contracts: the `stateManager` and the `transferManager`. + +By the Version 2 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 Version 3.0 of the standard. + +The specification proposed in this file addresses all the previous concerns. + +## Path to Active + +### Acceptance Criteria +- [ ] 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