From 525ce39a89bde1ddb62e126e347828e3bf0feb58 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Fri, 20 Jan 2023 01:54:27 +0100 Subject: [PATCH 01/45] meta-assets (ERC20-like assets) initial draft --- CIP-meta-assets (ERC20-like assets)/README.md | 753 ++++++++++++++++++ 1 file changed, 753 insertions(+) create mode 100644 CIP-meta-assets (ERC20-like assets)/README.md diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md new file mode 100644 index 0000000000..5cee8f5c28 --- /dev/null +++ b/CIP-meta-assets (ERC20-like assets)/README.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 From b02ff3ec82286f2d96df74256390c70299d780e4 Mon Sep 17 00:00:00 2001 From: michele-nuzzi Date: Fri, 7 Apr 2023 15:04:56 +0200 Subject: [PATCH 02/45] semplified the specification --- CIP-meta-assets (ERC20-like assets)/README.md | 792 +++++------------- 1 file changed, 227 insertions(+), 565 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 5cee8f5c28..0b22e3bc44 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -1,5 +1,5 @@ --- -CIP: ? +CIP: _??? Title: meta-assets (ERC20-like assets) Category: Tokens Status: Proposed @@ -32,7 +32,7 @@ License: CC-BY-4.0 ## 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 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. @@ -45,9 +45,9 @@ If adopted it would allow to introduce the programmability over the transfer of 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 +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 soultion can co-exsist with the exsiting native tokens +4) the implementaiton is possible without hardfork since Vasil 5) optimized implementations should not take significant computation, especially on transfers. ## Specification @@ -63,32 +63,37 @@ data Data | 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. +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 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 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)); -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. +Unlike the ERC20 standard; this CIP: -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. +- allows for multiple entries with the same key (same credentials can be used for multiple accounts) +- DOESN'T 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 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 +> **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 +> +> whith 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: -- 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) +The implementation requires + +- a parametrized 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; -in particular we need to the fine the types for a transacntion output reference: - ```ts const PTxId = pstruct({ PTxId: { txId: bs } @@ -100,6 +105,13 @@ const PTxOutRef = pstruct({ index: int } }); + +const PCredential = pstruct({ + PPubKeyCredential: { pkh: bs }, + PScriptCredential: { valHash: bs }, +}); + +const PCurrencySymbol = palias( bs ); ``` which are equivalent to the data: ```hs @@ -108,621 +120,271 @@ 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 +-- PCurrencySymbol +B currencySym +``` -> minting poilicy to be used for the distributed merkle tree contract's utxos +## `accountFactory` (minting policy) -redeemers: -```ts -const TreeMintingPolicyRdmr = pstruct({ - NewNode: {}, - NewLeaf: {}, - MakeRoot: {} -}) -``` -equivalent to the data: -```hs --- NewNode -Constr 0 [] +The `accountFactory` contract is responsabile of validating the creation of new accounts. --- NewLeaf -Constr 1 [] +### validation logic --- MakeRoot -Constr 2 [] -``` +If the creation of the account is considered valid (the minting policy suceeds) +it should result in a single minted asset going to an hard-coded (parametrized) `accountManager` contract. -### NewNode +The standard doesn't include a specification on the policy in case of burning but it is suggested to always allow for the assets burn. -- 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 +the data needed form the `ScriptContext` includes teh following fields: -#### validation logic +- `inputs` +- `output` +- `mint` -- 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 +used as follows: -### NewLeaf +- all the transaction inputs MUST NOT include any tokens having the same currency symbol of the `accountFactory` minting policy; +optionally a specific implementation might require a fixed number of inputs (eg. a single input) for performance reasons. -- mint: - - 1 NFT with asset name equal to the new key hash added -- outputs: - - output to the distributed merkle tree validator +- when minting assets only a single token should 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 serialiseData) fstUtxoRef]` where `fstUtxoRef` is the utxo reference (or `PTxOutRef`) of the very first input of the transaction) -#### validation logic +- the asset minted MUST be included in an output of the transaction being validated going to the hard-coded `accountManager` validator. -- 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 +- 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 output datum MUST: + - be constructed using the `InlineDatum` consturctor (consturctor index `2`) + - have datum value with the [`Account`](#Account) structure (explained below) with the foeeowing fields: + - amount: `0` + - currencySym: currency symbol of the `accountFactory` minting policy + - credentials: the first resolved input address credentials (both public key hash and validator hash supported) + - state: no restrictions (up to the specific implementaiton). -### MakeRoot +optionally a specific implementation might require a fixed number of outputs (eg. a single output) for performance reasons. -- mint: - - 1 NFT with asset name equal to the data hash of an **hard coded** TxOutRef -- outputs: - - output to the distributed merkle tree validator +## `accountManager` (spending validator) -#### validation logic +The `accountManager` contract is responsabile of managing exsiting accounts and their balances. -- 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 +This is done using some standard redeemers and optionally implementation specfic ones. -## Minting policy 2 +### `Account` -> minting poilicy to be used for the account manager contract's utxos +The `Account` data type is used as the `accountManager` datum; and is defined as follows: -redeemers: ```ts -const AccountMintingPolicyRdmr = pstruct({ - NewAccount: {}, - MintAllowedSpender: {}, - BurnAllowedSpender: {} -}) +const Account = pstruct({ + Account: { + amount: int, + credentials: PCredential.type, + currencySym: PCurrencySymbol.type, + state: data + } +}); ``` -### encoding payment credentials +### `AccounManagerRedeemer` -at the moment of writing there exsits two types of credentials: +The `AccounManagerRedeemer` is used to comunicate the contract the intention with which the utxo is being spent. -1) public keys -2) scripts +It includes 4 standard redeemers; none of which is meant to manipulate the state of the spending account. -of which the `blake2b_244` hash is used on-chain +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). -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) +The minimal `AccounManagerRedeemer` is: -however asset names are just bytestrings and do not allow for choiches. +```ts +const AccounManagerRdmr = pstruct({ + Mint: { // or Burn if `amount` is negative + amount: int + }, + Transfer: { + to: PCredential.type, + amount: int + }, + Receive: {}, + ForwardCompatibility: {} +}); +``` -for this reason we need a way to remember which type of credential a 28-length hash is. +The validation logic is different for each redeemer. -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. +#### Common operations and values -so that -```hs -PubKeyCredential pkh -``` -becomes -``` -00 + pkh -``` +Before proceeding with the redeemers validation logic here are some common operations and values between some of the redeemers. -and -```hs -ScriptCredential scriptHash -``` -becomes -``` -01 + scriptHash -``` +##### `ownUtxoRef` -> 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. +the `accountManager` contract is meant to be used only as spending validator. -### NewAccount +As succ we can extract the utxo being spent from the `ScriptPurpose` when constructed with the `Spending` contstructor and fail for the rest. -- mint: - - 1 NFT with asset name equal to the payment credentials -- outputs: - - output to the account manager validator +```ts +const ownUtxoRef = plet( + pmatch( ctx.purpose ) + .onSpending(({ utxoRef }) => utxoRef) + ._( _ => perror( PTxOutRef.type ) ) +); +``` -#### validation logic +##### `validatingInput` -- 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) +is the input with `utxoRef` field equivalent to `ownUtxoRef` -### MintAllowedSpender +##### `ownCreds` -- 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 +from the `validatingInput`: -#### validation logic +```ts +validatingInput.resolved.address.credential +``` -- 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) +##### `ownValue` -### BurnAllowedSpender +from the `validatingInput`: -- inputs: - - account manager contract input having the NFT to burn -- mint: - - (burn) 1 NFT with asset name equal to the payment credentials +```ts +validatingInput.resolved.value +``` -#### 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 +##### `isOwnOutput` -## DSMerkleTree contract +given an output; we recongize the output as "own" if the attached credantials are equivalent to `ownCreds` +and the attached value includes an entry for the `currencySym` field in the specified in the datum. -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: {} -}); +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 ) ) + ) + ) +); ``` -which translates to the following `Data`s: -```hs --- NewAccount -Constr 0 [ B credsBs ] +##### `isOwnInput` --- UpdateNode -Constr 1 [] -``` +an input is "own" if the resolved field is "own"; -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 - } -}); +const isOwnInput = plet( + pfn([ PTxInInfo.type ], bool ) + ( input => isOwnOutput.$( input.resolved ) ) +); ``` -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 -] -``` +##### `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 -### 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 - } -}); +const isOwnCurrSym = plet( account.currencySym.eqTerm ); ``` -which translates to the following `Data`s: -```hs --- Account -Constr 0 [ I amount ] - --- AllowedSpender -Constr 1 [ - B originalOwner, - B spender, - I remainingAmount -] -``` +##### `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. -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: {} -}); + const outIncludesNFT = plet( + pfn([ PTxOut.type ], bool ) + ( out => out.value.some( entry => isOwnCurrSym.$( entry.fst ) ) ) +); ``` -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 [] -``` +##### `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. + +This might not be true for any implementation specific additional redeemer. + +#### `Mint` + +the contract being called using the `Mint` redeemer MUST suceed 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. + +- only a single input is comes from the `accountManager` validator; + +- the value of the input coming from the `accountManager` MUST include an entry with `PCurrencySymbol` +equivalent to the one specified in the `currencySym` field of the `Account` datum. + +- 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) + +- there MUST be an 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` + - 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` should 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 +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 should 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` +- 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`) + +#### `Receive` + +- `ownInputs` to be of lenght `2` +- the sender input (the `ownInput` with different `utxoRef` of the `validatingInput`) +being spent with `Transfer` redeemer +- in the sender input `Transefer` redeemer the `to` field is equal to the `account.credentials` (where `account` is the receiver datum) + +> **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. -### 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 +The main purpose of the redeemer is to avoid breaking compatibilities for addtional implementation specific redeemers ## Rationale: how does this CIP achieve its goals? From 147363137d7f7d981c75a44c94a8035d4cd10029 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Wed, 17 Jan 2024 12:28:09 +0100 Subject: [PATCH 03/45] links to implementation --- CIP-meta-assets (ERC20-like assets)/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 0b22e3bc44..5a5377254f 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -408,6 +408,10 @@ It must also explain how the proposal affects the backward compatibility of exis ### Implementation Plan +- [x] [PoC implementation](https://github.com/HarmonicLabs/erc20-like) +- [x] [showcase transactions](https://github.com/HarmonicLabs/erc20-like) +- [ ] wallet implementation + ## Copyright From d3dcb2ebee779958311c9ff3bf1e3ad0f94d8583 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:00:07 +0100 Subject: [PATCH 04/45] added implementation plan --- CIP-meta-assets (ERC20-like assets)/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 0b22e3bc44..5a5377254f 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -408,6 +408,10 @@ It must also explain how the proposal affects the backward compatibility of exis ### Implementation Plan +- [x] [PoC implementation](https://github.com/HarmonicLabs/erc20-like) +- [x] [showcase transactions](https://github.com/HarmonicLabs/erc20-like) +- [ ] wallet implementation + ## Copyright From 867f70b69431863e555d24e2e5e892e9102b1391 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Fri, 26 Jan 2024 14:18:58 +0100 Subject: [PATCH 05/45] added some modificaitons based on the PoC implementation --- CIP-meta-assets (ERC20-like assets)/README.md | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 5a5377254f..e0308d8ec3 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -46,8 +46,11 @@ If adopted it would allow to introduce the programmability over the transfer of 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 soultion can co-exsist with the exsiting native tokens + 4) the implementaiton is possible without hardfork since Vasil + 5) optimized implementations should not take significant computation, especially on transfers. ## Specification @@ -154,12 +157,13 @@ used as follows: optionally a specific implementation might require a fixed number of inputs (eg. a single input) for performance reasons. - when minting assets only a single token should 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 serialiseData) fstUtxoRef]` where `fstUtxoRef` is the utxo reference (or `PTxOutRef`) of the very first input of the transaction) +- the only asset minted MUST have an unique asset name (we suggest using the output of `[(builtin sha2_256) [(builtin serialiseData) fstUtxoRef]]` where `fstUtxoRef` 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 hard-coded `accountManager` validator. - 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`](#Account) structure (explained below) with the foeeowing fields: @@ -340,8 +344,10 @@ equivalent to the one specified in the `currencySym` field of the `Account` datu - 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) +- to prevent DoS by token spamming the output going back to the `accountManager` must have at most length 2. + - there MUST be an 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` + - 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` should change based on `mint.amount` as described below. @@ -360,17 +366,39 @@ The `Transfer` redeemer is used to pay an other account in the same account mana When used as redeemer the contract checks for the following conditions to be true; - `ownInputs` to be of lenght `2` + +- the receiver input (the `ownInput` with different `utxoRef` of the `validatingInput`) +being spent with `Receive` redeemer + +- the sender input (`validatinInput`) must have the NFT according to `account.currencySym` (the input is valid) + +- the output with the sender's credentials must preserve the NFT + +- the account fields `credentials`, `currencySym` and `state` are unchanged between sender input datum and sender output datum + +- the account fields `credentials`, `currencySym` and `state` are unchanged between receiver input datum and receiver output datum + - `ownOuts` to be of lenght `2` + +- every member of `ownOuts` must have at most 2 entries to prevent DoS by token spam. + - 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 sender input (the `ownInput` with different `utxoRef` of the `validatingInput`) being spent with `Transfer` redeemer + - in the sender input `Transefer` redeemer the `to` field is equal to the `account.credentials` (where `account` is the receiver datum) > **NOTE** From d6e22a5b946e00e8fff3aceb07ec17f53b3b4b1e Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Fri, 26 Jan 2024 14:37:58 +0100 Subject: [PATCH 06/45] added matteocoppola as co-proposer --- CIP-meta-assets (ERC20-like assets)/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index e0308d8ec3..d37f811bfe 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -6,6 +6,7 @@ Status: Proposed Authors: - Michele Nuzzi - Harmonic Laboratories + - Matteo Coppola Implementors: [] Discussions: - https://github.com/cardano-foundation/cips/pulls/? From 7eac75d8bc79bd937ffd90925f216a3fb2e7923e Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:55:34 +0100 Subject: [PATCH 07/45] consistency with other drafts Co-authored-by: Robert Phair --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index d37f811bfe..df4b712f31 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -1,5 +1,5 @@ --- -CIP: _??? +CIP: ? Title: meta-assets (ERC20-like assets) Category: Tokens Status: Proposed From 7c0534e47d19dcb2c457e71507b61c04bc879a08 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:55:44 +0100 Subject: [PATCH 08/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Robert Phair --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index df4b712f31..89f090740c 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -9,7 +9,7 @@ Authors: - Matteo Coppola Implementors: [] Discussions: - - https://github.com/cardano-foundation/cips/pulls/? + - https://github.com/cardano-foundation/CIPs/pull/444 Created: 2023-01-14 License: CC-BY-4.0 --- From f952b00a3e328a302e27a1b74180523373b5be57 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:56:07 +0100 Subject: [PATCH 09/45] consistency with other CIP titles. Co-authored-by: Robert Phair --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 89f090740c..51e527a7b2 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -1,6 +1,6 @@ --- CIP: ? -Title: meta-assets (ERC20-like assets) +Title: ERC20-like assets Category: Tokens Status: Proposed Authors: From eb984d6af4b9e9d4b6d47b53861e372d1319066f Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:56:19 +0100 Subject: [PATCH 10/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Robert Phair --- CIP-meta-assets (ERC20-like assets)/README.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 51e527a7b2..3b459ac679 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -14,23 +14,6 @@ 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 ecosistems at the price of the token true ownership. From f6c4fbf6e0cedad02da9514a5f3b86d099546e73 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:56:29 +0100 Subject: [PATCH 11/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Robert Phair --- CIP-meta-assets (ERC20-like assets)/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 3b459ac679..322ac296b9 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -15,7 +15,6 @@ 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. From d25ed9d098121ef0b03870ac4b9c1cd631c5ffff Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:56:36 +0100 Subject: [PATCH 12/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Robert Phair --- CIP-meta-assets (ERC20-like assets)/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 322ac296b9..3b025fa7e2 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -20,7 +20,6 @@ This CIP proposes a standard that if adopted would allow the same level of progr 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)). From 2ef019e32318e55720e741ed88deb7cea75d597f Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:56:43 +0100 Subject: [PATCH 13/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Robert Phair --- CIP-meta-assets (ERC20-like assets)/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 3b025fa7e2..e35d32c308 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -36,7 +36,6 @@ The solution proposed includes (answering to the open questions of CPS-0003): 5) optimized implementations should not take significant computation, especially on transfers. ## Specification - In the specifiaction we'll use the haskell data type `Data`: ```hs From 83ab44a0e6b0d366b0a5bd9e8a2a42a5ed4b2e84 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:57:38 +0100 Subject: [PATCH 14/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Robert Phair --- CIP-meta-assets (ERC20-like assets)/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index e35d32c308..5fb7262cb9 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -422,7 +422,5 @@ It must also explain how the proposal affects the backward compatibility of exis - [ ] wallet implementation ## Copyright - -[CC-BY-4.0]: https://creativecommons.org/licenses/by/4.0/legalcode -[Apache-2.0]: http://www.apache.org/licenses/LICENSE-2.0 +This CIP is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/legalcode). From 8344f936cc1f6e7bd01752b1943ac2f1e3f2d08b Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 11 Feb 2024 22:56:48 +0100 Subject: [PATCH 15/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Robert Phair --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 5fb7262cb9..ae46174f68 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -1,6 +1,6 @@ --- CIP: ? -Title: ERC20-like assets +Title: Programmable token-like assets Category: Tokens Status: Proposed Authors: From 552be3c53ff829d92781e3efef4cfbaf1b89f70b Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 11 Feb 2024 22:56:55 +0100 Subject: [PATCH 16/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Robert Phair --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index ae46174f68..167d619534 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -1,5 +1,5 @@ --- -CIP: ? +CIP: 113 Title: Programmable token-like assets Category: Tokens Status: Proposed From 1662901e97293dd0ce52e2a0e7476328490123ee Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Mon, 12 Feb 2024 15:40:49 +0100 Subject: [PATCH 17/45] add franken address utility for offchain query --- CIP-meta-assets (ERC20-like assets)/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 167d619534..7659167484 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -122,7 +122,9 @@ The `accountFactory` contract is responsabile of validating the creation of new ### validation logic If the creation of the account is considered valid (the minting policy suceeds) -it should result in a single minted asset going to an hard-coded (parametrized) `accountManager` contract. +it should result in a single minted asset going to an address with payment credentials equalst to an hard-coded (parametrized) `accountManager` contract, and stake credentials equals to the stake cretentials of the users (if any). + +This done to facilitate the query of the user's accounts in the offchain. The standard doesn't include a specification on the policy in case of burning but it is suggested to always allow for the assets burn. @@ -140,7 +142,7 @@ optionally a specific implementation might require a fixed number of inputs (eg. - when minting assets only a single token should 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) fstUtxoRef]]` where `fstUtxoRef` 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 hard-coded `accountManager` validator. +- the asset minted MUST be included in an output of the transaction being validated going to the address as specified above (payment credentials that are the hard-coded `accountManager` validator, and 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 From 66b51f42475ae79d3d0539de1dcb7f2ff0082515 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:50:28 +0100 Subject: [PATCH 18/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Ryan Williams <44342099+Ryun1@users.noreply.github.com> --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 7659167484..46afe2bd55 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -115,7 +115,7 @@ Constr 1 [ B valHash ] B currencySym ``` -## `accountFactory` (minting policy) +### `accountFactory` (minting policy) The `accountFactory` contract is responsabile of validating the creation of new accounts. From 97c5ad061392d4e3717bcfcf376ba8f25113c18c Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:50:39 +0100 Subject: [PATCH 19/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Ryan Williams <44342099+Ryun1@users.noreply.github.com> --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 46afe2bd55..d98319cc53 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -157,7 +157,7 @@ optionally a specific implementation might require a fixed number of inputs (eg. optionally a specific implementation might require a fixed number of outputs (eg. a single output) for performance reasons. -## `accountManager` (spending validator) +### `accountManager` (spending validator) The `accountManager` contract is responsabile of managing exsiting accounts and their balances. From d1732f4ba5b19f8e4bbe8b291579ddcb1d71537b Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:28:24 +0100 Subject: [PATCH 20/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Sebastian Nagel --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index d98319cc53..96c955f61f 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -29,7 +29,7 @@ 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 soultion can co-exsist with the exsiting native tokens +3) the solution can co-exist with the existing native tokens 4) the implementaiton is possible without hardfork since Vasil From 85bbd0cc9e5908859db4205ea3c24fe3fe8b0944 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:28:42 +0100 Subject: [PATCH 21/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Sebastian Nagel --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 96c955f61f..9108c940f9 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -31,7 +31,7 @@ The solution proposed includes (answering to the open questions of CPS-0003): 3) the solution can co-exist with the existing native tokens -4) the implementaiton is possible without hardfork since Vasil +4) the implementation is possible without hardfork since Vasil 5) optimized implementations should not take significant computation, especially on transfers. From f344a6bf5b533ec35d8f05ce2219493cd281143a Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:28:52 +0100 Subject: [PATCH 22/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Sebastian Nagel --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 9108c940f9..cbdcdc6b7c 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -37,7 +37,7 @@ The solution proposed includes (answering to the open questions of CPS-0003): ## Specification -In the specifiaction we'll use the haskell data type `Data`: +In the specification we'll use the haskell data type `Data`: ```hs data Data = Constr Integer [Data] From b0e1579a6db05f658423bbff5a3ee0ce05dd549b Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:29:03 +0100 Subject: [PATCH 23/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Sebastian Nagel --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index cbdcdc6b7c..167d329212 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -312,7 +312,7 @@ This might not be true for any implementation specific additional redeemer. #### `Mint` -the contract being called using the `Mint` redeemer MUST suceed only if the following conditions are met: +the contract being called using the `Mint` redeemer MUST succeed only if the following conditions are met: > **NOTE** > From f03e74c47447fea97a025f82d2c96e7c5ff370c6 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:29:13 +0100 Subject: [PATCH 24/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Sebastian Nagel --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 167d329212..109ada371d 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -382,7 +382,7 @@ any additional check can be made based on the `account.state` (implementation sp - the sender input (the `ownInput` with different `utxoRef` of the `validatingInput`) being spent with `Transfer` redeemer -- in the sender input `Transefer` redeemer the `to` field is equal to the `account.credentials` (where `account` is the receiver datum) +- in the sender input `Transfer` redeemer the `to` field is equal to the `account.credentials` (where `account` is the receiver datum) > **NOTE** > From 0785182cb1739a48dd39330a1a61335e14fe26b1 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:30:45 +0100 Subject: [PATCH 25/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Sebastian Nagel --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 109ada371d..bcafdcd15e 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -246,7 +246,7 @@ validatingInput.resolved.value ##### `isOwnOutput` -given an output; we recongize the output as "own" if the attached credantials are equivalent to `ownCreds` +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. ```ts From e69714de375a2fac1cb5c7aaf36558dc6e0f4de0 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:30:58 +0100 Subject: [PATCH 26/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Sebastian Nagel --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index bcafdcd15e..83f80c4bcd 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -213,7 +213,7 @@ Before proceeding with the redeemers validation logic here are some common opera the `accountManager` contract is meant to be used only as spending validator. -As succ we can extract the utxo being spent from the `ScriptPurpose` when constructed with the `Spending` contstructor and fail for the rest. +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( From d597ac76ea9c6ccbe1d129b99bffcdd20c24d37f Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:31:09 +0100 Subject: [PATCH 27/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Sebastian Nagel --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 83f80c4bcd..5d71005dbf 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -71,7 +71,7 @@ Unlike the ERC20 standard; this CIP: The implementation requires -- a parametrized minting policy to validate the creation of new accounts (in this CIP also referred as `accountFactory`) +- 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 From 522013094b9d2cf5e8fd5ce0702ca874b70fce66 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:31:19 +0100 Subject: [PATCH 28/45] Update CIP-meta-assets (ERC20-like assets)/README.md Co-authored-by: Sebastian Nagel --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 5d71005dbf..c0857cc572 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -62,7 +62,7 @@ Unlike the ERC20 standard; this CIP: > > this would allow for a more powerful model than the account based equivalent but implies higher execution costs > -> whith the goal of keeping the standard interoperable and easy to understand and implement +> 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 From 5bf321b1e2d06425f4e0ec37aceedda86a165d85 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Sun, 18 Feb 2024 11:44:11 +0100 Subject: [PATCH 29/45] RFC 2119 --- CIP-meta-assets (ERC20-like assets)/README.md | 180 ++++++++++++------ 1 file changed, 127 insertions(+), 53 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index c0857cc572..0fee4cb75b 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -33,10 +33,15 @@ The solution proposed includes (answering to the open questions of CPS-0003): 4) the implementation is possible without hardfork since Vasil -5) optimized implementations should not take significant computation, especially on transfers. +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 @@ -54,7 +59,7 @@ The core idea of the implementation is to emulate the ERC20 standard; where toke Unlike the ERC20 standard; this CIP: - allows for multiple entries with the same key (same credentials can be used for multiple accounts) -- DOESN'T 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. +- 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** > @@ -68,11 +73,10 @@ Unlike the ERC20 standard; this CIP: > 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`)) +- a spending validator to manage the accounts (in this CIP also referred as `accountManager`) ### standard data types used @@ -121,14 +125,34 @@ The `accountFactory` contract is responsabile of validating the creation of new ### validation logic -If the creation of the account is considered valid (the minting policy suceeds) -it should result in a single minted asset going to an address with payment credentials equalst to an hard-coded (parametrized) `accountManager` contract, and stake credentials equals to the stake cretentials of the users (if any). +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. -This 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 standard doesn't include a specification on the policy in case of burning but it is suggested to always allow for the assets burn. +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 form the `ScriptContext` includes teh following fields: +the data needed from the `ScriptContext` includes the following fields: - `inputs` - `output` @@ -137,31 +161,29 @@ the data needed form the `ScriptContext` includes teh following fields: 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 might require a fixed number of inputs (eg. a single input) for performance reasons. +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 should 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) fstUtxoRef]]` where `fstUtxoRef` is the utxo reference (or `PTxOutRef`) of the very first input of the transaction) +- 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 (payment credentials that are the hard-coded `accountManager` validator, and stake credtentials of the user if any). +- 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 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`](#Account) structure (explained below) with the foeeowing fields: + - have datum value with the [`Account` structure (explained below)](#Account) with the following fields: - amount: `0` - - currencySym: currency symbol of the `accountFactory` minting policy - - credentials: the first resolved input address credentials (both public key hash and validator hash supported) + - 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). -optionally a specific implementation might require a fixed number of outputs (eg. a single output) for performance reasons. - ### `accountManager` (spending validator) The `accountManager` contract is responsabile of managing exsiting accounts and their balances. -This is done using some standard redeemers and optionally implementation specfic ones. +This is done using some standard redeemers and optionally some implementation-specfic ones. ### `Account` @@ -178,13 +200,39 @@ const Account = pstruct({ }); ``` +> **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 MUST 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 +> } +> }); +> ``` + ### `AccounManagerRedeemer` The `AccounManagerRedeemer` is used to comunicate the contract the intention with which the utxo is being spent. -It includes 4 standard redeemers; none of which is meant to manipulate the state of the spending account. +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 +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 `AccounManagerRedeemer` is: @@ -227,6 +275,16 @@ const ownUtxoRef = plet( 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`: @@ -243,11 +301,16 @@ from the `validatingInput`: 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. +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( @@ -280,7 +343,7 @@ 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.eqTerm ); +const isOwnCurrSym = plet( account.currencySym.peq ); ``` ##### `outIncludesNFT` @@ -306,9 +369,11 @@ 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. +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 might not be true for any implementation specific additional redeemer. +This MAY NOT be true for any additional implementation-specific redeemer. #### `Mint` @@ -319,28 +384,32 @@ the contract being called using the `Mint` redeemer MUST succeed only if the fol > 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. -- only a single input is comes from the `accountManager` validator; - -- the value of the input coming from the `accountManager` MUST include an entry with `PCurrencySymbol` -equivalent to the one specified in the `currencySym` field of the `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) -- to prevent DoS by token spamming the output going back to the `accountManager` must have at most length 2. +```ts +const noAccountsCreated = pisEmpty.$( + tx.mint.filter( isOwnAssetEntry ) +); +``` -- there MUST be an 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` should change based on `mint.amount` as described below. +- 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. -- if the `mint.amount` is positive the output datum MUST - be equal to the sum of `account.datum` and `mint.datum` +- to prevent DoS by token spamming the output going back to the `accountManager` MUST have at most length 2. -- if the `amount` field included in the `Mint` redeemer is negative -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 should check for the output datum `amount` field to be equal to the result of the sum +- 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` @@ -350,20 +419,23 @@ When used as redeemer the contract checks for the following conditions to be tru - `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 sender input (`validatinInput`) must have the NFT according to `account.currencySym` (the input is valid) +- 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 output with the sender's credentials must preserve the NFT +- the sender input (`validatingInput`) MUST have the NFT according to `account.currencySym` (the input is valid) -- the account fields `credentials`, `currencySym` and `state` are unchanged between sender input datum and sender output datum +- the output with the sender's credentials MUST preserve the NFT -- the account fields `credentials`, `currencySym` and `state` are unchanged between receiver input datum and receiver output datum +- the sender account fields `credentials`, `currencySym` and `state` are unchanged between sender input datum and sender output datum -- `ownOuts` to be of lenght `2` - -- every member of `ownOuts` must have at most 2 entries to prevent DoS by token spam. +- 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`) @@ -379,10 +451,12 @@ any additional check can be made based on the `account.state` (implementation sp - `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 `to` field is equal to the `account.credentials` (where `account` is the receiver datum) +- in the sender input `Transfer` redeemer the `amount` field is strictly grather than 0 > **NOTE** > @@ -398,9 +472,9 @@ 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? - ## Path to Active @@ -417,7 +491,7 @@ It must also explain how the proposal affects the backward compatibility of exis - 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) From cd8e02d8b22b70689409da7a23e9b473a255442f Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Sun, 18 Feb 2024 17:17:58 +0100 Subject: [PATCH 30/45] AccounManagerRedeemer -> AccountManagerRedeemer (fix typo) --- CIP-meta-assets (ERC20-like assets)/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 0fee4cb75b..a44a377995 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -226,19 +226,19 @@ const Account = pstruct({ > }); > ``` -### `AccounManagerRedeemer` +### `AccountManagerRedeemer` -The `AccounManagerRedeemer` is used to comunicate the contract the intention with which the utxo is being spent. +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 `AccounManagerRedeemer` is: +The minimal `AccountManagerRedeemer` is: ```ts -const AccounManagerRdmr = pstruct({ +const AccountManagerRedeemer = pstruct({ Mint: { // or Burn if `amount` is negative amount: int }, From 311c3e3bf69f02614c34acbd4e5610294cfa6582 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Sun, 18 Feb 2024 17:39:02 +0100 Subject: [PATCH 31/45] add rationale --- CIP-meta-assets (ERC20-like assets)/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index a44a377995..85ad245c24 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -477,6 +477,24 @@ The main purpose of the redeemer is to avoid breaking compatibilities for addtio It MUST also explain how the proposal affects the backward compatibility of existing solutions when applicable. If the proposal responds to a CPS, the 'Rationale' section SHOULD explain how it addresses the CPS, and answer any questions that the CPS poses for potential solutions. --> +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 From f45867d6651f94ba53503833098d550326913a0f Mon Sep 17 00:00:00 2001 From: Michele Nuzzi <64633004+michele-nuzzi@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:28:43 +0100 Subject: [PATCH 32/45] Update README.md --- CIP-meta-assets (ERC20-like assets)/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 85ad245c24..cce8fe7483 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -205,7 +205,7 @@ const Account = pstruct({ > 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 MUST be added +> 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: > From b32150a62c2b9ad825de3a3c498008d939ef9761 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Sat, 10 Aug 2024 16:59:37 +0200 Subject: [PATCH 33/45] update rationale to explain the latest evolutions --- CIP-meta-assets (ERC20-like assets)/README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 85ad245c24..03c4b5775b 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -477,7 +477,7 @@ The main purpose of the redeemer is to avoid breaking compatibilities for addtio It MUST also explain how the proposal affects the backward compatibility of existing solutions when applicable. If the proposal responds to a CPS, the 'Rationale' section SHOULD explain how it addresses the CPS, and answer any questions that the CPS poses for potential solutions. --> -The [first proposed implementation](https://github.com/cardano-foundation/CIPs/pull/444/commits/525ce39a89bde1ddb62e126e347828e3bf0feb58) was quite different by the one shown in this document +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; @@ -490,10 +490,14 @@ which included definitions for `TransferFrom`, `Approve` and `RevokeApproval` re 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, 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. +exsisting native tokens are not conflicting for the standard, instead, native tokes are used in this specification for various purposes. ## Path to Active @@ -506,13 +510,13 @@ it would be infact much harder to prove the validity of an utxo without a native - 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 + - independent 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) +- [ ] [PoC implementation](https://github.com/HarmonicLabs/erc20-like) +- [ ] [showcase transactions](https://github.com/HarmonicLabs/erc20-like) - [ ] wallet implementation ## Copyright From c7e6b999dda2d01c9bfe545919229442fdce9300 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Sat, 10 Aug 2024 19:01:26 +0200 Subject: [PATCH 34/45] initial spec --- CIP-meta-assets (ERC20-like assets)/README.md | 288 ++---------------- 1 file changed, 17 insertions(+), 271 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index 946f6385f3..d10a6a2ce9 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -52,138 +52,41 @@ data Data | 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. +and 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. 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) +- 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** +> **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 interoperable and easy to understand and implement -> in this first implementation we restricts transfers from a single account to a single account +> with the goal of keeping the standard simple we allow a single sender > -> if necessary; this restriction might be dropped in a future version of the CIP +> we MUST however account for multiple inputs from the same sender due to +> the creation of new UTxOs in receiving transactions. -The implementation requires +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. -- 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`) +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. -### standard data types used +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. -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` @@ -200,59 +103,6 @@ const Account = pstruct({ }); ``` -> **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. @@ -367,110 +217,6 @@ transaction outputs filtered by `isOwnOutput`; 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? 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)); @@ -68,11 +179,12 @@ Unlike the ERC20 standard; this CIP: > > this would allow for a more powerful model than the account based equivalent but implies higher execution costs > -> with the goal of keeping the standard simple we allow a single sender +> 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. @@ -85,6 +197,35 @@ with the introduction of [CIP-69](https://github.com/cardano-foundation/CIPs/tre - 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] + + transferManagerPolicy <-. stame contract .-> transferManagerContract + + transferManagerPolicy -. mints CNTs .-> transferManagerContract + + transferManagerContract -- transfer --> transferManagerContract + + end + + subgraph stateManager + stateManagerPolicy[(state manager policy)] + stateManagerContract[state manager] + + stateManagerPolicy <-. stame contract .-> stateManagerContract + + stateManagerPolicy -. mints validity NFTs .-> 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. From 1b6fe515702a6f7d5faaf51bd51dc28a1e7175d4 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Thu, 29 Aug 2024 13:40:41 +0200 Subject: [PATCH 36/45] add transfer observer --- CIP-meta-assets (ERC20-like assets)/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index a6275a8027..c3f3b43b5e 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -203,11 +203,12 @@ flowchart LR subgraph transferManager transferManagerPolicy[(transfer manager policy)] transferManagerContract[transfer manager] + transferManagerObserver([transfer manager observer]) - transferManagerPolicy <-. stame contract .-> transferManagerContract - transferManagerPolicy -. mints CNTs .-> transferManagerContract + transferManagerContract <-. validates many inputs .-> transferManagerObserver + transferManagerContract -- transfer --> transferManagerContract end @@ -216,8 +217,6 @@ flowchart LR stateManagerPolicy[(state manager policy)] stateManagerContract[state manager] - stateManagerPolicy <-. stame contract .-> stateManagerContract - stateManagerPolicy -. mints validity NFTs .-> stateManagerContract end From 09ec4a786de3a2718c00e9b875e8f38047809826 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Thu, 29 Aug 2024 13:42:19 +0200 Subject: [PATCH 37/45] add state mutation --- CIP-meta-assets (ERC20-like assets)/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index c3f3b43b5e..e9e185a35c 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -218,6 +218,8 @@ flowchart LR stateManagerContract[state manager] stateManagerPolicy -. mints validity NFTs .-> stateManagerContract + + stateManagerContract -- mutates state --> stateManagerContract end stateManager -. hash .-> transferManager From f5b4ad095cf67a3754ce12877ade7c0304002b74 Mon Sep 17 00:00:00 2001 From: Michele Nuzzi Date: Fri, 30 Aug 2024 00:58:19 +0200 Subject: [PATCH 38/45] transactions --- CIP-meta-assets (ERC20-like assets)/README.md | 193 +++++++++++------- 1 file changed, 117 insertions(+), 76 deletions(-) diff --git a/CIP-meta-assets (ERC20-like assets)/README.md b/CIP-meta-assets (ERC20-like assets)/README.md index e9e185a35c..3821cb69a2 100644 --- a/CIP-meta-assets (ERC20-like assets)/README.md +++ b/CIP-meta-assets (ERC20-like assets)/README.md @@ -230,134 +230,175 @@ flowchart LR 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. -### `Account` +### Datums and Redeemers -The `Account` data type is used as the `accountManager` datum; and is defined as follows: +#### `Account` + +The `Account` data type is used as the `stateManager` datum; and is defined as follows: ```ts const Account = pstruct({ Account: { - amount: int, credentials: PCredential.type, - currencySym: PCurrencySymbol.type, state: data } }); ``` -#### Common operations and values +The only rule when spending an utxo with `Account` datum is that the NFT present on the utxo +stays in the same contract. -Before proceeding with the redeemers validation logic here are some common operations and values between some of the redeemers. +The standard does not impose any rules on the redeemer to spend the utxo, +as updating the state is implementation-specific. -##### `ownUtxoRef` +#### `TransferRedeemer` -the `accountManager` contract is meant to be used only as spending validator. +redeemer to be used in the withdraw 0 contract +when spending (possibly many) utxos from the `transferManager` contract. -As such we can extract the utxo being spent from the `ScriptPurpose` when constructed with the `Spending` constructor and fail for the rest. +> NOTE: +> +> there is no standard datum for the `transferManager` since the utxos might not have a datum at all +> +> ```ts -const ownUtxoRef = plet( - pmatch( ctx.purpose ) - .onSpending(({ utxoRef }) => utxoRef) - ._( _ => perror( PTxOutRef.type ) ) -); +const TransferOutput = pstruct({ + TransferOutput: { + credential: PCredential.type, + amount: int + } +}); + +const TransferRedeemer = pstruct({ + Transfer: { + inputIndicies: list( int ), + outputs: list( TransferOutput.type ), + firstOutputIndex: int + } +}); ``` -##### `validatingInput` +#### `SingleTransferRedeemer` -is the input with `utxoRef` field equivalent to `ownUtxoRef` +redeemer to be used on a single utxo of the `transferManager`; ```ts -const validatingInput = plet( - pmatch( - tx.inputs.find( i => i.utxoRef.eq( ownUtxoRef ) ) - ) - .onJust(({ val }) => val.resolved ) - .onNothing(_ => perror( PTxOut.type ) ) -); +const SingleTransferRedeemer = pstruct({ + Transfer: {}, + Burn: {} +}); ``` -##### `ownCreds` -from the `validatingInput`: +### Transactions -```ts -validatingInput.resolved.address.credential -``` +#### New Account generation -##### `ownValue` -from the `validatingInput`: +```mermaid +flowchart LR -```ts -validatingInput.resolved.value + stateManagerPolicy[(state manager policy)] + + subgraph transaction + .[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + end + + stateManagerContract[state manager] + + stateManagerPolicy -. validity NFTs .-> transaction + --o stateManagerContract ``` -##### `isOwnOutput` +The only purpose of this transaction is to validate the initial state at the moment of creation of an account -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" +#### Account State Update -```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 ) ) - ) - ) -); -``` +```mermaid +flowchart LR -##### `isOwnInput` + subgraph transaction + .[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + end -an input is "own" if the resolved field is "own"; + stateManagerContract[state manager] + stateManager[state manager] -```ts -const isOwnInput = plet( - pfn([ PTxInInfo.type ], bool ) - ( input => isOwnOutput.$( input.resolved ) ) -); + stateManager --o transaction + --o stateManagerContract ``` -##### `isOwnCurrencySym` +also here, the standard does not impose any rules, as state update is meant to be implementation-specific -given an asset policy we might want to know if it is the one being validated. +#### Minting Tokens -this is done by comparing it with the one specified in the datum field +```mermaid +flowchart LR + + subgraph transaction + .[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + end + + transferManagerPolicy[(transfer manager policy)] + transferManagerContract[transfer manager] + + transferManagerPolicy -. mints CNTs .-> transaction --o transferManagerContract -```ts -const isOwnCurrSym = plet( account.currencySym.peq ); ``` -##### `outIncludesNFT` +implementation specific -given a transaction output is useful to check if the value includes the NFT generated from the `accountFactory`; +#### Burning Tokens -this is done by checking that at least one of the attached value's entry satisfies `isOwnCurrencySym` for the policy. +```mermaid +flowchart LR -```ts - const outIncludesNFT = plet( - pfn([ PTxOut.type ], bool ) - ( out => out.value.some( entry => isOwnCurrSym.$( entry.fst ) ) ) -); + subgraph transaction + .[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + end + + transferManagerContract[transfer manager] + + transferManagerContract -- Burn --o transaction -..-x a[ ] + + style a fill:#FFFFFF00, stroke:#FFFFFF00; ``` -##### `ownOuts` +#### Transfer + +```mermaid +flowchart LR + transferManagerContract[transfer manager] + transferManagerObserver([transfer manager observer]) + + A[transfer manager] + B[transfer manager] + same[transfer manager] + + subgraph transaction + .[ ] + style . fill:#FFFFFF00, stroke:#FFFFFF00; + end + + transferManagerObserver -. validates inputs .-> transaction -transaction outputs filtered by `isOwnOutput`; + transferManagerContract --o transaction + transferManagerContract -- possibly --o transaction + transferManagerContract -- many --o transaction + transferManagerContract -- inputs --o transaction + transferManagerContract -- (same credentials) --o transaction -##### `ownInputs` + transaction -- stake creds A --o A + transaction -- stake creds B --o B + transaction -- change (if needed) --o same +``` -transaction inputs filtered by `isOwnInput`; ## Rationale: how does this CIP achieve its goals?