diff --git a/.github/workflows/bindings-python.yml b/.github/workflows/bindings-python.yml index deede4e630..3a66aca538 100644 --- a/.github/workflows/bindings-python.yml +++ b/.github/workflows/bindings-python.yml @@ -82,6 +82,7 @@ jobs: sudo apt-get update sudo apt-get install libudev-dev libusb-1.0-0-dev - - name: Run Tox - working-directory: bindings/python - run: tox + # TODO temporarily disabled https://github.com/iotaledger/iota-sdk/issues/647 + # - name: Run Tox + # working-directory: bindings/python + # run: tox diff --git a/Cargo.lock b/Cargo.lock index badc74e2c7..8d27806126 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,12 +74,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "allocator-api2" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" - [[package]] name = "android-tzdata" version = "0.1.1" @@ -696,21 +690,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "curve25519-dalek" -version = "4.0.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d928d978dbec61a1167414f5ec534f24bea0d7a0d24dd9b6233d3d8223e585" -dependencies = [ - "cfg-if", - "digest 0.10.7", - "fiat-crypto", - "packed_simd_2", - "platforms", - "subtle", - "zeroize", -] - [[package]] name = "darling" version = "0.14.4" @@ -753,7 +732,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" dependencies = [ "const-oid", - "pem-rfc7468", "zeroize", ] @@ -905,28 +883,17 @@ dependencies = [ "digest 0.10.7", "elliptic-curve", "rfc6979", - "serdect", "signature", "spki", ] -[[package]] -name = "ed25519" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" -dependencies = [ - "serde", - "signature", -] - [[package]] name = "ed25519-zebra" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ - "curve25519-dalek 3.2.0", + "curve25519-dalek", "hashbrown 0.12.3", "hex", "rand_core 0.6.4", @@ -934,22 +901,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ed25519-zebra" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af5e1fb700a3c779c7a7ed25c8c0b7f193db101de3773ac46e704bcb882d772" -dependencies = [ - "curve25519-dalek 4.0.0-rc.2", - "ed25519", - "hashbrown 0.14.0", - "hex", - "rand_core 0.6.4", - "serde", - "sha2 0.10.7", - "zeroize", -] - [[package]] name = "elliptic-curve" version = "0.13.5" @@ -963,11 +914,9 @@ dependencies = [ "generic-array", "group", "hkdf", - "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", - "serdect", "subtle", "zeroize", ] @@ -1067,12 +1016,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - [[package]] name = "fixed-hash" version = "0.8.0" @@ -1361,10 +1304,6 @@ name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" -dependencies = [ - "ahash 0.8.3", - "allocator-api2", -] [[package]] name = "heck" @@ -1383,9 +1322,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -1598,33 +1537,6 @@ name = "iota-crypto" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eb2df6763689b6e2b95787941eb9a58b29d16d17c2055392f550b3daf40bdce" -dependencies = [ - "aead", - "autocfg", - "blake2", - "chacha20poly1305", - "cipher", - "digest 0.10.7", - "ed25519-zebra 3.1.0", - "generic-array", - "getrandom", - "hmac", - "k256", - "num-traits", - "pbkdf2", - "rand", - "serde", - "sha2 0.10.7", - "tiny-keccak", - "unicode-normalization", - "zeroize", -] - -[[package]] -name = "iota-crypto" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d22b9d6f6b7601c3fcff97cdf6298cbfdd6fb44812b4e6f82a016152be1c891" dependencies = [ "aead", "aes", @@ -1634,15 +1546,15 @@ dependencies = [ "blake2", "chacha20poly1305", "cipher", - "curve25519-dalek 3.2.0", + "curve25519-dalek", "digest 0.10.7", - "ed25519-zebra 4.0.0", + "ed25519-zebra", "generic-array", "getrandom", "hkdf", "hmac", - "iterator-sorted", "k256", + "num-traits", "pbkdf2", "rand", "scrypt", @@ -1696,7 +1608,7 @@ dependencies = [ "heck", "hex", "instant", - "iota-crypto 0.20.1", + "iota-crypto", "iota-ledger-nano", "iota-sdk", "iota_stronghold", @@ -1733,7 +1645,7 @@ dependencies = [ "derivative", "fern-logger", "futures", - "iota-crypto 0.20.1", + "iota-crypto", "iota-sdk", "log", "packable", @@ -1802,12 +1714,12 @@ dependencies = [ [[package]] name = "iota_stronghold" -version = "2.0.0-rc.0" -source = "git+https://github.com/iotaledger/stronghold.rs?branch=2.0#87d0ae07a26fc6711843174d1d706b066c85f350" +version = "1.1.0" +source = "git+https://github.com/iotaledger/stronghold.rs?rev=9df6d19de325481771d69b47534d151e03eae623#9df6d19de325481771d69b47534d151e03eae623" dependencies = [ "bincode", "hkdf", - "iota-crypto 0.21.2", + "iota-crypto", "rust-argon2", "serde", "stronghold-derive", @@ -1829,7 +1741,7 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi 0.3.2", "rustix", "windows-sys 0.48.0", ] @@ -1842,9 +1754,9 @@ checksum = "d101775d2bc8f99f4ac18bf29b9ed70c0dd138b9a1e88d7b80179470cbbe8bd2" [[package]] name = "itoa" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0aa48fab2893d8a49caa94082ae8488f4e1050d73b367881dcd2198f4199fd8" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" [[package]] name = "jobserver" @@ -1874,7 +1786,6 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "serdect", "sha2 0.10.7", ] @@ -1953,12 +1864,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "libm" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" - [[package]] name = "librocksdb-sys" version = "0.11.0+8.1.1" @@ -2181,7 +2086,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi 0.3.2", "libc", ] @@ -2246,16 +2151,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "packed_simd_2" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" -dependencies = [ - "cfg-if", - "libm", -] - [[package]] name = "parity-scale-codec" version = "3.6.3" @@ -2307,9 +2202,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" [[package]] name = "pbkdf2" @@ -2327,15 +2222,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.0" @@ -2400,12 +2286,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[package]] -name = "platforms" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" - [[package]] name = "poly1305" version = "0.8.0" @@ -2869,9 +2749,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" [[package]] name = "salsa20" @@ -2937,7 +2817,6 @@ dependencies = [ "der", "generic-array", "pkcs8", - "serdect", "subtle", "zeroize", ] @@ -2988,9 +2867,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.165" +version = "1.0.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c939f902bb7d0ccc5bce4f03297e161543c2dcb30914faf032c2bd0b7a0d48fc" +checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8" dependencies = [ "serde_derive", ] @@ -3006,9 +2885,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.165" +version = "1.0.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaae920e25fffe4019b75ff65e7660e72091e59dd204cb5849bbd6a3fd343d7" +checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6" dependencies = [ "proc-macro2", "quote", @@ -3028,9 +2907,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "6f0a21fba416426ac927b1691996e82079f8b6156e920c85345f135b2e9ba2de" dependencies = [ "proc-macro2", "quote", @@ -3049,16 +2928,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serdect" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" -dependencies = [ - "base16ct", - "serde", -] - [[package]] name = "sha-1" version = "0.9.8" @@ -3199,7 +3068,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stronghold-derive" version = "1.0.0" -source = "git+https://github.com/iotaledger/stronghold.rs?branch=2.0#87d0ae07a26fc6711843174d1d706b066c85f350" +source = "git+https://github.com/iotaledger/stronghold.rs?rev=9df6d19de325481771d69b47534d151e03eae623#9df6d19de325481771d69b47534d151e03eae623" dependencies = [ "proc-macro2", "quote", @@ -3208,11 +3077,11 @@ dependencies = [ [[package]] name = "stronghold-runtime" -version = "2.0.0-rc.0" -source = "git+https://github.com/iotaledger/stronghold.rs?branch=2.0#87d0ae07a26fc6711843174d1d706b066c85f350" +version = "1.1.0" +source = "git+https://github.com/iotaledger/stronghold.rs?rev=9df6d19de325481771d69b47534d151e03eae623#9df6d19de325481771d69b47534d151e03eae623" dependencies = [ "dirs", - "iota-crypto 0.21.2", + "iota-crypto", "libc", "libsodium-sys", "log", @@ -3227,7 +3096,7 @@ dependencies = [ [[package]] name = "stronghold-utils" version = "1.0.0" -source = "git+https://github.com/iotaledger/stronghold.rs?branch=2.0#87d0ae07a26fc6711843174d1d706b066c85f350" +source = "git+https://github.com/iotaledger/stronghold.rs?rev=9df6d19de325481771d69b47534d151e03eae623#9df6d19de325481771d69b47534d151e03eae623" dependencies = [ "rand", "stronghold-derive", @@ -3235,13 +3104,13 @@ dependencies = [ [[package]] name = "stronghold_engine" -version = "2.0.0-rc.0" -source = "git+https://github.com/iotaledger/stronghold.rs?branch=2.0#87d0ae07a26fc6711843174d1d706b066c85f350" +version = "1.1.0" +source = "git+https://github.com/iotaledger/stronghold.rs?rev=9df6d19de325481771d69b47534d151e03eae623#9df6d19de325481771d69b47534d151e03eae623" dependencies = [ "anyhow", "dirs-next", "hex", - "iota-crypto 0.21.2", + "iota-crypto", "once_cell", "paste", "serde", @@ -3560,9 +3429,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" [[package]] name = "unicode-normalization" @@ -4029,7 +3898,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ - "curve25519-dalek 3.2.0", + "curve25519-dalek", "rand_core 0.5.1", "zeroize", ] diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 0e3d5af0b6..61cbc8b1a5 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -43,7 +43,7 @@ heck = { version = "0.4.1", default-features = false, optional = true } instant = { version = "0.1.12", default-features = false, optional = true } iota-ledger-nano = { version = "1.0.0-alpha.4", default-features = false, optional = true } # iota_stronghold = { version = "1.0.5", default-features = false, optional = true } -iota_stronghold = { git = "https://github.com/iotaledger/stronghold.rs", branch = "2.0", default-features = false, optional = true } +iota_stronghold = { git = "https://github.com/iotaledger/stronghold.rs", rev = "9df6d19de325481771d69b47534d151e03eae623", default-features = false, optional = true } log = { version = "0.4.18", default-features = false, optional = true } num_cpus = { version = "1.15.0", default-features = false, optional = true } once_cell = { version = "1.17.2", default-features = false, optional = true } @@ -408,11 +408,6 @@ name = "client_config" path = "examples/client/client_config.rs" required-features = [ "client" ] -[[example]] -name = "custom_remainder_address" -path = "examples/client/custom_remainder_address.rs" -required-features = [ "client" ] - [[example]] name = "get_block" path = "examples/client/get_block.rs" diff --git a/sdk/examples/client/02_address_balance.rs b/sdk/examples/client/02_address_balance.rs index a37200cd01..8da5cd15d9 100644 --- a/sdk/examples/client/02_address_balance.rs +++ b/sdk/examples/client/02_address_balance.rs @@ -52,13 +52,12 @@ async fn main() -> Result<()> { .await?; // Get the outputs by their id - let outputs_responses = client.get_outputs(&output_ids_response.items).await?; + let outputs = client.get_outputs(&output_ids_response.items).await?; // Calculate the total amount and native tokens let mut total_amount = 0; let mut total_native_tokens = NativeTokensBuilder::new(); - for output_response in outputs_responses { - let output = output_response.output(); + for output in outputs { if let Some(native_tokens) = output.native_tokens() { total_native_tokens.add_native_tokens(native_tokens.clone())?; } diff --git a/sdk/examples/client/03_simple_block.rs b/sdk/examples/client/03_simple_block.rs index 1688c7516b..3db9ec28fc 100644 --- a/sdk/examples/client/03_simple_block.rs +++ b/sdk/examples/client/03_simple_block.rs @@ -21,7 +21,7 @@ async fn main() -> Result<()> { .finish() .await?; - let block = client.block().finish().await?; + let block = client.finish_block_builder(None, None).await?; println!( "Empty block sent: {}/block/{}", diff --git a/sdk/examples/client/block/02_block_custom_parents.rs b/sdk/examples/client/block/02_block_custom_parents.rs index aa34016fdf..af097c5a03 100644 --- a/sdk/examples/client/block/02_block_custom_parents.rs +++ b/sdk/examples/client/block/02_block_custom_parents.rs @@ -29,7 +29,7 @@ async fn main() -> Result<()> { // Create and send the block with custom parents. let block = client - .finish_block_builder(Some(StrongParents::from_vec(parents)?), None) + .finish_block_builder(Some(StrongParents::from_vec(tips)?), None) .await?; println!("{block:#?}"); diff --git a/sdk/examples/client/block/04_block_tagged_data.rs b/sdk/examples/client/block/04_block_tagged_data.rs index 21eca8b49b..76a2080b67 100644 --- a/sdk/examples/client/block/04_block_tagged_data.rs +++ b/sdk/examples/client/block/04_block_tagged_data.rs @@ -20,9 +20,6 @@ async fn main() -> Result<()> { let node_url = std::env::var("NODE_URL").unwrap(); - let tag = std::env::args().nth(1).unwrap_or_else(|| "Hello".to_string()); - let data = std::env::args().nth(2).unwrap_or_else(|| "Tangle".to_string()); - // Create a node client. let client = Client::builder().with_node(&node_url)?.finish().await?; @@ -31,7 +28,17 @@ async fn main() -> Result<()> { .finish_block_builder( None, Some(Payload::TaggedData(Box::new( - TaggedDataPayload::new(b"Hello".to_vec(), b"Tangle".to_vec()).unwrap(), + TaggedDataPayload::new( + std::env::args() + .nth(1) + .unwrap_or_else(|| "Hello".to_string()) + .as_bytes(), + std::env::args() + .nth(2) + .unwrap_or_else(|| "Tangle".to_string()) + .as_bytes(), + ) + .unwrap(), ))), ) .await?; diff --git a/sdk/examples/client/custom_remainder_address.rs b/sdk/examples/client/custom_remainder_address.rs deleted file mode 100644 index 3ce803a733..0000000000 --- a/sdk/examples/client/custom_remainder_address.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will send a certain amount of tokens to a given receiver address and any remaining -//! tokens to a custom remainder address. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example custom_remainder_address [AMOUNT] -//! ``` - -use iota_sdk::client::{ - api::GetAddressesOptions, node_api::indexer::query_parameters::QueryParameter, request_funds_from_faucet, - secret::SecretManager, Client, Result, -}; - -#[tokio::main] -async fn main() -> Result<()> { - let amount = std::env::args() - .nth(1) - .map(|s| s.parse::().unwrap()) - .unwrap_or(9_000_000); - - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - // Create a client instance. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = - SecretManager::try_from_mnemonic(std::env::var("NON_SECURE_USE_OF_DEVELOPMENT_MNEMONIC_1").unwrap())?; - - let addresses = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::from_client(&client) - .await? - .with_account_index(0) - .with_range(0..3), - ) - .await?; - - let sender_address = &addresses[0]; - let receiver_address = &addresses[1]; - let remainder_address = &addresses[2]; - - println!("sender address: {sender_address}"); - println!("receiver address: {receiver_address}"); - println!("remainder address: {remainder_address}"); - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), sender_address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let output_ids_response = client - .basic_output_ids([QueryParameter::Address(*sender_address)]) - .await?; - println!("{output_ids_response:#?}"); - - let block = client - .block() - .with_secret_manager(&secret_manager) - .with_output(receiver_address, amount) - .await? - .with_custom_remainder_address(remainder_address)? - .finish() - .await?; - - println!( - "Block with custom remainder sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - Ok(()) -} diff --git a/sdk/examples/client/ledger_nano_transaction.rs b/sdk/examples/client/ledger_nano_transaction.rs index 10009f697d..efd0070592 100644 --- a/sdk/examples/client/ledger_nano_transaction.rs +++ b/sdk/examples/client/ledger_nano_transaction.rs @@ -23,7 +23,7 @@ use iota_sdk::client::{ Client, Result, }; -const AMOUNT: u64 = 1_000_000; +// const AMOUNT: u64 = 1_000_000; #[tokio::main] async fn main() -> Result<()> { diff --git a/sdk/examples/client/node_api_indexer/01_get_account_output.rs b/sdk/examples/client/node_api_indexer/01_get_account_output.rs index 584606e0d4..79bcd484da 100644 --- a/sdk/examples/client/node_api_indexer/01_get_account_output.rs +++ b/sdk/examples/client/node_api_indexer/01_get_account_output.rs @@ -33,7 +33,7 @@ async fn main() -> Result<()> { let account_id = std::env::args() .nth(1) .expect("missing example argument: ACCOUNT_ID") - .parse::()?; + .parse::()?; // Get the output ID by providing the corresponding account ID. let output_id = client.account_output_id(account_id).await?; diff --git a/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs b/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs index f30dd917c6..0cd5aee1ec 100644 --- a/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs +++ b/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs @@ -41,7 +41,7 @@ async fn main() -> Result<()> { // Get output IDs of foundry outputs that can be controlled by this address. let output_ids_response = client - .foundry_output_ids([QueryParameter::AliasAddress(address)]) + .foundry_output_ids([QueryParameter::AccountAddress(address)]) .await?; println!("Foundry output IDs: {output_ids_response:#?}"); diff --git a/sdk/examples/wallet/update_account_output.rs b/sdk/examples/wallet/update_account_output.rs index 03a8627bc7..f643a4b045 100644 --- a/sdk/examples/wallet/update_account_output.rs +++ b/sdk/examples/wallet/update_account_output.rs @@ -41,7 +41,7 @@ async fn main() -> Result<()> { .await?; // Get the account output by its account id - let account_id = AccountId::from_str(ACCOUNT_ID)?; + let account_id = ACCOUNT_ID.parse::()?; if let Some(account_output_data) = account.unspent_account_output(&account_id).await? { println!( "Account '{ACCOUNT_ID}' found in unspent output: '{}'", diff --git a/sdk/src/types/block/output/chain_id.rs b/sdk/src/types/block/output/chain_id.rs index b030013569..a3159a0ae3 100644 --- a/sdk/src/types/block/output/chain_id.rs +++ b/sdk/src/types/block/output/chain_id.rs @@ -3,7 +3,7 @@ use derive_more::From; -use crate::types::block::output::{AccountId, FoundryId, NftId, OutputId}; +use crate::types::block::output::{AccountId, DelegationId, FoundryId, NftId, OutputId}; /// #[derive(Clone, Copy, Eq, Hash, PartialEq, Ord, PartialOrd, From)] @@ -15,6 +15,8 @@ pub enum ChainId { Foundry(FoundryId), /// Nft(NftId), + /// + Delegation(DelegationId), } impl core::fmt::Debug for ChainId { @@ -24,6 +26,7 @@ impl core::fmt::Debug for ChainId { Self::Account(id) => formatter.field(id), Self::Foundry(id) => formatter.field(id), Self::Nft(id) => formatter.field(id), + Self::Delegation(id) => formatter.field(id), }; formatter.finish() } @@ -36,6 +39,7 @@ impl ChainId { Self::Account(id) => id.is_null(), Self::Foundry(id) => id.is_null(), Self::Nft(id) => id.is_null(), + Self::Delegation(id) => id.is_null(), } } @@ -49,6 +53,7 @@ impl ChainId { Self::Account(_) => Self::Account(AccountId::from(output_id)), Self::Foundry(_) => self, Self::Nft(_) => Self::Nft(NftId::from(output_id)), + Self::Delegation(_) => Self::Delegation(DelegationId::from(output_id)), } } } @@ -59,6 +64,7 @@ impl core::fmt::Display for ChainId { Self::Account(id) => write!(f, "{id}"), Self::Foundry(id) => write!(f, "{id}"), Self::Nft(id) => write!(f, "{id}"), + Self::Delegation(id) => write!(f, "{id}"), } } } diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs new file mode 100644 index 0000000000..7c2ecb6869 --- /dev/null +++ b/sdk/src/types/block/output/delegation.rs @@ -0,0 +1,599 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use alloc::{collections::BTreeSet, vec::Vec}; + +use packable::{ + error::{UnpackError, UnpackErrorExt}, + packer::Packer, + unpacker::Unpacker, + Packable, +}; + +use crate::types::block::{ + address::Address, + output::{ + account_id::AccountId, + chain_id::ChainId, + feature::{verify_allowed_features, Feature, FeatureFlags, Features}, + unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + verify_output_amount, Output, OutputBuilderAmount, OutputId, Rent, RentStructure, + }, + protocol::ProtocolParameters, + semantic::{ConflictReason, ValidationContext}, + unlock::Unlock, + Error, +}; + +impl_id!(pub DelegationId, 32, "Unique identifier of the Delegation Output, which is the BLAKE2b-256 hash of the Output ID that created it."); + +#[cfg(feature = "serde")] +string_serde_impl!(DelegationId); + +impl From<&OutputId> for DelegationId { + fn from(output_id: &OutputId) -> Self { + Self::from(output_id.hash()) + } +} + +impl DelegationId { + pub fn or_from_output_id(self, output_id: &OutputId) -> Self { + if self.is_null() { Self::from(output_id) } else { self } + } +} + +/// Builder for a [`DelegationOutput`]. +#[derive(Clone)] +#[must_use] +pub struct DelegationOutputBuilder { + amount: OutputBuilderAmount, + delegated_amount: u64, + delegation_id: DelegationId, + validator_id: AccountId, + start_epoch: u64, + end_epoch: u64, + unlock_conditions: BTreeSet, + immutable_features: BTreeSet, +} + +impl DelegationOutputBuilder { + /// Creates a [`DelegationOutputBuilder`] with a provided amount. + pub fn new_with_amount( + amount: u64, + delegated_amount: u64, + delegation_id: DelegationId, + validator_id: AccountId, + ) -> Self { + Self::new( + OutputBuilderAmount::Amount(amount), + delegated_amount, + delegation_id, + validator_id, + ) + } + + /// Creates a [`DelegationOutputBuilder`] with a provided rent structure. + /// The amount will be set to the minimum storage deposit. + pub fn new_with_minimum_storage_deposit( + rent_structure: RentStructure, + delegated_amount: u64, + delegation_id: DelegationId, + validator_id: AccountId, + ) -> Self { + Self::new( + OutputBuilderAmount::MinimumStorageDeposit(rent_structure), + delegated_amount, + delegation_id, + validator_id, + ) + } + + fn new( + amount: OutputBuilderAmount, + delegated_amount: u64, + delegation_id: DelegationId, + validator_id: AccountId, + ) -> Self { + Self { + amount, + delegated_amount, + delegation_id, + validator_id, + start_epoch: 0, + end_epoch: 0, + unlock_conditions: BTreeSet::new(), + immutable_features: BTreeSet::new(), + } + } + + /// Sets the amount to the provided value. + pub fn with_amount(mut self, amount: u64) -> Self { + self.amount = OutputBuilderAmount::Amount(amount); + self + } + + /// Sets the amount to the minimum storage deposit. + pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self { + self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure); + self + } + + /// Sets the delegation ID to the provided value. + pub fn with_delegation_id(mut self, delegation_id: DelegationId) -> Self { + self.delegation_id = delegation_id; + self + } + + /// Sets the validator ID to the provided value. + pub fn with_validator_id(mut self, validator_id: AccountId) -> Self { + self.validator_id = validator_id; + self + } + + /// Sets the start epoch to the provided value. + pub fn with_start_epoch(mut self, start_epoch: u64) -> Self { + self.start_epoch = start_epoch; + self + } + + /// Sets the end epoch to the provided value. + pub fn with_end_epoch(mut self, end_epoch: u64) -> Self { + self.end_epoch = end_epoch; + self + } + + /// Adds an [`UnlockCondition`] to the builder, if one does not already exist of that type. + pub fn add_unlock_condition(mut self, unlock_condition: impl Into) -> Self { + self.unlock_conditions.insert(unlock_condition.into()); + self + } + + /// Sets the [`UnlockConditions`]s in the builder, overwriting any existing values. + pub fn with_unlock_conditions( + mut self, + unlock_conditions: impl IntoIterator>, + ) -> Self { + self.unlock_conditions = unlock_conditions.into_iter().map(Into::into).collect(); + self + } + + /// Replaces an [`UnlockCondition`] of the builder with a new one, or adds it. + pub fn replace_unlock_condition(mut self, unlock_condition: impl Into) -> Self { + self.unlock_conditions.replace(unlock_condition.into()); + self + } + + /// Clears all [`UnlockConditions`]s from the builder. + pub fn clear_unlock_conditions(mut self) -> Self { + self.unlock_conditions.clear(); + self + } + + /// Adds an immutable [`Feature`] to the builder, if one does not already exist of that type. + pub fn add_immutable_feature(mut self, immutable_feature: impl Into) -> Self { + self.immutable_features.insert(immutable_feature.into()); + self + } + + /// Sets the immutable [`Feature`]s in the builder, overwriting any existing values. + pub fn with_immutable_features(mut self, immutable_features: impl IntoIterator>) -> Self { + self.immutable_features = immutable_features.into_iter().map(Into::into).collect(); + self + } + + /// Replaces an immutable [`Feature`] of the builder with a new one, or adds it. + pub fn replace_immutable_feature(mut self, immutable_feature: impl Into) -> Self { + self.immutable_features.replace(immutable_feature.into()); + self + } + + /// Clears all immutable [`Feature`]s from the builder. + pub fn clear_immutable_features(mut self) -> Self { + self.immutable_features.clear(); + self + } + + /// Finishes the builder into a [`DelegationOutput`] without amount verification. + pub fn finish_unverified(self) -> Result { + let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; + + verify_unlock_conditions::(&unlock_conditions)?; + + let immutable_features = Features::from_set(self.immutable_features)?; + + verify_allowed_features(&immutable_features, DelegationOutput::ALLOWED_IMMUTABLE_FEATURES)?; + + let mut output = DelegationOutput { + amount: 1u64, + delegated_amount: self.delegated_amount, + delegation_id: self.delegation_id, + validator_id: self.validator_id, + start_epoch: self.start_epoch, + end_epoch: self.end_epoch, + unlock_conditions, + immutable_features, + }; + + output.amount = match self.amount { + OutputBuilderAmount::Amount(amount) => amount, + OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { + Output::Delegation(output.clone()).rent_cost(&rent_structure) + } + }; + + Ok(output) + } + + /// Finishes the builder into a [`DelegationOutput`] with amount verification. + pub fn finish(self, token_supply: u64) -> Result { + let output = self.finish_unverified()?; + + verify_output_amount::(&output.amount, &token_supply)?; + + Ok(output) + } + + /// Finishes the [`DelegationOutputBuilder`] into an [`Output`]. + pub fn finish_output(self, token_supply: u64) -> Result { + Ok(Output::Delegation(self.finish(token_supply)?)) + } +} + +impl From<&DelegationOutput> for DelegationOutputBuilder { + fn from(output: &DelegationOutput) -> Self { + Self { + amount: OutputBuilderAmount::Amount(output.amount), + delegated_amount: output.delegated_amount, + delegation_id: output.delegation_id, + validator_id: output.validator_id, + start_epoch: output.start_epoch, + end_epoch: output.end_epoch, + unlock_conditions: output.unlock_conditions.iter().cloned().collect(), + immutable_features: output.immutable_features.iter().cloned().collect(), + } + } +} + +/// Describes a Delegation output, which delegates its contained IOTA tokens as voting power to a validator. +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DelegationOutput { + // Amount of IOTA tokens held by the output. + amount: u64, + /// The amount of delegated coins. + delegated_amount: u64, + /// Unique identifier of the Delegation Output, which is the BLAKE2b-256 hash of the Output ID that created it. + delegation_id: DelegationId, + /// The Account ID of the validator to which this output is delegating. + validator_id: AccountId, + /// The index of the first epoch for which this output delegates. + start_epoch: u64, + /// The index of the last epoch for which this output delegates. + end_epoch: u64, + unlock_conditions: UnlockConditions, + immutable_features: Features, +} + +impl DelegationOutput { + /// The [`Output`](crate::types::block::output::Output) kind of a [`DelegationOutput`]. + pub const KIND: u8 = 7; + /// The set of allowed [`UnlockCondition`]s for a [`DelegationOutput`]. + pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::ADDRESS; + /// The set of allowed immutable [`Feature`]s for a [`DelegationOutput`]. + pub const ALLOWED_IMMUTABLE_FEATURES: FeatureFlags = FeatureFlags::ISSUER; + + /// Creates a new [`DelegationOutputBuilder`] with a provided amount. + pub fn build_with_amount( + amount: u64, + delegated_amount: u64, + delegation_id: DelegationId, + validator_id: AccountId, + ) -> DelegationOutputBuilder { + DelegationOutputBuilder::new_with_amount(amount, delegated_amount, delegation_id, validator_id) + } + + /// Creates a new [`DelegationOutputBuilder`] with a provided rent structure. + /// The amount will be set to the minimum storage deposit. + pub fn build_with_minimum_storage_deposit( + rent_structure: RentStructure, + delegated_amount: u64, + delegation_id: DelegationId, + validator_id: AccountId, + ) -> DelegationOutputBuilder { + DelegationOutputBuilder::new_with_minimum_storage_deposit( + rent_structure, + delegated_amount, + delegation_id, + validator_id, + ) + } + + /// Returns the amount of the [`DelegationOutput`]. + pub fn amount(&self) -> u64 { + self.amount + } + + /// Returns the delegated amount of the [`DelegationOutput`]. + pub fn delegated_amount(&self) -> u64 { + self.delegated_amount + } + + /// Returns the delegation ID of the [`DelegationOutput`]. + pub fn delegation_id(&self) -> &DelegationId { + &self.delegation_id + } + + /// Returns the delegation ID of the [`DelegationOutput`] if not null, or creates it from the output ID. + pub fn delegation_id_non_null(&self, output_id: &OutputId) -> DelegationId { + self.delegation_id.or_from_output_id(output_id) + } + + /// Returns the validator ID of the [`DelegationOutput`]. + pub fn validator_id(&self) -> &AccountId { + &self.validator_id + } + + /// Returns the start epoch of the [`DelegationOutput`]. + pub fn start_epoch(&self) -> u64 { + self.start_epoch + } + + /// Returns the end epoch of the [`DelegationOutput`]. + pub fn end_epoch(&self) -> u64 { + self.end_epoch + } + + /// Returns the unlock conditions of the [`DelegationOutput`]. + pub fn unlock_conditions(&self) -> &UnlockConditions { + &self.unlock_conditions + } + + /// Returns the immutable features of the [`DelegationOutput`]. + pub fn immutable_features(&self) -> &Features { + &self.immutable_features + } + + /// Returns the address of the [`DelegationOutput`]. + pub fn address(&self) -> &Address { + // An DelegationOutput must have an AddressUnlockCondition. + self.unlock_conditions + .address() + .map(|unlock_condition| unlock_condition.address()) + .unwrap() + } + + /// Returns the chain ID of the [`DelegationOutput`]. + #[inline(always)] + pub fn chain_id(&self) -> ChainId { + ChainId::Delegation(self.delegation_id) + } + + /// Tries to unlock the [`DelegationOutput`]. + pub fn unlock( + &self, + _output_id: &OutputId, + unlock: &Unlock, + inputs: &[(OutputId, &Output)], + context: &mut ValidationContext<'_>, + ) -> Result<(), ConflictReason> { + self.unlock_conditions() + .locked_address(self.address(), context.milestone_timestamp) + .unlock(unlock, inputs, context) + } +} + +impl Packable for DelegationOutput { + type UnpackError = Error; + type UnpackVisitor = ProtocolParameters; + + fn pack(&self, packer: &mut P) -> Result<(), P::Error> { + self.amount.pack(packer)?; + self.delegated_amount.pack(packer)?; + self.delegation_id.pack(packer)?; + self.validator_id.pack(packer)?; + self.start_epoch.pack(packer)?; + self.end_epoch.pack(packer)?; + self.unlock_conditions.pack(packer)?; + self.immutable_features.pack(packer)?; + + Ok(()) + } + + fn unpack( + unpacker: &mut U, + visitor: &Self::UnpackVisitor, + ) -> Result> { + let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + + verify_output_amount::(&amount, &visitor.token_supply()).map_err(UnpackError::Packable)?; + + let delegated_amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let delegation_id = DelegationId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let validator_id = AccountId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let start_epoch = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let end_epoch = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?; + + verify_unlock_conditions::(&unlock_conditions).map_err(UnpackError::Packable)?; + + let immutable_features = Features::unpack::<_, VERIFY>(unpacker, &())?; + + if VERIFY { + verify_allowed_features(&immutable_features, Self::ALLOWED_IMMUTABLE_FEATURES) + .map_err(UnpackError::Packable)?; + } + + Ok(Self { + amount, + delegated_amount, + delegation_id, + validator_id, + start_epoch, + end_epoch, + unlock_conditions, + immutable_features, + }) + } +} + +fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), Error> { + if VERIFY { + if unlock_conditions.address().is_none() { + Err(Error::MissingAddressUnlockCondition) + } else { + verify_allowed_unlock_conditions(unlock_conditions, DelegationOutput::ALLOWED_UNLOCK_CONDITIONS) + } + } else { + Ok(()) + } +} + +#[allow(missing_docs)] +pub mod dto { + use alloc::string::{String, ToString}; + + use serde::{Deserialize, Serialize}; + + use super::*; + use crate::types::block::{ + output::{dto::OutputBuilderAmountDto, feature::dto::FeatureDto, unlock_condition::dto::UnlockConditionDto}, + Error, + }; + + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct DelegationOutputDto { + #[serde(rename = "type")] + pub kind: u8, + pub amount: String, + pub delegated_amount: String, + pub delegation_id: DelegationId, + pub validator_id: AccountId, + start_epoch: u64, + end_epoch: u64, + pub unlock_conditions: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub immutable_features: Vec, + } + + impl From<&DelegationOutput> for DelegationOutputDto { + fn from(value: &DelegationOutput) -> Self { + Self { + kind: DelegationOutput::KIND, + amount: value.amount().to_string(), + delegated_amount: value.delegated_amount().to_string(), + delegation_id: *value.delegation_id(), + validator_id: *value.validator_id(), + start_epoch: value.start_epoch(), + end_epoch: value.end_epoch(), + unlock_conditions: value.unlock_conditions().iter().map(Into::into).collect::<_>(), + immutable_features: value.immutable_features().iter().map(Into::into).collect::<_>(), + } + } + } + + impl DelegationOutput { + pub fn try_from_dto(value: DelegationOutputDto, token_supply: u64) -> Result { + let mut builder = DelegationOutputBuilder::new_with_amount( + value.amount.parse::().map_err(|_| Error::InvalidField("amount"))?, + value + .delegated_amount + .parse::() + .map_err(|_| Error::InvalidField("delegatedAmount"))?, + value.delegation_id, + value.validator_id, + ); + + builder = builder.with_start_epoch(value.start_epoch); + builder = builder.with_end_epoch(value.end_epoch); + + for b in value.immutable_features { + builder = builder.add_immutable_feature(Feature::try_from(b)?); + } + + for u in value.unlock_conditions { + builder = builder.add_unlock_condition(UnlockCondition::try_from_dto(u, token_supply)?); + } + + builder.finish(token_supply) + } + + pub fn try_from_dto_unverified(value: DelegationOutputDto) -> Result { + let mut builder = DelegationOutputBuilder::new_with_amount( + value.amount.parse::().map_err(|_| Error::InvalidField("amount"))?, + value + .delegated_amount + .parse::() + .map_err(|_| Error::InvalidField("delegatedAmount"))?, + value.delegation_id, + value.validator_id, + ); + + builder = builder.with_start_epoch(value.start_epoch); + builder = builder.with_end_epoch(value.end_epoch); + + for u in value.unlock_conditions { + builder = builder.add_unlock_condition(UnlockCondition::try_from_dto_unverified(u)?); + } + + for b in value.immutable_features { + builder = builder.add_immutable_feature(Feature::try_from(b)?); + } + + builder.finish_unverified() + } + + #[allow(clippy::too_many_arguments)] + pub fn try_from_dtos( + amount: OutputBuilderAmountDto, + delegated_amount: String, + delegation_id: &DelegationId, + validator_id: &AccountId, + start_epoch: u64, + end_epoch: u64, + unlock_conditions: Vec, + immutable_features: Option>, + token_supply: u64, + ) -> Result { + let mut builder = match amount { + OutputBuilderAmountDto::Amount(amount) => DelegationOutputBuilder::new_with_amount( + amount.parse().map_err(|_| Error::InvalidField("amount"))?, + delegated_amount + .parse() + .map_err(|_| Error::InvalidField("delegatedAmount"))?, + *delegation_id, + *validator_id, + ), + OutputBuilderAmountDto::MinimumStorageDeposit(rent_structure) => { + DelegationOutputBuilder::new_with_minimum_storage_deposit( + rent_structure, + delegated_amount + .parse() + .map_err(|_| Error::InvalidField("delegatedAmount"))?, + *delegation_id, + *validator_id, + ) + } + }; + + builder = builder.with_start_epoch(start_epoch); + builder = builder.with_end_epoch(end_epoch); + + let unlock_conditions = unlock_conditions + .into_iter() + .map(|u| UnlockCondition::try_from_dto(u, token_supply)) + .collect::, Error>>()?; + builder = builder.with_unlock_conditions(unlock_conditions); + + if let Some(immutable_features) = immutable_features { + let immutable_features = immutable_features + .into_iter() + .map(Feature::try_from) + .collect::, Error>>()?; + builder = builder.with_immutable_features(immutable_features); + } + + builder.finish(token_supply) + } + } +} diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 7eca9085a4..8b9ac54f37 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -3,6 +3,7 @@ mod account_id; mod chain_id; +mod delegation; mod foundry_id; mod inputs_commitment; mod metadata; @@ -50,6 +51,7 @@ pub use self::{ account_id::AccountId, basic::{BasicOutput, BasicOutputBuilder}, chain_id::ChainId, + delegation::{DelegationId, DelegationOutput, DelegationOutputBuilder}, feature::{Feature, Features}, foundry::{FoundryOutput, FoundryOutputBuilder}, foundry_id::FoundryId, @@ -133,6 +135,8 @@ pub enum Output { Foundry(FoundryOutput), /// An NFT output. Nft(NftOutput), + /// A delegation output. + Delegation(DelegationOutput), } impl core::fmt::Debug for Output { @@ -142,6 +146,7 @@ impl core::fmt::Debug for Output { Self::Account(output) => output.fmt(f), Self::Foundry(output) => output.fmt(f), Self::Nft(output) => output.fmt(f), + Self::Delegation(output) => output.fmt(f), } } } @@ -157,6 +162,7 @@ impl Output { Self::Account(_) => AccountOutput::KIND, Self::Foundry(_) => FoundryOutput::KIND, Self::Nft(_) => NftOutput::KIND, + Self::Delegation(_) => DelegationOutput::KIND, } } @@ -167,6 +173,7 @@ impl Output { Self::Account(output) => output.amount(), Self::Foundry(output) => output.amount(), Self::Nft(output) => output.amount(), + Self::Delegation(output) => output.amount(), } } @@ -177,6 +184,7 @@ impl Output { Self::Account(output) => Some(output.native_tokens()), Self::Foundry(output) => Some(output.native_tokens()), Self::Nft(output) => Some(output.native_tokens()), + Self::Delegation(_) => None, } } @@ -187,6 +195,7 @@ impl Output { Self::Account(output) => Some(output.unlock_conditions()), Self::Foundry(output) => Some(output.unlock_conditions()), Self::Nft(output) => Some(output.unlock_conditions()), + Self::Delegation(output) => Some(output.unlock_conditions()), } } @@ -197,6 +206,7 @@ impl Output { Self::Account(output) => Some(output.features()), Self::Foundry(output) => Some(output.features()), Self::Nft(output) => Some(output.features()), + Self::Delegation(_) => None, } } @@ -207,6 +217,7 @@ impl Output { Self::Account(output) => Some(output.immutable_features()), Self::Foundry(output) => Some(output.immutable_features()), Self::Nft(output) => Some(output.immutable_features()), + Self::Delegation(output) => Some(output.immutable_features()), } } @@ -217,6 +228,7 @@ impl Output { Self::Account(output) => Some(output.chain_id()), Self::Foundry(output) => Some(output.chain_id()), Self::Nft(output) => Some(output.chain_id()), + Self::Delegation(_) => None, } } @@ -226,12 +238,12 @@ impl Output { } /// Gets the output as an actual [`BasicOutput`]. - /// PANIC: do not call on a non-basic output. + /// NOTE: Will panic if the output is not a [`BasicOutput`]. pub fn as_basic(&self) -> &BasicOutput { if let Self::Basic(output) = self { output } else { - panic!("as_basic called on a non-basic output"); + panic!("invalid downcast of non-BasicOutput"); } } @@ -241,12 +253,12 @@ impl Output { } /// Gets the output as an actual [`AccountOutput`]. - /// PANIC: do not call on a non-account output. + /// NOTE: Will panic if the output is not a [`AccountOutput`]. pub fn as_account(&self) -> &AccountOutput { if let Self::Account(output) = self { output } else { - panic!("as_account called on a non-account output"); + panic!("invalid downcast of non-AccountOutput"); } } @@ -256,12 +268,12 @@ impl Output { } /// Gets the output as an actual [`FoundryOutput`]. - /// PANIC: do not call on a non-foundry output. + /// NOTE: Will panic if the output is not a [`FoundryOutput`]. pub fn as_foundry(&self) -> &FoundryOutput { if let Self::Foundry(output) = self { output } else { - panic!("as_foundry called on a non-foundry output"); + panic!("invalid downcast of non-FoundryOutput"); } } @@ -271,12 +283,27 @@ impl Output { } /// Gets the output as an actual [`NftOutput`]. - /// PANIC: do not call on a non-nft output. + /// NOTE: Will panic if the output is not a [`NftOutput`]. pub fn as_nft(&self) -> &NftOutput { if let Self::Nft(output) = self { output } else { - panic!("as_nft called on a non-nft output"); + panic!("invalid downcast of non-NftOutput"); + } + } + + /// Checks whether the output is a [`DelegationOutput`]. + pub fn is_delegation(&self) -> bool { + matches!(self, Self::Delegation(_)) + } + + /// Gets the output as an actual [`DelegationOutput`]. + /// NOTE: Will panic if the output is not a [`DelegationOutput`]. + pub fn as_delegation(&self) -> &DelegationOutput { + if let Self::Delegation(output) = self { + output + } else { + panic!("invalid downcast of non-DelegationOutput"); } } @@ -290,6 +317,12 @@ impl Output { account_transition: Option, ) -> Result<(Address, Option
), Error> { match self { + Self::Basic(output) => Ok(( + *output + .unlock_conditions() + .locked_address(output.address(), current_time), + None, + )), Self::Account(output) => { if account_transition.unwrap_or(AccountTransition::State) == AccountTransition::State { // Account address is only unlocked if it's a state transition @@ -301,19 +334,19 @@ impl Output { Ok((*output.governor_address(), None)) } } - Self::Basic(output) => Ok(( + Self::Foundry(output) => Ok((Address::Account(*output.account_address()), None)), + Self::Nft(output) => Ok(( *output .unlock_conditions() .locked_address(output.address(), current_time), - None, + Some(Address::Nft(output.nft_address(output_id))), )), - Self::Nft(output) => Ok(( + Self::Delegation(output) => Ok(( *output .unlock_conditions() .locked_address(output.address(), current_time), - Some(Address::Nft(output.nft_address(output_id))), + None, )), - Self::Foundry(output) => Ok((Address::Account(*output.account_address()), None)), } } @@ -415,6 +448,10 @@ impl Packable for Output { NftOutput::KIND.pack(packer)?; output.pack(packer) } + Self::Delegation(output) => { + DelegationOutput::KIND.pack(packer)?; + output.pack(packer) + } }?; Ok(()) @@ -429,6 +466,7 @@ impl Packable for Output { AccountOutput::KIND => Self::from(AccountOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), FoundryOutput::KIND => Self::from(FoundryOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), NftOutput::KIND => Self::from(NftOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), + DelegationOutput::KIND => Self::from(DelegationOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), k => return Err(Error::InvalidOutputKind(k)).map_err(UnpackError::Packable), }) } @@ -478,6 +516,7 @@ pub mod dto { pub use super::{ account::dto::AccountOutputDto, basic::dto::BasicOutputDto, + delegation::dto::DelegationOutputDto, foundry::dto::FoundryOutputDto, metadata::dto::OutputMetadataDto, nft::dto::NftOutputDto, @@ -508,6 +547,7 @@ pub mod dto { Account(AccountOutputDto), Foundry(FoundryOutputDto), Nft(NftOutputDto), + Delegation(DelegationOutputDto), } impl From<&Output> for OutputDto { @@ -517,6 +557,7 @@ pub mod dto { Output::Account(o) => Self::Account(o.into()), Output::Foundry(o) => Self::Foundry(o.into()), Output::Nft(o) => Self::Nft(o.into()), + Output::Delegation(o) => Self::Delegation(o.into()), } } } @@ -528,6 +569,7 @@ pub mod dto { OutputDto::Account(o) => Self::Account(AccountOutput::try_from_dto(o, token_supply)?), OutputDto::Foundry(o) => Self::Foundry(FoundryOutput::try_from_dto(o, token_supply)?), OutputDto::Nft(o) => Self::Nft(NftOutput::try_from_dto(o, token_supply)?), + OutputDto::Delegation(o) => Self::Delegation(DelegationOutput::try_from_dto(o, token_supply)?), }) } @@ -537,6 +579,7 @@ pub mod dto { OutputDto::Account(o) => Self::Account(AccountOutput::try_from_dto_unverified(o)?), OutputDto::Foundry(o) => Self::Foundry(FoundryOutput::try_from_dto_unverified(o)?), OutputDto::Nft(o) => Self::Nft(NftOutput::try_from_dto_unverified(o)?), + OutputDto::Delegation(o) => Self::Delegation(DelegationOutput::try_from_dto_unverified(o)?), }) } } @@ -566,6 +609,11 @@ pub mod dto { NftOutputDto::deserialize(value) .map_err(|e| serde::de::Error::custom(format!("cannot deserialize NFT output: {e}")))?, ), + DelegationOutput::KIND => { + Self::Delegation(DelegationOutputDto::deserialize(value).map_err(|e| { + serde::de::Error::custom(format!("cannot deserialize delegation output: {e}")) + })?) + } _ => return Err(serde::de::Error::custom("invalid output type")), }, ) @@ -584,6 +632,7 @@ pub mod dto { T3(&'a AccountOutputDto), T4(&'a FoundryOutputDto), T5(&'a NftOutputDto), + T6(&'a DelegationOutputDto), } #[derive(Serialize)] struct TypedOutput<'a> { @@ -603,6 +652,9 @@ pub mod dto { Self::Nft(o) => TypedOutput { output: OutputDto_::T5(o), }, + Self::Delegation(o) => TypedOutput { + output: OutputDto_::T6(o), + }, }; output.serialize(serializer) } diff --git a/sdk/src/types/block/payload/transaction/essence/regular.rs b/sdk/src/types/block/payload/transaction/essence/regular.rs index 824b776950..791bbc0409 100644 --- a/sdk/src/types/block/payload/transaction/essence/regular.rs +++ b/sdk/src/types/block/payload/transaction/essence/regular.rs @@ -263,10 +263,11 @@ fn verify_outputs(outputs: &[Output], visitor: &ProtocolPara for output in outputs.iter() { let (amount, native_tokens, chain_id) = match output { - Output::Basic(output) => (output.amount(), output.native_tokens(), None), - Output::Account(output) => (output.amount(), output.native_tokens(), Some(output.chain_id())), - Output::Foundry(output) => (output.amount(), output.native_tokens(), Some(output.chain_id())), - Output::Nft(output) => (output.amount(), output.native_tokens(), Some(output.chain_id())), + Output::Basic(output) => (output.amount(), Some(output.native_tokens()), None), + Output::Account(output) => (output.amount(), Some(output.native_tokens()), Some(output.chain_id())), + Output::Foundry(output) => (output.amount(), Some(output.native_tokens()), Some(output.chain_id())), + Output::Nft(output) => (output.amount(), Some(output.native_tokens()), Some(output.chain_id())), + Output::Delegation(output) => (output.amount(), None, Some(output.chain_id())), }; amount_sum = amount_sum @@ -278,12 +279,14 @@ fn verify_outputs(outputs: &[Output], visitor: &ProtocolPara return Err(Error::InvalidTransactionAmountSum(amount_sum as u128)); } - native_tokens_count = native_tokens_count.checked_add(native_tokens.len() as u8).ok_or( - Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16 + native_tokens.len() as u16), - )?; + if let Some(native_tokens) = native_tokens { + native_tokens_count = native_tokens_count.checked_add(native_tokens.len() as u8).ok_or( + Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16 + native_tokens.len() as u16), + )?; - if native_tokens_count > NativeTokens::COUNT_MAX { - return Err(Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16)); + if native_tokens_count > NativeTokens::COUNT_MAX { + return Err(Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16)); + } } if let Some(chain_id) = chain_id { @@ -308,22 +311,25 @@ fn verify_outputs_unverified(outputs: &[Output]) -> Result<( for output in outputs.iter() { let (amount, native_tokens, chain_id) = match output { - Output::Basic(output) => (output.amount(), output.native_tokens(), None), - Output::Account(output) => (output.amount(), output.native_tokens(), Some(output.chain_id())), - Output::Foundry(output) => (output.amount(), output.native_tokens(), Some(output.chain_id())), - Output::Nft(output) => (output.amount(), output.native_tokens(), Some(output.chain_id())), + Output::Basic(output) => (output.amount(), Some(output.native_tokens()), None), + Output::Account(output) => (output.amount(), Some(output.native_tokens()), Some(output.chain_id())), + Output::Foundry(output) => (output.amount(), Some(output.native_tokens()), Some(output.chain_id())), + Output::Nft(output) => (output.amount(), Some(output.native_tokens()), Some(output.chain_id())), + Output::Delegation(output) => (output.amount(), None, Some(output.chain_id())), }; amount_sum = amount_sum .checked_add(amount) .ok_or(Error::InvalidTransactionAmountSum(amount_sum as u128 + amount as u128))?; - native_tokens_count = native_tokens_count.checked_add(native_tokens.len() as u8).ok_or( - Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16 + native_tokens.len() as u16), - )?; + if let Some(native_tokens) = native_tokens { + native_tokens_count = native_tokens_count.checked_add(native_tokens.len() as u8).ok_or( + Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16 + native_tokens.len() as u16), + )?; - if native_tokens_count > NativeTokens::COUNT_MAX { - return Err(Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16)); + if native_tokens_count > NativeTokens::COUNT_MAX { + return Err(Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16)); + } } if let Some(chain_id) = chain_id { diff --git a/sdk/src/types/block/semantic.rs b/sdk/src/types/block/semantic.rs index 62d8a3f1f6..13f1d44b9e 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic.rs @@ -201,25 +201,31 @@ pub fn semantic_validation( Output::Basic(output) => ( output.unlock(output_id, unlock, inputs, &mut context), output.amount(), - output.native_tokens(), + Some(output.native_tokens()), output.unlock_conditions(), ), Output::Account(output) => ( output.unlock(output_id, unlock, inputs, &mut context), output.amount(), - output.native_tokens(), + Some(output.native_tokens()), output.unlock_conditions(), ), Output::Foundry(output) => ( output.unlock(output_id, unlock, inputs, &mut context), output.amount(), - output.native_tokens(), + Some(output.native_tokens()), output.unlock_conditions(), ), Output::Nft(output) => ( output.unlock(output_id, unlock, inputs, &mut context), output.amount(), - output.native_tokens(), + Some(output.native_tokens()), + output.unlock_conditions(), + ), + Output::Delegation(output) => ( + output.unlock(output_id, unlock, inputs, &mut context), + output.amount(), + None, output.unlock_conditions(), ), }; @@ -250,12 +256,14 @@ pub fn semantic_validation( .checked_add(amount) .ok_or(Error::ConsumedAmountOverflow)?; - for native_token in consumed_native_tokens.iter() { - let native_token_amount = context.input_native_tokens.entry(*native_token.token_id()).or_default(); + if let Some(consumed_native_tokens) = consumed_native_tokens { + for native_token in consumed_native_tokens.iter() { + let native_token_amount = context.input_native_tokens.entry(*native_token.token_id()).or_default(); - *native_token_amount = native_token_amount - .checked_add(native_token.amount()) - .ok_or(Error::ConsumedNativeTokensAmountOverflow)?; + *native_token_amount = native_token_amount + .checked_add(native_token.amount()) + .ok_or(Error::ConsumedNativeTokensAmountOverflow)?; + } } } @@ -271,14 +279,15 @@ pub fn semantic_validation( .ok_or(Error::CreatedAmountOverflow)?; } - (output.amount(), output.native_tokens(), output.features()) + (output.amount(), Some(output.native_tokens()), Some(output.features())) } - Output::Account(output) => (output.amount(), output.native_tokens(), output.features()), - Output::Foundry(output) => (output.amount(), output.native_tokens(), output.features()), - Output::Nft(output) => (output.amount(), output.native_tokens(), output.features()), + Output::Account(output) => (output.amount(), Some(output.native_tokens()), Some(output.features())), + Output::Foundry(output) => (output.amount(), Some(output.native_tokens()), Some(output.features())), + Output::Nft(output) => (output.amount(), Some(output.native_tokens()), Some(output.features())), + Output::Delegation(output) => (output.amount(), None, None), }; - if let Some(sender) = features.sender() { + if let Some(sender) = features.and_then(|f| f.sender()) { if !context.unlocked_addresses.contains(sender.address()) { return Ok(ConflictReason::UnverifiedSender); } @@ -289,15 +298,17 @@ pub fn semantic_validation( .checked_add(amount) .ok_or(Error::CreatedAmountOverflow)?; - for native_token in created_native_tokens.iter() { - let native_token_amount = context - .output_native_tokens - .entry(*native_token.token_id()) - .or_default(); + if let Some(created_native_tokens) = created_native_tokens { + for native_token in created_native_tokens.iter() { + let native_token_amount = context + .output_native_tokens + .entry(*native_token.token_id()) + .or_default(); - *native_token_amount = native_token_amount - .checked_add(native_token.amount()) - .ok_or(Error::CreatedNativeTokensAmountOverflow)?; + *native_token_amount = native_token_amount + .checked_add(native_token.amount()) + .ok_or(Error::CreatedNativeTokensAmountOverflow)?; + } } } diff --git a/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/melt_native_token.rs b/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/melt_native_token.rs index 48c0374d3c..941eab67f0 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/melt_native_token.rs +++ b/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/melt_native_token.rs @@ -105,7 +105,7 @@ where } } // Not interested in these outputs here - Output::Basic(_) | Output::Nft(_) => {} + _ => {} } if existing_account_output_data.is_some() && existing_foundry_output.is_some() {