diff --git a/lnpbp-0020.md b/lnpbp-0020.md index b4ff183..6570c9c 100644 --- a/lnpbp-0020.md +++ b/lnpbp-0020.md @@ -1,249 +1,141 @@ ``` LNPBP: 0020 +Aliases: RGB20 Vertical: Smart contracts -Title: RGB fungible assets schema (RGB-20) -Authors: Dr Maxim Orlovsky , +Title: RGB fungible assets interface (RGB-20) +Authors: Dr Maxim Orlovsky , Giacomo Zucco, Marco Amadori, Nicola Busanello, Federico Tenga, Sabina Sachtachtinskagia, Martino Salvetti -Comments-URI: -Status: Final +Comments-URI: +Status: Proposal Type: Standards Track Created: 2019-09-23 -Finalized: 2020-10-10 +Updated: 2022-12-23 +Finalized: ~ +Copyright: (0) public domain License: CC0-1.0 ``` -Schema ID: `sch19s8js2gyxvtyzztp82l4tt4gr3x8m28yrgks93exsfuwc5u4fqlqf46ah5` - -Encoded schema data: `schema1qxx4qkgjsgcqcl2atrzgmugynnpuk7q5c7dcplkppl9jr0ywx75xnk3zyvup3p3ke99auju22h9734efqs6gfppkg6qcyy69a8uhnw7czt37ckszd950xwwn9mjr59payf3sd2yuezjdy5vu5jdkew7mr78prvvnjkg0x34qyf6fdfsxyd4fk6guzrpuuxp7exgkyhlntx8ruw9j2s2e7dt8sgjhczjkk5e2a3np886u86mq5ge92r5ckde9gr4htt2td66gkzvypm2hwpsr8gdm6vqzmgca3ltn8j0qqkd0rl5s6f3nllngjfldlt7kz7uarne8w0zkhwpr90k0smcxk48dx` - -Schema source: -```rust -Schema { - rgb_features: none!(), - root_id: none!(), - genesis: GenesisSchema { - metadata: type_map! { - FieldType::Ticker => Once, - FieldType::Name => Once, - FieldType::ContractText => NoneOrOnce, - FieldType::Precision => Once, - FieldType::Timestamp => Once, - FieldType::IssuedSupply => Once - }, - owned_rights: type_map! { - OwnedRightsType::Inflation => NoneOrMore, - OwnedRightsType::Epoch => NoneOrOnce, - OwnedRightsType::Assets => NoneOrMore, - OwnedRightsType::Renomination => NoneOrOnce - }, - public_rights: none!(), - abi: none!(), - }, - extensions: none!(), - transitions: type_map! { - TransitionType::Issue => TransitionSchema { - metadata: type_map! { - FieldType::IssuedSupply => Once - }, - closes: type_map! { - OwnedRightsType::Inflation => Once - }, - owned_rights: type_map! { - OwnedRightsType::Inflation => NoneOrMore, - OwnedRightsType::Epoch => NoneOrOnce, - OwnedRightsType::Assets => NoneOrMore - }, - public_rights: none!(), - abi: bmap! { - // sum(in(inflation)) >= sum(out(inflation), out(assets)) - TransitionAction::Validate => Procedure::Embedded(StandardProcedure::FungibleInflation) - } - }, - TransitionType::Transfer => TransitionSchema { - metadata: type_map! {}, - closes: type_map! { - OwnedRightsType::Assets => NoneOrMore - }, - owned_rights: type_map! { - OwnedRightsType::Assets => NoneOrMore - }, - public_rights: none!(), - abi: none!() - }, - TransitionType::Epoch => TransitionSchema { - metadata: none!(), - closes: type_map! { - OwnedRightsType::Epoch => Once - }, - owned_rights: type_map! { - OwnedRightsType::Epoch => NoneOrOnce, - OwnedRightsType::BurnReplace => NoneOrOnce - }, - public_rights: none!(), - abi: none!() - }, - TransitionType::Burn => TransitionSchema { - metadata: type_map! { - FieldType::BurnedSupply => Once, - // Normally issuer should aggregate burned assets into a - // single UTXO; however if burn happens as a result of - // mistake this will be impossible, so we allow to have - // multiple burned UTXOs as a part of a single operation - FieldType::BurnUtxo => OnceOrUpTo(None), - FieldType::HistoryProofFormat => Once, - FieldType::HistoryProof => NoneOrMore, - }, - closes: type_map! { - OwnedRightsType::BurnReplace => Once - }, - owned_rights: type_map! { - OwnedRightsType::BurnReplace => NoneOrOnce - }, - public_rights: none!(), - abi: bmap! { - TransitionAction::Validate => Procedure::Embedded(StandardProcedure::ProofOfBurn) - } - }, - TransitionType::BurnAndReplace => TransitionSchema { - metadata: type_map! { - FieldType::BurnedSupply => Once, - // Normally issuer should aggregate burned assets into a - // single UTXO; however if burn happens as a result of - // mistake this will be impossible, so we allow to have - // multiple burned UTXOs as a part of a single operation - FieldType::BurnUtxo => OnceOrMore, - FieldType::HistoryProofFormat => Once, - FieldType::HistoryProof => NoneOrMore - }, - closes: type_map! { - OwnedRightsType::BurnReplace => Once - }, - owned_rights: type_map! { - OwnedRightsType::BurnReplace => NoneOrOnce, - OwnedRightsType::Assets => OnceOrMore - }, - public_rights: none!(), - abi: bmap! { - TransitionAction::Validate => Procedure::Embedded(StandardProcedure::ProofOfBurn) - } - }, - TransitionType::Renomination => TransitionSchema { - metadata: type_map! { - FieldType::Ticker => NoneOrOnce, - FieldType::Name => NoneOrOnce, - FieldType::ContractText => NoneOrOnce, - FieldType::Precision => NoneOrOnce - }, - closes: type_map! { - OwnedRightsType::Renomination => Once - }, - owned_rights: type_map! { - OwnedRightsType::Renomination => NoneOrOnce - }, - public_rights: none!(), - abi: none!() - }, - // Allows split of rights if they were occasionally allocated to the - // same UTXO, for instance both assets and issuance right. Without - // this type of transition either assets or inflation rights will be - // lost. - TransitionType::RightsSplit => TransitionSchema { - metadata: none!(), - closes: type_map! { - OwnedRightsType::Inflation => NoneOrMore, - OwnedRightsType::Assets => NoneOrMore, - OwnedRightsType::Epoch => NoneOrOnce, - OwnedRightsType::BurnReplace => NoneOrOnce, - OwnedRightsType::Renomination => NoneOrOnce - }, - owned_rights: type_map! { - OwnedRightsType::Inflation => NoneOrMore, - OwnedRightsType::Assets => NoneOrMore, - OwnedRightsType::Epoch => NoneOrOnce, - OwnedRightsType::BurnReplace => NoneOrOnce, - OwnedRightsType::Renomination => NoneOrOnce - }, - public_rights: none!(), - abi: bmap! { - // We must allocate exactly one or none rights per each - // right used as input (i.e. closed seal); plus we need to - // control that sum of inputs is equal to the sum of outputs - // for each of state types having assigned confidential - // amounts - TransitionAction::Validate => Procedure::Embedded(StandardProcedure::RightsSplit) - } - } - }, - field_types: type_map! { - // Rational: if we will use just 26 letters of English alphabet (and - // we are not limited by them), we will have 26^8 possible tickers, - // i.e. > 208 trillions, which is sufficient amount - FieldType::Ticker => DataFormat::String(8), - FieldType::Name => DataFormat::String(256), - // Contract text may contain URL, text or text representation of - // Ricardian contract, up to 64kb. If the contract doesn't fit, a - // double SHA256 hash and URL should be used instead, pointing to - // the full contract text, where hash must be represented by a - // hexadecimal string, optionally followed by `\n` and text URL - FieldType::ContractText => DataFormat::String(core::u16::MAX), - FieldType::Precision => DataFormat::Unsigned(Bits::Bit8, 0, 18u128), - // We need this b/c allocated amounts are hidden behind Pedersen - // commitments - FieldType::IssuedSupply => DataFormat::Unsigned(Bits::Bit64, 0, core::u64::MAX as u128), - // Supply in either burn or burn-and-replace procedure - FieldType::BurnedSupply => DataFormat::Unsigned(Bits::Bit64, 0, core::u64::MAX as u128), - // While UNIX timestamps allow negative numbers; in context of RGB - // Schema, assets can't be issued in the past before RGB or Bitcoin - // even existed; so we prohibit all the dates before RGB release - // This timestamp is equal to 10/10/2020 @ 2:37pm (UTC) - FieldType::Timestamp => DataFormat::Integer(Bits::Bit64, 1602340666, core::i64::MAX as i128), - FieldType::HistoryProof => DataFormat::Bytes(core::u16::MAX), - FieldType::HistoryProofFormat => DataFormat::Enum(HistoryProofFormat::all()), - FieldType::BurnUtxo => DataFormat::TxOutPoint - }, - owned_right_types: type_map! { - OwnedRightsType::Inflation => StateSchema { - // How much issuer can issue tokens on this path. If there is no - // limit, than `core::u64::MAX` / sum(inflation_assignments) - // must be used, as this will be a de-facto limit to the - // issuance - format: StateFormat::CustomData(DataFormat::Unsigned(Bits::Bit64, 0, core::u64::MAX as u128)), - // Validation involves other state data, so it is performed - // at the level of `issue` state transition - abi: none!() - }, - OwnedRightsType::Assets => StateSchema { - format: StateFormat::DiscreteFiniteField(DiscreteFiniteFieldFormat::Unsigned64bit), - abi: bmap! { - // sum(inputs) == sum(outputs) - AssignmentAction::Validate => Procedure::Embedded(StandardProcedure::NoInflationBySum) - } - }, - OwnedRightsType::Epoch => StateSchema { - format: StateFormat::Declarative, - abi: none!() - }, - OwnedRightsType::BurnReplace => StateSchema { - format: StateFormat::Declarative, - abi: none!() - }, - OwnedRightsType::Renomination => StateSchema { - format: StateFormat::Declarative, - abi: none!() - } - }, - public_right_types: none!(), -} +- [Abstract](#abstract) +- [Background](#background) +- [Motivation](#motivation) +- [Design](#design) +- [Specification](#specification) +- [Compatibility](#compatibility) +- [Rationale](#rationale) +- [Reference implementation](#reference-implementation) +- [Acknowledgements](#acknowledgements) +- [References](#references) +- [Copyright](#copyright) + + +## Abstract + + +## Background + + +## Motivation + + +## Design + + + +## Specification + +Interface specification is the following Contractum code: + +```haskell +-- # Defining main data structures + +-- number of decimal fractions (decimal numbers after floating point) +data DecFractions :: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | + 14 | 15 | 16 | 17 | 18 + +data TxOut :: txid [Byte ^ 32], vout U16 + +data POR :: -- proof of reserves + utxo TxOut, + proof [Bytes] -- auxilary data which are schema-specific + +data Amount :: U64 -- asset amount + +data Denomination :: + ticker [Ascii ^ 1..8], + name [Ascii ^ 1..40], + details [Unicode ^ 40..256]?, + precision DecFractions + +interface RGB20 :: RGB20Info + global Denomination :: Denomination + + -- Contract text is separated from the denomination since it must not be + -- changeable by an issuer. + global Contract :: [Unicode] + -- The difference between `global _ :: [_]` and `global _? :: _` is that + -- the first indicates that the global state may not be present, while + -- the second requires the state must be present and have a form of array + -- of the elements. + + -- state which accumulates amounts issued + global Issued* :: Amount + -- state which accumulates amounts burned + global Burned* :: Amount + -- state which accumulates amounts burned and then replaced + global Repalced* :: Amount + + owned IssueRight* :: Amount + owned DenominationRight? + owned BurnRight? + + owned Assets* :: Amount + + -- operations are declared as + -- `op` NAME `::` CLOSED_SEALS,+ INPUT_METADATA,* + -- `->` DEFINED_SEALS,+ + -- `<-` NEW_GLOBAL_STATE,* + -- `!!` ERRORS|* + + op transfer :: inputs [Assets] + -> beneficiaries [Assets] + !! inequalAmounts + + -- question mark denotes optional operation, which may not be supported by + -- some of schemata implementing the intrface + + op? issue :: using IssueRight + -> next IssueRight?, beneficiaries [Assets] + <- amount Issued + !! nonEqualAmounts + + op? dcntrlIssue -> reserves POR, beneficiaries [Assets] + <- amount Issued + !! invalidReserves + | insufficientReserves + + op? burn :: using BurnRight, proofs [POR], amount Amount + -> next BurnRight? + <- amount Burned + !! nonEqualAmounts + | invalidProof(POR) + + op? replace :: using BurnRight, proofs [POR] + -> next BurnRight?, beneficiaries [Assets] + <- amount Replaced + !! nonEqualAmounts + | invalidProof(POR) + + op? rename :: using DenominationRight + -> next DenominationRight?, new Denomination ``` -## Subschemata +## Compatibility -- https://github.com/LNP-BP/LNPBPs/issues/44 ## Rationale @@ -251,3 +143,85 @@ Include from - https://github.com/LNP-BP/LNPBPs/issues/27 - https://github.com/LNP-BP/LNPBPs/issues/28 - https://github.com/LNP-BP/LNPBPs/issues/50 + +## Reference implementation + +Simple asset issuance: + +```Haskell +import RGB20 + +schema SimpleAsset + global Denomination :: Denomination + global Contract :: [Unicode] + + global Issued* :: Amount + owned Assets* :: Amount + + genesis -> allocations [Assets] + <- totalAmount Issued, naming Denomination, contract Contract + sum allocations =? totalAmount !! nonEqualAmounts + + op transfer :: inputs [Assets] -> beneficiaries [Assets] + sum inputs =? sum beneficiaries !! nonEqualAmounts + +implement RGB20 for SimpleAsset + +let issuerOwned := Seal fac503c4641c3deda72a2d00bc9d6ff1094b15276c386efea403746a91436772, 1 + +contract sampleAsset := SimpleAsset ( + naming := Denomination( + ticker := "OTI", + name := "One time issued token", + details := "Absolutely useless", + precision := 8 + ), + contract := """ + THE ASSET TOKEN IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE ASSET TOKEN OR THE USE + OR OTHER DEALINGS IN THE CONTRACT. + """, + allocations := [ + (issuerOwned, 10_000_000__0000_0000) + ], + totalAmount := 10_000_000__0000_0000 +) + +let friendOwned := Seal ~, 0 +let issuerChange := Seal ~, 1 + +transition sendToFriend := SimpleAsset.transfer ( + inputs := [issuerOwned], + beneficiaries := [ + (friendOwned, 1_000_000__0000_0000), + (issuerChange, 9_000_000__0000_0000) + ] +) +``` + + +## Acknowledgements + + +## References + + +## Copyright + +This document is licensed under the Creative Commons CC0 1.0 Universal license. + +

+ + CC0 + +
+ To the extent possible under law, + + LNP/BP Standards Association + has waived all copyright and related or neighboring rights to this work. +