diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a976d738d..88d9f1b9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,10 @@ env: CARGO_TERM_COLOR: always DASEL_VERSION: https://github.com/TomWright/dasel/releases/download/v2.3.6/dasel_linux_amd64 RUSTFLAGS: "-D warnings" - FUEL_CORE_VERSION: 0.22.1 + FUEL_CORE_VERSION: 0.23.0 FUEL_CORE_PATCH_BRANCH: RUST_VERSION: 1.74.0 - FORC_VERSION: 0.50.0 + FORC_VERSION: 0.51.1 FORC_PATCH_BRANCH: "" FORC_PATCH_REVISION: "" diff --git a/Cargo.toml b/Cargo.toml index 31302deca..42cda2325 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,25 +68,25 @@ tokio = { version = "1.34.0", default-features = false } tracing = "0.1.40" trybuild = "1.0.85" uint = { version = "0.9.5", default-features = false } -which = { version = "5.0.0", default-features = false } +which = { version = "6.0.0", default-features = false } zeroize = "1.7.0" # Dependencies from the `fuel-core` repository: -fuel-core = { version = "0.22.1", default-features = false } -fuel-core-chain-config = { version = "0.22.1", default-features = false } -fuel-core-client = { version = "0.22.1", default-features = false } -fuel-core-poa = { version = "0.22.1", default-features = false } -fuel-core-services = { version = "0.22.1", default-features = false } -fuel-core-types = { version = "0.22.1", default-features = false } +fuel-core = { version = "0.23.0", default-features = false } +fuel-core-chain-config = { version = "0.23.0", default-features = false } +fuel-core-client = { version = "0.23.0", default-features = false } +fuel-core-poa = { version = "0.23.0", default-features = false } +fuel-core-services = { version = "0.23.0", default-features = false } +fuel-core-types = { version = "0.23.0", default-features = false } # Dependencies from the `fuel-vm` repository: -fuel-asm = { version = "0.43.2" } -fuel-crypto = { version = "0.43.2" } -fuel-merkle = { version = "0.43.2" } -fuel-storage = { version = "0.43.2" } -fuel-tx = { version = "0.43.2" } -fuel-types = { version = "0.43.2" } -fuel-vm = { version = "0.43.2" } +fuel-asm = { version = "0.47.1" } +fuel-crypto = { version = "0.47.1" } +fuel-merkle = { version = "0.47.1" } +fuel-storage = { version = "0.47.1" } +fuel-tx = { version = "0.47.1" } +fuel-types = { version = "0.47.1" } +fuel-vm = { version = "0.47.1" } # Workspace projects fuels = { version = "0.55.0", path = "./packages/fuels" } diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 38fd55b8a..5362bb423 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -51,7 +51,6 @@ - [`Address`](./types/address.md) - [`ContractId`](./types/contract-id.md) - [`AssetId`](./types/asset-id.md) - - [Converting native types](./types/conversion.md) - [`Bech32`](./types/bech32.md) - [Structs and enums](./types/custom_types.md) - [`String`](./types/string.md) @@ -60,6 +59,7 @@ - [`B512`](./types/B512.md) - [`EvmAddress`](./types/evm_address.md) - [Vectors](./types/vectors.md) + - [Converting types](./types/conversion.md) - [Codec](./codec/index.md) - [Encoding](./codec/encoding.md) - [Decoding](./codec/decoding.md) diff --git a/docs/src/calling-contracts/call-params.md b/docs/src/calling-contracts/call-params.md index 75d019897..bea40fcb0 100644 --- a/docs/src/calling-contracts/call-params.md +++ b/docs/src/calling-contracts/call-params.md @@ -28,7 +28,7 @@ Then, in Rust, after setting up and deploying the above contract, you can config `call_params` returns a result to ensure you don't forward assets to a contract method that isn't payable. -In the following example, we try to forward an amount of 100 of the base asset to `non_payable`. As its name suggests, `non_payable` isn't annotated with `#[payable]` in the contract code. Passing `CallParameters` with an amount other than 0 leads to an `InvalidCallParameters` error: +In the following example, we try to forward an amount of `100` of the base asset to `non_payable`. As its name suggests, `non_payable` isn't annotated with `#[payable]` in the contract code. Passing `CallParameters` with an amount other than `0` leads to an error: ```rust,ignore {{#include ../../../packages/fuels/tests/contracts.rs:non_payable_params}} diff --git a/docs/src/calling-contracts/cost-estimation.md b/docs/src/calling-contracts/cost-estimation.md index 12b30db1b..824bbcf8d 100644 --- a/docs/src/calling-contracts/cost-estimation.md +++ b/docs/src/calling-contracts/cost-estimation.md @@ -1,16 +1,9 @@ # Estimating contract call cost -With the function `estimate_transaction_cost(tolerance: Option)` provided by `ContractCallHandler` and `ContractMultiCallHandler`, you can get a cost estimation for a specific call. The return type, `TransactionCost`, is a struct that contains relevant information for the estimation: +With the function `estimate_transaction_cost(tolerance: Option, block_horizon: Option)` provided by `ContractCallHandler` and `ContractMultiCallHandler`, you can get a cost estimation for a specific call. The return type, `TransactionCost`, is a struct that contains relevant information for the estimation: ```rust,ignore -TransactionCost { - min_gas_price: u64, - min_byte_price: u64, - gas_price: u64, - gas_used: u64, - metered_bytes_size: u64, - total_fee: f64, // where total_fee is the sum of the gas and byte fees -} +{{#include ../../../packages/fuels-accounts/src/provider.rs:transaction_cost}} ``` Below are examples that show how to get the estimated transaction cost from single and multi call transactions. @@ -24,3 +17,5 @@ Below are examples that show how to get the estimated transaction cost from sing ``` The transaction cost estimation can be used to set the gas limit for an actual call, or to show the user the estimated cost. + +> **Note** The same estimation interface is available for scripts. diff --git a/docs/src/calling-contracts/logs.md b/docs/src/calling-contracts/logs.md index 39f8b3ccd..3d13b22d1 100644 --- a/docs/src/calling-contracts/logs.md +++ b/docs/src/calling-contracts/logs.md @@ -22,4 +22,4 @@ You can use the `decode_logs()` function to retrieve a `LogResult` struct contai Due to possible performance hits, it is not recommended to use `decode_logs()` outside of a debugging scenario. -> **Note:** String slices can not be logged directly. Use the `__to_str_array()` function to convert it to a `str[N]` first. +> **Note:** String slices cannot be logged directly. Use the `__to_str_array()` function to convert it to a `str[N]` first. diff --git a/docs/src/calling-contracts/tx-policies.md b/docs/src/calling-contracts/tx-policies.md index e83b90c2e..c02d83964 100644 --- a/docs/src/calling-contracts/tx-policies.md +++ b/docs/src/calling-contracts/tx-policies.md @@ -10,13 +10,13 @@ Transaction policies are defined as follows: Where: -1. **Gas Price** - Maximum gas price for transaction. +1. **Tip** - amount to pay the block producer to prioritize the transaction. 2. **Witness Limit** - The maximum amount of witness data allowed for the transaction. 3. **Maturity** - Block until which the transaction cannot be included. 4. **Max Fee** - The maximum fee payable by this transaction. 5. **Script Gas Limit** - The maximum amount of gas the transaction may consume for executing its script code. -When the **Script Gas Limit** is not set, the Rust SDK will estimate the consumed gas in the background and set it as the limit. Similarly, if no **Gas Price** is defined, the Rust SDK defaults to the network's minimum gas price. +When the **Script Gas Limit** is not set, the Rust SDK will estimate the consumed gas in the background and set it as the limit. If the **Witness Limit** is not set, the SDK will set it to the size of all witnesses and signatures defined in the transaction builder. diff --git a/docs/src/types/conversion.md b/docs/src/types/conversion.md index f376e7abc..b5984de83 100644 --- a/docs/src/types/conversion.md +++ b/docs/src/types/conversion.md @@ -1,7 +1,214 @@ -# Converting native types +# Converting Types + +Below you can find examples for common type conversions: + +- [Convert Between Native Types](#convert-between-native-types) +- [Convert to `Bytes32`](#convert-to-bytes32) +- [Convert to `Address`](#convert-to-address) +- [Convert to `ContractId`](#convert-to-contractid) +- [Convert to `Identity`](#convert-to-identity) +- [Convert to `AssetId`](#convert-to-assetid) +- [Convert to `Bech32`](#convert-to-bech32) +- [Convert to `str`](#convert-to-str) +- [Convert to `Bits256`](#convert-to-bits256) +- [Convert to `Bytes`](#convert-to-bytes) +- [Convert to `B512`](#convert-to-b512) +- [Convert to `EvmAddress`](#convert-to-evmaddress) + +## Convert Between Native Types You might want to convert between the native types (`Bytes32`, `Address`, `ContractId`, and `AssetId`). Because these types are wrappers on `[u8; 32]`, converting is a matter of dereferencing one and instantiating the other using the dereferenced value. Here's an example: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:type_conversion}} ``` + +## Convert to `Bytes32` + +Convert a `[u8; 32]` array to `Bytes32`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:array_to_bytes32}} +``` + +Convert a hex string to `Bytes32`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:hex_string_to_bytes32}} +``` + +## Convert to `Address` + +Convert a `[u8; 32]` array to an `Address`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:array_to_address}} +``` + +Convert a `Bech32` address to an `Address`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:bech32_to_address}} +``` + +Convert a wallet to an `Address`: + +```rust,ignore +{{#include ../../../examples/wallets/src/lib.rs:wallet_to_address}} +``` + +Convert a hex string to an `Address`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:hex_string_to_address}} +``` + +## Convert to `ContractId` + +Convert a `[u8; 32]` array to to `ContractId`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:array_to_contract_id}} +``` + +Convert a hex string to a `ContractId`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:string_to_contract_id}} +``` + +Convert a contract instance to a `ContractId`: + +```rust,ignore +{{#include ../../../packages/fuels/tests/logs.rs:instance_to_contract_id}} +``` + +## Convert to `Identity` + +Convert an `Address` to an `Identity`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:address_to_identity}} +``` + +Convert a `ContractId` to an `Identity`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:contract_id_to_identity}} +``` + +## Convert to `AssetId` + +Convert a `[u8; 32]` array to an `AssetId`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:array_to_asset_id}} +``` + +Convert a hex string to an `AssetId`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:string_to_asset_id}} +``` + +## Convert to `Bech32` + +Convert a `[u8; 32]` array to a `Bech32` address: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:array_to_bech32}} +``` + +Convert `Bytes32` to a `Bech32` address: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:bytes32_to_bech32}} +``` + +Convert a string to a `Bech32` address: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:str_to_bech32}} +``` + +Convert an `Address` to a `Bech32` address: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:address_to_bech32}} +``` + +## Convert to `str` + +Convert a `ContractId` to a `str`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:contract_id_to_str}} +``` + +Convert an `Address` to a `str`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:address_to_str}} +``` + +Convert an `AssetId` to a `str`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:asset_id_to_str}} +``` + +Convert `Bytes32` to a `str`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:bytes32_to_str}} +``` + +## Convert to `Bits256` + +Convert a hex string to `Bits256`: + +```rust,ignore +{{#include ../../../packages/fuels-core/src/types/core/bits.rs:hex_str_to_bits256}} +``` + +Convert a `ContractId` to `Bits256`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:contract_id_to_bits256}} +``` + +Convert an `Address` to `Bits256`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:address_to_bits256}} +``` + +Convert an `AssetId` to `Bits256`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:asset_id_to_bits256}} +``` + +## Convert to `Bytes` + +Convert a string to `Bytes`: + +```rust,ignore +{{#include ../../../packages/fuels-core/src/types/core/bytes.rs:hex_string_to_bytes32}} +``` + +## Convert to `B512` + +Convert two hex strings to `B512`: + +```rust,ignore +{{#include ../../../packages/fuels/tests/types_contracts.rs:b512_example}} +``` + +## Convert to `EvmAddress` + +Convert a `Bits256` address to an `EvmAddress`: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:b256_to_evm_address}} +``` diff --git a/examples/contracts/src/lib.rs b/examples/contracts/src/lib.rs index 3869d53b2..1e1b7d8e2 100644 --- a/examples/contracts/src/lib.rs +++ b/examples/contracts/src/lib.rs @@ -1,10 +1,12 @@ #[cfg(test)] mod tests { - use fuels::core::codec::EncoderConfig; use fuels::{ - core::codec::DecoderConfig, + core::codec::{DecoderConfig, EncoderConfig}, prelude::{Config, LoadConfiguration, StorageConfiguration}, - types::{errors::Result, Bits256}, + types::{ + errors::{transaction::Reason, Result}, + Bits256, + }, }; #[tokio::test] @@ -96,15 +98,16 @@ mod tests { // ANCHOR: contract_call_cost_estimation let contract_instance = MyContract::new(contract_id, wallet); - let tolerance = 0.0; + let tolerance = Some(0.0); + let block_horizon = Some(1); let transaction_cost = contract_instance .methods() .initialize_counter(42) // Build the ABI call - .estimate_transaction_cost(Some(tolerance)) // Get estimated transaction cost + .estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost .await?; // ANCHOR_END: contract_call_cost_estimation - assert_eq!(transaction_cost.gas_used, 841); + assert_eq!(transaction_cost.gas_used, 791); Ok(()) } @@ -142,7 +145,7 @@ mod tests { // Optional: Configure deployment parameters let tx_policies = TxPolicies::default() - .with_gas_price(0) + .with_tip(1) .with_script_gas_limit(1_000_000) .with_maturity(0); @@ -279,7 +282,7 @@ mod tests { let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods(); let tx_policies = TxPolicies::default() - .with_gas_price(1) + .with_tip(1) .with_script_gas_limit(1_000_000) .with_maturity(0); @@ -403,7 +406,7 @@ mod tests { assert!(matches!( response, - Err(Error::RevertTransactionError { .. }) + Err(Error::Transaction(Reason::Reverted { .. })) )); // ANCHOR_END: dependency_estimation_fail @@ -438,80 +441,32 @@ mod tests { #[tokio::test] #[allow(unused_variables)] async fn get_contract_outputs() -> Result<()> { - use fuels::{prelude::*, tx::Receipt}; - { - abigen!(Contract( - name = "TestContract", - abi = - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - )); - let wallet = launch_provider_and_get_wallet().await?; - - let contract_id = Contract::load_from( - "../../packages/fuels/tests/contracts/contract_test/out/debug/contract_test.bin", - LoadConfiguration::default(), - )? - .deploy(&wallet, TxPolicies::default()) - .await?; + use fuels::prelude::*; - let contract_methods = TestContract::new(contract_id, wallet).methods(); - - let response = contract_methods.increment_counter(162).call().await?; - let response = contract_methods.increment_counter(162).call().await; - match response { - // The transaction is valid and executes to completion - Ok(call_response) => { - let receipts: Vec = call_response.receipts; - // Do things with logs and receipts - } - // The transaction is malformed - Err(Error::ValidationError(e)) => { - println!("Transaction is malformed (ValidationError): {e}"); - } - // Failed request to provider - Err(Error::ProviderError(reason)) => { - println!("Provider request failed with reason: {reason}"); - } - // The transaction is valid but reverts - Err(Error::RevertTransactionError { - reason, receipts, .. - }) => { - println!("ContractCall failed with reason: {reason}"); - println!("Transaction receipts are: {receipts:?}"); - } - Err(_) => {} - } - } - { - // ANCHOR: deployed_contracts - abigen!(Contract( - name = "MyContract", - // Replace with your contract ABI.json path - abi = - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - )); - let wallet_original = launch_provider_and_get_wallet().await?; - - let wallet = wallet_original.clone(); - // Your bech32m encoded contract ID. - let contract_id: Bech32ContractId = - "fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy" - .parse() - .expect("Invalid ID"); - - let connected_contract_instance = MyContract::new(contract_id, wallet); - // You can now use the `connected_contract_instance` just as you did above! - // ANCHOR_END: deployed_contracts - - let wallet = wallet_original; - // ANCHOR: deployed_contracts_hex - let contract_id: ContractId = - "0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3" - .parse() - .expect("Invalid ID"); - let connected_contract_instance = MyContract::new(contract_id, wallet); - // ANCHOR_END: deployed_contracts_hex - } + // ANCHOR: deployed_contracts + abigen!(Contract( + name = "MyContract", + // Replace with your contract ABI.json path + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); + let wallet_original = launch_provider_and_get_wallet().await?; + + let wallet = wallet_original.clone(); + // Your bech32m encoded contract ID. + let contract_id: Bech32ContractId = + "fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?; + + let connected_contract_instance = MyContract::new(contract_id, wallet); + // You can now use the `connected_contract_instance` just as you did above! + // ANCHOR_END: deployed_contracts + + let wallet = wallet_original; + // ANCHOR: deployed_contracts_hex + let contract_id: ContractId = + "0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?; + + let connected_contract_instance = MyContract::new(contract_id, wallet); + // ANCHOR_END: deployed_contracts_hex Ok(()) } @@ -640,13 +595,14 @@ mod tests { .add_call(call_handler_1) .add_call(call_handler_2); - let tolerance = 0.0; + let tolerance = Some(0.0); + let block_horizon = Some(1); let transaction_cost = multi_call_handler - .estimate_transaction_cost(Some(tolerance)) // Get estimated transaction cost + .estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost .await?; // ANCHOR_END: multi_call_cost_estimation - assert_eq!(transaction_cost.gas_used, 1251); + assert_eq!(transaction_cost.gas_used, 1162); Ok(()) } diff --git a/examples/cookbook/src/lib.rs b/examples/cookbook/src/lib.rs index d37d1a286..94f39707e 100644 --- a/examples/cookbook/src/lib.rs +++ b/examples/cookbook/src/lib.rs @@ -35,9 +35,7 @@ mod tests { // ANCHOR: liquidity_wallet let base_asset_id: AssetId = - "0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c" - .parse() - .unwrap(); + "0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?; let asset_ids = [AssetId::default(), base_asset_id]; let asset_configs = asset_ids @@ -171,7 +169,7 @@ mod tests { let mut inputs = vec![]; let mut outputs = vec![]; for (id_string, amount) in balances { - let id = AssetId::from_str(&id_string).unwrap(); + let id = AssetId::from_str(&id_string)?; // leave the base asset to cover transaction fees if id == BASE_ASSET_ID { @@ -304,7 +302,7 @@ mod tests { // ANCHOR_END: custom_tx_adjust // ANCHOR: custom_tx_policies - let tx_policies = TxPolicies::default().with_gas_price(1); + let tx_policies = TxPolicies::default().with_tip(1); let tb = tb.with_tx_policies(tx_policies); // ANCHOR_END: custom_tx_policies diff --git a/examples/debugging/src/lib.rs b/examples/debugging/src/lib.rs index ef3424f34..bae1cceca 100644 --- a/examples/debugging/src/lib.rs +++ b/examples/debugging/src/lib.rs @@ -5,9 +5,10 @@ mod tests { use fuel_abi_types::abi::program::ProgramABI; use fuels::{ core::{ - codec::{calldata, fn_selector, resolve_fn_selector}, + codec::{calldata, fn_selector, resolve_fn_selector, ABIDecoder}, traits::Parameterize, }, + macros::abigen, types::{errors::Result, param_types::ParamType, SizedAsciiString}, }; @@ -69,4 +70,103 @@ mod tests { Ok(()) } + + #[test] + fn decoded_debug_matches_rust_debug() -> Result<()> { + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/types/contracts/generics/out/debug/generics-abi.json" + )); + + let json_abi_file = + "../../packages/fuels/tests/types/contracts/generics/out/debug/generics-abi.json"; + let abi_file_contents = std::fs::read_to_string(json_abi_file)?; + + let parsed_abi: ProgramABI = serde_json::from_str(&abi_file_contents)?; + + let type_lookup = parsed_abi + .types + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + let get_first_fn_argument = |fn_name: &str| { + parsed_abi + .functions + .iter() + .find(|abi_fun| abi_fun.name == fn_name) + .expect("should be there") + .inputs + .first() + .expect("should be there") + }; + let decoder = ABIDecoder::default(); + + { + // simple struct with a single generic parameter + let type_application = get_first_fn_argument("struct_w_generic"); + let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?; + + let expected_struct = SimpleGeneric { + single_generic_param: 123u64, + }; + + assert_eq!( + format!("{expected_struct:?}"), + decoder.decode_as_debug_str(¶m_type, &[0, 0, 0, 0, 0, 0, 0, 123])? + ); + } + { + // struct that delegates the generic param internally + let type_application = get_first_fn_argument("struct_delegating_generic"); + let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?; + + let expected_struct = PassTheGenericOn { + one: SimpleGeneric { + single_generic_param: SizedAsciiString::<3>::try_from("abc")?, + }, + }; + + assert_eq!( + format!("{expected_struct:?}"), + decoder.decode_as_debug_str(¶m_type, &[97, 98, 99])? + ); + } + { + // enum with generic in variant + let type_application = get_first_fn_argument("enum_w_generic"); + let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?; + + let expected_enum = EnumWGeneric::B(10u64); + + assert_eq!( + format!("{expected_enum:?}"), + decoder.decode_as_debug_str( + ¶m_type, + &[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10] + )? + ); + } + { + // logged type + let logged_type = parsed_abi + .logged_types + .as_ref() + .expect("has logs") + .first() + .expect("has log"); + + let param_type = + ParamType::try_from_type_application(&logged_type.application, &type_lookup)?; + + let expected_u8 = 1; + + assert_eq!( + format!("{expected_u8}"), + decoder.decode_as_debug_str(¶m_type, &[0, 0, 0, 0, 0, 0, 0, 1])? + ); + } + + Ok(()) + } } diff --git a/examples/predicates/src/lib.rs b/examples/predicates/src/lib.rs index 689ac9909..25bb5b8b4 100644 --- a/examples/predicates/src/lib.rs +++ b/examples/predicates/src/lib.rs @@ -11,19 +11,13 @@ mod tests { async fn predicate_example() -> Result<()> { // ANCHOR: predicate_wallets let secret_key1: SecretKey = - "0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301" - .parse() - .unwrap(); + "0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?; let secret_key2: SecretKey = - "0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd" - .parse() - .unwrap(); + "0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?; let secret_key3: SecretKey = - "0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb" - .parse() - .unwrap(); + "0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?; let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None); let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None); diff --git a/examples/providers/src/lib.rs b/examples/providers/src/lib.rs index f8e9f9d2a..e5228f15f 100644 --- a/examples/providers/src/lib.rs +++ b/examples/providers/src/lib.rs @@ -18,9 +18,9 @@ mod tests { let provider = Provider::connect("beta-4.fuel.network").await.unwrap(); // Setup a private key - let secret = - SecretKey::from_str("a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568") - .unwrap(); + let secret = SecretKey::from_str( + "a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568", + )?; // Create the wallet let wallet = WalletUnlocked::new_from_private_key(secret, Some(provider)); @@ -33,10 +33,9 @@ mod tests { let port = provider.url().split(':').last().unwrap(); // ANCHOR: local_node_address - let _provider = Provider::connect(format!("127.0.0.1:{port}")) - .await - .unwrap(); + let _provider = Provider::connect(format!("127.0.0.1:{port}")).await?; // ANCHOR_END: local_node_address + Ok(()) } diff --git a/examples/types/src/lib.rs b/examples/types/src/lib.rs index a3f4637d2..322395e4d 100644 --- a/examples/types/src/lib.rs +++ b/examples/types/src/lib.rs @@ -5,6 +5,7 @@ mod tests { #[cfg(feature = "fuel-core-lib")] use fuels::prelude::Config; use fuels::prelude::Result; + use fuels::types::{Bits256, EvmAddress, Identity}; #[tokio::test] async fn bytes32() -> Result<()> { @@ -21,13 +22,17 @@ mod tests { assert_eq!([0u8; 32], *b256); // From a `[u8; 32]`. + // ANCHOR: array_to_bytes32 let my_slice = [1u8; 32]; let b256 = Bytes32::new(my_slice); + // ANCHOR_END: array_to_bytes32 assert_eq!([1u8; 32], *b256); // From a hex string. + // ANCHOR: hex_string_to_bytes32 let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000"; - let b256 = Bytes32::from_str(hex_str).expect("failed to create Bytes32 from string"); + let b256 = Bytes32::from_str(hex_str)?; + // ANCHOR_END: hex_string_to_bytes32 assert_eq!([0u8; 32], *b256); // ANCHOR_END: bytes32 @@ -39,6 +44,10 @@ mod tests { assert_eq!(hex_str[2..], b256_string); assert_eq!(hex_str, b256_hex_string); + // ANCHOR: bytes32_to_str + let _str_from_bytes32: &str = b256.to_string().as_str(); + // ANCHOR_END: bytes32_to_str + Ok(()) } #[tokio::test] @@ -56,15 +65,36 @@ mod tests { assert_eq!([0u8; 32], *address); // From a `[u8; 32]`. + // ANCHOR: array_to_address let my_slice = [1u8; 32]; let address = Address::new(my_slice); + // ANCHOR_END: array_to_address assert_eq!([1u8; 32], *address); // From a string. + // ANCHOR: hex_string_to_address let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000"; - let address = Address::from_str(hex_str).expect("failed to create Address from string"); + let address = Address::from_str(hex_str)?; + // ANCHOR_END: hex_string_to_address assert_eq!([0u8; 32], *address); // ANCHOR_END: address + + // ANCHOR: address_to_identity + let _identity_from_address = Identity::Address(address); + // ANCHOR_END: address_to_identity + + // ANCHOR: address_to_str + let _str_from_address: &str = address.to_string().as_str(); + // ANCHOR_END: address_to_str + + // ANCHOR: address_to_bits256 + let bits_256 = Bits256(address.into()); + // ANCHOR_END: address_to_bits256 + + // ANCHOR: b256_to_evm_address + let _evm_address = EvmAddress::from(bits_256); + // ANCHOR_END: b256_to_evm_address + Ok(()) } #[tokio::test] @@ -73,27 +103,36 @@ mod tests { use fuels::types::{bech32::Bech32Address, Address, Bytes32}; // New from HRP string and a hash + // ANCHOR: array_to_bech32 let hrp = "fuel"; let my_slice = [1u8; 32]; let _bech32_address = Bech32Address::new(hrp, my_slice); + // ANCHOR_END: array_to_bech32 // Note that you can also pass a hash stored as Bytes32 to new: + // ANCHOR: bytes32_to_bech32 let my_hash = Bytes32::new([1u8; 32]); let _bech32_address = Bech32Address::new(hrp, my_hash); + // ANCHOR_END: bytes32_to_bech32 // From a string. + // ANCHOR: str_to_bech32 let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2"; - let bech32_address = - Bech32Address::from_str(address).expect("failed to create Bech32 address from string"); + let bech32_address = Bech32Address::from_str(address)?; + // ANCHOR_END: str_to_bech32 assert_eq!([0u8; 32], *bech32_address.hash()); // From Address + // ANCHOR: address_to_bech32 let plain_address = Address::new([0u8; 32]); let bech32_address = Bech32Address::from(plain_address); + // ANCHOR_END: address_to_bech32 assert_eq!([0u8; 32], *bech32_address.hash()); // Convert to Address + // ANCHOR: bech32_to_address let _plain_address: Address = bech32_address.into(); + // ANCHOR_END: bech32_to_address // ANCHOR_END: bech32 @@ -114,13 +153,17 @@ mod tests { assert_eq!([0u8; 32], *asset_id); // From a `[u8; 32]`. + // ANCHOR: array_to_asset_id let my_slice = [1u8; 32]; let asset_id = AssetId::new(my_slice); + // ANCHOR_END: array_to_asset_id assert_eq!([1u8; 32], *asset_id); // From a string. + // ANCHOR: string_to_asset_id let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000"; - let asset_id = AssetId::from_str(hex_str).expect("failed to create AssetId from string"); + let asset_id = AssetId::from_str(hex_str)?; + // ANCHOR_END: string_to_asset_id assert_eq!([0u8; 32], *asset_id); // ANCHOR_END: asset_id Ok(()) @@ -140,16 +183,28 @@ mod tests { assert_eq!([0u8; 32], *contract_id); // From a `[u8; 32]`. + // ANCHOR: array_to_contract_id let my_slice = [1u8; 32]; let contract_id = ContractId::new(my_slice); + // ANCHOR_END: array_to_contract_id assert_eq!([1u8; 32], *contract_id); // From a string. + // ANCHOR: string_to_contract_id let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000"; - let contract_id = - ContractId::from_str(hex_str).expect("failed to create ContractId from string"); + let contract_id = ContractId::from_str(hex_str)?; + // ANCHOR_END: string_to_contract_id assert_eq!([0u8; 32], *contract_id); // ANCHOR_END: contract_id + + // ANCHOR: contract_id_to_identity + let _identity_from_contract_id = Identity::ContractId(contract_id); + // ANCHOR_END: contract_id_to_identity + + // ANCHOR: contract_id_to_str + let _str_from_contract_id: &str = contract_id.to_string().as_str(); + // ANCHOR_END: contract_id_to_str + Ok(()) } @@ -164,6 +219,19 @@ mod tests { assert_eq!([1u8; 32], *asset_id); // ANCHOR_END: type_conversion + + // ANCHOR: asset_id_to_str + let _str_from_asset_id: &str = asset_id.to_string().as_str(); + // ANCHOR_END: asset_id_to_str + + // ANCHOR: contract_id_to_bits256 + let _contract_id_to_bits_256 = Bits256(contract_id.into()); + // ANCHOR_END: contract_id_to_bits256 + + // ANCHOR: asset_id_to_bits256 + let _asset_id_to_bits_256 = Bits256(asset_id.into()); + // ANCHOR_END: asset_id_to_bits256 + Ok(()) } diff --git a/examples/wallets/src/lib.rs b/examples/wallets/src/lib.rs index ec263f21f..e12905797 100644 --- a/examples/wallets/src/lib.rs +++ b/examples/wallets/src/lib.rs @@ -303,6 +303,11 @@ mod tests { let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets); let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?; // ANCHOR_END: custom_assets_wallet_short + + // ANCHOR: wallet_to_address + let wallet_unlocked = WalletUnlocked::new_random(None); + let address: Address = wallet_unlocked.address().into(); + // ANCHOR_END: wallet_to_address Ok(()) } @@ -353,9 +358,9 @@ mod tests { let wallet = wallets.first().unwrap(); let amount = 1000; - let base_layer_address = - Address::from_str("0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe") - .expect("Invalid address."); + let base_layer_address = Address::from_str( + "0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe", + )?; let base_layer_address = Bech32Address::from(base_layer_address); // Transfer an amount of 1000 to the specified base layer address let (tx_id, msg_id, _receipts) = wallet @@ -369,12 +374,13 @@ mod tests { .try_provider()? .get_message_proof(&tx_id, &msg_id, None, Some(2)) .await? - .expect("Failed to retrieve message proof."); + .expect("failed to retrieve message proof"); // Verify the amount and recipient assert_eq!(proof.amount, amount); assert_eq!(proof.recipient, base_layer_address); // ANCHOR_END: wallet_withdraw_to_base + Ok(()) } } diff --git a/packages/fuels-accounts/Cargo.toml b/packages/fuels-accounts/Cargo.toml index b5b97fe47..3918be22a 100644 --- a/packages/fuels-accounts/Cargo.toml +++ b/packages/fuels-accounts/Cargo.toml @@ -15,11 +15,11 @@ chrono = { workspace = true } elliptic-curve = { workspace = true, default-features = false } eth-keystore = { workspace = true, optional = true } fuel-core-client = { workspace = true, optional = true } +fuel-core-types = { workspace = true } fuel-crypto = { workspace = true, features = ["random"] } fuel-tx = { workspace = true } fuel-types = { workspace = true, features = ["random"] } fuels-core = { workspace = true, default-features = false } -hex = { workspace = true, default-features = false, features = ["std"] } rand = { workspace = true, default-features = false } semver = { workspace = true } tai64 = { workspace = true, features = ["serde"] } diff --git a/packages/fuels-accounts/src/account.rs b/packages/fuels-accounts/src/account.rs index 54061a593..ebeb01d80 100644 --- a/packages/fuels-accounts/src/account.rs +++ b/packages/fuels-accounts/src/account.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt::Display}; +use std::collections::HashMap; use async_trait::async_trait; use fuel_core_client::client::pagination::{PaginatedResult, PaginationRequest}; @@ -10,7 +10,7 @@ use fuels_core::{ bech32::{Bech32Address, Bech32ContractId}, coin::Coin, coin_type::CoinType, - errors::{Error, Result}, + errors::Result, input::Input, message::Message, transaction::{Transaction, TxPolicies}, @@ -26,36 +26,11 @@ use crate::{ provider::{Provider, ResourceFilter}, }; -#[derive(Debug)] -pub struct AccountError(String); - -impl AccountError { - pub fn no_provider() -> Self { - Self("No provider was setup: make sure to set_provider in your account!".to_string()) - } -} - -impl Display for AccountError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -impl std::error::Error for AccountError {} - -impl From for Error { - fn from(e: AccountError) -> Self { - Error::AccountError(e.0) - } -} - -pub type AccountResult = std::result::Result; - #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait ViewOnlyAccount: std::fmt::Debug + Send + Sync + Clone { fn address(&self) -> &Bech32Address; - fn try_provider(&self) -> AccountResult<&Provider>; + fn try_provider(&self) -> Result<&Provider>; async fn get_transactions( &self, @@ -82,7 +57,6 @@ pub trait ViewOnlyAccount: std::fmt::Debug + Send + Sync + Clone { self.try_provider()? .get_asset_balance(self.address(), *asset_id) .await - .map_err(Into::into) } /// Gets all unspent messages owned by the account. @@ -94,10 +68,7 @@ pub trait ViewOnlyAccount: std::fmt::Debug + Send + Sync + Clone { /// the coins because we are only returning the sum of UTXOs coins amount and not the UTXOs /// coins themselves. async fn get_balances(&self) -> Result> { - self.try_provider()? - .get_balances(self.address()) - .await - .map_err(Into::into) + self.try_provider()?.get_balances(self.address()).await } /// Get some spendable resources (coins and messages) of asset `asset_id` owned by the account @@ -115,10 +86,7 @@ pub trait ViewOnlyAccount: std::fmt::Debug + Send + Sync + Clone { ..Default::default() }; - self.try_provider()? - .get_spendable_resources(filter) - .await - .map_err(Into::into) + self.try_provider()?.get_spendable_resources(filter).await } } @@ -225,7 +193,7 @@ pub trait Account: ViewOnlyAccount { balance: u64, asset_id: AssetId, tx_policies: TxPolicies, - ) -> std::result::Result<(String, Vec), Error> { + ) -> Result<(String, Vec)> { let provider = self.try_provider()?; let zeroes = Bytes32::zeroed(); @@ -277,7 +245,7 @@ pub trait Account: ViewOnlyAccount { to: &Bech32Address, amount: u64, tx_policies: TxPolicies, - ) -> std::result::Result<(TxId, Nonce, Vec), Error> { + ) -> Result<(TxId, Nonce, Vec)> { let provider = self.try_provider()?; let inputs = self @@ -324,16 +292,13 @@ mod tests { use crate::wallet::WalletUnlocked; #[tokio::test] - async fn sign_and_verify() -> std::result::Result<(), Box> { + async fn sign_and_verify() -> Result<()> { // ANCHOR: sign_message let mut rng = StdRng::seed_from_u64(2322u64); let mut secret_seed = [0u8; 32]; rng.fill_bytes(&mut secret_seed); - let secret = secret_seed - .as_slice() - .try_into() - .expect("The seed size is valid"); + let secret = secret_seed.as_slice().try_into()?; // Create a wallet using the private key created above. let wallet = WalletUnlocked::new_from_private_key(secret, None); @@ -366,10 +331,12 @@ mod tests { async fn dry_run_and_get_used_gas(&self, _: FuelTransaction, _: f32) -> Result { Ok(0) } + fn consensus_parameters(&self) -> &ConsensusParameters { &self.c_param } - async fn min_gas_price(&self) -> Result { + + async fn estimate_gas_price(&self, _block_header: u32) -> Result { Ok(0) } } @@ -425,7 +392,7 @@ mod tests { assert_eq!(signature, tx_signature); // Check if the signature is what we expect it to be - assert_eq!(signature, Signature::from_str("a7446cb9703d3bc9e68677715fc7ef6ed72ff4eeac0c67bdb0d9b9c8ba38048e078e38fdd85bf988cefd3737005f1be97ed8b9662f002b0480d4404ebb397fed")?); + assert_eq!(signature, Signature::from_str("37cf6bdefc9e673f99a7fdbbeff454cb5c1bdf632c072f19cf8ac68fa1ede2749c568c56f87d73fc5c97f73b76dfe637422b77c1fdc6010fb4f488444ff5df1a")?); // Recover the address that signed the transaction let recovered_address = signature.recover(&message)?; diff --git a/packages/fuels-accounts/src/accounts_utils.rs b/packages/fuels-accounts/src/accounts_utils.rs index 5d818ece5..762635ff7 100644 --- a/packages/fuels-accounts/src/accounts_utils.rs +++ b/packages/fuels-accounts/src/accounts_utils.rs @@ -4,7 +4,7 @@ use fuels_core::{ constants::BASE_ASSET_ID, types::{ bech32::Bech32Address, - errors::{error, Result}, + errors::{error, error_transaction, Error, Result}, input::Input, transaction_builders::TransactionBuilder, }, @@ -24,7 +24,10 @@ pub async fn calculate_missing_base_amount( let transaction_fee = tb .fee_checked_from_tx(provider) .await? - .ok_or(error!(InvalidData, "Error calculating TransactionFee"))?; + .ok_or(error_transaction!( + Other, + "error calculating `TransactionFee`" + ))?; let available_amount = available_base_amount(tb); @@ -75,3 +78,10 @@ pub fn adjust_inputs_outputs( .push(Output::change(address.into(), 0, BASE_ASSET_ID)); } } + +pub(crate) fn try_provider_error() -> Error { + error!( + Other, + "no provider available. Make sure to use `set_provider`" + ) +} diff --git a/packages/fuels-accounts/src/predicate.rs b/packages/fuels-accounts/src/predicate.rs index cdbdb62ba..0f76a0aff 100644 --- a/packages/fuels-accounts/src/predicate.rs +++ b/packages/fuels-accounts/src/predicate.rs @@ -8,7 +8,9 @@ use fuels_core::{ }; #[cfg(feature = "std")] -use crate::{provider::Provider, Account, AccountError, AccountResult, ViewOnlyAccount}; +use crate::accounts_utils::try_provider_error; +#[cfg(feature = "std")] +use crate::{provider::Provider, Account, ViewOnlyAccount}; #[derive(Debug, Clone)] pub struct Predicate { @@ -98,8 +100,8 @@ impl ViewOnlyAccount for Predicate { self.address() } - fn try_provider(&self) -> AccountResult<&Provider> { - self.provider.as_ref().ok_or(AccountError::no_provider()) + fn try_provider(&self) -> Result<&Provider> { + self.provider.as_ref().ok_or_else(try_provider_error) } } diff --git a/packages/fuels-accounts/src/provider.rs b/packages/fuels-accounts/src/provider.rs index ec68195ef..28bcf9da4 100644 --- a/packages/fuels-accounts/src/provider.rs +++ b/packages/fuels-accounts/src/provider.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt::Debug, io, net::SocketAddr}; +use std::{collections::HashMap, fmt::Debug, net::SocketAddr}; mod retry_util; mod retryable_client; @@ -10,28 +10,34 @@ use std::sync::Arc; use chrono::{DateTime, Utc}; use fuel_core_client::client::{ pagination::{PageDirection, PaginatedResult, PaginationRequest}, - types::{balance::Balance, contract::ContractBalance}, + types::{ + balance::Balance, + contract::ContractBalance, + gas_price::{EstimateGasPrice, LatestGasPrice}, + }, }; +use fuel_core_types::services::executor::{TransactionExecutionResult, TransactionExecutionStatus}; use fuel_tx::{ - AssetId, ConsensusParameters, Receipt, ScriptExecutionResult, Transaction as FuelTransaction, - TxId, UtxoId, + AssetId, ConsensusParameters, Receipt, Transaction as FuelTransaction, TxId, UtxoId, }; -use fuel_types::{Address, Bytes32, ChainId, Nonce}; +use fuel_types::{Address, BlockHeight, Bytes32, ChainId, Nonce}; #[cfg(feature = "coin-cache")] use fuels_core::types::coin_type_id::CoinTypeId; use fuels_core::{ - constants::{BASE_ASSET_ID, DEFAULT_GAS_ESTIMATION_TOLERANCE}, + constants::{ + BASE_ASSET_ID, DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON, DEFAULT_GAS_ESTIMATION_TOLERANCE, + }, types::{ bech32::{Bech32Address, Bech32ContractId}, block::Block, chain_info::ChainInfo, coin::Coin, coin_type::CoinType, - errors::{error, Error, Result}, + errors::{error, Result}, message::Message, message_proof::MessageProof, node_info::NodeInfo, - transaction::Transaction, + transaction::{Transaction, Transactions}, transaction_builders::DryRunner, transaction_response::TransactionResponse, tx_status::TxStatus, @@ -40,7 +46,6 @@ use fuels_core::{ pub use retry_util::{Backoff, RetryConfig}; use supported_versions::{check_fuel_core_version_compatibility, VersionCompatibility}; use tai64::Tai64; -use thiserror::Error; #[cfg(feature = "coin-cache")] use tokio::sync::Mutex; @@ -48,16 +53,15 @@ use tokio::sync::Mutex; use crate::coin_cache::CoinsCache; use crate::provider::retryable_client::RetryableClient; -type ProviderResult = std::result::Result; - #[derive(Debug)] +// ANCHOR: transaction_cost pub struct TransactionCost { - pub min_gas_price: u64, pub gas_price: u64, pub gas_used: u64, pub metered_bytes_size: u64, pub total_fee: u64, } +// ANCHOR_END: transaction_cost pub(crate) struct ResourceQueries { utxos: Vec, @@ -131,28 +135,6 @@ impl Default for ResourceFilter { } } -#[derive(Debug, Error)] -pub enum ProviderError { - // Every IO error in the context of Provider comes from the gql client - #[error("Client request error: {0}")] - ClientRequestError(#[from] io::Error), - #[error("Receipts have not yet been propagated. Retry the request later.")] - ReceiptsNotPropagatedYet, - #[error("Invalid Fuel client version: {0}")] - InvalidFuelClientVersion(#[from] semver::Error), - #[error("Unsupported Fuel client version. Current version: {current}, supported version: {supported}")] - UnsupportedFuelClientVersion { - current: semver::Version, - supported: semver::Version, - }, -} - -impl From for Error { - fn from(e: ProviderError) -> Self { - Error::ProviderError(e.to_string()) - } -} - /// Encapsulates common client operations in the SDK. /// Note that you may also use `client`, which is an instance /// of `FuelClient`, directly, which provides a broader API. @@ -231,6 +213,7 @@ impl Provider { } self.validate_transaction(tx.clone()).await?; + Ok(()) } @@ -245,15 +228,11 @@ impl Provider { async fn validate_transaction(&self, tx: T) -> Result<()> { let tolerance = 0.0; - let TransactionCost { - gas_used, - min_gas_price, - .. - } = self - .estimate_transaction_cost(tx.clone(), Some(tolerance)) + let TransactionCost { gas_used, .. } = self + .estimate_transaction_cost(tx.clone(), Some(tolerance), None) .await?; - tx.validate_gas(min_gas_price, gas_used)?; + tx.validate_gas(gas_used)?; Ok(()) } @@ -272,15 +251,11 @@ impl Provider { Ok(tx_id) } - pub async fn tx_status(&self, tx_id: &TxId) -> ProviderResult { - self.client - .transaction_status(tx_id) - .await - .map(Into::into) - .map_err(Into::into) + pub async fn tx_status(&self, tx_id: &TxId) -> Result { + Ok(self.client.transaction_status(tx_id).await?.into()) } - pub async fn chain_info(&self) -> ProviderResult { + pub async fn chain_info(&self) -> Result { Ok(self.client.chain_info().await?.into()) } @@ -288,8 +263,12 @@ impl Provider { &self.consensus_parameters } - fn ensure_client_version_is_supported(node_info: &NodeInfo) -> ProviderResult<()> { - let node_version = node_info.node_version.parse::()?; + fn ensure_client_version_is_supported(node_info: &NodeInfo) -> Result<()> { + let node_version = node_info + .node_version + .parse::() + .map_err(|e| error!(Provider, "could not parse Fuel client version: {}", e))?; + let VersionCompatibility { supported_version, is_major_supported, @@ -298,15 +277,18 @@ impl Provider { } = check_fuel_core_version_compatibility(node_version.clone()); if !is_major_supported || !is_minor_supported { - return Err(ProviderError::UnsupportedFuelClientVersion { - current: node_version, - supported: supported_version, - }); + return Err(error!( + Provider, + "unsupported Fuel client version. \\ + Current version: {}, supported version: {}", + node_version, + supported_version + )); } else if !is_patch_supported { tracing::warn!( fuel_client_version = %node_version, supported_version = %supported_version, - "The patch versions of the client and SDK differ.", + "the patch versions of the client and SDK differ", ); }; @@ -317,51 +299,92 @@ impl Provider { self.consensus_parameters.chain_id } - pub async fn node_info(&self) -> ProviderResult { + pub async fn node_info(&self) -> Result { Ok(self.client.node_info().await?.into()) } - pub async fn checked_dry_run(&self, tx: T) -> Result { - let receipts = self.dry_run(tx).await?; - Ok(Self::tx_status_from_receipts(receipts)) + pub async fn latest_gas_price(&self) -> Result { + Ok(self.client.latest_gas_price().await?) } - fn tx_status_from_receipts(receipts: Vec) -> TxStatus { - let revert_reason = receipts.iter().find_map(|receipt| match receipt { - Receipt::ScriptResult { result, .. } if *result != ScriptExecutionResult::Success => { - Some(format!("{result:?}")) - } - _ => None, - }); - - match revert_reason { - Some(reason) => TxStatus::Revert { - receipts, - reason, - revert_id: 0, - }, - None => TxStatus::Success { receipts }, - } + pub async fn estimate_gas_price(&self, block_horizon: u32) -> Result { + Ok(self.client.estimate_gas_price(block_horizon).await?) + } + + pub async fn dry_run(&self, tx: impl Transaction) -> Result { + let [(_, tx_status)] = self + .client + .dry_run(Transactions::new().insert(tx).as_slice()) + .await? + .into_iter() + .map(Self::tx_status_from_execution_status) + .collect::>() + .try_into() + .expect("should have only one element"); + + Ok(tx_status) } - pub async fn dry_run(&self, tx: T) -> Result> { - let receipts = self.client.dry_run(&tx.into()).await?; + pub async fn dry_run_multiple( + &self, + transactions: Transactions, + ) -> Result> { + Ok(self + .client + .dry_run(transactions.as_slice()) + .await? + .into_iter() + .map(Self::tx_status_from_execution_status) + .collect()) + } - Ok(receipts) + fn tx_status_from_execution_status( + tx_execution_status: TransactionExecutionStatus, + ) -> (TxId, TxStatus) { + ( + tx_execution_status.id, + match tx_execution_status.result { + TransactionExecutionResult::Success { receipts, .. } => { + TxStatus::Success { receipts } + } + TransactionExecutionResult::Failed { receipts, result } => TxStatus::Revert { + reason: TransactionExecutionResult::reason(&receipts, &result), + receipts, + revert_id: 0, + }, + }, + ) } - pub async fn dry_run_no_validation(&self, tx: T) -> Result> { - let receipts = self.client.dry_run_opt(&tx.into(), Some(false)).await?; + pub async fn dry_run_no_validation(&self, tx: impl Transaction) -> Result { + let [(_, tx_status)] = self + .client + .dry_run_opt(Transactions::new().insert(tx).as_slice(), Some(false)) + .await? + .into_iter() + .map(Self::tx_status_from_execution_status) + .collect::>() + .try_into() + .expect("should have only one element"); - Ok(receipts) + Ok(tx_status) } - /// Gets all unspent coins owned by address `from`, with asset ID `asset_id`. - pub async fn get_coins( + pub async fn dry_run_no_validation_multiple( &self, - from: &Bech32Address, - asset_id: AssetId, - ) -> ProviderResult> { + transactions: Transactions, + ) -> Result> { + Ok(self + .client + .dry_run_opt(transactions.as_slice(), Some(false)) + .await? + .into_iter() + .map(Self::tx_status_from_execution_status) + .collect()) + } + + /// Gets all unspent coins owned by address `from`, with asset ID `asset_id`. + pub async fn get_coins(&self, from: &Bech32Address, asset_id: AssetId) -> Result> { let mut coins: Vec = vec![]; let mut cursor = None; @@ -390,10 +413,7 @@ impl Provider { Ok(coins) } - async fn request_coins_to_spend( - &self, - filter: ResourceFilter, - ) -> ProviderResult> { + async fn request_coins_to_spend(&self, filter: ResourceFilter) -> Result> { let queries = filter.resource_queries(); let res = self @@ -406,8 +426,8 @@ impl Provider { .await? .into_iter() .flatten() - .map(|c| CoinType::try_from(c).map_err(ProviderError::ClientRequestError)) - .collect::>>()?; + .map(CoinType::try_from) + .collect::>>()?; Ok(res) } @@ -416,10 +436,7 @@ impl Provider { /// amount `amount`. The returned coins (UTXOs) are actual coins that can be spent. The number /// of coins (UXTOs) is optimized to prevent dust accumulation. #[cfg(not(feature = "coin-cache"))] - pub async fn get_spendable_resources( - &self, - filter: ResourceFilter, - ) -> ProviderResult> { + pub async fn get_spendable_resources(&self, filter: ResourceFilter) -> Result> { self.request_coins_to_spend(filter).await } @@ -431,8 +448,9 @@ impl Provider { pub async fn get_spendable_resources( &self, mut filter: ResourceFilter, - ) -> ProviderResult> { + ) -> Result> { self.extend_filter_with_cached(&mut filter).await; + self.request_coins_to_spend(filter).await } @@ -472,11 +490,11 @@ impl Provider { &self, address: &Bech32Address, asset_id: AssetId, - ) -> ProviderResult { - self.client + ) -> Result { + Ok(self + .client .balance(&address.into(), Some(&asset_id)) - .await - .map_err(Into::into) + .await?) } /// Get the balance of all spendable coins `asset_id` for contract with id `contract_id`. @@ -484,20 +502,17 @@ impl Provider { &self, contract_id: &Bech32ContractId, asset_id: AssetId, - ) -> ProviderResult { - self.client + ) -> Result { + Ok(self + .client .contract_balance(&contract_id.into(), Some(&asset_id)) - .await - .map_err(Into::into) + .await?) } /// Get all the spendable balances of all assets for address `address`. This is different from /// getting the coins because we are only returning the numbers (the sum of UTXOs coins amount /// for each asset id) and not the UTXOs coins themselves - pub async fn get_balances( - &self, - address: &Bech32Address, - ) -> ProviderResult> { + pub async fn get_balances(&self, address: &Bech32Address) -> Result> { // We don't paginate results because there are likely at most ~100 different assets in one // wallet let pagination = PaginationRequest { @@ -527,7 +542,7 @@ impl Provider { pub async fn get_contract_balances( &self, contract_id: &Bech32ContractId, - ) -> ProviderResult> { + ) -> Result> { // We don't paginate results because there are likely at most ~100 different assets in one // wallet let pagination = PaginationRequest { @@ -554,17 +569,14 @@ impl Provider { Ok(balances) } - pub async fn get_transaction_by_id( - &self, - tx_id: &TxId, - ) -> ProviderResult> { + pub async fn get_transaction_by_id(&self, tx_id: &TxId) -> Result> { Ok(self.client.transaction(tx_id).await?.map(Into::into)) } pub async fn get_transactions( &self, request: PaginationRequest, - ) -> ProviderResult> { + ) -> Result> { let pr = self.client.transactions(request).await?; Ok(PaginatedResult { @@ -580,7 +592,7 @@ impl Provider { &self, owner: &Bech32Address, request: PaginationRequest, - ) -> ProviderResult> { + ) -> Result> { let pr = self .client .transactions_by_owner(&owner.into(), request) @@ -594,11 +606,11 @@ impl Provider { }) } - pub async fn latest_block_height(&self) -> ProviderResult { + pub async fn latest_block_height(&self) -> Result { Ok(self.chain_info().await?.latest_block.header.height) } - pub async fn latest_block_time(&self) -> ProviderResult>> { + pub async fn latest_block_time(&self) -> Result>> { Ok(self.chain_info().await?.latest_block.header.time) } @@ -606,25 +618,29 @@ impl Provider { &self, blocks_to_produce: u32, start_time: Option>, - ) -> io::Result { + ) -> Result { let start_time = start_time.map(|time| Tai64::from_unix(time.timestamp()).0); - self.client + + Ok(self + .client .produce_blocks(blocks_to_produce, start_time) - .await - .map(Into::into) + .await? + .into()) + } + + pub async fn block(&self, block_id: &Bytes32) -> Result> { + Ok(self.client.block(block_id).await?.map(Into::into)) } - /// Get block by id. - pub async fn block(&self, block_id: &Bytes32) -> ProviderResult> { - let block = self.client.block(block_id).await?.map(Into::into); - Ok(block) + pub async fn block_by_height(&self, height: BlockHeight) -> Result> { + Ok(self.client.block_by_height(height).await?.map(Into::into)) } // - Get block(s) pub async fn get_blocks( &self, request: PaginationRequest, - ) -> ProviderResult> { + ) -> Result> { let pr = self.client.blocks(request).await?; Ok(PaginatedResult { @@ -639,22 +655,23 @@ impl Provider { &self, tx: T, tolerance: Option, + block_horizon: Option, ) -> Result { - let NodeInfo { min_gas_price, .. } = self.node_info().await?; - let gas_price = std::cmp::max(tx.gas_price(), min_gas_price); + let block_horizon = block_horizon.unwrap_or(DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON); let tolerance = tolerance.unwrap_or(DEFAULT_GAS_ESTIMATION_TOLERANCE); + let EstimateGasPrice { gas_price, .. } = self.estimate_gas_price(block_horizon).await?; + let gas_used = self .get_gas_used_with_tolerance(tx.clone(), tolerance) .await?; let transaction_fee = tx .clone() - .fee_checked_from_tx(&self.consensus_parameters) + .fee_checked_from_tx(&self.consensus_parameters, gas_price) .expect("Error calculating TransactionFee"); Ok(TransactionCost { - min_gas_price, gas_price, gas_used, metered_bytes_size: tx.metered_bytes_size() as u64, @@ -668,7 +685,9 @@ impl Provider { tx: T, tolerance: f64, ) -> Result { - let gas_used = self.get_gas_used(&self.dry_run_no_validation(tx).await?); + let receipts = self.dry_run_no_validation(tx).await?.take_receipts(); + let gas_used = self.get_gas_used(&receipts); + Ok((gas_used as f64 * (1.0 + tolerance)) as u64) } @@ -684,12 +703,13 @@ impl Provider { .unwrap_or(0) } - pub async fn get_messages(&self, from: &Bech32Address) -> ProviderResult> { + pub async fn get_messages(&self, from: &Bech32Address) -> Result> { let pagination = PaginationRequest { cursor: None, results: 100, direction: PageDirection::Forward, }; + Ok(self .client .messages(Some(&from.into()), pagination) @@ -706,7 +726,7 @@ impl Provider { nonce: &Nonce, commit_block_id: Option<&Bytes32>, commit_block_height: Option, - ) -> ProviderResult> { + ) -> Result> { let proof = self .client .message_proof( @@ -717,11 +737,13 @@ impl Provider { ) .await? .map(Into::into); + Ok(proof) } pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self { self.client.set_retry_config(retry_config); + self } } @@ -729,16 +751,20 @@ impl Provider { #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] impl DryRunner for Provider { async fn dry_run_and_get_used_gas(&self, tx: FuelTransaction, tolerance: f32) -> Result { - let receipts = self.client.dry_run_opt(&tx, Some(false)).await?; - let gas_used = self.get_gas_used(&receipts); + let [tx_execution_status] = self + .client + .dry_run_opt(&vec![tx], Some(false)) + .await? + .try_into() + .expect("should have only one element"); + + let gas_used = self.get_gas_used(tx_execution_status.result.receipts()); + Ok((gas_used as f64 * (1.0 + tolerance as f64)) as u64) } - async fn min_gas_price(&self) -> Result { - self.node_info() - .await - .map(|ni| ni.min_gas_price) - .map_err(Into::into) + async fn estimate_gas_price(&self, block_horizon: u32) -> Result { + Ok(self.estimate_gas_price(block_horizon).await?.gas_price) } fn consensus_parameters(&self) -> &ConsensusParameters { diff --git a/packages/fuels-accounts/src/provider/retry_util.rs b/packages/fuels-accounts/src/provider/retry_util.rs index 631ba81a2..fa9450df5 100644 --- a/packages/fuels-accounts/src/provider/retry_util.rs +++ b/packages/fuels-accounts/src/provider/retry_util.rs @@ -1,6 +1,6 @@ use std::{fmt::Debug, future::Future, num::NonZeroU32, time::Duration}; -use fuels_core::types::errors::{error, Result as SdkResult}; +use fuels_core::types::errors::{error, Result}; /// A set of strategies to control retry intervals between attempts. /// @@ -81,9 +81,9 @@ pub struct RetryConfig { // ANCHOR_END: retry_config impl RetryConfig { - pub fn new(max_attempts: u32, interval: Backoff) -> SdkResult { + pub fn new(max_attempts: u32, interval: Backoff) -> Result { let max_attempts = NonZeroU32::new(max_attempts) - .ok_or_else(|| error!(InvalidData, "`max_attempts` must be greater than 0."))?; + .ok_or_else(|| error!(Other, "`max_attempts` must be greater than `0`"))?; Ok(RetryConfig { max_attempts, @@ -95,7 +95,7 @@ impl RetryConfig { impl Default for RetryConfig { fn default() -> Self { Self { - max_attempts: NonZeroU32::new(1).expect("Should not fail!"), + max_attempts: NonZeroU32::new(1).expect("should not fail"), interval: Default::default(), } } @@ -145,7 +145,7 @@ where tokio::time::sleep(retry_config.interval.wait_duration(attempt)).await; } - last_result.expect("Should not happen") + last_result.expect("should not happen") } #[cfg(test)] @@ -153,10 +153,7 @@ mod tests { mod retry_until { use std::time::{Duration, Instant}; - use fuels_core::{ - error, - types::errors::{Error, Result}, - }; + use fuels_core::types::errors::{error, Result}; use tokio::sync::Mutex; use crate::provider::{retry_util, Backoff, RetryConfig}; @@ -164,7 +161,7 @@ mod tests { #[tokio::test] async fn returns_last_received_response() -> Result<()> { // given - let err_msgs = ["Err1", "Err2", "Err3"]; + let err_msgs = ["err1", "err2", "err3"]; let number_of_attempts = Mutex::new(0usize); let will_always_fail = || async { @@ -183,7 +180,7 @@ mod tests { retry_util::retry(will_always_fail, &retry_options, should_retry_fn).await; // then - assert_eq!(response, "Err3"); + assert_eq!(response, "err3"); Ok(()) } @@ -216,7 +213,7 @@ mod tests { let will_fail_and_record_timestamp = || async { timestamps.lock().await.push(Instant::now()); - Result::<()>::Err(Error::InvalidData("Error".to_string())) + Result::<()>::Err(error!(Other, "error")) }; let should_retry_fn = |_res: &_| -> bool { true }; @@ -244,7 +241,7 @@ mod tests { assert!( timestamps_spaced_out_at_least_100_mills, - "Retry did not wait for the specified time between attempts." + "retry did not wait for the specified time between attempts" ); Ok(()) @@ -257,7 +254,7 @@ mod tests { let will_fail_and_record_timestamp = || async { timestamps.lock().await.push(Instant::now()); - Result::<()>::Err(Error::InvalidData("Error".to_string())) + Result::<()>::Err(error!(Other, "error")) }; let should_retry_fn = |_res: &_| -> bool { true }; @@ -286,7 +283,7 @@ mod tests { assert!( timestamps_spaced_out_at_least_100_mills, - "Retry did not wait for the specified time between attempts." + "retry did not wait for the specified time between attempts" ); Ok(()) @@ -299,7 +296,7 @@ mod tests { let will_fail_and_record_timestamp = || async { timestamps.lock().await.push(Instant::now()); - Result::<()>::Err(error!(InvalidData, "Error")) + Result::<()>::Err(error!(Other, "error")) }; let should_retry_fn = |_res: &_| -> bool { true }; @@ -329,7 +326,7 @@ mod tests { assert!( timestamps_spaced_out_at_least_100_mills, - "Retry did not wait for the specified time between attempts." + "retry did not wait for the specified time between attempts" ); Ok(()) diff --git a/packages/fuels-accounts/src/provider/retryable_client.rs b/packages/fuels-accounts/src/provider/retryable_client.rs index 450652fb1..3a1da44fc 100644 --- a/packages/fuels-accounts/src/provider/retryable_client.rs +++ b/packages/fuels-accounts/src/provider/retryable_client.rs @@ -2,16 +2,35 @@ use std::{future::Future, io}; use fuel_core_client::client::{ pagination::{PaginatedResult, PaginationRequest}, - types, - types::{primitives::BlockId, TransactionResponse, TransactionStatus}, + types::{ + gas_price::{EstimateGasPrice, LatestGasPrice}, + primitives::{BlockId, TransactionId}, + Balance, Block, ChainInfo, Coin, CoinType, ContractBalance, Message, MessageProof, + NodeInfo, TransactionResponse, TransactionStatus, + }, FuelClient, }; -use fuel_tx::{Receipt, Transaction, TxId, UtxoId}; +use fuel_core_types::services::executor::TransactionExecutionStatus; +use fuel_tx::{Transaction, TxId, UtxoId}; use fuel_types::{Address, AssetId, BlockHeight, ContractId, Nonce}; -use fuels_core::{error, types::errors::Result}; +use fuels_core::types::errors::{error, Error, Result}; use crate::provider::{retry_util, RetryConfig}; +#[derive(Debug, thiserror::Error)] +pub(crate) enum RequestError { + #[error(transparent)] + IO(#[from] io::Error), +} + +type RequestResult = std::result::Result; + +impl From for Error { + fn from(e: RequestError) -> Self { + Error::Provider(e.to_string()) + } +} + #[derive(Debug, Clone)] pub(crate) struct RetryableClient { client: FuelClient, @@ -22,7 +41,8 @@ pub(crate) struct RetryableClient { impl RetryableClient { pub(crate) fn new(url: impl AsRef, retry_config: RetryConfig) -> Result { let url = url.as_ref().to_string(); - let client = FuelClient::new(&url).map_err(|err| error!(InfrastructureError, "{err}"))?; + let client = FuelClient::new(&url).map_err(|e| error!(Provider, "{e}"))?; + Ok(Self { client, retry_config, @@ -38,56 +58,73 @@ impl RetryableClient { self.retry_config = retry_config; } - async fn our_retry(&self, action: impl Fn() -> Fut) -> io::Result + async fn our_retry(&self, action: impl Fn() -> Fut) -> RequestResult where Fut: Future>, { - retry_util::retry(action, &self.retry_config, |result| result.is_err()).await + Ok(retry_util::retry(action, &self.retry_config, |result| result.is_err()).await?) } // DELEGATION START - pub async fn health(&self) -> io::Result { + pub async fn health(&self) -> RequestResult { self.our_retry(|| self.client.health()).await } - pub async fn transaction(&self, id: &TxId) -> io::Result> { + pub async fn transaction(&self, id: &TxId) -> RequestResult> { self.our_retry(|| self.client.transaction(id)).await } - pub(crate) async fn chain_info(&self) -> io::Result { + pub(crate) async fn chain_info(&self) -> RequestResult { self.our_retry(|| self.client.chain_info()).await } - pub async fn await_transaction_commit(&self, id: &TxId) -> io::Result { + pub async fn await_transaction_commit(&self, id: &TxId) -> RequestResult { self.our_retry(|| self.client.await_transaction_commit(id)) .await } - pub async fn submit_and_await_commit(&self, tx: &Transaction) -> io::Result { + pub async fn submit_and_await_commit( + &self, + tx: &Transaction, + ) -> RequestResult { self.our_retry(|| self.client.submit_and_await_commit(tx)) .await } - pub async fn submit(&self, tx: &Transaction) -> io::Result { + pub async fn submit(&self, tx: &Transaction) -> RequestResult { self.our_retry(|| self.client.submit(tx)).await } - pub async fn transaction_status(&self, id: &TxId) -> io::Result { + pub async fn transaction_status(&self, id: &TxId) -> RequestResult { self.our_retry(|| self.client.transaction_status(id)).await } - pub async fn node_info(&self) -> io::Result { + pub async fn node_info(&self) -> RequestResult { self.our_retry(|| self.client.node_info()).await } - pub async fn dry_run(&self, tx: &Transaction) -> io::Result> { + + pub async fn latest_gas_price(&self) -> RequestResult { + self.our_retry(|| self.client.latest_gas_price()).await + } + + pub async fn estimate_gas_price(&self, block_horizon: u32) -> RequestResult { + self.our_retry(|| self.client.estimate_gas_price(block_horizon)) + .await + .map(Into::into) + } + + pub async fn dry_run( + &self, + tx: &[Transaction], + ) -> RequestResult> { self.our_retry(|| self.client.dry_run(tx)).await } pub async fn dry_run_opt( &self, - tx: &Transaction, + tx: &[Transaction], utxo_validation: Option, - ) -> io::Result> { + ) -> RequestResult> { self.our_retry(|| self.client.dry_run_opt(tx, utxo_validation)) .await } @@ -97,7 +134,7 @@ impl RetryableClient { owner: &Address, asset_id: Option<&AssetId>, request: PaginationRequest, - ) -> io::Result> { + ) -> RequestResult> { self.our_retry(move || self.client.coins(owner, asset_id, request.clone())) .await } @@ -107,13 +144,15 @@ impl RetryableClient { owner: &Address, spend_query: Vec<(AssetId, u64, Option)>, excluded_ids: Option<(Vec, Vec)>, - ) -> io::Result>> { - self.client - .coins_to_spend(owner, spend_query, excluded_ids) - .await + ) -> RequestResult>> { + self.our_retry(move || { + self.client + .coins_to_spend(owner, spend_query.clone(), excluded_ids.clone()) + }) + .await } - pub async fn balance(&self, owner: &Address, asset_id: Option<&AssetId>) -> io::Result { + pub async fn balance(&self, owner: &Address, asset_id: Option<&AssetId>) -> RequestResult { self.our_retry(|| self.client.balance(owner, asset_id)) .await } @@ -122,7 +161,7 @@ impl RetryableClient { &self, id: &ContractId, asset: Option<&AssetId>, - ) -> io::Result { + ) -> RequestResult { self.our_retry(|| self.client.contract_balance(id, asset)) .await } @@ -131,7 +170,7 @@ impl RetryableClient { &self, contract: &ContractId, request: PaginationRequest, - ) -> io::Result> { + ) -> RequestResult> { self.our_retry(|| self.client.contract_balances(contract, request.clone())) .await } @@ -140,7 +179,7 @@ impl RetryableClient { &self, owner: &Address, request: PaginationRequest, - ) -> io::Result> { + ) -> RequestResult> { self.our_retry(|| self.client.balances(owner, request.clone())) .await } @@ -148,7 +187,7 @@ impl RetryableClient { pub async fn transactions( &self, request: PaginationRequest, - ) -> io::Result> { + ) -> RequestResult> { self.our_retry(|| self.client.transactions(request.clone())) .await } @@ -157,7 +196,7 @@ impl RetryableClient { &self, owner: &Address, request: PaginationRequest, - ) -> io::Result> { + ) -> RequestResult> { self.our_retry(|| self.client.transactions_by_owner(owner, request.clone())) .await } @@ -166,7 +205,7 @@ impl RetryableClient { &self, blocks_to_produce: u32, start_timestamp: Option, - ) -> io::Result { + ) -> RequestResult { self.our_retry(|| { self.client .produce_blocks(blocks_to_produce, start_timestamp) @@ -174,14 +213,18 @@ impl RetryableClient { .await } - pub async fn block(&self, id: &BlockId) -> io::Result> { + pub async fn block(&self, id: &BlockId) -> RequestResult> { self.our_retry(|| self.client.block(id)).await } + pub async fn block_by_height(&self, height: BlockHeight) -> RequestResult> { + self.our_retry(|| self.client.block_by_height(height)).await + } + pub async fn blocks( &self, request: PaginationRequest, - ) -> io::Result> { + ) -> RequestResult> { self.our_retry(|| self.client.blocks(request.clone())).await } @@ -189,7 +232,7 @@ impl RetryableClient { &self, owner: Option<&Address>, request: PaginationRequest, - ) -> io::Result> { + ) -> RequestResult> { self.our_retry(|| self.client.messages(owner, request.clone())) .await } @@ -201,7 +244,7 @@ impl RetryableClient { nonce: &Nonce, commit_block_id: Option<&BlockId>, commit_block_height: Option, - ) -> io::Result> { + ) -> RequestResult> { self.our_retry(|| { self.client .message_proof(transaction_id, nonce, commit_block_id, commit_block_height) diff --git a/packages/fuels-accounts/src/provider/supported_versions.rs b/packages/fuels-accounts/src/provider/supported_versions.rs index bd67bb516..478b6e894 100644 --- a/packages/fuels-accounts/src/provider/supported_versions.rs +++ b/packages/fuels-accounts/src/provider/supported_versions.rs @@ -1,7 +1,7 @@ use semver::Version; fn get_supported_fuel_core_version() -> Version { - "0.22.0".parse().unwrap() + "0.23.0".parse().expect("is valid version") } #[derive(Debug, PartialEq, Eq)] diff --git a/packages/fuels-accounts/src/wallet.rs b/packages/fuels-accounts/src/wallet.rs index b17eef2a8..c8f261883 100644 --- a/packages/fuels-accounts/src/wallet.rs +++ b/packages/fuels-accounts/src/wallet.rs @@ -2,57 +2,24 @@ use std::{fmt, ops, path::Path}; use async_trait::async_trait; use elliptic_curve::rand_core; -use eth_keystore::KeystoreError; use fuel_crypto::{Message, PublicKey, SecretKey, Signature}; use fuels_core::{ traits::Signer, types::{ bech32::{Bech32Address, FUEL_BECH32_HRP}, - errors::{Error, Result}, + errors::{error, Result}, input::Input, transaction_builders::TransactionBuilder, AssetId, }, }; use rand::{CryptoRng, Rng}; -use thiserror::Error; use zeroize::{Zeroize, ZeroizeOnDrop}; -use crate::{ - provider::{Provider, ProviderError}, - Account, AccountError, AccountResult, ViewOnlyAccount, -}; +use crate::{accounts_utils::try_provider_error, provider::Provider, Account, ViewOnlyAccount}; pub const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/1179993420'"; -#[derive(Error, Debug)] -/// Error thrown by the Wallet module -pub enum WalletError { - /// Error propagated from the hex crate. - #[error(transparent)] - Hex(#[from] hex::FromHexError), - /// Error propagated by parsing of a slice - #[error("Failed to parse slice")] - Parsing(#[from] std::array::TryFromSliceError), - /// Keystore error - #[error(transparent)] - KeystoreError(#[from] KeystoreError), - #[error(transparent)] - FuelCrypto(#[from] fuel_crypto::Error), - #[error(transparent)] - ProviderError(#[from] ProviderError), - #[error("Called `try_provider` method on wallet where no provider was set up")] - NoProviderError, -} - -impl From for Error { - fn from(e: WalletError) -> Self { - Error::WalletError(e.to_string()) - } -} - -type WalletResult = std::result::Result; - /// A FuelVM-compatible wallet that can be used to list assets, balances and more. /// /// Note that instances of the `Wallet` type only know their public address, and as a result can @@ -115,8 +82,8 @@ impl ViewOnlyAccount for Wallet { self.address() } - fn try_provider(&self) -> AccountResult<&Provider> { - self.provider.as_ref().ok_or(AccountError::no_provider()) + fn try_provider(&self) -> Result<&Provider> { + self.provider.as_ref().ok_or_else(try_provider_error) } } @@ -152,10 +119,7 @@ impl WalletUnlocked { /// Creates a new wallet from a mnemonic phrase. /// The default derivation path is used. - pub fn new_from_mnemonic_phrase( - phrase: &str, - provider: Option, - ) -> WalletResult { + pub fn new_from_mnemonic_phrase(phrase: &str, provider: Option) -> Result { let path = format!("{DEFAULT_DERIVATION_PATH_PREFIX}/0'/0/0"); Self::new_from_mnemonic_phrase_with_path(phrase, provider, &path) } @@ -166,7 +130,7 @@ impl WalletUnlocked { phrase: &str, provider: Option, path: &str, - ) -> WalletResult { + ) -> Result { let secret_key = SecretKey::new_from_mnemonic_phrase_with_path(phrase, path)?; Ok(Self::new_from_private_key(secret_key, provider)) @@ -178,16 +142,16 @@ impl WalletUnlocked { rng: &mut R, password: S, provider: Option, - ) -> WalletResult<(Self, String)> + ) -> Result<(Self, String)> where P: AsRef, R: Rng + CryptoRng + rand_core::CryptoRng, S: AsRef<[u8]>, { - let (secret, uuid) = eth_keystore::new(dir, rng, password, None)?; + let (secret, uuid) = + eth_keystore::new(dir, rng, password, None).map_err(|e| error!(Other, "{e}"))?; - let secret_key = - SecretKey::try_from(secret.as_slice()).expect("A new secret should be correct size"); + let secret_key = SecretKey::try_from(secret.as_slice()).expect("should have correct size"); let wallet = Self::new_from_private_key(secret_key, provider); @@ -196,33 +160,25 @@ impl WalletUnlocked { /// Encrypts the wallet's private key with the given password and saves it /// to the given path. - pub fn encrypt(&self, dir: P, password: S) -> WalletResult + pub fn encrypt(&self, dir: P, password: S) -> Result where P: AsRef, S: AsRef<[u8]>, { let mut rng = rand::thread_rng(); - Ok(eth_keystore::encrypt_key( - dir, - &mut rng, - *self.private_key, - password, - None, - )?) + eth_keystore::encrypt_key(dir, &mut rng, *self.private_key, password, None) + .map_err(|e| error!(Other, "{e}")) } /// Recreates a wallet from an encrypted JSON wallet given the provided path and password. - pub fn load_keystore( - keypath: P, - password: S, - provider: Option, - ) -> WalletResult + pub fn load_keystore(keypath: P, password: S, provider: Option) -> Result where P: AsRef, S: AsRef<[u8]>, { - let secret = eth_keystore::decrypt_key(keypath, password)?; + let secret = + eth_keystore::decrypt_key(keypath, password).map_err(|e| error!(Other, "{e}"))?; let secret_key = SecretKey::try_from(secret.as_slice()) .expect("Decrypted key should have a correct size"); Ok(Self::new_from_private_key(secret_key, provider)) @@ -238,8 +194,8 @@ impl ViewOnlyAccount for WalletUnlocked { self.wallet.address() } - fn try_provider(&self) -> AccountResult<&Provider> { - self.provider.as_ref().ok_or(AccountError::no_provider()) + fn try_provider(&self) -> Result<&Provider> { + self.provider.as_ref().ok_or_else(try_provider_error) } } @@ -300,7 +256,7 @@ impl ops::Deref for WalletUnlocked { /// Generates a random mnemonic phrase given a random number generator and the number of words to /// generate, `count`. -pub fn generate_mnemonic_phrase(rng: &mut R, count: usize) -> WalletResult { +pub fn generate_mnemonic_phrase(rng: &mut R, count: usize) -> Result { Ok(fuel_crypto::generate_mnemonic_phrase(rng, count)?) } @@ -338,8 +294,8 @@ mod tests { #[tokio::test] async fn mnemonic_generation() -> Result<()> { let mnemonic = generate_mnemonic_phrase(&mut rand::thread_rng(), 12)?; - let _wallet = WalletUnlocked::new_from_mnemonic_phrase(&mnemonic, None)?; + Ok(()) } diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs b/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs index d8a1595b8..1fd8649a4 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs @@ -65,7 +65,7 @@ impl FromStr for ProgramType { "Predicate" => ProgramType::Predicate, _ => { return Err(error!( - "'{string}' is not a valid program type. Expected one of: 'Script', 'Contract', 'Predicate'." + "`{string}` is not a valid program type. Expected one of: `Script`, `Contract`, `Predicate`" )) } }; diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs index 5bde162ba..18fffa729 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs @@ -1,7 +1,8 @@ +use std::default::Default; + use fuel_abi_types::abi::full_program::FullProgramABI; use proc_macro2::{Ident, TokenStream}; use quote::quote; -use std::default::Default; use crate::{ error::Result, diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/utils.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/utils.rs index 8b17b50c8..29f35e580 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/utils.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/utils.rs @@ -32,7 +32,7 @@ mod tests { fn correctly_extracts_the_main_fn() { let functions = ["fn_1", "main", "fn_2"].map(given_a_fun_named); - let fun = extract_main_fn(&functions).expect("Should have succeeded"); + let fun = extract_main_fn(&functions).expect("should have succeeded"); assert_eq!(*fun, functions[1]); } @@ -41,7 +41,7 @@ mod tests { fn fails_if_there_is_more_than_one_main_fn() { let functions = ["main", "another", "main"].map(given_a_fun_named); - let err = extract_main_fn(&functions).expect_err("Should have failed."); + let err = extract_main_fn(&functions).expect_err("should have failed"); assert_eq!( err.to_string(), diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs b/packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs index 416868b03..32a4fdd30 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs @@ -51,7 +51,7 @@ fn generate_struct_decl(configurable_struct_name: &Ident) -> TokenStream { #[derive(Clone, Debug, Default)] pub struct #configurable_struct_name { offsets_with_data: ::std::vec::Vec<(u64, ::std::vec::Vec)>, - encoder: ::fuels::core::codec::ABIEncoder, + encoder: ::fuels::core::codec::ConfigurablesEncoder, } } } @@ -66,7 +66,7 @@ fn generate_struct_impl( impl #configurable_struct_name { pub fn new(encoder_config: ::fuels::core::codec::EncoderConfig) -> Self { Self { - encoder: ::fuels::core::codec::ABIEncoder::new(encoder_config), + encoder: ::fuels::core::codec::ConfigurablesEncoder::new(encoder_config), ..::std::default::Default::default() } } diff --git a/packages/fuels-code-gen/src/program_bindings/custom_types/enums.rs b/packages/fuels-code-gen/src/program_bindings/custom_types/enums.rs index 35c120594..efe8bfde2 100644 --- a/packages/fuels-code-gen/src/program_bindings/custom_types/enums.rs +++ b/packages/fuels-code-gen/src/program_bindings/custom_types/enums.rs @@ -25,7 +25,7 @@ pub(crate) fn expand_custom_enum( let components = Components::new(&type_decl.components, false, enum_type_path.parent())?; if components.is_empty() { - return Err(error!("Enum must have at least one component!")); + return Err(error!("enum must have at least one component")); } let generics = extract_generic_parameters(type_decl); diff --git a/packages/fuels-code-gen/src/program_bindings/generated_code.rs b/packages/fuels-code-gen/src/program_bindings/generated_code.rs index d6b7025b1..dea142915 100644 --- a/packages/fuels-code-gen/src/program_bindings/generated_code.rs +++ b/packages/fuels-code-gen/src/program_bindings/generated_code.rs @@ -343,6 +343,6 @@ mod tests { } fn given_type_path(path: &str) -> TypePath { - TypePath::new(path).expect("Hand crafted, should be valid.") + TypePath::new(path).expect("hand crafted, should be valid") } } diff --git a/packages/fuels-code-gen/src/program_bindings/resolved_type.rs b/packages/fuels-code-gen/src/program_bindings/resolved_type.rs index ab6d809bc..32cc49ef8 100644 --- a/packages/fuels-code-gen/src/program_bindings/resolved_type.rs +++ b/packages/fuels-code-gen/src/program_bindings/resolved_type.rs @@ -133,7 +133,7 @@ impl TypeResolver { } let type_field = &type_application.type_decl.type_field; - Err(error!("Could not resolve '{type_field}' to any known type")) + Err(error!("could not resolve '{type_field}' to any known type")) } fn resolve_multiple( @@ -149,7 +149,7 @@ impl TypeResolver { fn is_deprecated(type_field: &str) -> Result<()> { match type_field { "struct std::u256::U256" | "struct U256" => { - Err(error!("{} is deprecated. Use `u256` instead.", type_field)) + Err(error!("{} is deprecated. Use `u256` instead", type_field)) } _ => Ok(()), } @@ -178,7 +178,7 @@ impl TypeResolver { [single_type] => single_type, other => { return Err(error!( - "Array must have only one component! Actual components: {other:?}" + "array must have only one component. Actual components: {other:?}" )); } }; diff --git a/packages/fuels-core/Cargo.toml b/packages/fuels-core/Cargo.toml index 48531b88c..c9b2dfe5c 100644 --- a/packages/fuels-core/Cargo.toml +++ b/packages/fuels-core/Cargo.toml @@ -32,7 +32,7 @@ uint = { workspace = true, default-features = false } [dev-dependencies] fuels-macros = { workspace = true } -tokio = { workspace = true, features = ["test-util"] } +tokio = { workspace = true, features = ["test-util", "macros"] } [features] default = ["std"] diff --git a/packages/fuels-core/src/codec/abi_decoder.rs b/packages/fuels-core/src/codec/abi_decoder.rs index 17570bd00..4d15a5b8a 100644 --- a/packages/fuels-core/src/codec/abi_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder.rs @@ -1,11 +1,14 @@ mod bounded_decoder; +mod decode_as_debug_str; #[cfg(experimental)] mod experimental_bounded_decoder; #[cfg(experimental)] use crate::codec::abi_decoder::experimental_bounded_decoder::ExperimentalBoundedDecoder; use crate::{ - codec::abi_decoder::bounded_decoder::BoundedDecoder, + codec::abi_decoder::{ + bounded_decoder::BoundedDecoder, decode_as_debug_str::decode_as_debug_str, + }, types::{errors::Result, param_types::ParamType, Token}, }; @@ -82,6 +85,32 @@ impl ABIDecoder { BoundedDecoder::new(self.config).decode_multiple(param_types, bytes) } + /// Decodes `bytes` following the schema described in `param_type` into its respective debug + /// string. + /// + /// # Arguments + /// + /// * `param_type`: The `ParamType` of the type we expect is encoded + /// inside `bytes`. + /// * `bytes`: The bytes to be used in the decoding process. + /// # Examples + /// + /// ``` + /// use fuels_core::codec::ABIDecoder; + /// use fuels_core::types::param_types::ParamType; + /// + /// let decoder = ABIDecoder::default(); + /// + /// let debug_string = decoder.decode_as_debug_str(&ParamType::U64, &[0, 0, 0, 0, 0, 0, 0, 7]).unwrap(); + /// let expected_value = 7u64; + /// + /// assert_eq!(debug_string, format!("{expected_value}")); + /// ``` + pub fn decode_as_debug_str(&self, param_type: &ParamType, bytes: &[u8]) -> Result { + let token = BoundedDecoder::new(self.config).decode(param_type, bytes)?; + decode_as_debug_str(param_type, &token) + } + #[cfg(experimental)] pub fn experimental_decode(&self, param_type: &ParamType, bytes: &[u8]) -> Result { ExperimentalBoundedDecoder::new(self.config).decode(param_type, bytes) @@ -106,8 +135,9 @@ mod tests { use super::*; use crate::{ constants::WORD_SIZE, + to_named, traits::Parameterize, - types::{enum_variants::EnumVariants, errors::Error, StaticStringToken, U256}, + types::{errors::Error, param_types::EnumVariants, StaticStringToken, U256}, }; #[test] @@ -117,6 +147,7 @@ mod tests { let decoded = ABIDecoder::default().decode(&ParamType::U32, &data)?; assert_eq!(decoded, Token::U32(u32::MAX)); + Ok(()) } @@ -153,6 +184,7 @@ mod tests { Token::U256(U256::MAX), ]; assert_eq!(decoded, expected); + Ok(()) } @@ -166,6 +198,7 @@ mod tests { let expected = vec![Token::Bool(true), Token::Bool(false)]; assert_eq!(decoded, expected); + Ok(()) } @@ -180,6 +213,7 @@ mod tests { let decoded = ABIDecoder::default().decode(&ParamType::B256, &data)?; assert_eq!(decoded, Token::B256(data)); + Ok(()) } @@ -204,6 +238,7 @@ mod tests { ]; assert_eq!(decoded, expected); + Ok(()) } @@ -224,6 +259,7 @@ mod tests { ))]; assert_eq!(decoded, expected); + Ok(()) } @@ -237,6 +273,7 @@ mod tests { let expected = vec![Token::Array(vec![Token::U8(255), Token::U8(42)])]; assert_eq!(decoded, expected); + Ok(()) } @@ -251,7 +288,8 @@ mod tests { 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; let param_type = ParamType::Struct { - fields: vec![ParamType::U8, ParamType::Bool], + name: "".to_string(), + fields: to_named(&[ParamType::U8, ParamType::Bool]), generics: vec![], }; @@ -260,6 +298,7 @@ mod tests { let expected = Token::Struct(vec![Token::U8(1), Token::Bool(true)]); assert_eq!(decoded, expected); + Ok(()) } @@ -271,6 +310,7 @@ mod tests { let expected = Token::Bytes(data.to_vec()); assert_eq!(decoded, expected); + Ok(()) } @@ -281,10 +321,11 @@ mod tests { // y: bool, // } - let types = vec![ParamType::U32, ParamType::Bool]; + let types = to_named(&[ParamType::U32, ParamType::Bool]); let inner_enum_types = EnumVariants::new(types)?; let types = vec![ParamType::Enum { - variants: inner_enum_types.clone(), + name: "".to_string(), + enum_variants: inner_enum_types.clone(), generics: vec![], }]; @@ -297,6 +338,7 @@ mod tests { let expected = vec![Token::Enum(Box::new((0, Token::U32(42), inner_enum_types)))]; assert_eq!(decoded, expected); + Ok(()) } @@ -312,17 +354,19 @@ mod tests { // y: u32, // } - let types = vec![ParamType::B256, ParamType::U32]; + let types = to_named(&[ParamType::B256, ParamType::U32]); let inner_enum_types = EnumVariants::new(types)?; - let fields = vec![ + let fields = to_named(&[ ParamType::Enum { - variants: inner_enum_types.clone(), + name: "".to_string(), + enum_variants: inner_enum_types.clone(), generics: vec![], }, ParamType::U32, - ]; + ]); let struct_type = ParamType::Struct { + name: "".to_string(), fields, generics: vec![], }; @@ -349,6 +393,7 @@ mod tests { Token::U32(54321), ]); assert_eq!(decoded, expected); + Ok(()) } @@ -364,17 +409,19 @@ mod tests { // b: u8[2], // } - let fields = vec![ + let fields = to_named(&[ ParamType::U16, ParamType::Struct { - fields: vec![ + name: "".to_string(), + fields: to_named(&[ ParamType::Bool, ParamType::Array(Box::new(ParamType::U8), 2), - ], + ]), generics: vec![], }, - ]; + ]); let nested_struct = ParamType::Struct { + name: "".to_string(), fields, generics: vec![], }; @@ -395,6 +442,7 @@ mod tests { ]; assert_eq!(decoded, Token::Struct(my_nested_struct)); + Ok(()) } @@ -413,17 +461,19 @@ mod tests { // fn: long_function(Foo,u8[2],b256,str[3],str) // Parameters - let fields = vec![ + let fields = to_named(&[ ParamType::U16, ParamType::Struct { - fields: vec![ + name: "".to_string(), + fields: to_named(&[ ParamType::Bool, ParamType::Array(Box::new(ParamType::U8), 2), - ], + ]), generics: vec![], }, - ]; + ]); let nested_struct = ParamType::Struct { + name: "".to_string(), fields, generics: vec![], }; @@ -471,6 +521,7 @@ mod tests { let expected: Vec = vec![foo, u8_arr, b256]; assert_eq!(decoded, expected); + Ok(()) } @@ -480,7 +531,8 @@ mod tests { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, ]; let struct_type = ParamType::Struct { - fields: vec![ParamType::Unit, ParamType::U64], + name: "".to_string(), + fields: to_named(&[ParamType::Unit, ParamType::U64]), generics: vec![], }; @@ -488,42 +540,47 @@ mod tests { let expected = Token::Struct(vec![Token::Unit, Token::U64(u64::MAX)]); assert_eq!(actual, expected); + Ok(()) } #[test] fn enums_with_all_unit_variants_are_decoded_from_one_word() -> Result<()> { let data = [0, 0, 0, 0, 0, 0, 0, 1]; - let types = vec![ParamType::Unit, ParamType::Unit]; - let variants = EnumVariants::new(types)?; + let types = to_named(&[ParamType::Unit, ParamType::Unit]); + let enum_variants = EnumVariants::new(types)?; let enum_w_only_units = ParamType::Enum { - variants: variants.clone(), + name: "".to_string(), + enum_variants: enum_variants.clone(), generics: vec![], }; let result = ABIDecoder::default().decode(&enum_w_only_units, &data)?; - let expected_enum = Token::Enum(Box::new((1, Token::Unit, variants))); + let expected_enum = Token::Enum(Box::new((1, Token::Unit, enum_variants))); assert_eq!(result, expected_enum); + Ok(()) } #[test] fn out_of_bounds_discriminant_is_detected() -> Result<()> { let data = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2]; - let types = vec![ParamType::U32]; - let variants = EnumVariants::new(types)?; + let types = to_named(&[ParamType::U32]); + let enum_variants = EnumVariants::new(types)?; let enum_type = ParamType::Enum { - variants, + name: "".to_string(), + enum_variants, generics: vec![], }; let result = ABIDecoder::default().decode(&enum_type, &data); - let error = result.expect_err("Should have resulted in an error"); + let error = result.expect_err("should have resulted in an error"); + + let expected_msg = "discriminant `1` doesn't point to any variant: "; + assert!(matches!(error, Error::Other(str) if str.starts_with(expected_msg))); - let expected_msg = "Discriminant '1' doesn't point to any variant: "; - assert!(matches!(error, Error::InvalidData(str) if str.starts_with(expected_msg))); Ok(()) } @@ -531,14 +588,15 @@ mod tests { pub fn division_by_zero() { let param_type = Vec::<[u16; 0]>::param_type(); let result = ABIDecoder::default().decode(¶m_type, &[]); - assert!(matches!(result, Err(Error::InvalidType(_)))); + assert!(matches!(result, Err(Error::Codec(_)))); } #[test] pub fn multiply_overflow_enum() { let result = ABIDecoder::default().decode( &Enum { - variants: EnumVariants::new(vec![ + name: "".to_string(), + enum_variants: EnumVariants::new(to_named(&[ Array(Box::new(Array(Box::new(RawSlice), 8)), usize::MAX), B256, B256, @@ -550,13 +608,14 @@ mod tests { B256, B256, B256, - ]) + ])) .unwrap(), generics: vec![U16], }, &[], ); - assert!(matches!(result, Err(Error::InvalidType(_)))); + + assert!(matches!(result, Err(Error::Codec(_)))); } #[test] @@ -567,12 +626,13 @@ mod tests { } let result = ABIDecoder::default().decode( &Enum { - variants: EnumVariants::new(vec![param_type]).unwrap(), + name: "".to_string(), + enum_variants: EnumVariants::new(to_named(&[param_type])).unwrap(), generics: vec![U16], }, &[], ); - assert!(matches!(result, Err(Error::InvalidType(_)))); + assert!(matches!(result, Err(Error::Codec(_)))); } #[test] @@ -581,7 +641,7 @@ mod tests { &Array(Box::new(Array(Box::new(Tuple(vec![])), usize::MAX)), 1), &[], ); - assert!(matches!(result, Err(Error::InvalidType(_)))); + assert!(matches!(result, Err(Error::Codec(_)))); } #[test] @@ -591,14 +651,14 @@ mod tests { param_type = Vector(Box::new(param_type)); } let result = ABIDecoder::default().decode(¶m_type, &[]); - assert!(matches!(result, Err(Error::InvalidType(_)))); + assert!(matches!(result, Err(Error::Codec(_)))); } #[test] pub fn capacity_maloc() { let param_type = Array(Box::new(U8), usize::MAX); let result = ABIDecoder::default().decode(¶m_type, &[]); - assert!(matches!(result, Err(Error::InvalidData(_)))); + assert!(matches!(result, Err(Error::Codec(_)))); } #[test] @@ -613,27 +673,28 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - let variants = EnumVariants::new(param_types.clone())?; + let enum_variants = EnumVariants::new(to_named(¶m_types))?; let enum_param_type = ParamType::Enum { - variants, + name: "".to_string(), + enum_variants, generics: vec![], }; // it works if there is only one heap type let _ = ABIDecoder::default().decode(&enum_param_type, &data)?; param_types.append(&mut vec![ParamType::Bytes]); - let variants = EnumVariants::new(param_types)?; + let enum_variants = EnumVariants::new(to_named(¶m_types))?; let enum_param_type = ParamType::Enum { - variants, + name: "".to_string(), + enum_variants, generics: vec![], }; // fails if there is more than one variant using heap type in the enum let error = ABIDecoder::default() .decode(&enum_param_type, &data) - .expect_err("Should fail"); + .expect_err("should fail"); let expected_error = - "Invalid type: Enums currently support only one heap-type variant. Found: 2" - .to_string(); + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); assert_eq!(error.to_string(), expected_error); Ok(()) @@ -641,16 +702,18 @@ mod tests { #[test] fn enums_w_too_deeply_nested_heap_types_not_allowed() { - let param_types = vec![ + let variants = to_named(&[ ParamType::U8, ParamType::Struct { - fields: vec![ParamType::RawSlice], + name: "".to_string(), + fields: to_named(&[ParamType::RawSlice]), generics: vec![], }, - ]; - let variants = EnumVariants::new(param_types).unwrap(); + ]); + let enum_variants = EnumVariants::new(variants).unwrap(); let enum_param_type = ParamType::Enum { - variants, + name: "".to_string(), + enum_variants, generics: vec![], }; @@ -658,13 +721,13 @@ mod tests { .decode(&enum_param_type, &[]) .expect_err("should have failed"); - let Error::InvalidType(msg) = err else { - panic!("Unexpected err: {err}"); + let Error::Codec(msg) = err else { + panic!("unexpected err: {err}"); }; assert_eq!( msg, - "Enums currently support only one level deep heap types." + "enums currently support only one level deep heap types" ); } @@ -675,7 +738,7 @@ mod tests { max_depth: MAX_DEPTH, ..Default::default() }; - let msg = format!("Depth limit ({MAX_DEPTH}) reached while decoding. Try increasing it."); + let msg = format!("depth limit `{MAX_DEPTH}` reached while decoding. Try increasing it"); // for each nested enum so that it may read the discriminant let data = [0; MAX_DEPTH * WORD_SIZE]; @@ -706,7 +769,8 @@ mod tests { // Wrapping everything in a structure so that we may check whether the depth is // decremented after finishing every struct field. ParamType::Struct { - fields: vec![param_type.clone(), param_type], + name: "".to_string(), + fields: to_named(&[param_type.clone(), param_type]), generics: vec![], } }) @@ -723,20 +787,21 @@ mod tests { }; let data = [0; 3 * WORD_SIZE]; - let el = ParamType::U8; + let inner_param_types = vec![ParamType::U8; 3]; for param_type in [ ParamType::Struct { - fields: vec![el.clone(); 3], + name: "".to_string(), + fields: to_named(&inner_param_types), generics: vec![], }, - ParamType::Tuple(vec![el.clone(); 3]), - ParamType::Array(Box::new(el.clone()), 3), - ParamType::Vector(Box::new(el)), + ParamType::Tuple(inner_param_types.clone()), + ParamType::Array(Box::new(ParamType::U8), 3), + ParamType::Vector(Box::new(ParamType::U8)), ] { assert_decoding_failed_w_data( config, ¶m_type, - "Token limit (3) reached while decoding. Try increasing it.", + "token limit `3` reached while decoding. Try increasing it", &data, ); } @@ -748,14 +813,15 @@ mod tests { let err = ABIDecoder::default() .decode(¶m_type, &[]) - .expect_err("Vectors of ZST should be prohibited"); + .expect_err("vectors of ZST should be prohibited"); - let Error::InvalidType(msg) = err else { - panic!("Expected error of type InvalidType") + let Error::Codec(msg) = err else { + panic!("expected error of type Codec") }; + assert_eq!( msg, - "Cannot calculate the number of elements because the type is zero-sized." + "cannot calculate the number of elements because the type is zero-sized" ); } @@ -776,7 +842,7 @@ mod tests { let result = decoder.decode(¶m_type, &[]); // then - result.expect("Element count to be reset"); + result.expect("element count to be reset"); } fn assert_decoding_failed_w_data( @@ -789,9 +855,10 @@ mod tests { let err = decoder.decode(param_type, data); - let Err(Error::InvalidType(actual_msg)) = err else { - panic!("Unexpected an InvalidType error! Got: {err:?}"); + let Err(Error::Codec(actual_msg)) = err else { + panic!("expected a `Codec` error. Got: `{err:?}`"); }; + assert_eq!(actual_msg, msg); } @@ -799,10 +866,11 @@ mod tests { let fields = if depth == 1 { vec![] } else { - vec![nested_struct(depth - 1)] + to_named(&[nested_struct(depth - 1)]) }; ParamType::Struct { + name: "".to_string(), fields, generics: vec![], } @@ -810,13 +878,14 @@ mod tests { fn nested_enum(depth: usize) -> ParamType { let fields = if depth == 1 { - vec![ParamType::U8] + to_named(&[ParamType::U8]) } else { - vec![nested_enum(depth - 1)] + to_named(&[nested_enum(depth - 1)]) }; ParamType::Enum { - variants: EnumVariants::new(fields).unwrap(), + name: "".to_string(), + enum_variants: EnumVariants::new(fields).unwrap(), generics: vec![], } } diff --git a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs index bdae9b0a8..4bc169d50 100644 --- a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs @@ -8,9 +8,8 @@ use crate::{ }, constants::WORD_SIZE, types::{ - enum_variants::EnumVariants, errors::{error, Result}, - param_types::ParamType, + param_types::{EnumVariants, NamedParamType, ParamType}, StaticStringToken, Token, U256, }, }; @@ -30,9 +29,9 @@ const B256_BYTES_SIZE: usize = 4 * WORD_SIZE; impl BoundedDecoder { pub(crate) fn new(config: DecoderConfig) -> Self { let depth_tracker = - CounterWithLimit::new(config.max_depth, "Depth", CodecDirection::Decoding); + CounterWithLimit::new(config.max_depth, "depth", CodecDirection::Decoding); let token_tracker = - CounterWithLimit::new(config.max_tokens, "Token", CodecDirection::Decoding); + CounterWithLimit::new(config.max_tokens, "token", CodecDirection::Decoding); Self { depth_tracker, token_tracker, @@ -107,8 +106,8 @@ impl BoundedDecoder { ParamType::Struct { fields, .. } => { self.run_w_depth_tracking(|ctx| ctx.decode_struct(fields, bytes)) } - ParamType::Enum { variants, .. } => { - self.run_w_depth_tracking(|ctx| ctx.decode_enum(bytes, variants)) + ParamType::Enum { enum_variants, .. } => { + self.run_w_depth_tracking(|ctx| ctx.decode_enum(bytes, enum_variants)) } ParamType::Tuple(types) => { self.run_w_depth_tracking(|ctx| ctx.decode_tuple(types, bytes)) @@ -167,12 +166,12 @@ impl BoundedDecoder { }) } - fn decode_struct(&mut self, param_types: &[ParamType], bytes: &[u8]) -> Result { + fn decode_struct(&mut self, param_types: &[NamedParamType], bytes: &[u8]) -> Result { let mut tokens = vec![]; let mut bytes_read = 0; - for param_type in param_types.iter() { + for (_, param_type) in param_types.iter() { // padding has to be taken into account bytes_read = checked_round_up_to_word_alignment(bytes_read)?; let res = self.decode_param(param_type, skip(bytes, bytes_read)?)?; @@ -324,13 +323,13 @@ impl BoundedDecoder { /// /// * `data`: slice of encoded data on whose beginning we're expecting an encoded enum /// * `variants`: all types that this particular enum type could hold - fn decode_enum(&mut self, bytes: &[u8], variants: &EnumVariants) -> Result { - let enum_width_in_bytes = variants.compute_enum_width_in_bytes()?; + fn decode_enum(&mut self, bytes: &[u8], enum_variants: &EnumVariants) -> Result { + let enum_width_in_bytes = enum_variants.compute_enum_width_in_bytes()?; let discriminant = peek_u64(bytes)?; - let selected_variant = variants.param_type_of_variant(discriminant)?; + let (_, selected_variant) = enum_variants.select_variant(discriminant)?; - let skip_extra_in_bytes = match variants.heap_type_variant() { + let skip_extra_in_bytes = match enum_variants.heap_type_variant() { Some((heap_type_discriminant, heap_type)) if heap_type_discriminant == discriminant => { heap_type.compute_encoding_in_bytes()? } @@ -341,9 +340,10 @@ impl BoundedDecoder { + skip_extra_in_bytes; let enum_content_bytes = skip(bytes, bytes_to_skip)?; - let result = self.decode_token_in_enum(enum_content_bytes, variants, selected_variant)?; + let result = + self.decode_token_in_enum(enum_content_bytes, enum_variants, selected_variant)?; - let selector = Box::new((discriminant, result.token, variants.clone())); + let selector = Box::new((discriminant, result.token, enum_variants.clone())); Ok(Decoded { token: Token::Enum(selector), bytes_read: enum_width_in_bytes, @@ -396,7 +396,7 @@ fn peek_u32(bytes: &[u8]) -> Result { let slice = peek_fixed::(bytes)?; let bytes = slice[WORD_SIZE - BYTES..] .try_into() - .expect("peek_u32: You must use a slice containing exactly 4B."); + .expect("peek_u32: You must use a slice containing exactly 4B"); Ok(u32::from_be_bytes(bytes)) } @@ -406,7 +406,7 @@ fn peek_u16(bytes: &[u8]) -> Result { let slice = peek_fixed::(bytes)?; let bytes = slice[WORD_SIZE - BYTES..] .try_into() - .expect("peek_u16: You must use a slice containing exactly 2B."); + .expect("peek_u16: You must use a slice containing exactly 2B"); Ok(u16::from_be_bytes(bytes)) } @@ -416,7 +416,7 @@ fn peek_u8(bytes: &[u8]) -> Result { let slice = peek_fixed::<1>(bytes)?; let bytes = slice[1 - BYTES..] .try_into() - .expect("peek_u8: You must use a slice containing exactly 1B."); + .expect("peek_u8: You must use a slice containing exactly 1B"); Ok(u8::from_be_bytes(bytes)) } @@ -429,7 +429,7 @@ fn peek_fixed(data: &[u8]) -> Result<&[u8; LEN]> { fn peek(data: &[u8], len: usize) -> Result<&[u8]> { if len > data.len() { Err(error!( - InvalidData, + Codec, "tried to read {len} bytes from response but only had {} remaining!", data.len() )) @@ -441,7 +441,7 @@ fn peek(data: &[u8], len: usize) -> Result<&[u8]> { fn skip(slice: &[u8], num_bytes: usize) -> Result<&[u8]> { if num_bytes > slice.len() { Err(error!( - InvalidData, + Codec, "tried to consume {num_bytes} bytes from response but only had {} remaining!", slice.len() )) diff --git a/packages/fuels-core/src/codec/abi_decoder/decode_as_debug_str.rs b/packages/fuels-core/src/codec/abi_decoder/decode_as_debug_str.rs new file mode 100644 index 000000000..15c4d9e9c --- /dev/null +++ b/packages/fuels-core/src/codec/abi_decoder/decode_as_debug_str.rs @@ -0,0 +1,262 @@ +use std::iter::zip; + +use crate::types::{ + errors::{error, Result}, + param_types::ParamType, + Token, +}; + +fn inner_types_debug(tokens: &[Token], inner_type: &ParamType, join_str: &str) -> Result { + let inner_types_log = tokens + .iter() + .map(|token| decode_as_debug_str(inner_type, token)) + .collect::>>()? + .join(join_str); + + Ok(inner_types_log) +} + +pub(crate) fn decode_as_debug_str(param_type: &ParamType, token: &Token) -> Result { + let result = match (param_type, token) { + (ParamType::Unit, Token::Unit) => "()".to_string(), + (ParamType::Bool, Token::Bool(val)) => val.to_string(), + (ParamType::U8, Token::U8(val)) => val.to_string(), + (ParamType::U16, Token::U16(val)) => val.to_string(), + (ParamType::U32, Token::U32(val)) => val.to_string(), + (ParamType::U64, Token::U64(val)) => val.to_string(), + (ParamType::U128, Token::U128(val)) => val.to_string(), + (ParamType::U256, Token::U256(val)) => val.to_string(), + (ParamType::B256, Token::B256(val)) => { + format!("Bits256({val:?})") + } + (ParamType::Bytes, Token::Bytes(val)) => { + format!("Bytes({val:?})") + } + (ParamType::String, Token::String(val)) => val.clone(), + (ParamType::RawSlice, Token::RawSlice(val)) => { + format!("RawSlice({val:?})") + } + (ParamType::StringArray(..), Token::StringArray(str_token)) => { + format!("SizedAsciiString {{ data: \"{}\" }}", str_token.data) + } + (ParamType::StringSlice, Token::StringSlice(str_token)) => { + format!("AsciiString {{ data: \"{}\" }}", str_token.data) + } + (ParamType::Tuple(types), Token::Tuple(tokens)) => { + let elements = zip(types, tokens) + .map(|(ptype, token)| decode_as_debug_str(ptype, token)) + .collect::>>()? + .join(", "); + + format!("({elements})") + } + (ParamType::Array(inner_type, _), Token::Array(tokens)) => { + let elements = inner_types_debug(tokens, inner_type, ", ")?; + format!("[{elements}]") + } + (ParamType::Vector(inner_type), Token::Vector(tokens)) => { + let elements = inner_types_debug(tokens, inner_type, ", ")?; + format!("[{elements}]") + } + (ParamType::Struct { name, fields, .. }, Token::Struct(field_tokens)) => { + let fields = zip(fields, field_tokens) + .map(|((field_name, param_type), token)| -> Result<_> { + Ok(format!( + "{field_name}: {}", + decode_as_debug_str(param_type, token)? + )) + }) + .collect::>>()? + .join(", "); + format!("{name} {{ {fields} }}") + } + (ParamType::Enum { .. }, Token::Enum(selector)) => { + let (discriminant, token, variants) = selector.as_ref(); + + let (variant_name, variant_param_type) = variants.select_variant(*discriminant)?; + let variant_str = decode_as_debug_str(variant_param_type, token)?; + let variant_str = if variant_str == "()" { + "".into() + } else { + format!("({variant_str})") + }; + + format!("{variant_name}{variant_str}") + } + _ => { + return Err(error!( + Codec, + "could not decode debug from param type: `{param_type:?}` and token: `{token:?}`" + )) + } + }; + Ok(result) +} + +#[cfg(test)] +mod tests { + use crate::{ + codec::ABIDecoder, + traits::Parameterize, + types::{ + errors::Result, AsciiString, Bits256, Bytes, EvmAddress, RawSlice, SizedAsciiString, + U256, + }, + }; + + #[test] + fn param_type_decode_debug() -> Result<()> { + let decoder = ABIDecoder::default(); + { + assert_eq!( + format!("{:?}", true), + decoder.decode_as_debug_str(&bool::param_type(), &[0, 0, 0, 0, 0, 0, 0, 1])? + ); + + assert_eq!( + format!("{:?}", 128u8), + decoder.decode_as_debug_str(&u8::param_type(), &[0, 0, 0, 0, 0, 0, 0, 128])? + ); + + assert_eq!( + format!("{:?}", 256u16), + decoder.decode_as_debug_str(&u16::param_type(), &[0, 0, 0, 0, 0, 0, 1, 0])? + ); + + assert_eq!( + format!("{:?}", 512u32), + decoder.decode_as_debug_str(&u32::param_type(), &[0, 0, 0, 0, 0, 0, 2, 0])? + ); + + assert_eq!( + format!("{:?}", 1024u64), + decoder.decode_as_debug_str(&u64::param_type(), &[0, 0, 0, 0, 0, 0, 4, 0])? + ); + + assert_eq!( + format!("{:?}", 1024u128), + decoder.decode_as_debug_str( + &u128::param_type(), + &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0] + )? + ); + + assert_eq!( + format!("{:?}", U256::from(2048)), + decoder.decode_as_debug_str( + &U256::param_type(), + &[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 8, 0 + ] + )? + ); + } + { + let bytes = [ + 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, + 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74, + ]; + let bits256 = Bits256(bytes); + + assert_eq!( + format!("{bits256:?}"), + decoder.decode_as_debug_str( + &Bits256::param_type(), + &[ + 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, + 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 + ] + )? + ); + + assert_eq!( + format!("{:?}", Bytes(bytes.to_vec())), + decoder.decode_as_debug_str( + &Bytes::param_type(), + &[ + 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, + 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 + ] + )? + ); + + assert_eq!( + format!("{:?}", RawSlice(bytes.to_vec())), + decoder.decode_as_debug_str( + &RawSlice::param_type(), + &[ + 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, + 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 + ] + )? + ); + + assert_eq!( + format!("{:?}", EvmAddress::from(bits256)), + decoder.decode_as_debug_str( + &EvmAddress::param_type(), + &[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 166, 225, 89, 161, 16, 60, 239, 183, + 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 + ] + )? + ); + } + { + assert_eq!( + format!("{:?}", AsciiString::new("Fuel".to_string())?), + decoder.decode_as_debug_str(&AsciiString::param_type(), &[70, 117, 101, 108])? + ); + + assert_eq!( + format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?), + decoder.decode_as_debug_str( + &SizedAsciiString::<4>::param_type(), + &[70, 117, 101, 108, 0, 0, 0, 0] + )? + ); + + assert_eq!( + format!("{}", "Fuel"), + decoder.decode_as_debug_str(&String::param_type(), &[70, 117, 101, 108])? + ); + } + { + assert_eq!( + format!("{:?}", (1, 2)), + decoder.decode_as_debug_str( + &<(u8, u8)>::param_type(), + &[1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0] + )? + ); + + assert_eq!( + format!("{:?}", [3, 4]), + decoder.decode_as_debug_str( + &<[u64; 2]>::param_type(), + &[0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4] + )? + ); + } + { + assert_eq!( + format!("{:?}", Some(42)), + decoder.decode_as_debug_str( + &>::param_type(), + &[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 42] + )? + ); + + assert_eq!( + format!("{:?}", Err::(42u64)), + decoder.decode_as_debug_str( + &>::param_type(), + &[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 42] + )? + ); + } + + Ok(()) + } +} diff --git a/packages/fuels-core/src/codec/abi_decoder/experimental_bounded_decoder.rs b/packages/fuels-core/src/codec/abi_decoder/experimental_bounded_decoder.rs index e8728bf08..4660c863a 100644 --- a/packages/fuels-core/src/codec/abi_decoder/experimental_bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/experimental_bounded_decoder.rs @@ -4,9 +4,8 @@ use crate::{ codec::DecoderConfig, constants::WORD_SIZE, types::{ - enum_variants::EnumVariants, errors::{error, Result}, - param_types::ParamType, + param_types::{EnumVariants, NamedParamType, ParamType}, StaticStringToken, Token, U256, }, }; @@ -30,8 +29,8 @@ const DISCRIMINANT_BYTES_SIZE: usize = WORD_SIZE; impl ExperimentalBoundedDecoder { pub(crate) fn new(config: DecoderConfig) -> Self { - let depth_tracker = CounterWithLimit::new(config.max_depth, "Depth"); - let token_tracker = CounterWithLimit::new(config.max_tokens, "Token"); + let depth_tracker = CounterWithLimit::new(config.max_depth, "depth"); + let token_tracker = CounterWithLimit::new(config.max_tokens, "token"); Self { depth_tracker, token_tracker, @@ -93,8 +92,8 @@ impl ExperimentalBoundedDecoder { ParamType::Struct { fields, .. } => { self.run_w_depth_tracking(|ctx| ctx.decode_struct(fields, bytes)) } - ParamType::Enum { variants, .. } => { - self.run_w_depth_tracking(|ctx| ctx.decode_enum(bytes, variants)) + ParamType::Enum { enum_variants, .. } => { + self.run_w_depth_tracking(|ctx| ctx.decode_enum(bytes, enum_variants)) } } } @@ -249,8 +248,8 @@ impl ExperimentalBoundedDecoder { }) } - fn decode_struct(&mut self, param_types: &[ParamType], bytes: &[u8]) -> Result { - let (tokens, bytes_read) = self.decode_params(param_types, bytes)?; + fn decode_struct(&mut self, fields: &[NamedParamType], bytes: &[u8]) -> Result { + let (tokens, bytes_read) = self.decode_params(fields.iter().map(|(_, pt)| pt), bytes)?; Ok(Decoded { token: Token::Struct(tokens), @@ -258,15 +257,19 @@ impl ExperimentalBoundedDecoder { }) } - fn decode_enum(&mut self, bytes: &[u8], variants: &EnumVariants) -> Result { + fn decode_enum(&mut self, bytes: &[u8], enum_variants: &EnumVariants) -> Result { let discriminant = peek_discriminant(bytes)?; let variant_bytes = skip(bytes, DISCRIMINANT_BYTES_SIZE)?; - let selected_variant = variants.param_type_of_variant(discriminant)?; + let (_, selected_variant) = enum_variants.select_variant(discriminant)?; let decoded = self.decode_param(selected_variant, variant_bytes)?; Ok(Decoded { - token: Token::Enum(Box::new((discriminant, decoded.token, variants.clone()))), + token: Token::Enum(Box::new(( + discriminant, + decoded.token, + enum_variants.clone(), + ))), bytes_read: DISCRIMINANT_BYTES_SIZE + decoded.bytes_read, }) } @@ -314,8 +317,8 @@ impl CounterWithLimit { self.count += 1; if self.count > self.max { return Err(error!( - InvalidType, - "{} limit ({}) reached while decoding. Try increasing it.", self.name, self.max + Codec, + "{} limit `{}` reached while decoding. Try increasing it", self.name, self.max )); } @@ -364,7 +367,7 @@ fn peek_length(bytes: &[u8]) -> Result { u64::from_be_bytes(*slice) .try_into() - .map_err(|_| error!(InvalidData, "could not convert `u64` to `usize`")) + .map_err(|_| error!(Other, "could not convert `u64` to `usize`")) } fn peek_discriminant(bytes: &[u8]) -> Result { @@ -374,7 +377,7 @@ fn peek_discriminant(bytes: &[u8]) -> Result { fn peek(data: &[u8], len: usize) -> Result<&[u8]> { (len <= data.len()).then_some(&data[..len]).ok_or(error!( - InvalidData, + Codec, "tried to read `{len}` bytes but only had `{}` remaining!", data.len() )) @@ -391,7 +394,7 @@ fn skip(slice: &[u8], num_bytes: usize) -> Result<&[u8]> { (num_bytes <= slice.len()) .then_some(&slice[num_bytes..]) .ok_or(error!( - InvalidData, + Codec, "tried to consume `{num_bytes}` bytes but only had `{}` remaining!", slice.len() )) diff --git a/packages/fuels-core/src/codec/abi_encoder.rs b/packages/fuels-core/src/codec/abi_encoder.rs index c685761ea..a2b1df39e 100644 --- a/packages/fuels-core/src/codec/abi_encoder.rs +++ b/packages/fuels-core/src/codec/abi_encoder.rs @@ -44,22 +44,44 @@ impl ABIEncoder { /// Encodes `Token`s in `args` following the ABI specs defined /// [here](https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/abi.md) pub fn encode(&self, args: &[Token]) -> Result { - BoundedEncoder::new(self.config).encode(args) + BoundedEncoder::new(self.config, false).encode(args) + } +} + +#[derive(Default, Clone, Debug)] +pub struct ConfigurablesEncoder { + pub config: EncoderConfig, +} + +impl ConfigurablesEncoder { + pub fn new(config: EncoderConfig) -> Self { + Self { config } + } + + /// Encodes `Token`s in `args` following the ABI specs defined + /// [here](https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/abi.md) + pub fn encode(&self, args: &[Token]) -> Result { + BoundedEncoder::new(self.config, true).encode(args) } } #[cfg(test)] mod tests { + use std::slice; + use itertools::chain; use sha2::{Digest, Sha256}; - use std::slice; use super::*; - use crate::types::errors::Error; use crate::{ codec::first_four_bytes_of_sha256_hash, constants::WORD_SIZE, - types::{enum_variants::EnumVariants, param_types::ParamType, StaticStringToken, U256}, + to_named, + types::{ + errors::Error, + param_types::{EnumVariants, ParamType}, + StaticStringToken, U256, + }, }; const VEC_METADATA_SIZE: usize = 3 * WORD_SIZE; @@ -491,7 +513,7 @@ mod tests { // x: u32, // y: bool, // } - let types = vec![ParamType::U32, ParamType::Bool]; + let types = to_named(&[ParamType::U32, ParamType::Bool]); let params = EnumVariants::new(types)?; // An `EnumSelector` indicating that we've chosen the first Enum variant, @@ -525,7 +547,7 @@ mod tests { // Our enum has two variants: B256, and U64. So the enum will set aside // 256b of space or 4 WORDS because that is the space needed to fit the // largest variant(B256). - let types = vec![ParamType::B256, ParamType::U64]; + let types = to_named(&[ParamType::B256, ParamType::U64]); let enum_variants = EnumVariants::new(types)?; let enum_selector = Box::new((1, Token::U64(42), enum_variants)); @@ -556,7 +578,7 @@ mod tests { v2: str[10] } */ - let types = vec![ParamType::Bool, ParamType::StringArray(10)]; + let types = to_named(&[ParamType::Bool, ParamType::StringArray(10)]); let deeper_enum_variants = EnumVariants::new(types)?; let deeper_enum_token = Token::StringArray(StaticStringToken::new("0123456789".into(), Some(10))); @@ -568,14 +590,16 @@ mod tests { } */ - let fields = vec![ + let fields = to_named(&[ ParamType::Enum { - variants: deeper_enum_variants.clone(), + name: "".to_string(), + enum_variants: deeper_enum_variants.clone(), generics: vec![], }, ParamType::Bool, - ]; + ]); let struct_a_type = ParamType::Struct { + name: "".to_string(), fields, generics: vec![], }; @@ -593,7 +617,7 @@ mod tests { } */ - let types = vec![struct_a_type, ParamType::Bool, ParamType::U64]; + let types = to_named(&[struct_a_type, ParamType::Bool, ParamType::U64]); let top_level_enum_variants = EnumVariants::new(types)?; let top_level_enum_token = Token::Enum(Box::new((0, struct_a_token, top_level_enum_variants))); @@ -770,7 +794,7 @@ mod tests { fn enums_with_only_unit_variants_are_encoded_in_one_word() -> Result<()> { let expected = [0, 0, 0, 0, 0, 0, 0, 1]; - let types = vec![ParamType::Unit, ParamType::Unit]; + let types = to_named(&[ParamType::Unit, ParamType::Unit]); let enum_selector = Box::new((1, Token::Unit, EnumVariants::new(types)?)); let actual = ABIEncoder::default() @@ -799,7 +823,7 @@ mod tests { let padding = vec![0; 32]; let expected: Vec = [discriminant, padding].into_iter().flatten().collect(); - let types = vec![ParamType::B256, ParamType::Unit]; + let types = to_named(&[ParamType::B256, ParamType::Unit]); let enum_selector = Box::new((1, Token::Unit, EnumVariants::new(types)?)); let actual = ABIEncoder::default() @@ -873,7 +897,7 @@ mod tests { fn a_vec_in_an_enum() -> Result<()> { // arrange let offset = 40; - let types = vec![ParamType::B256, ParamType::Vector(Box::new(ParamType::U64))]; + let types = to_named(&[ParamType::B256, ParamType::Vector(Box::new(ParamType::U64))]); let variants = EnumVariants::new(types)?; let selector = (1, Token::Vector(vec![Token::U64(5)]), variants); let token = Token::Enum(Box::new(selector)); @@ -914,7 +938,7 @@ mod tests { fn an_enum_in_a_vec() -> Result<()> { // arrange let offset = 40; - let types = vec![ParamType::B256, ParamType::U8]; + let types = to_named(&[ParamType::B256, ParamType::U8]); let variants = EnumVariants::new(types)?; let selector = (1, Token::U8(8), variants); let enum_token = Token::Enum(Box::new(selector)); @@ -1087,15 +1111,17 @@ mod tests { let token = Token::Enum(Box::new(( 1, Token::String("".to_string()), - EnumVariants::new(vec![ + EnumVariants::new(to_named(&[ ParamType::StringArray(18446742977385549567), ParamType::U8, - ])?, + ]))?, ))); let capacity_overflow_error = ABIEncoder::default().encode(&[token]).unwrap_err(); + assert!(capacity_overflow_error .to_string() - .contains("Try increasing encoder max memory")); + .contains("Try increasing maximum total enum width")); + Ok(()) } @@ -1106,23 +1132,47 @@ mod tests { max_depth: MAX_DEPTH, ..Default::default() }; - let msg = "Depth limit (2) reached while encoding. Try increasing it.".to_string(); + let msg = "depth limit `2` reached while encoding. Try increasing it".to_string(); [nested_struct, nested_enum, nested_tuple, nested_array] .iter() .map(|fun| fun(MAX_DEPTH + 1)) .for_each(|token| { - assert_decoding_failed(config, token, &msg); - }) + assert_encoding_failed(config, token, &msg); + }); + } + + #[test] + fn encoder_for_configurables_optimizes_top_level_u8() { + // given + let encoder = ConfigurablesEncoder::default(); + + // when + let encoded = encoder.encode(&[Token::U8(255)]).unwrap().resolve(0); + + // then + assert_eq!(encoded, vec![255]); + } + + #[test] + fn encoder_for_configurables_optimizes_top_level_bool() { + // given + let encoder = ConfigurablesEncoder::default(); + + // when + let encoded = encoder.encode(&[Token::Bool(true)]).unwrap().resolve(0); + + // then + assert_eq!(encoded, vec![1]); } - fn assert_decoding_failed(config: EncoderConfig, token: Token, msg: &str) { + fn assert_encoding_failed(config: EncoderConfig, token: Token, msg: &str) { let encoder = ABIEncoder::new(config); let err = encoder.encode(&[token]); - let Err(Error::InvalidType(actual_msg)) = err else { - panic!("Unexpected an InvalidType error! Got: {err:?}"); + let Err(Error::Codec(actual_msg)) = err else { + panic!("expected a Codec error. Got: `{err:?}`"); }; assert_eq!(actual_msg, msg); } @@ -1149,7 +1199,7 @@ mod tests { let selector = ( 0u64, inner_enum, - EnumVariants::new(vec![ParamType::U64]).unwrap(), + EnumVariants::new(to_named(&[ParamType::U64])).unwrap(), ); Token::Enum(Box::new(selector)) diff --git a/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs b/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs index 28ca32761..9abd00979 100644 --- a/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs +++ b/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs @@ -1,7 +1,11 @@ -use crate::codec::utils::CodecDirection; +use fuel_types::bytes::padded_len_usize; + use crate::{ checked_round_up_to_word_alignment, - codec::{utils::CounterWithLimit, EncoderConfig}, + codec::{ + utils::{CodecDirection, CounterWithLimit}, + EncoderConfig, + }, error, types::{ errors::Result, @@ -10,24 +14,25 @@ use crate::{ EnumSelector, StaticStringToken, Token, U256, }, }; -use fuel_types::bytes::padded_len_usize; pub(crate) struct BoundedEncoder { + used_for_configurables: bool, depth_tracker: CounterWithLimit, token_tracker: CounterWithLimit, max_total_enum_width: usize, } impl BoundedEncoder { - pub(crate) fn new(config: EncoderConfig) -> Self { + pub(crate) fn new(config: EncoderConfig, used_for_configurables: bool) -> Self { let depth_tracker = - CounterWithLimit::new(config.max_depth, "Depth", CodecDirection::Encoding); + CounterWithLimit::new(config.max_depth, "depth", CodecDirection::Encoding); let token_tracker = - CounterWithLimit::new(config.max_tokens, "Token", CodecDirection::Encoding); + CounterWithLimit::new(config.max_tokens, "token", CodecDirection::Encoding); Self { depth_tracker, token_tracker, max_total_enum_width: config.max_total_enum_width, + used_for_configurables, } } @@ -38,8 +43,16 @@ impl BoundedEncoder { // going through the whole array of tokens, which can be pretty inefficient. let data = if args.len() == 1 { match args[0] { - Token::Bool(arg_bool) => vec![Self::encode_bool_as_u64(arg_bool)], + Token::U8(arg_u8) if self.used_for_configurables => { + vec![Self::encode_u8_as_byte(arg_u8)] + } Token::U8(arg_u8) => vec![Self::encode_u8_as_u64(arg_u8)], + Token::Bool(arg_bool) if self.used_for_configurables => { + vec![Self::encode_bool_as_byte(arg_bool)] + } + Token::Bool(arg_bool) => { + vec![Self::encode_bool_as_u64(arg_bool)] + } _ => self.encode_tokens(args, true)?, } } else { @@ -53,10 +66,10 @@ impl BoundedEncoder { let mut offset_in_bytes = 0; let mut data = vec![]; - for token in tokens.iter() { + for token in tokens { self.token_tracker.increase()?; let mut new_data = self.encode_token(token)?; - offset_in_bytes += new_data.iter().map(|x| x.size_in_bytes()).sum::(); + offset_in_bytes += new_data.iter().map(Data::size_in_bytes).sum::(); data.append(&mut new_data); @@ -99,7 +112,7 @@ impl BoundedEncoder { Token::U256(arg_u256) => vec![Self::encode_u256(*arg_u256)], Token::Bool(arg_bool) => vec![Self::encode_bool_as_byte(*arg_bool)], Token::B256(arg_bits256) => vec![Self::encode_b256(arg_bits256)], - Token::RawSlice(data) => Self::encode_raw_slice(data.to_vec())?, + Token::RawSlice(data) => Self::encode_raw_slice(data.clone())?, Token::StringSlice(arg_string) => Self::encode_string_slice(arg_string)?, Token::StringArray(arg_string) => vec![Self::encode_string_array(arg_string)?], Token::Array(arg_array) => { @@ -186,13 +199,13 @@ impl BoundedEncoder { // Enums that contain only Units as variants have only their discriminant encoded. if !variants.only_units_inside() { - let variant_param_type = variants.param_type_of_variant(*discriminant)?; + let (_, variant_param_type) = variants.select_variant(*discriminant)?; let enum_width_in_bytes = variants.compute_enum_width_in_bytes()?; if enum_width_in_bytes > self.max_total_enum_width { return Err(error!( - InvalidData, - "Cannot encode Enum with variants {variants:?}: it is {enum_width_in_bytes} bytes wide. Try increasing encoder max memory." + Codec, + "cannot encode enum with variants: {variants:?}. It is `{enum_width_in_bytes}` bytes wide. Try increasing maximum total enum width." )); } let padding_amount = variants.compute_padding_amount_in_bytes(variant_param_type)?; diff --git a/packages/fuels-core/src/codec/function_selector.rs b/packages/fuels-core/src/codec/function_selector.rs index 4877ac15e..77c179c1a 100644 --- a/packages/fuels-core/src/codec/function_selector.rs +++ b/packages/fuels-core/src/codec/function_selector.rs @@ -1,6 +1,9 @@ use sha2::{Digest, Sha256}; -use crate::types::{param_types::ParamType, ByteArray}; +use crate::types::{ + param_types::{NamedParamType, ParamType}, + ByteArray, +}; /// Given a function name and its inputs will return a ByteArray representing /// the function selector as specified in the Fuel specs. @@ -16,8 +19,15 @@ fn resolve_fn_signature(name: &str, inputs: &[ParamType]) -> String { format!("{name}({fn_args})") } -fn resolve_args(arg: &[ParamType]) -> String { - arg.iter().map(resolve_arg).collect::>().join(",") +fn resolve_args(args: &[ParamType]) -> String { + args.iter().map(resolve_arg).collect::>().join(",") +} + +fn resolve_named_args(args: &[NamedParamType]) -> String { + args.iter() + .map(|(_, param_type)| resolve_arg(param_type)) + .collect::>() + .join(",") } fn resolve_arg(arg: &ParamType) -> String { @@ -43,7 +53,7 @@ fn resolve_arg(arg: &ParamType) -> String { fields, generics, .. } => { let gen_params = resolve_args(generics); - let field_params = resolve_args(fields); + let field_params = resolve_named_args(fields); let gen_params = if !gen_params.is_empty() { format!("<{gen_params}>") } else { @@ -52,12 +62,12 @@ fn resolve_arg(arg: &ParamType) -> String { format!("s{gen_params}({field_params})") } ParamType::Enum { - variants: fields, + enum_variants, generics, .. } => { let gen_params = resolve_args(generics); - let field_params = resolve_args(fields.param_types()); + let field_params = resolve_named_args(enum_variants.variants()); let gen_params = if !gen_params.is_empty() { format!("<{gen_params}>") } else { @@ -119,7 +129,7 @@ pub use calldata; #[cfg(test)] mod tests { use super::*; - use crate::types::enum_variants::EnumVariants; + use crate::{to_named, types::param_types::EnumVariants}; #[test] fn handles_primitive_types() { @@ -173,9 +183,13 @@ mod tests { #[test] fn handles_structs() { - let fields = vec![ParamType::U64, ParamType::U32]; + let fields = to_named(&[ParamType::U64, ParamType::U32]); let generics = vec![ParamType::U32]; - let inputs = [ParamType::Struct { fields, generics }]; + let inputs = [ParamType::Struct { + name: "".to_string(), + fields, + generics, + }]; let selector = resolve_fn_signature("some_fun", &inputs); @@ -202,10 +216,14 @@ mod tests { #[test] fn handles_enums() { - let types = vec![ParamType::U64, ParamType::U32]; - let variants = EnumVariants::new(types).unwrap(); + let types = to_named(&[ParamType::U64, ParamType::U32]); + let enum_variants = EnumVariants::new(types).unwrap(); let generics = vec![ParamType::U32]; - let inputs = [ParamType::Enum { variants, generics }]; + let inputs = [ParamType::Enum { + name: "".to_string(), + enum_variants, + generics, + }]; let selector = resolve_fn_signature("some_fun", &inputs); @@ -214,29 +232,34 @@ mod tests { #[test] fn ultimate_test() { - let fields = vec![ParamType::Struct { - fields: vec![ParamType::StringArray(2)], + let fields = to_named(&[ParamType::Struct { + name: "".to_string(), + + fields: to_named(&[ParamType::StringArray(2)]), generics: vec![ParamType::StringArray(2)], - }]; + }]); let struct_a = ParamType::Struct { + name: "".to_string(), fields, generics: vec![ParamType::StringArray(2)], }; - let fields = vec![ParamType::Array(Box::new(struct_a.clone()), 2)]; + let fields = to_named(&[ParamType::Array(Box::new(struct_a.clone()), 2)]); let struct_b = ParamType::Struct { + name: "".to_string(), fields, generics: vec![struct_a], }; - let fields = vec![ParamType::Tuple(vec![struct_b.clone(), struct_b.clone()])]; + let fields = to_named(&[ParamType::Tuple(vec![struct_b.clone(), struct_b.clone()])]); let struct_c = ParamType::Struct { + name: "".to_string(), fields, generics: vec![struct_b], }; - let types = vec![ParamType::U64, struct_c.clone()]; - let fields = vec![ + let types = to_named(&[ParamType::U64, struct_c.clone()]); + let fields = to_named(&[ ParamType::Tuple(vec![ ParamType::Array(Box::new(ParamType::B256), 2), ParamType::StringArray(2), @@ -244,16 +267,18 @@ mod tests { ParamType::Tuple(vec![ ParamType::Array( Box::new(ParamType::Enum { - variants: EnumVariants::new(types).unwrap(), + name: "".to_string(), + enum_variants: EnumVariants::new(types).unwrap(), generics: vec![struct_c], }), 1, ), ParamType::U32, ]), - ]; + ]); let inputs = [ParamType::Struct { + name: "".to_string(), fields, generics: vec![ParamType::StringArray(2), ParamType::B256], }]; diff --git a/packages/fuels-core/src/codec/logs.rs b/packages/fuels-core/src/codec/logs.rs index 672f1dbdb..23ac10893 100644 --- a/packages/fuels-core/src/codec/logs.rs +++ b/packages/fuels-core/src/codec/logs.rs @@ -48,11 +48,11 @@ impl LogFormatter { #[cfg(not(experimental))] fn can_decode_log_with_type() -> Result<()> { match T::param_type() { - // String slices can not be decoded from logs as they are encoded as ptr, len + // String slices cannot be decoded from logs as they are encoded as ptr, len // TODO: Once https://github.com/FuelLabs/sway/issues/5110 is resolved we can remove this ParamType::StringSlice => Err(error!( - InvalidData, - "String slices can not be decoded from logs. Convert the slice to `str[N]` with `__to_str_array`" + Codec, + "string slices cannot be decoded from logs. Convert the slice to `str[N]` with `__to_str_array`" )), _ => Ok(()), } @@ -137,9 +137,9 @@ impl LogDecoder { .get(log_id) .ok_or_else(|| { error!( - InvalidData, + Codec, "missing log formatter for log_id: `{:?}`, data: `{:?}`. \ - Consider adding external contracts with `with_contracts()`", + Consider adding external contracts using `with_contracts()`", log_id, data ) @@ -153,7 +153,7 @@ impl LogDecoder { .rev() .extract_log_id_and_data() .next() - .ok_or_else(|| error!(InvalidData, "No receipts found for decoding last log.")) + .ok_or_else(|| error!(Codec, "no receipts found for decoding last log")) .and_then(|(log_id, data)| self.format_log(&log_id, &data)) } @@ -169,7 +169,7 @@ impl LogDecoder { match res.as_deref() { Ok([rhs, lhs]) => Ok((lhs.to_string(), rhs.to_string())), Ok(some_slice) => Err(error!( - InvalidData, + Codec, "expected to have two logs. Found {}", some_slice.len() )), diff --git a/packages/fuels-core/src/codec/utils.rs b/packages/fuels-core/src/codec/utils.rs index a87d07057..c176bf73a 100644 --- a/packages/fuels-core/src/codec/utils.rs +++ b/packages/fuels-core/src/codec/utils.rs @@ -36,8 +36,8 @@ impl CounterWithLimit { self.count += 1; if self.count > self.max { return Err(error!( - InvalidType, - "{} limit ({}) reached while {}. Try increasing it.", + Codec, + "{} limit `{}` reached while {}. Try increasing it", self.name, self.max, self.direction diff --git a/packages/fuels-core/src/traits/parameterize.rs b/packages/fuels-core/src/traits/parameterize.rs index 447101a78..76777a905 100644 --- a/packages/fuels-core/src/traits/parameterize.rs +++ b/packages/fuels-core/src/traits/parameterize.rs @@ -1,8 +1,8 @@ use fuel_types::{Address, AssetId, ContractId}; use crate::types::{ - enum_variants::EnumVariants, param_types::ParamType, AsciiString, Bits256, Bytes, RawSlice, - SizedAsciiString, + param_types::{EnumVariants, ParamType}, + AsciiString, Bits256, Bytes, RawSlice, SizedAsciiString, }; /// `abigen` requires `Parameterized` to construct nested types. It is also used by `try_from_bytes` @@ -50,7 +50,8 @@ impl Parameterize for String { impl Parameterize for Address { fn param_type() -> ParamType { ParamType::Struct { - fields: vec![ParamType::B256], + name: "Address".to_string(), + fields: vec![("0".to_string(), ParamType::B256)], generics: vec![], } } @@ -59,7 +60,8 @@ impl Parameterize for Address { impl Parameterize for ContractId { fn param_type() -> ParamType { ParamType::Struct { - fields: vec![ParamType::B256], + name: "ContractId".to_string(), + fields: vec![("0".to_string(), ParamType::B256)], generics: vec![], } } @@ -68,7 +70,8 @@ impl Parameterize for ContractId { impl Parameterize for AssetId { fn param_type() -> ParamType { ParamType::Struct { - fields: vec![ParamType::B256], + name: "AssetId".to_string(), + fields: vec![("0".to_string(), ParamType::B256)], generics: vec![], } } @@ -121,11 +124,15 @@ where T: Parameterize, { fn param_type() -> ParamType { - let param_types = vec![ParamType::Unit, T::param_type()]; - let variants = EnumVariants::new(param_types) + let variant_param_types = vec![ + ("None".to_string(), ParamType::Unit), + ("Some".to_string(), T::param_type()), + ]; + let enum_variants = EnumVariants::new(variant_param_types) .expect("should never happen as we provided valid Option param types"); ParamType::Enum { - variants, + name: "Option".to_string(), + enum_variants, generics: vec![T::param_type()], } } @@ -137,12 +144,16 @@ where E: Parameterize, { fn param_type() -> ParamType { - let param_types = vec![T::param_type(), E::param_type()]; - let variants = EnumVariants::new(param_types.clone()) + let variant_param_types = vec![ + ("Ok".to_string(), T::param_type()), + ("Err".to_string(), E::param_type()), + ]; + let enum_variants = EnumVariants::new(variant_param_types) .expect("should never happen as we provided valid Result param types"); ParamType::Enum { - variants, - generics: param_types, + name: "Result".to_string(), + enum_variants, + generics: vec![T::param_type(), E::param_type()], } } } diff --git a/packages/fuels-core/src/traits/tokenizable.rs b/packages/fuels-core/src/traits/tokenizable.rs index e87d3b865..40cacafdc 100644 --- a/packages/fuels-core/src/traits/tokenizable.rs +++ b/packages/fuels-core/src/traits/tokenizable.rs @@ -35,8 +35,8 @@ impl Tokenizable for Bits256 { match token { Token::B256(data) => Ok(Bits256(data)), _ => Err(error!( - InvalidData, - "Bits256 cannot be constructed from token {token}" + Other, + "`Bits256` cannot be constructed from token {token}" )), } } @@ -55,8 +55,8 @@ impl Tokenizable for Vec { tokens.into_iter().map(Tokenizable::from_token).collect() } else { Err(error!( - InvalidData, - "Vec::from_token must only be given a Token::Vector. Got: {token}" + Other, + "`Vec::from_token` must only be given a `Token::Vector`. Got: `{token}`" )) } } @@ -71,10 +71,7 @@ impl Tokenizable for bool { fn from_token(token: Token) -> Result { match token { Token::Bool(data) => Ok(data), - other => Err(error!( - InstantiationError, - "Expected `bool`, got {:?}", other - )), + other => Err(error!(Other, "expected `bool`, got `{:?}`", other)), } } fn into_token(self) -> Token { @@ -89,10 +86,7 @@ impl Tokenizable for () { { match token { Token::Unit => Ok(()), - other => Err(error!( - InstantiationError, - "Expected `Unit`, got {:?}", other - )), + other => Err(error!(Other, "expected `Unit`, got `{:?}`", other)), } } @@ -105,7 +99,7 @@ impl Tokenizable for u8 { fn from_token(token: Token) -> Result { match token { Token::U8(data) => Ok(data), - other => Err(error!(InstantiationError, "Expected `u8`, got {:?}", other)), + other => Err(error!(Other, "expected `u8`, got `{:?}`", other)), } } fn into_token(self) -> Token { @@ -117,10 +111,7 @@ impl Tokenizable for u16 { fn from_token(token: Token) -> Result { match token { Token::U16(data) => Ok(data), - other => Err(error!( - InstantiationError, - "Expected `u16`, got {:?}", other - )), + other => Err(error!(Other, "expected `u16`, got `{:?}`", other)), } } fn into_token(self) -> Token { @@ -132,10 +123,7 @@ impl Tokenizable for u32 { fn from_token(token: Token) -> Result { match token { Token::U32(data) => Ok(data), - other => Err(error!( - InstantiationError, - "Expected `u32`, got {:?}", other - )), + other => Err(error!(Other, "expected `u32`, got {:?}", other)), } } fn into_token(self) -> Token { @@ -147,10 +135,7 @@ impl Tokenizable for u64 { fn from_token(token: Token) -> Result { match token { Token::U64(data) => Ok(data), - other => Err(error!( - InstantiationError, - "Expected `u64`, got {:?}", other - )), + other => Err(error!(Other, "expected `u64`, got {:?}", other)), } } fn into_token(self) -> Token { @@ -162,10 +147,7 @@ impl Tokenizable for u128 { fn from_token(token: Token) -> Result { match token { Token::U128(data) => Ok(data), - other => Err(error!( - InstantiationError, - "Expected `u128`, got {:?}", other - )), + other => Err(error!(Other, "expected `u128`, got {:?}", other)), } } fn into_token(self) -> Token { @@ -180,8 +162,8 @@ impl Tokenizable for RawSlice { { match token { Token::RawSlice(contents) => Ok(Self(contents)), - _ => Err(error!(InvalidData, - "RawSlice::from_token expected a token of the variant Token::RawSlice, got: {token}" + _ => Err(error!(Other, + "`RawSlice::from_token` expected a token of the variant `Token::RawSlice`, got: `{token}`" )), } } @@ -199,8 +181,8 @@ impl Tokenizable for Bytes { match token { Token::Bytes(contents) => Ok(Self(contents)), _ => Err(error!( - InvalidData, - "Bytes::from_token expected a token of the variant Token::Bytes, got: {token}" + Other, + "`Bytes::from_token` expected a token of the variant `Token::Bytes`, got: `{token}`" )), } } @@ -218,8 +200,8 @@ impl Tokenizable for String { match token { Token::String(string) => Ok(string), _ => Err(error!( - InvalidData, - "String::from_token expected a token of the variant Token::String, got: {token}" + Other, + "`String::from_token` expected a token of the variant `Token::String`, got: `{token}`" )), } } @@ -246,15 +228,15 @@ macro_rules! impl_tokenizable_tuples { let mut it = tokens.into_iter(); let mut next_token = move || { it.next().ok_or_else(|| { - error!(InstantiationError,"Ran out of tokens before tuple could be constructed") + error!(Other, "ran out of tokens before tuple could be constructed") }) }; Ok(($( $ty::from_token(next_token()?)?, )+)) }, - other => Err(error!(InstantiationError, - "Expected `Tuple`, got {:?}", + other => Err(error!(Other, + "expected `Tuple`, got `{:?}`", other )), } @@ -299,14 +281,14 @@ impl Tokenizable for ContractId { Ok(ContractId::from(*data)) } else { Err(error!( - InstantiationError, - "ContractId expected one `Token::B256`, got {tokens:?}" + Other, + "`ContractId` expected one `Token::B256`, got `{tokens:?}`" )) } } else { Err(error!( - InstantiationError, - "Address expected `Token::Struct` got {token:?}" + Other, + "`ContractId` expected `Token::Struct` got `{token:?}`" )) } } @@ -327,14 +309,14 @@ impl Tokenizable for Address { Ok(Address::from(*data)) } else { Err(error!( - InstantiationError, - "Address expected one `Token::B256`, got {tokens:?}" + Other, + "`Address` expected one `Token::B256`, got `{tokens:?}`" )) } } else { Err(error!( - InstantiationError, - "Address expected `Token::Struct` got {token:?}" + Other, + "`Address` expected `Token::Struct` got `{token:?}`" )) } } @@ -356,14 +338,14 @@ impl Tokenizable for AssetId { Ok(AssetId::from(*data)) } else { Err(error!( - InstantiationError, - "AssetId expected one `Token::B256`, got {tokens:?}" + Other, + "`AssetId` expected one `Token::B256`, got `{tokens:?}`" )) } } else { Err(error!( - InstantiationError, - "AssetId expected `Token::Struct` got {token:?}" + Other, + "`AssetId` expected `Token::Struct` got `{token:?}`" )) } } @@ -384,14 +366,15 @@ where (0, _, _) => Ok(None), (1, token, _) => Ok(Option::::Some(T::from_token(token)?)), (_, _, _) => Err(error!( - InstantiationError, - "Could not construct Option from enum_selector. Received: {:?}", enum_selector + Other, + "could not construct `Option` from `enum_selector`. Received: `{:?}`", + enum_selector )), } } else { Err(error!( - InstantiationError, - "Could not construct Option from token. Received: {token:?}" + Other, + "could not construct `Option` from token. Received: `{token:?}`" )) } } @@ -400,11 +383,11 @@ where None => (0, Token::Unit), Some(value) => (1, value.into_token()), }; - if let ParamType::Enum { variants, .. } = Self::param_type() { - let selector = (dis, tok, variants); + if let ParamType::Enum { enum_variants, .. } = Self::param_type() { + let selector = (dis, tok, enum_variants); Token::Enum(Box::new(selector)) } else { - panic!("should never happen as Option::param_type() returns valid Enum variants"); + panic!("should never happen as `Option::param_type()` returns valid Enum variants"); } } } @@ -420,14 +403,15 @@ where (0, token, _) => Ok(std::result::Result::::Ok(T::from_token(token)?)), (1, token, _) => Ok(std::result::Result::::Err(E::from_token(token)?)), (_, _, _) => Err(error!( - InstantiationError, - "Could not construct Result from enum_selector. Received: {:?}", enum_selector + Other, + "could not construct `Result` from `enum_selector`. Received: `{:?}`", + enum_selector )), } } else { Err(error!( - InstantiationError, - "Could not construct Result from token. Received: {token:?}" + Other, + "could not construct `Result` from token. Received: `{token:?}`" )) } } @@ -436,8 +420,8 @@ where Ok(value) => (0, value.into_token()), Err(value) => (1, value.into_token()), }; - if let ParamType::Enum { variants, .. } = Self::param_type() { - let selector = (dis, tok, variants); + if let ParamType::Enum { enum_variants, .. } = Self::param_type() { + let selector = (dis, tok, enum_variants); Token::Enum(Box::new(selector)) } else { panic!("should never happen as Result::param_type() returns valid Enum variants"); @@ -450,19 +434,14 @@ impl Tokenizable for [T; SIZE] { where Self: Sized, { - let gen_error = |reason| { - error!( - InvalidData, - "While constructing an array of size {SIZE}: {reason}" - ) - }; + let gen_error = |reason| error!(Other, "constructing an array of size {SIZE}: {reason}"); match token { Token::Array(elements) => { let len = elements.len(); if len != SIZE { return Err(gen_error(format!( - "Was given a Token::Array with wrong number of elements: {len}" + "`Token::Array` has wrong number of elements: {len}" ))); } @@ -475,10 +454,10 @@ impl Tokenizable for [T; SIZE] { })?; Ok(detokenized.try_into().unwrap_or_else(|_| { - panic!("This should never fail since we're checking the length beforehand.") + panic!("this should never fail since we're checking the length beforehand") })) } - _ => Err(gen_error(format!("Expected a Token::Array, got {token}"))), + _ => Err(gen_error(format!("expected a `Token::Array`, got {token}"))), } } @@ -496,12 +475,12 @@ impl Tokenizable for SizedAsciiString { Token::StringArray(contents) => { let expected_len = contents.get_encodable_str()?.len() ; if expected_len!= LEN { - return Err(error!(InvalidData,"SizedAsciiString<{LEN}>::from_token got a Token::StringArray whose expected length({}) is != {LEN}", expected_len)) + return Err(error!(Other,"`SizedAsciiString<{LEN}>::from_token` got a `Token::StringArray` whose expected length({}) is != {LEN}", expected_len)) } Self::new(contents.try_into()?) }, _ => { - Err(error!(InvalidData,"SizedAsciiString<{LEN}>::from_token expected a token of the variant Token::StringArray, got: {token}")) + Err(error!(Other,"`SizedAsciiString<{LEN}>::from_token` expected a token of the variant `Token::StringArray`, got: `{token}`")) } } } @@ -521,7 +500,7 @@ impl Tokenizable for AsciiString { Self::new(contents.try_into()?) }, _ => { - Err(error!(InvalidData,"AsciiString::from_token expected a token of the variant Token::StringSlice, got: {token}")) + Err(error!(Other,"`AsciiString::from_token` expected a token of the variant `Token::StringSlice`, got: `{token}`")) } } } @@ -591,7 +570,7 @@ mod tests { assert_eq!(contents, "abc"); } _ => { - panic!("Not tokenized correctly! Should have gotten a Token::String") + panic!("not tokenized correctly! Should have gotten a `Token::String`") } } @@ -603,7 +582,7 @@ mod tests { let token = Token::StringArray(StaticStringToken::new("abc".to_string(), Some(3))); let sized_ascii_string = - SizedAsciiString::<3>::from_token(token).expect("Should have succeeded"); + SizedAsciiString::<3>::from_token(token).expect("should have succeeded"); assert_eq!(sized_ascii_string, "abc"); diff --git a/packages/fuels-core/src/types.rs b/packages/fuels-core/src/types.rs index 47210f620..502077f2c 100644 --- a/packages/fuels-core/src/types.rs +++ b/packages/fuels-core/src/types.rs @@ -2,19 +2,18 @@ use std::fmt; use fuel_types::bytes::padded_len; pub use fuel_types::{ - Address, AssetId, Bytes32, Bytes4, Bytes64, Bytes8, ChainId, ContractId, MessageId, Nonce, - Salt, Word, + Address, AssetId, BlockHeight, Bytes32, Bytes4, Bytes64, Bytes8, ChainId, ContractId, + MessageId, Nonce, Salt, Word, }; pub use crate::types::{core::*, wrappers::*}; use crate::types::{ - enum_variants::EnumVariants, errors::{error, Error, Result}, + param_types::EnumVariants, }; pub mod bech32; mod core; -pub mod enum_variants; pub mod errors; pub mod param_types; pub mod transaction_builders; @@ -28,7 +27,7 @@ pub type EnumSelector = (u64, Token, EnumVariants); #[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] pub struct StaticStringToken { - data: String, + pub(crate) data: String, expected_len: Option, } @@ -39,17 +38,14 @@ impl StaticStringToken { fn validate(&self) -> Result<()> { if !self.data.is_ascii() { - return Err(error!( - InvalidData, - "String data can only have ascii values" - )); + return Err(error!(Codec, "string data can only have ascii values")); } if let Some(expected_len) = self.expected_len { if self.data.len() != expected_len { return Err(error!( - InvalidData, - "String data has len {}, but the expected len is {}", + Codec, + "string data has len {}, but the expected len is {}", self.data.len(), expected_len )); diff --git a/packages/fuels-core/src/types/core/bits.rs b/packages/fuels-core/src/types/core/bits.rs index 7271f2b1a..eefc5e5c3 100644 --- a/packages/fuels-core/src/types/core/bits.rs +++ b/packages/fuels-core/src/types/core/bits.rs @@ -1,7 +1,7 @@ use fuel_types::AssetId; use fuels_macros::{Parameterize, Tokenizable, TryFrom}; -use crate::types::errors::{error, Result}; +use crate::types::errors::Result; // A simple wrapper around [u8; 32] representing the `b256` type. Exists // mainly so that we may differentiate `Parameterize` and `Tokenizable` @@ -25,12 +25,8 @@ impl Bits256 { }; let mut bytes = [0u8; 32]; - hex::decode_to_slice(hex, &mut bytes as &mut [u8]).map_err(|e| { - error!( - InvalidData, - "Could not convert hex str '{hex}' to Bits256! {e}" - ) - })?; + hex::decode_to_slice(hex, &mut bytes as &mut [u8])?; + Ok(Bits256(bytes)) } } @@ -112,9 +108,11 @@ mod tests { assert_eq!(bits256.0, [1u8; 32]); // With the `0x0` prefix + // ANCHOR: hex_str_to_bits256 let hex_str = "0x0101010101010101010101010101010101010101010101010101010101010101"; let bits256 = Bits256::from_hex_str(hex_str)?; + // ANCHOR_END: hex_str_to_bits256 assert_eq!(bits256.0, [1u8; 32]); // ANCHOR_END: from_hex_str @@ -127,7 +125,8 @@ mod tests { assert_eq!( EvmAddress::param_type(), ParamType::Struct { - fields: vec![ParamType::B256], + name: "EvmAddress".to_string(), + fields: vec![("value".to_string(), ParamType::B256)], generics: vec![] } ); diff --git a/packages/fuels-core/src/types/core/bytes.rs b/packages/fuels-core/src/types/core/bytes.rs index dcea9f354..02e6dc651 100644 --- a/packages/fuels-core/src/types/core/bytes.rs +++ b/packages/fuels-core/src/types/core/bytes.rs @@ -1,4 +1,4 @@ -use crate::types::errors::{error, Result}; +use crate::types::errors::Result; #[derive(Debug, PartialEq, Clone, Eq)] pub struct Bytes(pub Vec); @@ -12,13 +12,7 @@ impl Bytes { } else { hex }; - - let bytes = hex::decode(hex).map_err(|e| { - error!( - InvalidData, - "Could not convert hex str '{hex}' to Bytes! {e}" - ) - })?; + let bytes = hex::decode(hex)?; Ok(Bytes(bytes)) } @@ -56,9 +50,11 @@ mod tests { assert_eq!(bytes.0, vec![1u8; 32]); // With the `0x0` prefix + // ANCHOR: hex_string_to_bytes32 let hex_str = "0x0101010101010101010101010101010101010101010101010101010101010101"; let bytes = Bytes::from_hex_str(hex_str)?; + // ANCHOR_END: hex_string_to_bytes32 assert_eq!(bytes.0, vec![1u8; 32]); // ANCHOR_END: bytes_from_hex_str diff --git a/packages/fuels-core/src/types/core/sized_ascii_string.rs b/packages/fuels-core/src/types/core/sized_ascii_string.rs index 1eccc9850..698109701 100644 --- a/packages/fuels-core/src/types/core/sized_ascii_string.rs +++ b/packages/fuels-core/src/types/core/sized_ascii_string.rs @@ -14,8 +14,8 @@ pub struct AsciiString { impl AsciiString { pub fn new(data: String) -> Result { if !data.is_ascii() { - return Err(error!(InvalidData, - "AsciiString must be constructed from a string containing only ascii encodable characters. Got: {data}" + return Err(error!(Other, + "`AsciiString` must be constructed from a string containing only ascii encodable characters. Got: `{data}`" )); } Ok(Self { data }) @@ -82,13 +82,13 @@ pub struct SizedAsciiString { impl SizedAsciiString { pub fn new(data: String) -> Result { if !data.is_ascii() { - return Err(error!(InvalidData, - "SizedAsciiString must be constructed from a string containing only ascii encodable characters. Got: {data}" + return Err(error!(Other, + "`SizedAsciiString` must be constructed from a `String` containing only ascii encodable characters. Got: `{data}`" )); } if data.len() != LEN { - return Err(error!(InvalidData, - "SizedAsciiString<{LEN}> can only be constructed from a String of length {LEN}. Got: {data}" + return Err(error!(Other, + "`SizedAsciiString<{LEN}>` must be constructed from a `String` of length {LEN}. Got: `{data}`" )); } Ok(Self { data }) @@ -108,8 +108,8 @@ impl SizedAsciiString { pub fn new_with_right_whitespace_padding(data: String) -> Result { if data.len() > LEN { return Err(error!( - InvalidData, - "SizedAsciiString<{LEN}> cannot be constructed from a string of size {}", + Other, + "`SizedAsciiString<{LEN}>` cannot be constructed from a string of size {}", data.len() )); } @@ -194,7 +194,7 @@ mod tests { let ascii_data = "abc".to_string(); SizedAsciiString::<3>::new(ascii_data) - .expect("Should have succeeded since we gave ascii data of correct length!"); + .expect("should have succeeded since we gave ascii data of correct length!"); // ANCHOR_END: string_simple_example } @@ -203,10 +203,10 @@ mod tests { let ascii_data = "ab©".to_string(); let err = SizedAsciiString::<3>::new(ascii_data) - .expect_err("Should not have succeeded since we gave non ascii data"); + .expect_err("should not have succeeded since we gave non ascii data"); - let expected_reason = "SizedAsciiString must be constructed from a string containing only ascii encodable characters. Got: "; - assert!(matches!(err, Error::InvalidData(reason) if reason.starts_with(expected_reason))); + let expected_reason = "`SizedAsciiString` must be constructed from a `String` containing only ascii encodable characters. Got: "; + assert!(matches!(err, Error::Other(reason) if reason.starts_with(expected_reason))); } #[test] @@ -214,22 +214,22 @@ mod tests { let ascii_data = "abcd".to_string(); let err = SizedAsciiString::<3>::new(ascii_data) - .expect_err("Should not have succeeded since we gave data of wrong length"); + .expect_err("should not have succeeded since we gave data of wrong length"); let expected_reason = - "SizedAsciiString<3> can only be constructed from a String of length 3. Got: abcd"; - assert!(matches!(err, Error::InvalidData(reason) if reason.starts_with(expected_reason))); + "`SizedAsciiString<3>` must be constructed from a `String` of length 3. Got: `abcd`"; + assert!(matches!(err, Error::Other(reason) if reason.starts_with(expected_reason))); } // ANCHOR: conversion #[test] fn can_be_constructed_from_str_ref() { - let _: SizedAsciiString<3> = "abc".try_into().expect("Should have succeeded"); + let _: SizedAsciiString<3> = "abc".try_into().expect("should have succeeded"); } #[test] fn can_be_constructed_from_string() { - let _: SizedAsciiString<3> = "abc".to_string().try_into().expect("Should have succeeded"); + let _: SizedAsciiString<3> = "abc".to_string().try_into().expect("should have succeeded"); } #[test] diff --git a/packages/fuels-core/src/types/core/u256.rs b/packages/fuels-core/src/types/core/u256.rs index dcca899eb..d2f504918 100644 --- a/packages/fuels-core/src/types/core/u256.rs +++ b/packages/fuels-core/src/types/core/u256.rs @@ -31,8 +31,8 @@ impl Tokenizable for U256 { match token { Token::U256(data) => Ok(data), _ => Err(error!( - InvalidData, - "U256 cannot be constructed from token {token}" + Other, + "`U256` cannot be constructed from token `{token}`" )), } } diff --git a/packages/fuels-core/src/types/enum_variants.rs b/packages/fuels-core/src/types/enum_variants.rs deleted file mode 100644 index 268c3643a..000000000 --- a/packages/fuels-core/src/types/enum_variants.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::{ - constants::ENUM_DISCRIMINANT_BYTE_WIDTH, - types::{ - errors::{error, Result}, - param_types::ParamType, - }, - utils::checked_round_up_to_word_alignment, -}; - -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct EnumVariants { - param_types: Vec, -} - -impl EnumVariants { - pub fn new(param_types: Vec) -> Result { - if param_types.is_empty() { - return Err(error!(InvalidData, "Enum variants can not be empty!")); - } - Ok(EnumVariants { param_types }) - } - - pub fn param_types(&self) -> &[ParamType] { - &self.param_types - } - - pub fn param_type_of_variant(&self, discriminant: u64) -> Result<&ParamType> { - self.param_types.get(discriminant as usize).ok_or_else(|| { - error!( - InvalidData, - "Discriminant '{discriminant}' doesn't point to any variant: {:?}", - self.param_types() - ) - }) - } - - pub fn heap_type_variant(&self) -> Option<(u64, &ParamType)> { - self.param_types() - .iter() - .enumerate() - .find_map(|(d, p)| p.is_extra_receipt_needed(false).then_some((d as u64, p))) - } - - pub fn only_units_inside(&self) -> bool { - self.param_types - .iter() - .all(|param_type| *param_type == ParamType::Unit) - } - - /// Calculates how many bytes are needed to encode an enum. - pub fn compute_enum_width_in_bytes(&self) -> Result { - if self.only_units_inside() { - return Ok(ENUM_DISCRIMINANT_BYTE_WIDTH); - } - - let width = self.param_types().iter().try_fold(0, |a, p| -> Result<_> { - let size = p.compute_encoding_in_bytes()?; - Ok(a.max(size)) - })?; - - checked_round_up_to_word_alignment(width)? - .checked_add(ENUM_DISCRIMINANT_BYTE_WIDTH) - .ok_or_else(|| error!(InvalidType, "Enum variants are too wide")) - } - - /// Determines the padding needed for the provided enum variant (based on the width of the - /// biggest variant) and returns it. - pub fn compute_padding_amount_in_bytes(&self, variant_param_type: &ParamType) -> Result { - let enum_width = self.compute_enum_width_in_bytes()?; - // No need to use checked arithmetics since we called `compute_enum_width_in_bytes` - let biggest_variant_width = enum_width - ENUM_DISCRIMINANT_BYTE_WIDTH; - let variant_width = variant_param_type.compute_encoding_in_bytes()?; - Ok(biggest_variant_width - variant_width) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_get_heap_type_variant_discriminant() -> Result<()> { - let param_types = vec![ - ParamType::U64, - ParamType::Bool, - ParamType::Vector(Box::from(ParamType::U64)), - ]; - let variants = EnumVariants::new(param_types)?; - assert_eq!(variants.heap_type_variant().unwrap().0, 2); - - let param_types = vec![ - ParamType::Vector(Box::from(ParamType::U64)), - ParamType::U64, - ParamType::Bool, - ]; - let variants = EnumVariants::new(param_types)?; - assert_eq!(variants.heap_type_variant().unwrap().0, 0); - - let param_types = vec![ParamType::U64, ParamType::Bool]; - let variants = EnumVariants::new(param_types)?; - assert!(variants.heap_type_variant().is_none()); - Ok(()) - } -} diff --git a/packages/fuels-core/src/types/errors.rs b/packages/fuels-core/src/types/errors.rs index 5e42bf608..58743627b 100644 --- a/packages/fuels-core/src/types/errors.rs +++ b/packages/fuels-core/src/types/errors.rs @@ -2,51 +2,50 @@ use std::{array::TryFromSliceError, str::Utf8Error}; use fuel_tx::{Receipt, ValidityError}; use fuel_vm::checked_transaction::CheckError; +use hex::FromHexError; use thiserror::Error; +pub mod transaction { + use super::*; + + #[derive(Error, Debug)] + pub enum Reason { + #[error("builder: {0}")] + Builder(String), + #[error("validation: {0}")] + Validation(String), + #[error("squeezedOut: {0}")] + SqueezedOut(String), + #[error("reverted: {reason}, receipts: {receipts:?}")] + Reverted { + reason: String, + revert_id: u64, + receipts: Vec, + }, + #[error(": {0}")] + Other(String), + } +} +use transaction::Reason; + #[derive(Error, Debug)] pub enum Error { - #[error("Invalid data: {0}")] - InvalidData(String), - #[error("Serialization error: {0}")] - SerdeJson(#[from] serde_json::Error), - #[error("IO error: {0}")] - IOError(#[from] std::io::Error), - #[error("Invalid type: {0}")] - InvalidType(String), - #[error("Utf8 error: {0}")] - Utf8Error(#[from] Utf8Error), - #[error("Instantiation error: {0}")] - InstantiationError(String), - #[error("Infrastructure error: {0}")] - InfrastructureError(String), - #[error("Account error: {0}")] - AccountError(String), - #[error("Wallet error: {0}")] - WalletError(String), - #[error("Provider error: {0}")] - ProviderError(String), - #[error("Validation error: {0}")] - ValidationError(String), - #[error("Tried to forward assets to a contract method that is not payable.")] - AssetsForwardedToNonPayableMethod, - #[error("Revert transaction error: {reason},\n receipts: {receipts:?}")] - RevertTransactionError { - reason: String, - revert_id: u64, - receipts: Vec, - }, - #[error("Transaction was squeezed out. Reason: `{0}`")] - SqueezedOutTransactionError(String), - #[error("Transaction build error: {0}")] - TransactionBuildError(String), + #[error("io: {0}")] + IO(#[from] std::io::Error), + #[error("codec: {0}")] + Codec(String), + #[error("transaction {0}")] + Transaction(Reason), + #[error("provider: {0}")] + Provider(String), + #[error("{0}")] + Other(String), } pub type Result = std::result::Result; /// This macro can only be used for `Error` variants that have a `String` field. -/// Those are: `InvalidData`, `InvalidType`, `InfrastructureError`, -/// `InstantiationError`, `WalletError`, `ProviderError`, `TransactionBuildError` +/// Those are: `IO`, `Codec`, `Provider`, `Other`. #[macro_export] macro_rules! error { ($err_variant:ident, $fmt_str: literal $(,$arg: expr)*) => { @@ -55,27 +54,43 @@ macro_rules! error { } pub use error; -macro_rules! impl_error_from { - ($err_variant:ident, $err_type:ty ) => { - impl From<$err_type> for Error { - fn from(err: $err_type) -> Error { - Error::$err_variant(err.to_string()) - } - } - }; +/// This macro can only be used for `Error::Transaction` variants that have a `String` field. +/// Those are: `Builder`, `Validation`, `SqueezedOut`, `Other`. +#[macro_export] +macro_rules! error_transaction { + ($err_variant:ident, $fmt_str: literal $(,$arg: expr)*) => { + $crate::types::errors::Error::Transaction( + $crate::types::errors::transaction::Reason::$err_variant(format!($fmt_str,$($arg),*))) + } } +pub use error_transaction; impl From for Error { fn from(err: CheckError) -> Error { - Error::ValidationError(format!("{:?}", err)) + error_transaction!(Validation, "{err:?}") } } impl From for Error { fn from(err: ValidityError) -> Error { - Error::ValidationError(format!("{:?}", err)) + error_transaction!(Validation, "{err:?}") } } -impl_error_from!(InvalidData, bech32::Error); -impl_error_from!(InvalidData, TryFromSliceError); +macro_rules! impl_error_from { + ($err_variant:ident, $err_type:ty ) => { + impl From<$err_type> for $crate::types::errors::Error { + fn from(err: $err_type) -> $crate::types::errors::Error { + $crate::types::errors::Error::$err_variant(err.to_string()) + } + } + }; +} + +impl_error_from!(Other, &'static str); +impl_error_from!(Other, bech32::Error); +impl_error_from!(Other, fuel_crypto::Error); +impl_error_from!(Other, serde_json::Error); +impl_error_from!(Other, FromHexError); +impl_error_from!(Other, TryFromSliceError); +impl_error_from!(Other, Utf8Error); diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index c2bff0e2f..5ea2d1aec 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -1,1910 +1,7 @@ -use std::{collections::HashMap, fmt, iter::zip}; +mod debug_with_depth; +mod enum_variants; +mod from_type_application; +mod param_type; -use fuel_abi_types::{ - abi::program::{TypeApplication, TypeDeclaration}, - utils::{extract_array_len, extract_generic_name, extract_str_len, has_tuple_format}, -}; -use itertools::chain; - -use crate::{ - checked_round_up_to_word_alignment, - types::{ - enum_variants::EnumVariants, - errors::{error, Error, Result}, - }, -}; - -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub enum ParamType { - Unit, - Bool, - U8, - U16, - U32, - U64, - U128, - U256, - B256, - Bytes, - String, - RawSlice, - StringArray(usize), - StringSlice, - Tuple(Vec), - Array(Box, usize), - Vector(Box), - Struct { - fields: Vec, - generics: Vec, - }, - Enum { - variants: EnumVariants, - generics: Vec, - }, -} - -pub enum ReturnLocation { - Return, - ReturnData, -} - -impl ParamType { - // Depending on the type, the returned value will be stored - // either in `Return` or `ReturnData`. - pub fn get_return_location(&self) -> ReturnLocation { - match self { - Self::Unit | Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::Bool => { - ReturnLocation::Return - } - - _ => ReturnLocation::ReturnData, - } - } - - /// Given a [ParamType], return the number of elements of that [ParamType] that can fit in - /// `available_bytes`: it is the length of the corresponding heap type. - pub fn calculate_num_of_elements( - param_type: &ParamType, - available_bytes: usize, - ) -> Result { - let memory_size = param_type.compute_encoding_in_bytes()?; - if memory_size == 0 { - return Err(error!( - InvalidType, - "Cannot calculate the number of elements because the type is zero-sized." - )); - } - let remainder = available_bytes % memory_size; - if remainder != 0 { - return Err(error!( - InvalidData, - "{remainder} extra bytes detected while decoding heap type" - )); - } - let num_of_elements = available_bytes - .checked_div(memory_size) - .ok_or_else(|| error!(InvalidData, "Type {param_type:?} has a memory_size of 0"))?; - Ok(num_of_elements) - } - - pub fn children_need_extra_receipts(&self) -> bool { - match self { - ParamType::Array(inner, _) | ParamType::Vector(inner) => { - inner.is_extra_receipt_needed(false) - } - ParamType::Struct { fields, .. } => fields - .iter() - .any(|param_type| param_type.is_extra_receipt_needed(false)), - ParamType::Enum { variants, .. } => variants - .param_types() - .iter() - .any(|param_type| param_type.is_extra_receipt_needed(false)), - ParamType::Tuple(inner_types) => inner_types - .iter() - .any(|param_type| param_type.is_extra_receipt_needed(false)), - _ => false, - } - } - - pub fn validate_is_decodable(&self, max_depth: usize) -> Result<()> { - if let ParamType::Enum { variants, .. } = self { - let all_param_types = variants.param_types(); - let grandchildren_need_receipts = all_param_types - .iter() - .any(|child| child.children_need_extra_receipts()); - if grandchildren_need_receipts { - return Err(error!( - InvalidType, - "Enums currently support only one level deep heap types." - )); - } - - let num_of_children_needing_receipts = all_param_types - .iter() - .filter(|param_type| param_type.is_extra_receipt_needed(false)) - .count(); - if num_of_children_needing_receipts > 1 { - return Err(error!( - InvalidType, - "Enums currently support only one heap-type variant. Found: \ - {num_of_children_needing_receipts}" - )); - } - } else if self.children_need_extra_receipts() { - return Err(error!( - InvalidType, - "type {:?} is not decodable: nested heap types are currently not supported except in Enums.", - DebugWithDepth::new(self, max_depth) - )); - } - self.compute_encoding_in_bytes()?; - Ok(()) - } - - pub fn is_extra_receipt_needed(&self, top_level_type: bool) -> bool { - match self { - ParamType::Vector(_) | ParamType::Bytes | ParamType::String => true, - ParamType::Array(inner, _) => inner.is_extra_receipt_needed(false), - ParamType::Struct { fields, generics } => { - chain!(fields, generics).any(|param_type| param_type.is_extra_receipt_needed(false)) - } - ParamType::Enum { variants, generics } => chain!(variants.param_types(), generics) - .any(|param_type| param_type.is_extra_receipt_needed(false)), - ParamType::Tuple(elements) => elements - .iter() - .any(|param_type| param_type.is_extra_receipt_needed(false)), - ParamType::RawSlice | ParamType::StringSlice => !top_level_type, - _ => false, - } - } - - /// Compute the inner memory size of a containing heap type (`Bytes` or `Vec`s). - pub fn heap_inner_element_size(&self, top_level_type: bool) -> Result> { - let heap_bytes_size = match &self { - ParamType::Vector(inner_param_type) => { - Some(inner_param_type.compute_encoding_in_bytes()?) - } - // `Bytes` type is byte-packed in the VM, so it's the size of an u8 - ParamType::Bytes | ParamType::String => Some(std::mem::size_of::()), - ParamType::StringSlice if !top_level_type => { - Some(ParamType::U8.compute_encoding_in_bytes()?) - } - ParamType::RawSlice if !top_level_type => { - Some(ParamType::U64.compute_encoding_in_bytes()?) - } - _ => None, - }; - Ok(heap_bytes_size) - } - - /// Calculates the number of bytes the VM expects this parameter to be encoded in. - pub fn compute_encoding_in_bytes(&self) -> Result { - let overflow_error = || { - error!( - InvalidType, - "Reached overflow while computing encoding size for {:?}", self - ) - }; - match &self { - ParamType::Unit | ParamType::U8 | ParamType::Bool => Ok(1), - ParamType::U16 | ParamType::U32 | ParamType::U64 => Ok(8), - ParamType::U128 | ParamType::RawSlice | ParamType::StringSlice => Ok(16), - ParamType::U256 | ParamType::B256 => Ok(32), - ParamType::Vector(_) | ParamType::Bytes | ParamType::String => Ok(24), - ParamType::Array(param, count) => param - .compute_encoding_in_bytes()? - .checked_mul(*count) - .ok_or_else(overflow_error), - ParamType::StringArray(len) => { - checked_round_up_to_word_alignment(*len).map_err(|_| overflow_error()) - } - ParamType::Tuple(fields) | ParamType::Struct { fields, .. } => { - fields.iter().try_fold(0, |a: usize, param_type| { - let size = checked_round_up_to_word_alignment( - param_type.compute_encoding_in_bytes()?, - )?; - a.checked_add(size).ok_or_else(overflow_error) - }) - } - ParamType::Enum { variants, .. } => variants - .compute_enum_width_in_bytes() - .map_err(|_| overflow_error()), - } - } - - /// For when you need to convert a ABI JSON's TypeApplication into a ParamType. - /// - /// # Arguments - /// - /// * `type_application`: The TypeApplication you wish to convert into a ParamType - /// * `type_lookup`: A HashMap of TypeDeclarations mentioned in the - /// TypeApplication where the type id is the key. - pub fn try_from_type_application( - type_application: &TypeApplication, - type_lookup: &HashMap, - ) -> Result { - Type::try_from(type_application, type_lookup)?.try_into() - } -} - -#[derive(Debug, Clone)] -struct Type { - type_field: String, - generic_params: Vec, - components: Vec, -} - -impl Type { - /// Will recursively drill down the given generic parameters until all types are - /// resolved. - /// - /// # Arguments - /// - /// * `type_application`: the type we wish to resolve - /// * `types`: all types used in the function call - pub fn try_from( - type_application: &TypeApplication, - type_lookup: &HashMap, - ) -> Result { - Self::resolve(type_application, type_lookup, &[]) - } - - fn resolve( - type_application: &TypeApplication, - type_lookup: &HashMap, - parent_generic_params: &[(usize, Type)], - ) -> Result { - let type_declaration = type_lookup.get(&type_application.type_id).ok_or_else(|| { - error!( - InvalidData, - "type id {} not found in type lookup", type_application.type_id - ) - })?; - - if extract_generic_name(&type_declaration.type_field).is_some() { - let (_, generic_type) = parent_generic_params - .iter() - .find(|(id, _)| *id == type_application.type_id) - .ok_or_else(|| { - error!( - InvalidData, - "type id {} not found in parent's generic parameters", - type_application.type_id - ) - })?; - - return Ok(generic_type.clone()); - } - - // Figure out what does the current type do with the inherited generic - // parameters and reestablish the mapping since the current type might have - // renamed the inherited generic parameters. - let generic_params_lookup = Self::determine_generics_for_type( - type_application, - type_lookup, - type_declaration, - parent_generic_params, - )?; - - // Resolve the enclosed components (if any) with the newly resolved generic - // parameters. - let components = type_declaration - .components - .iter() - .flatten() - .map(|component| Self::resolve(component, type_lookup, &generic_params_lookup)) - .collect::>>()?; - - Ok(Type { - type_field: type_declaration.type_field.clone(), - components, - generic_params: generic_params_lookup - .into_iter() - .map(|(_, ty)| ty) - .collect(), - }) - } - - /// For the given type generates generic_type_id -> Type mapping describing to - /// which types generic parameters should be resolved. - /// - /// # Arguments - /// - /// * `type_application`: The type on which the generic parameters are defined. - /// * `types`: All types used. - /// * `parent_generic_params`: The generic parameters as inherited from the - /// enclosing type (a struct/enum/array etc.). - fn determine_generics_for_type( - type_application: &TypeApplication, - type_lookup: &HashMap, - type_declaration: &TypeDeclaration, - parent_generic_params: &[(usize, Type)], - ) -> Result> { - match &type_declaration.type_parameters { - // The presence of type_parameters indicates that the current type - // (a struct or an enum) defines some generic parameters (i.e. SomeStruct). - Some(params) if !params.is_empty() => { - // Determine what Types the generics will resolve to. - let generic_params_from_current_type = type_application - .type_arguments - .iter() - .flatten() - .map(|ty| Self::resolve(ty, type_lookup, parent_generic_params)) - .collect::>>()?; - - let generics_to_use = if !generic_params_from_current_type.is_empty() { - generic_params_from_current_type - } else { - // Types such as arrays and enums inherit and forward their - // generic parameters, without declaring their own. - parent_generic_params - .iter() - .map(|(_, ty)| ty) - .cloned() - .collect() - }; - - // All inherited but unused generic types are dropped. The rest are - // re-mapped to new type_ids since child types are free to rename - // the generic parameters as they see fit -- i.e. - // struct ParentStruct{ - // b: ChildStruct - // } - // struct ChildStruct { - // c: K - // } - - Ok(zip(params.clone(), generics_to_use).collect()) - } - _ => Ok(parent_generic_params.to_vec()), - } - } -} - -impl TryFrom for ParamType { - type Error = Error; - - fn try_from(value: Type) -> Result { - (&value).try_into() - } -} - -impl TryFrom<&Type> for ParamType { - type Error = Error; - - fn try_from(the_type: &Type) -> Result { - let matched_param_type = [ - try_primitive, - try_array, - try_str_array, - try_str_slice, - try_tuple, - try_vector, - try_bytes, - try_std_string, - try_raw_slice, - try_enum, - try_u128, - try_u256, - try_struct, - ] - .into_iter() - .map(|fun| fun(the_type)) - .flat_map(|result| result.ok().flatten()) - .next(); - - matched_param_type.map(Ok).unwrap_or_else(|| { - Err(error!( - InvalidType, - "Type {} couldn't be converted into a ParamType", the_type.type_field - )) - }) - } -} - -fn convert_into_param_types(coll: &[Type]) -> Result> { - coll.iter().map(ParamType::try_from).collect() -} - -fn try_struct(the_type: &Type) -> Result> { - let result = if has_struct_format(&the_type.type_field) { - let generics = param_types(&the_type.generic_params)?; - - let fields = convert_into_param_types(&the_type.components)?; - Some(ParamType::Struct { fields, generics }) - } else { - None - }; - - Ok(result) -} - -fn has_struct_format(field: &str) -> bool { - field.starts_with("struct ") -} - -fn try_vector(the_type: &Type) -> Result> { - if !["struct std::vec::Vec", "struct Vec"].contains(&the_type.type_field.as_str()) { - return Ok(None); - } - - if the_type.generic_params.len() != 1 { - return Err(error!( - InvalidType, - "Vec must have exactly one generic argument for its type. Found: {:?}", - the_type.generic_params - )); - } - - let vec_elem_type = convert_into_param_types(&the_type.generic_params)?.remove(0); - - Ok(Some(ParamType::Vector(Box::new(vec_elem_type)))) -} - -fn try_u128(the_type: &Type) -> Result> { - Ok(["struct std::u128::U128", "struct U128"] - .contains(&the_type.type_field.as_str()) - .then_some(ParamType::U128)) -} - -fn try_u256(the_type: &Type) -> Result> { - Ok(["struct std::u256::U256", "struct U256"] - .contains(&the_type.type_field.as_str()) - .then_some(ParamType::U256)) -} - -fn try_bytes(the_type: &Type) -> Result> { - Ok(["struct std::bytes::Bytes", "struct Bytes"] - .contains(&the_type.type_field.as_str()) - .then_some(ParamType::Bytes)) -} - -fn try_std_string(the_type: &Type) -> Result> { - Ok(["struct std::string::String", "struct String"] - .contains(&the_type.type_field.as_str()) - .then_some(ParamType::String)) -} - -fn try_raw_slice(the_type: &Type) -> Result> { - Ok((the_type.type_field == "raw untyped slice").then_some(ParamType::RawSlice)) -} - -fn try_enum(the_type: &Type) -> Result> { - let field = &the_type.type_field; - let result = if field.starts_with("enum ") { - let generics = param_types(&the_type.generic_params)?; - - let components = convert_into_param_types(&the_type.components)?; - let variants = EnumVariants::new(components)?; - - Some(ParamType::Enum { variants, generics }) - } else { - None - }; - - Ok(result) -} - -fn try_tuple(the_type: &Type) -> Result> { - let result = if has_tuple_format(&the_type.type_field) { - let tuple_elements = param_types(&the_type.components)?; - Some(ParamType::Tuple(tuple_elements)) - } else { - None - }; - - Ok(result) -} - -fn param_types(coll: &[Type]) -> Result> { - coll.iter().map(|e| e.try_into()).collect() -} - -fn try_str_array(the_type: &Type) -> Result> { - Ok(extract_str_len(&the_type.type_field).map(ParamType::StringArray)) -} - -fn try_str_slice(the_type: &Type) -> Result> { - Ok(if the_type.type_field == "str" { - Some(ParamType::StringSlice) - } else { - None - }) -} - -fn try_array(the_type: &Type) -> Result> { - if let Some(len) = extract_array_len(&the_type.type_field) { - return match the_type.components.as_slice() { - [single_type] => { - let array_type = single_type.try_into()?; - Ok(Some(ParamType::Array(Box::new(array_type), len))) - } - _ => Err(error!( - InvalidType, - "An array must have elements of exactly one type. Array types: {:?}", - the_type.components - )), - }; - } - Ok(None) -} - -fn try_primitive(the_type: &Type) -> Result> { - let result = match the_type.type_field.as_str() { - "bool" => Some(ParamType::Bool), - "u8" => Some(ParamType::U8), - "u16" => Some(ParamType::U16), - "u32" => Some(ParamType::U32), - "u64" => Some(ParamType::U64), - "b256" => Some(ParamType::B256), - "()" => Some(ParamType::Unit), - "str" => Some(ParamType::StringSlice), - _ => None, - }; - - Ok(result) -} - -/// Allows `Debug` formatting of arbitrary-depth nested `ParamTypes` by -/// omitting the details of inner types if max depth is exceeded. -pub(crate) struct DebugWithDepth<'a> { - param_type: &'a ParamType, - depth_left: usize, -} - -impl<'a> DebugWithDepth<'a> { - pub(crate) fn new(param_type: &'a ParamType, depth_left: usize) -> Self { - Self { - param_type, - depth_left, - } - } - - fn descend(&'a self, param_type: &'a ParamType) -> Self { - Self { - param_type, - depth_left: self.depth_left - 1, - } - } -} - -impl<'a> fmt::Debug for DebugWithDepth<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.depth_left == 0 { - return write!(f, "..."); - } - - match &self.param_type { - ParamType::Array(inner, size) => f - .debug_tuple("Array") - .field(&self.descend(inner)) - .field(&size) - .finish(), - ParamType::Struct { fields, generics } => f - .debug_struct("Struct") - .field( - "fields", - &fields - .iter() - .map(|field| self.descend(field)) - .collect::>(), - ) - .field( - "generics", - &generics - .iter() - .map(|generic| self.descend(generic)) - .collect::>(), - ) - .finish(), - ParamType::Enum { variants, generics } => f - .debug_struct("Enum") - .field( - "variants", - &variants - .param_types() - .iter() - .map(|variant| self.descend(variant)) - .collect::>(), - ) - .field( - "generics", - &generics - .iter() - .map(|generic| self.descend(generic)) - .collect::>(), - ) - .finish(), - ParamType::Tuple(inner) => f - .debug_tuple("Tuple") - .field( - &inner - .iter() - .map(|param_type| self.descend(param_type)) - .collect::>(), - ) - .finish(), - ParamType::Vector(inner) => { - f.debug_tuple("Vector").field(&self.descend(inner)).finish() - } - _ => write!(f, "{:?}", self.param_type), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{codec::DecoderConfig, constants::WORD_SIZE, types::param_types::ParamType}; - - const WIDTH_OF_B256: usize = 32; - const WIDTH_OF_U32: usize = 8; - const WIDTH_OF_BOOL: usize = 1; - - #[test] - fn array_size_dependent_on_num_of_elements() { - const NUM_ELEMENTS: usize = 11; - let param = ParamType::Array(Box::new(ParamType::B256), NUM_ELEMENTS); - - let width = param.compute_encoding_in_bytes().unwrap(); - - let expected = NUM_ELEMENTS * WIDTH_OF_B256; - assert_eq!(expected, width); - } - - #[test] - fn string_size_dependent_on_num_of_elements() { - const NUM_ASCII_CHARS: usize = 9; - let param = ParamType::StringArray(NUM_ASCII_CHARS); - - let width = param.compute_encoding_in_bytes().unwrap(); - - assert_eq!(16, width); - } - - #[test] - fn structs_are_all_elements_combined_with_padding() -> Result<()> { - let inner_struct = ParamType::Struct { - fields: vec![ParamType::U32, ParamType::U32], - generics: vec![], - }; - - let a_struct = ParamType::Struct { - fields: vec![ParamType::B256, ParamType::Bool, inner_struct], - generics: vec![], - }; - - let width = a_struct.compute_encoding_in_bytes().unwrap(); - - const INNER_STRUCT_WIDTH: usize = WIDTH_OF_U32 * 2; - let expected_width: usize = - WIDTH_OF_B256 + checked_round_up_to_word_alignment(WIDTH_OF_BOOL)? + INNER_STRUCT_WIDTH; - assert_eq!(expected_width, width); - Ok(()) - } - - #[test] - fn enums_are_as_big_as_their_biggest_variant_plus_a_word() -> Result<()> { - let fields = vec![ParamType::B256]; - let inner_struct = ParamType::Struct { - fields, - generics: vec![], - }; - let types = vec![ParamType::U32, inner_struct]; - let param = ParamType::Enum { - variants: EnumVariants::new(types)?, - generics: vec![], - }; - - let width = param.compute_encoding_in_bytes().unwrap(); - - const INNER_STRUCT_SIZE: usize = WIDTH_OF_B256; - const EXPECTED_WIDTH: usize = INNER_STRUCT_SIZE + WORD_SIZE; - assert_eq!(EXPECTED_WIDTH, width); - Ok(()) - } - - #[test] - fn tuples_are_just_all_elements_combined() { - let inner_tuple = ParamType::Tuple(vec![ParamType::B256]); - let param = ParamType::Tuple(vec![ParamType::U32, inner_tuple]); - - let width = param.compute_encoding_in_bytes().unwrap(); - - const INNER_TUPLE_WIDTH: usize = WIDTH_OF_B256; - const EXPECTED_WIDTH: usize = WIDTH_OF_U32 + INNER_TUPLE_WIDTH; - assert_eq!(EXPECTED_WIDTH, width); - } - - #[test] - fn handles_simple_types() -> Result<()> { - let parse_param_type = |type_field: &str| { - let type_application = TypeApplication { - name: "".to_string(), - type_id: 0, - type_arguments: None, - }; - - let declarations = [TypeDeclaration { - type_id: 0, - type_field: type_field.to_string(), - components: None, - type_parameters: None, - }]; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - ParamType::try_from_type_application(&type_application, &type_lookup) - }; - - assert_eq!(parse_param_type("u8")?, ParamType::U8); - assert_eq!(parse_param_type("u16")?, ParamType::U16); - assert_eq!(parse_param_type("u32")?, ParamType::U32); - assert_eq!(parse_param_type("u64")?, ParamType::U64); - assert_eq!(parse_param_type("bool")?, ParamType::Bool); - assert_eq!(parse_param_type("b256")?, ParamType::B256); - assert_eq!(parse_param_type("()")?, ParamType::Unit); - assert_eq!(parse_param_type("str[21]")?, ParamType::StringArray(21)); - assert_eq!(parse_param_type("str")?, ParamType::StringSlice); - - Ok(()) - } - - #[test] - fn handles_arrays() -> Result<()> { - // given - let type_application = TypeApplication { - name: "".to_string(), - type_id: 0, - type_arguments: None, - }; - - let declarations = [ - TypeDeclaration { - type_id: 0, - type_field: "[_; 10]".to_string(), - components: Some(vec![TypeApplication { - name: "__array_element".to_string(), - type_id: 1, - type_arguments: None, - }]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 1, - type_field: "u8".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - assert_eq!(result, ParamType::Array(Box::new(ParamType::U8), 10)); - - Ok(()) - } - - #[test] - fn handles_vectors() -> Result<()> { - // given - let declarations = [ - TypeDeclaration { - type_id: 1, - type_field: "generic T".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 2, - type_field: "raw untyped ptr".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 3, - type_field: "struct std::vec::RawVec".to_string(), - components: Some(vec![ - TypeApplication { - name: "ptr".to_string(), - type_id: 2, - type_arguments: None, - }, - TypeApplication { - name: "cap".to_string(), - type_id: 5, - type_arguments: None, - }, - ]), - type_parameters: Some(vec![1]), - }, - TypeDeclaration { - type_id: 4, - type_field: "struct std::vec::Vec".to_string(), - components: Some(vec![ - TypeApplication { - name: "buf".to_string(), - type_id: 3, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 1, - type_arguments: None, - }]), - }, - TypeApplication { - name: "len".to_string(), - type_id: 5, - type_arguments: None, - }, - ]), - type_parameters: Some(vec![1]), - }, - TypeDeclaration { - type_id: 5, - type_field: "u64".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 6, - type_field: "u8".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_application = TypeApplication { - name: "arg".to_string(), - type_id: 4, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 6, - type_arguments: None, - }]), - }; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - assert_eq!(result, ParamType::Vector(Box::new(ParamType::U8))); - - Ok(()) - } - - #[test] - fn handles_structs() -> Result<()> { - // given - let declarations = [ - TypeDeclaration { - type_id: 1, - type_field: "generic T".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 2, - type_field: "struct SomeStruct".to_string(), - components: Some(vec![TypeApplication { - name: "field".to_string(), - type_id: 1, - type_arguments: None, - }]), - type_parameters: Some(vec![1]), - }, - TypeDeclaration { - type_id: 3, - type_field: "u8".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_application = TypeApplication { - name: "arg".to_string(), - type_id: 2, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 3, - type_arguments: None, - }]), - }; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - assert_eq!( - result, - ParamType::Struct { - fields: vec![ParamType::U8], - generics: vec![ParamType::U8] - } - ); - - Ok(()) - } - - #[test] - fn handles_enums() -> Result<()> { - // given - let declarations = [ - TypeDeclaration { - type_id: 1, - type_field: "generic T".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 2, - type_field: "enum SomeEnum".to_string(), - components: Some(vec![TypeApplication { - name: "variant".to_string(), - type_id: 1, - type_arguments: None, - }]), - type_parameters: Some(vec![1]), - }, - TypeDeclaration { - type_id: 3, - type_field: "u8".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_application = TypeApplication { - name: "arg".to_string(), - type_id: 2, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 3, - type_arguments: None, - }]), - }; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - assert_eq!( - result, - ParamType::Enum { - variants: EnumVariants::new(vec![ParamType::U8])?, - generics: vec![ParamType::U8] - } - ); - - Ok(()) - } - - #[test] - fn handles_tuples() -> Result<()> { - // given - let declarations = [ - TypeDeclaration { - type_id: 1, - type_field: "(_, _)".to_string(), - components: Some(vec![ - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 3, - type_arguments: None, - }, - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 2, - type_arguments: None, - }, - ]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 2, - type_field: "str[15]".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 3, - type_field: "u8".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_application = TypeApplication { - name: "arg".to_string(), - type_id: 1, - type_arguments: None, - }; - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - assert_eq!( - result, - ParamType::Tuple(vec![ParamType::U8, ParamType::StringArray(15)]) - ); - - Ok(()) - } - - #[test] - fn ultimate_example() -> Result<()> { - // given - let declarations = [ - TypeDeclaration { - type_id: 1, - type_field: "(_, _)".to_string(), - components: Some(vec![ - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 11, - type_arguments: None, - }, - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 11, - type_arguments: None, - }, - ]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 2, - type_field: "(_, _)".to_string(), - components: Some(vec![ - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 4, - type_arguments: None, - }, - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 24, - type_arguments: None, - }, - ]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 3, - type_field: "(_, _)".to_string(), - components: Some(vec![ - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 5, - type_arguments: None, - }, - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 13, - type_arguments: None, - }, - ]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 4, - type_field: "[_; 1]".to_string(), - components: Some(vec![TypeApplication { - name: "__array_element".to_string(), - type_id: 8, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 22, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 21, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 18, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 13, - type_arguments: None, - }]), - }]), - }]), - }]), - }]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 5, - type_field: "[_; 2]".to_string(), - components: Some(vec![TypeApplication { - name: "__array_element".to_string(), - type_id: 14, - type_arguments: None, - }]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 6, - type_field: "[_; 2]".to_string(), - components: Some(vec![TypeApplication { - name: "__array_element".to_string(), - type_id: 10, - type_arguments: None, - }]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 7, - type_field: "b256".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 8, - type_field: "enum EnumWGeneric".to_string(), - components: Some(vec![ - TypeApplication { - name: "a".to_string(), - type_id: 25, - type_arguments: None, - }, - TypeApplication { - name: "b".to_string(), - type_id: 12, - type_arguments: None, - }, - ]), - type_parameters: Some(vec![12]), - }, - TypeDeclaration { - type_id: 9, - type_field: "generic K".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 10, - type_field: "generic L".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 11, - type_field: "generic M".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 12, - type_field: "generic N".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 13, - type_field: "generic T".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 14, - type_field: "generic U".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 15, - type_field: "raw untyped ptr".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 16, - type_field: "str[2]".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 17, - type_field: "struct MegaExample".to_string(), - components: Some(vec![ - TypeApplication { - name: "a".to_string(), - type_id: 3, - type_arguments: None, - }, - TypeApplication { - name: "b".to_string(), - type_id: 23, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 2, - type_arguments: None, - }]), - }, - ]), - type_parameters: Some(vec![13, 14]), - }, - TypeDeclaration { - type_id: 18, - type_field: "struct PassTheGenericOn".to_string(), - components: Some(vec![TypeApplication { - name: "one".to_string(), - type_id: 20, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 9, - type_arguments: None, - }]), - }]), - type_parameters: Some(vec![9]), - }, - TypeDeclaration { - type_id: 19, - type_field: "struct std::vec::RawVec".to_string(), - components: Some(vec![ - TypeApplication { - name: "ptr".to_string(), - type_id: 15, - type_arguments: None, - }, - TypeApplication { - name: "cap".to_string(), - type_id: 25, - type_arguments: None, - }, - ]), - type_parameters: Some(vec![13]), - }, - TypeDeclaration { - type_id: 20, - type_field: "struct SimpleGeneric".to_string(), - components: Some(vec![TypeApplication { - name: "single_generic_param".to_string(), - type_id: 13, - type_arguments: None, - }]), - type_parameters: Some(vec![13]), - }, - TypeDeclaration { - type_id: 21, - type_field: "struct StructWArrayGeneric".to_string(), - components: Some(vec![TypeApplication { - name: "a".to_string(), - type_id: 6, - type_arguments: None, - }]), - type_parameters: Some(vec![10]), - }, - TypeDeclaration { - type_id: 22, - type_field: "struct StructWTupleGeneric".to_string(), - components: Some(vec![TypeApplication { - name: "a".to_string(), - type_id: 1, - type_arguments: None, - }]), - type_parameters: Some(vec![11]), - }, - TypeDeclaration { - type_id: 23, - type_field: "struct std::vec::Vec".to_string(), - components: Some(vec![ - TypeApplication { - name: "buf".to_string(), - type_id: 19, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 13, - type_arguments: None, - }]), - }, - TypeApplication { - name: "len".to_string(), - type_id: 25, - type_arguments: None, - }, - ]), - type_parameters: Some(vec![13]), - }, - TypeDeclaration { - type_id: 24, - type_field: "u32".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 25, - type_field: "u64".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - let type_application = TypeApplication { - name: "arg1".to_string(), - type_id: 17, - type_arguments: Some(vec![ - TypeApplication { - name: "".to_string(), - type_id: 16, - type_arguments: None, - }, - TypeApplication { - name: "".to_string(), - type_id: 7, - type_arguments: None, - }, - ]), - }; - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - let expected_param_type = { - let fields = vec![ParamType::Struct { - fields: vec![ParamType::StringArray(2)], - generics: vec![ParamType::StringArray(2)], - }]; - let pass_the_generic_on = ParamType::Struct { - fields, - generics: vec![ParamType::StringArray(2)], - }; - - let fields = vec![ParamType::Array(Box::from(pass_the_generic_on.clone()), 2)]; - let struct_w_array_generic = ParamType::Struct { - fields, - generics: vec![pass_the_generic_on], - }; - - let fields = vec![ParamType::Tuple(vec![ - struct_w_array_generic.clone(), - struct_w_array_generic.clone(), - ])]; - let struct_w_tuple_generic = ParamType::Struct { - fields, - generics: vec![struct_w_array_generic], - }; - - let types = vec![ParamType::U64, struct_w_tuple_generic.clone()]; - let fields = vec![ - ParamType::Tuple(vec![ - ParamType::Array(Box::from(ParamType::B256), 2), - ParamType::StringArray(2), - ]), - ParamType::Vector(Box::from(ParamType::Tuple(vec![ - ParamType::Array( - Box::from(ParamType::Enum { - variants: EnumVariants::new(types).unwrap(), - generics: vec![struct_w_tuple_generic], - }), - 1, - ), - ParamType::U32, - ]))), - ]; - ParamType::Struct { - fields, - generics: vec![ParamType::StringArray(2), ParamType::B256], - } - }; - - assert_eq!(result, expected_param_type); - - Ok(()) - } - - #[test] - fn validate_is_decodable_simple_types() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - assert!(ParamType::U8.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::U16.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::U32.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::U64.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::U128.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::U256.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::Bool.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::B256.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::Unit.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::StringSlice - .validate_is_decodable(max_depth) - .is_ok()); - assert!(ParamType::StringArray(10) - .validate_is_decodable(max_depth) - .is_ok()); - assert!(ParamType::RawSlice.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::Bytes.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::String.validate_is_decodable(max_depth).is_ok()); - Ok(()) - } - - #[test] - fn validate_is_decodable_complex_types_containing_bytes() -> Result<()> { - let param_types_containing_bytes = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; - let param_types_no_bytes = vec![ParamType::U64, ParamType::U32]; - let max_depth = DecoderConfig::default().max_depth; - let nested_heap_type_error_message = |p: ParamType| { - format!( - "Invalid type: type {:?} is not decodable: nested heap types are currently not \ - supported except in Enums.", - DebugWithDepth::new(&p, max_depth) - ) - }; - let cannot_be_decoded = |p: ParamType| { - assert_eq!( - p.validate_is_decodable(max_depth) - .expect_err(&format!("Should not be decodable: {:?}", p)) - .to_string(), - nested_heap_type_error_message(p) - ) - }; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - - can_be_decoded(ParamType::Array(Box::new(ParamType::U64), 10usize)); - cannot_be_decoded(ParamType::Array(Box::new(ParamType::Bytes), 10usize)); - - can_be_decoded(ParamType::Vector(Box::new(ParamType::U64))); - cannot_be_decoded(ParamType::Vector(Box::new(ParamType::Bytes))); - - can_be_decoded(ParamType::Struct { - generics: param_types_no_bytes.clone(), - fields: param_types_no_bytes.clone(), - }); - cannot_be_decoded(ParamType::Struct { - fields: param_types_containing_bytes.clone(), - generics: param_types_no_bytes.clone(), - }); - - can_be_decoded(ParamType::Tuple(param_types_no_bytes.clone())); - cannot_be_decoded(ParamType::Tuple(param_types_containing_bytes.clone())); - - Ok(()) - } - - #[test] - fn validate_is_decodable_enum_containing_bytes() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - let param_types_containing_bytes = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; - let param_types_no_bytes = vec![ParamType::U64, ParamType::U32]; - let variants_no_bytes_type = EnumVariants::new(param_types_no_bytes.clone())?; - let variants_one_bytes_type = EnumVariants::new(param_types_containing_bytes.clone())?; - let variants_two_bytes_type = EnumVariants::new(vec![ParamType::Bytes, ParamType::Bytes])?; - can_be_decoded(ParamType::Enum { - variants: variants_no_bytes_type.clone(), - generics: param_types_no_bytes.clone(), - }); - can_be_decoded(ParamType::Enum { - variants: variants_one_bytes_type.clone(), - generics: param_types_no_bytes.clone(), - }); - let expected = "Invalid type: Enums currently support only one heap-type variant. Found: 2" - .to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_bytes_type.clone(), - generics: param_types_no_bytes.clone(), - } - .validate_is_decodable(max_depth) - .expect_err("Should not be decodable") - .to_string(), - expected - ); - can_be_decoded(ParamType::Enum { - variants: variants_no_bytes_type, - generics: param_types_containing_bytes.clone(), - }); - can_be_decoded(ParamType::Enum { - variants: variants_one_bytes_type, - generics: param_types_containing_bytes.clone(), - }); - let expected = "Invalid type: Enums currently support only one heap-type variant. Found: 2" - .to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_bytes_type.clone(), - generics: param_types_containing_bytes.clone(), - } - .validate_is_decodable(max_depth) - .expect_err("Should not be decodable") - .to_string(), - expected - ); - Ok(()) - } - - #[test] - fn validate_is_decodable_complex_types_containing_string() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - let base_string = ParamType::String; - let param_types_no_nested_string = vec![ParamType::U64, ParamType::U32]; - let param_types_nested_string = vec![ParamType::Unit, ParamType::Bool, base_string.clone()]; - let nested_heap_type_error_message = |p: ParamType| { - format!( - "Invalid type: type {:?} is not decodable: nested heap types \ - are currently not supported except in Enums.", - DebugWithDepth::new(&p, max_depth) - ) - }; - let cannot_be_decoded = |p: ParamType| { - assert_eq!( - p.validate_is_decodable(max_depth) - .expect_err(&format!("Should not be decodable: {:?}", p)) - .to_string(), - nested_heap_type_error_message(p) - ) - }; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - - can_be_decoded(base_string.clone()); - cannot_be_decoded(ParamType::Vector(Box::from(base_string.clone()))); - - can_be_decoded(ParamType::Array(Box::from(ParamType::U8), 10)); - cannot_be_decoded(ParamType::Array(Box::from(base_string), 10)); - - can_be_decoded(ParamType::Tuple(param_types_no_nested_string.clone())); - cannot_be_decoded(ParamType::Tuple(param_types_nested_string.clone())); - - can_be_decoded(ParamType::Struct { - generics: param_types_no_nested_string.clone(), - fields: param_types_no_nested_string.clone(), - }); - - can_be_decoded(ParamType::Struct { - generics: param_types_nested_string.clone(), - fields: param_types_no_nested_string.clone(), - }); - - cannot_be_decoded(ParamType::Struct { - generics: param_types_no_nested_string.clone(), - fields: param_types_nested_string.clone(), - }); - - Ok(()) - } - - #[test] - fn validate_is_decodable_enum_containing_string() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - let param_types_containing_string = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; - let param_types_no_string = vec![ParamType::U64, ParamType::U32]; - let variants_no_string_type = EnumVariants::new(param_types_no_string.clone())?; - let variants_one_string_type = EnumVariants::new(param_types_containing_string.clone())?; - let variants_two_string_type = EnumVariants::new(vec![ParamType::Bytes, ParamType::Bytes])?; - can_be_decoded(ParamType::Enum { - variants: variants_no_string_type.clone(), - generics: param_types_no_string.clone(), - }); - can_be_decoded(ParamType::Enum { - variants: variants_one_string_type.clone(), - generics: param_types_no_string.clone(), - }); - let expected = "Invalid type: Enums currently support only one heap-type variant. Found: 2" - .to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_string_type.clone(), - generics: param_types_no_string.clone(), - } - .validate_is_decodable(1) - .expect_err("Should not be decodable") - .to_string(), - expected - ); - can_be_decoded(ParamType::Enum { - variants: variants_no_string_type, - generics: param_types_containing_string.clone(), - }); - can_be_decoded(ParamType::Enum { - variants: variants_one_string_type, - generics: param_types_containing_string.clone(), - }); - let expected = "Invalid type: Enums currently support only one heap-type variant. Found: 2" - .to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_string_type.clone(), - generics: param_types_containing_string.clone(), - } - .validate_is_decodable(1) - .expect_err("Should not be decodable") - .to_string(), - expected - ); - Ok(()) - } - - #[test] - fn validate_is_decodable_complex_types_containing_vector() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - let param_types_containing_vector = vec![ - ParamType::Vector(Box::new(ParamType::U32)), - ParamType::U64, - ParamType::Bool, - ]; - let param_types_no_vector = vec![ParamType::U64, ParamType::U32]; - let nested_heap_type_error_message = |p: ParamType| { - format!( - "Invalid type: type {:?} is not decodable: nested heap types \ - are currently not supported except in Enums.", - DebugWithDepth::new(&p, max_depth) - ) - }; - let cannot_be_decoded = |p: ParamType| { - assert_eq!( - p.validate_is_decodable(max_depth) - .expect_err(&format!("Should not be decodable: {:?}", p)) - .to_string(), - nested_heap_type_error_message(p) - ) - }; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - - can_be_decoded(ParamType::Array(Box::new(ParamType::U64), 10usize)); - cannot_be_decoded(ParamType::Array( - Box::new(ParamType::Vector(Box::new(ParamType::U8))), - 10usize, - )); - - can_be_decoded(ParamType::Vector(Box::new(ParamType::U64))); - cannot_be_decoded(ParamType::Vector(Box::new(ParamType::Vector(Box::new( - ParamType::U8, - ))))); - - can_be_decoded(ParamType::Struct { - fields: param_types_no_vector.clone(), - generics: param_types_no_vector.clone(), - }); - cannot_be_decoded(ParamType::Struct { - generics: param_types_no_vector.clone(), - fields: param_types_containing_vector.clone(), - }); - - can_be_decoded(ParamType::Tuple(param_types_no_vector.clone())); - cannot_be_decoded(ParamType::Tuple(param_types_containing_vector.clone())); - - Ok(()) - } - - #[test] - fn validate_is_decodable_enum_containing_vector() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - let param_types_containing_vector = vec![ - ParamType::Vector(Box::new(ParamType::Bool)), - ParamType::U64, - ParamType::Bool, - ]; - let param_types_no_vector = vec![ParamType::U64, ParamType::U32]; - let variants_no_vector_type = EnumVariants::new(param_types_no_vector.clone())?; - let variants_one_vector_type = EnumVariants::new(param_types_containing_vector.clone())?; - let variants_two_vector_type = EnumVariants::new(vec![ - ParamType::Vector(Box::new(ParamType::U8)), - ParamType::Vector(Box::new(ParamType::U16)), - ])?; - can_be_decoded(ParamType::Enum { - variants: variants_no_vector_type.clone(), - generics: param_types_no_vector.clone(), - }); - can_be_decoded(ParamType::Enum { - variants: variants_one_vector_type.clone(), - generics: param_types_no_vector.clone(), - }); - let expected = "Invalid type: Enums currently support only one heap-type variant. Found: 2" - .to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_vector_type.clone(), - generics: param_types_no_vector.clone(), - } - .validate_is_decodable(max_depth) - .expect_err("Should not be decodable") - .to_string(), - expected - ); - can_be_decoded(ParamType::Enum { - variants: variants_no_vector_type, - generics: param_types_containing_vector.clone(), - }); - can_be_decoded(ParamType::Enum { - variants: variants_one_vector_type, - generics: param_types_containing_vector.clone(), - }); - let expected = "Invalid type: Enums currently support only one heap-type variant. Found: 2" - .to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_vector_type.clone(), - generics: param_types_containing_vector.clone(), - } - .validate_is_decodable(max_depth) - .expect_err("Should not be decodable") - .to_string(), - expected - ); - - Ok(()) - } - #[test] - fn try_vector_is_type_path_backward_compatible() { - // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. - let the_type = given_generic_type_with_path("Vec"); - - let param_type = try_vector(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::Vector(Box::new(ParamType::U8))); - } - - #[test] - fn try_vector_correctly_resolves_param_type() { - let the_type = given_generic_type_with_path("std::vec::Vec"); - - let param_type = try_vector(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::Vector(Box::new(ParamType::U8))); - } - - #[test] - fn try_bytes_is_type_path_backward_compatible() { - // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. - let the_type = given_type_with_path("Bytes"); - - let param_type = try_bytes(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::Bytes); - } - - #[test] - fn try_bytes_correctly_resolves_param_type() { - let the_type = given_type_with_path("std::bytes::Bytes"); - - let param_type = try_bytes(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::Bytes); - } - - #[test] - fn try_raw_slice_correctly_resolves_param_type() { - let the_type = Type { - type_field: "raw untyped slice".to_string(), - generic_params: vec![], - components: vec![], - }; - - let param_type = try_raw_slice(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::RawSlice); - } - - #[test] - fn try_std_string_correctly_resolves_param_type() { - let the_type = given_type_with_path("std::string::String"); - - let param_type = try_std_string(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::String); - } - - #[test] - fn try_std_string_is_type_path_backward_compatible() { - // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. - let the_type = given_type_with_path("String"); - - let param_type = try_std_string(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::String); - } - - #[test] - fn test_compute_encoding_in_bytes_overflows() -> Result<()> { - let overflows = |p: ParamType| { - let error = p.compute_encoding_in_bytes().unwrap_err(); - let overflow_error = error!( - InvalidType, - "Reached overflow while computing encoding size for {:?}", p - ); - assert_eq!(error.to_string(), overflow_error.to_string()); - }; - let tuple_with_fields_too_wide = ParamType::Tuple(vec![ - ParamType::StringArray(12514849900987264429), - ParamType::StringArray(7017071859781709229), - ]); - overflows(tuple_with_fields_too_wide); - - let struct_with_fields_too_wide = ParamType::Struct { - fields: vec![ - ParamType::StringArray(12514849900987264429), - ParamType::StringArray(7017071859781709229), - ], - generics: vec![], - }; - overflows(struct_with_fields_too_wide); - - let enum_with_variants_too_wide = ParamType::Enum { - variants: EnumVariants::new(vec![ParamType::StringArray(usize::MAX - 8)]).unwrap(), - generics: vec![], - }; - overflows(enum_with_variants_too_wide); - - let array_too_big = ParamType::Array(Box::new(ParamType::U64), usize::MAX); - overflows(array_too_big); - - let string_array_too_big = ParamType::StringArray(usize::MAX); - overflows(string_array_too_big); - Ok(()) - } - - #[test] - fn calculate_num_of_elements() -> Result<()> { - let failing_param_type = ParamType::Array(Box::new(ParamType::U16), usize::MAX); - assert!(ParamType::calculate_num_of_elements(&failing_param_type, 0) - .unwrap_err() - .to_string() - .contains("Reached overflow")); - let zero_sized_type = ParamType::Array(Box::new(ParamType::StringArray(0)), 1000); - assert!(ParamType::calculate_num_of_elements(&zero_sized_type, 0) - .unwrap_err() - .to_string() - .contains("the type is zero-sized")); - assert!(ParamType::calculate_num_of_elements(&ParamType::U16, 9) - .unwrap_err() - .to_string() - .contains("1 extra bytes detected while decoding heap type")); - Ok(()) - } - - fn given_type_with_path(path: &str) -> Type { - Type { - type_field: format!("struct {path}"), - generic_params: vec![], - components: vec![], - } - } - - fn given_generic_type_with_path(path: &str) -> Type { - Type { - type_field: format!("struct {path}"), - generic_params: vec![Type { - type_field: "u8".to_string(), - generic_params: vec![], - components: vec![], - }], - components: vec![], - } - } -} +pub use enum_variants::*; +pub use param_type::*; diff --git a/packages/fuels-core/src/types/param_types/debug_with_depth.rs b/packages/fuels-core/src/types/param_types/debug_with_depth.rs new file mode 100644 index 000000000..7eef88ba4 --- /dev/null +++ b/packages/fuels-core/src/types/param_types/debug_with_depth.rs @@ -0,0 +1,256 @@ +use std::fmt; + +use crate::types::param_types::ParamType; + +/// Allows `Debug` formatting of arbitrary-depth nested `ParamTypes` by +/// omitting the details of inner types if max depth is exceeded. +pub(crate) struct DebugWithDepth<'a> { + param_type: &'a ParamType, + depth_left: usize, +} + +impl<'a> DebugWithDepth<'a> { + pub(crate) fn new(param_type: &'a ParamType, depth_left: usize) -> Self { + Self { + param_type, + depth_left, + } + } + + fn descend(&'a self, param_type: &'a ParamType) -> Self { + Self { + param_type, + depth_left: self.depth_left - 1, + } + } +} + +impl<'a> fmt::Debug for DebugWithDepth<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.depth_left == 0 { + return write!(f, "..."); + } + + match &self.param_type { + ParamType::Array(inner, size) => f + .debug_tuple("Array") + .field(&self.descend(inner)) + .field(&size) + .finish(), + ParamType::Struct { + fields, + generics, + name, + } => f + .debug_struct(name) + .field( + "fields", + &fields + .iter() + .map(|(_, field)| self.descend(field)) + .collect::>(), + ) + .field( + "generics", + &generics + .iter() + .map(|generic| self.descend(generic)) + .collect::>(), + ) + .finish(), + ParamType::Enum { + enum_variants, + generics, + name, + } => f + .debug_struct(name) + .field( + "variants", + &enum_variants + .param_types() + .map(|variant| self.descend(variant)) + .collect::>(), + ) + .field( + "generics", + &generics + .iter() + .map(|generic| self.descend(generic)) + .collect::>(), + ) + .finish(), + ParamType::Tuple(inner) => f + .debug_tuple("Tuple") + .field( + &inner + .iter() + .map(|param_type| self.descend(param_type)) + .collect::>(), + ) + .finish(), + ParamType::Vector(inner) => { + f.debug_tuple("Vector").field(&self.descend(inner)).finish() + } + _ => write!(f, "{:?}", self.param_type), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{codec::DecoderConfig, to_named, types::errors::Result}; + + #[test] + fn validate_is_decodable_complex_types_containing_bytes() -> Result<()> { + let param_types_containing_bytes = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; + let param_types_no_bytes = vec![ParamType::U64, ParamType::U32]; + let max_depth = DecoderConfig::default().max_depth; + let nested_heap_type_error_message = |p: ParamType| { + format!( + "codec: type `{:?}` is not decodable: nested heap types are currently not \ + supported except in enums", + DebugWithDepth::new(&p, max_depth) + ) + }; + let cannot_be_decoded = |p: ParamType| { + assert_eq!( + p.validate_is_decodable(max_depth) + .expect_err(&format!("should not be decodable: {:?}", p)) + .to_string(), + nested_heap_type_error_message(p) + ) + }; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + + can_be_decoded(ParamType::Array(Box::new(ParamType::U64), 10usize)); + cannot_be_decoded(ParamType::Array(Box::new(ParamType::Bytes), 10usize)); + + can_be_decoded(ParamType::Vector(Box::new(ParamType::U64))); + cannot_be_decoded(ParamType::Vector(Box::new(ParamType::Bytes))); + + can_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_no_bytes), + generics: param_types_no_bytes.clone(), + }); + cannot_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_containing_bytes), + generics: param_types_no_bytes.clone(), + }); + + can_be_decoded(ParamType::Tuple(param_types_no_bytes.clone())); + cannot_be_decoded(ParamType::Tuple(param_types_containing_bytes.clone())); + + Ok(()) + } + + #[test] + fn validate_is_decodable_complex_types_containing_string() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + let base_string = ParamType::String; + let param_types_no_nested_string = vec![ParamType::U64, ParamType::U32]; + let param_types_nested_string = vec![ParamType::Unit, ParamType::Bool, base_string.clone()]; + let nested_heap_type_error_message = |p: ParamType| { + format!( + "codec: type `{:?}` is not decodable: nested heap types \ + are currently not supported except in enums", + DebugWithDepth::new(&p, max_depth) + ) + }; + let cannot_be_decoded = |p: ParamType| { + assert_eq!( + p.validate_is_decodable(max_depth) + .expect_err(&format!("should not be decodable: {:?}", p)) + .to_string(), + nested_heap_type_error_message(p) + ) + }; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + + can_be_decoded(base_string.clone()); + cannot_be_decoded(ParamType::Vector(Box::from(base_string.clone()))); + + can_be_decoded(ParamType::Array(Box::from(ParamType::U8), 10)); + cannot_be_decoded(ParamType::Array(Box::from(base_string), 10)); + + can_be_decoded(ParamType::Tuple(param_types_no_nested_string.clone())); + cannot_be_decoded(ParamType::Tuple(param_types_nested_string.clone())); + + can_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_no_nested_string), + generics: param_types_no_nested_string.clone(), + }); + + can_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_no_nested_string), + generics: param_types_nested_string.clone(), + }); + + cannot_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_nested_string), + generics: param_types_no_nested_string.clone(), + }); + + Ok(()) + } + + #[test] + fn validate_is_decodable_complex_types_containing_vector() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + let param_types_containing_vector = vec![ + ParamType::Vector(Box::new(ParamType::U32)), + ParamType::U64, + ParamType::Bool, + ]; + let param_types_no_vector = vec![ParamType::U64, ParamType::U32]; + let nested_heap_type_error_message = |p: ParamType| { + format!( + "codec: type `{:?}` is not decodable: nested heap types \ + are currently not supported except in enums", + DebugWithDepth::new(&p, max_depth) + ) + }; + + let cannot_be_decoded = |p: ParamType| { + assert_eq!( + p.validate_is_decodable(max_depth) + .expect_err(&format!("should not be decodable: {:?}", p)) + .to_string(), + nested_heap_type_error_message(p) + ) + }; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + + can_be_decoded(ParamType::Array(Box::new(ParamType::U64), 10usize)); + cannot_be_decoded(ParamType::Array( + Box::new(ParamType::Vector(Box::new(ParamType::U8))), + 10usize, + )); + + can_be_decoded(ParamType::Vector(Box::new(ParamType::U64))); + cannot_be_decoded(ParamType::Vector(Box::new(ParamType::Vector(Box::new( + ParamType::U8, + ))))); + + can_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_no_vector), + generics: param_types_no_vector.clone(), + }); + cannot_be_decoded(ParamType::Struct { + name: "".to_string(), + generics: param_types_no_vector.clone(), + fields: to_named(¶m_types_containing_vector), + }); + + can_be_decoded(ParamType::Tuple(param_types_no_vector.clone())); + cannot_be_decoded(ParamType::Tuple(param_types_containing_vector.clone())); + + Ok(()) + } +} diff --git a/packages/fuels-core/src/types/param_types/enum_variants.rs b/packages/fuels-core/src/types/param_types/enum_variants.rs new file mode 100644 index 000000000..fa82824ce --- /dev/null +++ b/packages/fuels-core/src/types/param_types/enum_variants.rs @@ -0,0 +1,117 @@ +use crate::{ + constants::ENUM_DISCRIMINANT_BYTE_WIDTH, + types::{ + errors::{error, Result}, + param_types::{NamedParamType, ParamType}, + }, + utils::checked_round_up_to_word_alignment, +}; + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct EnumVariants { + variants: Vec, +} + +impl EnumVariants { + pub fn new(variants: Vec) -> Result { + if variants.is_empty() { + return Err(error!(Other, "enum variants cannot be empty!")); + } + + Ok(EnumVariants { variants }) + } + + pub fn variants(&self) -> &Vec { + &self.variants + } + + pub fn param_types(&self) -> impl Iterator { + self.variants.iter().map(|(_, param_type)| param_type) + } + + pub fn select_variant(&self, discriminant: u64) -> Result<&NamedParamType> { + self.variants.get(discriminant as usize).ok_or_else(|| { + error!( + Other, + "discriminant `{discriminant}` doesn't point to any variant: {:?}", + self.variants() + ) + }) + } + + pub fn heap_type_variant(&self) -> Option<(u64, &ParamType)> { + self.param_types() + .enumerate() + .find_map(|(d, p)| p.is_extra_receipt_needed(false).then_some((d as u64, p))) + } + + pub fn only_units_inside(&self) -> bool { + self.variants + .iter() + .all(|(_, param_type)| *param_type == ParamType::Unit) + } + + /// Calculates how many bytes are needed to encode an enum. + pub fn compute_enum_width_in_bytes(&self) -> Result { + if self.only_units_inside() { + return Ok(ENUM_DISCRIMINANT_BYTE_WIDTH); + } + + let width = self.param_types().try_fold(0, |a, p| -> Result<_> { + let size = p.compute_encoding_in_bytes()?; + Ok(a.max(size)) + })?; + + checked_round_up_to_word_alignment(width)? + .checked_add(ENUM_DISCRIMINANT_BYTE_WIDTH) + .ok_or_else(|| error!(Other, "enum variants are too wide")) + } + + /// Determines the padding needed for the provided enum variant (based on the width of the + /// biggest variant) and returns it. + pub fn compute_padding_amount_in_bytes(&self, variant_param_type: &ParamType) -> Result { + let enum_width = self.compute_enum_width_in_bytes()?; + // No need to use checked arithmetics since we called `compute_enum_width_in_bytes` + let biggest_variant_width = enum_width - ENUM_DISCRIMINANT_BYTE_WIDTH; + let variant_width = variant_param_type.compute_encoding_in_bytes()?; + Ok(biggest_variant_width - variant_width) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::to_named; + + #[test] + fn test_get_heap_type_variant_discriminant() -> Result<()> { + { + let variants = to_named(&[ + ParamType::U64, + ParamType::Bool, + ParamType::Vector(Box::from(ParamType::U64)), + ]); + let enum_variants = EnumVariants::new(variants)?; + + assert_eq!(enum_variants.heap_type_variant().unwrap().0, 2); + } + { + let variants = to_named(&[ + ParamType::Vector(Box::from(ParamType::U64)), + ParamType::U64, + ParamType::Bool, + ]); + let enum_variants = EnumVariants::new(variants)?; + + assert_eq!(enum_variants.heap_type_variant().unwrap().0, 0); + } + { + let variants = to_named(&[ParamType::U64, ParamType::Bool]); + let enum_variants = EnumVariants::new(variants)?; + + assert!(enum_variants.heap_type_variant().is_none()); + } + + Ok(()) + } +} diff --git a/packages/fuels-core/src/types/param_types/from_type_application.rs b/packages/fuels-core/src/types/param_types/from_type_application.rs new file mode 100644 index 000000000..96139edc9 --- /dev/null +++ b/packages/fuels-core/src/types/param_types/from_type_application.rs @@ -0,0 +1,1196 @@ +use std::{collections::HashMap, iter::zip}; + +use fuel_abi_types::{ + abi::program::{TypeApplication, TypeDeclaration}, + utils::{extract_array_len, extract_generic_name, extract_str_len, has_tuple_format}, +}; + +use crate::types::{ + errors::{error, Error, Result}, + param_types::{EnumVariants, NamedParamType, ParamType}, +}; + +impl ParamType { + /// For when you need to convert a ABI JSON's TypeApplication into a ParamType. + /// + /// # Arguments + /// + /// * `type_application`: The TypeApplication you wish to convert into a ParamType + /// * `type_lookup`: A HashMap of TypeDeclarations mentioned in the + /// TypeApplication where the type id is the key. + pub fn try_from_type_application( + type_application: &TypeApplication, + type_lookup: &HashMap, + ) -> Result { + Type::try_from(type_application, type_lookup)?.try_into() + } +} + +#[derive(Debug, Clone)] +struct Type { + name: String, + type_field: String, + generic_params: Vec, + components: Vec, +} + +impl Type { + /// Will recursively drill down the given generic parameters until all types are + /// resolved. + /// + /// # Arguments + /// + /// * `type_application`: the type we wish to resolve + /// * `types`: all types used in the function call + pub fn try_from( + type_application: &TypeApplication, + type_lookup: &HashMap, + ) -> Result { + Self::resolve(type_application, type_lookup, &[]) + } + + fn resolve( + type_application: &TypeApplication, + type_lookup: &HashMap, + parent_generic_params: &[(usize, Type)], + ) -> Result { + let type_declaration = type_lookup.get(&type_application.type_id).ok_or_else(|| { + error!( + Codec, + "type id {} not found in type lookup", type_application.type_id + ) + })?; + + if extract_generic_name(&type_declaration.type_field).is_some() { + let (_, generic_type) = parent_generic_params + .iter() + .find(|(id, _)| *id == type_application.type_id) + .ok_or_else(|| { + error!( + Codec, + "type id {} not found in parent's generic parameters", + type_application.type_id + ) + })?; + + // The generic will inherit the name from the parent `type_application` + return Ok(Self { + name: type_application.name.clone(), + ..generic_type.clone() + }); + } + + // Figure out what does the current type do with the inherited generic + // parameters and reestablish the mapping since the current type might have + // renamed the inherited generic parameters. + let generic_params_lookup = Self::determine_generics_for_type( + type_application, + type_lookup, + type_declaration, + parent_generic_params, + )?; + + // Resolve the enclosed components (if any) with the newly resolved generic + // parameters. + let components = type_declaration + .components + .iter() + .flatten() + .map(|component| Self::resolve(component, type_lookup, &generic_params_lookup)) + .collect::>>()?; + + Ok(Type { + name: type_application.name.clone(), + type_field: type_declaration.type_field.clone(), + components, + generic_params: generic_params_lookup + .into_iter() + .map(|(_, ty)| ty) + .collect(), + }) + } + + /// For the given type generates generic_type_id -> Type mapping describing to + /// which types generic parameters should be resolved. + /// + /// # Arguments + /// + /// * `type_application`: The type on which the generic parameters are defined. + /// * `types`: All types used. + /// * `parent_generic_params`: The generic parameters as inherited from the + /// enclosing type (a struct/enum/array etc.). + fn determine_generics_for_type( + type_application: &TypeApplication, + type_lookup: &HashMap, + type_declaration: &TypeDeclaration, + parent_generic_params: &[(usize, Type)], + ) -> Result> { + match &type_declaration.type_parameters { + // The presence of type_parameters indicates that the current type + // (a struct or an enum) defines some generic parameters (i.e. SomeStruct). + Some(params) if !params.is_empty() => { + // Determine what Types the generics will resolve to. + let generic_params_from_current_type = type_application + .type_arguments + .iter() + .flatten() + .map(|ty| Self::resolve(ty, type_lookup, parent_generic_params)) + .collect::>>()?; + + let generics_to_use = if !generic_params_from_current_type.is_empty() { + generic_params_from_current_type + } else { + // Types such as arrays and enums inherit and forward their + // generic parameters, without declaring their own. + parent_generic_params + .iter() + .map(|(_, ty)| ty) + .cloned() + .collect() + }; + + // All inherited but unused generic types are dropped. The rest are + // re-mapped to new type_ids since child types are free to rename + // the generic parameters as they see fit -- i.e. + // struct ParentStruct{ + // b: ChildStruct + // } + // struct ChildStruct { + // c: K + // } + + Ok(zip(params.clone(), generics_to_use).collect()) + } + _ => Ok(parent_generic_params.to_vec()), + } + } +} + +impl TryFrom for ParamType { + type Error = Error; + + fn try_from(value: Type) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&Type> for ParamType { + type Error = Error; + + fn try_from(the_type: &Type) -> Result { + let matched_param_type = [ + try_primitive, + try_array, + try_str_array, + try_str_slice, + try_tuple, + try_vector, + try_bytes, + try_std_string, + try_raw_slice, + try_enum, + try_u128, + try_struct, + ] + .into_iter() + .map(|fun| fun(the_type)) + .flat_map(|result| result.ok().flatten()) + .next(); + + matched_param_type.map(Ok).unwrap_or_else(|| { + Err(error!( + Codec, + "type {} couldn't be converted into a ParamType", the_type.type_field + )) + }) + } +} + +fn convert_into_param_types(coll: &[Type]) -> Result> { + coll.iter().map(ParamType::try_from).collect() +} + +fn named_param_types(coll: &[Type]) -> Result> { + coll.iter() + .map(|ttype| Ok((ttype.name.clone(), ttype.try_into()?))) + .collect() +} + +fn try_struct(the_type: &Type) -> Result> { + let field = &the_type.type_field; + if field.starts_with("struct ") { + let fields = named_param_types(&the_type.components)?; + let generics = param_types(&the_type.generic_params)?; + + return Ok(Some(ParamType::Struct { + name: the_type + .type_field + .strip_prefix("struct ") + .expect("has `struct`") + .to_string(), + fields, + generics, + })); + } + + Ok(None) +} + +fn try_vector(the_type: &Type) -> Result> { + if !["struct std::vec::Vec", "struct Vec"].contains(&the_type.type_field.as_str()) { + return Ok(None); + } + + if the_type.generic_params.len() != 1 { + return Err(error!( + Codec, + "`Vec` must have exactly one generic argument for its type. Found: `{:?}`", + the_type.generic_params + )); + } + + let vec_elem_type = convert_into_param_types(&the_type.generic_params)?.remove(0); + + Ok(Some(ParamType::Vector(Box::new(vec_elem_type)))) +} + +fn try_u128(the_type: &Type) -> Result> { + Ok(["struct std::u128::U128", "struct U128"] + .contains(&the_type.type_field.as_str()) + .then_some(ParamType::U128)) +} + +fn try_bytes(the_type: &Type) -> Result> { + Ok(["struct std::bytes::Bytes", "struct Bytes"] + .contains(&the_type.type_field.as_str()) + .then_some(ParamType::Bytes)) +} + +fn try_std_string(the_type: &Type) -> Result> { + Ok(["struct std::string::String", "struct String"] + .contains(&the_type.type_field.as_str()) + .then_some(ParamType::String)) +} + +fn try_raw_slice(the_type: &Type) -> Result> { + Ok((the_type.type_field == "raw untyped slice").then_some(ParamType::RawSlice)) +} + +fn try_enum(the_type: &Type) -> Result> { + let field = &the_type.type_field; + if field.starts_with("enum ") { + let components = named_param_types(&the_type.components)?; + let enum_variants = EnumVariants::new(components)?; + let generics = param_types(&the_type.generic_params)?; + + return Ok(Some(ParamType::Enum { + name: field.strip_prefix("enum ").expect("has `enum`").to_string(), + enum_variants, + generics, + })); + } + + Ok(None) +} + +fn try_tuple(the_type: &Type) -> Result> { + let result = if has_tuple_format(&the_type.type_field) { + let tuple_elements = param_types(&the_type.components)?; + Some(ParamType::Tuple(tuple_elements)) + } else { + None + }; + + Ok(result) +} + +fn param_types(coll: &[Type]) -> Result> { + coll.iter().map(|t| t.try_into()).collect() +} + +fn try_str_array(the_type: &Type) -> Result> { + Ok(extract_str_len(&the_type.type_field).map(ParamType::StringArray)) +} + +fn try_str_slice(the_type: &Type) -> Result> { + Ok(if the_type.type_field == "str" { + Some(ParamType::StringSlice) + } else { + None + }) +} + +fn try_array(the_type: &Type) -> Result> { + if let Some(len) = extract_array_len(&the_type.type_field) { + return match the_type.components.as_slice() { + [single_type] => { + let array_type = single_type.try_into()?; + Ok(Some(ParamType::Array(Box::new(array_type), len))) + } + _ => Err(error!( + Codec, + "array must have elements of exactly one type. Array types: {:?}", + the_type.components + )), + }; + } + Ok(None) +} + +fn try_primitive(the_type: &Type) -> Result> { + let result = match the_type.type_field.as_str() { + "bool" => Some(ParamType::Bool), + "u8" => Some(ParamType::U8), + "u16" => Some(ParamType::U16), + "u32" => Some(ParamType::U32), + "u64" => Some(ParamType::U64), + "u256" => Some(ParamType::U256), + "b256" => Some(ParamType::B256), + "()" => Some(ParamType::Unit), + "str" => Some(ParamType::StringSlice), + _ => None, + }; + + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn handles_simple_types() -> Result<()> { + let parse_param_type = |type_field: &str| { + let type_application = TypeApplication { + name: "".to_string(), + type_id: 0, + type_arguments: None, + }; + + let declarations = [TypeDeclaration { + type_id: 0, + type_field: type_field.to_string(), + components: None, + type_parameters: None, + }]; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + ParamType::try_from_type_application(&type_application, &type_lookup) + }; + + assert_eq!(parse_param_type("()")?, ParamType::Unit); + assert_eq!(parse_param_type("bool")?, ParamType::Bool); + assert_eq!(parse_param_type("u8")?, ParamType::U8); + assert_eq!(parse_param_type("u16")?, ParamType::U16); + assert_eq!(parse_param_type("u32")?, ParamType::U32); + assert_eq!(parse_param_type("u64")?, ParamType::U64); + assert_eq!(parse_param_type("u256")?, ParamType::U256); + assert_eq!(parse_param_type("b256")?, ParamType::B256); + assert_eq!(parse_param_type("str[21]")?, ParamType::StringArray(21)); + assert_eq!(parse_param_type("str")?, ParamType::StringSlice); + + Ok(()) + } + + #[test] + fn handles_arrays() -> Result<()> { + // given + let type_application = TypeApplication { + name: "".to_string(), + type_id: 0, + type_arguments: None, + }; + + let declarations = [ + TypeDeclaration { + type_id: 0, + type_field: "[_; 10]".to_string(), + components: Some(vec![TypeApplication { + name: "__array_element".to_string(), + type_id: 1, + type_arguments: None, + }]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 1, + type_field: "u8".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + assert_eq!(result, ParamType::Array(Box::new(ParamType::U8), 10)); + + Ok(()) + } + + #[test] + fn handles_vectors() -> Result<()> { + // given + let declarations = [ + TypeDeclaration { + type_id: 1, + type_field: "generic T".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 2, + type_field: "raw untyped ptr".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 3, + type_field: "struct std::vec::RawVec".to_string(), + components: Some(vec![ + TypeApplication { + name: "ptr".to_string(), + type_id: 2, + type_arguments: None, + }, + TypeApplication { + name: "cap".to_string(), + type_id: 5, + type_arguments: None, + }, + ]), + type_parameters: Some(vec![1]), + }, + TypeDeclaration { + type_id: 4, + type_field: "struct std::vec::Vec".to_string(), + components: Some(vec![ + TypeApplication { + name: "buf".to_string(), + type_id: 3, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 1, + type_arguments: None, + }]), + }, + TypeApplication { + name: "len".to_string(), + type_id: 5, + type_arguments: None, + }, + ]), + type_parameters: Some(vec![1]), + }, + TypeDeclaration { + type_id: 5, + type_field: "u64".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 6, + type_field: "u8".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_application = TypeApplication { + name: "arg".to_string(), + type_id: 4, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 6, + type_arguments: None, + }]), + }; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + assert_eq!(result, ParamType::Vector(Box::new(ParamType::U8))); + + Ok(()) + } + + #[test] + fn handles_structs() -> Result<()> { + // given + let declarations = [ + TypeDeclaration { + type_id: 1, + type_field: "generic T".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 2, + type_field: "struct SomeStruct".to_string(), + components: Some(vec![TypeApplication { + name: "field".to_string(), + type_id: 1, + type_arguments: None, + }]), + type_parameters: Some(vec![1]), + }, + TypeDeclaration { + type_id: 3, + type_field: "u8".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_application = TypeApplication { + name: "arg".to_string(), + type_id: 2, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 3, + type_arguments: None, + }]), + }; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + assert_eq!( + result, + ParamType::Struct { + name: "SomeStruct".to_string(), + fields: vec![("field".to_string(), ParamType::U8)], + generics: vec![ParamType::U8] + } + ); + + Ok(()) + } + + #[test] + fn handles_enums() -> Result<()> { + // given + let declarations = [ + TypeDeclaration { + type_id: 1, + type_field: "generic T".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 2, + type_field: "enum SomeEnum".to_string(), + components: Some(vec![TypeApplication { + name: "Variant".to_string(), + type_id: 1, + type_arguments: None, + }]), + type_parameters: Some(vec![1]), + }, + TypeDeclaration { + type_id: 3, + type_field: "u8".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_application = TypeApplication { + name: "arg".to_string(), + type_id: 2, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 3, + type_arguments: None, + }]), + }; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + assert_eq!( + result, + ParamType::Enum { + name: "SomeEnum".to_string(), + enum_variants: EnumVariants::new(vec![("Variant".to_string(), ParamType::U8)])?, + generics: vec![ParamType::U8] + } + ); + + Ok(()) + } + + #[test] + fn handles_tuples() -> Result<()> { + // given + let declarations = [ + TypeDeclaration { + type_id: 1, + type_field: "(_, _)".to_string(), + components: Some(vec![ + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 3, + type_arguments: None, + }, + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 2, + type_arguments: None, + }, + ]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 2, + type_field: "str[15]".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 3, + type_field: "u8".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_application = TypeApplication { + name: "arg".to_string(), + type_id: 1, + type_arguments: None, + }; + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + assert_eq!( + result, + ParamType::Tuple(vec![ParamType::U8, ParamType::StringArray(15)]) + ); + + Ok(()) + } + + #[test] + fn ultimate_example() -> Result<()> { + // given + let declarations = [ + TypeDeclaration { + type_id: 1, + type_field: "(_, _)".to_string(), + components: Some(vec![ + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 11, + type_arguments: None, + }, + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 11, + type_arguments: None, + }, + ]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 2, + type_field: "(_, _)".to_string(), + components: Some(vec![ + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 4, + type_arguments: None, + }, + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 24, + type_arguments: None, + }, + ]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 3, + type_field: "(_, _)".to_string(), + components: Some(vec![ + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 5, + type_arguments: None, + }, + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 13, + type_arguments: None, + }, + ]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 4, + type_field: "[_; 1]".to_string(), + components: Some(vec![TypeApplication { + name: "__array_element".to_string(), + type_id: 8, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 22, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 21, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 18, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 13, + type_arguments: None, + }]), + }]), + }]), + }]), + }]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 5, + type_field: "[_; 2]".to_string(), + components: Some(vec![TypeApplication { + name: "__array_element".to_string(), + type_id: 14, + type_arguments: None, + }]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 6, + type_field: "[_; 2]".to_string(), + components: Some(vec![TypeApplication { + name: "__array_element".to_string(), + type_id: 10, + type_arguments: None, + }]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 7, + type_field: "b256".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 8, + type_field: "enum EnumWGeneric".to_string(), + components: Some(vec![ + TypeApplication { + name: "A".to_string(), + type_id: 25, + type_arguments: None, + }, + TypeApplication { + name: "B".to_string(), + type_id: 12, + type_arguments: None, + }, + ]), + type_parameters: Some(vec![12]), + }, + TypeDeclaration { + type_id: 9, + type_field: "generic K".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 10, + type_field: "generic L".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 11, + type_field: "generic M".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 12, + type_field: "generic N".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 13, + type_field: "generic T".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 14, + type_field: "generic U".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 15, + type_field: "raw untyped ptr".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 16, + type_field: "str[2]".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 17, + type_field: "struct MegaExample".to_string(), + components: Some(vec![ + TypeApplication { + name: "a".to_string(), + type_id: 3, + type_arguments: None, + }, + TypeApplication { + name: "b".to_string(), + type_id: 23, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 2, + type_arguments: None, + }]), + }, + ]), + type_parameters: Some(vec![13, 14]), + }, + TypeDeclaration { + type_id: 18, + type_field: "struct PassTheGenericOn".to_string(), + components: Some(vec![TypeApplication { + name: "one".to_string(), + type_id: 20, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 9, + type_arguments: None, + }]), + }]), + type_parameters: Some(vec![9]), + }, + TypeDeclaration { + type_id: 19, + type_field: "struct std::vec::RawVec".to_string(), + components: Some(vec![ + TypeApplication { + name: "ptr".to_string(), + type_id: 15, + type_arguments: None, + }, + TypeApplication { + name: "cap".to_string(), + type_id: 25, + type_arguments: None, + }, + ]), + type_parameters: Some(vec![13]), + }, + TypeDeclaration { + type_id: 20, + type_field: "struct SimpleGeneric".to_string(), + components: Some(vec![TypeApplication { + name: "single_generic_param".to_string(), + type_id: 13, + type_arguments: None, + }]), + type_parameters: Some(vec![13]), + }, + TypeDeclaration { + type_id: 21, + type_field: "struct StructWArrayGeneric".to_string(), + components: Some(vec![TypeApplication { + name: "a".to_string(), + type_id: 6, + type_arguments: None, + }]), + type_parameters: Some(vec![10]), + }, + TypeDeclaration { + type_id: 22, + type_field: "struct StructWTupleGeneric".to_string(), + components: Some(vec![TypeApplication { + name: "a".to_string(), + type_id: 1, + type_arguments: None, + }]), + type_parameters: Some(vec![11]), + }, + TypeDeclaration { + type_id: 23, + type_field: "struct std::vec::Vec".to_string(), + components: Some(vec![ + TypeApplication { + name: "buf".to_string(), + type_id: 19, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 13, + type_arguments: None, + }]), + }, + TypeApplication { + name: "len".to_string(), + type_id: 25, + type_arguments: None, + }, + ]), + type_parameters: Some(vec![13]), + }, + TypeDeclaration { + type_id: 24, + type_field: "u32".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 25, + type_field: "u64".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + let type_application = TypeApplication { + name: "arg1".to_string(), + type_id: 17, + type_arguments: Some(vec![ + TypeApplication { + name: "".to_string(), + type_id: 16, + type_arguments: None, + }, + TypeApplication { + name: "".to_string(), + type_id: 7, + type_arguments: None, + }, + ]), + }; + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + let expected_param_type = { + let fields = vec![( + "one".to_string(), + ParamType::Struct { + name: "SimpleGeneric".to_string(), + fields: vec![( + "single_generic_param".to_string(), + ParamType::StringArray(2), + )], + generics: vec![ParamType::StringArray(2)], + }, + )]; + let pass_the_generic_on = ParamType::Struct { + name: "PassTheGenericOn".to_string(), + fields, + generics: vec![ParamType::StringArray(2)], + }; + + let fields = vec![( + "a".to_string(), + ParamType::Array(Box::from(pass_the_generic_on.clone()), 2), + )]; + let struct_w_array_generic = ParamType::Struct { + name: "StructWArrayGeneric".to_string(), + fields, + generics: vec![pass_the_generic_on], + }; + + let fields = vec![( + "a".to_string(), + ParamType::Tuple(vec![ + struct_w_array_generic.clone(), + struct_w_array_generic.clone(), + ]), + )]; + let struct_w_tuple_generic = ParamType::Struct { + name: "StructWTupleGeneric".to_string(), + fields, + generics: vec![struct_w_array_generic], + }; + + let types = vec![ + ("A".to_string(), ParamType::U64), + ("B".to_string(), struct_w_tuple_generic.clone()), + ]; + let fields = vec![ + ( + "a".to_string(), + ParamType::Tuple(vec![ + ParamType::Array(Box::from(ParamType::B256), 2), + ParamType::StringArray(2), + ]), + ), + ( + "b".to_string(), + ParamType::Vector(Box::from(ParamType::Tuple(vec![ + ParamType::Array( + Box::from(ParamType::Enum { + name: "EnumWGeneric".to_string(), + enum_variants: EnumVariants::new(types).unwrap(), + generics: vec![struct_w_tuple_generic], + }), + 1, + ), + ParamType::U32, + ]))), + ), + ]; + ParamType::Struct { + name: "MegaExample".to_string(), + fields, + generics: vec![ParamType::StringArray(2), ParamType::B256], + } + }; + + assert_eq!(result, expected_param_type); + + Ok(()) + } + + #[test] + fn try_vector_is_type_path_backward_compatible() { + // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. + let the_type = given_generic_type_with_path("Vec"); + + let param_type = try_vector(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::Vector(Box::new(ParamType::U8))); + } + + #[test] + fn try_vector_correctly_resolves_param_type() { + let the_type = given_generic_type_with_path("std::vec::Vec"); + + let param_type = try_vector(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::Vector(Box::new(ParamType::U8))); + } + + #[test] + fn try_bytes_is_type_path_backward_compatible() { + // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. + let the_type = given_type_with_path("Bytes"); + + let param_type = try_bytes(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::Bytes); + } + + #[test] + fn try_bytes_correctly_resolves_param_type() { + let the_type = given_type_with_path("std::bytes::Bytes"); + + let param_type = try_bytes(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::Bytes); + } + + #[test] + fn try_raw_slice_correctly_resolves_param_type() { + let the_type = Type { + name: "".to_string(), + type_field: "raw untyped slice".to_string(), + generic_params: vec![], + components: vec![], + }; + + let param_type = try_raw_slice(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::RawSlice); + } + + #[test] + fn try_std_string_correctly_resolves_param_type() { + let the_type = given_type_with_path("std::string::String"); + + let param_type = try_std_string(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::String); + } + + #[test] + fn try_std_string_is_type_path_backward_compatible() { + // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. + let the_type = given_type_with_path("String"); + + let param_type = try_std_string(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::String); + } + + fn given_type_with_path(path: &str) -> Type { + Type { + name: "".to_string(), + type_field: format!("struct {path}"), + generic_params: vec![], + components: vec![], + } + } + + fn given_generic_type_with_path(path: &str) -> Type { + Type { + name: "".to_string(), + type_field: format!("struct {path}"), + generic_params: vec![Type { + name: "".to_string(), + type_field: "u8".to_string(), + generic_params: vec![], + components: vec![], + }], + components: vec![], + } + } +} diff --git a/packages/fuels-core/src/types/param_types/param_type.rs b/packages/fuels-core/src/types/param_types/param_type.rs new file mode 100644 index 000000000..9d7a2238a --- /dev/null +++ b/packages/fuels-core/src/types/param_types/param_type.rs @@ -0,0 +1,608 @@ +use itertools::chain; + +use crate::{ + checked_round_up_to_word_alignment, + types::{ + errors::{error, Result}, + param_types::{debug_with_depth::DebugWithDepth, EnumVariants}, + }, +}; + +pub type NamedParamType = (String, ParamType); + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum ParamType { + Unit, + Bool, + U8, + U16, + U32, + U64, + U128, + U256, + B256, + Bytes, + String, + RawSlice, + StringArray(usize), + StringSlice, + Tuple(Vec), + Array(Box, usize), + Vector(Box), + Struct { + name: String, + fields: Vec, + generics: Vec, + }, + Enum { + name: String, + enum_variants: EnumVariants, + generics: Vec, + }, +} + +pub enum ReturnLocation { + Return, + ReturnData, +} + +impl ParamType { + // Depending on the type, the returned value will be stored + // either in `Return` or `ReturnData`. + pub fn get_return_location(&self) -> ReturnLocation { + match self { + Self::Unit | Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::Bool => { + ReturnLocation::Return + } + + _ => ReturnLocation::ReturnData, + } + } + + /// Given a [ParamType], return the number of elements of that [ParamType] that can fit in + /// `available_bytes`: it is the length of the corresponding heap type. + pub fn calculate_num_of_elements( + param_type: &ParamType, + available_bytes: usize, + ) -> Result { + let memory_size = param_type.compute_encoding_in_bytes()?; + if memory_size == 0 { + return Err(error!( + Codec, + "cannot calculate the number of elements because the type is zero-sized" + )); + } + + let remainder = available_bytes % memory_size; + if remainder != 0 { + return Err(error!( + Codec, + "{remainder} extra bytes detected while decoding heap type" + )); + } + let num_of_elements = available_bytes + .checked_div(memory_size) + .ok_or_else(|| error!(Codec, "type {param_type:?} has a memory_size of 0"))?; + + Ok(num_of_elements) + } + + pub fn children_need_extra_receipts(&self) -> bool { + match self { + ParamType::Array(inner, _) | ParamType::Vector(inner) => { + inner.is_extra_receipt_needed(false) + } + ParamType::Struct { fields, .. } => fields + .iter() + .any(|(_, param_type)| param_type.is_extra_receipt_needed(false)), + ParamType::Enum { enum_variants, .. } => enum_variants + .param_types() + .any(|param_type| param_type.is_extra_receipt_needed(false)), + ParamType::Tuple(inner_types) => inner_types + .iter() + .any(|param_type| param_type.is_extra_receipt_needed(false)), + _ => false, + } + } + + pub fn validate_is_decodable(&self, max_depth: usize) -> Result<()> { + if let ParamType::Enum { enum_variants, .. } = self { + let grandchildren_need_receipts = enum_variants + .param_types() + .any(|child| child.children_need_extra_receipts()); + if grandchildren_need_receipts { + return Err(error!( + Codec, + "enums currently support only one level deep heap types" + )); + } + + let num_of_children_needing_receipts = enum_variants + .param_types() + .filter(|param_type| param_type.is_extra_receipt_needed(false)) + .count(); + if num_of_children_needing_receipts > 1 { + return Err(error!( + Codec, + "enums currently support only one heap-type variant. Found: \ + {num_of_children_needing_receipts}" + )); + } + } else if self.children_need_extra_receipts() { + return Err(error!( + Codec, + "type `{:?}` is not decodable: nested heap types are currently not \ + supported except in enums", + DebugWithDepth::new(self, max_depth) + )); + } + self.compute_encoding_in_bytes()?; + + Ok(()) + } + + pub fn is_extra_receipt_needed(&self, top_level_type: bool) -> bool { + match self { + ParamType::Vector(_) | ParamType::Bytes | ParamType::String => true, + ParamType::Array(inner, _) => inner.is_extra_receipt_needed(false), + ParamType::Struct { + fields, generics, .. + } => chain!(fields.iter().map(|(_, param_type)| param_type), generics,) + .any(|param_type| param_type.is_extra_receipt_needed(false)), + ParamType::Enum { + enum_variants, + generics, + .. + } => chain!(enum_variants.param_types(), generics) + .any(|param_type| param_type.is_extra_receipt_needed(false)), + ParamType::Tuple(elements) => elements + .iter() + .any(|param_type| param_type.is_extra_receipt_needed(false)), + ParamType::RawSlice | ParamType::StringSlice => !top_level_type, + _ => false, + } + } + + /// Compute the inner memory size of a containing heap type (`Bytes` or `Vec`s). + pub fn heap_inner_element_size(&self, top_level_type: bool) -> Result> { + let heap_bytes_size = match &self { + ParamType::Vector(inner_param_type) => { + Some(inner_param_type.compute_encoding_in_bytes()?) + } + // `Bytes` type is byte-packed in the VM, so it's the size of an u8 + ParamType::Bytes | ParamType::String => Some(std::mem::size_of::()), + ParamType::StringSlice if !top_level_type => { + Some(ParamType::U8.compute_encoding_in_bytes()?) + } + ParamType::RawSlice if !top_level_type => { + Some(ParamType::U64.compute_encoding_in_bytes()?) + } + _ => None, + }; + Ok(heap_bytes_size) + } + + /// Calculates the number of bytes the VM expects this parameter to be encoded in. + pub fn compute_encoding_in_bytes(&self) -> Result { + let overflow_error = || { + error!( + Codec, + "reached overflow while computing encoding size for {:?}", self + ) + }; + match &self { + ParamType::Unit | ParamType::U8 | ParamType::Bool => Ok(1), + ParamType::U16 | ParamType::U32 | ParamType::U64 => Ok(8), + ParamType::U128 | ParamType::RawSlice | ParamType::StringSlice => Ok(16), + ParamType::U256 | ParamType::B256 => Ok(32), + ParamType::Vector(_) | ParamType::Bytes | ParamType::String => Ok(24), + ParamType::Array(param, count) => param + .compute_encoding_in_bytes()? + .checked_mul(*count) + .ok_or_else(overflow_error), + ParamType::StringArray(len) => { + checked_round_up_to_word_alignment(*len).map_err(|_| overflow_error()) + } + ParamType::Tuple(fields) => fields.iter().try_fold(0, |a: usize, param_type| { + let size = + checked_round_up_to_word_alignment(param_type.compute_encoding_in_bytes()?)?; + a.checked_add(size).ok_or_else(overflow_error) + }), + ParamType::Struct { fields, .. } => fields + .iter() + .map(|(_, param_type)| param_type) + .try_fold(0, |a: usize, param_type| { + let size = checked_round_up_to_word_alignment( + param_type.compute_encoding_in_bytes()?, + )?; + a.checked_add(size).ok_or_else(overflow_error) + }), + ParamType::Enum { enum_variants, .. } => enum_variants + .compute_enum_width_in_bytes() + .map_err(|_| overflow_error()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + checked_round_up_to_word_alignment, codec::DecoderConfig, constants::WORD_SIZE, to_named, + types::param_types::ParamType, + }; + + const WIDTH_OF_B256: usize = 32; + const WIDTH_OF_U32: usize = 8; + const WIDTH_OF_BOOL: usize = 1; + + #[test] + fn calculate_num_of_elements() -> Result<()> { + let failing_param_type = ParamType::Array(Box::new(ParamType::U16), usize::MAX); + assert!(ParamType::calculate_num_of_elements(&failing_param_type, 0) + .unwrap_err() + .to_string() + .contains("reached overflow")); + + let zero_sized_type = ParamType::Array(Box::new(ParamType::StringArray(0)), 1000); + assert!(ParamType::calculate_num_of_elements(&zero_sized_type, 0) + .unwrap_err() + .to_string() + .contains("the type is zero-sized")); + + assert!(ParamType::calculate_num_of_elements(&ParamType::U16, 9) + .unwrap_err() + .to_string() + .contains("1 extra bytes detected while decoding heap type")); + + Ok(()) + } + + #[test] + fn array_size_dependent_on_num_of_elements() { + const NUM_ELEMENTS: usize = 11; + let param = ParamType::Array(Box::new(ParamType::B256), NUM_ELEMENTS); + + let width = param.compute_encoding_in_bytes().unwrap(); + + let expected = NUM_ELEMENTS * WIDTH_OF_B256; + assert_eq!(expected, width); + } + + #[test] + fn string_size_dependent_on_num_of_elements() { + const NUM_ASCII_CHARS: usize = 9; + let param = ParamType::StringArray(NUM_ASCII_CHARS); + + let width = param.compute_encoding_in_bytes().unwrap(); + + assert_eq!(16, width); + } + + #[test] + fn structs_are_all_elements_combined_with_padding() -> Result<()> { + let inner_struct = ParamType::Struct { + name: "".to_string(), + fields: to_named(&[ParamType::U32, ParamType::U32]), + generics: vec![], + }; + + let a_struct = ParamType::Struct { + name: "".to_string(), + fields: to_named(&[ParamType::B256, ParamType::Bool, inner_struct]), + generics: vec![], + }; + + let width = a_struct.compute_encoding_in_bytes().unwrap(); + + const INNER_STRUCT_WIDTH: usize = WIDTH_OF_U32 * 2; + let expected_width: usize = + WIDTH_OF_B256 + checked_round_up_to_word_alignment(WIDTH_OF_BOOL)? + INNER_STRUCT_WIDTH; + assert_eq!(expected_width, width); + Ok(()) + } + + #[test] + fn enums_are_as_big_as_their_biggest_variant_plus_a_word() -> Result<()> { + let fields = to_named(&[ParamType::B256]); + let inner_struct = ParamType::Struct { + name: "".to_string(), + fields, + generics: vec![], + }; + let types = to_named(&[ParamType::U32, inner_struct]); + let param = ParamType::Enum { + name: "".to_string(), + enum_variants: EnumVariants::new(types)?, + generics: vec![], + }; + + let width = param.compute_encoding_in_bytes().unwrap(); + + const INNER_STRUCT_SIZE: usize = WIDTH_OF_B256; + const EXPECTED_WIDTH: usize = INNER_STRUCT_SIZE + WORD_SIZE; + assert_eq!(EXPECTED_WIDTH, width); + Ok(()) + } + + #[test] + fn tuples_are_just_all_elements_combined() { + let inner_tuple = ParamType::Tuple(vec![ParamType::B256]); + let param = ParamType::Tuple(vec![ParamType::U32, inner_tuple]); + + let width = param.compute_encoding_in_bytes().unwrap(); + + const INNER_TUPLE_WIDTH: usize = WIDTH_OF_B256; + const EXPECTED_WIDTH: usize = WIDTH_OF_U32 + INNER_TUPLE_WIDTH; + assert_eq!(EXPECTED_WIDTH, width); + } + + #[test] + fn test_compute_encoding_in_bytes_overflows() -> Result<()> { + let overflows = |p: ParamType| { + let error = p.compute_encoding_in_bytes().unwrap_err(); + let overflow_error = error!( + Codec, + "reached overflow while computing encoding size for {:?}", p + ); + assert_eq!(error.to_string(), overflow_error.to_string()); + }; + let tuple_with_fields_too_wide = ParamType::Tuple(vec![ + ParamType::StringArray(12514849900987264429), + ParamType::StringArray(7017071859781709229), + ]); + overflows(tuple_with_fields_too_wide); + + let struct_with_fields_too_wide = ParamType::Struct { + name: "".to_string(), + fields: to_named(&[ + ParamType::StringArray(12514849900987264429), + ParamType::StringArray(7017071859781709229), + ]), + generics: vec![], + }; + overflows(struct_with_fields_too_wide); + + let enum_with_variants_too_wide = ParamType::Enum { + name: "".to_string(), + enum_variants: EnumVariants::new(to_named(&[ParamType::StringArray(usize::MAX - 8)]))?, + generics: vec![], + }; + overflows(enum_with_variants_too_wide); + + let array_too_big = ParamType::Array(Box::new(ParamType::U64), usize::MAX); + overflows(array_too_big); + + let string_array_too_big = ParamType::StringArray(usize::MAX); + overflows(string_array_too_big); + Ok(()) + } + + #[test] + fn validate_is_decodable_simple_types() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + assert!(ParamType::U8.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::U16.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::U32.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::U64.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::U128.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::U256.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::Bool.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::B256.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::Unit.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::StringSlice + .validate_is_decodable(max_depth) + .is_ok()); + assert!(ParamType::StringArray(10) + .validate_is_decodable(max_depth) + .is_ok()); + assert!(ParamType::RawSlice.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::Bytes.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::String.validate_is_decodable(max_depth).is_ok()); + Ok(()) + } + + #[test] + fn validate_is_decodable_enum_containing_bytes() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + let param_types_containing_bytes = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; + let param_types_no_bytes = vec![ParamType::U64, ParamType::U32]; + let variants_no_bytes_type = EnumVariants::new(to_named(¶m_types_no_bytes))?; + let variants_one_bytes_type = EnumVariants::new(to_named(¶m_types_containing_bytes))?; + let variants_two_bytes_type = + EnumVariants::new(to_named(&[ParamType::Bytes, ParamType::Bytes]))?; + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_bytes_type.clone(), + generics: param_types_no_bytes.clone(), + }); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_bytes_type.clone(), + generics: param_types_no_bytes.clone(), + }); + + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_bytes_type.clone(), + generics: param_types_no_bytes.clone(), + } + .validate_is_decodable(max_depth) + .expect_err("should not be decodable") + .to_string(), + expected + ); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_bytes_type, + generics: param_types_containing_bytes.clone(), + }); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_bytes_type, + generics: param_types_containing_bytes.clone(), + }); + + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_bytes_type.clone(), + generics: param_types_containing_bytes.clone(), + } + .validate_is_decodable(max_depth) + .expect_err("should not be decodable") + .to_string(), + expected + ); + + Ok(()) + } + + #[test] + fn validate_is_decodable_enum_containing_string() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + let param_types_containing_string = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; + let param_types_no_string = vec![ParamType::U64, ParamType::U32]; + let variants_no_string_type = EnumVariants::new(to_named(¶m_types_no_string))?; + let variants_one_string_type = EnumVariants::new(to_named(¶m_types_containing_string))?; + let variants_two_string_type = + EnumVariants::new(to_named(&[ParamType::Bytes, ParamType::Bytes]))?; + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_string_type.clone(), + generics: param_types_no_string.clone(), + }); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_string_type.clone(), + generics: param_types_no_string.clone(), + }); + + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_string_type.clone(), + generics: param_types_no_string.clone(), + } + .validate_is_decodable(1) + .expect_err("should not be decodable") + .to_string(), + expected + ); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_string_type, + generics: param_types_containing_string.clone(), + }); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_string_type, + generics: param_types_containing_string.clone(), + }); + + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_string_type.clone(), + generics: param_types_containing_string.clone(), + } + .validate_is_decodable(1) + .expect_err("should not be decodable") + .to_string(), + expected + ); + + Ok(()) + } + + #[test] + fn validate_is_decodable_enum_containing_vector() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + let param_types_containing_vector = vec![ + ParamType::Vector(Box::new(ParamType::Bool)), + ParamType::U64, + ParamType::Bool, + ]; + let param_types_no_vector = vec![ParamType::U64, ParamType::U32]; + let variants_no_vector_type = EnumVariants::new(to_named(¶m_types_no_vector))?; + let variants_one_vector_type = EnumVariants::new(to_named(¶m_types_containing_vector))?; + let variants_two_vector_type = EnumVariants::new(to_named(&[ + ParamType::Vector(Box::new(ParamType::U8)), + ParamType::Vector(Box::new(ParamType::U16)), + ]))?; + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_vector_type.clone(), + generics: param_types_no_vector.clone(), + }); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_vector_type.clone(), + generics: param_types_no_vector.clone(), + }); + + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_vector_type.clone(), + generics: param_types_no_vector.clone(), + } + .validate_is_decodable(max_depth) + .expect_err("should not be decodable") + .to_string(), + expected + ); + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_vector_type, + generics: param_types_containing_vector.clone(), + }); + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_vector_type, + generics: param_types_containing_vector.clone(), + }); + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_vector_type.clone(), + generics: param_types_containing_vector.clone(), + } + .validate_is_decodable(max_depth) + .expect_err("should not be decodable") + .to_string(), + expected + ); + + Ok(()) + } +} diff --git a/packages/fuels-core/src/types/transaction_builders.rs b/packages/fuels-core/src/types/transaction_builders.rs index e99be96ca..ed664d2ab 100644 --- a/packages/fuels-core/src/types/transaction_builders.rs +++ b/packages/fuels-core/src/types/transaction_builders.rs @@ -10,7 +10,7 @@ use async_trait::async_trait; use fuel_asm::{op, GTFArgs, RegId}; use fuel_crypto::{Message as CryptoMessage, Signature}; use fuel_tx::{ - field::{Inputs, WitnessLimit, Witnesses}, + field::{Inputs, Policies as PoliciesField, WitnessLimit, Witnesses}, policies::{Policies, PolicyType}, Buildable, Chargeable, ConsensusParameters, Create, Input as FuelInput, Output, Script, StorageSlot, Transaction as FuelTransaction, TransactionFee, TxPointer, UniqueIdentifier, @@ -27,7 +27,7 @@ use crate::{ bech32::Bech32Address, coin::Coin, coin_type::CoinType, - errors::{error, Result}, + errors::{error_transaction, Result}, input::Input, message::Message, transaction::{ @@ -42,7 +42,7 @@ use crate::{ #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait DryRunner: Send + Sync { async fn dry_run_and_get_used_gas(&self, tx: FuelTransaction, tolerance: f32) -> Result; - async fn min_gas_price(&self) -> Result; + async fn estimate_gas_price(&self, block_horizon: u32) -> Result; fn consensus_parameters(&self) -> &ConsensusParameters; } @@ -52,8 +52,8 @@ impl DryRunner for &T { (*self).dry_run_and_get_used_gas(tx, tolerance).await } - async fn min_gas_price(&self) -> Result { - (*self).min_gas_price().await + async fn estimate_gas_price(&self, block_horizon: u32) -> Result { + (*self).estimate_gas_price(block_horizon).await } fn consensus_parameters(&self) -> &ConsensusParameters { @@ -70,12 +70,12 @@ struct UnresolvedWitnessIndexes { pub trait BuildableTransaction: sealed::Sealed { type TxType: Transaction; - async fn build(self, provider: &impl DryRunner) -> Result; + async fn build(self, provider: impl DryRunner) -> Result; /// Building without signatures will set the witness indexes of signed coins in the /// order as they appear in the inputs. Multiple coins with the same owner will have /// the same witness index. Make sure you sign the built transaction in the expected order. - async fn build_without_signatures(self, provider: &impl DryRunner) -> Result; + async fn build_without_signatures(self, provider: impl DryRunner) -> Result; } impl sealed::Sealed for ScriptTransactionBuilder {} @@ -84,11 +84,11 @@ impl sealed::Sealed for ScriptTransactionBuilder {} impl BuildableTransaction for ScriptTransactionBuilder { type TxType = ScriptTransaction; - async fn build(self, provider: &impl DryRunner) -> Result { + async fn build(self, provider: impl DryRunner) -> Result { self.build(provider).await } - async fn build_without_signatures(mut self, provider: &impl DryRunner) -> Result { + async fn build_without_signatures(mut self, provider: impl DryRunner) -> Result { self.set_witness_indexes(); self.unresolved_signers = Default::default(); @@ -102,11 +102,11 @@ impl sealed::Sealed for CreateTransactionBuilder {} impl BuildableTransaction for CreateTransactionBuilder { type TxType = CreateTransaction; - async fn build(self, provider: &impl DryRunner) -> Result { + async fn build(self, provider: impl DryRunner) -> Result { self.build(provider).await } - async fn build_without_signatures(mut self, provider: &impl DryRunner) -> Result { + async fn build_without_signatures(mut self, provider: impl DryRunner) -> Result { self.set_witness_indexes(); self.unresolved_signers = Default::default(); @@ -119,10 +119,8 @@ pub trait TransactionBuilder: BuildableTransaction + Send + sealed::Sealed { type TxType: Transaction; fn add_signer(&mut self, signer: impl Signer + Send + Sync) -> Result<&mut Self>; - async fn fee_checked_from_tx( - &self, - provider: &impl DryRunner, - ) -> Result>; + async fn fee_checked_from_tx(&self, provider: impl DryRunner) + -> Result>; fn with_tx_policies(self, tx_policies: TxPolicies) -> Self; fn with_inputs(self, inputs: Vec) -> Self; fn with_outputs(self, outputs: Vec) -> Self; @@ -133,6 +131,7 @@ pub trait TransactionBuilder: BuildableTransaction + Send + sealed::Sealed { fn outputs_mut(&mut self) -> &mut Vec; fn witnesses(&self) -> &Vec; fn witnesses_mut(&mut self) -> &mut Vec; + fn with_estimation_horizon(self, block_horizon: u32) -> Self; } macro_rules! impl_tx_trait { @@ -148,9 +147,9 @@ macro_rules! impl_tx_trait { .owner_to_idx_offset .contains_key(address) { - return Err(error!( - InvalidData, - "Already added `Signer` with address: `{address}`" + return Err(error_transaction!( + Builder, + "already added `Signer` with address: `{address}`" )); } @@ -165,7 +164,7 @@ macro_rules! impl_tx_trait { async fn fee_checked_from_tx( &self, - provider: &impl DryRunner, + provider: impl DryRunner, ) -> Result> { let mut fee_estimation_tb = self.clone_without_signers(); @@ -177,7 +176,7 @@ macro_rules! impl_tx_trait { .extend(repeat(witness).take(self.unresolved_signers.len())); let mut tx = - BuildableTransaction::build_without_signatures(fee_estimation_tb, provider) + BuildableTransaction::build_without_signatures(fee_estimation_tb, &provider) .await?; let consensus_parameters = provider.consensus_parameters(); @@ -190,6 +189,9 @@ macro_rules! impl_tx_trait { &consensus_parameters.gas_costs, &consensus_parameters.fee_params, &tx.tx, + provider + .estimate_gas_price(self.gas_price_estimation_block_horizon) + .await?, )) } @@ -237,6 +239,12 @@ macro_rules! impl_tx_trait { fn witnesses_mut(&mut self) -> &mut Vec { &mut self.witnesses } + + fn with_estimation_horizon(mut self, block_horizon: u32) -> Self { + self.gas_price_estimation_block_horizon = block_horizon; + + self + } } impl $ty { @@ -255,9 +263,11 @@ macro_rules! impl_tx_trait { .collect(); } - fn generate_fuel_policies(&self, network_min_gas_price: u64) -> Policies { + fn generate_fuel_policies(&self) -> Policies { let mut policies = Policies::default(); - policies.set(PolicyType::MaxFee, self.tx_policies.max_fee()); + // `MaxFee` set to `tip` or `0` for `dry_run` + policies.set(PolicyType::MaxFee, self.tx_policies.tip().or(Some(0))); + policies.set(PolicyType::Maturity, self.tx_policies.maturity()); let witness_limit = self @@ -266,10 +276,7 @@ macro_rules! impl_tx_trait { .or_else(|| self.calculate_witnesses_size()); policies.set(PolicyType::WitnessLimit, witness_limit); - policies.set( - PolicyType::GasPrice, - self.tx_policies.gas_price().or(Some(network_min_gas_price)), - ); + policies.set(PolicyType::Tip, self.tx_policies.tip()); policies } @@ -284,9 +291,9 @@ macro_rules! impl_tx_trait { let num_witnesses = self.witnesses().len(); if num_witnesses + self.unresolved_signers.len() > 256 { - return Err(error!( - InvalidData, - "tx can not have more than 256 witnesses" + return Err(error_transaction!( + Builder, + "tx cannot have more than 256 witnesses" )); } @@ -300,6 +307,31 @@ macro_rules! impl_tx_trait { Some(padded_len_usize(witnesses_size + signature_size) as u64) } + + async fn set_max_fee_policy( + tx: &mut T, + provider: impl DryRunner, + block_horizon: u32, + ) -> Result<()> { + let gas_price = provider.estimate_gas_price(block_horizon).await?; + let consensus_parameters = provider.consensus_parameters(); + + let tx_fee = TransactionFee::checked_from_tx( + &consensus_parameters.gas_costs, + consensus_parameters.fee_params(), + tx, + gas_price, + ) + .ok_or(error_transaction!( + Other, + "error calculating `TransactionFee` in `TransactionBuilder`" + ))?; + + tx.policies_mut() + .set(PolicyType::MaxFee, Some(tx_fee.max_fee())); + + Ok(()) + } } }; } @@ -321,6 +353,7 @@ pub struct ScriptTransactionBuilder { pub witnesses: Vec, pub tx_policies: TxPolicies, pub gas_estimation_tolerance: f32, + pub gas_price_estimation_block_horizon: u32, unresolved_witness_indexes: UnresolvedWitnessIndexes, unresolved_signers: Vec>, } @@ -335,6 +368,7 @@ pub struct CreateTransactionBuilder { pub witnesses: Vec, pub tx_policies: TxPolicies, pub salt: Salt, + pub gas_price_estimation_block_horizon: u32, unresolved_witness_indexes: UnresolvedWitnessIndexes, unresolved_signers: Vec>, } @@ -343,7 +377,7 @@ impl_tx_trait!(ScriptTransactionBuilder, ScriptTransaction); impl_tx_trait!(CreateTransactionBuilder, CreateTransaction); impl ScriptTransactionBuilder { - async fn build(self, provider: &impl DryRunner) -> Result { + async fn build(self, provider: impl DryRunner) -> Result { let is_using_predicates = self.is_using_predicates(); let base_offset = if is_using_predicates { self.base_offset(provider.consensus_parameters()) @@ -385,7 +419,7 @@ impl ScriptTransactionBuilder { async fn set_script_gas_limit_to_gas_used( tx: &mut Script, - provider: &impl DryRunner, + provider: impl DryRunner, tolerance: f32, ) -> Result<()> { let consensus_params = provider.consensus_parameters(); @@ -401,7 +435,6 @@ impl ScriptTransactionBuilder { Default::default(), TxPointer::default(), 0, - 0u32.into(), )); // Add an empty `Witness` for the `coin_signed` we just added @@ -431,13 +464,9 @@ impl ScriptTransactionBuilder { Ok(()) } - async fn resolve_fuel_tx( - self, - base_offset: usize, - provider: &impl DryRunner, - ) -> Result