diff --git a/Cargo.lock b/Cargo.lock index d84c1afa3..a48f7c2cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -635,7 +635,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", - "tower 0.5.1", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -731,6 +731,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.10.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" + [[package]] name = "bech32" version = "0.11.0" @@ -782,6 +788,20 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitcoin" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" +dependencies = [ + "bech32 0.10.0-beta", + "bitcoin-internals", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1 0.28.2", +] + [[package]] name = "bitcoin-bech32" version = "0.13.0" @@ -972,9 +992,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "regex-automata 0.4.9", @@ -1914,7 +1934,7 @@ dependencies = [ "rpc-description", "rstest", "schnorrkel", - "secp256k1", + "secp256k1 0.29.1", "serde", "serialization", "sha-1 0.10.1", @@ -3139,6 +3159,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hexf-parse" version = "0.2.1" @@ -3147,9 +3173,9 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hickory-client" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab9683b08d8f8957a857b0236455d80e1886eaa8c6178af556aa7871fb61b55" +checksum = "949d2fef0bbdd31a0f6affc6bf390b4a0017492903eff6f7516cb382d9e85536" dependencies = [ "cfg-if", "data-encoding", @@ -3166,9 +3192,9 @@ dependencies = [ [[package]] name = "hickory-proto" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" +checksum = "447afdcdb8afb9d0a852af6dc65d9b285ce720ed7a59e42a8bf2e931c67bc1b5" dependencies = [ "async-trait", "cfg-if", @@ -3177,7 +3203,7 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 0.4.0", + "idna", "ipnet", "once_cell", "rand 0.8.5", @@ -3190,9 +3216,9 @@ dependencies = [ [[package]] name = "hickory-server" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be0e43c556b9b3fdb6c7c71a9a32153a2275d02419e3de809e520bfcfe40c37" +checksum = "35e6d1c2df0614595224b32479c72dd6fc82c9bda85962907c45fdb95a691489" dependencies = [ "async-trait", "bytes", @@ -3731,16 +3757,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "1.0.3" @@ -4143,7 +4159,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", ] [[package]] @@ -4169,6 +4185,18 @@ dependencies = [ "escape8259", ] +[[package]] +name = "libusb1-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -5539,7 +5567,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -5922,11 +5950,31 @@ dependencies = [ "unarray", ] +[[package]] +name = "protobuf" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror", +] + +[[package]] +name = "protobuf-support" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c" +dependencies = [ + "thiserror", +] + [[package]] name = "psl" -version = "2.1.67" +version = "2.1.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b12d418db33ae2247c46f2685d2b1530b315de16b253da8fc964eeeb8eb8371" +checksum = "e5b6a7f841e8c42f9efabd5ad60569b357ad5771e790162a5868c84716853c2b" dependencies = [ "psl-types", ] @@ -6139,9 +6187,9 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.22.5" +version = "0.22.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a04b892cb6f91951f144c33321843790c8574c825aafdb16d815fd7183b5229" +checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f" dependencies = [ "bytemuck", "font-types", @@ -6167,9 +6215,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -6524,6 +6572,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rusb" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" +dependencies = [ + "libc", + "libusb1-sys", +] + [[package]] name = "rusqlite" version = "0.32.1" @@ -6843,6 +6901,16 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "bitcoin_hashes", + "secp256k1-sys 0.9.2", +] + [[package]] name = "secp256k1" version = "0.29.1" @@ -6850,7 +6918,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.10.1", +] + +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", ] [[package]] @@ -6893,15 +6970,15 @@ checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -6917,9 +6994,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -7390,7 +7467,7 @@ dependencies = [ "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "rustix", "tiny-xlib", "wasm-bindgen", @@ -8137,14 +8214,14 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -8274,6 +8351,36 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "trezor-client" +version = "0.1.4" +source = "git+https://github.com/mintlayer/mintlayer-trezor-firmware?branch=feature/mintlayer-pk#9fb3fc1dd1de9e53ce3e14c03a1d47c29e6dc74c" +dependencies = [ + "bitcoin", + "byteorder", + "hex", + "protobuf", + "rusb", + "thiserror", + "tracing", + "unicode-normalization", +] + +[[package]] +name = "trezor-common" +version = "1.0.1" +dependencies = [ + "common", + "crypto", + "num-derive", + "num-traits", + "parity-scale-codec", + "rstest", + "strum", + "test-utils", + "trezor-client", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -8459,7 +8566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 1.0.3", + "idna", "percent-encoding", "serde", ] @@ -9373,7 +9480,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "wasite", "web-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 31bb03d5e..4629b3887 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ members = [ "utils", # Various utilities. "utils/networking", # Various async/tokio utilities. "utxo", # Utxo and related utilities (cache, undo, etc.). + "trezor-common", # Code used by Trezor firmware repository. "wallet", # Wallet primitives. "wallet/wallet-cli", # Wallet CLI/REPL binary. "wallet/wallet-cli-lib", # Wallet CLI/REPL lib. diff --git a/build-tools/codecheck/codecheck.py b/build-tools/codecheck/codecheck.py index c17de5fc9..679779b1e 100755 --- a/build-tools/codecheck/codecheck.py +++ b/build-tools/codecheck/codecheck.py @@ -377,7 +377,7 @@ def check_trailing_whitespaces(): def run_checks(): return all([ - disallow(SCALECODEC_RE, exclude = ['serialization/core', 'merkletree']), + disallow(SCALECODEC_RE, exclude = ['serialization/core', 'trezor-common']), disallow(JSONRPSEE_RE, exclude = ['rpc']), check_local_licenses(), check_crate_versions(), diff --git a/trezor-common/Cargo.toml b/trezor-common/Cargo.toml new file mode 100644 index 000000000..f9031a338 --- /dev/null +++ b/trezor-common/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "trezor-common" +license.workspace = true +version.workspace = true +edition.workspace = true +rust-version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num-derive.workspace = true +num-traits = { version = "0.2", default-features = false, features = ["libm"] } +parity-scale-codec = { version = "3.1", default-features = false, features = ["derive", "chain-error"] } +strum = { version = "0.26", default-features = false, features = ["derive"] } + +[dev-dependencies] +test-utils = { path = "../test-utils" } +common = { path = "../common" } +crypto = { path = "../crypto/" } +trezor-client = { git = "https://github.com/mintlayer/mintlayer-trezor-firmware", branch = "feature/mintlayer-pk", features = ["bitcoin", "mintlayer"] } + +rstest.workspace = true diff --git a/trezor-common/src/lib.rs b/trezor-common/src/lib.rs new file mode 100644 index 000000000..54850703d --- /dev/null +++ b/trezor-common/src/lib.rs @@ -0,0 +1,375 @@ +// Copyright (c) 2024 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// 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. + +//! Common code used by Trezor firware with no-std + +#![no_std] + +use num_derive::FromPrimitive; +use parity_scale_codec::{Decode, Encode}; +use strum::{EnumDiscriminants, EnumIter}; + +/// Specifies which parts of the transaction a signature commits to. +/// +/// The values of the flags are the same as in Bitcoin. +#[derive(Eq, PartialEq, Clone, Copy, Debug, Ord, PartialOrd, Encode, Decode)] +pub struct SigHashType(u8); + +impl SigHashType { + pub const ALL: u8 = 0x01; + pub const NONE: u8 = 0x02; + pub const SINGLE: u8 = 0x03; + pub const ANYONECANPAY: u8 = 0x80; + + pub const MASK_OUT: u8 = 0x7f; + pub const MASK_IN: u8 = 0x80; + + pub fn get(&self) -> u8 { + self.0 + } +} + +type UnsignedIntType = u128; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] +pub struct Amount { + #[codec(compact)] + atoms: UnsignedIntType, +} + +impl Amount { + pub const MAX: Self = Self::from_atoms(UnsignedIntType::MAX); + pub const ZERO: Self = Self::from_atoms(0); + + pub const fn from_atoms(v: UnsignedIntType) -> Self { + Amount { atoms: v } + } + + pub const fn into_atoms(&self) -> UnsignedIntType { + self.atoms + } + + pub fn from_bytes_be(bytes: &[u8]) -> Option { + bytes + .try_into() + .ok() + .map(|b| Self::from_atoms(UnsignedIntType::from_be_bytes(b))) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub enum OutputValue { + Coin(Amount), + TokenV0, + TokenV1(H256, Amount), +} + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, EnumDiscriminants)] +#[strum_discriminants(name(OutputTimeLockTag), derive(EnumIter, FromPrimitive))] +pub enum OutputTimeLock { + #[codec(index = 0)] + UntilHeight(#[codec(compact)] u64), + #[codec(index = 1)] + UntilTime(#[codec(compact)] u64), + #[codec(index = 2)] + ForBlockCount(#[codec(compact)] u64), + #[codec(index = 3)] + ForSeconds(#[codec(compact)] u64), +} + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub struct StakePoolData { + pub pledge: Amount, + pub staker: Destination, + pub vrf_public_key: VRFPublicKeyHolder, + pub decommission_key: Destination, + pub margin_ratio_per_thousand: u16, + pub cost_per_block: Amount, +} + +const HASH_SIZE: usize = 20; +const PK_SIZE: usize = 33; +const VRF_PK_SIZE: usize = 32; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] +pub struct PublicKeyHash(pub [u8; HASH_SIZE]); + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] +pub struct PublicKey(pub [u8; PK_SIZE]); + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] +pub struct VRFPublicKey(pub [u8; VRF_PK_SIZE]); + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Decode, Encode)] +pub enum VRFPublicKeyHolder { + #[codec(index = 0)] + Schnorrkel(VRFPublicKey), +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Decode, Encode)] +pub enum PublicKeyHolder { + #[codec(index = 0)] + Secp256k1Schnorr(PublicKey), +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] +pub enum Destination { + #[codec(index = 0)] + AnyoneCanSpend, /* zero verification; used primarily for testing. Never use this for real + * money */ + #[codec(index = 1)] + PublicKeyHash(PublicKeyHash), + #[codec(index = 2)] + PublicKey(PublicKeyHolder), + #[codec(index = 3)] + ScriptHash(H256), + #[codec(index = 4)] + ClassicMultisig(PublicKeyHash), +} + +#[derive(Encode)] +pub enum TokenIssuance { + #[codec(index = 1)] + V1(TokenIssuanceV1), +} + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, FromPrimitive)] +pub enum IsTokenFreezable { + #[codec(index = 0)] + No, + #[codec(index = 1)] + Yes, +} + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, EnumDiscriminants)] +#[strum_discriminants(name(TokenTotalSupplyTag), derive(EnumIter, FromPrimitive))] +pub enum TokenTotalSupply { + #[codec(index = 0)] + Fixed(Amount), // fixed to a certain amount + #[codec(index = 1)] + Lockable, // not known in advance but can be locked once at some point in time + #[codec(index = 2)] + Unlimited, // limited only by the Amount data type +} + +#[derive(Encode)] +pub struct TokenIssuanceV1 { + pub token_ticker: parity_scale_codec::alloc::vec::Vec, + pub number_of_decimals: u8, + pub metadata_uri: parity_scale_codec::alloc::vec::Vec, + pub total_supply: TokenTotalSupply, + pub authority: Destination, + pub is_freezable: IsTokenFreezable, +} + +#[derive(Encode)] +pub enum NftIssuance { + #[codec(index = 0)] + V0(NftIssuanceV0), +} + +#[derive(Encode)] +pub struct NftIssuanceV0 { + pub metadata: Metadata, +} + +#[derive(Encode)] +pub struct Metadata { + pub creator: Option, + pub name: parity_scale_codec::alloc::vec::Vec, + pub description: parity_scale_codec::alloc::vec::Vec, + pub ticker: parity_scale_codec::alloc::vec::Vec, + pub icon_uri: parity_scale_codec::alloc::vec::Vec, + pub additional_metadata_uri: parity_scale_codec::alloc::vec::Vec, + pub media_uri: parity_scale_codec::alloc::vec::Vec, + pub media_hash: parity_scale_codec::alloc::vec::Vec, +} + +#[derive(Encode)] +pub struct OrderData { + /// The key that can authorize conclusion of an order + pub conclude_key: Destination, + /// `Ask` and `give` fields represent amounts of currencies + /// that an order maker wants to exchange. + /// E.g. Creator of an order asks for 5 coins and gives 10 tokens in + /// exchange. + pub ask: OutputValue, + pub give: OutputValue, +} + +#[derive(Encode)] +pub enum TxOutput { + /// Transfer an output, giving the provided Destination the authority to + /// spend it (no conditions) + #[codec(index = 0)] + Transfer(OutputValue, Destination), + /// Same as Transfer, but with the condition that an output can only be + /// specified after some point in time. + #[codec(index = 1)] + LockThenTransfer(OutputValue, Destination, OutputTimeLock), + /// Burn an amount (whether coin or token) + #[codec(index = 2)] + Burn(OutputValue), + /// Output type that is used to create a stake pool + #[codec(index = 3)] + CreateStakePool(H256, StakePoolData), + /// Output type that represents spending of a stake pool output in a block + /// reward in order to produce a block + #[codec(index = 4)] + ProduceBlockFromStake(Destination, H256), + /// Create a delegation; takes the owner destination (address authorized to + /// withdraw from the delegation) and a pool id + #[codec(index = 5)] + CreateDelegationId(Destination, H256), + /// Transfer an amount to a delegation that was previously created for + /// staking + #[codec(index = 6)] + DelegateStaking(Amount, H256), + #[codec(index = 7)] + IssueFungibleToken(TokenIssuance), + #[codec(index = 8)] + IssueNft(H256, NftIssuance, Destination), + #[codec(index = 9)] + DataDeposit(parity_scale_codec::alloc::vec::Vec), + #[codec(index = 10)] + Htlc(OutputValue, HashedTimelockContract), + #[codec(index = 11)] + CreateOrder(OrderData), +} + +#[derive(Encode)] +pub struct HashedTimelockContract { + // can be spent either by a specific address that knows the secret + pub secret_hash: HtlcSecretHash, + pub spend_key: Destination, + + // or by a multisig after timelock expires making it possible to refund + pub refund_timelock: OutputTimeLock, + pub refund_key: Destination, +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Encode, Decode)] +pub struct H256(pub [u8; 32]); + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Encode, Decode)] +pub struct HtlcSecretHash(pub [u8; 20]); + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Ord, PartialOrd, EnumDiscriminants)] +#[strum_discriminants(name(OutPointSourceIdTag), derive(EnumIter, FromPrimitive))] +pub enum OutPointSourceId { + #[codec(index = 0)] + Transaction(H256), + #[codec(index = 1)] + BlockReward(H256), +} + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Ord, PartialOrd)] +pub struct UtxoOutPoint { + id: OutPointSourceId, + index: u32, +} + +impl UtxoOutPoint { + pub fn new(outpoint_source_id: OutPointSourceId, output_index: u32) -> Self { + UtxoOutPoint { + id: outpoint_source_id, + index: output_index, + } + } + + pub fn source_id(&self) -> OutPointSourceId { + self.id.clone() + } + + pub fn output_index(&self) -> u32 { + self.index + } +} + +#[derive(Encode)] +pub enum AccountSpending { + #[codec(index = 0)] + DelegationBalance(H256, Amount), +} + +#[derive(Encode)] +pub struct AccountOutPoint { + #[codec(compact)] + pub nonce: u64, + pub account: AccountSpending, +} + +#[derive(Encode, Decode)] +pub enum IsTokenUnfreezable { + #[codec(index = 0)] + No, + #[codec(index = 1)] + Yes, +} + +type OrderId = H256; +type TokenId = H256; + +#[derive(Encode, EnumDiscriminants)] +#[strum_discriminants(name(AccountCommandTag), derive(EnumIter, FromPrimitive))] +pub enum AccountCommand { + // Create certain amount of tokens and add them to circulating supply + #[codec(index = 0)] + MintTokens(TokenId, Amount), + // Take tokens out of circulation. Not the same as Burn because unminting means that certain + // amount of tokens is no longer supported by underlying fiat currency, which can only be + // done by the authority. + #[codec(index = 1)] + UnmintTokens(TokenId), + // After supply is locked tokens cannot be minted or unminted ever again. + // Works only for Lockable tokens supply. + #[codec(index = 2)] + LockTokenSupply(TokenId), + // Freezing token forbids any operation with all the tokens (except for optional unfreeze) + #[codec(index = 3)] + FreezeToken(TokenId, IsTokenUnfreezable), + // By unfreezing token all operations are available for the tokens again + #[codec(index = 4)] + UnfreezeToken(TokenId), + // Change the authority who can authorize operations for a token + #[codec(index = 5)] + ChangeTokenAuthority(TokenId, Destination), + #[codec(index = 6)] + ConcludeOrder(OrderId), + #[codec(index = 7)] + FillOrder(OrderId, Amount, Destination), + // Change token metadata uri + #[codec(index = 8)] + ChangeTokenMetadataUri(TokenId, parity_scale_codec::alloc::vec::Vec), +} + +#[derive(Encode)] +pub enum TxInput { + #[codec(index = 0)] + Utxo(UtxoOutPoint), + #[codec(index = 1)] + Account(AccountOutPoint), + #[codec(index = 2)] + AccountCommand(#[codec(compact)] u64, AccountCommand), +} + +#[cfg(test)] +extern crate std; + +#[cfg(test)] +use std::prelude::v1::*; + +#[cfg(test)] +mod tests; diff --git a/trezor-common/src/tests/mod.rs b/trezor-common/src/tests/mod.rs new file mode 100644 index 000000000..363b26259 --- /dev/null +++ b/trezor-common/src/tests/mod.rs @@ -0,0 +1,745 @@ +// Copyright (c) 2024 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// 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::{boxed::Box, println, vec::Vec}; + +use common::{chain, primitives}; +use crypto::key::KeyKind; +use parity_scale_codec::{DecodeAll, Encode}; +use rstest::rstest; +use strum::IntoEnumIterator; +use test_utils::random::{make_seedable_rng, CryptoRng, IteratorRandom, Rng, Seed}; + +impl From for crate::H256 { + fn from(value: primitives::H256) -> Self { + Self(value.0) + } +} + +impl From for crate::OutPointSourceId { + fn from(value: chain::OutPointSourceId) -> Self { + match value { + chain::OutPointSourceId::Transaction(tx) => Self::Transaction(tx.to_hash().into()), + chain::OutPointSourceId::BlockReward(tx) => Self::BlockReward(tx.to_hash().into()), + } + } +} + +impl From for crate::UtxoOutPoint { + fn from(value: chain::UtxoOutPoint) -> Self { + Self { + id: value.source_id().into(), + index: value.output_index(), + } + } +} + +impl From for crate::Amount { + fn from(value: primitives::Amount) -> Self { + Self::from_atoms(value.into_atoms()) + } +} + +impl From for crate::AccountSpending { + fn from(value: chain::AccountSpending) -> Self { + match value { + chain::AccountSpending::DelegationBalance(delegation_id, amount) => { + Self::DelegationBalance(delegation_id.to_hash().into(), amount.into()) + } + } + } +} + +impl From for crate::AccountOutPoint { + fn from(value: chain::AccountOutPoint) -> Self { + Self { + nonce: value.nonce().value(), + account: value.account().clone().into(), + } + } +} + +impl From for crate::IsTokenUnfreezable { + fn from(value: chain::tokens::IsTokenUnfreezable) -> Self { + match value { + chain::tokens::IsTokenUnfreezable::No => Self::No, + chain::tokens::IsTokenUnfreezable::Yes => Self::Yes, + } + } +} + +impl From for crate::PublicKeyHolder { + fn from(value: crypto::key::PublicKey) -> Self { + crate::PublicKeyHolder::decode_all(&mut value.encode().as_slice()).unwrap() + } +} + +impl From for crate::PublicKeyHash { + fn from(value: common::address::pubkeyhash::PublicKeyHash) -> Self { + Self(value.0) + } +} + +impl From for crate::Destination { + fn from(value: common::chain::Destination) -> Self { + match value { + chain::Destination::AnyoneCanSpend => Self::AnyoneCanSpend, + chain::Destination::PublicKey(pk) => Self::PublicKey(pk.into()), + chain::Destination::ScriptHash(id) => Self::ScriptHash(id.to_hash().into()), + chain::Destination::PublicKeyHash(pkh) => Self::PublicKeyHash(pkh.into()), + chain::Destination::ClassicMultisig(pkh) => Self::ClassicMultisig(pkh.into()), + } + } +} + +impl From for crate::AccountCommand { + fn from(value: chain::AccountCommand) -> Self { + match value { + chain::AccountCommand::MintTokens(token_id, amount) => { + Self::MintTokens(token_id.to_hash().into(), amount.into()) + } + chain::AccountCommand::UnmintTokens(token_id) => { + Self::UnmintTokens(token_id.to_hash().into()) + } + chain::AccountCommand::LockTokenSupply(token_id) => { + Self::LockTokenSupply(token_id.to_hash().into()) + } + chain::AccountCommand::FreezeToken(token_id, is_unfreezable) => { + Self::FreezeToken(token_id.to_hash().into(), is_unfreezable.into()) + } + chain::AccountCommand::UnfreezeToken(token_id) => { + Self::UnfreezeToken(token_id.to_hash().into()) + } + chain::AccountCommand::ChangeTokenAuthority(token_id, dest) => { + Self::ChangeTokenAuthority(token_id.to_hash().into(), dest.into()) + } + chain::AccountCommand::ConcludeOrder(order_id) => { + Self::ConcludeOrder(order_id.to_hash().into()) + } + chain::AccountCommand::FillOrder(order_id, amount, dest) => { + Self::FillOrder(order_id.to_hash().into(), amount.into(), dest.into()) + } + chain::AccountCommand::ChangeTokenMetadataUri(token_id, uri) => { + Self::ChangeTokenMetadataUri(token_id.to_hash().into(), uri) + } + } + } +} + +impl From for crate::TxInput { + fn from(value: chain::TxInput) -> Self { + match value { + chain::TxInput::Utxo(utxo) => Self::Utxo(utxo.into()), + chain::TxInput::Account(acc) => Self::Account(acc.into()), + chain::TxInput::AccountCommand(nonce, command) => { + Self::AccountCommand(nonce.value(), command.into()) + } + } + } +} + +impl From for crate::OutputValue { + fn from(value: chain::output_value::OutputValue) -> Self { + match value { + chain::output_value::OutputValue::Coin(amount) => Self::Coin(amount.into()), + chain::output_value::OutputValue::TokenV0(_) => panic!("unsupported V0"), + chain::output_value::OutputValue::TokenV1(token_id, amount) => { + Self::TokenV1(token_id.to_hash().into(), amount.into()) + } + } + } +} + +impl From for crate::OutputTimeLock { + fn from(value: chain::timelock::OutputTimeLock) -> Self { + match value { + chain::timelock::OutputTimeLock::UntilHeight(x) => Self::UntilHeight(x.into_int()), + chain::timelock::OutputTimeLock::UntilTime(x) => Self::UntilTime(x.as_int_seconds()), + chain::timelock::OutputTimeLock::ForSeconds(x) => Self::ForSeconds(x), + chain::timelock::OutputTimeLock::ForBlockCount(x) => Self::ForBlockCount(x), + } + } +} + +impl From for crate::HashedTimelockContract { + fn from(value: chain::htlc::HashedTimelockContract) -> Self { + Self { + secret_hash: crate::HtlcSecretHash(value.secret_hash.0), + spend_key: value.spend_key.into(), + refund_timelock: value.refund_timelock.into(), + refund_key: value.refund_key.into(), + } + } +} + +impl From for crate::OrderData { + fn from(value: chain::OrderData) -> Self { + Self { + conclude_key: value.conclude_key().clone().into(), + ask: value.ask().clone().into(), + give: value.give().clone().into(), + } + } +} + +impl From<&crypto::vrf::VRFPublicKey> for crate::VRFPublicKeyHolder { + fn from(value: &crypto::vrf::VRFPublicKey) -> Self { + Self::decode_all(&mut value.encode().as_slice()).unwrap() + } +} + +impl From for crate::StakePoolData { + fn from(value: chain::stakelock::StakePoolData) -> Self { + Self { + pledge: value.pledge().into(), + staker: value.staker().clone().into(), + vrf_public_key: value.vrf_public_key().into(), + decommission_key: value.decommission_key().clone().into(), + margin_ratio_per_thousand: value.margin_ratio_per_thousand().value(), + cost_per_block: value.cost_per_block().into(), + } + } +} + +impl From for crate::NftIssuance { + fn from(value: chain::tokens::NftIssuance) -> Self { + match value { + chain::tokens::NftIssuance::V0(data) => Self::V0(crate::NftIssuanceV0 { + metadata: crate::Metadata { + creator: data.metadata.creator.map(|c| c.public_key.into()), + name: data.metadata.name, + description: data.metadata.description, + ticker: data.metadata.ticker, + icon_uri: data + .metadata + .icon_uri + .as_ref() + .clone() + .map_or(Vec::new(), Into::into), + additional_metadata_uri: data + .metadata + .additional_metadata_uri + .as_ref() + .clone() + .map_or(Vec::new(), Into::into), + media_uri: data + .metadata + .media_uri + .as_ref() + .clone() + .map_or(Vec::new(), Into::into), + media_hash: data.metadata.media_hash, + }, + }), + } + } +} + +impl From for crate::TokenTotalSupply { + fn from(value: chain::tokens::TokenTotalSupply) -> Self { + match value { + chain::tokens::TokenTotalSupply::Lockable => Self::Lockable, + chain::tokens::TokenTotalSupply::Unlimited => Self::Unlimited, + chain::tokens::TokenTotalSupply::Fixed(amount) => Self::Fixed(amount.into()), + } + } +} + +impl From for crate::IsTokenFreezable { + fn from(value: chain::tokens::IsTokenFreezable) -> Self { + match value { + chain::tokens::IsTokenFreezable::No => Self::No, + chain::tokens::IsTokenFreezable::Yes => Self::Yes, + } + } +} + +impl From for crate::TokenIssuance { + fn from(value: chain::tokens::TokenIssuance) -> Self { + match value { + chain::tokens::TokenIssuance::V1(data) => Self::V1(crate::TokenIssuanceV1 { + token_ticker: data.token_ticker, + number_of_decimals: data.number_of_decimals, + metadata_uri: data.metadata_uri, + total_supply: data.total_supply.into(), + authority: data.authority.into(), + is_freezable: data.is_freezable.into(), + }), + } + } +} + +impl From for crate::TxOutput { + fn from(value: chain::TxOutput) -> Self { + match value { + chain::TxOutput::Transfer(value, dest) => Self::Transfer(value.into(), dest.into()), + chain::TxOutput::LockThenTransfer(value, dest, lock) => { + Self::LockThenTransfer(value.into(), dest.into(), lock.into()) + } + chain::TxOutput::Burn(amount) => Self::Burn(amount.into()), + chain::TxOutput::DataDeposit(data) => Self::DataDeposit(data), + chain::TxOutput::CreateDelegationId(dest, pool_id) => { + Self::CreateDelegationId(dest.into(), pool_id.to_hash().into()) + } + chain::TxOutput::DelegateStaking(amount, delegation_id) => { + Self::DelegateStaking(amount.into(), delegation_id.to_hash().into()) + } + chain::TxOutput::ProduceBlockFromStake(dest, pool_id) => { + Self::ProduceBlockFromStake(dest.into(), pool_id.to_hash().into()) + } + chain::TxOutput::Htlc(value, lock) => Self::Htlc(value.into(), (*lock).into()), + chain::TxOutput::CreateOrder(data) => Self::CreateOrder((*data).into()), + chain::TxOutput::CreateStakePool(pool_id, data) => { + Self::CreateStakePool(pool_id.to_hash().into(), (*data).into()) + } + chain::TxOutput::IssueNft(token_id, data, dest) => { + Self::IssueNft(token_id.to_hash().into(), (*data).into(), dest.into()) + } + chain::TxOutput::IssueFungibleToken(data) => Self::IssueFungibleToken((*data).into()), + } + } +} + +fn make_random_destination(rng: &mut (impl Rng + CryptoRng)) -> chain::Destination { + match chain::DestinationTag::iter().choose(rng).unwrap() { + chain::DestinationTag::AnyoneCanSpend => chain::Destination::AnyoneCanSpend, + chain::DestinationTag::PublicKey => chain::Destination::PublicKey( + crypto::key::PrivateKey::new_from_rng(rng, KeyKind::Secp256k1Schnorr).1, + ), + chain::DestinationTag::ScriptHash => { + chain::Destination::ScriptHash(primitives::H256(rng.gen()).into()) + } + chain::DestinationTag::ClassicMultisig => chain::Destination::ClassicMultisig( + (&crypto::key::PrivateKey::new_from_rng(rng, KeyKind::Secp256k1Schnorr).1).into(), + ), + chain::DestinationTag::PublicKeyHash => chain::Destination::PublicKeyHash( + (&crypto::key::PrivateKey::new_from_rng(rng, KeyKind::Secp256k1Schnorr).1).into(), + ), + } +} + +fn make_random_bytes(rng: &mut (impl Rng + CryptoRng)) -> Vec { + (0..rng.gen_range(1..10)).map(|_| rng.gen()).collect() +} + +fn make_random_account_command(rng: &mut (impl Rng + CryptoRng)) -> chain::AccountCommand { + match crate::AccountCommandTag::iter().choose(rng).unwrap() { + crate::AccountCommandTag::MintTokens => chain::AccountCommand::MintTokens( + primitives::H256(rng.gen()).into(), + primitives::Amount::from_atoms(rng.gen()), + ), + crate::AccountCommandTag::UnmintTokens => { + chain::AccountCommand::UnmintTokens(primitives::H256(rng.gen()).into()) + } + crate::AccountCommandTag::LockTokenSupply => { + chain::AccountCommand::LockTokenSupply(primitives::H256(rng.gen()).into()) + } + crate::AccountCommandTag::FreezeToken => chain::AccountCommand::FreezeToken( + primitives::H256(rng.gen()).into(), + if rng.gen::() { + chain::tokens::IsTokenUnfreezable::Yes + } else { + chain::tokens::IsTokenUnfreezable::No + }, + ), + crate::AccountCommandTag::UnfreezeToken => { + chain::AccountCommand::UnfreezeToken(primitives::H256(rng.gen()).into()) + } + crate::AccountCommandTag::ChangeTokenAuthority => { + chain::AccountCommand::ChangeTokenAuthority( + primitives::H256(rng.gen()).into(), + make_random_destination(rng), + ) + } + crate::AccountCommandTag::ConcludeOrder => { + chain::AccountCommand::ConcludeOrder(primitives::H256(rng.gen()).into()) + } + crate::AccountCommandTag::FillOrder => chain::AccountCommand::FillOrder( + primitives::H256(rng.gen()).into(), + primitives::Amount::from_atoms(rng.gen()), + make_random_destination(rng), + ), + crate::AccountCommandTag::ChangeTokenMetadataUri => { + chain::AccountCommand::ChangeTokenMetadataUri( + primitives::H256(rng.gen()).into(), + make_random_bytes(rng), + ) + } + } +} + +fn make_random_input(rng: &mut (impl Rng + CryptoRng)) -> chain::TxInput { + match rng.gen_range(0..=2) { + 0 => chain::TxInput::Utxo(chain::UtxoOutPoint::new( + chain::OutPointSourceId::Transaction(primitives::H256(rng.gen()).into()), + rng.gen(), + )), + 1 => chain::TxInput::Account(chain::AccountOutPoint::new( + chain::AccountNonce::new(rng.gen()), + chain::AccountSpending::DelegationBalance( + primitives::H256(rng.gen()).into(), + primitives::Amount::from_atoms(rng.gen()), + ), + )), + _ => chain::TxInput::AccountCommand( + chain::AccountNonce::new(rng.gen()), + make_random_account_command(rng), + ), + } +} + +fn make_random_value(rng: &mut (impl Rng + CryptoRng)) -> chain::output_value::OutputValue { + if rng.gen::() { + chain::output_value::OutputValue::Coin(primitives::Amount::from_atoms(rng.gen())) + } else { + chain::output_value::OutputValue::TokenV1( + primitives::H256(rng.gen()).into(), + primitives::Amount::from_atoms(rng.gen()), + ) + } +} + +fn make_random_lock(rng: &mut (impl Rng + CryptoRng)) -> chain::timelock::OutputTimeLock { + match crate::OutputTimeLockTag::iter().choose(rng).unwrap() { + crate::OutputTimeLockTag::UntilHeight => { + chain::timelock::OutputTimeLock::UntilHeight(primitives::BlockHeight::new(rng.gen())) + } + crate::OutputTimeLockTag::UntilTime => chain::timelock::OutputTimeLock::UntilTime( + chain::block::timestamp::BlockTimestamp::from_int_seconds(rng.gen()), + ), + crate::OutputTimeLockTag::ForSeconds => { + chain::timelock::OutputTimeLock::ForSeconds(rng.gen()) + } + crate::OutputTimeLockTag::ForBlockCount => { + chain::timelock::OutputTimeLock::ForBlockCount(rng.gen()) + } + } +} + +fn make_random_token_total_supply( + rng: &mut (impl Rng + CryptoRng), +) -> chain::tokens::TokenTotalSupply { + match crate::TokenTotalSupplyTag::iter().choose(rng).unwrap() { + crate::TokenTotalSupplyTag::Unlimited => chain::tokens::TokenTotalSupply::Unlimited, + crate::TokenTotalSupplyTag::Lockable => chain::tokens::TokenTotalSupply::Lockable, + crate::TokenTotalSupplyTag::Fixed => { + chain::tokens::TokenTotalSupply::Fixed(primitives::Amount::from_atoms(rng.gen())) + } + } +} + +fn make_random_output(rng: &mut (impl Rng + CryptoRng)) -> chain::TxOutput { + match rng.gen_range(0..=11) { + 0 => chain::TxOutput::Transfer(make_random_value(rng), make_random_destination(rng)), + 1 => chain::TxOutput::LockThenTransfer( + make_random_value(rng), + make_random_destination(rng), + make_random_lock(rng), + ), + 2 => chain::TxOutput::Burn(make_random_value(rng)), + 3 => chain::TxOutput::CreateStakePool( + primitives::H256(rng.gen()).into(), + Box::new(chain::stakelock::StakePoolData::new( + primitives::Amount::from_atoms(rng.gen()), + make_random_destination(rng), + crypto::vrf::VRFPrivateKey::new_from_rng(rng, crypto::vrf::VRFKeyKind::Schnorrkel) + .1, + make_random_destination(rng), + primitives::per_thousand::PerThousand::new_from_rng(rng), + primitives::Amount::from_atoms(rng.gen()), + )), + ), + 4 => chain::TxOutput::ProduceBlockFromStake( + make_random_destination(rng), + primitives::H256(rng.gen()).into(), + ), + 5 => chain::TxOutput::CreateDelegationId( + make_random_destination(rng), + primitives::H256(rng.gen()).into(), + ), + 6 => chain::TxOutput::DelegateStaking( + primitives::Amount::from_atoms(rng.gen()), + primitives::H256(rng.gen()).into(), + ), + 7 => chain::TxOutput::IssueFungibleToken(Box::new(chain::tokens::TokenIssuance::V1( + chain::tokens::TokenIssuanceV1 { + token_ticker: make_random_bytes(rng), + number_of_decimals: rng.gen(), + metadata_uri: make_random_bytes(rng), + total_supply: make_random_token_total_supply(rng), + authority: make_random_destination(rng), + is_freezable: if rng.gen::() { + chain::tokens::IsTokenFreezable::Yes + } else { + chain::tokens::IsTokenFreezable::No + }, + }, + ))), + 8 => chain::TxOutput::IssueNft( + primitives::H256(rng.gen()).into(), + Box::new(chain::tokens::NftIssuance::V0( + chain::tokens::NftIssuanceV0 { + metadata: chain::tokens::Metadata { + creator: if rng.gen::() { + Some(chain::tokens::TokenCreator { + public_key: crypto::key::PrivateKey::new_from_rng( + rng, + KeyKind::Secp256k1Schnorr, + ) + .1, + }) + } else { + None + }, + name: make_random_bytes(rng), + description: make_random_bytes(rng), + ticker: make_random_bytes(rng), + icon_uri: if rng.gen::() { + Some(make_random_bytes(rng)).into() + } else { + None.into() + }, + additional_metadata_uri: if rng.gen::() { + Some(make_random_bytes(rng)).into() + } else { + None.into() + }, + media_uri: if rng.gen::() { + Some(make_random_bytes(rng)).into() + } else { + None.into() + }, + media_hash: make_random_bytes(rng), + }, + }, + )), + make_random_destination(rng), + ), + 9 => chain::TxOutput::DataDeposit(make_random_bytes(rng)), + 10 => chain::TxOutput::Htlc( + make_random_value(rng), + Box::new(chain::htlc::HashedTimelockContract { + secret_hash: chain::htlc::HtlcSecretHash(rng.gen()), + spend_key: make_random_destination(rng), + refund_timelock: make_random_lock(rng), + refund_key: make_random_destination(rng), + }), + ), + _ => chain::TxOutput::CreateOrder(Box::new(chain::OrderData::new( + make_random_destination(rng), + make_random_value(rng), + make_random_value(rng), + ))), + } +} + +#[rstest] +#[trace] +#[case(Seed::from_entropy())] +fn check_input_encodings(#[case] seed: Seed) { + for _ in 0..1000 { + let mut rng = make_seedable_rng(seed); + + let inp = make_random_input(&mut rng); + + let simple_inp: crate::TxInput = inp.clone().into(); + + assert_eq!(inp.encode(), simple_inp.encode()); + + let decoded_simple_inp = + chain::TxInput::decode_all(&mut simple_inp.encode().as_slice()).unwrap(); + assert_eq!(decoded_simple_inp, inp); + } +} + +#[rstest] +#[trace] +#[case(Seed::from_entropy())] +fn check_output_encodings(#[case] seed: Seed) { + for _ in 0..1000 { + let mut rng = make_seedable_rng(seed); + + let out = make_random_output(&mut rng); + + let simple_out: crate::TxOutput = out.clone().into(); + + assert_eq!(out.encode(), simple_out.encode()); + + let decoded_simple_out = + chain::TxOutput::decode_all(&mut simple_out.encode().as_slice()).unwrap(); + assert_eq!(decoded_simple_out, out); + } +} + +#[rstest] +fn check_total_supply() { + use num_traits::FromPrimitive; + + for x in crate::TokenTotalSupplyTag::iter() { + match x { + crate::TokenTotalSupplyTag::Fixed => { + assert_eq!( + crate::TokenTotalSupplyTag::from_u32( + trezor_client::protos::MintlayerTokenTotalSupplyType::FIXED as u32 + ), + Some(x) + ); + } + crate::TokenTotalSupplyTag::Lockable => { + assert_eq!( + crate::TokenTotalSupplyTag::from_u32( + trezor_client::protos::MintlayerTokenTotalSupplyType::LOCKABLE as u32 + ), + Some(x) + ); + } + crate::TokenTotalSupplyTag::Unlimited => { + assert_eq!( + crate::TokenTotalSupplyTag::from_u32( + trezor_client::protos::MintlayerTokenTotalSupplyType::UNLIMITED as u32 + ), + Some(x) + ); + } + } + } +} + +#[rstest] +fn check_account_command_tag() { + use num_traits::FromPrimitive; + + for x in crate::AccountCommandTag::iter() { + match x { + crate::AccountCommandTag::MintTokens => { + assert_eq!( + crate::AccountCommandTag::from_u32( + trezor_client::protos::MintlayerAccountCommandType::MINT_TOKENS as u32 + ), + Some(x) + ); + } + crate::AccountCommandTag::UnmintTokens => { + assert_eq!( + crate::AccountCommandTag::from_u32( + trezor_client::protos::MintlayerAccountCommandType::UNMINT_TOKENS as u32 + ), + Some(x) + ); + } + crate::AccountCommandTag::FreezeToken => { + assert_eq!( + crate::AccountCommandTag::from_u32( + trezor_client::protos::MintlayerAccountCommandType::FREEZE_TOKEN as u32 + ), + Some(x) + ); + } + crate::AccountCommandTag::UnfreezeToken => { + assert_eq!( + crate::AccountCommandTag::from_u32( + trezor_client::protos::MintlayerAccountCommandType::UNFREEZE_TOKEN as u32 + ), + Some(x) + ); + } + crate::AccountCommandTag::FillOrder => { + assert_eq!( + crate::AccountCommandTag::from_u32( + trezor_client::protos::MintlayerAccountCommandType::FILL_ORDER as u32 + ), + Some(x) + ); + } + crate::AccountCommandTag::ConcludeOrder => { + assert_eq!( + crate::AccountCommandTag::from_u32( + trezor_client::protos::MintlayerAccountCommandType::CONCLUDE_ORDER as u32 + ), + Some(x) + ); + } + crate::AccountCommandTag::ChangeTokenAuthority => { + assert_eq!( + crate::AccountCommandTag::from_u32( + trezor_client::protos::MintlayerAccountCommandType::CHANGE_TOKEN_AUTHORITY + as u32 + ), + Some(x) + ); + } + crate::AccountCommandTag::ChangeTokenMetadataUri => { + assert_eq!( + crate::AccountCommandTag::from_u32( + trezor_client::protos::MintlayerAccountCommandType::CHANGE_TOKEN_METADATA_URI + as u32 + ), + Some(x) + ); + } + crate::AccountCommandTag::LockTokenSupply => { + assert_eq!( + crate::AccountCommandTag::from_u32( + trezor_client::protos::MintlayerAccountCommandType::LOCK_TOKEN_SUPPLY + as u32 + ), + Some(x) + ); + } + } + } +} + +#[rstest] +fn check_output_timelock_type() { + use num_traits::FromPrimitive; + + for x in crate::OutputTimeLockTag::iter() { + match x { + crate::OutputTimeLockTag::UntilTime => { + assert_eq!( + crate::OutputTimeLockTag::from_u32( + trezor_client::protos::MintlayerOutputTimeLockType::UNTIL_TIME as u32 + ), + Some(x) + ); + } + crate::OutputTimeLockTag::UntilHeight => { + assert_eq!( + crate::OutputTimeLockTag::from_u32( + trezor_client::protos::MintlayerOutputTimeLockType::UNTIL_HEIGHT as u32 + ), + Some(x) + ); + } + crate::OutputTimeLockTag::ForSeconds => { + assert_eq!( + crate::OutputTimeLockTag::from_u32( + trezor_client::protos::MintlayerOutputTimeLockType::FOR_SECONDS as u32 + ), + Some(x) + ); + } + crate::OutputTimeLockTag::ForBlockCount => { + assert_eq!( + crate::OutputTimeLockTag::from_u32( + trezor_client::protos::MintlayerOutputTimeLockType::FOR_BLOCK_COUNT as u32 + ), + Some(x) + ); + } + } + } +}