From a5baa8964039abb3ef639f5ba449fd3f5de043cf Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 2 Dec 2023 19:22:51 +0100 Subject: [PATCH 01/31] chore: move invoice library from rgb-wallet repo --- Cargo.lock | 154 +++++++--- Cargo.toml | 17 +- invoice/Cargo.toml | 34 +++ invoice/src/amount.rs | 303 +++++++++++++++++++ invoice/src/builder.rs | 169 +++++++++++ invoice/src/invoice.rs | 70 +++++ invoice/src/lib.rs | 43 +++ invoice/src/parse.rs | 654 +++++++++++++++++++++++++++++++++++++++++ src/interface/rgb20.rs | 5 +- src/interface/rgb25.rs | 3 +- src/lib.rs | 3 +- src/stl/mod.rs | 7 +- src/stl/specs.rs | 277 +---------------- src/stl/stl.rs | 5 +- 14 files changed, 1414 insertions(+), 330 deletions(-) create mode 100644 invoice/Cargo.toml create mode 100644 invoice/src/amount.rs create mode 100644 invoice/src/builder.rs create mode 100644 invoice/src/invoice.rs create mode 100644 invoice/src/lib.rs create mode 100644 invoice/src/parse.rs diff --git a/Cargo.lock b/Cargo.lock index 1b18e458..49f08cb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,12 +158,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + [[package]] name = "bitcoin-private" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -239,6 +261,17 @@ dependencies = [ "strict_encoding", ] +[[package]] +name = "bp-invoice" +version = "0.11.0-beta.2" +source = "git+https://github.com/BP-WG/bp-std?branch=v0.11#664fa950efcb5bb653c9af4238c64e0610447200" +dependencies = [ + "amplify", + "bech32", + "bitcoin_hashes", + "bp-consensus", +] + [[package]] name = "bp-seals" version = "0.11.0-beta.1" @@ -344,9 +377,9 @@ checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" @@ -389,6 +422,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -423,9 +465,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -433,6 +475,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hex-conservative" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -468,15 +516,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -532,9 +580,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "paste" @@ -542,6 +590,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -550,9 +604,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -617,6 +671,23 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "rgb-invoice" +version = "0.11.0-beta.2" +dependencies = [ + "amplify", + "baid58", + "bp-core", + "bp-invoice", + "fluent-uri", + "indexmap", + "percent-encoding", + "rgb-core", + "serde", + "strict_encoding", + "strict_types", +] + [[package]] name = "rgb-std" version = "0.11.0-beta.2" @@ -631,6 +702,7 @@ dependencies = [ "indexmap", "rand", "rgb-core", + "rgb-invoice", "serde", "strict_encoding", "strict_types", @@ -657,9 +729,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "scoped-tls" @@ -685,7 +757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acea373acb8c21ecb5a23741452acd2593ed44ee3d343e72baaa143bc89d0d5" dependencies = [ "rand", - "secp256k1-sys 0.9.0", + "secp256k1-sys 0.9.1", "serde", ] @@ -700,9 +772,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e67c467c38fd24bd5499dc9a18183b31575c12ee549197e3e20d57aa4fe3b7" +checksum = "4dd97a086ec737e30053fd5c46f097465d25bb81dd3608825f65298c4c98be83" dependencies = [ "cc", ] @@ -732,18 +804,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -975,9 +1047,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -985,9 +1057,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", @@ -1000,9 +1072,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -1012,9 +1084,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1022,9 +1094,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -1035,15 +1107,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-bindgen-test" -version = "0.3.38" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6433b7c56db97397842c46b67e11873eda263170afeb3a2dc74a7cb370fee0d" +checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403" dependencies = [ "console_error_panic_hook", "js-sys", @@ -1055,9 +1127,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.38" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "493fcbab756bb764fa37e6bee8cec2dd709eb4273d06d0c282a5e74275ded735" +checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" dependencies = [ "proc-macro2", "quote", @@ -1066,9 +1138,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -1142,9 +1214,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 38319151..b3e4dc48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,12 @@ [workspace] members = [ ".", + "invoice", "stl" ] default-members = [ ".", + "invoice" ] resolver = "2" @@ -13,6 +15,8 @@ version = "0.11.0-beta.2" authors = ["Dr Maxim Orlovsky "] homepage = "https://github.com/RGB-WG" repository = "https://github.com/RGB-WG/rgb-wallet" +keywords = ["bitcoin", "lightning", "rgb", "smart-contracts", "lnp-bp"] +categories = ["cryptography::cryptocurrencies"] rust-version = "1.67" # Due to strict encoding library edition = "2021" license = "Apache-2.0" @@ -24,15 +28,17 @@ strict_encoding = "2.6.1" strict_types = "1.6.3" commit_verify = { version = "0.11.0-beta.1", features = ["stl"] } bp-core = { version = "0.11.0-beta.1", features = ["stl"] } +bp-invoice = { version = "0.11.0-beta.2" } rgb-core = { version = "0.11.0-beta.2", features = ["stl"] } +indexmap = "2.0.2" serde_crate = { package = "serde", version = "1", features = ["derive"] } [package] name = "rgb-std" version = { workspace = true } description = "RGB standard library for working with smart contracts on Bitcoin & Lightning" -keywords = ["bitcoin", "lightning", "rgb", "smart-contracts", "lnp-bp"] -categories = ["cryptography::cryptocurrencies"] +keywords = { workspace = true } +categories = { workspace = true } authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } @@ -52,10 +58,11 @@ strict_types = { workspace = true } commit_verify = { workspace = true } bp-core = { workspace = true } rgb-core = { workspace = true } +rgb-invoice = { version = "0.11.0-beta.2", path = "invoice" } baid58 = { workspace = true } base85 = "=2.0.0" chrono = "0.4.31" -indexmap = "2.0.2" +indexmap = { workspace = true } serde_crate = { workspace = true, optional = true } [features] @@ -69,6 +76,7 @@ serde = [ "commit_verify/serde", "bp-core/serde", "rgb-core/serde", + "rgb-invoice/serde" ] fs = [] @@ -82,3 +90,6 @@ wasm-bindgen-test = "0.3" [package.metadata.docs.rs] features = [ "all" ] + +[patch.crates-io] +bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } diff --git a/invoice/Cargo.toml b/invoice/Cargo.toml new file mode 100644 index 00000000..181595f6 --- /dev/null +++ b/invoice/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "rgb-invoice" +version = { workspace = true } +description = "Invoicing library for RGB smart contracts" +keywords = { workspace = true } +categories = { workspace = true } +readme = "../README.md" +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +rust-version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[lib] +name = "invoice" + +[dependencies] +amplify = { workspace = true } +baid58 = { workspace = true } +strict_encoding = { workspace = true } +strict_types = { workspace = true } +bp-core = { workspace = true } +bp-invoice = { workspace = true } +rgb-core = { workspace = true } +indexmap = { workspace = true } +fluent-uri = "0.1.4" +percent-encoding = "2.3.0" +serde_crate = { workspace = true, optional = true } + +[features] +default = [] +serde = ["serde_crate"] +# TODO: Separate URL with a feature gate diff --git a/invoice/src/amount.rs b/invoice/src/amount.rs new file mode 100644 index 00000000..f5785c9c --- /dev/null +++ b/invoice/src/amount.rs @@ -0,0 +1,303 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt; +use std::fmt::{Display, Formatter, Write}; +use std::iter::Sum; + +use bp::Sats; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use strict_encoding::{StrictDeserialize, StrictSerialize}; +use strict_types::StrictVal; + +use crate::LIB_NAME_RGB_CONTRACT; + +#[derive( + Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From +)] +#[wrapper(Display, FromStr, Add, Sub, Mul, Div, Rem)] +#[wrapper_mut(AddAssign, SubAssign, MulAssign, DivAssign, RemAssign)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_CONTRACT)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct Amount( + #[from] + #[from(u32)] + #[from(u16)] + #[from(u8)] + #[from(Sats)] + u64, +); + +impl StrictSerialize for Amount {} +impl StrictDeserialize for Amount {} + +impl Amount { + pub const ZERO: Self = Amount(0); + + pub fn from_strict_val_unchecked(value: &StrictVal) -> Self { + value.unwrap_uint::().into() + } + + pub fn with_precision(amount: u64, precision: impl Into) -> Self { + precision.into().unchecked_convert(amount) + } + + pub fn with_precision_checked(amount: u64, precision: impl Into) -> Option { + precision.into().checked_convert(amount) + } + + pub fn value(self) -> u64 { self.0 } + + pub fn split(self, precision: impl Into) -> (u64, u64) { + let precision = precision.into(); + let int = self.floor(precision); + let fract = self.rem(precision); + (int, fract) + } + + pub fn round(&self, precision: impl Into) -> u64 { + let precision = precision.into(); + let mul = precision.multiplier(); + if self.0 == 0 { + return 0; + } + let inc = 2 * self.rem(precision) / mul; + self.0 / mul + inc + } + + pub fn ceil(&self, precision: impl Into) -> u64 { + let precision = precision.into(); + if self.0 == 0 { + return 0; + } + let inc = if self.rem(precision) > 0 { 1 } else { 0 }; + self.0 / precision.multiplier() + inc + } + + pub fn floor(&self, precision: impl Into) -> u64 { + if self.0 == 0 { + return 0; + } + self.0 / precision.into().multiplier() + } + + pub fn rem(&self, precision: impl Into) -> u64 { + self.0 % precision.into().multiplier() + } + + pub fn saturating_add(&self, other: impl Into) -> Self { + self.0.saturating_add(other.into().0).into() + } + pub fn saturating_sub(&self, other: impl Into) -> Self { + self.0.saturating_sub(other.into().0).into() + } + + pub fn saturating_add_assign(&mut self, other: impl Into) { + *self = self.0.saturating_add(other.into().0).into(); + } + pub fn saturating_sub_assign(&mut self, other: impl Into) { + *self = self.0.saturating_sub(other.into().0).into(); + } +} + +impl Sum for Amount { + fn sum>(iter: I) -> Self { + iter.fold(Amount::ZERO, |sum, value| sum.saturating_add(value)) + } +} + +impl Sum for Amount { + fn sum>(iter: I) -> Self { + iter.fold(Amount::ZERO, |sum, value| sum.saturating_add(value)) + } +} + +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)] +#[repr(u8)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_CONTRACT, tags = repr, into_u8, try_from_u8)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub enum Precision { + Indivisible = 0, + Deci = 1, + Centi = 2, + Milli = 3, + DeciMilli = 4, + CentiMilli = 5, + Micro = 6, + DeciMicro = 7, + #[default] + CentiMicro = 8, + Nano = 9, + DeciNano = 10, + CentiNano = 11, + Pico = 12, + DeciPico = 13, + CentiPico = 14, + Femto = 15, + DeciFemto = 16, + CentiFemto = 17, + Atto = 18, +} +impl StrictSerialize for Precision {} +impl StrictDeserialize for Precision {} + +impl Precision { + pub fn from_strict_val_unchecked(value: &StrictVal) -> Self { value.unwrap_enum() } + pub const fn decimals(self) -> u8 { self as u8 } + pub const fn decimals_u32(self) -> u32 { self as u8 as u32 } + + pub const fn multiplier(self) -> u64 { + match self { + Precision::Indivisible => 1, + Precision::Deci => 10, + Precision::Centi => 100, + Precision::Milli => 1000, + Precision::DeciMilli => 10_000, + Precision::CentiMilli => 100_000, + Precision::Micro => 1_000_000, + Precision::DeciMicro => 10_000_000, + Precision::CentiMicro => 100_000_000, + Precision::Nano => 1_000_000_000, + Precision::DeciNano => 10_000_000_000, + Precision::CentiNano => 100_000_000_000, + Precision::Pico => 1_000_000_000_000, + Precision::DeciPico => 10_000_000_000_000, + Precision::CentiPico => 100_000_000_000_000, + Precision::Femto => 1_000_000_000_000_000, + Precision::DeciFemto => 10_000_000_000_000_000, + Precision::CentiFemto => 100_000_000_000_000_000, + Precision::Atto => 1_000_000_000_000_000_000, + } + } + + pub fn unchecked_convert(self, amount: impl Into) -> Amount { + (amount.into() * self.multiplier()).into() + } + + pub fn checked_convert(self, amount: impl Into) -> Option { + amount + .into() + .checked_mul(self.multiplier()) + .map(Amount::from) + } + pub fn saturating_convert(self, amount: impl Into) -> Amount { + amount.into().saturating_mul(self.multiplier()).into() + } +} + +impl From for u16 { + fn from(value: Precision) -> Self { value as u8 as u16 } +} + +impl From for u32 { + fn from(value: Precision) -> Self { value as u8 as u32 } +} + +impl From for u64 { + fn from(value: Precision) -> Self { value as u8 as u64 } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct CoinAmount { + pub int: u64, + pub fract: u64, + pub precision: Precision, +} + +impl CoinAmount { + pub fn with(value: impl Into, precision: impl Into) -> Self { + let precision = precision.into(); + let value = value.into(); + let (int, fract) = value.split(precision); + CoinAmount { + int, + fract, + precision, + } + } +} + +impl Display for CoinAmount { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut int = self.int.to_string(); + if f.alternate() { + int = int + .chars() + .rev() + .collect::() + .as_bytes() + .chunks(3) + .map(<[u8]>::to_owned) + .map(|mut chunk| unsafe { + chunk.reverse(); + String::from_utf8_unchecked(chunk) + }) + .rev() + .collect::>() + .join("`"); + } + f.write_str(&int)?; + if self.fract > 0 { + f.write_char('.')?; + let mut float = self.fract.to_string(); + let len = float.len(); + if let Some(decimals) = f.precision() { + float.push_str(&"0".repeat(decimals - len)); + } + if f.alternate() { + float = float + .as_bytes() + .chunks(3) + .map(|chunk| unsafe { String::from_utf8_unchecked(chunk.to_owned()) }) + .collect::>() + .join("`"); + } + f.write_str(&float)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[allow(clippy::inconsistent_digit_grouping)] + fn coin_amount() { + let amount = CoinAmount::with(10_000_436_081_95u64, Precision::default()); + assert_eq!(amount.int, 10_000); + assert_eq!(amount.fract, 436_081_95); + assert_eq!(format!("{amount}"), "10000.43608195"); + assert_eq!(format!("{amount:#.10}"), "10`000.436`081`950`0"); + } +} diff --git a/invoice/src/builder.rs b/invoice/src/builder.rs new file mode 100644 index 00000000..b786635c --- /dev/null +++ b/invoice/src/builder.rs @@ -0,0 +1,169 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::str::FromStr; + +use invoice::Network; +use rgb::ContractId; + +use super::{Beneficiary, InvoiceState, Precision, RgbInvoice, RgbTransport, TransportParseError}; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct RgbInvoiceBuilder(RgbInvoice); + +#[allow(clippy::result_large_err)] +impl RgbInvoiceBuilder { + pub fn new(beneficiary: impl Into) -> Self { + Self(RgbInvoice { + transports: vec![RgbTransport::UnspecifiedMeans], + contract: None, + iface: None, + operation: None, + assignment: None, + beneficiary: beneficiary.into(), + owned_state: InvoiceState::Void, + network: None, + expiry: None, + unknown_query: none!(), + }) + } + + pub fn with(contract_id: ContractId, beneficiary: impl Into) -> Self { + Self::new(beneficiary).set_contract(contract_id) + } + + pub fn rgb20(contract_id: ContractId, beneficiary: impl Into) -> Self { + Self::with(contract_id, beneficiary).set_interface("RGB20") + } + + pub fn rgb20_anything(beneficiary: impl Into) -> Self { + Self::new(beneficiary).set_interface("RGB20") + } + + pub fn set_contract(mut self, contract_id: ContractId) -> Self { + self.0.contract = Some(contract_id); + self + } + + pub fn set_interface(mut self, name: &'static str) -> Self { + self.0.iface = Some(tn!(name)); + self + } + + pub fn set_operation(mut self, name: &'static str) -> Self { + self.0.operation = Some(tn!(name)); + self + } + + pub fn set_assignment(mut self, name: &'static str) -> Self { + self.0.assignment = Some(fname!(name)); + self + } + + pub fn set_amount_raw(mut self, amount: u64) -> Self { + self.0.owned_state = InvoiceState::Amount(amount); + self + } + + pub fn set_amount( + self, + integer: u64, + decimals: u64, + precision: Precision, + ) -> Result { + // 2^64 ~ 10^19 < 10^18 (18 is max value for Precision enum) + let pow = 10u64.pow(precision as u32); + // number of decimals can't be larger than the smallest possible integer + if decimals >= pow { + return Err(self); + } + let Some(mut amount) = integer.checked_mul(pow) else { + return Err(self); + }; + amount = amount.checked_add(decimals).expect( + "integer has at least the same number of zeros in the lowest digits as much as \ + decimals has digits at most, so overflow is not possible", + ); + Ok(self.set_amount_raw(amount)) + } + + /// # Safety + /// + /// The function may cause the loss of the information about the precise + /// amout of the asset, since f64 type doesn't provide full precision + /// required for that. + pub unsafe fn set_amount_approx(self, amount: f64, precision: Precision) -> Result { + if amount <= 0.0 { + return Err(self); + } + let coins = amount.floor(); + let cents = amount - coins; + self.set_amount(coins as u64, cents as u64, precision) + } + + pub fn set_network(mut self, network: impl Into) -> Self { + self.0.network = Some(network.into()); + self + } + + pub fn set_expiry_timestamp(mut self, expiry: i64) -> Self { + self.0.expiry = Some(expiry); + self + } + + pub fn add_transport(self, transport: &str) -> Result { + let transport = match RgbTransport::from_str(transport) { + Err(err) => return Err((self, err)), + Ok(transport) => transport, + }; + Ok(self.add_transport_raw(transport)) + } + + pub fn add_transport_raw(mut self, transport: RgbTransport) -> Self { + self.0.transports.push(transport); + self + } + + pub fn add_transports<'a>( + self, + transports: impl IntoIterator, + ) -> Result { + let res = transports + .into_iter() + .map(RgbTransport::from_str) + .collect::, TransportParseError>>(); + let transports = match res { + Err(err) => return Err((self, err)), + Ok(transports) => transports, + }; + Ok(self.add_transports_raw(transports)) + } + + pub fn add_transports_raw( + mut self, + transports: impl IntoIterator, + ) -> Self { + self.0.transports.extend(transports); + self + } + + pub fn finish(self) -> RgbInvoice { self.0 } +} diff --git a/invoice/src/invoice.rs b/invoice/src/invoice.rs new file mode 100644 index 00000000..3b2f97d9 --- /dev/null +++ b/invoice/src/invoice.rs @@ -0,0 +1,70 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use indexmap::IndexMap; +use invoice::{Address, Network}; +use rgb::{AttachId, ContractId, SecretSeal}; +use strict_encoding::{FieldName, TypeName}; + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub enum RgbTransport { + JsonRpc { tls: bool, host: String }, + RestHttp { tls: bool, host: String }, + WebSockets { tls: bool, host: String }, + Storm {/* todo */}, + UnspecifiedMeans, +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)] +pub enum InvoiceState { + #[display("")] + Void, + #[display("{0}")] + Amount(u64), + #[display("...")] // TODO + Data(Vec /* StrictVal */), + #[display(inner)] + Attach(AttachId), +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Display, From)] +#[display(inner)] +pub enum Beneficiary { + #[from] + BlindedSeal(SecretSeal), + #[from] + WitnessUtxo(Address), +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct RgbInvoice { + pub transports: Vec, + pub contract: Option, + pub iface: Option, + pub operation: Option, + pub assignment: Option, + pub beneficiary: Beneficiary, + pub owned_state: InvoiceState, + pub network: Option, + /// UTC unix timestamp + pub expiry: Option, + pub unknown_query: IndexMap, +} diff --git a/invoice/src/lib.rs b/invoice/src/lib.rs new file mode 100644 index 00000000..b4cf7152 --- /dev/null +++ b/invoice/src/lib.rs @@ -0,0 +1,43 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate amplify; +#[macro_use] +extern crate strict_encoding; +#[cfg(feature = "serde")] +extern crate serde_crate as serde; + +/// Re-exporting BP invoice data types. +pub use ::invoice::*; + +#[allow(clippy::module_inception)] +mod invoice; +mod parse; +mod builder; +mod amount; + +pub use amount::{Amount, CoinAmount, Precision}; +pub use builder::RgbInvoiceBuilder; +pub use invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; +pub use parse::{InvoiceParseError, TransportParseError}; + +pub const LIB_NAME_RGB_CONTRACT: &str = "RGBContract"; diff --git a/invoice/src/parse.rs b/invoice/src/parse.rs new file mode 100644 index 00000000..22353d67 --- /dev/null +++ b/invoice/src/parse.rs @@ -0,0 +1,654 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::{self, Debug, Display, Formatter}; +use std::num::ParseIntError; +use std::str::FromStr; + +use fluent_uri::enc::EStr; +use fluent_uri::Uri; +use indexmap::IndexMap; +use invoice::{Address, AddressNetwork, Network, UnknownNetwork}; +use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; +use rgb::{ContractId, SecretSeal}; +use strict_encoding::{InvalidIdent, TypeName}; + +use super::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; + +const OMITTED: char = '~'; +const EXPIRY: &str = "expiry"; +const NETWORK: &str = "network"; +const ENDPOINTS: &str = "endpoints"; +const TRANSPORT_SEP: char = ','; +const TRANSPORT_HOST_SEP: &str = "://"; +const QUERY_ENCODE: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'"') + .add(b'#') + .add(b'<') + .add(b'>') + .add(b'[') + .add(b']') + .add(b'&') + .add(b'='); + +#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] +#[display(inner)] +pub enum TransportParseError { + #[display(doc_comments)] + /// invalid transport {0}. + InvalidTransport(String), + + #[display(doc_comments)] + /// invalid transport host {0}. + InvalidTransportHost(String), +} + +#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] +#[display(inner)] +pub enum InvoiceParseError { + #[from] + Uri(fluent_uri::ParseError), + + #[display(doc_comments)] + /// invalid invoice. + Invalid, + + #[display(doc_comments)] + /// invalid invoice scheme {0}. + InvalidScheme(String), + + #[display(doc_comments)] + /// no invoice transport has been provided. + NoTransport, + + #[display(doc_comments)] + /// invalid invoice: contract ID present but no contract interface provided. + ContractIdNoIface, + + #[display(doc_comments)] + /// invalid contract ID. + InvalidContractId(String), + + #[display(doc_comments)] + /// invalid interface {0}. + InvalidIface(String), + + #[display(doc_comments)] + /// invalid expiration timestamp {0}. + InvalidExpiration(String), + + #[display(inner)] + #[from] + InvalidNetwork(UnknownNetwork), + + #[display(doc_comments)] + /// address network `{0:#?}` doesn't match network `{1}` specified in the + /// invoice. + NetworkMismatch(AddressNetwork, Network), + + #[display(doc_comments)] + /// invalid query parameter {0}. + InvalidQueryParam(String), + + #[from] + Id(baid58::Baid58ParseError), + + #[display(doc_comments)] + /// can't recognize beneficiary "": it should be either a bitcoin address or + /// a blinded UTXO seal. + Beneficiary(String), + + #[from] + Num(ParseIntError), + + #[from] + #[display(doc_comments)] + /// invalid interface name. + IfaceName(InvalidIdent), +} + +impl RgbInvoice { + fn has_params(&self) -> bool { + self.expiry.is_some() || + self.transports != vec![RgbTransport::UnspecifiedMeans] || + !self.unknown_query.is_empty() + } + + fn query_params(&self) -> IndexMap { + let mut query_params: IndexMap = IndexMap::new(); + if let Some(expiry) = self.expiry { + query_params.insert(EXPIRY.to_string(), expiry.to_string()); + } + if self.transports != vec![RgbTransport::UnspecifiedMeans] { + let mut transports: Vec = vec![]; + for transport in self.transports.clone() { + transports.push(transport.to_string()); + } + query_params.insert(ENDPOINTS.to_string(), transports.join(&TRANSPORT_SEP.to_string())); + } + query_params.extend(self.unknown_query.clone()); + query_params + } +} + +impl Display for RgbTransport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RgbTransport::JsonRpc { tls, host } => { + let s = if *tls { "s" } else { "" }; + write!(f, "rpc{s}{TRANSPORT_HOST_SEP}{}", host)?; + } + RgbTransport::RestHttp { tls, host } => { + let s = if *tls { "s" } else { "" }; + write!(f, "http{s}{TRANSPORT_HOST_SEP}{}", host)?; + } + RgbTransport::WebSockets { tls, host } => { + let s = if *tls { "s" } else { "" }; + write!(f, "ws{s}{TRANSPORT_HOST_SEP}{}", host)?; + } + RgbTransport::Storm {} => { + write!(f, "storm{TRANSPORT_HOST_SEP}_/")?; + } + RgbTransport::UnspecifiedMeans => {} + }; + Ok(()) + } +} + +impl FromStr for RgbTransport { + type Err = TransportParseError; + + fn from_str(s: &str) -> Result { + let tokens = s.split_once(TRANSPORT_HOST_SEP); + if tokens.is_none() { + return Err(TransportParseError::InvalidTransport(s.to_string())); + } + let (trans_type, host) = tokens.unwrap(); + if host.is_empty() { + return Err(TransportParseError::InvalidTransportHost(host.to_string())); + } + let host = host.to_string(); + let transport = match trans_type { + "rpc" => RgbTransport::JsonRpc { tls: false, host }, + "rpcs" => RgbTransport::JsonRpc { tls: true, host }, + "http" => RgbTransport::RestHttp { tls: false, host }, + "https" => RgbTransport::RestHttp { tls: true, host }, + "ws" => RgbTransport::WebSockets { tls: false, host }, + "wss" => RgbTransport::WebSockets { tls: true, host }, + "storm" => RgbTransport::Storm {}, + _ => return Err(TransportParseError::InvalidTransport(s.to_string())), + }; + Ok(transport) + } +} + +impl Display for RgbInvoice { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let amt = self.owned_state.to_string(); + if let Some(contract) = self.contract { + Display::fmt(&contract, f)?; + f.write_str("/")?; + } else { + write!(f, "rgb:{OMITTED}/")?; + } + if let Some(iface) = self.iface.clone() { + write!(f, "{iface}/")?; + } else { + write!(f, "{OMITTED}/")?; + } + if let Some(ref op) = self.operation { + write!(f, "{op}/")?; + } + if let Some(ref assignment_name) = self.assignment { + write!(f, "{assignment_name}/")?; + } + if !amt.is_empty() { + write!(f, "{amt}+")?; + } + Display::fmt(&self.beneficiary, f)?; + if self.has_params() { + f.write_str("?")?; + } + let query_params = self.query_params(); + for (key, val) in query_params.iter().take(1) { + write!( + f, + "{}={}", + utf8_percent_encode(key, QUERY_ENCODE), + utf8_percent_encode(val, QUERY_ENCODE) + )?; + } + for (key, val) in query_params.iter().skip(1) { + write!( + f, + "&{}={}", + utf8_percent_encode(key, QUERY_ENCODE), + utf8_percent_encode(val, QUERY_ENCODE) + )?; + } + Ok(()) + } +} + +impl FromStr for RgbInvoice { + type Err = InvoiceParseError; + + fn from_str(s: &str) -> Result { + let uri = Uri::parse(s)?; + + let scheme = uri.scheme().ok_or(InvoiceParseError::Invalid)?.to_string(); + if scheme != "rgb" { + return Err(InvoiceParseError::InvalidScheme(scheme)); + } + + let path = uri + .path() + .segments() + .map(|e| e.to_string()) + .collect::>(); + + let mut network = None; + let mut address_network = None; + + let mut next_path_index = 0; + + let contract_id_str = &path[next_path_index]; + let contract = match ContractId::from_str(contract_id_str) { + Ok(cid) => Some(cid), + Err(_) if contract_id_str == &OMITTED.to_string() => None, + Err(_) => return Err(InvoiceParseError::InvalidContractId(contract_id_str.clone())), + }; + next_path_index += 1; + + let iface_str = &path[next_path_index]; + let iface = match TypeName::try_from(iface_str.clone()) { + Ok(i) => Some(i), + Err(_) if iface_str == &OMITTED.to_string() => None, + Err(_) => return Err(InvoiceParseError::InvalidIface(iface_str.clone())), + }; + next_path_index += 1; + if contract.is_some() && iface.is_none() { + return Err(InvoiceParseError::ContractIdNoIface); + } + + let mut assignment = path[next_path_index].split('+'); + // TODO: support other state types + let (beneficiary_str, value) = match (assignment.next(), assignment.next()) { + (Some(a), Some(b)) => (b, InvoiceState::Amount(a.parse::()?)), + (Some(b), None) => (b, InvoiceState::Void), + _ => return Err(InvoiceParseError::Invalid), + }; + + let beneficiary = + match (SecretSeal::from_str(beneficiary_str), Address::from_str(beneficiary_str)) { + (Ok(seal), Err(_)) => Beneficiary::BlindedSeal(seal), + (Err(_), Ok(addr)) => { + address_network = Some(addr.network); + Beneficiary::WitnessUtxo(addr) + } + (Err(_), Err(_)) => { + return Err(InvoiceParseError::Beneficiary(beneficiary_str.to_owned())); + } + (Ok(_), Ok(_)) => { + panic!("found a string which is both valid bitcoin address and UTXO blind seal") + } + }; + + let mut query_params = map_query_params(&uri)?; + + let transports = if let Some(endpoints) = query_params.remove(ENDPOINTS) { + let tokens: Vec<&str> = endpoints.split(TRANSPORT_SEP).collect(); + let mut transport_vec: Vec = vec![]; + for token in tokens { + transport_vec.push( + RgbTransport::from_str(token) + .map_err(|e| InvoiceParseError::InvalidQueryParam(e.to_string()))?, + ); + } + transport_vec + } else { + vec![RgbTransport::UnspecifiedMeans] + }; + + let mut expiry = None; + if let Some(exp) = query_params.remove(EXPIRY) { + let timestamp = exp + .parse::() + .map_err(|e| InvoiceParseError::InvalidExpiration(e.to_string()))?; + expiry = Some(timestamp); + } + + if let Some(nw) = query_params.remove(NETWORK) { + let nw = Network::from_str(&nw)?; + if let Some(an) = address_network { + if an.is_testnet() != nw.is_testnet() { + return Err(InvoiceParseError::NetworkMismatch(an, nw)); + } + } + } else if let Some(an) = address_network { + network = Some(match an { + AddressNetwork::Mainnet => Network::Mainnet, + AddressNetwork::Testnet => Network::Testnet3, + AddressNetwork::Regtest => Network::Regtest, + }) + } + + Ok(RgbInvoice { + transports, + contract, + iface, + operation: None, + assignment: None, + beneficiary, + owned_state: value, + network, + expiry, + unknown_query: query_params, + }) + } +} + +fn percent_decode(estr: &EStr) -> Result { + Ok(estr + .decode() + .into_string() + .map_err(|e| InvoiceParseError::InvalidQueryParam(e.to_string()))? + .to_string()) +} + +fn map_query_params(uri: &Uri<&str>) -> Result, InvoiceParseError> { + let mut map: IndexMap = IndexMap::new(); + if let Some(q) = uri.query() { + let params = q.split('&'); + for p in params { + if let Some((k, v)) = p.split_once('=') { + map.insert(percent_decode(k)?, percent_decode(v)?); + } else { + return Err(InvoiceParseError::InvalidQueryParam(p.to_string())); + } + } + } + Ok(map) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse() { + // all path parameters + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + assert_eq!(format!("{invoice:#}"), invoice_str.replace('-', "")); + + // no amount + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // no contract ID + let invoice_str = + "rgb:~/RGB20/utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // no contract ID nor iface + let invoice_str = "rgb:~/~/utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // contract ID provided but no iface + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/~/utxob:\ + egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::ContractIdNoIface))); + + // invalid contract ID + let invalid_contract_id = "invalid"; + let invoice_str = format!( + "rgb:{invalid_contract_id}/RGB20/utxob:\ + egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb" + ); + let result = RgbInvoice::from_str(&invoice_str); + assert!(matches!(result, + Err(InvoiceParseError::InvalidContractId(c)) if c == invalid_contract_id)); + + // with expiration + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + expiry=1682086371"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // bad expiration + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + expiry=six"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidExpiration(_)))); + + // with bad query parameter + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?expiry"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // with an unknown query parameter + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + unknown=new"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // with two unknown query parameters + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + unknown=new&another=new"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // with expiration and an unknown query parameter + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + expiry=1682086371&unknown=new"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // with an unknown query parameter containing percent-encoded text + let invoice_base = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?"; + let query_key_encoded = ":@-%20%23"; + let query_key_decoded = ":@- #"; + let query_val_encoded = "?/.%26%3D"; + let query_val_decoded = "?/.&="; + let invoice = + RgbInvoice::from_str(&format!("{invoice_base}{query_key_encoded}={query_val_encoded}")) + .unwrap(); + let query_params = invoice.query_params(); + assert_eq!(query_params[query_key_decoded], query_val_decoded); + assert_eq!( + invoice.to_string(), + format!("{invoice_base}{query_key_encoded}={query_val_encoded}") + ); + + // no scheme + let invoice_str = "2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/~/utxob:\ + egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::Invalid))); + + // invalid scheme + let invoice_str = "bad:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/~/utxob:\ + egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidScheme(_)))); + + // empty transport endpoint specification + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints="; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // invalid transport endpoint specification + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=bad"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // invalid transport variant + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpca://host.example.com"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // rgb-rpc variant + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpc://host.example.com"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.transports, vec![RgbTransport::JsonRpc { + tls: false, + host: "host.example.com".to_string() + }]); + assert_eq!(invoice.to_string(), invoice_str); + + // rgb-rpc variant, host containing authentication, "-" characters and port + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpcs://user:pass@host-1.ex-ample.com:1234"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.transports, vec![RgbTransport::JsonRpc { + tls: true, + host: "user:pass@host-1.ex-ample.com:1234".to_string() + }]); + assert_eq!(invoice.to_string(), invoice_str); + + // rgb-rpc variant, IPv6 host + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpcs://%5B2001:db8::1%5D:1234"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.transports, vec![RgbTransport::JsonRpc { + tls: true, + host: "[2001:db8::1]:1234".to_string() + }]); + assert_eq!(invoice.to_string(), invoice_str); + + // rgb-rpc variant with missing host + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpc://"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // rgb-rpc variant with invalid separator + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpc/host.example.com"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // rgb-rpc variant with invalid transport host specification + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpc://ho]t"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::Uri(_)))); + + // rgb+http variant + let invoice_str = "rgb:\ + 2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?endpoints=https://\ + host.example.com"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + let transports = vec![RgbTransport::RestHttp { + tls: true, + host: "host.example.com".to_string(), + }]; + assert_eq!(invoice.transports, transports); + assert_eq!(invoice.to_string(), invoice_str); + + // rgb+ws variant + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=wss://host.example.com"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + let transports = vec![RgbTransport::WebSockets { + tls: true, + host: "host.example.com".to_string(), + }]; + assert_eq!(invoice.transports, transports); + assert_eq!(invoice.to_string(), invoice_str); + + // TODO: rgb+storm variant + + // multiple transports + let invoice_str = "rgb:\ + 2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?endpoints=rpcs://\ + host1.example.com,http://host2.example.com,ws://host3.example.com"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + let transports = vec![ + RgbTransport::JsonRpc { + tls: true, + host: "host1.example.com".to_string(), + }, + RgbTransport::RestHttp { + tls: false, + host: "host2.example.com".to_string(), + }, + RgbTransport::WebSockets { + tls: false, + host: "host3.example.com".to_string(), + }, + ]; + assert_eq!(invoice.transports, transports); + assert_eq!(invoice.to_string(), invoice_str); + + // empty transport parse error + let result = RgbTransport::from_str(""); + assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); + + // invalid transport parse error + let result = RgbTransport::from_str("bad"); + assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); + + // invalid transport variant parse error + let result = RgbTransport::from_str("rpca://host.example.com"); + assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); + + // rgb-rpc variant with missing host parse error + let result = RgbTransport::from_str("rpc://"); + assert!(matches!(result, Err(TransportParseError::InvalidTransportHost(_)))); + + // rgb-rpc variant with invalid separator parse error + let result = RgbTransport::from_str("rpc/host.example.com"); + assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); + } +} diff --git a/src/interface/rgb20.rs b/src/interface/rgb20.rs index bd31db70..9bafecaf 100644 --- a/src/interface/rgb20.rs +++ b/src/interface/rgb20.rs @@ -21,6 +21,7 @@ use amplify::confinement::LargeVec; use bp::bc::stl::bp_tx_stl; +use invoice::Amount; use strict_types::{CompileError, LibBuilder, TypeLib}; use super::{ @@ -29,9 +30,7 @@ use super::{ use crate::interface::{ ArgSpec, ContractIface, FungibleAllocation, IfaceId, IfaceWrapper, OutpointFilter, }; -use crate::stl::{ - rgb_contract_stl, Amount, ContractData, DivisibleAssetSpec, StandardTypes, Timestamp, -}; +use crate::stl::{rgb_contract_stl, ContractData, DivisibleAssetSpec, StandardTypes, Timestamp}; pub const LIB_NAME_RGB20: &str = "RGB20"; /// Strict types id for the library providing data types for RGB20 interface. diff --git a/src/interface/rgb25.rs b/src/interface/rgb25.rs index 219a8d83..65bf3ff0 100644 --- a/src/interface/rgb25.rs +++ b/src/interface/rgb25.rs @@ -24,6 +24,7 @@ use std::fmt::Debug; use bp::bc::stl::bp_tx_stl; +use invoice::{Amount, Precision}; use strict_encoding::{StrictDumb, StrictEncode}; use strict_types::stl::std_stl; use strict_types::{CompileError, LibBuilder, TypeLib}; @@ -32,7 +33,7 @@ use super::{ AssignIface, GenesisIface, GlobalIface, Iface, OwnedIface, Req, TransitionIface, VerNo, }; use crate::interface::{ArgSpec, ContractIface, IfaceId, IfaceWrapper}; -use crate::stl::{rgb_contract_stl, Amount, ContractData, Details, Name, Precision, StandardTypes}; +use crate::stl::{rgb_contract_stl, ContractData, Details, Name, StandardTypes}; pub const LIB_NAME_RGB25: &str = "RGB25"; /// Strict types id for the library providing data types for RGB25 interface. diff --git a/src/lib.rs b/src/lib.rs index 598a8adc..6840fdcd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,8 @@ extern crate strict_encoding; #[macro_use] extern crate serde_crate as serde; -pub use rgb::{contract, schema, validation, vm}; +/// Re-exporting all invoice data types (RGB and BP). +pub extern crate invoice; pub mod stl; pub mod interface; diff --git a/src/stl/mod.rs b/src/stl/mod.rs index f5940828..87ae154f 100644 --- a/src/stl/mod.rs +++ b/src/stl/mod.rs @@ -28,14 +28,13 @@ mod chain; pub use chain::ProofOfReserves; use error::Error; +pub use invoice::LIB_NAME_RGB_CONTRACT; pub use mime::{MediaRegName, MediaType}; pub use specs::{ - Amount, AssetNaming, Attachment, BurnMeta, CoinAmount, ContractData, Details, - DivisibleAssetSpec, IssueMeta, Name, Precision, RicardianContract, Ticker, Timestamp, + AssetNaming, Attachment, BurnMeta, ContractData, Details, DivisibleAssetSpec, IssueMeta, Name, + RicardianContract, Ticker, Timestamp, }; pub use stl::{ rgb_contract_stl, rgb_std_stl, StandardTypes, LIB_ID_RGB, LIB_ID_RGB_CONTRACT, LIB_ID_RGB_STD, }; - -pub const LIB_NAME_RGB_CONTRACT: &str = "RGBContract"; pub const LIB_NAME_RGB_STD: &str = "RGBStd"; diff --git a/src/stl/specs.rs b/src/stl/specs.rs index fc33656a..55755d5f 100644 --- a/src/stl/specs.rs +++ b/src/stl/specs.rs @@ -21,15 +21,13 @@ #![allow(unused_braces)] // caused by rustc unable to understand strict_dumb -use std::fmt; -use std::fmt::{Debug, Display, Formatter, Write}; -use std::iter::Sum; +use std::fmt::{self, Debug, Formatter}; use std::str::FromStr; use amplify::ascii::AsciiString; use amplify::confinement::{Confined, NonEmptyString, NonEmptyVec, SmallOrdSet, SmallString, U8}; -use bp::Sats; use chrono::{DateTime, Local, NaiveDateTime, Utc}; +use invoice::Precision; use strict_encoding::stl::{AlphaCapsNum, AsciiPrintable}; use strict_encoding::{ InvalidIdent, StrictDeserialize, StrictDumb, StrictEncode, StrictSerialize, StrictType, @@ -68,262 +66,6 @@ pub struct IssueMeta { impl StrictSerialize for IssueMeta {} impl StrictDeserialize for IssueMeta {} -#[derive( - Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From -)] -#[wrapper(Display, FromStr, Add, Sub, Mul, Div, Rem)] -#[wrapper_mut(AddAssign, SubAssign, MulAssign, DivAssign, RemAssign)] -#[derive(StrictType, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_RGB_CONTRACT)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", transparent) -)] -pub struct Amount( - #[from] - #[from(u32)] - #[from(u16)] - #[from(u8)] - #[from(Sats)] - u64, -); - -impl StrictSerialize for Amount {} -impl StrictDeserialize for Amount {} - -impl Amount { - pub const ZERO: Self = Amount(0); - - pub fn from_strict_val_unchecked(value: &StrictVal) -> Self { - value.unwrap_uint::().into() - } - - pub fn with_precision(amount: u64, precision: impl Into) -> Self { - precision.into().unchecked_convert(amount) - } - - pub fn with_precision_checked(amount: u64, precision: impl Into) -> Option { - precision.into().checked_convert(amount) - } - - pub fn value(self) -> u64 { self.0 } - - pub fn split(self, precision: impl Into) -> (u64, u64) { - let precision = precision.into(); - let int = self.floor(precision); - let fract = self.rem(precision); - (int, fract) - } - - pub fn round(&self, precision: impl Into) -> u64 { - let precision = precision.into(); - let mul = precision.multiplier(); - if self.0 == 0 { - return 0; - } - let inc = 2 * self.rem(precision) / mul; - self.0 / mul + inc - } - - pub fn ceil(&self, precision: impl Into) -> u64 { - let precision = precision.into(); - if self.0 == 0 { - return 0; - } - let inc = if self.rem(precision) > 0 { 1 } else { 0 }; - self.0 / precision.multiplier() + inc - } - - pub fn floor(&self, precision: impl Into) -> u64 { - if self.0 == 0 { - return 0; - } - self.0 / precision.into().multiplier() - } - - pub fn rem(&self, precision: impl Into) -> u64 { - self.0 % precision.into().multiplier() - } - - pub fn saturating_add(&self, other: impl Into) -> Self { - self.0.saturating_add(other.into().0).into() - } - pub fn saturating_sub(&self, other: impl Into) -> Self { - self.0.saturating_sub(other.into().0).into() - } - - pub fn saturating_add_assign(&mut self, other: impl Into) { - *self = self.0.saturating_add(other.into().0).into(); - } - pub fn saturating_sub_assign(&mut self, other: impl Into) { - *self = self.0.saturating_sub(other.into().0).into(); - } -} - -impl Sum for Amount { - fn sum>(iter: I) -> Self { - iter.fold(Amount::ZERO, |sum, value| sum.saturating_add(value)) - } -} - -impl Sum for Amount { - fn sum>(iter: I) -> Self { - iter.fold(Amount::ZERO, |sum, value| sum.saturating_add(value)) - } -} - -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)] -#[repr(u8)] -#[derive(StrictType, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_RGB_CONTRACT, tags = repr, into_u8, try_from_u8)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase") -)] -pub enum Precision { - Indivisible = 0, - Deci = 1, - Centi = 2, - Milli = 3, - DeciMilli = 4, - CentiMilli = 5, - Micro = 6, - DeciMicro = 7, - #[default] - CentiMicro = 8, - Nano = 9, - DeciNano = 10, - CentiNano = 11, - Pico = 12, - DeciPico = 13, - CentiPico = 14, - Femto = 15, - DeciFemto = 16, - CentiFemto = 17, - Atto = 18, -} -impl StrictSerialize for Precision {} -impl StrictDeserialize for Precision {} - -impl Precision { - pub fn from_strict_val_unchecked(value: &StrictVal) -> Self { value.unwrap_enum() } - pub const fn decimals(self) -> u8 { self as u8 } - pub const fn decimals_u32(self) -> u32 { self as u8 as u32 } - - pub const fn multiplier(self) -> u64 { - match self { - Precision::Indivisible => 1, - Precision::Deci => 10, - Precision::Centi => 100, - Precision::Milli => 1000, - Precision::DeciMilli => 10_000, - Precision::CentiMilli => 100_000, - Precision::Micro => 1_000_000, - Precision::DeciMicro => 10_000_000, - Precision::CentiMicro => 100_000_000, - Precision::Nano => 1_000_000_000, - Precision::DeciNano => 10_000_000_000, - Precision::CentiNano => 100_000_000_000, - Precision::Pico => 1_000_000_000_000, - Precision::DeciPico => 10_000_000_000_000, - Precision::CentiPico => 100_000_000_000_000, - Precision::Femto => 1_000_000_000_000_000, - Precision::DeciFemto => 10_000_000_000_000_000, - Precision::CentiFemto => 100_000_000_000_000_000, - Precision::Atto => 1_000_000_000_000_000_000, - } - } - - pub fn unchecked_convert(self, amount: impl Into) -> Amount { - (amount.into() * self.multiplier()).into() - } - - pub fn checked_convert(self, amount: impl Into) -> Option { - amount - .into() - .checked_mul(self.multiplier()) - .map(Amount::from) - } - pub fn saturating_convert(self, amount: impl Into) -> Amount { - amount.into().saturating_mul(self.multiplier()).into() - } -} - -impl From for u16 { - fn from(value: Precision) -> Self { value as u8 as u16 } -} - -impl From for u32 { - fn from(value: Precision) -> Self { value as u8 as u32 } -} - -impl From for u64 { - fn from(value: Precision) -> Self { value as u8 as u64 } -} - -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -pub struct CoinAmount { - pub int: u64, - pub fract: u64, - pub precision: Precision, -} - -impl CoinAmount { - pub fn with(value: impl Into, precision: impl Into) -> Self { - let precision = precision.into(); - let value = value.into(); - let (int, fract) = value.split(precision); - CoinAmount { - int, - fract, - precision, - } - } -} - -impl Display for CoinAmount { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut int = self.int.to_string(); - if f.alternate() { - int = int - .chars() - .rev() - .collect::() - .as_bytes() - .chunks(3) - .map(<[u8]>::to_owned) - .map(|mut chunk| unsafe { - chunk.reverse(); - String::from_utf8_unchecked(chunk) - }) - .rev() - .collect::>() - .join("`"); - } - f.write_str(&int)?; - if self.fract > 0 { - f.write_char('.')?; - let mut float = self.fract.to_string(); - let len = float.len(); - if let Some(decimals) = f.precision() { - float.push_str(&"0".repeat(decimals - len)); - } - if f.alternate() { - float = float - .as_bytes() - .chunks(3) - .map(|chunk| unsafe { String::from_utf8_unchecked(chunk.to_owned()) }) - .collect::>() - .join("`"); - } - f.write_str(&float)?; - } - Ok(()) - } -} - #[derive(Wrapper, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, From)] #[wrapper(Deref, Display)] #[derive(StrictDumb, StrictType, StrictDecode)] @@ -774,18 +516,3 @@ impl ContractData { Self { terms, media } } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - #[allow(clippy::inconsistent_digit_grouping)] - fn coin_amount() { - let amount = CoinAmount::with(10_000_436_081_95u64, Precision::default()); - assert_eq!(amount.int, 10_000); - assert_eq!(amount.fract, 436_081_95); - assert_eq!(format!("{amount}"), "10000.43608195"); - assert_eq!(format!("{amount:#.10}"), "10`000.436`081`950`0"); - } -} diff --git a/src/stl/stl.rs b/src/stl/stl.rs index 8fbc5200..3cc77afb 100644 --- a/src/stl/stl.rs +++ b/src/stl/stl.rs @@ -22,14 +22,15 @@ use bp::bc::stl::bp_tx_stl; use bp::stl::bp_core_stl; use commit_verify::stl::commit_verify_stl; +use invoice::Amount; pub use rgb::stl::{aluvm_stl, rgb_core_stl, LIB_ID_RGB}; use strict_types::stl::{std_stl, strict_types_stl}; use strict_types::typesys::SystemBuilder; use strict_types::{CompileError, LibBuilder, SemId, SymbolicSys, TypeLib, TypeSystem}; use super::{ - Amount, BurnMeta, ContractData, DivisibleAssetSpec, Error, IssueMeta, MediaType, - RicardianContract, Timestamp, LIB_NAME_RGB_CONTRACT, + BurnMeta, ContractData, DivisibleAssetSpec, Error, IssueMeta, MediaType, RicardianContract, + Timestamp, LIB_NAME_RGB_CONTRACT, }; use crate::containers::{Contract, Transfer}; use crate::persistence::Stock; From f2b87e73447a9c4c4ae64f4eb3c18ac15bbcad9e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 11 Dec 2023 00:53:40 +0100 Subject: [PATCH 02/31] wip on transfers --- Cargo.lock | 3 +- Cargo.toml | 1 + invoice/src/invoice.rs | 20 +++- invoice/src/parse.rs | 2 +- src/containers/mod.rs | 2 + src/containers/partials.rs | 92 +++++++++++++++++ src/persistence/hoard.rs | 7 +- src/persistence/inventory.rs | 186 +++++++++++++++++++++++++++++++---- src/persistence/stock.rs | 35 ++++--- 9 files changed, 304 insertions(+), 44 deletions(-) create mode 100644 src/containers/partials.rs diff --git a/Cargo.lock b/Cargo.lock index 49f08cb2..504b0664 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -653,8 +653,7 @@ dependencies = [ [[package]] name = "rgb-core" version = "0.11.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f1484191ad41598bc3c9b2a5aa3ed0a323ce1a56fc3916c8c4f7fa81d8eb" +source = "git+https://github.com/RGB-WG/rgb-core?branch=v0.11#ce9dc87499fb42476d0609f8d4d4a3819f739df6" dependencies = [ "aluvm", "amplify", diff --git a/Cargo.toml b/Cargo.toml index b3e4dc48..45af1438 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,3 +93,4 @@ features = [ "all" ] [patch.crates-io] bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } +rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "v0.11" } diff --git a/invoice/src/invoice.rs b/invoice/src/invoice.rs index 3b2f97d9..e045ce93 100644 --- a/invoice/src/invoice.rs +++ b/invoice/src/invoice.rs @@ -21,7 +21,7 @@ use indexmap::IndexMap; use invoice::{Address, Network}; -use rgb::{AttachId, ContractId, SecretSeal}; +use rgb::{AttachId, ContractId, Layer1, SecretSeal}; use strict_encoding::{FieldName, TypeName}; #[derive(Clone, Eq, PartialEq, Hash, Debug)] @@ -48,10 +48,26 @@ pub enum InvoiceState { #[derive(Clone, Eq, PartialEq, Hash, Debug, Display, From)] #[display(inner)] pub enum Beneficiary { + // TODO: Create wrapping type for SecretSeal to cover/commit to a specific layer1. + // Move Baid58 encoding from BP seals to here. Use utxob1 for bitcoin, and use + // utxol1 for liquid. #[from] BlindedSeal(SecretSeal), #[from] - WitnessUtxo(Address), + WitnessVoutBitcoin(Address), + // TODO: Add support for Liquid beneficiaries + //#[from] + //WitnessVoutLiquid(Address), +} + +impl Beneficiary { + pub fn layer1(&self) -> Layer1 { + match self { + // TODO: Fix supporting liquid + Beneficiary::BlindedSeal(_) => Layer1::Bitcoin, + Beneficiary::WitnessVoutBitcoin(_) => Layer1::Bitcoin, + } + } } #[derive(Clone, Eq, PartialEq, Debug)] diff --git a/invoice/src/parse.rs b/invoice/src/parse.rs index 22353d67..168c7005 100644 --- a/invoice/src/parse.rs +++ b/invoice/src/parse.rs @@ -303,7 +303,7 @@ impl FromStr for RgbInvoice { (Ok(seal), Err(_)) => Beneficiary::BlindedSeal(seal), (Err(_), Ok(addr)) => { address_network = Some(addr.network); - Beneficiary::WitnessUtxo(addr) + Beneficiary::WitnessVoutBitcoin(addr) } (Err(_), Err(_)) => { return Err(InvoiceParseError::Beneficiary(beneficiary_str.to_owned())); diff --git a/src/containers/mod.rs b/src/containers/mod.rs index 5bc0a9dd..dcbfd97e 100644 --- a/src/containers/mod.rs +++ b/src/containers/mod.rs @@ -36,10 +36,12 @@ mod seal; mod util; mod validate; mod certs; +mod partials; pub use bindle::{Bindle, BindleContent, BindleParseError, LoadError, UniversalBindle}; pub use certs::{Cert, ContentId, ContentSigs, Identity}; pub use consignment::{Consignment, Contract, Transfer}; pub use disclosure::Disclosure; +pub use partials::{Batch, BatchItem, Fascia}; pub use seal::{BuilderSeal, TerminalSeal, VoutSeal}; pub use util::{ContainerVer, Terminal}; diff --git a/src/containers/partials.rs b/src/containers/partials.rs new file mode 100644 index 00000000..44ff641b --- /dev/null +++ b/src/containers/partials.rs @@ -0,0 +1,92 @@ +// RGB standard library for working with smart contracts on Bitcoin & Lightning +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::vec; + +use amplify::confinement::MediumVec; +use commit_verify::mpc; +use rgb::{Anchor, OpId, Output, Transition, TransitionBundle}; +use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize}; + +use crate::LIB_NAME_RGB_STD; + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct BatchItem { + pub id: OpId, + pub inputs: MediumVec, + pub transition: Transition, +} + +/// A batch of state transitions under different contracts which are associated +/// with some specific transfer and will be anchored within a single layer 1 +/// transaction. +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct Batch { + pub main: BatchItem, + pub blanks: MediumVec, +} + +impl StrictSerialize for Batch {} +impl StrictDeserialize for Batch {} + +impl IntoIterator for Batch { + type Item = BatchItem; + type IntoIter = vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let mut vec = self.blanks.into_inner(); + vec.push(self.main); + vec.into_iter() + } +} + +/// Structure exported from a PSBT for merging into the stash. It contains a set +/// of finalized state transitions (under multiple contracts), packed into +/// bundles, and anchored to a single layer 1 transaction. +#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct Fascia { + pub anchor: Anchor, + pub bundles: MediumVec, +} + +impl StrictSerialize for Fascia {} +impl StrictDeserialize for Fascia {} diff --git a/src/persistence/hoard.rs b/src/persistence/hoard.rs index 481477b7..c997bf4b 100644 --- a/src/persistence/hoard.rs +++ b/src/persistence/hoard.rs @@ -27,8 +27,8 @@ use amplify::confinement::{Confined, LargeOrdMap, SmallOrdMap, TinyOrdMap, TinyO use bp::dbc::anchor::MergeError; use commit_verify::mpc; use rgb::{ - Anchor, AnchorId, AnchoredBundle, AssetTag, AssignmentType, BundleId, ContractId, Extension, - Genesis, OpId, Operation, SchemaId, TransitionBundle, + Anchor, AnchorId, AnchoredBundle, AssetTag, AssignmentType, BundleError, BundleId, ContractId, + Extension, Genesis, OpId, Operation, SchemaId, TransitionBundle, }; use strict_encoding::TypeName; @@ -47,6 +47,9 @@ pub enum ConsumeError { #[from] Anchor(mpc::InvalidProof), + #[from] + Bundle(BundleError), + #[from] Merge(MergeError), diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index 83995e17..eaf630e5 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -19,33 +19,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, BTreeSet}; +use std::cmp::Ordering; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::error::Error; +use std::iter; use std::ops::Deref; -use amplify::confinement::{self, Confined}; +use amplify::confinement::{self, Confined, MediumOrdMap, MediumVec}; use bp::seals::txout::blind::SingleBlindSeal; -use bp::Txid; +use bp::seals::txout::CloseMethod; +use bp::{Txid, Vout}; +use chrono::Utc; use commit_verify::{mpc, Conceal}; +use invoice::{Beneficiary, RgbInvoice}; use rgb::{ - validation, Anchor, AnchoredBundle, BundleId, ContractId, ExposedSeal, GraphSeal, OpId, - Operation, Opout, Output, SchemaId, SealDefinition, SecretSeal, SubSchema, Transition, - TransitionBundle, WitnessId, + validation, Anchor, AnchoredBundle, AssignmentType, BundleError, BundleId, ContractId, + ExposedSeal, GraphSeal, Layer1, OpId, Operation, Opout, Output, SchemaId, SealDefinition, + SecretSeal, SubSchema, Transition, TransitionBundle, WitnessId, }; use strict_encoding::TypeName; use crate::accessors::{BundleExt, MergeRevealError, RevealError}; use crate::containers::{ - Bindle, BuilderSeal, Cert, Consignment, ContentId, Contract, Terminal, Transfer, + Batch, Bindle, BuilderSeal, Cert, Consignment, ContentId, Contract, Fascia, Terminal, Transfer, }; use crate::interface::{ - ContractIface, Iface, IfaceId, IfaceImpl, IfacePair, IfaceWrapper, TransitionBuilder, - TypedState, + BuilderError, ContractIface, ContractSuppl, Iface, IfaceId, IfaceImpl, IfacePair, IfaceWrapper, + TransitionBuilder, TypedState, VelocityHint, }; use crate::persistence::hoard::ConsumeError; use crate::persistence::stash::StashInconsistency; use crate::persistence::{Stash, StashError}; use crate::resolvers::ResolveHeight; +use crate::Outpoint; #[derive(Debug, Display, Error, From)] #[display(doc_comments)] @@ -84,6 +90,7 @@ pub enum InventoryError { /// errors during consume operation. // TODO: Make part of connectivity error #[from] + #[from(BundleError)] Consume(ConsumeError), /// error in input data. @@ -301,22 +308,32 @@ pub trait Inventory: Deref { where R::Error: 'static; - /// # Safety + /// Imports fascia into the stash, index and inventory. + /// + /// Part of the transfer workflow. Called once PSBT is completed and an RGB + /// fascia containing anchor and all state transitions is exported from + /// it. /// - /// Assumes that the bundle belongs to a non-mined witness transaction. Must - /// be used only to consume locally-produced bundles before witness - /// transactions are mined. - fn consume_anchor( + /// Must be called before the consignment is created, when witness + /// transaction is not yet mined. + fn consume(&mut self, fascia: Fascia) -> Result<(), InventoryError> { + let witness_id = fascia.anchor.witness_id(); + unsafe { self.consume_anchor(fascia.anchor)? }; + for bundle in fascia.bundles { + let contract_id = bundle.validate()?; + unsafe { self.consume_bundle(contract_id, bundle, witness_id)? }; + } + Ok(()) + } + + #[doc(hidden)] + unsafe fn consume_anchor( &mut self, anchor: Anchor, ) -> Result<(), InventoryError>; - /// # Safety - /// - /// Assumes that the bundle belongs to a non-mined witness transaction. Must - /// be used only to consume locally-produced bundles before witness - /// transactions are mined. - fn consume_bundle( + #[doc(hidden)] + unsafe fn consume_bundle( &mut self, contract_id: ContractId, bundle: TransitionBundle, @@ -470,12 +487,13 @@ pub trait Inventory: Deref { &mut self, contract_id: ContractId, outputs: impl IntoIterator>, - ) -> Result, InventoryError>; + ) -> Result, InventoryError>; fn store_seal_secret( &mut self, seal: SealDefinition, ) -> Result<(), InventoryError>; + fn seal_secrets( &mut self, ) -> Result>, InventoryError>; @@ -609,4 +627,130 @@ pub trait Inventory: Deref { Ok(consignment) } + + /// Composes a batch of state transitions updating state for the provided + /// set of previous outputs, satisfying requirements of the invoice, paying + /// the change back and including the necessary blank state transitions. + fn compose( + &mut self, + invoice: RgbInvoice, + prev_outputs: impl IntoIterator>, + method: CloseMethod, + change_vout: Vout, + allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, + ) -> Result> { + let mut output_for_assignment = |id: ContractId, + assignment_type: AssignmentType| + -> Result, ComposeError> { + // TODO: select supplement basing on the signer trust level + let suppl = self.contract_suppl(id).and_then(|set| set.first()); + let velocity = suppl + .and_then(|suppl| suppl.owned_state.get(&assignment_type)) + .map(|s| s.velocity) + .unwrap_or_default(); + let vout = allocator(id, assignment_type, velocity) + .ok_or(ComposeError::NoBlankOrChange(velocity, assignment_type))?; + let seal = GraphSeal::new_vout(method, vout); + Ok(BuilderSeal::Revealed(SealDefinition::with(invoice.layer1(), seal))) + }; + + // 1. Prepare the data + if let Some(expiry) = invoice.expiry { + if expiry < Utc::now().timestamp() { + return Err(ComposeError::InvoiceExpired); + } + } + let contract_id = invoice.contract.ok_or(ComposeError::NoContract)?; + let iface = invoice.iface.ok_or(ComposeError::NoIface)?; + let mut main_builder = + self.transition_builder(contract_id, iface.clone(), invoice.operation)?; + + let beneficiary = match invoice.beneficiary { + Beneficiary::BlindedSeal(seal) => BuilderSeal::Concealed(seal), + Beneficiary::WitnessVoutBitcoin(_) => BuilderSeal::Revealed(SealDefinition::Bitcoin( + GraphSeal::new_vout(method, change_vout), + )), + }; + + // 2. Prepare transition + let mut main_inputs = MediumVec::::new(); + let assignment_name = invoice + .assignment + .as_ref() + .or_else(|| main_builder.default_assignment().ok()) + .ok_or(BuilderError::NoDefaultAssignment)?; + let assignment_id = main_builder + .assignments_type(assignment_name) + .ok_or(BuilderError::InvalidStateField(assignment_name.clone()))?; + let mut sum_inputs = 0u64; + for ((opout, output), state) in self.state_for_outputs(contract_id, prev_outputs)? { + main_builder = main_builder.add_input(opout)?; + main_inputs.push(output)?; + if opout.ty != assignment_id { + let seal = output_for_assignment(contract_id, opout.ty)?; + main_builder = main_builder.add_raw_state(opout.ty, seal, state)?; + } else if let TypedState::Amount(value, _) = state { + sum_inputs += value; + } + } + // Add change + let main_transition = match invoice.owned_state { + TypedState::Amount(amt) => { + match sum_inputs.cmp(&amt) { + Ordering::Greater => { + let seal = output_for_assignment(contract_id, assignment_id)?; + let change = TypedState::Amount(sum_inputs - amt); + main_builder = main_builder.add_raw_state(assignment_id, seal, change)?; + } + Ordering::Less => return Err(ComposeError::InsufficientState), + Ordering::Equal => {} + } + main_builder + .add_raw_state(assignment_id, beneficiary, TypedState::Amount(amt))? + .complete_transition(contract_id)? + } + _ => { + todo!("only TypedState::Amount is currently supported") + } + }; + + // 3. Prepare other transitions + // Enumerate state + let mut spent_state = HashMap::>::new(); + for output in prev_outputs { + let output = output.into(); + for id in self.contracts_by_outpoints([output])? { + if id == contract_id { + continue; + } + spent_state + .entry(id) + .or_default() + .extend(self.state_for_outputs(id, [output])?); + } + } + // Construct blank transitions + let mut blank_transitions = MediumOrdMap::with_capacity(spent_state.len()); + for (id, opouts) in spent_state { + let mut blank_builder = self.blank_builder(id, iface.clone())?; + let mut outputs = MediumVec::with_capacity(opouts.len()); + for ((opout, output), state) in opouts { + let seal = output_for_assignment(id, opout.ty)?; + outputs.push(output)?; + blank_builder = blank_builder + .add_input(opout)? + .add_raw_state(opout.ty, seal, state)?; + } + + let transition = blank_builder.complete_transition(contract_id)?; + blank_transitions.insert(transition.id(), (transition, outputs))?; + } + + Ok(Batch { + main_id: main_transition.id(), + main_transition, + main_inputs, + blank_transitions, + }) + } } diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index 51d1550c..9fb29e45 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -471,7 +471,7 @@ impl Inventory for Stock { self.consume_consignment(transfer, resolver, force) } - fn consume_anchor( + unsafe fn consume_anchor( &mut self, anchor: Anchor, ) -> Result<(), InventoryError> { @@ -484,7 +484,7 @@ impl Inventory for Stock { Ok(()) } - fn consume_bundle( + unsafe fn consume_bundle( &mut self, contract_id: ContractId, bundle: TransitionBundle, @@ -643,7 +643,7 @@ impl Inventory for Stock { &mut self, contract_id: ContractId, outputs: impl IntoIterator>, - ) -> Result, InventoryError> { + ) -> Result, InventoryError> { let outputs = outputs .into_iter() .map(|o| o.into()) @@ -656,30 +656,33 @@ impl Inventory for Stock { let mut res = BTreeMap::new(); - for output in history.fungibles() { - if outputs.contains(&output.output) { + for item in history.fungibles() { + if outputs.contains(&item.output) { res.insert( - output.opout, - TypedState::Amount(output.state.value.as_u64(), output.state.blinding), + (item.opout, item.output), + TypedState::Amount(item.state.value.as_u64(), item.state.blinding), ); } } - for output in history.data() { - if outputs.contains(&output.output) { - res.insert(output.opout, TypedState::Data(output.state.clone())); + for item in history.data() { + if outputs.contains(&item.output) { + res.insert((item.opout, item.output), TypedState::Data(item.state.clone())); } } - for output in history.rights() { - if outputs.contains(&output.output) { - res.insert(output.opout, TypedState::Void); + for item in history.rights() { + if outputs.contains(&item.output) { + res.insert((item.opout, item.output), TypedState::Void); } } - for output in history.attach() { - if outputs.contains(&output.output) { - res.insert(output.opout, TypedState::Attachment(output.state.clone().into())); + for item in history.attach() { + if outputs.contains(&item.output) { + res.insert( + (item.opout, item.output), + TypedState::Attachment(item.state.clone().into()), + ); } } From 673b885410c87c940550583946fd14d311a14ced Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 13 Dec 2023 18:48:12 +0100 Subject: [PATCH 03/31] transfers --- Cargo.lock | 2 +- invoice/src/invoice.rs | 8 ++ src/containers/partials.rs | 12 +- src/interface/builder.rs | 260 ++++++++++++++++++++++++++++++----- src/interface/contract.rs | 6 +- src/persistence/inventory.rs | 164 ++++++++++++++-------- src/persistence/mod.rs | 3 +- src/persistence/stock.rs | 20 +-- 8 files changed, 372 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 504b0664..c0dd2941 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -653,7 +653,7 @@ dependencies = [ [[package]] name = "rgb-core" version = "0.11.0-beta.2" -source = "git+https://github.com/RGB-WG/rgb-core?branch=v0.11#ce9dc87499fb42476d0609f8d4d4a3819f739df6" +source = "git+https://github.com/RGB-WG/rgb-core?branch=v0.11#1fc21032159d2b04443b823f6e6bfb3340ef0622" dependencies = [ "aluvm", "amplify", diff --git a/invoice/src/invoice.rs b/invoice/src/invoice.rs index e045ce93..1c0204e8 100644 --- a/invoice/src/invoice.rs +++ b/invoice/src/invoice.rs @@ -84,3 +84,11 @@ pub struct RgbInvoice { pub expiry: Option, pub unknown_query: IndexMap, } + +impl RgbInvoice { + pub fn layer1(&self) -> Layer1 { + match self.beneficiary { + Beneficiary::BlindedSeal(_) | Beneficiary::WitnessVoutBitcoin(_) => Layer1::Bitcoin, + } + } +} diff --git a/src/containers/partials.rs b/src/containers/partials.rs index 44ff641b..bfa1f0b3 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -23,7 +23,7 @@ use std::vec; use amplify::confinement::MediumVec; use commit_verify::mpc; -use rgb::{Anchor, OpId, Output, Transition, TransitionBundle}; +use rgb::{Anchor, OpId, Operation, Output, Transition, TransitionBundle}; use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize}; use crate::LIB_NAME_RGB_STD; @@ -42,6 +42,16 @@ pub struct BatchItem { pub transition: Transition, } +impl BatchItem { + pub fn new(transition: Transition, inputs: MediumVec) -> Self { + BatchItem { + id: transition.id(), + inputs, + transition, + } + } +} + /// A batch of state transitions under different contracts which are associated /// with some specific transfer and will be anchored within a single layer 1 /// transaction. diff --git a/src/interface/builder.rs b/src/interface/builder.rs index 72136af7..849201b6 100644 --- a/src/interface/builder.rs +++ b/src/interface/builder.rs @@ -19,20 +19,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use amplify::confinement::{Confined, TinyOrdMap, TinyOrdSet, U16, U8}; use amplify::{confinement, Wrapper}; use rgb::{ AltLayer1, AltLayer1Set, AssetTag, Assign, AssignmentType, Assignments, BlindingFactor, ContractId, ExposedSeal, FungibleType, Genesis, GenesisSeal, GlobalState, GraphSeal, Input, - Opout, RevealedData, RevealedValue, SealDefinition, StateSchema, SubSchema, Transition, - TransitionType, TypedAssigns, + Opout, RevealedData, RevealedValue, StateSchema, SubSchema, Transition, TransitionType, + TypedAssigns, }; use strict_encoding::{FieldName, SerializeError, StrictSerialize, TypeName}; use strict_types::decode; use crate::containers::{BuilderSeal, Contract}; +use crate::interface::contract::AttachedState; use crate::interface::{Iface, IfaceImpl, IfacePair, TransitionIface, TypedState}; #[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -160,23 +161,52 @@ impl ContractBuilder { Ok(self) } + pub fn add_owned_state_raw( + mut self, + type_id: AssignmentType, + seal: BuilderSeal, + state: TypedState, + ) -> Result { + self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; + Ok(self) + } + + pub fn add_rights( + mut self, + name: impl Into, + seal: BuilderSeal, + ) -> Result { + self.builder = self.builder.add_rights(name, seal, None)?; + Ok(self) + } + pub fn add_fungible_state( mut self, name: impl Into, - seal: impl Into, + seal: BuilderSeal, value: u64, ) -> Result { self.builder = self.builder.add_fungible_state(name, seal, value, None)?; Ok(self) } - pub fn add_data_state( + pub fn add_data( mut self, name: impl Into, - seal: impl Into, + seal: BuilderSeal, value: impl StrictSerialize, ) -> Result { - self.builder = self.builder.add_data_state(name, seal, value, None)?; + self.builder = self.builder.add_data(name, seal, value, None)?; + Ok(self) + } + + pub fn add_attachment( + mut self, + name: impl Into, + seal: BuilderSeal, + attachment: AttachedState, + ) -> Result { + self.builder = self.builder.add_attachment(name, seal, attachment, None)?; Ok(self) } @@ -293,9 +323,36 @@ impl TransitionBuilder { .ok_or(BuilderError::NoDefaultAssignment) } + #[inline] + pub fn assignments_type(&self, name: &FieldName) -> Option { + self.builder + .assignments_type(name, Some(self.transition_type)) + } + + pub fn add_owned_state_raw( + mut self, + type_id: AssignmentType, + seal: BuilderSeal, + state: TypedState, + ) -> Result { + self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; + Ok(self) + } + + pub fn add_rights( + mut self, + name: impl Into, + seal: BuilderSeal, + ) -> Result { + self.builder = self + .builder + .add_rights(name, seal, Some(self.transition_type))?; + Ok(self) + } + pub fn add_fungible_default_state( self, - seal: impl Into, + seal: BuilderSeal, value: u64, ) -> Result { let assignment_name = self.default_assignment()?.clone(); @@ -305,7 +362,7 @@ impl TransitionBuilder { pub fn add_fungible_state( mut self, name: impl Into, - seal: impl Into, + seal: BuilderSeal, value: u64, ) -> Result { self.builder = @@ -314,15 +371,36 @@ impl TransitionBuilder { Ok(self) } - pub fn add_data_state( + pub fn add_data( mut self, name: impl Into, - seal: impl Into, + seal: BuilderSeal, value: impl StrictSerialize, + ) -> Result { + self.builder = self + .builder + .add_data(name, seal, value, Some(self.transition_type))?; + Ok(self) + } + + pub fn add_data_default( + self, + seal: BuilderSeal, + value: impl StrictSerialize, + ) -> Result { + let assignment_name = self.default_assignment()?.clone(); + self.add_data(assignment_name, seal.into(), value) + } + + pub fn add_attachment( + mut self, + name: impl Into, + seal: BuilderSeal, + attachment: AttachedState, ) -> Result { self.builder = self.builder - .add_data_state(name, seal, value, Some(self.transition_type))?; + .add_attachment(name, seal, attachment, Some(self.transition_type))?; Ok(self) } @@ -357,11 +435,12 @@ pub struct OperationBuilder { asset_tags: TinyOrdMap, global: GlobalState, - // rights: TinyOrdMap>, 1, U8>>, + rights: TinyOrdMap>, 1, U8>>, fungible: TinyOrdMap, RevealedValue>, 1, U8>>, data: TinyOrdMap, RevealedData>, 1, U8>>, - // TODO: add attachments + attachments: + TinyOrdMap, AttachedState>, 1, U8>>, // TODO: add valencies } @@ -385,7 +464,9 @@ impl OperationBuilder { asset_tags: none!(), global: none!(), + rights: none!(), fungible: none!(), + attachments: none!(), data: none!(), }) } @@ -472,10 +553,89 @@ impl OperationBuilder { Ok(self) } + fn add_owned_state_raw( + self, + type_id: AssignmentType, + seal: BuilderSeal, + state: TypedState, + ) -> Result { + match state { + TypedState::Void => self.add_rights_raw(type_id, seal), + TypedState::Amount(value, blinding, tag) => self.add_fungible_state_raw( + type_id, + seal, + RevealedValue::with_blinding(value, blinding, tag), + ), + TypedState::Data(data) => self.add_data_raw(type_id, seal, data), + TypedState::Attachment(attach) => self.add_attachment_raw(type_id, seal, attach), + } + } + + fn add_rights_raw( + mut self, + type_id: AssignmentType, + seal: BuilderSeal, + ) -> Result { + let state_schema = self.state_schema(type_id); + if *state_schema != StateSchema::Fungible(FungibleType::Unsigned64Bit) { + return Err(BuilderError::InvalidState(type_id)); + } + + match self.rights.get_mut(&type_id) { + Some(assignments) => { + assignments.push(seal)?; + } + None => { + self.rights.insert(type_id, Confined::with(seal))?; + } + } + + Ok(self) + } + + fn add_rights( + self, + name: impl Into, + seal: BuilderSeal, + ty: Option, + ) -> Result { + let name = name.into(); + + let type_id = self + .assignments_type(&name, ty) + .ok_or(BuilderError::AssignmentNotFound(name))?; + + self.add_rights_raw(type_id, seal) + } + + fn add_fungible_state_raw( + mut self, + type_id: AssignmentType, + seal: BuilderSeal, + state: RevealedValue, + ) -> Result { + let state_schema = self.state_schema(type_id); + if *state_schema != StateSchema::Fungible(FungibleType::Unsigned64Bit) { + return Err(BuilderError::InvalidState(type_id)); + } + + match self.fungible.get_mut(&type_id) { + Some(assignments) => { + assignments.insert(seal, state)?; + } + None => { + self.fungible + .insert(type_id, Confined::with((seal, state)))?; + } + } + + Ok(self) + } + fn add_fungible_state( mut self, name: impl Into, - seal: impl Into, + seal: BuilderSeal, value: u64, ty: Option, ) -> Result { @@ -498,30 +658,35 @@ impl OperationBuilder { }; let state = RevealedValue::new_random_blinding(value, tag); + self.add_fungible_state_raw(type_id, seal, state) + } + fn add_data_raw( + mut self, + type_id: AssignmentType, + seal: BuilderSeal, + state: RevealedData, + ) -> Result { let state_schema = self.state_schema(type_id); - if *state_schema != StateSchema::Fungible(FungibleType::Unsigned64Bit) { - return Err(BuilderError::InvalidState(type_id)); - } - - let seal = BuilderSeal::::from(SealDefinition::Bitcoin(seal.into())); - match self.fungible.get_mut(&type_id) { - Some(assignments) => { - assignments.insert(seal, state)?; - } - None => { - self.fungible - .insert(type_id, Confined::with((seal, state)))?; + if let StateSchema::Structured(_) = *state_schema { + match self.data.get_mut(&type_id) { + Some(assignments) => { + assignments.insert(seal, state)?; + } + None => { + self.data.insert(type_id, Confined::with((seal, state)))?; + } } + } else { + return Err(BuilderError::InvalidState(type_id)); } - Ok(self) } - fn add_data_state( - mut self, + fn add_data( + self, name: impl Into, - seal: impl Into, + seal: BuilderSeal, value: impl StrictSerialize, ty: Option, ) -> Result { @@ -533,15 +698,24 @@ impl OperationBuilder { .assignments_type(&name, ty) .ok_or(BuilderError::AssignmentNotFound(name))?; + self.add_data_raw(type_id, seal, state) + } + + fn add_attachment_raw( + mut self, + type_id: AssignmentType, + seal: BuilderSeal, + state: AttachedState, + ) -> Result { let state_schema = self.state_schema(type_id); - let seal = BuilderSeal::::from(SealDefinition::Bitcoin(seal.into())); if let StateSchema::Structured(_) = *state_schema { - match self.data.get_mut(&type_id) { + match self.attachments.get_mut(&type_id) { Some(assignments) => { assignments.insert(seal, state)?; } None => { - self.data.insert(type_id, Confined::with((seal, state)))?; + self.attachments + .insert(type_id, Confined::with((seal, state)))?; } } } else { @@ -550,6 +724,22 @@ impl OperationBuilder { Ok(self) } + fn add_attachment( + self, + name: impl Into, + seal: BuilderSeal, + state: AttachedState, + ty: Option, + ) -> Result { + let name = name.into(); + + let type_id = self + .assignments_type(&name, ty) + .ok_or(BuilderError::AssignmentNotFound(name))?; + + self.add_attachment_raw(type_id, seal, state) + } + fn complete( self, inputs: Option<&TinyOrdMap>, @@ -579,7 +769,7 @@ impl OperationBuilder { i.iter() .filter(|(out, _)| out.prev_out.ty == id) .map(|(_, ts)| match ts { - TypedState::Amount(_, blinding) => *blinding, + TypedState::Amount(_, blinding, _) => *blinding, _ => panic!("previous state has invalid type"), }) .collect::>() diff --git a/src/interface/contract.rs b/src/interface/contract.rs index dc1c986f..4ab670ae 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -25,8 +25,8 @@ use std::ops::Deref; use amplify::confinement::{LargeOrdMap, LargeVec, SmallVec}; use bp::Outpoint; use rgb::{ - AssignmentType, AttachId, BlindingFactor, ContractId, ContractState, FungibleOutput, MediaType, - Output, RevealedAttach, RevealedData, WitnessId, + AssetTag, AssignmentType, AttachId, BlindingFactor, ContractId, ContractState, FungibleOutput, + MediaType, Output, RevealedAttach, RevealedData, WitnessId, }; use strict_encoding::FieldName; use strict_types::typify::TypedVal; @@ -50,7 +50,7 @@ pub enum ContractError { pub enum TypedState { #[display("")] Void, - Amount(u64, BlindingFactor), + Amount(u64, BlindingFactor, AssetTag), #[from] Data(RevealedData), #[from] diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index eaf630e5..b549f93b 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -20,38 +20,37 @@ // limitations under the License. use std::cmp::Ordering; -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::error::Error; -use std::iter; use std::ops::Deref; -use amplify::confinement::{self, Confined, MediumOrdMap, MediumVec}; +use amplify::confinement::{self, Confined, MediumVec}; use bp::seals::txout::blind::SingleBlindSeal; use bp::seals::txout::CloseMethod; use bp::{Txid, Vout}; use chrono::Utc; use commit_verify::{mpc, Conceal}; -use invoice::{Beneficiary, RgbInvoice}; +use invoice::{Beneficiary, InvoiceState, RgbInvoice}; use rgb::{ validation, Anchor, AnchoredBundle, AssignmentType, BundleError, BundleId, ContractId, - ExposedSeal, GraphSeal, Layer1, OpId, Operation, Opout, Output, SchemaId, SealDefinition, - SecretSeal, SubSchema, Transition, TransitionBundle, WitnessId, + ExposedSeal, GraphSeal, OpId, Operation, Opout, Output, SchemaId, SealDefinition, SecretSeal, + SubSchema, Transition, TransitionBundle, WitnessId, }; use strict_encoding::TypeName; use crate::accessors::{BundleExt, MergeRevealError, RevealError}; use crate::containers::{ - Batch, Bindle, BuilderSeal, Cert, Consignment, ContentId, Contract, Fascia, Terminal, Transfer, + Batch, BatchItem, Bindle, BuilderSeal, Cert, Consignment, ContentId, Contract, Fascia, + Terminal, Transfer, }; use crate::interface::{ - BuilderError, ContractIface, ContractSuppl, Iface, IfaceId, IfaceImpl, IfacePair, IfaceWrapper, + BuilderError, ContractIface, Iface, IfaceId, IfaceImpl, IfacePair, IfaceWrapper, TransitionBuilder, TypedState, VelocityHint, }; use crate::persistence::hoard::ConsumeError; use crate::persistence::stash::StashInconsistency; use crate::persistence::{Stash, StashError}; use crate::resolvers::ResolveHeight; -use crate::Outpoint; #[derive(Debug, Display, Error, From)] #[display(doc_comments)] @@ -66,18 +65,56 @@ pub enum ConsignerError { /// public state at operation output {0} is concealed. ConcealedPublicState(Opout), - #[display(inner)] #[from] + #[display(inner)] Reveal(RevealError), - #[display(inner)] #[from] #[from(InventoryInconsistency)] + #[display(inner)] InventoryError(InventoryError), - #[display(inner)] #[from] #[from(StashInconsistency)] + #[display(inner)] + StashError(StashError), +} + +#[derive(Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum ComposeError { + /// no outputs available to store state of type {1} with velocity class + /// '{0}'. + NoBlankOrChange(VelocityHint, AssignmentType), + + /// expired invoice. + InvoiceExpired, + + /// the invoice contains no contract information. + NoContract, + + /// the invoice contains no interface information. + NoIface, + + /// the invoice requirements can't be fulfilled using available assets or + /// smart contract state. + InsufficientState, + + /// the operation produces too many state transitions which can't fit the + /// container requirements. + #[from] + Confinement(confinement::Error), + + #[from] + #[display(inner)] + Builder(BuilderError), + + #[from] + #[display(inner)] + InventoryError(InventoryError), + + #[from] + #[display(inner)] StashError(StashError), } @@ -352,7 +389,7 @@ pub trait Inventory: Deref { where R::Error: 'static; - fn contracts_by_iface(&mut self) -> Result, InventoryError> + fn contracts_by_iface(&self) -> Result, InventoryError> where Self::Error: From<::Error>, InventoryError: From<::Error>, @@ -364,7 +401,7 @@ pub trait Inventory: Deref { } fn contracts_by_iface_name( - &mut self, + &self, iface: impl Into, ) -> Result, InventoryError> where @@ -380,7 +417,7 @@ pub trait Inventory: Deref { } fn contract_iface_named( - &mut self, + &self, contract_id: ContractId, iface: impl Into, ) -> Result> @@ -394,7 +431,7 @@ pub trait Inventory: Deref { } fn contract_iface_wrapped( - &mut self, + &self, contract_id: ContractId, ) -> Result> { self.contract_iface_id(contract_id, W::IFACE_ID) @@ -402,7 +439,7 @@ pub trait Inventory: Deref { } fn contract_iface_id( - &mut self, + &self, contract_id: ContractId, iface_id: IfaceId, ) -> Result>; @@ -410,7 +447,7 @@ pub trait Inventory: Deref { fn anchored_bundle(&self, opid: OpId) -> Result>; fn transition_builder( - &mut self, + &self, contract_id: ContractId, iface: impl Into, transition_name: Option>, @@ -440,7 +477,7 @@ pub trait Inventory: Deref { } fn blank_builder( - &mut self, + &self, contract_id: ContractId, iface: impl Into, ) -> Result> @@ -463,28 +500,28 @@ pub trait Inventory: Deref { fn transition(&self, opid: OpId) -> Result<&Transition, InventoryError>; fn contracts_by_outputs( - &mut self, + &self, outputs: impl IntoIterator>, ) -> Result, InventoryError>; fn public_opouts( - &mut self, + &self, contract_id: ContractId, ) -> Result, InventoryError>; fn opouts_by_outputs( - &mut self, + &self, contract_id: ContractId, outputs: impl IntoIterator>, ) -> Result, InventoryError>; fn opouts_by_terminals( - &mut self, + &self, terminals: impl IntoIterator, ) -> Result, InventoryError>; fn state_for_outputs( - &mut self, + &self, contract_id: ContractId, outputs: impl IntoIterator>, ) -> Result, InventoryError>; @@ -495,12 +532,12 @@ pub trait Inventory: Deref { ) -> Result<(), InventoryError>; fn seal_secrets( - &mut self, + &self, ) -> Result>, InventoryError>; #[allow(clippy::type_complexity)] fn export_contract( - &mut self, + &self, contract_id: ContractId, ) -> Result< Bindle, @@ -515,7 +552,7 @@ pub trait Inventory: Deref { #[allow(clippy::type_complexity)] fn transfer( - &mut self, + &self, contract_id: ContractId, seals: impl IntoIterator>>, ) -> Result< @@ -529,7 +566,7 @@ pub trait Inventory: Deref { } fn consign( - &mut self, + &self, contract_id: ContractId, seals: impl IntoIterator>>, ) -> Result< @@ -632,16 +669,28 @@ pub trait Inventory: Deref { /// set of previous outputs, satisfying requirements of the invoice, paying /// the change back and including the necessary blank state transitions. fn compose( - &mut self, + &self, invoice: RgbInvoice, prev_outputs: impl IntoIterator>, method: CloseMethod, change_vout: Vout, allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, - ) -> Result> { - let mut output_for_assignment = |id: ContractId, - assignment_type: AssignmentType| - -> Result, ComposeError> { + ) -> Result::Target as Stash>::Error>> + where + Self::Error: From<::Error>, + { + let layer1 = invoice.layer1(); + let prev_outputs = prev_outputs + .into_iter() + .map(|o| o.into()) + .collect::>(); + + let output_for_assignment = |id: ContractId, + assignment_type: AssignmentType| + -> Result< + BuilderSeal, + ComposeError::Target as Stash>::Error>, + > { // TODO: select supplement basing on the signer trust level let suppl = self.contract_suppl(id).and_then(|set| set.first()); let velocity = suppl @@ -651,7 +700,7 @@ pub trait Inventory: Deref { let vout = allocator(id, assignment_type, velocity) .ok_or(ComposeError::NoBlankOrChange(velocity, assignment_type))?; let seal = GraphSeal::new_vout(method, vout); - Ok(BuilderSeal::Revealed(SealDefinition::with(invoice.layer1(), seal))) + Ok(BuilderSeal::Revealed(SealDefinition::with(layer1, seal))) }; // 1. Prepare the data @@ -678,35 +727,44 @@ pub trait Inventory: Deref { .assignment .as_ref() .or_else(|| main_builder.default_assignment().ok()) - .ok_or(BuilderError::NoDefaultAssignment)?; + .ok_or(BuilderError::NoDefaultAssignment)? + .clone(); let assignment_id = main_builder - .assignments_type(assignment_name) + .assignments_type(&assignment_name) .ok_or(BuilderError::InvalidStateField(assignment_name.clone()))?; let mut sum_inputs = 0u64; - for ((opout, output), state) in self.state_for_outputs(contract_id, prev_outputs)? { - main_builder = main_builder.add_input(opout)?; + for ((opout, output), state) in + self.state_for_outputs(contract_id, prev_outputs.iter().cloned())? + { + main_builder = main_builder.add_input(opout, state.clone())?; main_inputs.push(output)?; if opout.ty != assignment_id { let seal = output_for_assignment(contract_id, opout.ty)?; - main_builder = main_builder.add_raw_state(opout.ty, seal, state)?; - } else if let TypedState::Amount(value, _) = state { + // TODO: Update blinding factor + main_builder = main_builder.add_owned_state_raw(opout.ty, seal, state)?; + } else if let TypedState::Amount(value, _, _) = state { sum_inputs += value; } } // Add change let main_transition = match invoice.owned_state { - TypedState::Amount(amt) => { + InvoiceState::Amount(amt) => { match sum_inputs.cmp(&amt) { Ordering::Greater => { let seal = output_for_assignment(contract_id, assignment_id)?; - let change = TypedState::Amount(sum_inputs - amt); - main_builder = main_builder.add_raw_state(assignment_id, seal, change)?; + // TODO: Use deterministic entropy + main_builder = main_builder.add_fungible_state( + assignment_name.clone(), + seal, + sum_inputs - amt, + )?; } Ordering::Less => return Err(ComposeError::InsufficientState), Ordering::Equal => {} } + // TODO: Use deterministic entropy main_builder - .add_raw_state(assignment_id, beneficiary, TypedState::Amount(amt))? + .add_fungible_state(assignment_name.clone(), beneficiary, amt)? .complete_transition(contract_id)? } _ => { @@ -718,8 +776,7 @@ pub trait Inventory: Deref { // Enumerate state let mut spent_state = HashMap::>::new(); for output in prev_outputs { - let output = output.into(); - for id in self.contracts_by_outpoints([output])? { + for id in self.contracts_by_outputs([output])? { if id == contract_id { continue; } @@ -730,27 +787,26 @@ pub trait Inventory: Deref { } } // Construct blank transitions - let mut blank_transitions = MediumOrdMap::with_capacity(spent_state.len()); + let mut blanks = MediumVec::with_capacity(spent_state.len()); for (id, opouts) in spent_state { let mut blank_builder = self.blank_builder(id, iface.clone())?; let mut outputs = MediumVec::with_capacity(opouts.len()); for ((opout, output), state) in opouts { let seal = output_for_assignment(id, opout.ty)?; outputs.push(output)?; + // TODO: update blinding factor blank_builder = blank_builder - .add_input(opout)? - .add_raw_state(opout.ty, seal, state)?; + .add_input(opout, state.clone())? + .add_owned_state_raw(opout.ty, seal, state)?; } let transition = blank_builder.complete_transition(contract_id)?; - blank_transitions.insert(transition.id(), (transition, outputs))?; + blanks.push(BatchItem::new(transition, outputs))?; } Ok(Batch { - main_id: main_transition.id(), - main_transition, - main_inputs, - blank_transitions, + main: BatchItem::new(main_transition, main_inputs), + blanks, }) } } diff --git a/src/persistence/mod.rs b/src/persistence/mod.rs index 62ecc32a..b9a35adb 100644 --- a/src/persistence/mod.rs +++ b/src/persistence/mod.rs @@ -39,7 +39,8 @@ pub mod hoard; pub use hoard::Hoard; pub use inventory::{ - ConsignerError, Inventory, InventoryDataError, InventoryError, InventoryInconsistency, + ComposeError, ConsignerError, Inventory, InventoryDataError, InventoryError, + InventoryInconsistency, }; pub use stash::{Stash, StashError, StashInconsistency}; pub use stock::Stock; diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index 9fb29e45..e12af130 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -517,7 +517,7 @@ impl Inventory for Stock { } fn contract_iface_id( - &mut self, + &self, contract_id: ContractId, iface_id: IfaceId, ) -> Result> { @@ -577,7 +577,7 @@ impl Inventory for Stock { } fn contracts_by_outputs( - &mut self, + &self, outputs: impl IntoIterator>, ) -> Result, InventoryError> { let outputs = outputs @@ -596,7 +596,7 @@ impl Inventory for Stock { } fn public_opouts( - &mut self, + &self, contract_id: ContractId, ) -> Result, InventoryError> { let index = self @@ -607,7 +607,7 @@ impl Inventory for Stock { } fn opouts_by_outputs( - &mut self, + &self, contract_id: ContractId, outputs: impl IntoIterator>, ) -> Result, InventoryError> { @@ -627,7 +627,7 @@ impl Inventory for Stock { } fn opouts_by_terminals( - &mut self, + &self, terminals: impl IntoIterator, ) -> Result, InventoryError> { let terminals = terminals.into_iter().collect::>(); @@ -640,7 +640,7 @@ impl Inventory for Stock { } fn state_for_outputs( - &mut self, + &self, contract_id: ContractId, outputs: impl IntoIterator>, ) -> Result, InventoryError> { @@ -660,7 +660,11 @@ impl Inventory for Stock { if outputs.contains(&item.output) { res.insert( (item.opout, item.output), - TypedState::Amount(item.state.value.as_u64(), item.state.blinding), + TypedState::Amount( + item.state.value.as_u64(), + item.state.blinding, + item.state.tag, + ), ); } } @@ -698,7 +702,7 @@ impl Inventory for Stock { } fn seal_secrets( - &mut self, + &self, ) -> Result>, InventoryError> { Ok(self.seal_secrets.to_inner()) } From b7251f65442dd52a952b08132064017825cc63bb Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 13 Dec 2023 19:16:26 +0100 Subject: [PATCH 04/31] iface: deterministic fungible state --- src/interface/builder.rs | 108 ++++++++++++++++++++++++++--------- src/interface/contract.rs | 11 ++++ src/persistence/inventory.rs | 49 ++++++++++++---- 3 files changed, 128 insertions(+), 40 deletions(-) diff --git a/src/interface/builder.rs b/src/interface/builder.rs index 849201b6..b220c125 100644 --- a/src/interface/builder.rs +++ b/src/interface/builder.rs @@ -65,9 +65,17 @@ pub enum BuilderError { /// state `{0}` provided to the builder has invalid name. InvalidState(AssignmentType), - /// asset tag for the state `{0}` must be added before any fungible state of + /// asset tag for state `{0}` must be added before any fungible state of /// the same type. - AssetTagSet(AssignmentType), + AssetTagMissed(AssignmentType), + + /// asset tag for state `{0}` was already automatically created. Please call + /// `add_asset_tag` before adding any fungible state to the builder. + AssetTagAutomatic(AssignmentType), + + /// state data for state type `{0}` are invalid: asset tag doesn't match the + /// tag defined by the contract. + AssetTagInvalid(AssignmentType), /// interface doesn't specifies default operation name, thus an explicit /// operation type must be provided with `set_operation_type` method. @@ -186,7 +194,30 @@ impl ContractBuilder { seal: BuilderSeal, value: u64, ) -> Result { - self.builder = self.builder.add_fungible_state(name, seal, value, None)?; + let name = name.into(); + let type_id = self + .builder + .assignments_type(&name, None) + .ok_or(BuilderError::AssignmentNotFound(name.clone()))?; + let tag = match self.builder.asset_tags.get(&type_id) { + Some(asset_tag) => *asset_tag, + None => { + let asset_tag = AssetTag::new_random( + format!( + "{}/{}", + self.builder.schema.schema_id(), + self.builder.iface.iface_id() + ), + type_id, + ); + self.builder.asset_tags.insert(type_id, asset_tag)?; + asset_tag + } + }; + + self.builder = self + .builder + .add_fungible_state(name, seal, value, tag, None)?; Ok(self) } @@ -329,16 +360,6 @@ impl TransitionBuilder { .assignments_type(name, Some(self.transition_type)) } - pub fn add_owned_state_raw( - mut self, - type_id: AssignmentType, - seal: BuilderSeal, - state: TypedState, - ) -> Result { - self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; - Ok(self) - } - pub fn add_rights( mut self, name: impl Into, @@ -359,15 +380,49 @@ impl TransitionBuilder { self.add_fungible_state(assignment_name, seal.into(), value) } + pub fn add_owned_state_raw( + mut self, + type_id: AssignmentType, + seal: BuilderSeal, + state: TypedState, + ) -> Result { + if matches!(state, TypedState::Amount(_, _, tag) if self.builder.asset_tag(type_id)? != tag) + { + return Err(BuilderError::AssetTagInvalid(type_id)); + } + self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; + Ok(self) + } + + pub fn add_fungible_state_raw( + mut self, + type_id: AssignmentType, + seal: BuilderSeal, + value: u64, + blinding: BlindingFactor, + ) -> Result { + let tag = self.builder.asset_tag(type_id)?; + let state = RevealedValue::with_blinding(value, blinding, tag); + self.builder = self.builder.add_fungible_state_raw(type_id, seal, state)?; + Ok(self) + } + pub fn add_fungible_state( mut self, name: impl Into, seal: BuilderSeal, value: u64, ) -> Result { + let name = name.into(); + let type_id = self + .builder + .assignments_type(&name, None) + .ok_or(BuilderError::AssignmentNotFound(name.clone()))?; + let tag = self.builder.asset_tag(type_id)?; + self.builder = self.builder - .add_fungible_state(name, seal, value, Some(self.transition_type))?; + .add_fungible_state(name, seal, value, tag, Some(self.transition_type))?; Ok(self) } @@ -500,6 +555,14 @@ impl OperationBuilder { .expect("schema should match interface: must be checked by the constructor") } + #[inline] + pub fn asset_tag(&self, type_id: AssignmentType) -> Result { + self.asset_tags + .get(&type_id) + .ok_or(BuilderError::AssetTagMissed(type_id)) + .copied() + } + #[inline] pub fn add_asset_tag( mut self, @@ -513,7 +576,7 @@ impl OperationBuilder { .ok_or(BuilderError::AssignmentNotFound(name))?; if self.fungible.contains_key(&type_id) { - return Err(BuilderError::AssetTagSet(type_id)); + return Err(BuilderError::AssetTagAutomatic(type_id)); } self.asset_tags.insert(type_id, asset_tag)?; @@ -633,10 +696,11 @@ impl OperationBuilder { } fn add_fungible_state( - mut self, + self, name: impl Into, seal: BuilderSeal, value: u64, + tag: AssetTag, ty: Option, ) -> Result { let name = name.into(); @@ -645,18 +709,6 @@ impl OperationBuilder { .assignments_type(&name, ty) .ok_or(BuilderError::AssignmentNotFound(name))?; - let tag = match self.asset_tags.get(&type_id) { - Some(asset_tag) => *asset_tag, - None => { - let asset_tag = AssetTag::new_random( - format!("{}/{}", self.schema.schema_id(), self.iface.iface_id()), - type_id, - ); - self.asset_tags.insert(type_id, asset_tag)?; - asset_tag - } - }; - let state = RevealedValue::new_random_blinding(value, tag); self.add_fungible_state_raw(type_id, seal, state) } diff --git a/src/interface/contract.rs b/src/interface/contract.rs index 4ab670ae..bca5ace3 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -57,6 +57,17 @@ pub enum TypedState { Attachment(AttachedState), } +impl TypedState { + pub fn update_blinding(&mut self, blinding: BlindingFactor) { + match self { + TypedState::Void => {} + TypedState::Amount(_, b, _) => *b = blinding, + TypedState::Data(_) => {} + TypedState::Attachment(_) => {} + } + } +} + #[derive(Clone, Eq, PartialEq, Hash, Debug, Display)] #[display("{id}:{media_type}")] pub struct AttachedState { diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index b549f93b..c0c66328 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -32,9 +32,9 @@ use chrono::Utc; use commit_verify::{mpc, Conceal}; use invoice::{Beneficiary, InvoiceState, RgbInvoice}; use rgb::{ - validation, Anchor, AnchoredBundle, AssignmentType, BundleError, BundleId, ContractId, - ExposedSeal, GraphSeal, OpId, Operation, Opout, Output, SchemaId, SealDefinition, SecretSeal, - SubSchema, Transition, TransitionBundle, WitnessId, + validation, Anchor, AnchoredBundle, AssignmentType, BlindingFactor, BundleError, BundleId, + ContractId, ExposedSeal, GraphSeal, OpId, Operation, Opout, Output, SchemaId, SealDefinition, + SecretSeal, SubSchema, Transition, TransitionBundle, WitnessId, }; use strict_encoding::TypeName; @@ -676,6 +676,25 @@ pub trait Inventory: Deref { change_vout: Vout, allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, ) -> Result::Target as Stash>::Error>> + where + Self::Error: From<::Error>, + { + self.compose_deterministic(invoice, prev_outputs, method, change_vout, allocator, |_, _| { + BlindingFactor::random() + }) + } + /// Composes a batch of state transitions updating state for the provided + /// set of previous outputs, satisfying requirements of the invoice, paying + /// the change back and including the necessary blank state transitions. + fn compose_deterministic( + &self, + invoice: RgbInvoice, + prev_outputs: impl IntoIterator>, + method: CloseMethod, + change_vout: Vout, + allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, + blinder: impl Fn(ContractId, AssignmentType) -> BlindingFactor, + ) -> Result::Target as Stash>::Error>> where Self::Error: From<::Error>, { @@ -733,14 +752,14 @@ pub trait Inventory: Deref { .assignments_type(&assignment_name) .ok_or(BuilderError::InvalidStateField(assignment_name.clone()))?; let mut sum_inputs = 0u64; - for ((opout, output), state) in + for ((opout, output), mut state) in self.state_for_outputs(contract_id, prev_outputs.iter().cloned())? { main_builder = main_builder.add_input(opout, state.clone())?; main_inputs.push(output)?; if opout.ty != assignment_id { let seal = output_for_assignment(contract_id, opout.ty)?; - // TODO: Update blinding factor + state.update_blinding(blinder(contract_id, assignment_id)); main_builder = main_builder.add_owned_state_raw(opout.ty, seal, state)?; } else if let TypedState::Amount(value, _, _) = state { sum_inputs += value; @@ -752,19 +771,23 @@ pub trait Inventory: Deref { match sum_inputs.cmp(&amt) { Ordering::Greater => { let seal = output_for_assignment(contract_id, assignment_id)?; - // TODO: Use deterministic entropy - main_builder = main_builder.add_fungible_state( - assignment_name.clone(), + main_builder = main_builder.add_fungible_state_raw( + assignment_id, seal, sum_inputs - amt, + blinder(contract_id, assignment_id), )?; } Ordering::Less => return Err(ComposeError::InsufficientState), Ordering::Equal => {} } - // TODO: Use deterministic entropy main_builder - .add_fungible_state(assignment_name.clone(), beneficiary, amt)? + .add_fungible_state_raw( + assignment_id, + beneficiary, + amt, + blinder(contract_id, assignment_id), + )? .complete_transition(contract_id)? } _ => { @@ -791,10 +814,12 @@ pub trait Inventory: Deref { for (id, opouts) in spent_state { let mut blank_builder = self.blank_builder(id, iface.clone())?; let mut outputs = MediumVec::with_capacity(opouts.len()); - for ((opout, output), state) in opouts { + for ((opout, output), mut state) in opouts { let seal = output_for_assignment(id, opout.ty)?; outputs.push(output)?; - // TODO: update blinding factor + if let TypedState::Amount(_, ref mut blinding, _) = state { + *blinding = blinder(id, opout.ty); + } blank_builder = blank_builder .add_input(opout, state.clone())? .add_owned_state_raw(opout.ty, seal, state)?; From eec60571a25c7347d0ffa2a524d14ce61d20adb3 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 13 Dec 2023 20:16:49 +0100 Subject: [PATCH 05/31] persistence: API improvements --- src/persistence/hoard.rs | 8 +++++++- src/persistence/inventory.rs | 17 +++++++++-------- src/persistence/stash.rs | 3 ++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/persistence/hoard.rs b/src/persistence/hoard.rs index c997bf4b..64f92fcc 100644 --- a/src/persistence/hoard.rs +++ b/src/persistence/hoard.rs @@ -275,7 +275,13 @@ impl Stash for Hoard { Ok(self.geneses.keys().copied().collect()) } - fn contract_suppl(&self, contract_id: ContractId) -> Option<&TinyOrdSet> { + fn contract_suppl(&self, contract_id: ContractId) -> Option<&ContractSuppl> { + // TODO: select supplement basing on the signer trust level + self.contract_suppl_all(contract_id) + .and_then(|set| set.first()) + } + + fn contract_suppl_all(&self, contract_id: ContractId) -> Option<&TinyOrdSet> { self.suppl.get(&contract_id) } diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index c0c66328..5e540017 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -670,10 +670,10 @@ pub trait Inventory: Deref { /// the change back and including the necessary blank state transitions. fn compose( &self, - invoice: RgbInvoice, + invoice: &RgbInvoice, prev_outputs: impl IntoIterator>, method: CloseMethod, - change_vout: Vout, + change_vout: impl Into, allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, ) -> Result::Target as Stash>::Error>> where @@ -683,15 +683,16 @@ pub trait Inventory: Deref { BlindingFactor::random() }) } + /// Composes a batch of state transitions updating state for the provided /// set of previous outputs, satisfying requirements of the invoice, paying /// the change back and including the necessary blank state transitions. fn compose_deterministic( &self, - invoice: RgbInvoice, + invoice: &RgbInvoice, prev_outputs: impl IntoIterator>, method: CloseMethod, - change_vout: Vout, + change_vout: impl Into, allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, blinder: impl Fn(ContractId, AssignmentType) -> BlindingFactor, ) -> Result::Target as Stash>::Error>> @@ -699,6 +700,7 @@ pub trait Inventory: Deref { Self::Error: From<::Error>, { let layer1 = invoice.layer1(); + let change_vout = change_vout.into(); let prev_outputs = prev_outputs .into_iter() .map(|o| o.into()) @@ -710,8 +712,7 @@ pub trait Inventory: Deref { BuilderSeal, ComposeError::Target as Stash>::Error>, > { - // TODO: select supplement basing on the signer trust level - let suppl = self.contract_suppl(id).and_then(|set| set.first()); + let suppl = self.contract_suppl(id); let velocity = suppl .and_then(|suppl| suppl.owned_state.get(&assignment_type)) .map(|s| s.velocity) @@ -729,9 +730,9 @@ pub trait Inventory: Deref { } } let contract_id = invoice.contract.ok_or(ComposeError::NoContract)?; - let iface = invoice.iface.ok_or(ComposeError::NoIface)?; + let iface = invoice.iface.as_ref().ok_or(ComposeError::NoIface)?; let mut main_builder = - self.transition_builder(contract_id, iface.clone(), invoice.operation)?; + self.transition_builder(contract_id, iface.clone(), invoice.operation.clone())?; let beneficiary = match invoice.beneficiary { Beneficiary::BlindedSeal(seal) => BuilderSeal::Concealed(seal), diff --git a/src/persistence/stash.rs b/src/persistence/stash.rs index 2224e3f5..0e4a6395 100644 --- a/src/persistence/stash.rs +++ b/src/persistence/stash.rs @@ -112,7 +112,8 @@ pub trait Stash { self.schema(genesis.schema_id) } - fn contract_suppl(&self, contract_id: ContractId) -> Option<&TinyOrdSet>; + fn contract_suppl(&self, contract_id: ContractId) -> Option<&ContractSuppl>; + fn contract_suppl_all(&self, contract_id: ContractId) -> Option<&TinyOrdSet>; fn contract_asset_tags( &self, From 49bd860a9cf61400debf059fecdb245b869526c4 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 14 Dec 2023 11:54:01 +0100 Subject: [PATCH 06/31] iface: improve APIs for builder seals --- Cargo.lock | 27 +++++++-------- Cargo.toml | 1 + src/containers/seal.rs | 16 +++++++-- src/interface/builder.rs | 74 +++++++++++++++++++++------------------- 4 files changed, 67 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0dd2941..8c6589d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,8 +217,7 @@ dependencies = [ [[package]] name = "bp-consensus" version = "0.11.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b50dbcec1e75163cdd1ba1cc265f1bb67ae4319b00469c36b3972a2eb49b879" +source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#31e9b11b96a8678afa3d39a774bfa686cb9c0def" dependencies = [ "amplify", "chrono", @@ -264,7 +263,7 @@ dependencies = [ [[package]] name = "bp-invoice" version = "0.11.0-beta.2" -source = "git+https://github.com/BP-WG/bp-std?branch=v0.11#664fa950efcb5bb653c9af4238c64e0610447200" +source = "git+https://github.com/BP-WG/bp-std?branch=v0.11#04dc07f0c93359d5ceef26efc28365a7a507866f" dependencies = [ "amplify", "bech32", @@ -537,9 +536,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "log" @@ -818,7 +817,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -951,9 +950,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -977,7 +976,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1065,7 +1064,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-shared", ] @@ -1099,7 +1098,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1132,7 +1131,7 @@ checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1213,9 +1212,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.26" +version = "0.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 45af1438..1e807529 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,4 +93,5 @@ features = [ "all" ] [patch.crates-io] bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } +bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "v0.11" } diff --git a/src/containers/seal.rs b/src/containers/seal.rs index 0975e269..40a78b72 100644 --- a/src/containers/seal.rs +++ b/src/containers/seal.rs @@ -27,9 +27,9 @@ use std::str::FromStr; use bp::seals::txout::blind::ParseError; use bp::seals::txout::{CloseMethod, TxPtr}; use bp::secp256k1::rand::{thread_rng, RngCore}; -use bp::Vout; +use bp::{Outpoint, Vout}; use commit_verify::Conceal; -use rgb::{ExposedSeal, GraphSeal, SealDefinition, SecretSeal}; +use rgb::{ExposedSeal, GenesisSeal, GraphSeal, SealDefinition, SecretSeal}; use crate::LIB_NAME_RGB_STD; @@ -243,3 +243,15 @@ pub enum BuilderSeal { #[from] Concealed(SecretSeal), } + +impl From for BuilderSeal { + fn from(outpoint: Outpoint) -> Self { + BuilderSeal::Revealed(SealDefinition::Bitcoin(outpoint.into())) + } +} + +impl From for BuilderSeal { + fn from(outpoint: Outpoint) -> Self { + BuilderSeal::Revealed(SealDefinition::Bitcoin(outpoint.into())) + } +} diff --git a/src/interface/builder.rs b/src/interface/builder.rs index b220c125..0fcbf961 100644 --- a/src/interface/builder.rs +++ b/src/interface/builder.rs @@ -172,7 +172,7 @@ impl ContractBuilder { pub fn add_owned_state_raw( mut self, type_id: AssignmentType, - seal: BuilderSeal, + seal: impl Into>, state: TypedState, ) -> Result { self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; @@ -182,7 +182,7 @@ impl ContractBuilder { pub fn add_rights( mut self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, ) -> Result { self.builder = self.builder.add_rights(name, seal, None)?; Ok(self) @@ -191,7 +191,7 @@ impl ContractBuilder { pub fn add_fungible_state( mut self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, value: u64, ) -> Result { let name = name.into(); @@ -224,7 +224,7 @@ impl ContractBuilder { pub fn add_data( mut self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, value: impl StrictSerialize, ) -> Result { self.builder = self.builder.add_data(name, seal, value, None)?; @@ -234,7 +234,7 @@ impl ContractBuilder { pub fn add_attachment( mut self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, attachment: AttachedState, ) -> Result { self.builder = self.builder.add_attachment(name, seal, attachment, None)?; @@ -360,10 +360,24 @@ impl TransitionBuilder { .assignments_type(name, Some(self.transition_type)) } + pub fn add_owned_state_raw( + mut self, + type_id: AssignmentType, + seal: impl Into>, + state: TypedState, + ) -> Result { + if matches!(state, TypedState::Amount(_, _, tag) if self.builder.asset_tag(type_id)? != tag) + { + return Err(BuilderError::AssetTagInvalid(type_id)); + } + self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; + Ok(self) + } + pub fn add_rights( mut self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, ) -> Result { self.builder = self .builder @@ -373,31 +387,17 @@ impl TransitionBuilder { pub fn add_fungible_default_state( self, - seal: BuilderSeal, + seal: impl Into>, value: u64, ) -> Result { let assignment_name = self.default_assignment()?.clone(); self.add_fungible_state(assignment_name, seal.into(), value) } - pub fn add_owned_state_raw( - mut self, - type_id: AssignmentType, - seal: BuilderSeal, - state: TypedState, - ) -> Result { - if matches!(state, TypedState::Amount(_, _, tag) if self.builder.asset_tag(type_id)? != tag) - { - return Err(BuilderError::AssetTagInvalid(type_id)); - } - self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; - Ok(self) - } - pub fn add_fungible_state_raw( mut self, type_id: AssignmentType, - seal: BuilderSeal, + seal: impl Into>, value: u64, blinding: BlindingFactor, ) -> Result { @@ -410,7 +410,7 @@ impl TransitionBuilder { pub fn add_fungible_state( mut self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, value: u64, ) -> Result { let name = name.into(); @@ -429,7 +429,7 @@ impl TransitionBuilder { pub fn add_data( mut self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, value: impl StrictSerialize, ) -> Result { self.builder = self @@ -440,7 +440,7 @@ impl TransitionBuilder { pub fn add_data_default( self, - seal: BuilderSeal, + seal: impl Into>, value: impl StrictSerialize, ) -> Result { let assignment_name = self.default_assignment()?.clone(); @@ -450,7 +450,7 @@ impl TransitionBuilder { pub fn add_attachment( mut self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, attachment: AttachedState, ) -> Result { self.builder = @@ -619,7 +619,7 @@ impl OperationBuilder { fn add_owned_state_raw( self, type_id: AssignmentType, - seal: BuilderSeal, + seal: impl Into>, state: TypedState, ) -> Result { match state { @@ -637,13 +637,14 @@ impl OperationBuilder { fn add_rights_raw( mut self, type_id: AssignmentType, - seal: BuilderSeal, + seal: impl Into>, ) -> Result { let state_schema = self.state_schema(type_id); if *state_schema != StateSchema::Fungible(FungibleType::Unsigned64Bit) { return Err(BuilderError::InvalidState(type_id)); } + let seal = seal.into(); match self.rights.get_mut(&type_id) { Some(assignments) => { assignments.push(seal)?; @@ -659,7 +660,7 @@ impl OperationBuilder { fn add_rights( self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, ty: Option, ) -> Result { let name = name.into(); @@ -674,7 +675,7 @@ impl OperationBuilder { fn add_fungible_state_raw( mut self, type_id: AssignmentType, - seal: BuilderSeal, + seal: impl Into>, state: RevealedValue, ) -> Result { let state_schema = self.state_schema(type_id); @@ -682,6 +683,7 @@ impl OperationBuilder { return Err(BuilderError::InvalidState(type_id)); } + let seal = seal.into(); match self.fungible.get_mut(&type_id) { Some(assignments) => { assignments.insert(seal, state)?; @@ -698,7 +700,7 @@ impl OperationBuilder { fn add_fungible_state( self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, value: u64, tag: AssetTag, ty: Option, @@ -716,11 +718,12 @@ impl OperationBuilder { fn add_data_raw( mut self, type_id: AssignmentType, - seal: BuilderSeal, + seal: impl Into>, state: RevealedData, ) -> Result { let state_schema = self.state_schema(type_id); if let StateSchema::Structured(_) = *state_schema { + let seal = seal.into(); match self.data.get_mut(&type_id) { Some(assignments) => { assignments.insert(seal, state)?; @@ -738,7 +741,7 @@ impl OperationBuilder { fn add_data( self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, value: impl StrictSerialize, ty: Option, ) -> Result { @@ -756,11 +759,12 @@ impl OperationBuilder { fn add_attachment_raw( mut self, type_id: AssignmentType, - seal: BuilderSeal, + seal: impl Into>, state: AttachedState, ) -> Result { let state_schema = self.state_schema(type_id); if let StateSchema::Structured(_) = *state_schema { + let seal = seal.into(); match self.attachments.get_mut(&type_id) { Some(assignments) => { assignments.insert(seal, state)?; @@ -779,7 +783,7 @@ impl OperationBuilder { fn add_attachment( self, name: impl Into, - seal: BuilderSeal, + seal: impl Into>, state: AttachedState, ty: Option, ) -> Result { From d66a13ce738f89d4306ce1ad31a00738626890e1 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 14 Dec 2023 17:24:36 +0100 Subject: [PATCH 07/31] epic: account for seal closing method --- Cargo.lock | 13 +- Cargo.toml | 5 +- src/accessors/assignments.rs | 12 +- src/accessors/bundle.rs | 6 +- src/containers/consignment.rs | 6 +- src/containers/mod.rs | 2 +- src/containers/partials.rs | 80 +++- src/containers/seal.rs | 24 +- src/containers/util.rs | 37 +- src/interface/contract.rs | 51 ++- src/persistence/inventory.rs | 51 ++- src/persistence/stock.rs | 44 +- src/stl/stl.rs | 2 +- stl/RGBStd@0.1.0.sta | 817 +++++++++++++++++----------------- stl/RGBStd@0.1.0.stl | Bin 19782 -> 19823 bytes stl/RGBStd@0.1.0.sty | 177 ++++---- 16 files changed, 720 insertions(+), 607 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c6589d6..f073fd22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,7 +217,7 @@ dependencies = [ [[package]] name = "bp-consensus" version = "0.11.0-beta.2" -source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#31e9b11b96a8678afa3d39a774bfa686cb9c0def" +source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#59de59f7bfb2f0913a28d08e6e9fc58d22b2c0f9" dependencies = [ "amplify", "chrono", @@ -231,8 +231,7 @@ dependencies = [ [[package]] name = "bp-core" version = "0.11.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece2f2e6f91bdbf5684f26a105c078e50394f98e37dc173c20b23eba53e7be09" +source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#59de59f7bfb2f0913a28d08e6e9fc58d22b2c0f9" dependencies = [ "amplify", "bp-consensus", @@ -248,8 +247,7 @@ dependencies = [ [[package]] name = "bp-dbc" version = "0.11.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d9d7bbc4dc2debd9c30469752963410b44adee55af807378589e88c5fd913a" +source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#59de59f7bfb2f0913a28d08e6e9fc58d22b2c0f9" dependencies = [ "amplify", "base85", @@ -274,8 +272,7 @@ dependencies = [ [[package]] name = "bp-seals" version = "0.11.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3661c2c7bddda0b6fd37dafa1a56c863ba0ff9787ebc06d05eb8ac676f1d469e" +source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#59de59f7bfb2f0913a28d08e6e9fc58d22b2c0f9" dependencies = [ "amplify", "baid58", @@ -652,7 +649,7 @@ dependencies = [ [[package]] name = "rgb-core" version = "0.11.0-beta.2" -source = "git+https://github.com/RGB-WG/rgb-core?branch=v0.11#1fc21032159d2b04443b823f6e6bfb3340ef0622" +source = "git+https://github.com/RGB-WG/rgb-core?branch=v0.11#441e27c165607c3e9fee0416acda6e0176888f6a" dependencies = [ "aluvm", "amplify", diff --git a/Cargo.toml b/Cargo.toml index 1e807529..53288c4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,9 @@ wasm-bindgen-test = "0.3" features = [ "all" ] [patch.crates-io] -bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } +bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } +bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } +bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } +bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "v0.11" } diff --git a/src/accessors/assignments.rs b/src/accessors/assignments.rs index 70981052..7a91c456 100644 --- a/src/accessors/assignments.rs +++ b/src/accessors/assignments.rs @@ -23,20 +23,20 @@ use amplify::confinement::SmallVec; use commit_verify::Conceal; use rgb::{ Assign, AssignAttach, AssignData, AssignFungible, AssignRights, ExposedSeal, ExposedState, - SealDefinition, TypedAssigns, + TypedAssigns, Xchain, }; pub trait TypedAssignsExt { - fn reveal_seal(&mut self, seal: SealDefinition); + fn reveal_seal(&mut self, seal: Xchain); - fn filter_revealed_seals(&self) -> Vec>; + fn filter_revealed_seals(&self) -> Vec>; } impl TypedAssignsExt for TypedAssigns { - fn reveal_seal(&mut self, seal: SealDefinition) { + fn reveal_seal(&mut self, seal: Xchain) { fn reveal( vec: &mut SmallVec>, - revealed: SealDefinition, + revealed: Xchain, ) { for assign in vec.iter_mut() { match assign { @@ -65,7 +65,7 @@ impl TypedAssignsExt for TypedAssigns { } } - fn filter_revealed_seals(&self) -> Vec> { + fn filter_revealed_seals(&self) -> Vec> { match self { TypedAssigns::Declarative(s) => { s.iter().filter_map(AssignRights::revealed_seal).collect() diff --git a/src/accessors/bundle.rs b/src/accessors/bundle.rs index 190d40da..4a389426 100644 --- a/src/accessors/bundle.rs +++ b/src/accessors/bundle.rs @@ -19,7 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use rgb::{GraphSeal, OpId, Operation, SealDefinition, Transition, TransitionBundle}; +use rgb::{GraphSeal, OpId, Operation, Transition, TransitionBundle, Xchain}; use crate::accessors::TypedAssignsExt; @@ -32,7 +32,7 @@ pub enum RevealError { pub trait BundleExt { /// Ensures that the seal is revealed inside the bundle. - fn reveal_seal(&mut self, seal: SealDefinition); + fn reveal_seal(&mut self, seal: Xchain); /// Ensures that the transition is revealed inside the bundle. /// @@ -44,7 +44,7 @@ pub trait BundleExt { } impl BundleExt for TransitionBundle { - fn reveal_seal(&mut self, seal: SealDefinition) { + fn reveal_seal(&mut self, seal: Xchain) { for (_, item) in self.keyed_values_mut() { if let Some(transition) = &mut item.transition { for (_, assign) in transition.assignments.keyed_values_mut() { diff --git a/src/containers/consignment.rs b/src/containers/consignment.rs index 809eb520..b5d195a6 100644 --- a/src/containers/consignment.rs +++ b/src/containers/consignment.rs @@ -27,8 +27,8 @@ use commit_verify::Conceal; use rgb::validation::{self, ConsignmentApi}; use rgb::{ AnchoredBundle, AssetTag, AssignmentType, AttachId, BundleId, ContractHistory, ContractId, - Extension, Genesis, GraphSeal, OpId, OpRef, Operation, Schema, SchemaId, SealDefinition, - SecretSeal, SubSchema, Transition, TransitionBundle, + Extension, Genesis, GraphSeal, OpId, OpRef, Operation, Schema, SchemaId, SecretSeal, SubSchema, + Transition, TransitionBundle, Xchain, }; use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize}; @@ -221,7 +221,7 @@ impl Consignment { Ok(history) } - pub fn reveal_bundle_seal(&mut self, bundle_id: BundleId, revealed: SealDefinition) { + pub fn reveal_bundle_seal(&mut self, bundle_id: BundleId, revealed: Xchain) { for anchored_bundle in &mut self.bundles { if anchored_bundle.bundle.bundle_id() == bundle_id { anchored_bundle.bundle.reveal_seal(revealed); diff --git a/src/containers/mod.rs b/src/containers/mod.rs index dcbfd97e..4aa75948 100644 --- a/src/containers/mod.rs +++ b/src/containers/mod.rs @@ -44,4 +44,4 @@ pub use consignment::{Consignment, Contract, Transfer}; pub use disclosure::Disclosure; pub use partials::{Batch, BatchItem, Fascia}; pub use seal::{BuilderSeal, TerminalSeal, VoutSeal}; -pub use util::{ContainerVer, Terminal}; +pub use util::{ContainerVer, Terminal, XchainOutpoint}; diff --git a/src/containers/partials.rs b/src/containers/partials.rs index bfa1f0b3..b8d01669 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -19,17 +19,59 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::ops::{BitOr, BitOrAssign}; use std::vec; -use amplify::confinement::MediumVec; +use amplify::confinement; +use amplify::confinement::{Confined, MediumVec, U24}; +use bp::seals::txout::CloseMethod; use commit_verify::mpc; -use rgb::{Anchor, OpId, Operation, Output, Transition, TransitionBundle}; +use rgb::{Anchor, OpId, Operation, OutputSeal, Transition, TransitionBundle}; use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize}; +use crate::containers::XchainOutpoint; use crate::LIB_NAME_RGB_STD; -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD, tags = repr, into_u8, try_from_u8)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +#[repr(u8)] +pub enum BatchCloseMethods { + #[strict_type(dumb)] + TapretFirst = 0x01, + OpretFirst = 0x02, + Both = 0x03, +} + +impl BitOr for BatchCloseMethods { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { if self == rhs { self } else { Self::Both } } +} + +impl BitOrAssign for BatchCloseMethods { + fn bitor_assign(&mut self, rhs: Self) { *self = self.bitor(rhs); } +} + +impl From for BatchCloseMethods { + fn from(seal: OutputSeal) -> Self { seal.method().into() } +} + +impl From for BatchCloseMethods { + fn from(method: CloseMethod) -> Self { + match method { + CloseMethod::OpretFirst => BatchCloseMethods::OpretFirst, + CloseMethod::TapretFirst => BatchCloseMethods::TapretFirst, + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] #[cfg_attr( feature = "serde", @@ -38,17 +80,41 @@ use crate::LIB_NAME_RGB_STD; )] pub struct BatchItem { pub id: OpId, - pub inputs: MediumVec, + pub inputs: Confined, 1, U24>, pub transition: Transition, + pub methods: BatchCloseMethods, +} + +impl StrictDumb for BatchItem { + fn strict_dumb() -> Self { Self::new(strict_dumb!(), [strict_dumb!()]).unwrap() } } impl BatchItem { - pub fn new(transition: Transition, inputs: MediumVec) -> Self { - BatchItem { + pub fn new( + transition: Transition, + seals: impl AsRef<[OutputSeal]>, + ) -> Result { + let inputs = Confined::, 1, U24>::try_from_iter( + seals.as_ref().iter().copied().map(XchainOutpoint::from), + )?; + let methods = seals + .as_ref() + .iter() + .map(|seal| seal.method()) + .map(BatchCloseMethods::from) + .fold(None, |acc, i| { + Some(match acc { + None => i, + Some(a) => a | i, + }) + }) + .expect("confinement guarantees at least one item"); + Ok(BatchItem { id: transition.id(), inputs, transition, - } + methods, + }) } } diff --git a/src/containers/seal.rs b/src/containers/seal.rs index 40a78b72..cafe37ca 100644 --- a/src/containers/seal.rs +++ b/src/containers/seal.rs @@ -29,7 +29,7 @@ use bp::seals::txout::{CloseMethod, TxPtr}; use bp::secp256k1::rand::{thread_rng, RngCore}; use bp::{Outpoint, Vout}; use commit_verify::Conceal; -use rgb::{ExposedSeal, GenesisSeal, GraphSeal, SealDefinition, SecretSeal}; +use rgb::{ExposedSeal, GenesisSeal, GraphSeal, SecretSeal, Xchain}; use crate::LIB_NAME_RGB_STD; @@ -156,10 +156,10 @@ impl From for TerminalSeal { } } -impl From> for TerminalSeal { - fn from(seal: SealDefinition) -> Self { +impl From> for TerminalSeal { + fn from(seal: Xchain) -> Self { match seal { - SealDefinition::Bitcoin( + Xchain::Bitcoin( seal @ GraphSeal { txid: TxPtr::WitnessTx, .. @@ -169,7 +169,7 @@ impl From> for TerminalSeal { seal.vout, seal.blinding, )), - SealDefinition::Liquid( + Xchain::Liquid( seal @ GraphSeal { txid: TxPtr::WitnessTx, .. @@ -206,10 +206,10 @@ impl Conceal for TerminalSeal { match *self { TerminalSeal::ConcealedUtxo(hash) => hash, TerminalSeal::BitcoinWitnessVout(seal) => { - SealDefinition::Bitcoin(GraphSeal::from(seal)).conceal() + Xchain::Bitcoin(GraphSeal::from(seal)).conceal() } TerminalSeal::LiquidWitnessVout(seal) => { - SealDefinition::Liquid(GraphSeal::from(seal)).conceal() + Xchain::Liquid(GraphSeal::from(seal)).conceal() } } } @@ -239,19 +239,15 @@ impl FromStr for TerminalSeal { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, From)] pub enum BuilderSeal { #[from] - Revealed(SealDefinition), + Revealed(Xchain), #[from] Concealed(SecretSeal), } impl From for BuilderSeal { - fn from(outpoint: Outpoint) -> Self { - BuilderSeal::Revealed(SealDefinition::Bitcoin(outpoint.into())) - } + fn from(outpoint: Outpoint) -> Self { BuilderSeal::Revealed(Xchain::Bitcoin(outpoint.into())) } } impl From for BuilderSeal { - fn from(outpoint: Outpoint) -> Self { - BuilderSeal::Revealed(SealDefinition::Bitcoin(outpoint.into())) - } + fn from(outpoint: Outpoint) -> Self { BuilderSeal::Revealed(Xchain::Bitcoin(outpoint.into())) } } diff --git a/src/containers/util.rs b/src/containers/util.rs index c0063130..b6f0bc37 100644 --- a/src/containers/util.rs +++ b/src/containers/util.rs @@ -20,7 +20,8 @@ // limitations under the License. use amplify::confinement::SmallOrdSet; -use bp::Tx; +use bp::{Outpoint, Tx}; +use rgb::OutputSeal; use super::TerminalSeal; use crate::LIB_NAME_RGB_STD; @@ -69,3 +70,37 @@ pub enum ContainerVer { #[default] V2 = 2, } + +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD, tags = custom, dumb = Self::Bitcoin(strict_dumb!()))] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +#[non_exhaustive] +pub enum XchainOutpoint { + #[strict_type(tag = 0x00)] + #[display("bitcoin:{0}", alt = "{0}")] + Bitcoin(Outpoint), + + #[strict_type(tag = 0x01)] + #[display("liquid:{0}")] + Liquid(Outpoint), + /* + #[strict_type(tag = 0x10)] + Abraxas, + #[strict_type(tag = 0x11)] + Prime, + */ +} + +impl From for XchainOutpoint { + fn from(seal: OutputSeal) -> Self { + match seal { + OutputSeal::Bitcoin(s) => XchainOutpoint::Bitcoin(s.into()), + OutputSeal::Liquid(s) => XchainOutpoint::Liquid(s.into()), + } + } +} diff --git a/src/interface/contract.rs b/src/interface/contract.rs index bca5ace3..d1113bb5 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -26,12 +26,13 @@ use amplify::confinement::{LargeOrdMap, LargeVec, SmallVec}; use bp::Outpoint; use rgb::{ AssetTag, AssignmentType, AttachId, BlindingFactor, ContractId, ContractState, FungibleOutput, - MediaType, Output, RevealedAttach, RevealedData, WitnessId, + MediaType, OutputSeal, RevealedAttach, RevealedData, WitnessId, }; use strict_encoding::FieldName; use strict_types::typify::TypedVal; use strict_types::{decode, StrictVal}; +use crate::containers::XchainOutpoint; use crate::interface::{IfaceId, IfaceImpl}; #[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -104,7 +105,7 @@ impl From> for AllocationWitness { // TODO: Consider removing type in favour of `FungibleOutput` #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct FungibleAllocation { - pub owner: Output, + pub owner: OutputSeal, pub witness: AllocationWitness, pub value: u64, } @@ -116,7 +117,7 @@ impl From for FungibleAllocation { impl From<&FungibleOutput> for FungibleAllocation { fn from(out: &FungibleOutput) -> Self { FungibleAllocation { - owner: out.output, + owner: out.seal, witness: out.witness.into(), value: out.state.value.as_u64(), } @@ -124,22 +125,26 @@ impl From<&FungibleOutput> for FungibleAllocation { } pub trait OutpointFilter { - fn include_output(&self, output: Output) -> bool; + fn include_output(&self, output: impl Into) -> bool; } pub struct FilterIncludeAll; pub struct FilterExclude(pub T); impl OutpointFilter for &T { - fn include_output(&self, output: Output) -> bool { (*self).include_output(output) } + fn include_output(&self, output: impl Into) -> bool { + (*self).include_output(output) + } } impl OutpointFilter for &mut T { - fn include_output(&self, output: Output) -> bool { self.deref().include_output(output) } + fn include_output(&self, output: impl Into) -> bool { + self.deref().include_output(output) + } } impl OutpointFilter for Option { - fn include_output(&self, output: Output) -> bool { + fn include_output(&self, output: impl Into) -> bool { self.as_ref() .map(|filter| filter.include_output(output)) .unwrap_or(true) @@ -147,27 +152,37 @@ impl OutpointFilter for Option { } impl OutpointFilter for FilterIncludeAll { - fn include_output(&self, _: Output) -> bool { true } + fn include_output(&self, _: impl Into) -> bool { true } } impl OutpointFilter for FilterExclude { - fn include_output(&self, output: Output) -> bool { !self.0.include_output(output) } + fn include_output(&self, output: impl Into) -> bool { + !self.0.include_output(output.into()) + } } -impl OutpointFilter for &[Output] { - fn include_output(&self, output: Output) -> bool { self.contains(&output) } +impl OutpointFilter for &[XchainOutpoint] { + fn include_output(&self, output: impl Into) -> bool { + self.contains(&output.into()) + } } -impl OutpointFilter for Vec { - fn include_output(&self, output: Output) -> bool { self.contains(&output) } +impl OutpointFilter for Vec { + fn include_output(&self, output: impl Into) -> bool { + self.contains(&output.into()) + } } -impl OutpointFilter for HashSet { - fn include_output(&self, output: Output) -> bool { self.contains(&output) } +impl OutpointFilter for HashSet { + fn include_output(&self, output: impl Into) -> bool { + self.contains(&output.into()) + } } -impl OutpointFilter for BTreeSet { - fn include_output(&self, output: Output) -> bool { self.contains(&output) } +impl OutpointFilter for BTreeSet { + fn include_output(&self, output: impl Into) -> bool { + self.contains(&output.into()) + } } /// Contract state is an in-memory structure providing API to read structured @@ -226,7 +241,7 @@ impl ContractIface { .fungibles() .iter() .filter(|outp| outp.opout.ty == type_id) - .filter(|outp| filter.include_output(outp.output)) + .filter(|outp| filter.include_output(outp.seal)) .map(FungibleAllocation::from); Ok(LargeVec::try_from_iter(state).expect("same or smaller collection size")) } diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index 5e540017..bffc267f 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -33,8 +33,8 @@ use commit_verify::{mpc, Conceal}; use invoice::{Beneficiary, InvoiceState, RgbInvoice}; use rgb::{ validation, Anchor, AnchoredBundle, AssignmentType, BlindingFactor, BundleError, BundleId, - ContractId, ExposedSeal, GraphSeal, OpId, Operation, Opout, Output, SchemaId, SealDefinition, - SecretSeal, SubSchema, Transition, TransitionBundle, WitnessId, + ContractId, ExposedSeal, GraphSeal, OpId, Operation, Opout, OutputSeal, SchemaId, SecretSeal, + SubSchema, Transition, TransitionBundle, WitnessId, Xchain, }; use strict_encoding::TypeName; @@ -215,7 +215,7 @@ pub enum DataError { Merge(MergeRevealError), /// outpoint {0} is not part of the contract {1}. - OutpointUnknown(Output, ContractId), + OutpointUnknown(OutputSeal, ContractId), #[from] Confinement(confinement::Error), @@ -501,7 +501,7 @@ pub trait Inventory: Deref { fn contracts_by_outputs( &self, - outputs: impl IntoIterator>, + outputs: impl IntoIterator>, ) -> Result, InventoryError>; fn public_opouts( @@ -512,7 +512,7 @@ pub trait Inventory: Deref { fn opouts_by_outputs( &self, contract_id: ContractId, - outputs: impl IntoIterator>, + outputs: impl IntoIterator>, ) -> Result, InventoryError>; fn opouts_by_terminals( @@ -523,17 +523,15 @@ pub trait Inventory: Deref { fn state_for_outputs( &self, contract_id: ContractId, - outputs: impl IntoIterator>, - ) -> Result, InventoryError>; + outputs: impl IntoIterator>, + ) -> Result, InventoryError>; fn store_seal_secret( &mut self, - seal: SealDefinition, + seal: Xchain, ) -> Result<(), InventoryError>; - fn seal_secrets( - &self, - ) -> Result>, InventoryError>; + fn seal_secrets(&self) -> Result>, InventoryError>; #[allow(clippy::type_complexity)] fn export_contract( @@ -544,7 +542,7 @@ pub trait Inventory: Deref { ConsignerError::Target as Stash>::Error>, > { let mut consignment = - self.consign::(contract_id, [] as [SealDefinition; 0])?; + self.consign::(contract_id, [] as [Xchain; 0])?; consignment.transfer = false; Ok(consignment.into()) // TODO: Add known sigs to the bindle @@ -578,7 +576,7 @@ pub trait Inventory: Deref { let (outpoint_seals, terminal_seals) = seals .into_iter() .map(|seal| match seal.into() { - BuilderSeal::Revealed(seal) => (seal.output(), seal.conceal()), + BuilderSeal::Revealed(seal) => (seal.to_output_seal(), seal.conceal()), BuilderSeal::Concealed(seal) => (None, seal), }) .unzip::<_, _, Vec<_>, Vec<_>>(); @@ -671,7 +669,7 @@ pub trait Inventory: Deref { fn compose( &self, invoice: &RgbInvoice, - prev_outputs: impl IntoIterator>, + prev_outputs: impl IntoIterator>, method: CloseMethod, change_vout: impl Into, allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, @@ -690,7 +688,7 @@ pub trait Inventory: Deref { fn compose_deterministic( &self, invoice: &RgbInvoice, - prev_outputs: impl IntoIterator>, + prev_outputs: impl IntoIterator>, method: CloseMethod, change_vout: impl Into, allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, @@ -704,7 +702,7 @@ pub trait Inventory: Deref { let prev_outputs = prev_outputs .into_iter() .map(|o| o.into()) - .collect::>(); + .collect::>(); let output_for_assignment = |id: ContractId, assignment_type: AssignmentType| @@ -720,7 +718,7 @@ pub trait Inventory: Deref { let vout = allocator(id, assignment_type, velocity) .ok_or(ComposeError::NoBlankOrChange(velocity, assignment_type))?; let seal = GraphSeal::new_vout(method, vout); - Ok(BuilderSeal::Revealed(SealDefinition::with(layer1, seal))) + Ok(BuilderSeal::Revealed(Xchain::with(layer1, seal))) }; // 1. Prepare the data @@ -736,13 +734,13 @@ pub trait Inventory: Deref { let beneficiary = match invoice.beneficiary { Beneficiary::BlindedSeal(seal) => BuilderSeal::Concealed(seal), - Beneficiary::WitnessVoutBitcoin(_) => BuilderSeal::Revealed(SealDefinition::Bitcoin( - GraphSeal::new_vout(method, change_vout), - )), + Beneficiary::WitnessVoutBitcoin(_) => { + BuilderSeal::Revealed(Xchain::Bitcoin(GraphSeal::new_vout(method, change_vout))) + } }; // 2. Prepare transition - let mut main_inputs = MediumVec::::new(); + let mut main_inputs = MediumVec::::new(); let assignment_name = invoice .assignment .as_ref() @@ -798,7 +796,8 @@ pub trait Inventory: Deref { // 3. Prepare other transitions // Enumerate state - let mut spent_state = HashMap::>::new(); + let mut spent_state = + HashMap::>::new(); for output in prev_outputs { for id in self.contracts_by_outputs([output])? { if id == contract_id { @@ -814,10 +813,10 @@ pub trait Inventory: Deref { let mut blanks = MediumVec::with_capacity(spent_state.len()); for (id, opouts) in spent_state { let mut blank_builder = self.blank_builder(id, iface.clone())?; - let mut outputs = MediumVec::with_capacity(opouts.len()); + let mut outputs = Vec::with_capacity(opouts.len()); for ((opout, output), mut state) in opouts { let seal = output_for_assignment(id, opout.ty)?; - outputs.push(output)?; + outputs.push(output); if let TypedState::Amount(_, ref mut blinding, _) = state { *blinding = blinder(id, opout.ty); } @@ -827,11 +826,11 @@ pub trait Inventory: Deref { } let transition = blank_builder.complete_transition(contract_id)?; - blanks.push(BatchItem::new(transition, outputs))?; + blanks.push(BatchItem::new(transition, outputs)?)?; } Ok(Batch { - main: BatchItem::new(main_transition, main_inputs), + main: BatchItem::new(main_transition, main_inputs)?, blanks, }) } diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index e12af130..861ff9ad 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -30,8 +30,8 @@ use rgb::validation::{Status, Validity, Warning}; use rgb::{ validation, Anchor, AnchorId, AnchoredBundle, Assign, AssignmentType, BundleId, ContractHistory, ContractId, ContractState, ExposedState, Extension, Genesis, GenesisSeal, - GraphSeal, OpId, Operation, Opout, Output, SealDefinition, SecretSeal, SubSchema, Transition, - TransitionBundle, TypedAssigns, WitnessAnchor, WitnessId, + GraphSeal, OpId, Operation, Opout, OutputSeal, SecretSeal, SubSchema, Transition, + TransitionBundle, TypedAssigns, WitnessAnchor, WitnessId, Xchain, }; use strict_encoding::{StrictDeserialize, StrictSerialize}; @@ -56,7 +56,7 @@ pub struct IndexedBundle(ContractId, BundleId); #[strict_type(lib = LIB_NAME_RGB_STD)] pub struct ContractIndex { public_opouts: MediumOrdSet, - outpoint_opouts: MediumOrdMap>, + outpoint_opouts: MediumOrdMap>, } /// Stock is an in-memory inventory (stash, index, contract state) useful for @@ -78,7 +78,7 @@ pub struct Stock { contract_index: TinyOrdMap, terminal_index: MediumOrdMap, // secrets - seal_secrets: MediumOrdSet>, + seal_secrets: MediumOrdSet>, } impl Default for Stock { @@ -292,7 +292,9 @@ impl Stock { for (no, a) in vec.iter().enumerate() { let opout = Opout::new(opid, type_id, no as u16); if let Assign::ConfidentialState { seal, .. } | Assign::Revealed { seal, .. } = a { - let output = seal.output().expect("genesis seals always have outpoint"); + let output = seal + .to_output_seal() + .expect("genesis seals always have outpoint"); match index.outpoint_opouts.get_mut(&output) { Some(opouts) => { opouts.push(opout)?; @@ -328,7 +330,7 @@ impl Stock { let opout = Opout::new(opid, type_id, no as u16); if let Assign::ConfidentialState { seal, .. } | Assign::Revealed { seal, .. } = a { let output = seal - .output_or_witness(witness_id) + .try_to_output_seal(witness_id) .map_err(|_| DataError::ChainMismatch)?; match index.outpoint_opouts.get_mut(&output) { Some(opouts) => { @@ -578,7 +580,7 @@ impl Inventory for Stock { fn contracts_by_outputs( &self, - outputs: impl IntoIterator>, + outputs: impl IntoIterator>, ) -> Result, InventoryError> { let outputs = outputs .into_iter() @@ -609,7 +611,7 @@ impl Inventory for Stock { fn opouts_by_outputs( &self, contract_id: ContractId, - outputs: impl IntoIterator>, + outputs: impl IntoIterator>, ) -> Result, InventoryError> { let index = self .contract_index @@ -642,8 +644,8 @@ impl Inventory for Stock { fn state_for_outputs( &self, contract_id: ContractId, - outputs: impl IntoIterator>, - ) -> Result, InventoryError> { + outputs: impl IntoIterator>, + ) -> Result, InventoryError> { let outputs = outputs .into_iter() .map(|o| o.into()) @@ -657,9 +659,9 @@ impl Inventory for Stock { let mut res = BTreeMap::new(); for item in history.fungibles() { - if outputs.contains(&item.output) { + if outputs.contains(&item.seal) { res.insert( - (item.opout, item.output), + (item.opout, item.seal), TypedState::Amount( item.state.value.as_u64(), item.state.blinding, @@ -670,21 +672,21 @@ impl Inventory for Stock { } for item in history.data() { - if outputs.contains(&item.output) { - res.insert((item.opout, item.output), TypedState::Data(item.state.clone())); + if outputs.contains(&item.seal) { + res.insert((item.opout, item.seal), TypedState::Data(item.state.clone())); } } for item in history.rights() { - if outputs.contains(&item.output) { - res.insert((item.opout, item.output), TypedState::Void); + if outputs.contains(&item.seal) { + res.insert((item.opout, item.seal), TypedState::Void); } } for item in history.attach() { - if outputs.contains(&item.output) { + if outputs.contains(&item.seal) { res.insert( - (item.opout, item.output), + (item.opout, item.seal), TypedState::Attachment(item.state.clone().into()), ); } @@ -695,15 +697,13 @@ impl Inventory for Stock { fn store_seal_secret( &mut self, - seal: SealDefinition, + seal: Xchain, ) -> Result<(), InventoryError> { self.seal_secrets.push(seal)?; Ok(()) } - fn seal_secrets( - &self, - ) -> Result>, InventoryError> { + fn seal_secrets(&self) -> Result>, InventoryError> { Ok(self.seal_secrets.to_inner()) } } diff --git a/src/stl/stl.rs b/src/stl/stl.rs index 3cc77afb..fbc3caa3 100644 --- a/src/stl/stl.rs +++ b/src/stl/stl.rs @@ -44,7 +44,7 @@ pub const LIB_ID_RGB_CONTRACT: &str = /// Strict types id for the library representing of RGB StdLib data types. pub const LIB_ID_RGB_STD: &str = - "urn:ubideco:stl:5BWvsBkteVgD3yiGy8gHHuzhHFLQTz4mZU7kJob3JkHW#lazarus-ranger-neutral"; + "urn:ubideco:stl:DYA42oBWsH5kKa1dUVwHEEkzEmhDDypKnXWDF1F4RnVK#example-alice-salt"; fn _rgb_std_stl() -> Result { LibBuilder::new(libname!(LIB_NAME_RGB_STD), tiny_bset! { diff --git a/stl/RGBStd@0.1.0.sta b/stl/RGBStd@0.1.0.sta index bb2ca86e..daf420c6 100644 --- a/stl/RGBStd@0.1.0.sta +++ b/stl/RGBStd@0.1.0.sta @@ -1,428 +1,429 @@ -----BEGIN STRICT TYPE LIB----- -Id: urn:ubideco:stl:5BWvsBkteVgD3yiGy8gHHuzhHFLQTz4mZU7kJob3JkHW +Id: urn:ubideco:stl:DYA42oBWsH5kKa1dUVwHEEkzEmhDDypKnXWDF1F4RnVK Name: RGBStd Dependencies: - urn:ubideco:stl:5dLgsvZEbhQekV5j7ghFCTyu3JPZ1B99SqhqPEA4Fdc, urn:ubideco:stl:ZtHaBzu9ojbDahaGKEXe5v9DfSDxLERbLkEB23R6Q6V, urn:ubideco:stl:5XLKQ1sNryZm9bdFKU2kBY3MPYdZXhchVdQKBbHA3gby, urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ, + urn:ubideco:stl:9gBiimtSnE1Qxwa49rbdPpv2s5ggXfitb6aktnunpDjX, + urn:ubideco:stl:BrKg2wSDRFDFSGzn2RrQkbwFeuLw2yDAFtpQ7WRh3nrz, urn:ubideco:stl:DVtm25LRKU4TjbyZmVxPhvCmctZ6vKkPKqfpU2QsDNUo, - urn:ubideco:stl:E4xYrMV4cHgYhTPzrjG3DxzAyLEMnbvnsrBsGhQP4anV, urn:ubideco:stl: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 -WU8aGVR5cGVkQXNzaWduc0JsaW5kU2VhbFR4aWRFKqVffdYBSouhbcRmMrYP8bVs -3DpTLs+9a5PVZxmeiQxSZXNlcnZlZEJ5dGVGNH2lHu1oDF77by+mxG/p2cNS74mO -KbKURqaNxqBepgxHbG9iYWxWYWx1ZXNG7ebDCBz9uOZXpCpc4MYIhH/8H75edrlx -dKnK9YlZzgtWYWxlbmN5VHlwZUi9Gm4X+4Y7Fnx+JV41Z9uCQ+8qXrrrosUKzQmu -nlEaElBlZGVyc2VuQ29tbWl0bWVudEwOZhJWYVmGKwGopFLSjxSghbpucBuoQD3M -Yg/idq3oHFNlYWxEZWZpbml0aW9uQmxpbmRTZWFsVHhQdHJRcFb/JTa0M+NTgQaX -gPTuDQo5vskjWNzPydduxPGH2QZPdXRwdXRZolHNjebLiSRE7c91W7GqpdwcP9rB -NM1sJTbV9hOKwiBBc3NpZ25SZXZlYWxlZERhdGFCbGluZFNlYWxUeFB0clngYcQu -Y9cDm6Ud134roaFso8WFSGSXMKERWZKgIcxUDkFuY2hvcmVkQnVuZGxlZHUeQqkV -oTxDEYLV/4bVHNNEcKOQ4UrsoFDMOlNvSN4NRXh0ZW5zaW9uVHlwZWa0l4SPxHk5 -YN80kut2EpCzDqwQ0T03VC1SZBEIlFBxD0V4dGVuc2lvblNjaGVtYWhTNCAM3FPG -TXbiti6qZi/aOtmRvwarKQ680PZ6A0rMDlJldmVhbGVkQXR0YWNobUTG9C9qBTpD -FQ+m5sIsxOh65SyU+AbUDKXch/Z1jaAQUmV2ZWFsZWRGdW5naWJsZXANZRCygoFv -H7c95RJjkwNXCKVSYa0C4NS+WsXPp+oJDUNvbmNlYWxlZERhdGF52xr8CB6hdXgQ -w8hr7HE9twtbmdr9g/hGMltC0aMcWR5PdXRwdXRBc3NpZ25tZW50UmV2ZWFsZWRB -dHRhY2h69XkNjG4gtH3wH/XNhWn1y7zxEMGvZWy1kKKWtKv/AQlHbG9iYWxPcmSD -+kzgrluOjxrgLhTubmJilfVuAJtCDQ3jB/UybgLsOgdHZW5lc2lzhHENkyxO9MO3 -CEtpi7CHcCl+OWQkf0WR2NqDbdF9ujgIQXR0YWNoSWSFuPgru/Skpg2zvz9FuA+U -bniDw61SbZP0b6MBqG5H2g5CbGluZGluZ0ZhY3Rvcof+4mVYiGzoHL6GhLN5YycT -ZYPFtmgBXosUFjaxRIe5DkFzc2lnbm1lbnRUeXBlkxC8gLE0Wosvw1hS7g9NaNAd -t/o1y5tkkqtWCZr0mpcFT3BvdXSUUtPbA6urqFGfp/Y+0BTr1E19MT/8/gD6XSR6 -VASQEAhTY2hlbWFJZJXI5noedWJf1JZVQmqR635CkKFvWpjxvlD3tookEvfFBE9w -SWSY5gjVKbw/MM5PTKoArCznR/7POkLscrIKgsC+AhMCiRxBc3NpZ25Wb2lkU3Rh -dGVCbGluZFNlYWxUeGlkmZSlir0dPFRFLym65UnNAJfnIJSUv2dHX0hqyiz4Vhch -QXNzaWduUmV2ZWFsZWRWYWx1ZUJsaW5kU2VhbFR4UHRynwgsSTrIAqK6xd3cCyJ8 -IK+U1GjESM8aWiHgvcL1OjIKQ29udHJhY3RJZKIzyegoTsx1mPwGOec00MsCjEss -3ISRPpnZqkY+JNZSC0dsb2JhbFN0YXRlovrqnnBcnJHM291G7Y9w5Y71FIM+yD5c -ZLVqW8NTrbAJQWx1U2NyaXB0pabUe56ieBkXbm3T8ilmk3bA/g7JVbTE642QUrgU -LJ8KQnVuZGxlSXRlbaaMMJFHS8o6wmKMx5VEjSzdqsUUnwUzlav2PFVhBxcmDUZ1 -bmdpYmxlU3RhdGWoWGv4kWXawiMQbb2FxIbJN+awZusMZkH/Fi9oqHelmApSYW5n -ZVByb29mq0L/CsSQakUQ+FRfBiQqTQmMkFVYs9PbNyxwjFngTEMNR2VuZXNpc1Nj -aGVtYbGE2FqNX4O/S8m9j3uvw5TT6slgbWmmash4pOYVT7MiEUFuY2hvck1lcmts -ZUJsb2NrtRWURiX16DrteJt7itR6DTKoivrLIO0ESp8iRPogQ70YQXNzaWdubWVu -dHNCbGluZFNlYWxUeGlkvcdveMo95d9+PKqU9FGUnC0VNYeAXXvK6KRai0nxAX8Q -VHJhbnNpdGlvblNjaGVtYcIe7NwA077i648Cm3I6+7EQwDaX6c8DaBmUFaYEB2nK -CldpdG5lc3NPcmTCUa1l6XUNrJoSWczAhSRc1fexb1LYcjZGLPF1jJ9OXRFDb25j -ZWFsZWRGdW5naWJsZcSgCp7hCQITdyIBFVk7g8NT4mD4gRDkszbK42hGQScbCVdp -dG5lc3NJZMYYY3tnTQy0vKnAQ11/MmKDmHhzdCdD0TflRPu6EtBMBlNjcmlwdMeY -pthjNnhEHtpRbiw+i78OqLBKgMG3HbnpcuY/ceYkEUdsb2JhbFN0YXRlU2NoZW1h -yGuCewtafcaRBCRy5SInj9DPxRpXaKBLP6oxQBs8fiMJQWx0TGF5ZXIxyUJCIu0C -vkdp/U8jHbNFTqcovEOoEQ7bM8uPLwqeSEYFSW5wdXTJj5qpwwZLGv39ZxuXvCr8 -/kxojx9zyC3rcW/naZsirwhBc3NldFRhZ9Dt3q+bZlQ2vSmW24KeCQCmg7JWb0Ky -ZfKw+0WI+q8RGU91dHB1dEFzc2lnbm1lbnRWb2lkU3RhdGXV7pIOSYizafFqU9EH -Svu1I/jHUnEe+zY9VlkF4eQVyw9HbG9iYWxTdGF0ZVR5cGXYY93FeLsPNcGN8j2e -uhtJkH0Sl+eDrKaLkdAx6PyXYxxPdXRwdXRBc3NpZ25tZW50UmV2ZWFsZWREYXRh -2ptRE1gWVnaQh/uZ5VaUcjaA1zkMBqHMJJgsBWT3zNUDRmZ23YVmAG9hZBEU7o7x -16r4CbMaJLCqJ6mbsjDoqs8pR00JVmFsZW5jaWVz38+pkfWH5U1EtwEVXAAp/JVr -m/HNqUGOYziWqsAqg+gIUmVkZWVtZWTi8tuzmTSI9GNgcXARevZlWteS9+UM2b3V -yT4M4euxIBlBc3NpZ25tZW50c0JsaW5kU2VhbFR4UHRy6ta3unkK4FGpfiwZ+Pwh -nMW+Kp3v3/3VY6uveGsbggsNV2l0bmVzc0FuY2hvcvl4TaC2Q945fB7ZV40zjDfR -HMviSsHop5pM5NX8GCerBklucHV0c/n0rAhmrkF3ZtT9DBF9BLHZVP0OZ14SO2IE -63FP6eVGDEZ1bmdpYmxlVHlwZfvMF3YVORemNKbhvuAe3SvcYr+viVrldTly/ypB -l9iAIkFzc2lnblJldmVhbGVkQXR0YWNoQmxpbmRTZWFsVHhQdHL8NEXdX88NC/+s -FaR6ugUi4FuLKxswZVKHg497LeuOPQ9Db25jZWFsZWRBdHRhY2gDU3RkBAAUTa7I -MubEN5J7JV65m0KKh9kNUKjM0E1yEbW3vF9mzgVBc2NpaSLk4JbpvX1chvXh3113 -AWr+Occ82TSFUJRAiYyoo3leAlU1YYYi0Xuu8GYC3+d1yYDgs2tuuugJDYB191E7 -7EuT9k0EQm9vbHKOpoqX3nQg9ipZabBLhyYEv0XW3ziVnH4m56ckkOStDkFscGhh -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 +j7oGU2NoZW1hP6Xk/XbnoyVMKK58A3Bboboij3LYxcT6MOGKHPKwSRogQXNzaWdu +UmV2ZWFsZWRWYWx1ZUJsaW5kU2VhbFR4aWRCMGGFiMjUqxQmQMf9yRcszdD/EP8N +k4AARHyImt3MeQlNZWRpYVR5cGVFKqVffdYBSouhbcRmMrYP8bVs3DpTLs+9a5PV +ZxmeiQxSZXNlcnZlZEJ5dGVGNH2lHu1oDF77by+mxG/p2cNS74mOKbKURqaNxqBe +pgxHbG9iYWxWYWx1ZXNG7ebDCBz9uOZXpCpc4MYIhH/8H75edrlxdKnK9YlZzgtW +YWxlbmN5VHlwZUi9Gm4X+4Y7Fnx+JV41Z9uCQ+8qXrrrosUKzQmunlEaElBlZGVy +c2VuQ29tbWl0bWVudFK/rBG0XaXChTXqDcbShj4Hjo1oQYhGUhOneUb9oDn2GUFz +c2lnbm1lbnRzQmxpbmRTZWFsVHhQdHJfVlljb8tZ19U4VCAUF1/4byMGQCaxJ9Jd +slGChgurrBhBc3NpZ25tZW50c0JsaW5kU2VhbFR4aWRkdR5CqRWhPEMRgtX/htUc +00Rwo5DhSuygUMw6U29I3g1FeHRlbnNpb25UeXBlZhqiPpcUpz3ck/WWEKsHCw26 +ZTJB/uB0F8+8TH7BKcoQVHJhbnNpdGlvbkJ1bmRsZWa0l4SPxHk5YN80kut2EpCz +DqwQ0T03VC1SZBEIlFBxD0V4dGVuc2lvblNjaGVtYWhTNCAM3FPGTXbiti6qZi/a +OtmRvwarKQ680PZ6A0rMDlJldmVhbGVkQXR0YWNobUTG9C9qBTpDFQ+m5sIsxOh6 +5SyU+AbUDKXch/Z1jaAQUmV2ZWFsZWRGdW5naWJsZXANZRCygoFvH7c95RJjkwNX +CKVSYa0C4NS+WsXPp+oJDUNvbmNlYWxlZERhdGF69XkNjG4gtH3wH/XNhWn1y7zx +EMGvZWy1kKKWtKv/AQlHbG9iYWxPcmSEcQ2TLE70w7cIS2mLsIdwKX45ZCR/RZHY +2oNt0X26OAhBdHRhY2hJZIW4+Cu79KSmDbO/P0W4D5RueIPDrVJtk/RvowGobkfa +DkJsaW5kaW5nRmFjdG9yh/7iZViIbOgcvoaEs3ljJxNlg8W2aAFeixQWNrFEh7kO +QXNzaWdubWVudFR5cGWMwdNqHGGkrHkTbqPfYf8oiq8voLGVTcVv1Knx91k/WxlU +eXBlZEFzc2lnbnNCbGluZFNlYWxUeGlkkxC8gLE0Wosvw1hS7g9NaNAdt/o1y5tk +kqtWCZr0mpcFT3BvdXSUUtPbA6urqFGfp/Y+0BTr1E19MT/8/gD6XSR6VASQEAhT +Y2hlbWFJZJXI5noedWJf1JZVQmqR635CkKFvWpjxvlD3tookEvfFBE9wSWSfCCxJ +OsgCorrF3dwLInwgr5TUaMRIzxpaIeC9wvU6MgpDb250cmFjdElkojPJ6ChOzHWY +/AY55zTQywKMSyzchJE+mdmqRj4k1lILR2xvYmFsU3RhdGWi+uqecFyckczb3Ubt +j3DljvUUgz7IPlxktWpbw1OtsAlBbHVTY3JpcHSmjDCRR0vKOsJijMeVRI0s3arF +FJ8FM5Wr9jxVYQcXJg1GdW5naWJsZVN0YXRlqFhr+JFl2sIjEG29hcSGyTfmsGbr +DGZB/xYvaKh3pZgKUmFuZ2VQcm9vZqtC/wrEkGpFEPhUXwYkKk0JjJBVWLPT2zcs +cIxZ4ExDDUdlbmVzaXNTY2hlbWGxhNhajV+Dv0vJvY97r8OU0+rJYG1ppmrIeKTm +FU+zIhFBbmNob3JNZXJrbGVCbG9ja7htzpsm2HPh6rdqar8rsZXJtT7VVA/RqNsD +RzUE92VoHU91dHB1dEFzc2lnbm1lbnRSZXZlYWxlZFZhbHVlvcdveMo95d9+PKqU +9FGUnC0VNYeAXXvK6KRai0nxAX8QVHJhbnNpdGlvblNjaGVtYb61xaOXj7fsShcZ +qPMJYJ/2pmcSaP3VSOUTHyhQfchXClRyYW5zaXRpb27A4gov08gxGtaH+pfqjunb +IJMhb5y9o1wnOqguM8YczxNYY2hhaW5CbGluZFNlYWxUeGlkwh7s3ADTvuLrjwKb +cjr7sRDANpfpzwNoGZQVpgQHacoKV2l0bmVzc09yZMJRrWXpdQ2smhJZzMCFJFzV +97FvUthyNkYs8XWMn05dEUNvbmNlYWxlZEZ1bmdpYmxlxKAKnuEJAhN3IgEVWTuD +w1PiYPiBEOSzNsrjaEZBJxsJV2l0bmVzc0lkxhhje2dNDLS8qcBDXX8yYoOYeHN0 +J0PRN+VE+7oS0EwGU2NyaXB0x5im2GM2eEQe2lFuLD6Lvw6osEqAwbcduely5j9x +5iQRR2xvYmFsU3RhdGVTY2hlbWHIa4J7C1p9xpEEJHLlIieP0M/FGldooEs/qjFA +Gzx+IwlBbHRMYXllcjHJQkIi7QK+R2n9TyMds0VOpyi8Q6gRDtszy48vCp5IRgVJ +bnB1dMmPmqnDBksa/f1nG5e8Kvz+TGiPH3PILetxb+dpmyKvCEFzc2V0VGFn0w5E +BjhPjxHdHQFcDchHkhbOhJSUtO4S5yOMJtbNl9IcT3V0cHV0QXNzaWdubWVudFJl +dmVhbGVkRGF0YdXukg5JiLNp8WpT0QdK+7Uj+MdScR77Nj1WWQXh5BXLD0dsb2Jh +bFN0YXRlVHlwZdqbURNYFlZ2kIf7meVWlHI2gNc5DAahzCSYLAVk98zVA0Zmdt2F +ZgBvYWQRFO6O8deq+AmzGiSwqiepm7Iw6KrPKUdNCVZhbGVuY2llc9/PqZH1h+VN +RLcBFVwAKfyVa5vxzalBjmM4lqrAKoPoCFJlZGVlbWVk4VJZ7SCn5tO9rYWViW+y +78lyU2N4jndHTzX4yr9LTgsZT3V0cHV0QXNzaWdubWVudFZvaWRTdGF0ZeLEXUw5 +ZnoaK3B8KU2kyaZqt4FgMJavA3pDbbik/KhLIEFzc2lnblJldmVhbGVkRGF0YUJs +aW5kU2VhbFR4UHRy6ta3unkK4FGpfiwZ+PwhnMW+Kp3v3/3VY6uveGsbggsNV2l0 +bmVzc0FuY2hvcuvPsLOaQxaXI8eq2caF5CGSqDpmKwPkWV2zu+bybApaH0Fzc2ln +blJldmVhbGVkRGF0YUJsaW5kU2VhbFR4aWTsOO+/OuB4belJ1VMJ/9KkdILm1xKd +kR8sJ1uH2XWdRyFBc3NpZ25SZXZlYWxlZFZhbHVlQmxpbmRTZWFsVHhQdHLuDPbv +cwu3Ix7KFJ/LQ3wQWNWsVySNqFCGuJENkk+72B1Bc3NpZ25Wb2lkU3RhdGVCbGlu +ZFNlYWxUeFB0cvQLUtMsPfU23rFs3VZ0PxhF0vvdyPI7Fbn+UJFrnmzBHk91dHB1 +dEFzc2lnbm1lbnRSZXZlYWxlZEF0dGFjaPl4TaC2Q945fB7ZV40zjDfRHMviSsHo +p5pM5NX8GCerBklucHV0c/n0rAhmrkF3ZtT9DBF9BLHZVP0OZ14SO2IE63FP6eVG +DEZ1bmdpYmxlVHlwZfwRT1scTZruXN/EBYr/96CjXZLnYO0YGZg/cS8KJQV8IUFz +c2lnblJldmVhbGVkQXR0YWNoQmxpbmRTZWFsVHhpZPw0Rd1fzw0L/6wVpHq6BSLg +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 -MgIBL2mRQiac2OigKTkCtQVKSqM22KVvhTyl/rTVh83K6w33LL6KL2JDud+var7z -SUU5W4Xj1HG4kj2s3RQW/2+8AAAAAAAAAAD/AAAAAAAAAA1idW5kbGVPcEluZGV4 -AAoCAS9pkUImnNjooCk5ArUFSkqjNtilb4U8pf601YfNyuuVyOZ6HnViX9SWVUJq -ket+QpChb1qY8b5Q97aKJBL3xQHg4829hvZVZ6Z0tj+BcPH41Jc5akEWEqLcqmFS -t+/EJQAAAAAAAAAA////AAAAAAARYW5jaG9yQnVuZGxlSW5kZXgACgIBL2mRQiac -2OigKTkCtQVKSqM22KVvhTyl/rTVh83K6wN7k7U9GoUuB1kBJXfNtkHwCK1O5wBY -YYO1wEq94AJcAsIre2rJPE6lpvv6/FqJcOpuDjlXp/gPPUemc5UoBqyq9aKMORS3 -m+lXdDRSUor88iwX2yNTTVAzwhUGWTBduFcAAAAAAAAAAP///wAAAAAADWNvbnRy -YWN0SW5kZXgACgIBL2mRQiac2OigKTkCtQVKSqM22KVvhTyl/rTVh83K658ILEk6 -yAKiusXd3AsifCCvlNRoxEjPGloh4L3C9ToyAaqlJbnC3NLVb/EtivqaGABnCXAd -o1XE/CclpYW32VAPAAAAAAAAAAD/AAAAAAAAAA10ZXJtaW5hbEluZGV4AAoCwit7 -ask8TqWm+/r8Wolw6m4OOVen+A89R6ZzlSgGrKpoGeu81bMYq5ezmKVLNmXd2qcG -b+jpJOcDYKmUs70GTgIBL2mRQiac2OigKTkCtQVKSqM22KVvhTyl/rTVh83K65MQ -vICxNFqLL8NYUu4PTWjQHbf6NcubZJKrVgma9JqXAAAAAAAAAAD///8AAAAAAAtz -ZWFsU2VjcmV0cwAJAgEvaZFCJpzY6KApOQK1BUpKozbYpW+FPKX+tNWHzcrrTA5m -ElZhWYYrAaikUtKPFKCFum5wG6hAPcxiD+J2regAAAAAAAAAAP///wAAAAAAB1N1 -cHBsSWQFAQAHAABAIAAIVGVybWluYWwGAgVzZWFscwAJAfKBrNHOWmzs0Ji0/qFl -qdcaZrrlQgkU5p+7HChkm2dIAAAAAAAAAAD//wAAAAAAAAJ0eAAEAgAEbm9uZQAA -AAEEc29tZQAFAQL1bBNiI/Y5p0oJk9xHRsn5iqu4g1hdtdkWPxh+xCgaCsV7IZq/ -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 +tCultkB0NqVYPEA1rNiFXEP3LrpkdR5CqRWhPEMRgtX/htUc00Rwo5DhSuygUMw6 +U29I3gRuYW1lAkM0A5TYGzDiUiiQJh7zb/n4qDlRkfULaV4jKh6zt0HIqBTsKLDt +Ve3urlF5pOfln0Cz+VUWEG6bwS5yGr9+VhwIcmVzZXJ2ZWQBP5c6MouYxgL/Jhmf +Rq3RtZBnBBJs7Ui9hNlcqAkN0cwZTmFtZWRGaWVsZEdsb2JhbFN0YXRlVHlwZQYD +AmlkAoDnRG/jOi2nWLW0xLQrpbZAdDalWDxANazYhVxD9y661e6SDkmIs2nxalPR +B0r7tSP4x1JxHvs2PVZZBeHkFcsEbmFtZQJDNAOU2Bsw4lIokCYe82/5+Kg5UZH1 +C2leIyoes7dByKgU7Ciw7VXt7q5ReaTn5Z9As/lVFhBum8Euchq/flYcCHJlc2Vy +dmVkAT+XOjKLmMYC/yYZn0at0bWQZwQSbO1IvYTZXKgJDdHMFU5hbWVkRmllbGRW +YWxlbmN5VHlwZQYDAmlkAoDnRG/jOi2nWLW0xLQrpbZAdDalWDxANazYhVxD9y66 +Ru3mwwgc/bjmV6QqXODGCIR//B++Xna5cXSpyvWJWc4EbmFtZQJDNAOU2Bsw4lIo +kCYe82/5+Kg5UZH1C2leIyoes7dByKgU7Ciw7VXt7q5ReaTn5Z9As/lVFhBum8Eu +chq/flYcCHJlc2VydmVkAT+XOjKLmMYC/yYZn0at0bWQZwQSbO1IvYTZXKgJDdHM +F05hbWVkVHlwZVRyYW5zaXRpb25UeXBlBgMCaWQCgOdEb+M6LadYtbTEtCultkB0 +NqVYPEA1rNiFXEP3Lro0Ug+uE5YaXr0p/BEjP4VO8hA4BH/UBL7foUbZFqUyaARu +YW1lAkM0A5TYGzDiUiiQJh7zb/n4qDlRkfULaV4jKh6zt0HIDRQjZfKkr8YbU23U +Z1VyZiuwcqOQD8mLAGaK2DGA4rAIcmVzZXJ2ZWQBP5c6MouYxgL/JhmfRq3RtZBn +BBJs7Ui9hNlcqAkN0cwKT3duZWRJZmFjZQQGAANhbnkAAAABBnJpZ2h0cwAAAAIG +YW1vdW50AAAAAwdhbnlEYXRhAAAABAlhbnlBdHRhY2gAAAAFBGRhdGEABQECQzQD +lNgbMOJSKJAmHvNv+fioOVGR9QtpXiMqHrO3QchrBKMUnqaVABZnn+8CtKsk9ea3 +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 +KJAmHvNv+fioOVGR9QtpXiMqHrO3QcjampqqKfXsUiaPC1MLDos0/MSt5LN7q24B +vQ1EDWFpIA9UcmFuc2l0aW9uSWZhY2UGCAhvcHRpb25hbAJ7hIA8nvriESWnfCw5 +vHDS/ej5Q64N/Zz05oLtx2bKcGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xL +k/ZNCG1ldGFkYXRhAAQCAARub25lAAAAAQRzb21lAAUBAkM0A5TYGzDiUiiQJh7z +b/n4qDlRkfULaV4jKh6zt0HIawSjFJ6mlQAWZ5/vArSrJPXmt4pkyNnQvWX816NY +To0HZ2xvYmFscwAKAkM0A5TYGzDiUiiQJh7zb/n4qDlRkfULaV4jKh6zt0HIqBTs KLDtVe3urlF5pOfln0Cz+VUWEG6bwS5yGr9+VhwB9a78PJdE55ToYUqLRLW+xomc -jBJGACUsGxZwr3kocJMAAAAAAAAAAP8AAAAAAAAABmVycm9ycwAJAAABAAAAAAAA -AAD/AAAAAAAAABFkZWZhdWx0QXNzaWdubWVudAAEAgAEbm9uZQAAAAEEc29tZQAF -AQJDNAOU2Bsw4lIokCYe82/5+Kg5UZH1C2leIyoes7dByKgU7Ciw7VXt7q5ReaTn -5Z9As/lVFhBum8Euchq/flYcDFZhbGVuY3lJZmFjZQYCCHJlcXVpcmVkAnuEgDye -+uIRJad8LDm8cNL96PlDrg39nPTmgu3HZspwYYYi0Xuu8GYC3+d1yYDgs2tuuugJ -DYB191E77EuT9k0IbXVsdGlwbGUCe4SAPJ764hElp3wsObxw0v3o+UOuDf2c9OaC -7cdmynBhhiLRe67wZgLf53XJgOCza2666AkNgHX3UTvsS5P2TQxWZWxvY2l0eUhp -bnQDBgt1bnNwZWNpZmllZAAGc2VsZG9tDwhlcGlzb2RpYx8HcmVndWxhcj8IZnJl -cXVlbnR/DWhpZ2hGcmVxdWVuY3n/BVZlck5vAwECdjEACFZvdXRTZWFsBgMGbWV0 -aG9kAsIre2rJPE6lpvv6/FqJcOpuDjlXp/gPPUemc5UoBqyq0lIwfH1xkDX3MH7o -KCXsG4EroYfdnZhJi0qNFvpu1UMEdm91dAL1bBNiI/Y5p0oJk9xHRsn5iqu4g1hd -tdkWPxh+xCgaCiHjPkPFqlzyKSdTozjBZ+07Y5xN2c69qY80aRe6yUN1CGJsaW5k -aW5nAAAI +jBJGACUsGxZwr3kocJMAAAAAAAAAAP8AAAAAAAAABmlucHV0cwAKAkM0A5TYGzDi +UiiQJh7zb/n4qDlRkfULaV4jKh6zt0HIqBTsKLDtVe3urlF5pOfln0Cz+VUWEG6b +wS5yGr9+VhwB9a78PJdE55ToYUqLRLW+xomcjBJGACUsGxZwr3kocJMAAAAAAAAA +AP8AAAAAAAAAC2Fzc2lnbm1lbnRzAAoCQzQDlNgbMOJSKJAmHvNv+fioOVGR9Qtp +XiMqHrO3QcioFOwosO1V7e6uUXmk5+WfQLP5VRYQbpvBLnIav35WHAH1rvw8l0Tn +lOhhSotEtb7GiZyMEkYAJSwbFnCveShwkwAAAAAAAAAA/wAAAAAAAAAJdmFsZW5j +aWVzAAoCQzQDlNgbMOJSKJAmHvNv+fioOVGR9QtpXiMqHrO3QcioFOwosO1V7e6u +UXmk5+WfQLP5VRYQbpvBLnIav35WHAH1rvw8l0TnlOhhSotEtb7GiZyMEkYAJSwb +FnCveShwkwAAAAAAAAAA/wAAAAAAAAAGZXJyb3JzAAkAAAEAAAAAAAAAAP8AAAAA +AAAAEWRlZmF1bHRBc3NpZ25tZW50AAQCAARub25lAAAAAQRzb21lAAUBAkM0A5TY +GzDiUiiQJh7zb/n4qDlRkfULaV4jKh6zt0HIqBTsKLDtVe3urlF5pOfln0Cz+VUW +EG6bwS5yGr9+VhwMVmFsZW5jeUlmYWNlBgIIcmVxdWlyZWQCe4SAPJ764hElp3ws +Obxw0v3o+UOuDf2c9OaC7cdmynBhhiLRe67wZgLf53XJgOCza2666AkNgHX3UTvs +S5P2TQhtdWx0aXBsZQJ7hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLtx2bKcGGG +ItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNDFZlbG9jaXR5SGludAMGC3Vu +c3BlY2lmaWVkAAZzZWxkb20PCGVwaXNvZGljHwdyZWd1bGFyPwhmcmVxdWVudH8N +aGlnaEZyZXF1ZW5jef8FVmVyTm8DAQJ2MQAIVm91dFNlYWwGAwZtZXRob2QCoTdx +SVeWCLUi3KoTEAU77h/jYVWNNni3xCLnC/p2/bfSUjB8fXGQNfcwfugoJewbgSuh +h92dmEmLSo0W+m7VQwR2b3V0AvVsE2Ij9jmnSgmT3EdGyfmKq7iDWF212RY/GH7E +KBoKIeM+Q8WqXPIpJ1OjOMFn7TtjnE3Zzr2pjzRpF7rJQ3UIYmxpbmRpbmcAAAg= + -----END STRICT TYPE LIB----- diff --git a/stl/RGBStd@0.1.0.stl b/stl/RGBStd@0.1.0.stl index a5be69741fd2de0413353954e18f654a22be9d7f..57319d0fafaec7f68238a660f890db96800de2c8 100644 GIT binary patch delta 4504 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 ^ ..0xff IfacePair} , supplements {ContractSuppl ^ ..0xff} , assetTags {RGB.AssignmentType -> ^ ..0xff RGB.AssetTag {- urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft -}} - , genesis RGB.Genesis {- urn:ubideco:semid:9tBmxzHdxPM1Hn5g9GgBFHnSoydLtYzgJvuSDy11tSMw#brain-adam-oliver -} + , genesis RGB.Genesis {- urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -} , terminals {RGB.BundleId -> Terminal} - , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:73qjKLqMAqfv8qbNVyXjo4y2CwGWBgLUAWGamQW17ytP#eagle-jamaica-corner -} ^ ..0xffffffff] - , extensions [RGB.Extension {- urn:ubideco:semid:38W6V9XrEWRP4CQBcKVTQxVwMgZGBKYGJJ2DThX73dDq#pioneer-modest-angel -} ^ ..0xffffffff] + , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:3Gvh9PgqTGrHfTt1YzXbMXprZBFo1iCwXAxKTiB4nK9W#robert-thermos-campus -} ^ ..0xffffffff] + , extensions [RGB.Extension {- urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy -} ^ ..0xffffffff] , attachments {RGB.AttachId -> [Byte ^ ..0xffffff]} , signatures {ContentId -> ^ ..0xff ContentSigs} --- urn:ubideco:semid:9oDRVvmicJwtf2JY4FaUqMxJFUek4EJDRAvfG18qhng9#joel-byte-nuclear +-- urn:ubideco:semid:GHVgNXBAdEWmhNfUiJpmwNZJyGZADRUYaGMU2gZTC4F8#baby-justin-edward data Consignmenttrue :: version ContainerVer , transfer Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} , schema RGB.SchemaSchema {- urn:ubideco:semid:26TTgwB87FyCG5CJMPNpdoUdwdcEHd1STgCMiNmGqe8c#antonio-octopus-dexter -} , ifaces {IfaceId -> ^ ..0xff IfacePair} , supplements {ContractSuppl ^ ..0xff} , assetTags {RGB.AssignmentType -> ^ ..0xff RGB.AssetTag {- urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft -}} - , genesis RGB.Genesis {- urn:ubideco:semid:9tBmxzHdxPM1Hn5g9GgBFHnSoydLtYzgJvuSDy11tSMw#brain-adam-oliver -} + , genesis RGB.Genesis {- urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -} , terminals {RGB.BundleId -> Terminal} - , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:73qjKLqMAqfv8qbNVyXjo4y2CwGWBgLUAWGamQW17ytP#eagle-jamaica-corner -} ^ ..0xffffffff] - , extensions [RGB.Extension {- urn:ubideco:semid:38W6V9XrEWRP4CQBcKVTQxVwMgZGBKYGJJ2DThX73dDq#pioneer-modest-angel -} ^ ..0xffffffff] + , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:3Gvh9PgqTGrHfTt1YzXbMXprZBFo1iCwXAxKTiB4nK9W#robert-thermos-campus -} ^ ..0xffffffff] + , extensions [RGB.Extension {- urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy -} ^ ..0xffffffff] , attachments {RGB.AttachId -> [Byte ^ ..0xffffff]} , signatures {ContentId -> ^ ..0xff ContentSigs} -- urn:ubideco:semid:B286kifNGNcJXNxiRq9mSoCerJfsMF5UThZBrBVyYsg2#felix-burger-matrix @@ -221,8 +222,8 @@ data ContentId :: schema RGB.SchemaId {- urn:ubideco:semid:AyzbMn4ux89LLU | suppl SupplId -- urn:ubideco:semid:72v5XvfiTB7HJinscrxy5ZTa4PwubG9YCtkK8JQt7F5B#denver-almanac-cobalt data ContentSigs :: {Cert ^ 1..0xa} --- urn:ubideco:semid:CV8NDWQdTY4hy5edNiHSfNpd2M4RjXuGPjWPv58z8DSn#nurse-chris-song -data ContractIndex :: publicOpouts {RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -} ^ ..0xffffff}, outpointOpouts {RGB.Output -> ^ ..0xffffff {RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -} ^ ..0xffffff}} +-- urn:ubideco:semid:3qoy3xqtwHPREPRqZNYTLmA229jEZvsajfdUH6exRufk#jargon-road-world +data ContractIndex :: publicOpouts {RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -} ^ ..0xffffff}, outpointOpouts {RGB.XchainExplicitSeal -> ^ ..0xffffff {RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -} ^ ..0xffffff}} -- urn:ubideco:semid:3WPrDGfcJCVwN9ZtFUf1w6SqsutP7xuJ8wRdby9VgPHF#slang-mars-belgium data ContractSuppl :: contractId RGB.ContractId {- urn:ubideco:semid:Bho42Xw8wPy2nWxgz6H51rNdBBusaPyrVQT8VypvpZ3w#alarm-danube-vampire -} , ticker TickerSuppl @@ -247,14 +248,14 @@ data GenesisIface :: metadata StrictTypes.SemId {- urn:ubideco:semid:8Ckj2p3 data GlobalIface :: semId StrictTypes.SemId {- urn:ubideco:semid:8Ckj2p3GLKina636pSKJkj7GB6ft8XeoP4jfGkRUNwtp#cargo-plasma-catalog -}? , required Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} , multiple Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} --- urn:ubideco:semid:Dj5dhUz7R5dprigumwNhJVuDkbDuMKKN8GVVgurArJc9#hello-carlo-roger +-- urn:ubideco:semid:FPBT7W1UeWDBdTJrnijHMSmVoFGtugDxKcASXV5KxdcQ#book-moses-noise data Hoard :: schemata {RGB.SchemaId -> ^ ..0xff SchemaIfaces} , ifaces {IfaceId -> ^ ..0xff Iface} - , geneses {RGB.ContractId -> ^ ..0xff RGB.Genesis {- urn:ubideco:semid:9tBmxzHdxPM1Hn5g9GgBFHnSoydLtYzgJvuSDy11tSMw#brain-adam-oliver -}} + , geneses {RGB.ContractId -> ^ ..0xff RGB.Genesis {- urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -}} , suppl {RGB.ContractId -> ^ ..0xff {ContractSuppl ^ ..0xff}} , assetTags {RGB.ContractId -> ^ ..0xff {RGB.AssignmentType -> ^ ..0xff RGB.AssetTag {- urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft -}}} - , bundles {RGB.BundleId -> ^ ..0xffffffff RGB.TransitionBundle {- urn:ubideco:semid:3Cg6aD1To23ZakSmMDxiJYDHkb34cimto3qPLJonMePt#palma-philips-enigma -}} - , extensions {RGB.OpId -> ^ ..0xffffffff RGB.Extension {- urn:ubideco:semid:38W6V9XrEWRP4CQBcKVTQxVwMgZGBKYGJJ2DThX73dDq#pioneer-modest-angel -}} + , bundles {RGB.BundleId -> ^ ..0xffffffff RGB.TransitionBundle {- urn:ubideco:semid:7sa98fnfDHxPwQQt8ErKMotEnzLP7x6mLDWcS1vZGLcy#patent-fashion-between -}} + , extensions {RGB.OpId -> ^ ..0xffffffff RGB.Extension {- urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy -}} , anchors {BPCore.AnchorId -> ^ ..0xffffffff RGB.AnchorMerkleBlock {- urn:ubideco:semid:CwxenMEcD21WWNvFXXw9YvYexCNdiSHVKEe4jn3a6QxR#analog-textile-wave -}} , sigs {ContentId -> ContentSigs} -- urn:ubideco:semid:E3ntt6pxCqzXgJoqcM3471BBPLddnKcEGsdD3mYAkz5h#ibiza-nuclear-vision @@ -328,14 +329,14 @@ data OwnedStateSuppl :: meaning [Unicode ^ ..0xff], velocity VelocityHint data ReservedBytes04 :: [Byte ^ 4] -- urn:ubideco:semid:DTrbbsskfwyT3nspLiicSNA2Yb8vPk9JU9GcpAEVwvWb#lorenzo-strong-golf data SchemaIfaces :: schema RGB.SchemaSchema {- urn:ubideco:semid:26TTgwB87FyCG5CJMPNpdoUdwdcEHd1STgCMiNmGqe8c#antonio-octopus-dexter -}, iimpls {IfaceId -> ^ ..0xff IfaceImpl} --- urn:ubideco:semid:dxDGJVLkY5pySH1VgL9QLwUL45ZPAcLMLxPoU8Dhn4W#episode-aroma-benny +-- urn:ubideco:semid:Fc8eVAWFat3WBtbqTUJ8cV5c7HwERHQLaJuCtGzcFqfD#candid-campus-chariot data Stock :: hoard Hoard - , history {RGB.ContractId -> ^ ..0xff RGB.ContractHistory {- urn:ubideco:semid:wWuMgtgRtxV1sYctSZe4RQ5G9zdWqk913Sw9UBoeEZ1#flame-reverse-deliver -}} + , history {RGB.ContractId -> ^ ..0xff RGB.ContractHistory {- urn:ubideco:semid:54UnScbYJR9wEZ7RxfuUtGMtjmJAxQwWE8s1EnkbyiQc#depend-mercury-concept -}} , bundleOpIndex {RGB.OpId -> ^ ..0xffffff IndexedBundle} , anchorBundleIndex {RGB.BundleId -> ^ ..0xffffff BPCore.AnchorId {- urn:ubideco:semid:HXreMRXsXhE6goE2JsF8g9jy4rZ7p7AEeYmxYgfPF2tN#dinner-single-alarm -}} , contractIndex {RGB.ContractId -> ^ ..0xff ContractIndex} , terminalIndex {BPCore.SecretSeal -> ^ ..0xffffff RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -}} - , sealSecrets {RGB.SealDefinitionBlindSealTxPtr {- urn:ubideco:semid:67tiipcLpwkwMZTZj2rUbqZPTYWVFeCFs3U6rqc3UEdu#delphi-october-gregory -} ^ ..0xffffff} + , sealSecrets {RGB.XchainBlindSealTxPtr {- urn:ubideco:semid:3UQZiL3kChuaakzDdyt8Y2sFAVcxFVaJyTtjNC49D4Df#diamond-order-martin -} ^ ..0xffffff} -- urn:ubideco:semid:7wqgZas6f6Y7jWyDzLNxCeGEM8NXppB1f1gZNvNHJD72#partner-austin-dinner data SupplId :: [Byte ^ 32] -- urn:ubideco:semid:CXGPwRETAtPV783GHQKZmnpvrtbUzELpBP74ScXDBP22#system-billy-polaris From a0be601bb143724567cecf763965e2048575d033 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 15 Dec 2023 14:59:43 +0100 Subject: [PATCH 08/31] containers: rename BatchCloseMethod --- src/containers/partials.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/containers/partials.rs b/src/containers/partials.rs index b8d01669..9c3d417b 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -41,31 +41,31 @@ use crate::LIB_NAME_RGB_STD; serde(crate = "serde_crate", rename_all = "camelCase") )] #[repr(u8)] -pub enum BatchCloseMethods { +pub enum CloseMethodSet { #[strict_type(dumb)] TapretFirst = 0x01, OpretFirst = 0x02, Both = 0x03, } -impl BitOr for BatchCloseMethods { +impl BitOr for CloseMethodSet { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { if self == rhs { self } else { Self::Both } } } -impl BitOrAssign for BatchCloseMethods { +impl BitOrAssign for CloseMethodSet { fn bitor_assign(&mut self, rhs: Self) { *self = self.bitor(rhs); } } -impl From for BatchCloseMethods { +impl From for CloseMethodSet { fn from(seal: OutputSeal) -> Self { seal.method().into() } } -impl From for BatchCloseMethods { +impl From for CloseMethodSet { fn from(method: CloseMethod) -> Self { match method { - CloseMethod::OpretFirst => BatchCloseMethods::OpretFirst, - CloseMethod::TapretFirst => BatchCloseMethods::TapretFirst, + CloseMethod::OpretFirst => CloseMethodSet::OpretFirst, + CloseMethod::TapretFirst => CloseMethodSet::TapretFirst, } } } @@ -82,7 +82,7 @@ pub struct BatchItem { pub id: OpId, pub inputs: Confined, 1, U24>, pub transition: Transition, - pub methods: BatchCloseMethods, + pub methods: CloseMethodSet, } impl StrictDumb for BatchItem { @@ -101,7 +101,7 @@ impl BatchItem { .as_ref() .iter() .map(|seal| seal.method()) - .map(BatchCloseMethods::from) + .map(CloseMethodSet::from) .fold(None, |acc, i| { Some(match acc { None => i, From e50c77a12b4ea1b9c01cc61d3b73531bb65ad49a Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 15 Dec 2023 20:17:04 +0100 Subject: [PATCH 09/31] epic: support for AnchorSets (opret and tapret combined anchors) --- Cargo.lock | 14 +- Cargo.toml | 10 +- src/accessors/bundle.rs | 33 +- src/accessors/merge_reveal.rs | 65 +-- src/containers/consignment.rs | 114 ++--- src/persistence/hoard.rs | 27 +- src/persistence/inventory.rs | 22 +- src/persistence/stash.rs | 10 +- src/persistence/stock.rs | 82 ++-- src/stl/stl.rs | 2 +- stl/RGBStd@0.1.0.sta | 811 +++++++++++++++++----------------- stl/RGBStd@0.1.0.stl | Bin 19823 -> 19981 bytes stl/RGBStd@0.1.0.sty | 68 +-- 13 files changed, 618 insertions(+), 640 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f073fd22..29927cd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,7 +217,7 @@ dependencies = [ [[package]] name = "bp-consensus" version = "0.11.0-beta.2" -source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#59de59f7bfb2f0913a28d08e6e9fc58d22b2c0f9" +source = "git+https://github.com/BP-WG/bp-core?branch=doubleanchors#2d3806295804fc3d50b4259f1d5da41f8cf88356" dependencies = [ "amplify", "chrono", @@ -231,7 +231,7 @@ dependencies = [ [[package]] name = "bp-core" version = "0.11.0-beta.1" -source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#59de59f7bfb2f0913a28d08e6e9fc58d22b2c0f9" +source = "git+https://github.com/BP-WG/bp-core?branch=doubleanchors#2d3806295804fc3d50b4259f1d5da41f8cf88356" dependencies = [ "amplify", "bp-consensus", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "bp-dbc" version = "0.11.0-beta.1" -source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#59de59f7bfb2f0913a28d08e6e9fc58d22b2c0f9" +source = "git+https://github.com/BP-WG/bp-core?branch=doubleanchors#2d3806295804fc3d50b4259f1d5da41f8cf88356" dependencies = [ "amplify", "base85", @@ -272,7 +272,7 @@ dependencies = [ [[package]] name = "bp-seals" version = "0.11.0-beta.1" -source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#59de59f7bfb2f0913a28d08e6e9fc58d22b2c0f9" +source = "git+https://github.com/BP-WG/bp-core?branch=doubleanchors#2d3806295804fc3d50b4259f1d5da41f8cf88356" dependencies = [ "amplify", "baid58", @@ -649,7 +649,7 @@ dependencies = [ [[package]] name = "rgb-core" version = "0.11.0-beta.2" -source = "git+https://github.com/RGB-WG/rgb-core?branch=v0.11#441e27c165607c3e9fee0416acda6e0176888f6a" +source = "git+https://github.com/RGB-WG/rgb-core?branch=validation#5805b2871403af54eae9cfed271679fea6e681ff" dependencies = [ "aluvm", "amplify", @@ -1024,9 +1024,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unsafe-libyaml" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" [[package]] name = "version_check" diff --git a/Cargo.toml b/Cargo.toml index 53288c4f..5d9e2774 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,9 +92,9 @@ wasm-bindgen-test = "0.3" features = [ "all" ] [patch.crates-io] -bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } -bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } -bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } -bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } +bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" } +bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" } +bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" } +bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" } bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } -rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "v0.11" } +rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "validation" } diff --git a/src/accessors/bundle.rs b/src/accessors/bundle.rs index 4a389426..b5230c0a 100644 --- a/src/accessors/bundle.rs +++ b/src/accessors/bundle.rs @@ -40,31 +40,30 @@ pub trait BundleExt { /// /// `true` if the transition was previously concealed; `false` if it was /// already revealed; error if the transition is unrelated to the bundle. - fn reveal_transition(&mut self, transition: &Transition) -> Result; + fn reveal_transition(&mut self, transition: Transition) -> Result; } impl BundleExt for TransitionBundle { fn reveal_seal(&mut self, seal: Xchain) { - for (_, item) in self.keyed_values_mut() { - if let Some(transition) = &mut item.transition { - for (_, assign) in transition.assignments.keyed_values_mut() { - assign.reveal_seal(seal) - } + for (_, transition) in self.known_transitions.keyed_values_mut() { + for (_, assign) in transition.assignments.keyed_values_mut() { + assign.reveal_seal(seal) } } } - fn reveal_transition(&mut self, transition: &Transition) -> Result { - let id = transition.id(); - let item = self - .get_mut(&id) - .ok_or(RevealError::UnrelatedTransition(id))?; - match item.transition { - None => { - item.transition = Some(transition.clone()); - Ok(true) - } - Some(_) => Ok(false), + fn reveal_transition(&mut self, transition: Transition) -> Result { + let opid = transition.id(); + self.input_map + .values() + .find(|id| *id == &opid) + .ok_or(RevealError::UnrelatedTransition(opid))?; + if self.known_transitions.contains_key(&opid) { + return Ok(false); } + self.known_transitions + .insert(opid, transition.clone()) + .expect("same size as input map"); + Ok(true) } } diff --git a/src/accessors/merge_reveal.rs b/src/accessors/merge_reveal.rs index 6cdcb4d2..d365f7da 100644 --- a/src/accessors/merge_reveal.rs +++ b/src/accessors/merge_reveal.rs @@ -26,7 +26,7 @@ use amplify::Wrapper; use bp::dbc::anchor::MergeError; use commit_verify::{mpc, CommitmentId}; use rgb::{ - Anchor, AnchoredBundle, Assign, Assignments, BundleItem, ExposedSeal, ExposedState, Extension, + Anchor, AnchoredBundle, Assign, Assignments, ContractId, ExposedSeal, ExposedState, Extension, Genesis, OpId, Transition, TransitionBundle, TypedAssigns, }; @@ -41,6 +41,15 @@ pub enum MergeRevealError { #[from] #[display(inner)] AnchorMismatch(MergeError), + + /// the merged bundles contain excessive transactions. + ExcessiveTransactions, + + /// contract id provided for the merge-reveal operation doesn't matches + /// multi-protocol commitment. + #[from(mpc::InvalidProof)] + #[from(mpc::LeafNotKnown)] + ContractMismatch, } /// A trait to merge two structures modifying the revealed status @@ -64,6 +73,14 @@ pub trait MergeReveal: Sized { fn merge_reveal(self, other: Self) -> Result; } +pub trait MergeRevealContract: Sized { + fn merge_reveal_contract( + self, + other: Self, + contract_id: ContractId, + ) -> Result; +} + impl MergeReveal for Anchor { fn merge_reveal(self, other: Self) -> Result { Anchor::merge_reveal(self, other).map_err(MergeRevealError::from) @@ -171,41 +188,33 @@ impl MergeReveal for Assignments { } } -impl MergeReveal for BundleItem { - fn merge_reveal(mut self, other: Self) -> Result { - debug_assert_eq!(self.inputs, other.inputs); - match (self.transition, other.transition) { - (Some(op1), Some(op2)) => self.transition = Some(op1.merge_reveal(op2)?), - (None, Some(op)) => self.transition = Some(op), - (me, None) => self.transition = me, - } - Ok(self) - } -} - impl MergeReveal for TransitionBundle { - fn merge_reveal(self, other: Self) -> Result { + fn merge_reveal(mut self, other: Self) -> Result { debug_assert_eq!(self.commitment_id(), other.commitment_id()); - let mut result = BTreeMap::new(); - for (first, second) in self - .into_inner() - .into_iter() - .zip(other.into_inner().into_iter()) + if self.known_transitions.len() + other.known_transitions.len() > self.input_map.len() || + self.known_transitions + .extend(other.known_transitions) + .is_err() { - debug_assert_eq!(first.0, second.0); - result.insert(first.0, first.1.merge_reveal(second.1)?); + return Err(MergeRevealError::ExcessiveTransactions); } - Ok(TransitionBundle::from_inner( - Confined::try_from(result).expect("collection of the same size"), - )) + Ok(self) } } -impl MergeReveal for AnchoredBundle { - fn merge_reveal(self, other: Self) -> Result { +impl MergeRevealContract for AnchoredBundle { + fn merge_reveal_contract( + self, + other: Self, + contract_id: ContractId, + ) -> Result { + let bundle_id = self.bundle_id(); + let anchor1 = self.anchor.into_merkle_block(contract_id, bundle_id)?; + let anchor2 = other.anchor.into_merkle_block(contract_id, bundle_id)?; Ok(AnchoredBundle { - // TODO: uncomment - anchor: self.anchor, //.merge_reveal(other.anchor)?, + anchor: anchor1 + .merge_reveal(anchor2)? + .into_merkle_proof(contract_id)?, bundle: self.bundle.merge_reveal(other.bundle)?, }) } diff --git a/src/containers/consignment.rs b/src/containers/consignment.rs index b5d195a6..af1efd09 100644 --- a/src/containers/consignment.rs +++ b/src/containers/consignment.rs @@ -20,7 +20,8 @@ // limitations under the License. use std::collections::{BTreeMap, BTreeSet}; -use std::{iter, slice}; +use std::rc::Rc; +use std::{iter, vec}; use amplify::confinement::{LargeVec, MediumBlob, SmallOrdMap, TinyOrdMap, TinyOrdSet}; use commit_verify::Conceal; @@ -28,7 +29,7 @@ use rgb::validation::{self, ConsignmentApi}; use rgb::{ AnchoredBundle, AssetTag, AssignmentType, AttachId, BundleId, ContractHistory, ContractId, Extension, Genesis, GraphSeal, OpId, OpRef, Operation, Schema, SchemaId, SecretSeal, SubSchema, - Transition, TransitionBundle, Xchain, + Transition, Xchain, }; use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize}; @@ -159,6 +160,18 @@ impl Consignment { .find(|anchored_bundle| anchored_bundle.bundle.bundle_id() == bundle_id) } + fn transition(&self, opid: OpId) -> Option<&Transition> { + self.bundles + .iter() + .find_map(|ab| ab.bundle.known_transitions.get(&opid)) + } + + fn extension(&self, opid: OpId) -> Option<&Extension> { + self.extensions + .iter() + .find(|&extension| extension.id() == opid) + } + pub fn validation_status(&self) -> Option<&validation::Status> { self.validation_status.as_ref() } @@ -187,25 +200,23 @@ impl Consignment { .collect::>(); let mut ordered_extensions = BTreeMap::new(); for anchored_bundle in &self.bundles { - for item in anchored_bundle.bundle.values() { - if let Some(transition) = &item.transition { - let witness_anchor = resolver.resolve_anchor(&anchored_bundle.anchor)?; - - history.add_transition(transition, witness_anchor); - for (id, used) in &mut extension_idx { - if *used { - continue; - } - for input in &transition.inputs { - if input.prev_out.op == *id { - *used = true; - if let Some(ord) = ordered_extensions.get_mut(id) { - if *ord > witness_anchor { - *ord = witness_anchor; - } - } else { - ordered_extensions.insert(*id, witness_anchor); + for transition in anchored_bundle.bundle.known_transitions.values() { + let witness_anchor = resolver.resolve_anchor(&anchored_bundle.anchor)?; + + history.add_transition(transition, witness_anchor); + for (id, used) in &mut extension_idx { + if *used { + continue; + } + for input in &transition.inputs { + if input.prev_out.op == *id { + *used = true; + if let Some(ord) = ordered_extensions.get_mut(id) { + if *ord > witness_anchor { + *ord = witness_anchor; } + } else { + ordered_extensions.insert(*id, witness_anchor); } } } @@ -248,9 +259,19 @@ impl Consignment { } } +#[derive(Debug)] +pub struct BundleIdIter(vec::IntoIter); + +impl Iterator for BundleIdIter { + type Item = BundleId; + + fn next(&mut self) -> Option { + self.0.next().as_ref().map(AnchoredBundle::bundle_id) + } +} + impl ConsignmentApi for Consignment { - type BundleIter<'container> - = slice::Iter<'container, AnchoredBundle> where Self: 'container; + type Iter<'a> = BundleIdIter; fn schema(&self) -> &SubSchema { &self.schema } @@ -268,23 +289,6 @@ impl ConsignmentApi for Consignment { fn genesis(&self) -> &Genesis { &self.genesis } - fn transition(&self, opid: OpId) -> Option<&Transition> { - for anchored_bundle in &self.bundles { - for (id, item) in anchored_bundle.bundle.iter() { - if *id == opid { - return item.transition.as_ref(); - } - } - } - None - } - - fn extension(&self, opid: OpId) -> Option<&Extension> { - self.extensions - .iter() - .find(|&extension| extension.id() == opid) - } - fn terminals(&self) -> BTreeSet<(BundleId, SecretSeal)> { self.terminals .iter() @@ -297,34 +301,10 @@ impl ConsignmentApi for Consignment { .collect() } - fn anchored_bundles(&self) -> Self::BundleIter<'_> { self.bundles.iter() } - - fn bundle_by_id(&self, bundle_id: BundleId) -> Option<&TransitionBundle> { - self.anchored_bundle(bundle_id).map(|ab| &ab.bundle) - } - - fn op_ids_except(&self, ids: &BTreeSet) -> BTreeSet { - let mut exceptions = BTreeSet::new(); - for anchored_bundle in &self.bundles { - for item in anchored_bundle.bundle.values() { - if let Some(id) = item.transition.as_ref().map(Transition::id) { - if !ids.contains(&id) { - exceptions.insert(id); - } - } - } - } - exceptions - } - - fn has_operation(&self, opid: OpId) -> bool { self.operation(opid).is_some() } + fn bundle_ids<'a>(&self) -> Self::Iter<'a> { BundleIdIter(self.bundles.clone().into_iter()) } - fn known_transitions_by_bundle_id(&self, bundle_id: BundleId) -> Option> { - self.bundle_by_id(bundle_id).map(|bundle| { - bundle - .values() - .filter_map(|item| item.transition.as_ref()) - .collect() - }) + fn anchored_bundle(&self, bundle_id: BundleId) -> Option> { + self.anchored_bundle(bundle_id) + .map(|ab| Rc::new(ab.clone())) } } diff --git a/src/persistence/hoard.rs b/src/persistence/hoard.rs index 64f92fcc..ddeaa6d8 100644 --- a/src/persistence/hoard.rs +++ b/src/persistence/hoard.rs @@ -27,8 +27,8 @@ use amplify::confinement::{Confined, LargeOrdMap, SmallOrdMap, TinyOrdMap, TinyO use bp::dbc::anchor::MergeError; use commit_verify::mpc; use rgb::{ - Anchor, AnchorId, AnchoredBundle, AssetTag, AssignmentType, BundleError, BundleId, ContractId, - Extension, Genesis, OpId, Operation, SchemaId, TransitionBundle, + Anchor, AnchoredBundle, AssetTag, AssignmentType, BundleId, ContractId, Extension, Genesis, + OpId, Operation, SchemaId, TransitionBundle, WitnessId, }; use strict_encoding::TypeName; @@ -47,9 +47,6 @@ pub enum ConsumeError { #[from] Anchor(mpc::InvalidProof), - #[from] - Bundle(BundleError), - #[from] Merge(MergeError), @@ -73,7 +70,7 @@ pub struct Hoard { pub(super) asset_tags: TinyOrdMap>, pub(super) bundles: LargeOrdMap, pub(super) extensions: LargeOrdMap, - pub(super) anchors: LargeOrdMap>, + pub(super) anchors: LargeOrdMap>, pub(super) sigs: SmallOrdMap, } @@ -177,7 +174,7 @@ impl Hoard { for AnchoredBundle { anchor, bundle } in consignment.bundles { let bundle_id = bundle.bundle_id(); - let anchor = anchor.map(|a| a.into_merkle_block(contract_id, bundle_id.into()))?; + let anchor = anchor.into_merkle_block(contract_id, bundle_id.into())?; self.consume_anchor(anchor)?; self.consume_bundle(bundle)?; } @@ -208,11 +205,11 @@ impl Hoard { // TODO: Move into Stash trait and re-implement using trait accessor methods pub fn consume_anchor(&mut self, anchor: Anchor) -> Result<(), ConsumeError> { - let anchor_id = anchor.anchor_id(); - match self.anchors.get_mut(&anchor_id) { + let witness_id = anchor.witness_id(); + match self.anchors.get_mut(&witness_id) { Some(a) => *a = a.clone().merge_reveal(anchor)?, None => { - self.anchors.insert(anchor_id, anchor)?; + self.anchors.insert(witness_id, anchor)?; } } Ok(()) @@ -311,17 +308,13 @@ impl Stash for Hoard { .ok_or(StashInconsistency::OperationAbsent(op_id).into()) } - fn anchor_ids(&self) -> Result, Self::Error> { - Ok(self.anchors.keys().copied().collect()) - } - fn anchor( &self, - anchor_id: AnchorId, + witness_id: WitnessId, ) -> Result<&Anchor, StashError> { self.anchors - .get(&anchor_id) - .ok_or(StashInconsistency::AnchorAbsent(anchor_id).into()) + .get(&witness_id) + .ok_or(StashInconsistency::AnchorAbsent(witness_id).into()) } fn contract_asset_tags( diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index bffc267f..47c3ec9a 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -32,9 +32,9 @@ use chrono::Utc; use commit_verify::{mpc, Conceal}; use invoice::{Beneficiary, InvoiceState, RgbInvoice}; use rgb::{ - validation, Anchor, AnchoredBundle, AssignmentType, BlindingFactor, BundleError, BundleId, - ContractId, ExposedSeal, GraphSeal, OpId, Operation, Opout, OutputSeal, SchemaId, SecretSeal, - SubSchema, Transition, TransitionBundle, WitnessId, Xchain, + validation, Anchor, AnchoredBundle, AssignmentType, BlindingFactor, BundleId, ContractId, + ExposedSeal, GraphSeal, OpId, Operation, Opout, OutputSeal, SchemaId, SecretSeal, SubSchema, + Transition, TransitionBundle, WitnessId, Xchain, }; use strict_encoding::TypeName; @@ -127,7 +127,6 @@ pub enum InventoryError { /// errors during consume operation. // TODO: Make part of connectivity error #[from] - #[from(BundleError)] Consume(ConsumeError), /// error in input data. @@ -353,14 +352,21 @@ pub trait Inventory: Deref { /// /// Must be called before the consignment is created, when witness /// transaction is not yet mined. - fn consume(&mut self, fascia: Fascia) -> Result<(), InventoryError> { + fn consume(&mut self, _fascia: Fascia) -> Result<(), InventoryError> { + todo!(); + /* let witness_id = fascia.anchor.witness_id(); unsafe { self.consume_anchor(fascia.anchor)? }; for bundle in fascia.bundles { - let contract_id = bundle.validate()?; - unsafe { self.consume_bundle(contract_id, bundle, witness_id)? }; + let ids1 = bundle.known_transitions.keys().copied().collect::>(); + let ids2 = bundle.input_map.values().copied().collect::>(); + if !ids1.is_subset(&ids2) { + + } + unsafe { self.consume_bundle(fascia.contract_id, bundle, witness_id)? }; } Ok(()) + */ } #[doc(hidden)] @@ -637,7 +643,7 @@ pub trait Inventory: Deref { .entry(id) .or_insert(self.anchored_bundle(id)?.clone()) .bundle - .reveal_transition(transition)?; + .reveal_transition(transition.clone())?; } let genesis = self.genesis(contract_id)?; diff --git a/src/persistence/stash.rs b/src/persistence/stash.rs index 0e4a6395..fef282c4 100644 --- a/src/persistence/stash.rs +++ b/src/persistence/stash.rs @@ -27,8 +27,8 @@ use std::error::Error; use amplify::confinement::{TinyOrdMap, TinyOrdSet}; use commit_verify::mpc; use rgb::{ - Anchor, AnchorId, AssetTag, AssignmentType, BundleId, ContractId, Extension, Genesis, OpId, - SchemaId, TransitionBundle, + Anchor, AssetTag, AssignmentType, BundleId, ContractId, Extension, Genesis, OpId, SchemaId, + TransitionBundle, WitnessId, }; use strict_encoding::TypeName; @@ -77,7 +77,7 @@ pub enum StashInconsistency { /// /// It may happen due to RGB standard library bug, or indicate internal /// stash inconsistency and compromised stash data storage. - AnchorAbsent(AnchorId), + AnchorAbsent(WitnessId), /// bundle {0} is absent. /// @@ -130,10 +130,8 @@ pub trait Stash { fn extension(&self, op_id: OpId) -> Result<&Extension, StashError>; - fn anchor_ids(&self) -> Result, Self::Error>; - fn anchor( &self, - anchor_id: AnchorId, + witness_id: WitnessId, ) -> Result<&Anchor, StashError>; } diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index 861ff9ad..2161f801 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -24,14 +24,13 @@ use std::convert::Infallible; use std::ops::{Deref, DerefMut}; use amplify::confinement::{MediumOrdMap, MediumOrdSet, TinyOrdMap}; -use amplify::ByteArray; use commit_verify::{mpc, Conceal}; use rgb::validation::{Status, Validity, Warning}; use rgb::{ - validation, Anchor, AnchorId, AnchoredBundle, Assign, AssignmentType, BundleId, - ContractHistory, ContractId, ContractState, ExposedState, Extension, Genesis, GenesisSeal, - GraphSeal, OpId, Operation, Opout, OutputSeal, SecretSeal, SubSchema, Transition, - TransitionBundle, TypedAssigns, WitnessAnchor, WitnessId, Xchain, + validation, Anchor, AnchoredBundle, Assign, AssignmentType, BundleId, ContractHistory, + ContractId, ContractState, ExposedState, Extension, Genesis, GenesisSeal, GraphSeal, OpId, + Operation, Opout, OutputSeal, SecretSeal, SubSchema, Transition, TransitionBundle, + TypedAssigns, WitnessAnchor, WitnessId, Xchain, }; use strict_encoding::{StrictDeserialize, StrictSerialize}; @@ -74,7 +73,7 @@ pub struct Stock { history: TinyOrdMap, // index bundle_op_index: MediumOrdMap, - anchor_bundle_index: MediumOrdMap, + anchor_bundle_index: MediumOrdMap, contract_index: TinyOrdMap, terminal_index: MediumOrdMap, // secrets @@ -176,9 +175,9 @@ impl Stock { } for AnchoredBundle { anchor, bundle } in &mut consignment.bundles { let bundle_id = bundle.bundle_id(); - let anchor_id = anchor.anchor_id(contract_id, bundle_id.into())?; - self.anchor_bundle_index.insert(bundle_id, anchor_id)?; - self.index_bundle(contract_id, bundle, anchor.witness_id())?; + let witness_id = anchor.witness_id(); + self.anchor_bundle_index.insert(bundle_id, witness_id)?; + self.index_bundle(contract_id, bundle, witness_id)?; } self.hoard.consume_consignment(consignment)?; @@ -243,32 +242,22 @@ impl Stock { witness_id: WitnessId, ) -> Result<(), InventoryError<::Error>> { let bundle_id = bundle.bundle_id(); - for (opid, item) in bundle.iter() { - if let Some(transition) = &item.transition { - self.bundle_op_index - .insert(*opid, IndexedBundle(id, bundle_id))?; - for (type_id, assign) in transition.assignments.iter() { - match assign { - TypedAssigns::Declarative(vec) => { - self.index_transition_assignments( - id, vec, *opid, *type_id, witness_id, - )?; - } - TypedAssigns::Fungible(vec) => { - self.index_transition_assignments( - id, vec, *opid, *type_id, witness_id, - )?; - } - TypedAssigns::Structured(vec) => { - self.index_transition_assignments( - id, vec, *opid, *type_id, witness_id, - )?; - } - TypedAssigns::Attachment(vec) => { - self.index_transition_assignments( - id, vec, *opid, *type_id, witness_id, - )?; - } + for (opid, transition) in &bundle.known_transitions { + self.bundle_op_index + .insert(*opid, IndexedBundle(id, bundle_id))?; + for (type_id, assign) in transition.assignments.iter() { + match assign { + TypedAssigns::Declarative(vec) => { + self.index_transition_assignments(id, vec, *opid, *type_id, witness_id)?; + } + TypedAssigns::Fungible(vec) => { + self.index_transition_assignments(id, vec, *opid, *type_id, witness_id)?; + } + TypedAssigns::Structured(vec) => { + self.index_transition_assignments(id, vec, *opid, *type_id, witness_id)?; + } + TypedAssigns::Attachment(vec) => { + self.index_transition_assignments(id, vec, *opid, *type_id, witness_id)?; } } } @@ -477,10 +466,9 @@ impl Inventory for Stock { &mut self, anchor: Anchor, ) -> Result<(), InventoryError> { - let anchor_id = anchor.anchor_id(); - for (_, bundle_id) in anchor.mpc_proof.to_known_message_map() { - self.anchor_bundle_index - .insert(bundle_id.to_byte_array().into(), anchor_id)?; + let witness_id = anchor.witness_id(); + for (bundle_id, _) in anchor.known_bundle_ids() { + self.anchor_bundle_index.insert(bundle_id, witness_id)?; } self.hoard.consume_anchor(anchor)?; Ok(()) @@ -497,11 +485,9 @@ impl Inventory for Stock { .history .get_mut(&contract_id) .ok_or(InventoryInconsistency::StateAbsent(contract_id))?; - for item in bundle.values() { - if let Some(transition) = &item.transition { - let witness_anchor = WitnessAnchor::from_mempool(witness_id); - history.add_transition(transition, witness_anchor); - } + for transition in bundle.known_transitions.values() { + let witness_anchor = WitnessAnchor::from_mempool(witness_id); + history.add_transition(transition, witness_anchor); } self.hoard.consume_bundle(bundle)?; Ok(()) @@ -554,8 +540,10 @@ impl Inventory for Stock { .get(&opid) .ok_or(InventoryInconsistency::BundleAbsent(opid))?; let bundle = self.bundle(*bundle_id)?; - let item = bundle.get(&opid).ok_or(DataError::Concealed)?; - let transition = item.transition.as_ref().ok_or(DataError::Concealed)?; + let transition = bundle + .known_transitions + .get(&opid) + .ok_or(DataError::Concealed)?; Ok(transition) } @@ -572,7 +560,7 @@ impl Inventory for Stock { let bundle = self.bundle(*bundle_id)?.clone(); let anchor = self.anchor(*anchor_id)?; - let anchor = anchor.clone().map(|a| a.into_merkle_proof(*contract_id))?; + let anchor = anchor.to_merkle_proof(*contract_id)?; // TODO: Conceal all transitions except the one we need Ok(AnchoredBundle { anchor, bundle }) diff --git a/src/stl/stl.rs b/src/stl/stl.rs index fbc3caa3..a2d236b7 100644 --- a/src/stl/stl.rs +++ b/src/stl/stl.rs @@ -44,7 +44,7 @@ pub const LIB_ID_RGB_CONTRACT: &str = /// Strict types id for the library representing of RGB StdLib data types. pub const LIB_ID_RGB_STD: &str = - "urn:ubideco:stl:DYA42oBWsH5kKa1dUVwHEEkzEmhDDypKnXWDF1F4RnVK#example-alice-salt"; + "urn:ubideco:stl:E3LrF7ryeC9J8rABKrs1ceRg2skzsjaWVFk9xbi1x56#nobel-origami-tempo"; fn _rgb_std_stl() -> Result { LibBuilder::new(libname!(LIB_NAME_RGB_STD), tiny_bset! { diff --git a/stl/RGBStd@0.1.0.sta b/stl/RGBStd@0.1.0.sta index daf420c6..80aae0ca 100644 --- a/stl/RGBStd@0.1.0.sta +++ b/stl/RGBStd@0.1.0.sta @@ -1,429 +1,432 @@ -----BEGIN STRICT TYPE LIB----- -Id: urn:ubideco:stl:DYA42oBWsH5kKa1dUVwHEEkzEmhDDypKnXWDF1F4RnVK +Id: urn:ubideco:stl:E3LrF7ryeC9J8rABKrs1ceRg2skzsjaWVFk9xbi1x56 Name: RGBStd Dependencies: urn:ubideco:stl:ZtHaBzu9ojbDahaGKEXe5v9DfSDxLERbLkEB23R6Q6V, + urn:ubideco:stl:5Nyd3BhfDAEfc5th2httogefZGzMftfcJNh5Lo9guuTx, urn:ubideco:stl:5XLKQ1sNryZm9bdFKU2kBY3MPYdZXhchVdQKBbHA3gby, urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ, - urn:ubideco:stl:9gBiimtSnE1Qxwa49rbdPpv2s5ggXfitb6aktnunpDjX, - urn:ubideco:stl:BrKg2wSDRFDFSGzn2RrQkbwFeuLw2yDAFtpQ7WRh3nrz, + urn:ubideco:stl:CjHnUSMpAUVxemMGtj8hJtcCssguGxDghjBzfLrUfiMW, urn:ubideco:stl:DVtm25LRKU4TjbyZmVxPhvCmctZ6vKkPKqfpU2QsDNUo, urn:ubideco:stl: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 -BVR4VmVyqYWEd1OeaPuwv+7HmiHEV0PBVPj6vT+Y4NORPee3N3gKSW50ZXJuYWxQ -a7YzCakYv7aSDW7IWKQkhyNGWmk/ckMHv/8d1zpzgU7JB0xlYWZWZXK+/B78ZqUZ -/WRSajoTh0Dn8RAtC77/OsFGTvP3QHZ0XAxTY3JpcHRQdWJrZXnEcmuPjyjResGX -yB2ODaGbYkSc5tBkXf7H6Xg8tYfxowdCeXRlU3RyxXshmr/3OW5yRoCtRVYvfOyh -bG4/Jt3c//x+bAPm3EQCVHjoakDNXCX5veKE/2mlETKnQSshVb0OVhLBv+OElWDF -BghPdXRwb2ludPyipyq+kf7NgqixmJBjIsJOdqqqNfIk0XMFY6AYLohZB1hPbmx5 -UGsMQ29tbWl0VmVyaWZ5BgAv7s8eRNKhKbmKFDhHSzlxlSsoHKIBktUTJviyNmBe -ZwtNZXJrbGVQcm9vZjCVfuYdYTRZuwUI5OGvPWohv9b7+x0xgqd55UV04FaxClBy -b3RvY29sSWQ1N6lRFcjqhdxS96uB8nFlUQUmU5RCV6+JE+h71Jux0wdNZXNzYWdl -U0OVD0QzKiP/6IDPUOfPCAahBM3gSGsT4Qz8GINDsNcIVHJlZU5vZGVVjTcH+EWG -U4DuzEFVJOikmWBR05SCQ/GU9/GRVyPp5gpNZXJrbGVOb2RlxKN7LSxSbrVJWtXZ -ihWIvHNJ7AFaxfUJdqdV7py7D1QLTWVya2xlQmxvY2sDUkdCTwADe5O1PRqFLgdZ -ASV3zbZB8AitTucAWGGDtcBKveACXAhCdW5kbGVJZAeL+m/TAkZzkq3SwOCPOMBF -ZeR4uOTUtZR3Y2uJnI0GElhjaGFpbkV4cGxpY2l0U2VhbA4vvmYUIqM5uwVC9iKT -0KJ+nDHxgv62uras2xV08GJWHEFzc2lnblZvaWRTdGF0ZUJsaW5kU2VhbFR4aWQO -v7jtOGJupIO6NPMU+VR16VbZRzUT3CcYFjxjtuGJsQpXaXRuZXNzUG9zEEGCcjUX -HNmClZKmmRf+A3E/rW/i0yFOiMH+HKOnCp0MU2NoZW1hU2NoZW1hEtAfwys43KLn -UJYp12Wic7iT9zYOl0GE8mG4Ud4CUOcaVHlwZWRBc3NpZ25zQmxpbmRTZWFsVHhQ -dHIZG3iI3J1fGorunfRswvolcEGgvGk2UaeQ7UgGu2keLwlFeHRlbnNpb24aBlQW -1wHBizvL/lyc0UYqVhVEB88o3IpNZg1RAyq0MQZBbmNob3IedhfxJ33bPrvhag9y -Ebdt7VXfb0MNVRFfA3gnpUJXJwlOb2lzZUR1bWIg8lBWIo9mzvyR+upnvF/G8Glc -PUd5c1k/rNE3ynJIZQxSZXZlYWxlZERhdGEhy8u/1VgoMuPOxHhR940ouw4/7maw -X4beHkedGnD0LQ5BbmNob3JlZEJ1bmRsZSRXUthlgPCcymojN1QSRCBgfKMsf+7M -8RQQAG8KCSqMDEFsdExheWVyMVNldCS8sxmIo0c/6KhHAsl+dfw/A63pqq2HlAAw -UGuJa17KFFhjaGFpbkJsaW5kU2VhbFR4UHRyKZZ9nq4XJK6Z2/C/p/re+UZi6T2W -jZ/83r85fZXtnQEiQXNzaWduUmV2ZWFsZWRBdHRhY2hCbGluZFNlYWxUeFB0cisn -FPSGcSb9CAYG3uCo6oUWvK6qH3teWcOK7xHG7ke+CkJ1bmRsZUl0ZW0usqX+F8Aw -RDCY4FybG1ps1h7tdg1h3suAJtNk8xbhuwlWb2lkU3RhdGU0Ug+uE5YaXr0p/BEj -P4VO8hA4BH/UBL7foUbZFqUyaA5UcmFuc2l0aW9uVHlwZTSA6BeXQJfNJZ/lR9Vv -fV/nu0mPz7Y8Ai8GDAEvWZmaB0dlbmVzaXM2wTSh+qCBE6fdMIA8XrDywv3aFLDV -o9aTw26eDzKqeQtPY2N1cnJlbmNlczjKFOCFIsfjOYJGlLLDmVh1U6boygwO4eiV -ibqJdxvzC1N0YXRlU2NoZW1hPFLnrq+zlEL19wGQ1AoMvppMckAB/YH3oYJqXfsO -YJ0PQ29udHJhY3RIaXN0b3J5Pb9W0u6NtDDqL2vzu2puscG5UUq8OWWndY+AWAec -j7oGU2NoZW1hP6Xk/XbnoyVMKK58A3Bboboij3LYxcT6MOGKHPKwSRogQXNzaWdu -UmV2ZWFsZWRWYWx1ZUJsaW5kU2VhbFR4aWRCMGGFiMjUqxQmQMf9yRcszdD/EP8N -k4AARHyImt3MeQlNZWRpYVR5cGVFKqVffdYBSouhbcRmMrYP8bVs3DpTLs+9a5PV -ZxmeiQxSZXNlcnZlZEJ5dGVGNH2lHu1oDF77by+mxG/p2cNS74mOKbKURqaNxqBe -pgxHbG9iYWxWYWx1ZXNG7ebDCBz9uOZXpCpc4MYIhH/8H75edrlxdKnK9YlZzgtW -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 -ZgBvYWQRFO6O8deq+AmzGiSwqiepm7Iw6KrPKUdNCVZhbGVuY2llc9/PqZH1h+VN -RLcBFVwAKfyVa5vxzalBjmM4lqrAKoPoCFJlZGVlbWVk4VJZ7SCn5tO9rYWViW+y -78lyU2N4jndHTzX4yr9LTgsZT3V0cHV0QXNzaWdubWVudFZvaWRTdGF0ZeLEXUw5 -ZnoaK3B8KU2kyaZqt4FgMJavA3pDbbik/KhLIEFzc2lnblJldmVhbGVkRGF0YUJs -aW5kU2VhbFR4UHRy6ta3unkK4FGpfiwZ+PwhnMW+Kp3v3/3VY6uveGsbggsNV2l0 -bmVzc0FuY2hvcuvPsLOaQxaXI8eq2caF5CGSqDpmKwPkWV2zu+bybApaH0Fzc2ln -blJldmVhbGVkRGF0YUJsaW5kU2VhbFR4aWTsOO+/OuB4belJ1VMJ/9KkdILm1xKd -kR8sJ1uH2XWdRyFBc3NpZ25SZXZlYWxlZFZhbHVlQmxpbmRTZWFsVHhQdHLuDPbv -cwu3Ix7KFJ/LQ3wQWNWsVySNqFCGuJENkk+72B1Bc3NpZ25Wb2lkU3RhdGVCbGlu -ZFNlYWxUeFB0cvQLUtMsPfU23rFs3VZ0PxhF0vvdyPI7Fbn+UJFrnmzBHk91dHB1 -dEFzc2lnbm1lbnRSZXZlYWxlZEF0dGFjaPl4TaC2Q945fB7ZV40zjDfRHMviSsHo -p5pM5NX8GCerBklucHV0c/n0rAhmrkF3ZtT9DBF9BLHZVP0OZ14SO2IE63FP6eVG -DEZ1bmdpYmxlVHlwZfwRT1scTZruXN/EBYr/96CjXZLnYO0YGZg/cS8KJQV8IUFz -c2lnblJldmVhbGVkQXR0YWNoQmxpbmRTZWFsVHhpZPw0Rd1fzw0L/6wVpHq6BSLg -W4srGzBlUoeDj3st6449D0NvbmNlYWxlZEF0dGFjaANTdGQEABRNrsgy5sQ3knsl -XrmbQoqH2Q1QqMzQTXIRtbe8X2bOBUFzY2lpIuTglum9fVyG9eHfXXcBav45xzzZ -NIVQlECJjKijeV4CVTVhhiLRe67wZgLf53XJgOCza2666AkNgHX3UTvsS5P2TQRC -b29sco6mipfedCD2KllpsEuHJgS/RdbfOJWcfibnpySQ5K0OQWxwaGFOdW1Mb2Rh -c2gLU3RyaWN0VHlwZXMSAA0UI2XypK/GG1Nt1GdVcmYrsHKjkA/JiwBmitgxgOKw -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 ++upnvF/G8GlcPUd5c1k/rNE3ynJIZQxSZXZlYWxlZERhdGEkV1LYZYDwnMpqIzdU +EkQgYHyjLH/uzPEUEABvCgkqjAxBbHRMYXllcjFTZXQkvLMZiKNHP+ioRwLJfnX8 +PwOt6aqth5QAMFBriWteyhRYY2hhaW5CbGluZFNlYWxUeFB0cimWfZ6uFySumdvw +v6f63vlGYuk9lo2f/N6/OX2V7Z0BIkFzc2lnblJldmVhbGVkQXR0YWNoQmxpbmRT +ZWFsVHhQdHIusqX+F8AwRDCY4FybG1ps1h7tdg1h3suAJtNk8xbhuwlWb2lkU3Rh +dGUwgEw6iTEH29grmqM+pjbEpOsNCUKUYovL9AfXtL+NjQlBbmNob3JTZXQ0Ug+u +E5YaXr0p/BEjP4VO8hA4BH/UBL7foUbZFqUyaA5UcmFuc2l0aW9uVHlwZTSA6BeX +QJfNJZ/lR9VvfV/nu0mPz7Y8Ai8GDAEvWZmaB0dlbmVzaXM2wTSh+qCBE6fdMIA8 +XrDywv3aFLDVo9aTw26eDzKqeQtPY2N1cnJlbmNlczjKFOCFIsfjOYJGlLLDmVh1 +U6boygwO4eiVibqJdxvzC1N0YXRlU2NoZW1hPFLnrq+zlEL19wGQ1AoMvppMckAB +/YH3oYJqXfsOYJ0PQ29udHJhY3RIaXN0b3J5Pb9W0u6NtDDqL2vzu2puscG5UUq8 +OWWndY+AWAecj7oGU2NoZW1hP6Xk/XbnoyVMKK58A3Bboboij3LYxcT6MOGKHPKw +SRogQXNzaWduUmV2ZWFsZWRWYWx1ZUJsaW5kU2VhbFR4aWRCMGGFiMjUqxQmQMf9 +yRcszdD/EP8Nk4AARHyImt3MeQlNZWRpYVR5cGVFKqVffdYBSouhbcRmMrYP8bVs +3DpTLs+9a5PVZxmeiQxSZXNlcnZlZEJ5dGVGNH2lHu1oDF77by+mxG/p2cNS74mO +KbKURqaNxqBepgxHbG9iYWxWYWx1ZXNG7ebDCBz9uOZXpCpc4MYIhH/8H75edrlx +dKnK9YlZzgtWYWxlbmN5VHlwZUi9Gm4X+4Y7Fnx+JV41Z9uCQ+8qXrrrosUKzQmu +nlEaElBlZGVyc2VuQ29tbWl0bWVudFK/rBG0XaXChTXqDcbShj4Hjo1oQYhGUhOn +eUb9oDn2GUFzc2lnbm1lbnRzQmxpbmRTZWFsVHhQdHJfVlljb8tZ19U4VCAUF1/4 +byMGQCaxJ9JdslGChgurrBhBc3NpZ25tZW50c0JsaW5kU2VhbFR4aWRkdR5CqRWh +PEMRgtX/htUc00Rwo5DhSuygUMw6U29I3g1FeHRlbnNpb25UeXBlZrSXhI/EeTlg +3zSS63YSkLMOrBDRPTdULVJkEQiUUHEPRXh0ZW5zaW9uU2NoZW1haFM0IAzcU8ZN +duK2LqpmL9o62ZG/BqspDrzQ9noDSswOUmV2ZWFsZWRBdHRhY2htRMb0L2oFOkMV +D6bmwizE6HrlLJT4BtQMpdyH9nWNoBBSZXZlYWxlZEZ1bmdpYmxlcA1lELKCgW8f +tz3lEmOTA1cIpVJhrQLg1L5axc+n6gkNQ29uY2VhbGVkRGF0YXr1eQ2MbiC0ffAf +9c2FafXLvPEQwa9lbLWQopa0q/8BCUdsb2JhbE9yZIRxDZMsTvTDtwhLaYuwh3Ap +fjlkJH9Fkdjag23Rfbo4CEF0dGFjaElkhZvtqANQXFYRvbbnJfZzRFupMPkndqJn +j7pb75O6IPEUQW5jaG9yU2V0TWVya2xlQmxvY2uFuPgru/Skpg2zvz9FuA+UbniD +w61SbZP0b6MBqG5H2g5CbGluZGluZ0ZhY3Rvcof+4mVYiGzoHL6GhLN5YycTZYPF +tmgBXosUFjaxRIe5DkFzc2lnbm1lbnRUeXBljMHTahxhpKx5E26j32H/KIqvL6Cx +lU3Fb9Sp8fdZP1sZVHlwZWRBc3NpZ25zQmxpbmRTZWFsVHhpZJMQvICxNFqLL8NY +Uu4PTWjQHbf6NcubZJKrVgma9JqXBU9wb3V0lFLT2wOrq6hRn6f2PtAU69RNfTE/ +/P4A+l0kelQEkBAIU2NoZW1hSWSVyOZ6HnViX9SWVUJqket+QpChb1qY8b5Q97aK +JBL3xQRPcElknwgsSTrIAqK6xd3cCyJ8IK+U1GjESM8aWiHgvcL1OjIKQ29udHJh +Y3RJZKIzyegoTsx1mPwGOec00MsCjEss3ISRPpnZqkY+JNZSC0dsb2JhbFN0YXRl +ovrqnnBcnJHM291G7Y9w5Y71FIM+yD5cZLVqW8NTrbAJQWx1U2NyaXB0powwkUdL +yjrCYozHlUSNLN2qxRSfBTOVq/Y8VWEHFyYNRnVuZ2libGVTdGF0ZahYa/iRZdrC +IxBtvYXEhsk35rBm6wxmQf8WL2iod6WYClJhbmdlUHJvb2arQv8KxJBqRRD4VF8G +JCpNCYyQVViz09s3LHCMWeBMQw1HZW5lc2lzU2NoZW1huG3OmybYc+Hqt2pqvyux +lcm1PtVUD9Go2wNHNQT3ZWgdT3V0cHV0QXNzaWdubWVudFJldmVhbGVkVmFsdWW9 +x294yj3l3348qpT0UZScLRU1h4Bde8ropFqLSfEBfxBUcmFuc2l0aW9uU2NoZW1h +vrXFo5ePt+xKFxmo8wlgn/amZxJo/dVI5RMfKFB9yFcKVHJhbnNpdGlvbsDiCi/T +yDEa1of6l+qO6dsgkyFvnL2jXCc6qC4zxhzPE1hjaGFpbkJsaW5kU2VhbFR4aWTC +HuzcANO+4uuPAptyOvuxEMA2l+nPA2gZlBWmBAdpygpXaXRuZXNzT3JkwlGtZel1 +DayaElnMwIUkXNX3sW9S2HI2RizxdYyfTl0RQ29uY2VhbGVkRnVuZ2libGXEoAqe +4QkCE3ciARVZO4PDU+Jg+IEQ5LM2yuNoRkEnGwlXaXRuZXNzSWTGGGN7Z00MtLyp +wENdfzJig5h4c3QnQ9E35UT7uhLQTAZTY3JpcHTHmKbYYzZ4RB7aUW4sPou/Dqiw +SoDBtx256XLmP3HmJBFHbG9iYWxTdGF0ZVNjaGVtYchrgnsLWn3GkQQkcuUiJ4/Q +z8UaV2igSz+qMUAbPH4jCUFsdExheWVyMclCQiLtAr5Haf1PIx2zRU6nKLxDqBEO +2zPLjy8KnkhGBUlucHV0yY+aqcMGSxr9/Wcbl7wq/P5MaI8fc8gt63Fv52mbIq8I +QXNzZXRUYWfTDkQGOE+PEd0dAVwNyEeSFs6ElJS07hLnI4wm1s2X0hxPdXRwdXRB +c3NpZ25tZW50UmV2ZWFsZWREYXRh1a9+uW/fOXBOxLEZSIH6ZzopccDAQWLXok70 +CqaQn/cQVHJhbnNpdGlvbkJ1bmRsZdXukg5JiLNp8WpT0QdK+7Uj+MdScR77Nj1W +WQXh5BXLD0dsb2JhbFN0YXRlVHlwZdqbURNYFlZ2kIf7meVWlHI2gNc5DAahzCSY +LAVk98zVA0Zmdt2FZgBvYWQRFO6O8deq+AmzGiSwqiepm7Iw6KrPKUdNCVZhbGVu +Y2llc9/PqZH1h+VNRLcBFVwAKfyVa5vxzalBjmM4lqrAKoPoCFJlZGVlbWVk4VJZ +7SCn5tO9rYWViW+y78lyU2N4jndHTzX4yr9LTgsZT3V0cHV0QXNzaWdubWVudFZv +aWRTdGF0ZeLEXUw5ZnoaK3B8KU2kyaZqt4FgMJavA3pDbbik/KhLIEFzc2lnblJl +dmVhbGVkRGF0YUJsaW5kU2VhbFR4UHRy6ta3unkK4FGpfiwZ+PwhnMW+Kp3v3/3V +Y6uveGsbggsNV2l0bmVzc0FuY2hvcusK1Q6G0rhauKzgdMGSqgLjllvz2Y/z+RPc +mPa0aCMvEUFuY2hvck1lcmtsZUJsb2Nr68+ws5pDFpcjx6rZxoXkIZKoOmYrA+RZ +XbO75vJsClofQXNzaWduUmV2ZWFsZWREYXRhQmxpbmRTZWFsVHhpZOw477864Hht +6UnVUwn/0qR0gubXEp2RHywnW4fZdZ1HIUFzc2lnblJldmVhbGVkVmFsdWVCbGlu +ZFNlYWxUeFB0cu4M9u9zC7cjHsoUn8tDfBBY1axXJI2oUIa4kQ2ST7vYHUFzc2ln +blZvaWRTdGF0ZUJsaW5kU2VhbFR4UHRy9AtS0yw99TbesWzdVnQ/GEXS+93I8jsV +uf5QkWuebMEeT3V0cHV0QXNzaWdubWVudFJldmVhbGVkQXR0YWNo+XhNoLZD3jl8 +HtlXjTOMN9Ecy+JKweinmkzk1fwYJ6sGSW5wdXRz+fSsCGauQXdm1P0MEX0EsdlU +/Q5nXhI7YgTrcU/p5UYMRnVuZ2libGVUeXBl/BFPWxxNmu5c38QFiv/3oKNdkudg +7RgZmD9xLwolBXwhQXNzaWduUmV2ZWFsZWRBdHRhY2hCbGluZFNlYWxUeGlk/DRF +3V/PDQv/rBWkeroFIuBbiysbMGVSh4OPey3rjj0PQ29uY2VhbGVkQXR0YWNoA1N0 +ZAQAFE2uyDLmxDeSeyVeuZtCiofZDVCozNBNchG1t7xfZs4FQXNjaWki5OCW6b19 +XIb14d9ddwFq/jnHPNk0hVCUQImMqKN5XgJVNWGGItF7rvBmAt/ndcmA4LNrbrro +CQ2AdfdRO+xLk/ZNBEJvb2xyjqaKl950IPYqWWmwS4cmBL9F1t84lZx+JuenJJDk +rQ5BbHBoYU51bUxvZGFzaAtTdHJpY3RUeXBlcxIADRQjZfKkr8YbU23UZ1VyZiuw +cqOQD8mLAGaK2DGA4rAIVHlwZU5hbWUkY9q/fErx6pEg77+AwseJoVeS1PH7oyxY +mCOxD2kh+glQcmltaXRpdmUo1blYUNwtVYzQKCXTkW7dbtzyFz3KQHmB1yG2/wQH +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 +UwsOizT8xK3ks3urbgG9DUQNYWkgD1RyYW5zaXRpb25JZmFjZQYICG9wdGlvbmFs +AnuEgDye+uIRJad8LDm8cNL96PlDrg39nPTmgu3HZspwYYYi0Xuu8GYC3+d1yYDg +s2tuuugJDYB191E77EuT9k0IbWV0YWRhdGEABAIABG5vbmUAAAABBHNvbWUABQEC +QzQDlNgbMOJSKJAmHvNv+fioOVGR9QtpXiMqHrO3QchrBKMUnqaVABZnn+8CtKsk +9ea3imTI2dC9ZfzXo1hOjQdnbG9iYWxzAAoCQzQDlNgbMOJSKJAmHvNv+fioOVGR +9QtpXiMqHrO3QcioFOwosO1V7e6uUXmk5+WfQLP5VRYQbpvBLnIav35WHAH1rvw8 +l0TnlOhhSotEtb7GiZyMEkYAJSwbFnCveShwkwAAAAAAAAAA/wAAAAAAAAAGaW5w +dXRzAAoCQzQDlNgbMOJSKJAmHvNv+fioOVGR9QtpXiMqHrO3QcioFOwosO1V7e6u UXmk5+WfQLP5VRYQbpvBLnIav35WHAH1rvw8l0TnlOhhSotEtb7GiZyMEkYAJSwb -FnCveShwkwAAAAAAAAAA/wAAAAAAAAAGZXJyb3JzAAkAAAEAAAAAAAAAAP8AAAAA -AAAAEWRlZmF1bHRBc3NpZ25tZW50AAQCAARub25lAAAAAQRzb21lAAUBAkM0A5TY -GzDiUiiQJh7zb/n4qDlRkfULaV4jKh6zt0HIqBTsKLDtVe3urlF5pOfln0Cz+VUW -EG6bwS5yGr9+VhwMVmFsZW5jeUlmYWNlBgIIcmVxdWlyZWQCe4SAPJ764hElp3ws -Obxw0v3o+UOuDf2c9OaC7cdmynBhhiLRe67wZgLf53XJgOCza2666AkNgHX3UTvs -S5P2TQhtdWx0aXBsZQJ7hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLtx2bKcGGG -ItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNDFZlbG9jaXR5SGludAMGC3Vu -c3BlY2lmaWVkAAZzZWxkb20PCGVwaXNvZGljHwdyZWd1bGFyPwhmcmVxdWVudH8N -aGlnaEZyZXF1ZW5jef8FVmVyTm8DAQJ2MQAIVm91dFNlYWwGAwZtZXRob2QCoTdx -SVeWCLUi3KoTEAU77h/jYVWNNni3xCLnC/p2/bfSUjB8fXGQNfcwfugoJewbgSuh -h92dmEmLSo0W+m7VQwR2b3V0AvVsE2Ij9jmnSgmT3EdGyfmKq7iDWF212RY/GH7E -KBoKIeM+Q8WqXPIpJ1OjOMFn7TtjnE3Zzr2pjzRpF7rJQ3UIYmxpbmRpbmcAAAg= - +FnCveShwkwAAAAAAAAAA/wAAAAAAAAALYXNzaWdubWVudHMACgJDNAOU2Bsw4lIo +kCYe82/5+Kg5UZH1C2leIyoes7dByKgU7Ciw7VXt7q5ReaTn5Z9As/lVFhBum8Eu +chq/flYcAfWu/DyXROeU6GFKi0S1vsaJnIwSRgAlLBsWcK95KHCTAAAAAAAAAAD/ +AAAAAAAAAAl2YWxlbmNpZXMACgJDNAOU2Bsw4lIokCYe82/5+Kg5UZH1C2leIyoe +s7dByKgU7Ciw7VXt7q5ReaTn5Z9As/lVFhBum8Euchq/flYcAfWu/DyXROeU6GFK +i0S1vsaJnIwSRgAlLBsWcK95KHCTAAAAAAAAAAD/AAAAAAAAAAZlcnJvcnMACQAA +AQAAAAAAAAAA/wAAAAAAAAARZGVmYXVsdEFzc2lnbm1lbnQABAIABG5vbmUAAAAB +BHNvbWUABQECQzQDlNgbMOJSKJAmHvNv+fioOVGR9QtpXiMqHrO3QcioFOwosO1V +7e6uUXmk5+WfQLP5VRYQbpvBLnIav35WHAxWYWxlbmN5SWZhY2UGAghyZXF1aXJl +ZAJ7hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLtx2bKcGGGItF7rvBmAt/ndcmA +4LNrbrroCQ2AdfdRO+xLk/ZNCG11bHRpcGxlAnuEgDye+uIRJad8LDm8cNL96PlD +rg39nPTmgu3HZspwYYYi0Xuu8GYC3+d1yYDgs2tuuugJDYB191E77EuT9k0MVmVs +b2NpdHlIaW50AwYLdW5zcGVjaWZpZWQABnNlbGRvbQ8IZXBpc29kaWMfB3JlZ3Vs +YXI/CGZyZXF1ZW50fw1oaWdoRnJlcXVlbmN5/wVWZXJObwMBAnYxAAhWb3V0U2Vh +bAYDBm1ldGhvZAJBD/d2QIYSU77AjC52Xwu6XWojk0H9GfxAPSJfiKVy+9JSMHx9 +cZA19zB+6Cgl7BuBK6GH3Z2YSYtKjRb6btVDBHZvdXQC9WwTYiP2OadKCZPcR0bJ ++YqruINYXbXZFj8YfsQoGgoh4z5Dxapc8iknU6M4wWftO2OcTdnOvamPNGkXuslD +dQhibGluZGluZwAACA== -----END STRICT TYPE LIB----- diff --git a/stl/RGBStd@0.1.0.stl b/stl/RGBStd@0.1.0.stl index 57319d0fafaec7f68238a660f890db96800de2c8..4a37044bea22fc9938968dfef64d270789950df4 100644 GIT binary patch delta 3299 zcmaDqi?MeO;{;Q6NB-|+4sAlg`wsNzmBn-Kip^44Hg*wjc0`^nH{YC|!jdH?>^0lMdO8;GT$vFKMbc6hm9{mh&v!nHdtsqPO@yr!h|Hh%{j#NU%ADl2Rb<1k~?mwc$VJ!Gfe zMNO}G`LA2=o;}WR=JU75)i$0BkKC$`{BoBMq9HZKsWdMoCw1~>&hW{WTuUc&a%*WB zH27F`8nWNMp*?G{-7>QyOJ4JGI!#IHKK+IL`j-8@y_^ue!Ko#a^&Av72Xn7s+HAll z$Y?VA?F#0Am@vV;+n%d_D|U%qY4B6MY*BjuuITrZcPV@nf$M`rF4(i19r#6T~v=DY#7LSxf&sa6+YC6Cq_}w=1rQXi6{v`;kQ?RI$`5@TEeVMX%{l?f{%vhXpOd`(k8n>`6!B@{Do5%vqPzHua|>MDVP9azrD z5%zXpr!CM$_cJDY{9GKr`~W?RoD>~!K%7%)~QZ?};r zY}`W)g~?*JeS{TW;8mFX#C8H!7C_5*A cbDKgqP*%C;5LN^%+ct-Lw6PGj_peV800oUh4FCWD delta 3198 zcmeC3!}xv{;{?;m8!Z$j));se}jPrNB zUCcr5P7BQoJ;SGQY*o6mN?3r^`knma#L!-|itR^~o^$^y`@5aZDZn|uD0Sk6HH>_d zYZ>=W-ppjA>s%NyHEeR^Jsoe~!^K~`%zE-pr)M0EeXPF9_`dbl4|fC|^O7_2i+ody zvU5@=b2A%Fu4Gqvyu}IzvE5V^mq2nAA8C{(RCAzpWhX8(@4$lr7RAc1Jh|#5GKwDVvj6Y*?85875C~QP}*P!;Dc~iY-L! zI^)4^>(l>Y=3I2s3KMlrXO?8<=Ye%^PU911+`Nuon8|2k%Z;es_~!lIC-?SO zuRlEH@~e{xxtYtdPE;&;ChEUg2{Z6E9~4Ywn(XNzzqw4rka6=((R-{4gwoCC>(W9@ z1XWE=R60&r(FZw2!io&#EeR_+&8;wbl{_zDg}}(4?5|?uK**bf;(KzBv5XQ26Cq|NW<*YGOn0!R-8DSIi zH4G;IQWqku@B@c}G6N^!G?cFqL)b8o^SCub2rK*`r!aZGCgD^$8KTf=bAZ-q!o|X5 zP&h^#OebtV(4mTi{S3;`#fFl^7*{}8k&TfuVMRoiMh25V7)cX0ZGwxU1EJC(jX+@! zEM(*e+jPp9h*ET;g27~K6YhFKRX(Bmlwi!^uZ5U4D{Ve*vXXFm+C0IklZZ0W(q=Z{ z03t5Mfog_OTY199f%3%`+djeyE%_8CH`z@ftkA?AH-gwE2v~aw0-5 zQAug?R;LW_^426(_IXyB5)C1WL5s{crFd0ONlLB$VA!;pVR%Z7hWCo$Xr$005!G{{R30 diff --git a/stl/RGBStd@0.1.0.sty b/stl/RGBStd@0.1.0.sty index 8040f2bb..82f49fb2 100644 --- a/stl/RGBStd@0.1.0.sty +++ b/stl/RGBStd@0.1.0.sty @@ -1,5 +1,5 @@ {- - Id: urn:ubideco:stl:DYA42oBWsH5kKa1dUVwHEEkzEmhDDypKnXWDF1F4RnVK#example-alice-salt + Id: urn:ubideco:stl:E3LrF7ryeC9J8rABKrs1ceRg2skzsjaWVFk9xbi1x56#nobel-origami-tempo Name: RGBStd Version: 0.1.0 Description: RGB standard library @@ -19,6 +19,24 @@ import urn:ubideco:stl:ZtHaBzu9ojbDahaGKEXe5v9DfSDxLERbLkEB23R6Q6V#rhino-cover-f -- MerkleNode := urn:ubideco:semid:6kxYeCatpncbA9UiTdsFbxbxJdU56x6MdmTRkEeGAv6R#iceberg-rocket-velvet -- MerkleBlock := urn:ubideco:semid:EEbVZBjaYQWCQA7uRBe8hFkxV6U1uvpH2dT4PafmJ1ko#proxy-catalog-byte +import urn:ubideco:stl:5Nyd3BhfDAEfc5th2httogefZGzMftfcJNh5Lo9guuTx#father-soviet-mercy as BPCore +-- Imports: +-- TapretNodePartner := urn:ubideco:semid:6o6mGBNbDXJCcNgk5ohP6wgXcdXZvYd1ZWy1GMBy5q2#iceberg-poker-active +-- BlindSealTxid := urn:ubideco:semid:q529pAPHhD1aFgueAHy8QtfjUayszR85WgEg7s2a3KE#raymond-reply-phrase +-- TapretProof := urn:ubideco:semid:24LBor5ZVKjGk4uBbEb28w6D7rroV3mpVAntNAWQsYaP#forum-paint-tunnel +-- TapretPathProof := urn:ubideco:semid:2LANtvWZDRes61SHKFxtFPzSuTzaKQGCUvYsNowNf3n3#stage-element-update +-- AnchorMerkleProofTapretProof := urn:ubideco:semid:2RRuXmkGM51gpruJzNCL6mSFm7nQFrP6zXm6CmNr77uE#arcade-modest-fossil +-- AnchorMerkleBlockOpretProof := urn:ubideco:semid:3kBnCxpgfcoZ9sUfZRgFsjxcwUsCb3hVvydCMjr4jpY5#panel-urban-liquid +-- TapretRightBranch := urn:ubideco:semid:4nZtVVw7QJaMDHYffkHBWhxXSkLXLcJ89qTLZH4Z3xck#basket-prelude-bridge +-- OpretProof := urn:ubideco:semid:5oMeVsXXeicrdvrCCEZQiKWcrDHy4nQPbaQGhtnwM3ug#segment-ambient-totem +-- ExplicitSeal := urn:ubideco:semid:6xYzjyQ958BPRsJgBXL4N2DhswvgHWLp769Uzo4ybbUV#turtle-phoenix-panda +-- AnchorMerkleProofOpretProof := urn:ubideco:semid:7mkYAw62Vi8BuyQ16qGg3bgFw8WVtTXUkJoYBv1qoNg4#tape-mental-legacy +-- SecretSeal := urn:ubideco:semid:81NKrdc9pBoBjsKaGBVN9wXLG4tKjkK4f8DLj7TNMZxh#santana-domingo-needle +-- BlindSealTxPtr := urn:ubideco:semid:9XdJg1BFMpMXPfaiw4Te79W2qYgArsEye6XPJUtj31L8#metro-chris-olympic +-- TxPtr := urn:ubideco:semid:CyRtMpPJkKLX3AdhgY7ZyA7PnYAzCo7yFTeYwwGsUBhn#strange-source-father +-- CloseMethod := urn:ubideco:semid:FA1JhsEFKi2LLpuAjuvLA3qiBuEJrwpKyypB9J2aPicr#july-salmon-contact +-- AnchorMerkleBlockTapretProof := urn:ubideco:semid:HqZp5AT9Lz7eC3VE4CykrYmUZ5ZBxDwebTq8tvLGtwhF#mimosa-belgium-eric + import urn:ubideco:stl:5XLKQ1sNryZm9bdFKU2kBY3MPYdZXhchVdQKBbHA3gby#south-strong-welcome as StrictTypes -- Imports: -- TypeName := urn:ubideco:semid:t47Qbd4ggmas4GmrE6oxCSyKBsosvGNtiXc2B2tuvUo#jamaica-capsule-chance @@ -47,8 +65,9 @@ import urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ#justice-rock -- Bool := urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -- AlphaNumLodash := urn:ubideco:semid:8iBe2dh8beD1KUairdqCacEcxAr4h55XfUQN2PspWXjz#north-sound-salsa -import urn:ubideco:stl:9gBiimtSnE1Qxwa49rbdPpv2s5ggXfitb6aktnunpDjX#cosmos-gentle-goblin as RGB +import urn:ubideco:stl:CjHnUSMpAUVxemMGtj8hJtcCssguGxDghjBzfLrUfiMW#action-ribbon-diana as RGB -- Imports: +-- Anchor := urn:ubideco:semid:14qWN39y8UWuqcf23S365d4fWNo9BaKBkjA6VoGzNWud#radical-respond-escape -- BundleId := urn:ubideco:semid:EbWt9bmnjLpAu1LCN78snx734kHLNVUxyb5YxNr8tjd#desert-divide-visible -- XchainExplicitSeal := urn:ubideco:semid:WTenE41RzjcDoZ6DUhdrVV9yYoiVRrG72prS64ePDqP#prefix-halt-service -- AssignVoidStateBlindSealTxid := urn:ubideco:semid:xNw6CKrHd4mLq7UzJuua3Rwt1JZeCEWzVybkRqtKzDT#carol-data-copy @@ -56,15 +75,14 @@ import urn:ubideco:stl:9gBiimtSnE1Qxwa49rbdPpv2s5ggXfitb6aktnunpDjX#cosmos-gentl -- SchemaSchema := urn:ubideco:semid:26TTgwB87FyCG5CJMPNpdoUdwdcEHd1STgCMiNmGqe8c#antonio-octopus-dexter -- TypedAssignsBlindSealTxPtr := urn:ubideco:semid:2GSQTtMuGzoe3q3k4pmatte6ydhu7zonyNQoqseQR5Ga#quasi-natasha-dollar -- Extension := urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy --- Anchor := urn:ubideco:semid:2kbCRxs1hpVo6C9XpXBysmxSyx4HhE6ounqggmvWA1wW#spring-felix-model +-- AnchoredBundle := urn:ubideco:semid:2k2M1VohDqprfbcomFN4v2SkuzkgcuMaF4HG3dCrQNdz#cartel-crimson-appear -- NoiseDumb := urn:ubideco:semid:33ug4TwTBFQxz7D3YdFmwpKET415dv5zQRh5CkavC5fL#deal-orca-aztec -- RevealedData := urn:ubideco:semid:3DcMJ3YRokNwKN8Cce1ZqsYpiTEuto7EY1szCMEDH97A#velvet-david-manual --- AnchoredBundle := urn:ubideco:semid:3Gvh9PgqTGrHfTt1YzXbMXprZBFo1iCwXAxKTiB4nK9W#robert-thermos-campus -- AltLayer1Set := urn:ubideco:semid:3Sruah3S3s7c8XRpD8bN7c8rnSKemwvnxhocQzHy5D9m#manual-cycle-circus -- XchainBlindSealTxPtr := urn:ubideco:semid:3UQZiL3kChuaakzDdyt8Y2sFAVcxFVaJyTtjNC49D4Df#diamond-order-martin -- AssignRevealedAttachBlindSealTxPtr := urn:ubideco:semid:3oLp11hYVBVhVCgj7zVQRtoa7efF6jXhYqXAiPv1ceKa#brigade-rider-albino --- BundleItem := urn:ubideco:semid:3uT6XEmpeo8gDPevzmcaFSd8XzaSdDxN37fagKLDGPau#judge-kermit-welcome -- VoidState := urn:ubideco:semid:49HkbZvGaJE3phHjLBMQCR3NK1sGA462HJr5BkqQ6YQr#nectar-ceramic-driver +-- AnchorSet := urn:ubideco:semid:4GL38RVDhs4JkL5phqyF6pguwL6Av8qTUWCVNfGeWdUg#history-joel-ivory -- TransitionType := urn:ubideco:semid:4XEmzMLZTXc4XB3njvemMq5qdMmx5EKJPAXpJaBPrqCb#puma-joshua-evita -- Genesis := urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -- Occurrences := urn:ubideco:semid:4gjtVBchJQ5f1aAzoyxYWeGp6qZi9dPudJCbWKYKhw1a#unicorn-empire-mama @@ -80,13 +98,13 @@ import urn:ubideco:stl:9gBiimtSnE1Qxwa49rbdPpv2s5ggXfitb6aktnunpDjX#cosmos-gentl -- AssignmentsBlindSealTxPtr := urn:ubideco:semid:6a1wvpZ1TWeAW6fEUAiDEFxmgum7UdCXg4y8pGCew5j3#gamma-crack-tina -- AssignmentsBlindSealTxid := urn:ubideco:semid:7RA6jpEfnmpn4Y2PSPPxzSe4epXHXN2QZ3gteGAKYjby#stop-solar-explain -- ExtensionType := urn:ubideco:semid:7m9MHRdHSXnhYiheDeXybxnHAxPRgs84USnVELFH98Cd#mission-salsa-parole --- TransitionBundle := urn:ubideco:semid:7sa98fnfDHxPwQQt8ErKMotEnzLP7x6mLDWcS1vZGLcy#patent-fashion-between -- ExtensionSchema := urn:ubideco:semid:7uvJVMscXBA6G3cRwnJNeyJpbPPZ5LjR9RheoFjdSrxp#salmon-plato-contour -- RevealedAttach := urn:ubideco:semid:82EzCoBU6LaGX9bVA6WDXYNHmb2tCRZ8HgvxvNKWSGE7#antenna-balloon-manager -- RevealedFungible := urn:ubideco:semid:8MYGRakEEQtank63F1LQPuCW169TwTqBXAexRW7oiLp7#light-sonar-civil -- ConcealedData := urn:ubideco:semid:8YQWVpKJBaYsAwrXvuLDNEDEKRZxfoQJpu1G7X2ZN1tL#mirage-invite-newton -- GlobalOrd := urn:ubideco:semid:9GyqXE6fmewGdUwxz8xiSa8N4ZR6ZaGySoJVFxq5vuVJ#disney-belgium-sunset -- AttachId := urn:ubideco:semid:9uzoSojhudYvNZYgTmJ5sMwwxzLtdLnfUeudT6Ro8i23#delta-member-agenda +-- AnchorSetMerkleBlock := urn:ubideco:semid:9zZ8QKP3o4u5qKz9akLVJo2mG2UGTg7EW1gnGUSdP6tY#tina-forest-radio -- BlindingFactor := urn:ubideco:semid:9zzp5XyDaLvZSGhCEWtey1Y7xdD1soEYdGaimjyZexyf#agenda-ivory-blast -- AssignmentType := urn:ubideco:semid:A9sThAqgwKPfuJcR4GDfTQHUAbbS5sbEXG5XVk7FZHEg#hunter-hello-retro -- TypedAssignsBlindSealTxid := urn:ubideco:semid:AUTVu5drWPTHdN5sDioR2xKCBvCJ3gBgVw57b6nhRxXt#tahiti-adios-ivan @@ -99,7 +117,6 @@ import urn:ubideco:stl:9gBiimtSnE1Qxwa49rbdPpv2s5ggXfitb6aktnunpDjX#cosmos-gentl -- FungibleState := urn:ubideco:semid:CD8fR4UCdn4ZE6Y6bNPFkDpXdMCMH1Y8nVPx7NGqqYHB#natasha-profit-winter -- RangeProof := urn:ubideco:semid:CL9hFCwcjTpaybwDxfj2GzdWkapbgjLkm3fjHEErcFju#teacher-telecom-tuna -- GenesisSchema := urn:ubideco:semid:CXXy9rBURF1Kt8wL698oT6zSaS8WiFXHv2SPCjT7STAA#distant-bravo-athlete --- AnchorMerkleBlock := urn:ubideco:semid:CwxenMEcD21WWNvFXXw9YvYexCNdiSHVKEe4jn3a6QxR#analog-textile-wave -- OutputAssignmentRevealedValue := urn:ubideco:semid:DQw8ZLtqqxYjS3MRbKKfS5dyE7jvJZFo4XkPoHpmyyp3#viking-taxi-mission -- TransitionSchema := urn:ubideco:semid:DmpSKgcAYf3cyTdcaHRQCf92p3hzzcd3dzGSQRFxffLz#minus-bravo-frame -- Transition := urn:ubideco:semid:DqTDv5zeE9gGeFeeCrFrhJs3dFCmyj5pm1MEVGMHQdsU#saddle-cactus-baggage @@ -113,6 +130,7 @@ import urn:ubideco:stl:9gBiimtSnE1Qxwa49rbdPpv2s5ggXfitb6aktnunpDjX#cosmos-gentl -- Input := urn:ubideco:semid:EYdYhKC2ZuJsdZLsjbLcBJLfD8VabLQLgTuycV3b3TeD#carbon-dilemma-fragile -- AssetTag := urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft -- OutputAssignmentRevealedData := urn:ubideco:semid:FCseAzo3QYzXaPnA2Zp5wUWEPjYmkvmtXczb5MvN5DTo#lima-picnic-protect +-- TransitionBundle := urn:ubideco:semid:FP93oW4C5A4gkE17nD4haiUcYscHpJ1zkCh4GW2uA3H8#quality-gemini-susan -- GlobalStateType := urn:ubideco:semid:FQ6qHu9gQzjZu3i7dasU7T1PGi4qZi1a4goxJbHxHkbU#seminar-major-tape -- Ffv := urn:ubideco:semid:FiMEyh3t5FKEsUqVTgQFYJ5XfJF9m2RwKMN9NckympSG#silence-motel-toronto -- Valencies := urn:ubideco:semid:Fuj5cKfnMZ3wbn1bPT3vTa3A5kyj3Rof5ceyyJAZeazQ#lady-paper-anvil @@ -120,6 +138,7 @@ import urn:ubideco:stl:9gBiimtSnE1Qxwa49rbdPpv2s5ggXfitb6aktnunpDjX#cosmos-gentl -- OutputAssignmentVoidState := urn:ubideco:semid:GAZZhtwTL4d97Z9FMDAKsLmquuMcJfZ92UbpG92AsRPp#monica-declare-pump -- AssignRevealedDataBlindSealTxPtr := urn:ubideco:semid:GGCoiNzqtrHTpWdUhJQcFXVNVYTFFJpG5PZ8SuAtuiTt#protect-trident-life -- WitnessAnchor := urn:ubideco:semid:GoiJKnnFwMEfPauHo3j59bKoryXasRmpSVEkfeVsd6yg#canoe-sonar-money +-- AnchorMerkleBlock := urn:ubideco:semid:GpWPZqsCuQeew8qBAAAL6YAFvGsP4B51ubTjZtcjyBfk#opus-ticket-ariel -- AssignRevealedDataBlindSealTxid := urn:ubideco:semid:GsWVVejsUo7AAz8sATamNC6EyPhUSBkvCysEvGH5Defj#donald-genetic-dollar -- AssignRevealedValueBlindSealTxPtr := urn:ubideco:semid:Gu7a9uD8jQzSR3Vz3LhxGfXZLvPZFLUgjNWEu6JUyt8v#garbo-craft-william -- AssignVoidStateBlindSealTxPtr := urn:ubideco:semid:H2FVtLAWGiN4gwiTuSrTMrX7YJ5u5A2RmAGHWUxhpE3R#alert-place-button @@ -129,23 +148,6 @@ import urn:ubideco:stl:9gBiimtSnE1Qxwa49rbdPpv2s5ggXfitb6aktnunpDjX#cosmos-gentl -- AssignRevealedAttachBlindSealTxid := urn:ubideco:semid:Hxy3qQnuCnc1GhDJp1uCJth3vQKXVtPPLpXZfphDChRu#quality-latin-level -- ConcealedAttach := urn:ubideco:semid:HyVyGxhRswAZ3BHJqx6PKmcEGCUSHaL1Rc7qxxi811qE#pizza-natural-cyclone -import urn:ubideco:stl:BrKg2wSDRFDFSGzn2RrQkbwFeuLw2yDAFtpQ7WRh3nrz#dance-swim-liter as BPCore --- Imports: --- TapretNodePartner := urn:ubideco:semid:6o6mGBNbDXJCcNgk5ohP6wgXcdXZvYd1ZWy1GMBy5q2#iceberg-poker-active --- BlindSealTxid := urn:ubideco:semid:q529pAPHhD1aFgueAHy8QtfjUayszR85WgEg7s2a3KE#raymond-reply-phrase --- TapretProof := urn:ubideco:semid:24LBor5ZVKjGk4uBbEb28w6D7rroV3mpVAntNAWQsYaP#forum-paint-tunnel --- TapretPathProof := urn:ubideco:semid:2LANtvWZDRes61SHKFxtFPzSuTzaKQGCUvYsNowNf3n3#stage-element-update --- TapretRightBranch := urn:ubideco:semid:4nZtVVw7QJaMDHYffkHBWhxXSkLXLcJ89qTLZH4Z3xck#basket-prelude-bridge --- AnchorMerkleProof := urn:ubideco:semid:5YGZTLPUCHos5Wg8Gm5cLgMPZijqHQLp8CGTMoXTNPf1#coral-vienna-horizon --- ExplicitSeal := urn:ubideco:semid:6xYzjyQ958BPRsJgBXL4N2DhswvgHWLp769Uzo4ybbUV#turtle-phoenix-panda --- SecretSeal := urn:ubideco:semid:81NKrdc9pBoBjsKaGBVN9wXLG4tKjkK4f8DLj7TNMZxh#santana-domingo-needle --- BlindSealTxPtr := urn:ubideco:semid:9XdJg1BFMpMXPfaiw4Te79W2qYgArsEye6XPJUtj31L8#metro-chris-olympic --- TxPtr := urn:ubideco:semid:CyRtMpPJkKLX3AdhgY7ZyA7PnYAzCo7yFTeYwwGsUBhn#strange-source-father --- CloseMethod := urn:ubideco:semid:FA1JhsEFKi2LLpuAjuvLA3qiBuEJrwpKyypB9J2aPicr#july-salmon-contact --- Proof := urn:ubideco:semid:FJrSpHdBSVvF5B8YAq3vY15Qh2k3NUXvdkt8ESb1hLW7#hello-america-origin --- AnchorMerkleBlock := urn:ubideco:semid:GvQcwTUd1mjGZmPypNsvhjhjc6WcJs63nVwdF8YeBvUj#trade-brother-liquid --- AnchorId := urn:ubideco:semid:HXreMRXsXhE6goE2JsF8g9jy4rZ7p7AEeYmxYgfPF2tN#dinner-single-alarm - import urn:ubideco:stl:DVtm25LRKU4TjbyZmVxPhvCmctZ6vKkPKqfpU2QsDNUo#exodus-axiom-tommy as AluVM -- Imports: -- LibSite := urn:ubideco:semid:8Q9NNyK2PCcjZ7U7rDGUJBhk8q37hAnWLgSizGLmr56g#mission-papa-mercy @@ -185,7 +187,7 @@ data AssignIface :: ownedState OwnedIface , multiple Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} -- urn:ubideco:semid:86jgudF9LyMs3LUmADKSdsoXJ8XC4EinnwSvaGqZPRCK#cipher-cafe-koala data Cert :: signer Identity, signature [Byte ^ ..0xff] --- urn:ubideco:semid:5e4iYtmBz9cfhAzGFksMHVazB7QGcAYTAEvKhXPRvYuJ#jerome-brave-vocal +-- urn:ubideco:semid:6bbrnuw1M34XCKYN1KFh69GKJafHnDUb4MgURg8Y5zJi#burma-planet-elvis data Consignmentfalse :: version ContainerVer , transfer Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} , schema RGB.SchemaSchema {- urn:ubideco:semid:26TTgwB87FyCG5CJMPNpdoUdwdcEHd1STgCMiNmGqe8c#antonio-octopus-dexter -} @@ -194,11 +196,11 @@ data Consignmentfalse :: version ContainerVer , assetTags {RGB.AssignmentType -> ^ ..0xff RGB.AssetTag {- urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft -}} , genesis RGB.Genesis {- urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -} , terminals {RGB.BundleId -> Terminal} - , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:3Gvh9PgqTGrHfTt1YzXbMXprZBFo1iCwXAxKTiB4nK9W#robert-thermos-campus -} ^ ..0xffffffff] + , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:2k2M1VohDqprfbcomFN4v2SkuzkgcuMaF4HG3dCrQNdz#cartel-crimson-appear -} ^ ..0xffffffff] , extensions [RGB.Extension {- urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy -} ^ ..0xffffffff] , attachments {RGB.AttachId -> [Byte ^ ..0xffffff]} , signatures {ContentId -> ^ ..0xff ContentSigs} --- urn:ubideco:semid:GHVgNXBAdEWmhNfUiJpmwNZJyGZADRUYaGMU2gZTC4F8#baby-justin-edward +-- urn:ubideco:semid:25SpzTv8YMS9SXCuRFgqBL5BDHfS8LiG6zQb1tivpope#lake-venice-poker data Consignmenttrue :: version ContainerVer , transfer Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} , schema RGB.SchemaSchema {- urn:ubideco:semid:26TTgwB87FyCG5CJMPNpdoUdwdcEHd1STgCMiNmGqe8c#antonio-octopus-dexter -} @@ -207,7 +209,7 @@ data Consignmenttrue :: version ContainerVer , assetTags {RGB.AssignmentType -> ^ ..0xff RGB.AssetTag {- urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft -}} , genesis RGB.Genesis {- urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -} , terminals {RGB.BundleId -> Terminal} - , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:3Gvh9PgqTGrHfTt1YzXbMXprZBFo1iCwXAxKTiB4nK9W#robert-thermos-campus -} ^ ..0xffffffff] + , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:2k2M1VohDqprfbcomFN4v2SkuzkgcuMaF4HG3dCrQNdz#cartel-crimson-appear -} ^ ..0xffffffff] , extensions [RGB.Extension {- urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy -} ^ ..0xffffffff] , attachments {RGB.AttachId -> [Byte ^ ..0xffffff]} , signatures {ContentId -> ^ ..0xff ContentSigs} @@ -248,15 +250,15 @@ data GenesisIface :: metadata StrictTypes.SemId {- urn:ubideco:semid:8Ckj2p3 data GlobalIface :: semId StrictTypes.SemId {- urn:ubideco:semid:8Ckj2p3GLKina636pSKJkj7GB6ft8XeoP4jfGkRUNwtp#cargo-plasma-catalog -}? , required Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} , multiple Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} --- urn:ubideco:semid:FPBT7W1UeWDBdTJrnijHMSmVoFGtugDxKcASXV5KxdcQ#book-moses-noise +-- urn:ubideco:semid:6E6ifSMiXV7hbDa7GfwnPeLCqWtiFnxyn14WXGx8bDB5#domingo-love-athlete data Hoard :: schemata {RGB.SchemaId -> ^ ..0xff SchemaIfaces} , ifaces {IfaceId -> ^ ..0xff Iface} , geneses {RGB.ContractId -> ^ ..0xff RGB.Genesis {- urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -}} , suppl {RGB.ContractId -> ^ ..0xff {ContractSuppl ^ ..0xff}} , assetTags {RGB.ContractId -> ^ ..0xff {RGB.AssignmentType -> ^ ..0xff RGB.AssetTag {- urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft -}}} - , bundles {RGB.BundleId -> ^ ..0xffffffff RGB.TransitionBundle {- urn:ubideco:semid:7sa98fnfDHxPwQQt8ErKMotEnzLP7x6mLDWcS1vZGLcy#patent-fashion-between -}} + , bundles {RGB.BundleId -> ^ ..0xffffffff RGB.TransitionBundle {- urn:ubideco:semid:FP93oW4C5A4gkE17nD4haiUcYscHpJ1zkCh4GW2uA3H8#quality-gemini-susan -}} , extensions {RGB.OpId -> ^ ..0xffffffff RGB.Extension {- urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy -}} - , anchors {BPCore.AnchorId -> ^ ..0xffffffff RGB.AnchorMerkleBlock {- urn:ubideco:semid:CwxenMEcD21WWNvFXXw9YvYexCNdiSHVKEe4jn3a6QxR#analog-textile-wave -}} + , anchors {RGB.WitnessId -> ^ ..0xffffffff RGB.AnchorMerkleBlock {- urn:ubideco:semid:GpWPZqsCuQeew8qBAAAL6YAFvGsP4B51ubTjZtcjyBfk#opus-ticket-ariel -}} , sigs {ContentId -> ContentSigs} -- urn:ubideco:semid:E3ntt6pxCqzXgJoqcM3471BBPLddnKcEGsdD3mYAkz5h#ibiza-nuclear-vision data IdSuite :: pgp:0 | ssh:1 | ssi:2 @@ -329,11 +331,11 @@ data OwnedStateSuppl :: meaning [Unicode ^ ..0xff], velocity VelocityHint data ReservedBytes04 :: [Byte ^ 4] -- urn:ubideco:semid:DTrbbsskfwyT3nspLiicSNA2Yb8vPk9JU9GcpAEVwvWb#lorenzo-strong-golf data SchemaIfaces :: schema RGB.SchemaSchema {- urn:ubideco:semid:26TTgwB87FyCG5CJMPNpdoUdwdcEHd1STgCMiNmGqe8c#antonio-octopus-dexter -}, iimpls {IfaceId -> ^ ..0xff IfaceImpl} --- urn:ubideco:semid:Fc8eVAWFat3WBtbqTUJ8cV5c7HwERHQLaJuCtGzcFqfD#candid-campus-chariot +-- urn:ubideco:semid:3xZnYdvpMfwenxYBgABq1ig8UvX9FfK4k65Uyob4GQd2#henry-left-static data Stock :: hoard Hoard , history {RGB.ContractId -> ^ ..0xff RGB.ContractHistory {- urn:ubideco:semid:54UnScbYJR9wEZ7RxfuUtGMtjmJAxQwWE8s1EnkbyiQc#depend-mercury-concept -}} , bundleOpIndex {RGB.OpId -> ^ ..0xffffff IndexedBundle} - , anchorBundleIndex {RGB.BundleId -> ^ ..0xffffff BPCore.AnchorId {- urn:ubideco:semid:HXreMRXsXhE6goE2JsF8g9jy4rZ7p7AEeYmxYgfPF2tN#dinner-single-alarm -}} + , anchorBundleIndex {RGB.BundleId -> ^ ..0xffffff RGB.WitnessId {- urn:ubideco:semid:EEYT7goTNgX2nNFoKosg6FKx1CDSyFWHKNK1TRySs6gr#axiom-gyro-album -}} , contractIndex {RGB.ContractId -> ^ ..0xff ContractIndex} , terminalIndex {BPCore.SecretSeal -> ^ ..0xffffff RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -}} , sealSecrets {RGB.XchainBlindSealTxPtr {- urn:ubideco:semid:3UQZiL3kChuaakzDdyt8Y2sFAVcxFVaJyTtjNC49D4Df#diamond-order-martin -} ^ ..0xffffff} From fa304ff7ddfda566b750d9fbb5ed7a2340c380f2 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 15 Dec 2023 20:22:19 +0100 Subject: [PATCH 10/31] accessors: move MergeReveal implementations from core library --- Cargo.lock | 2 +- src/accessors/merge_reveal.rs | 52 +++++++++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29927cd1..d797504e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -649,7 +649,7 @@ dependencies = [ [[package]] name = "rgb-core" version = "0.11.0-beta.2" -source = "git+https://github.com/RGB-WG/rgb-core?branch=validation#5805b2871403af54eae9cfed271679fea6e681ff" +source = "git+https://github.com/RGB-WG/rgb-core?branch=validation#00fdba729ee484584d2f9a1ca54ea5c374dfb933" dependencies = [ "aluvm", "amplify", diff --git a/src/accessors/merge_reveal.rs b/src/accessors/merge_reveal.rs index d365f7da..46718020 100644 --- a/src/accessors/merge_reveal.rs +++ b/src/accessors/merge_reveal.rs @@ -26,8 +26,8 @@ use amplify::Wrapper; use bp::dbc::anchor::MergeError; use commit_verify::{mpc, CommitmentId}; use rgb::{ - Anchor, AnchoredBundle, Assign, Assignments, ContractId, ExposedSeal, ExposedState, Extension, - Genesis, OpId, Transition, TransitionBundle, TypedAssigns, + Anchor, AnchorSet, Assign, Assignments, ExposedSeal, ExposedState, Extension, Genesis, OpId, + Transition, TransitionBundle, TypedAssigns, }; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -73,6 +73,7 @@ pub trait MergeReveal: Sized { fn merge_reveal(self, other: Self) -> Result; } +/* pub trait MergeRevealContract: Sized { fn merge_reveal_contract( self, @@ -80,12 +81,7 @@ pub trait MergeRevealContract: Sized { contract_id: ContractId, ) -> Result; } - -impl MergeReveal for Anchor { - fn merge_reveal(self, other: Self) -> Result { - Anchor::merge_reveal(self, other).map_err(MergeRevealError::from) - } -} + */ impl MergeReveal for Assign { fn merge_reveal(self, other: Self) -> Result { @@ -202,6 +198,7 @@ impl MergeReveal for TransitionBundle { } } +/* impl MergeRevealContract for AnchoredBundle { fn merge_reveal_contract( self, @@ -219,6 +216,45 @@ impl MergeRevealContract for AnchoredBundle { }) } } + */ + +impl MergeReveal for Anchor { + fn merge_reveal(self, other: Self) -> Result { + match (self, other) { + (Anchor::Bitcoin(anchor), Anchor::Bitcoin(other)) => { + anchor.merge_reveal(other).map(Anchor::Bitcoin) + } + (Anchor::Liquid(anchor), Anchor::Liquid(other)) => { + anchor.merge_reveal(other).map(Anchor::Liquid) + } + _ => Err(MergeError::TxidMismatch.into()), + } + } +} + +impl MergeReveal for AnchorSet { + fn merge_reveal(self, other: Self) -> Result { + let (tapret1, opret1) = self.into_split(); + let (tapret2, opret2) = other.into_split(); + + let tapret = match (tapret1, tapret2) { + (Some(tr), None) | (None, Some(tr)) => Some(tr), + (Some(tapret1), Some(tapret2)) => Some(tapret1.merge_reveal(tapret2)?), + (None, None) => None, + }; + let opret = match (opret1, opret2) { + (Some(or), None) | (None, Some(or)) => Some(or), + (Some(opret1), Some(opret2)) => Some(opret1.merge_reveal(opret2)?), + (None, None) => None, + }; + Ok(match (tapret, opret) { + (Some(tapret), None) => Self::Tapret(tapret), + (None, Some(opret)) => Self::Opret(opret), + (Some(tapret), Some(opret)) => Self::Dual { tapret, opret }, + _ => unreachable!(), + }) + } +} impl MergeReveal for Genesis { fn merge_reveal(mut self, other: Self) -> Result { From 5ba136eb7da122b63623cd47f64994727e47ec4d Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 15 Dec 2023 20:30:39 +0100 Subject: [PATCH 11/31] chore: update to recent renames in core with XAnchor and XSeals --- Cargo.lock | 2 +- src/accessors/assignments.rs | 12 ++++++------ src/accessors/bundle.rs | 6 +++--- src/accessors/merge_reveal.rs | 14 +++++++------- src/containers/consignment.rs | 4 ++-- src/containers/partials.rs | 4 ++-- src/containers/seal.rs | 22 ++++++++++------------ src/persistence/hoard.rs | 13 ++++++++----- src/persistence/inventory.rs | 18 +++++++++--------- src/persistence/stash.rs | 6 +++--- src/persistence/stock.rs | 16 ++++++++-------- src/resolvers.rs | 4 ++-- src/stl/stl.rs | 2 +- 13 files changed, 62 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d797504e..b930db2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -649,7 +649,7 @@ dependencies = [ [[package]] name = "rgb-core" version = "0.11.0-beta.2" -source = "git+https://github.com/RGB-WG/rgb-core?branch=validation#00fdba729ee484584d2f9a1ca54ea5c374dfb933" +source = "git+https://github.com/RGB-WG/rgb-core?branch=validation#234a16f7ada64aa942ec83d6cbc2fb4db74329e8" dependencies = [ "aluvm", "amplify", diff --git a/src/accessors/assignments.rs b/src/accessors/assignments.rs index 7a91c456..08ca488b 100644 --- a/src/accessors/assignments.rs +++ b/src/accessors/assignments.rs @@ -23,20 +23,20 @@ use amplify::confinement::SmallVec; use commit_verify::Conceal; use rgb::{ Assign, AssignAttach, AssignData, AssignFungible, AssignRights, ExposedSeal, ExposedState, - TypedAssigns, Xchain, + TypedAssigns, XSeal, }; pub trait TypedAssignsExt { - fn reveal_seal(&mut self, seal: Xchain); + fn reveal_seal(&mut self, seal: XSeal); - fn filter_revealed_seals(&self) -> Vec>; + fn filter_revealed_seals(&self) -> Vec>; } impl TypedAssignsExt for TypedAssigns { - fn reveal_seal(&mut self, seal: Xchain) { + fn reveal_seal(&mut self, seal: XSeal) { fn reveal( vec: &mut SmallVec>, - revealed: Xchain, + revealed: XSeal, ) { for assign in vec.iter_mut() { match assign { @@ -65,7 +65,7 @@ impl TypedAssignsExt for TypedAssigns { } } - fn filter_revealed_seals(&self) -> Vec> { + fn filter_revealed_seals(&self) -> Vec> { match self { TypedAssigns::Declarative(s) => { s.iter().filter_map(AssignRights::revealed_seal).collect() diff --git a/src/accessors/bundle.rs b/src/accessors/bundle.rs index b5230c0a..aba5d9d1 100644 --- a/src/accessors/bundle.rs +++ b/src/accessors/bundle.rs @@ -19,7 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use rgb::{GraphSeal, OpId, Operation, Transition, TransitionBundle, Xchain}; +use rgb::{GraphSeal, OpId, Operation, Transition, TransitionBundle, XSeal}; use crate::accessors::TypedAssignsExt; @@ -32,7 +32,7 @@ pub enum RevealError { pub trait BundleExt { /// Ensures that the seal is revealed inside the bundle. - fn reveal_seal(&mut self, seal: Xchain); + fn reveal_seal(&mut self, seal: XSeal); /// Ensures that the transition is revealed inside the bundle. /// @@ -44,7 +44,7 @@ pub trait BundleExt { } impl BundleExt for TransitionBundle { - fn reveal_seal(&mut self, seal: Xchain) { + fn reveal_seal(&mut self, seal: XSeal) { for (_, transition) in self.known_transitions.keyed_values_mut() { for (_, assign) in transition.assignments.keyed_values_mut() { assign.reveal_seal(seal) diff --git a/src/accessors/merge_reveal.rs b/src/accessors/merge_reveal.rs index 46718020..7a951df3 100644 --- a/src/accessors/merge_reveal.rs +++ b/src/accessors/merge_reveal.rs @@ -26,8 +26,8 @@ use amplify::Wrapper; use bp::dbc::anchor::MergeError; use commit_verify::{mpc, CommitmentId}; use rgb::{ - Anchor, AnchorSet, Assign, Assignments, ExposedSeal, ExposedState, Extension, Genesis, OpId, - Transition, TransitionBundle, TypedAssigns, + AnchorSet, Assign, Assignments, ExposedSeal, ExposedState, Extension, Genesis, OpId, + Transition, TransitionBundle, TypedAssigns, XAnchor, }; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -218,14 +218,14 @@ impl MergeRevealContract for AnchoredBundle { } */ -impl MergeReveal for Anchor { +impl MergeReveal for XAnchor { fn merge_reveal(self, other: Self) -> Result { match (self, other) { - (Anchor::Bitcoin(anchor), Anchor::Bitcoin(other)) => { - anchor.merge_reveal(other).map(Anchor::Bitcoin) + (XAnchor::Bitcoin(anchor), XAnchor::Bitcoin(other)) => { + anchor.merge_reveal(other).map(XAnchor::Bitcoin) } - (Anchor::Liquid(anchor), Anchor::Liquid(other)) => { - anchor.merge_reveal(other).map(Anchor::Liquid) + (XAnchor::Liquid(anchor), XAnchor::Liquid(other)) => { + anchor.merge_reveal(other).map(XAnchor::Liquid) } _ => Err(MergeError::TxidMismatch.into()), } diff --git a/src/containers/consignment.rs b/src/containers/consignment.rs index af1efd09..d4d9d220 100644 --- a/src/containers/consignment.rs +++ b/src/containers/consignment.rs @@ -29,7 +29,7 @@ use rgb::validation::{self, ConsignmentApi}; use rgb::{ AnchoredBundle, AssetTag, AssignmentType, AttachId, BundleId, ContractHistory, ContractId, Extension, Genesis, GraphSeal, OpId, OpRef, Operation, Schema, SchemaId, SecretSeal, SubSchema, - Transition, Xchain, + Transition, XSeal, }; use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize}; @@ -232,7 +232,7 @@ impl Consignment { Ok(history) } - pub fn reveal_bundle_seal(&mut self, bundle_id: BundleId, revealed: Xchain) { + pub fn reveal_bundle_seal(&mut self, bundle_id: BundleId, revealed: XSeal) { for anchored_bundle in &mut self.bundles { if anchored_bundle.bundle.bundle_id() == bundle_id { anchored_bundle.bundle.reveal_seal(revealed); diff --git a/src/containers/partials.rs b/src/containers/partials.rs index 9c3d417b..d0ba6698 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -26,7 +26,7 @@ use amplify::confinement; use amplify::confinement::{Confined, MediumVec, U24}; use bp::seals::txout::CloseMethod; use commit_verify::mpc; -use rgb::{Anchor, OpId, Operation, OutputSeal, Transition, TransitionBundle}; +use rgb::{OpId, Operation, OutputSeal, Transition, TransitionBundle, XAnchor}; use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize}; use crate::containers::XchainOutpoint; @@ -160,7 +160,7 @@ impl IntoIterator for Batch { serde(crate = "serde_crate", rename_all = "camelCase") )] pub struct Fascia { - pub anchor: Anchor, + pub anchor: XAnchor, pub bundles: MediumVec, } diff --git a/src/containers/seal.rs b/src/containers/seal.rs index cafe37ca..ee7f5edd 100644 --- a/src/containers/seal.rs +++ b/src/containers/seal.rs @@ -29,7 +29,7 @@ use bp::seals::txout::{CloseMethod, TxPtr}; use bp::secp256k1::rand::{thread_rng, RngCore}; use bp::{Outpoint, Vout}; use commit_verify::Conceal; -use rgb::{ExposedSeal, GenesisSeal, GraphSeal, SecretSeal, Xchain}; +use rgb::{ExposedSeal, GenesisSeal, GraphSeal, SecretSeal, XSeal}; use crate::LIB_NAME_RGB_STD; @@ -156,10 +156,10 @@ impl From for TerminalSeal { } } -impl From> for TerminalSeal { - fn from(seal: Xchain) -> Self { +impl From> for TerminalSeal { + fn from(seal: XSeal) -> Self { match seal { - Xchain::Bitcoin( + XSeal::Bitcoin( seal @ GraphSeal { txid: TxPtr::WitnessTx, .. @@ -169,7 +169,7 @@ impl From> for TerminalSeal { seal.vout, seal.blinding, )), - Xchain::Liquid( + XSeal::Liquid( seal @ GraphSeal { txid: TxPtr::WitnessTx, .. @@ -206,11 +206,9 @@ impl Conceal for TerminalSeal { match *self { TerminalSeal::ConcealedUtxo(hash) => hash, TerminalSeal::BitcoinWitnessVout(seal) => { - Xchain::Bitcoin(GraphSeal::from(seal)).conceal() - } - TerminalSeal::LiquidWitnessVout(seal) => { - Xchain::Liquid(GraphSeal::from(seal)).conceal() + XSeal::Bitcoin(GraphSeal::from(seal)).conceal() } + TerminalSeal::LiquidWitnessVout(seal) => XSeal::Liquid(GraphSeal::from(seal)).conceal(), } } } @@ -239,15 +237,15 @@ impl FromStr for TerminalSeal { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, From)] pub enum BuilderSeal { #[from] - Revealed(Xchain), + Revealed(XSeal), #[from] Concealed(SecretSeal), } impl From for BuilderSeal { - fn from(outpoint: Outpoint) -> Self { BuilderSeal::Revealed(Xchain::Bitcoin(outpoint.into())) } + fn from(outpoint: Outpoint) -> Self { BuilderSeal::Revealed(XSeal::Bitcoin(outpoint.into())) } } impl From for BuilderSeal { - fn from(outpoint: Outpoint) -> Self { BuilderSeal::Revealed(Xchain::Bitcoin(outpoint.into())) } + fn from(outpoint: Outpoint) -> Self { BuilderSeal::Revealed(XSeal::Bitcoin(outpoint.into())) } } diff --git a/src/persistence/hoard.rs b/src/persistence/hoard.rs index ddeaa6d8..fbbea7a7 100644 --- a/src/persistence/hoard.rs +++ b/src/persistence/hoard.rs @@ -27,8 +27,8 @@ use amplify::confinement::{Confined, LargeOrdMap, SmallOrdMap, TinyOrdMap, TinyO use bp::dbc::anchor::MergeError; use commit_verify::mpc; use rgb::{ - Anchor, AnchoredBundle, AssetTag, AssignmentType, BundleId, ContractId, Extension, Genesis, - OpId, Operation, SchemaId, TransitionBundle, WitnessId, + AnchoredBundle, AssetTag, AssignmentType, BundleId, ContractId, Extension, Genesis, OpId, + Operation, SchemaId, TransitionBundle, WitnessId, XAnchor, }; use strict_encoding::TypeName; @@ -70,7 +70,7 @@ pub struct Hoard { pub(super) asset_tags: TinyOrdMap>, pub(super) bundles: LargeOrdMap, pub(super) extensions: LargeOrdMap, - pub(super) anchors: LargeOrdMap>, + pub(super) anchors: LargeOrdMap>, pub(super) sigs: SmallOrdMap, } @@ -204,7 +204,10 @@ impl Hoard { } // TODO: Move into Stash trait and re-implement using trait accessor methods - pub fn consume_anchor(&mut self, anchor: Anchor) -> Result<(), ConsumeError> { + pub fn consume_anchor( + &mut self, + anchor: XAnchor, + ) -> Result<(), ConsumeError> { let witness_id = anchor.witness_id(); match self.anchors.get_mut(&witness_id) { Some(a) => *a = a.clone().merge_reveal(anchor)?, @@ -311,7 +314,7 @@ impl Stash for Hoard { fn anchor( &self, witness_id: WitnessId, - ) -> Result<&Anchor, StashError> { + ) -> Result<&XAnchor, StashError> { self.anchors .get(&witness_id) .ok_or(StashInconsistency::AnchorAbsent(witness_id).into()) diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index 47c3ec9a..6b619597 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -32,9 +32,9 @@ use chrono::Utc; use commit_verify::{mpc, Conceal}; use invoice::{Beneficiary, InvoiceState, RgbInvoice}; use rgb::{ - validation, Anchor, AnchoredBundle, AssignmentType, BlindingFactor, BundleId, ContractId, - ExposedSeal, GraphSeal, OpId, Operation, Opout, OutputSeal, SchemaId, SecretSeal, SubSchema, - Transition, TransitionBundle, WitnessId, Xchain, + validation, AnchoredBundle, AssignmentType, BlindingFactor, BundleId, ContractId, ExposedSeal, + GraphSeal, OpId, Operation, Opout, OutputSeal, SchemaId, SecretSeal, SubSchema, Transition, + TransitionBundle, WitnessId, XAnchor, XSeal, }; use strict_encoding::TypeName; @@ -372,7 +372,7 @@ pub trait Inventory: Deref { #[doc(hidden)] unsafe fn consume_anchor( &mut self, - anchor: Anchor, + anchor: XAnchor, ) -> Result<(), InventoryError>; #[doc(hidden)] @@ -534,10 +534,10 @@ pub trait Inventory: Deref { fn store_seal_secret( &mut self, - seal: Xchain, + seal: XSeal, ) -> Result<(), InventoryError>; - fn seal_secrets(&self) -> Result>, InventoryError>; + fn seal_secrets(&self) -> Result>, InventoryError>; #[allow(clippy::type_complexity)] fn export_contract( @@ -548,7 +548,7 @@ pub trait Inventory: Deref { ConsignerError::Target as Stash>::Error>, > { let mut consignment = - self.consign::(contract_id, [] as [Xchain; 0])?; + self.consign::(contract_id, [] as [XSeal; 0])?; consignment.transfer = false; Ok(consignment.into()) // TODO: Add known sigs to the bindle @@ -724,7 +724,7 @@ pub trait Inventory: Deref { let vout = allocator(id, assignment_type, velocity) .ok_or(ComposeError::NoBlankOrChange(velocity, assignment_type))?; let seal = GraphSeal::new_vout(method, vout); - Ok(BuilderSeal::Revealed(Xchain::with(layer1, seal))) + Ok(BuilderSeal::Revealed(XSeal::with(layer1, seal))) }; // 1. Prepare the data @@ -741,7 +741,7 @@ pub trait Inventory: Deref { let beneficiary = match invoice.beneficiary { Beneficiary::BlindedSeal(seal) => BuilderSeal::Concealed(seal), Beneficiary::WitnessVoutBitcoin(_) => { - BuilderSeal::Revealed(Xchain::Bitcoin(GraphSeal::new_vout(method, change_vout))) + BuilderSeal::Revealed(XSeal::Bitcoin(GraphSeal::new_vout(method, change_vout))) } }; diff --git a/src/persistence/stash.rs b/src/persistence/stash.rs index fef282c4..89b92edc 100644 --- a/src/persistence/stash.rs +++ b/src/persistence/stash.rs @@ -27,8 +27,8 @@ use std::error::Error; use amplify::confinement::{TinyOrdMap, TinyOrdSet}; use commit_verify::mpc; use rgb::{ - Anchor, AssetTag, AssignmentType, BundleId, ContractId, Extension, Genesis, OpId, SchemaId, - TransitionBundle, WitnessId, + AssetTag, AssignmentType, BundleId, ContractId, Extension, Genesis, OpId, SchemaId, + TransitionBundle, WitnessId, XAnchor, }; use strict_encoding::TypeName; @@ -133,5 +133,5 @@ pub trait Stash { fn anchor( &self, witness_id: WitnessId, - ) -> Result<&Anchor, StashError>; + ) -> Result<&XAnchor, StashError>; } diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index 2161f801..b48d95e0 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -27,10 +27,10 @@ use amplify::confinement::{MediumOrdMap, MediumOrdSet, TinyOrdMap}; use commit_verify::{mpc, Conceal}; use rgb::validation::{Status, Validity, Warning}; use rgb::{ - validation, Anchor, AnchoredBundle, Assign, AssignmentType, BundleId, ContractHistory, - ContractId, ContractState, ExposedState, Extension, Genesis, GenesisSeal, GraphSeal, OpId, - Operation, Opout, OutputSeal, SecretSeal, SubSchema, Transition, TransitionBundle, - TypedAssigns, WitnessAnchor, WitnessId, Xchain, + validation, AnchoredBundle, Assign, AssignmentType, BundleId, ContractHistory, ContractId, + ContractState, ExposedState, Extension, Genesis, GenesisSeal, GraphSeal, OpId, Operation, + Opout, OutputSeal, SecretSeal, SubSchema, Transition, TransitionBundle, TypedAssigns, + WitnessAnchor, WitnessId, XAnchor, XSeal, }; use strict_encoding::{StrictDeserialize, StrictSerialize}; @@ -77,7 +77,7 @@ pub struct Stock { contract_index: TinyOrdMap, terminal_index: MediumOrdMap, // secrets - seal_secrets: MediumOrdSet>, + seal_secrets: MediumOrdSet>, } impl Default for Stock { @@ -464,7 +464,7 @@ impl Inventory for Stock { unsafe fn consume_anchor( &mut self, - anchor: Anchor, + anchor: XAnchor, ) -> Result<(), InventoryError> { let witness_id = anchor.witness_id(); for (bundle_id, _) in anchor.known_bundle_ids() { @@ -685,13 +685,13 @@ impl Inventory for Stock { fn store_seal_secret( &mut self, - seal: Xchain, + seal: XSeal, ) -> Result<(), InventoryError> { self.seal_secrets.push(seal)?; Ok(()) } - fn seal_secrets(&self) -> Result>, InventoryError> { + fn seal_secrets(&self) -> Result>, InventoryError> { Ok(self.seal_secrets.to_inner()) } } diff --git a/src/resolvers.rs b/src/resolvers.rs index 20de0b22..84577b07 100644 --- a/src/resolvers.rs +++ b/src/resolvers.rs @@ -19,10 +19,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use rgb::{Anchor, WitnessAnchor}; +use rgb::{WitnessAnchor, XAnchor}; pub trait ResolveHeight { type Error: std::error::Error; - fn resolve_anchor(&mut self, anchor: &Anchor) -> Result; + fn resolve_anchor(&mut self, anchor: &XAnchor) -> Result; } diff --git a/src/stl/stl.rs b/src/stl/stl.rs index a2d236b7..b5b98c89 100644 --- a/src/stl/stl.rs +++ b/src/stl/stl.rs @@ -44,7 +44,7 @@ pub const LIB_ID_RGB_CONTRACT: &str = /// Strict types id for the library representing of RGB StdLib data types. pub const LIB_ID_RGB_STD: &str = - "urn:ubideco:stl:E3LrF7ryeC9J8rABKrs1ceRg2skzsjaWVFk9xbi1x56#nobel-origami-tempo"; + "urn:ubideco:stl:2N7iehth1MBA1yQaJebjMxw45x9fGYAMNqrmGU3pxwvU#tribal-modem-andy"; fn _rgb_std_stl() -> Result { LibBuilder::new(libname!(LIB_NAME_RGB_STD), tiny_bset! { From 8c146a999da71f2ad56fe38068360145ca4de53e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 15 Dec 2023 20:41:20 +0100 Subject: [PATCH 12/31] chore: fix lints --- src/persistence/hoard.rs | 2 +- src/persistence/inventory.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/persistence/hoard.rs b/src/persistence/hoard.rs index fbbea7a7..cc1e06a0 100644 --- a/src/persistence/hoard.rs +++ b/src/persistence/hoard.rs @@ -174,7 +174,7 @@ impl Hoard { for AnchoredBundle { anchor, bundle } in consignment.bundles { let bundle_id = bundle.bundle_id(); - let anchor = anchor.into_merkle_block(contract_id, bundle_id.into())?; + let anchor = anchor.into_merkle_block(contract_id, bundle_id)?; self.consume_anchor(anchor)?; self.consume_bundle(bundle)?; } diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index 6b619597..4ec5e831 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -526,6 +526,7 @@ pub trait Inventory: Deref { terminals: impl IntoIterator, ) -> Result, InventoryError>; + #[allow(clippy::type_complexity)] fn state_for_outputs( &self, contract_id: ContractId, @@ -710,6 +711,7 @@ pub trait Inventory: Deref { .map(|o| o.into()) .collect::>(); + #[allow(clippy::type_complexity)] let output_for_assignment = |id: ContractId, assignment_type: AssignmentType| -> Result< From 0f1a632a4d769bbd8607faf9da8c65b03a93e5d9 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 17 Dec 2023 17:18:18 +0100 Subject: [PATCH 13/31] containers: export CloseMethodSet type --- src/containers/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/mod.rs b/src/containers/mod.rs index 4aa75948..829946a2 100644 --- a/src/containers/mod.rs +++ b/src/containers/mod.rs @@ -42,6 +42,6 @@ pub use bindle::{Bindle, BindleContent, BindleParseError, LoadError, UniversalBi pub use certs::{Cert, ContentId, ContentSigs, Identity}; pub use consignment::{Consignment, Contract, Transfer}; pub use disclosure::Disclosure; -pub use partials::{Batch, BatchItem, Fascia}; +pub use partials::{Batch, BatchItem, CloseMethodSet, Fascia}; pub use seal::{BuilderSeal, TerminalSeal, VoutSeal}; pub use util::{ContainerVer, Terminal, XchainOutpoint}; From 3ba6ad30f8f7244e47b27a9c2b36629048a6cc2d Mon Sep 17 00:00:00 2001 From: "Dr. Maxim Orlovsky" Date: Mon, 18 Dec 2023 11:06:51 +0100 Subject: [PATCH 14/31] Update invoice/src/lib.rs Co-authored-by: Armando CD --- invoice/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invoice/src/lib.rs b/invoice/src/lib.rs index b4cf7152..6ee62c20 100644 --- a/invoice/src/lib.rs +++ b/invoice/src/lib.rs @@ -37,7 +37,7 @@ mod amount; pub use amount::{Amount, CoinAmount, Precision}; pub use builder::RgbInvoiceBuilder; -pub use invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; +pub use crate::invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; pub use parse::{InvoiceParseError, TransportParseError}; pub const LIB_NAME_RGB_CONTRACT: &str = "RGBContract"; From 920440e743ad9b9ccc6d037cced4f17a698dccb9 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 18:14:34 +0100 Subject: [PATCH 15/31] containers: enrich CloseMethodSet APIs --- src/containers/partials.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/containers/partials.rs b/src/containers/partials.rs index d0ba6698..334d353c 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -48,6 +48,30 @@ pub enum CloseMethodSet { Both = 0x03, } +impl BitOr> for CloseMethodSet { + type Output = Self; + fn bitor(mut self, rhs: Option) -> Self::Output { + rhs.map(|m| self |= m); + self + } +} + +impl BitOrAssign> for CloseMethodSet { + fn bitor_assign(&mut self, rhs: Option) { rhs.map(|m| *self |= m); } +} + +impl BitOr for Option { + type Output = CloseMethodSet; + fn bitor(self, mut rhs: CloseMethodSet) -> Self::Output { + self.map(|m| rhs |= m); + rhs + } +} + +impl BitOrAssign for Option { + fn bitor_assign(&mut self, rhs: CloseMethodSet) { *self = Some(rhs | *self) } +} + impl BitOr for CloseMethodSet { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { if self == rhs { self } else { Self::Both } } @@ -70,6 +94,11 @@ impl From for CloseMethodSet { } } +impl CloseMethodSet { + pub fn has_tapret_first(self) -> bool { matches!(self, Self::TapretFirst | Self::Both) } + pub fn has_opret_first(self) -> bool { matches!(self, Self::OpretFirst | Self::Both) } +} + #[derive(Clone, PartialEq, Eq, Hash, Debug)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] From 20388f2e099b3d39b99fda83570be33ec4f913d4 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 19:00:06 +0100 Subject: [PATCH 16/31] containers: improve partial types confinements --- src/containers/partials.rs | 16 ++++++++++++---- src/persistence/inventory.rs | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/containers/partials.rs b/src/containers/partials.rs index 334d353c..9283846c 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -23,7 +23,7 @@ use std::ops::{BitOr, BitOrAssign}; use std::vec; use amplify::confinement; -use amplify::confinement::{Confined, MediumVec, U24}; +use amplify::confinement::{Confined, U24}; use bp::seals::txout::CloseMethod; use commit_verify::mpc; use rgb::{OpId, Operation, OutputSeal, Transition, TransitionBundle, XAnchor}; @@ -160,7 +160,7 @@ impl BatchItem { )] pub struct Batch { pub main: BatchItem, - pub blanks: MediumVec, + pub blanks: Confined, 0, { U24 - 1 }>, } impl StrictSerialize for Batch {} @@ -181,7 +181,7 @@ impl IntoIterator for Batch { /// of finalized state transitions (under multiple contracts), packed into /// bundles, and anchored to a single layer 1 transaction. #[derive(Clone, PartialEq, Eq, Debug)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] #[cfg_attr( feature = "serde", @@ -190,8 +190,16 @@ impl IntoIterator for Batch { )] pub struct Fascia { pub anchor: XAnchor, - pub bundles: MediumVec, + pub bundles: Confined, 1, U24>, } +impl StrictDumb for Fascia { + fn strict_dumb() -> Self { + Fascia { + anchor: strict_dumb!(), + bundles: confined_vec![strict_dumb!()], + } + } +} impl StrictSerialize for Fascia {} impl StrictDeserialize for Fascia {} diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index 4ec5e831..cf05f94c 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -24,7 +24,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::error::Error; use std::ops::Deref; -use amplify::confinement::{self, Confined, MediumVec}; +use amplify::confinement::{self, Confined, MediumVec, U24}; use bp::seals::txout::blind::SingleBlindSeal; use bp::seals::txout::CloseMethod; use bp::{Txid, Vout}; @@ -818,7 +818,7 @@ pub trait Inventory: Deref { } } // Construct blank transitions - let mut blanks = MediumVec::with_capacity(spent_state.len()); + let mut blanks = Confined::, 0, { U24 - 1 }>::with_capacity(spent_state.len()); for (id, opouts) in spent_state { let mut blank_builder = self.blank_builder(id, iface.clone())?; let mut outputs = Vec::with_capacity(opouts.len()); From 84e00d45a04aaa78d175880722b355aaba5c64df Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 19:02:48 +0100 Subject: [PATCH 17/31] containers: use mapping for Fascia --- src/containers/partials.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/containers/partials.rs b/src/containers/partials.rs index 9283846c..3591d89e 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -19,6 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::BTreeMap; use std::ops::{BitOr, BitOrAssign}; use std::vec; @@ -26,7 +27,7 @@ use amplify::confinement; use amplify::confinement::{Confined, U24}; use bp::seals::txout::CloseMethod; use commit_verify::mpc; -use rgb::{OpId, Operation, OutputSeal, Transition, TransitionBundle, XAnchor}; +use rgb::{ContractId, OpId, Operation, OutputSeal, Transition, TransitionBundle, XAnchor}; use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize}; use crate::containers::XchainOutpoint; @@ -190,14 +191,14 @@ impl IntoIterator for Batch { )] pub struct Fascia { pub anchor: XAnchor, - pub bundles: Confined, 1, U24>, + pub bundles: Confined, 1, U24>, } impl StrictDumb for Fascia { fn strict_dumb() -> Self { Fascia { anchor: strict_dumb!(), - bundles: confined_vec![strict_dumb!()], + bundles: confined_bmap![strict_dumb!() => strict_dumb!()], } } } From b65cf9ec35a4748862dd02b59ca72fb6b7370692 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 19:37:20 +0100 Subject: [PATCH 18/31] containers: make Batch deterministic --- src/containers/mod.rs | 2 +- src/containers/partials.rs | 36 +++++++++++++++++++++++++++--------- src/persistence/inventory.rs | 8 ++++---- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/containers/mod.rs b/src/containers/mod.rs index 829946a2..43dc5250 100644 --- a/src/containers/mod.rs +++ b/src/containers/mod.rs @@ -42,6 +42,6 @@ pub use bindle::{Bindle, BindleContent, BindleParseError, LoadError, UniversalBi pub use certs::{Cert, ContentId, ContentSigs, Identity}; pub use consignment::{Consignment, Contract, Transfer}; pub use disclosure::Disclosure; -pub use partials::{Batch, BatchItem, CloseMethodSet, Fascia}; +pub use partials::{Batch, CloseMethodSet, Fascia, TransitionInfo}; pub use seal::{BuilderSeal, TerminalSeal, VoutSeal}; pub use util::{ContainerVer, Terminal, XchainOutpoint}; diff --git a/src/containers/partials.rs b/src/containers/partials.rs index 3591d89e..9e518031 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -19,7 +19,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::cmp::Ordering; use std::collections::BTreeMap; +use std::hash::{Hash, Hasher}; use std::ops::{BitOr, BitOrAssign}; use std::vec; @@ -100,7 +102,7 @@ impl CloseMethodSet { pub fn has_opret_first(self) -> bool { matches!(self, Self::OpretFirst | Self::Both) } } -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Eq, Debug)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] #[cfg_attr( @@ -108,18 +110,34 @@ impl CloseMethodSet { derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] -pub struct BatchItem { +pub struct TransitionInfo { pub id: OpId, pub inputs: Confined, 1, U24>, pub transition: Transition, pub methods: CloseMethodSet, } -impl StrictDumb for BatchItem { +impl StrictDumb for TransitionInfo { fn strict_dumb() -> Self { Self::new(strict_dumb!(), [strict_dumb!()]).unwrap() } } -impl BatchItem { +impl PartialEq for TransitionInfo { + fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } +} + +impl Ord for TransitionInfo { + fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } +} + +impl PartialOrd for TransitionInfo { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} + +impl Hash for TransitionInfo { + fn hash(&self, state: &mut H) { state.write(self.id.as_slice()) } +} + +impl TransitionInfo { pub fn new( transition: Transition, seals: impl AsRef<[OutputSeal]>, @@ -139,7 +157,7 @@ impl BatchItem { }) }) .expect("confinement guarantees at least one item"); - Ok(BatchItem { + Ok(TransitionInfo { id: transition.id(), inputs, transition, @@ -160,16 +178,16 @@ impl BatchItem { serde(crate = "serde_crate", rename_all = "camelCase") )] pub struct Batch { - pub main: BatchItem, - pub blanks: Confined, 0, { U24 - 1 }>, + pub main: TransitionInfo, + pub blanks: Confined, 0, { U24 - 1 }>, } impl StrictSerialize for Batch {} impl StrictDeserialize for Batch {} impl IntoIterator for Batch { - type Item = BatchItem; - type IntoIter = vec::IntoIter; + type Item = TransitionInfo; + type IntoIter = vec::IntoIter; fn into_iter(self) -> Self::IntoIter { let mut vec = self.blanks.into_inner(); diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index cf05f94c..0810a1d5 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -40,8 +40,8 @@ use strict_encoding::TypeName; use crate::accessors::{BundleExt, MergeRevealError, RevealError}; use crate::containers::{ - Batch, BatchItem, Bindle, BuilderSeal, Cert, Consignment, ContentId, Contract, Fascia, - Terminal, Transfer, + Batch, Bindle, BuilderSeal, Cert, Consignment, ContentId, Contract, Fascia, Terminal, Transfer, + TransitionInfo, }; use crate::interface::{ BuilderError, ContractIface, Iface, IfaceId, IfaceImpl, IfacePair, IfaceWrapper, @@ -834,11 +834,11 @@ pub trait Inventory: Deref { } let transition = blank_builder.complete_transition(contract_id)?; - blanks.push(BatchItem::new(transition, outputs)?)?; + blanks.push(TransitionInfo::new(transition, outputs)?)?; } Ok(Batch { - main: BatchItem::new(main_transition, main_inputs)?, + main: TransitionInfo::new(main_transition, main_inputs)?, blanks, }) } From 71a79de45cdbb03ffe0b9cd6a66360d198cd2d2e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 19:58:11 +0100 Subject: [PATCH 19/31] iface: export ContractError type --- src/interface/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 8ec4554a..a9f46e3d 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -34,8 +34,8 @@ mod suppl; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder}; pub use contract::{ - AllocationWitness, ContractIface, FilterExclude, FilterIncludeAll, FungibleAllocation, - IfaceWrapper, OutpointFilter, TypedState, + AllocationWitness, ContractError, ContractIface, FilterExclude, FilterIncludeAll, + FungibleAllocation, IfaceWrapper, OutpointFilter, TypedState, }; pub use iface::{ ArgMap, ArgSpec, AssignIface, ExtensionIface, GenesisIface, GlobalIface, Iface, IfaceId, From fda3a394f93ec38b321b6a447766fc792522ad00 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 20:26:30 +0100 Subject: [PATCH 20/31] persistance: fix use of change vout in compose function --- invoice/src/invoice.rs | 2 +- src/persistence/inventory.rs | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/invoice/src/invoice.rs b/invoice/src/invoice.rs index 1c0204e8..14d15684 100644 --- a/invoice/src/invoice.rs +++ b/invoice/src/invoice.rs @@ -45,7 +45,7 @@ pub enum InvoiceState { Attach(AttachId), } -#[derive(Clone, Eq, PartialEq, Hash, Debug, Display, From)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, From)] #[display(inner)] pub enum Beneficiary { // TODO: Create wrapping type for SecretSeal to cover/commit to a specific layer1. diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index 0810a1d5..c5973d9b 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -87,6 +87,9 @@ pub enum ComposeError { /// '{0}'. NoBlankOrChange(VelocityHint, AssignmentType), + /// the provided PSBT doesn't pay any sats to the RGB beneficiary address. + NoBeneficiaryOutput, + /// expired invoice. InvoiceExpired, @@ -678,7 +681,7 @@ pub trait Inventory: Deref { invoice: &RgbInvoice, prev_outputs: impl IntoIterator>, method: CloseMethod, - change_vout: impl Into, + change_vout: Option>, allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, ) -> Result::Target as Stash>::Error>> where @@ -697,7 +700,7 @@ pub trait Inventory: Deref { invoice: &RgbInvoice, prev_outputs: impl IntoIterator>, method: CloseMethod, - change_vout: impl Into, + beneficiary_vout: Option>, allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, blinder: impl Fn(ContractId, AssignmentType) -> BlindingFactor, ) -> Result::Target as Stash>::Error>> @@ -705,7 +708,6 @@ pub trait Inventory: Deref { Self::Error: From<::Error>, { let layer1 = invoice.layer1(); - let change_vout = change_vout.into(); let prev_outputs = prev_outputs .into_iter() .map(|o| o.into()) @@ -740,10 +742,13 @@ pub trait Inventory: Deref { let mut main_builder = self.transition_builder(contract_id, iface.clone(), invoice.operation.clone())?; - let beneficiary = match invoice.beneficiary { - Beneficiary::BlindedSeal(seal) => BuilderSeal::Concealed(seal), - Beneficiary::WitnessVoutBitcoin(_) => { - BuilderSeal::Revealed(XSeal::Bitcoin(GraphSeal::new_vout(method, change_vout))) + let beneficiary = match (invoice.beneficiary, beneficiary_vout) { + (Beneficiary::BlindedSeal(seal), _) => BuilderSeal::Concealed(seal), + (Beneficiary::WitnessVoutBitcoin(_), Some(vout)) => { + BuilderSeal::Revealed(XSeal::Bitcoin(GraphSeal::new_vout(method, vout.into()))) + } + (Beneficiary::WitnessVoutBitcoin(_), None) => { + return Err(ComposeError::NoBeneficiaryOutput); } }; From 934018da9c67d28b1345fb135634c5f8c8a8d858 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 20:40:57 +0100 Subject: [PATCH 21/31] containers: add Batch::close_method_set --- src/containers/partials.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/containers/partials.rs b/src/containers/partials.rs index 9e518031..d6b6892c 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -196,6 +196,14 @@ impl IntoIterator for Batch { } } +impl Batch { + pub fn close_method_set(&self) -> CloseMethodSet { + let mut methods = self.main.methods; + self.blanks.iter().for_each(|i| methods |= i.methods); + methods + } +} + /// Structure exported from a PSBT for merging into the stash. It contains a set /// of finalized state transitions (under multiple contracts), packed into /// bundles, and anchored to a single layer 1 transaction. From f73f03bf8dca1953ae79d852e44f34255f876c7c Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 21:14:30 +0100 Subject: [PATCH 22/31] persistence: add Stash::witness_ids method --- src/persistence/hoard.rs | 4 ++++ src/persistence/stash.rs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/persistence/hoard.rs b/src/persistence/hoard.rs index cc1e06a0..8ca818f4 100644 --- a/src/persistence/hoard.rs +++ b/src/persistence/hoard.rs @@ -291,6 +291,10 @@ impl Stash for Hoard { .ok_or(StashInconsistency::ContractAbsent(contract_id).into()) } + fn witness_ids(&self) -> Result, Self::Error> { + Ok(self.anchors.keys().copied().collect()) + } + fn bundle_ids(&self) -> Result, Self::Error> { Ok(self.bundles.keys().copied().collect()) } diff --git a/src/persistence/stash.rs b/src/persistence/stash.rs index 89b92edc..241c8ec7 100644 --- a/src/persistence/stash.rs +++ b/src/persistence/stash.rs @@ -122,6 +122,8 @@ pub trait Stash { fn genesis(&self, contract_id: ContractId) -> Result<&Genesis, StashError>; + fn witness_ids(&self) -> Result, Self::Error>; + fn bundle_ids(&self) -> Result, Self::Error>; fn bundle(&self, bundle_id: BundleId) -> Result<&TransitionBundle, StashError>; From e11873ac4058db516926966bab79697458393c92 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 21:44:51 +0100 Subject: [PATCH 23/31] chore: fix clippy lints --- invoice/src/lib.rs | 3 ++- src/containers/partials.rs | 14 +++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/invoice/src/lib.rs b/invoice/src/lib.rs index 6ee62c20..20a79a10 100644 --- a/invoice/src/lib.rs +++ b/invoice/src/lib.rs @@ -37,7 +37,8 @@ mod amount; pub use amount::{Amount, CoinAmount, Precision}; pub use builder::RgbInvoiceBuilder; -pub use crate::invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; pub use parse::{InvoiceParseError, TransportParseError}; +pub use crate::invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; + pub const LIB_NAME_RGB_CONTRACT: &str = "RGBContract"; diff --git a/src/containers/partials.rs b/src/containers/partials.rs index d6b6892c..354e2726 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -54,19 +54,27 @@ pub enum CloseMethodSet { impl BitOr> for CloseMethodSet { type Output = Self; fn bitor(mut self, rhs: Option) -> Self::Output { - rhs.map(|m| self |= m); + if let Some(m) = rhs { + self |= m + }; self } } impl BitOrAssign> for CloseMethodSet { - fn bitor_assign(&mut self, rhs: Option) { rhs.map(|m| *self |= m); } + fn bitor_assign(&mut self, rhs: Option) { + if let Some(m) = rhs { + *self |= m + }; + } } impl BitOr for Option { type Output = CloseMethodSet; fn bitor(self, mut rhs: CloseMethodSet) -> Self::Output { - self.map(|m| rhs |= m); + if let Some(m) = self { + rhs |= m + }; rhs } } From 6d2f8c0f167ca1bdbcbfc252e2711aa7ae146767 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 21:49:02 +0100 Subject: [PATCH 24/31] docs: remove old comments on `Consignment` --- src/containers/consignment.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/containers/consignment.rs b/src/containers/consignment.rs index d4d9d220..3456be98 100644 --- a/src/containers/consignment.rs +++ b/src/containers/consignment.rs @@ -49,10 +49,7 @@ pub type Contract = Consignment; /// /// All consignments-related procedures, including validation or merging /// consignments data into stash or schema-specific data storage, must start -/// with `endpoints` and process up to the genesis. If any of the nodes within -/// the consignments are not part of the paths connecting endpoints with the -/// genesis, consignments validation will return -/// [`validation::Warning::ExcessiveNode`] warning. +/// with `endpoints` and process up to the genesis. #[derive(Clone, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] From b2ec8213fcc50dbec95f97155896c9ad5827df99 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 25 Dec 2023 00:07:16 +0100 Subject: [PATCH 25/31] iface: populate transaction builder with known asset tags --- src/interface/builder.rs | 21 ++++++++++++++++++++- src/persistence/inventory.rs | 8 +++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/interface/builder.rs b/src/interface/builder.rs index be7f50f4..a843efc3 100644 --- a/src/interface/builder.rs +++ b/src/interface/builder.rs @@ -331,6 +331,16 @@ impl TransitionBuilder { Ok(self) } + #[inline] + pub fn add_asset_tag_raw( + mut self, + type_id: AssignmentType, + asset_tag: AssetTag, + ) -> Result { + self.builder = self.builder.add_asset_tag_raw(type_id, asset_tag)?; + Ok(self) + } + #[inline] pub fn add_global_state( mut self, @@ -565,7 +575,7 @@ impl OperationBuilder { #[inline] pub fn add_asset_tag( - mut self, + self, name: impl Into, asset_tag: AssetTag, ty: Option, @@ -575,6 +585,15 @@ impl OperationBuilder { .assignments_type(&name, ty) .ok_or(BuilderError::AssignmentNotFound(name))?; + self.add_asset_tag_raw(type_id, asset_tag) + } + + #[inline] + pub fn add_asset_tag_raw( + mut self, + type_id: AssignmentType, + asset_tag: AssetTag, + ) -> Result { if self.fungible.contains_key(&type_id) { return Err(BuilderError::AssetTagAutomatic(type_id)); } diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index c5973d9b..6df0ac1a 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -471,7 +471,7 @@ pub trait Inventory: Deref { .iimpls .get(&iface.iface_id()) .ok_or(DataError::NoIfaceImpl(schema.schema_id(), iface.iface_id()))?; - let builder = if let Some(transition_name) = transition_name { + let mut builder = if let Some(transition_name) = transition_name { TransitionBuilder::named_transition( iface.clone(), schema.clone(), @@ -482,6 +482,12 @@ pub trait Inventory: Deref { TransitionBuilder::default_transition(iface.clone(), schema.clone(), iimpl.clone()) } .expect("internal inconsistency"); + let tags = self.contract_asset_tags(contract_id)?; + for (assignment_type, asset_tag) in tags { + builder = builder + .add_asset_tag_raw(*assignment_type, *asset_tag) + .expect("tags are in bset and must not repeat"); + } Ok(builder) } From e8ba2654e9e2480654facbe827984fc57f558cd5 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 25 Dec 2023 00:07:38 +0100 Subject: [PATCH 26/31] invoice: fix excessive allocations and panics --- invoice/src/parse.rs | 80 ++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/invoice/src/parse.rs b/invoice/src/parse.rs index 168c7005..ef7e0832 100644 --- a/invoice/src/parse.rs +++ b/invoice/src/parse.rs @@ -33,7 +33,7 @@ use strict_encoding::{InvalidIdent, TypeName}; use super::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; -const OMITTED: char = '~'; +const OMITTED: &str = "~"; const EXPIRY: &str = "expiry"; const NETWORK: &str = "network"; const ENDPOINTS: &str = "endpoints"; @@ -63,36 +63,39 @@ pub enum TransportParseError { } #[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] -#[display(inner)] +#[display(doc_comments)] pub enum InvoiceParseError { #[from] + #[display(inner)] Uri(fluent_uri::ParseError), - #[display(doc_comments)] /// invalid invoice. Invalid, - #[display(doc_comments)] + /// contract id is missed from the invoice. + ContractMissed, + + /// interface information is missed from the invoice. + IfaceMissed, + + /// assignment data is missed from the invoice. + AssignmentMissed, + /// invalid invoice scheme {0}. InvalidScheme(String), - #[display(doc_comments)] /// no invoice transport has been provided. NoTransport, - #[display(doc_comments)] /// invalid invoice: contract ID present but no contract interface provided. ContractIdNoIface, - #[display(doc_comments)] /// invalid contract ID. InvalidContractId(String), - #[display(doc_comments)] /// invalid interface {0}. InvalidIface(String), - #[display(doc_comments)] /// invalid expiration timestamp {0}. InvalidExpiration(String), @@ -100,28 +103,26 @@ pub enum InvoiceParseError { #[from] InvalidNetwork(UnknownNetwork), - #[display(doc_comments)] /// address network `{0:#?}` doesn't match network `{1}` specified in the /// invoice. NetworkMismatch(AddressNetwork, Network), - #[display(doc_comments)] /// invalid query parameter {0}. InvalidQueryParam(String), #[from] + #[display(inner)] Id(baid58::Baid58ParseError), - #[display(doc_comments)] /// can't recognize beneficiary "": it should be either a bitcoin address or /// a blinded UTXO seal. Beneficiary(String), #[from] + #[display(inner)] Num(ParseIntError), #[from] - #[display(doc_comments)] /// invalid interface name. IfaceName(InvalidIdent), } @@ -255,47 +256,52 @@ impl FromStr for RgbInvoice { fn from_str(s: &str) -> Result { let uri = Uri::parse(s)?; - let scheme = uri.scheme().ok_or(InvoiceParseError::Invalid)?.to_string(); - if scheme != "rgb" { - return Err(InvoiceParseError::InvalidScheme(scheme)); + let scheme = uri.scheme().ok_or(InvoiceParseError::Invalid)?; + if scheme.as_str() != "rgb" { + return Err(InvoiceParseError::InvalidScheme(scheme.to_string())); } - let path = uri - .path() - .segments() - .map(|e| e.to_string()) - .collect::>(); + let mut path = uri.path().segments(); let mut network = None; let mut address_network = None; - let mut next_path_index = 0; - - let contract_id_str = &path[next_path_index]; - let contract = match ContractId::from_str(contract_id_str) { + let Some(contract_id_str) = path.next() else { + return Err(InvoiceParseError::ContractMissed); + }; + let contract = match ContractId::from_str(contract_id_str.as_str()) { Ok(cid) => Some(cid), - Err(_) if contract_id_str == &OMITTED.to_string() => None, - Err(_) => return Err(InvoiceParseError::InvalidContractId(contract_id_str.clone())), + Err(_) if contract_id_str.as_str() == OMITTED => None, + Err(_) => { + return Err(InvoiceParseError::InvalidContractId(contract_id_str.to_string())); + } }; - next_path_index += 1; - let iface_str = &path[next_path_index]; - let iface = match TypeName::try_from(iface_str.clone()) { + let Some(iface_str) = path.next() else { + return Err(InvoiceParseError::IfaceMissed); + }; + let iface = match TypeName::try_from(iface_str.to_string()) { Ok(i) => Some(i), - Err(_) if iface_str == &OMITTED.to_string() => None, - Err(_) => return Err(InvoiceParseError::InvalidIface(iface_str.clone())), + Err(_) if iface_str.as_str() == OMITTED => None, + Err(_) => return Err(InvoiceParseError::InvalidIface(iface_str.to_string())), }; - next_path_index += 1; if contract.is_some() && iface.is_none() { return Err(InvoiceParseError::ContractIdNoIface); } - let mut assignment = path[next_path_index].split('+'); + let Some(assignment) = path.next() else { + return Err(InvoiceParseError::AssignmentMissed); + }; + let (amount, beneficiary) = assignment + .as_str() + .split_once('+') + .map(|(a, b)| (Some(a), Some(b))) + .unwrap_or((Some(&assignment.as_str()), None)); // TODO: support other state types - let (beneficiary_str, value) = match (assignment.next(), assignment.next()) { + let (beneficiary_str, value) = match (amount, beneficiary) { (Some(a), Some(b)) => (b, InvoiceState::Amount(a.parse::()?)), (Some(b), None) => (b, InvoiceState::Void), - _ => return Err(InvoiceParseError::Invalid), + _ => unreachable!(), }; let beneficiary = @@ -316,7 +322,7 @@ impl FromStr for RgbInvoice { let mut query_params = map_query_params(&uri)?; let transports = if let Some(endpoints) = query_params.remove(ENDPOINTS) { - let tokens: Vec<&str> = endpoints.split(TRANSPORT_SEP).collect(); + let tokens = endpoints.split(TRANSPORT_SEP); let mut transport_vec: Vec = vec![]; for token in tokens { transport_vec.push( From ce5ee6b2969cac02206b6e99a4cef803b65dec1b Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 25 Dec 2023 00:41:29 +0100 Subject: [PATCH 27/31] persistence: complete fascia consumption --- src/persistence/hoard.rs | 4 ++++ src/persistence/inventory.rs | 17 +++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/persistence/hoard.rs b/src/persistence/hoard.rs index 8ca818f4..827012d0 100644 --- a/src/persistence/hoard.rs +++ b/src/persistence/hoard.rs @@ -52,6 +52,10 @@ pub enum ConsumeError { #[from] MergeReveal(MergeRevealError), + + /// bundle {1} for contract {0} contains invalid transitioon input map + #[display(doc_comments)] + InvalidBundle(ContractId, BundleId), } impl From for InventoryError { diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index 6df0ac1a..8242c223 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -355,21 +355,22 @@ pub trait Inventory: Deref { /// /// Must be called before the consignment is created, when witness /// transaction is not yet mined. - fn consume(&mut self, _fascia: Fascia) -> Result<(), InventoryError> { - todo!(); - /* + fn consume(&mut self, fascia: Fascia) -> Result<(), InventoryError> { let witness_id = fascia.anchor.witness_id(); unsafe { self.consume_anchor(fascia.anchor)? }; - for bundle in fascia.bundles { - let ids1 = bundle.known_transitions.keys().copied().collect::>(); + for (contract_id, bundle) in fascia.bundles { + let ids1 = bundle + .known_transitions + .keys() + .copied() + .collect::>(); let ids2 = bundle.input_map.values().copied().collect::>(); if !ids1.is_subset(&ids2) { - + return Err(ConsumeError::InvalidBundle(contract_id, bundle.bundle_id()).into()); } - unsafe { self.consume_bundle(fascia.contract_id, bundle, witness_id)? }; + unsafe { self.consume_bundle(contract_id, bundle, witness_id)? }; } Ok(()) - */ } #[doc(hidden)] From a251ad3ce06239629697665369c9a6070b02dffd Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 25 Dec 2023 00:52:03 +0100 Subject: [PATCH 28/31] persistence: fix DataError display --- src/persistence/inventory.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index 8242c223..17a501f8 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -186,7 +186,7 @@ impl From> for InventoryError { } #[derive(Debug, Display, Error, From)] -#[display(inner)] +#[display(doc_comments)] pub enum DataError { /// the consignment was not validated by the local host and thus can't be /// imported. @@ -208,8 +208,8 @@ pub enum DataError { /// mismatch between witness seal chain and anchor chain. ChainMismatch, - #[display(inner)] #[from] + #[display(inner)] Reveal(RevealError), #[from] @@ -220,15 +220,18 @@ pub enum DataError { OutpointUnknown(OutputSeal, ContractId), #[from] + #[display(inner)] Confinement(confinement::Error), #[from] + #[display(inner)] IfaceImpl(IfaceImplError), /// schema {0} doesn't implement interface {1}. NoIfaceImpl(SchemaId, IfaceId), #[from] + #[display(inner)] HeightResolver(Box), /// Information is concealed. From dbd5f511c093f11d64250363dd690ee6bf32acfa Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 25 Dec 2023 02:12:47 +0100 Subject: [PATCH 29/31] containers: impl Display for UniversalBindle --- src/containers/bindle.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/containers/bindle.rs b/src/containers/bindle.rs index 5a97b42e..33ca1552 100644 --- a/src/containers/bindle.rs +++ b/src/containers/bindle.rs @@ -291,7 +291,8 @@ impl Bindle { } } -#[derive(Clone, Debug, From)] +#[derive(Clone, Debug, Display, From)] +#[display(inner)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), From d8a1161b68f4294797ce0721ebd87469d40b3198 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 25 Dec 2023 17:48:03 +0100 Subject: [PATCH 30/31] containers: impl DerefMut for Bindle --- src/containers/bindle.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/containers/bindle.rs b/src/containers/bindle.rs index 33ca1552..e9874ee4 100644 --- a/src/containers/bindle.rs +++ b/src/containers/bindle.rs @@ -26,7 +26,7 @@ use std::collections::BTreeMap; use std::fmt::{Debug, Display}; use std::io::{self, Read}; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use std::str::FromStr; use amplify::confinement::{self, Confined, TinyVec, U24}; @@ -155,6 +155,10 @@ impl Deref for Bindle { fn deref(&self) -> &Self::Target { &self.data } } +impl DerefMut for Bindle { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } +} + impl From for Bindle { fn from(data: C) -> Self { Bindle::new(data) } } From 8137ca4f0839a11709c382543a8e90e7206646a3 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 25 Dec 2023 23:10:33 +0100 Subject: [PATCH 31/31] chore: fix clippy --- invoice/src/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invoice/src/parse.rs b/invoice/src/parse.rs index ef7e0832..02a3e48b 100644 --- a/invoice/src/parse.rs +++ b/invoice/src/parse.rs @@ -296,7 +296,7 @@ impl FromStr for RgbInvoice { .as_str() .split_once('+') .map(|(a, b)| (Some(a), Some(b))) - .unwrap_or((Some(&assignment.as_str()), None)); + .unwrap_or((Some(assignment.as_str()), None)); // TODO: support other state types let (beneficiary_str, value) = match (amount, beneficiary) { (Some(a), Some(b)) => (b, InvoiceState::Amount(a.parse::()?)),