diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5afa9162c1..a45770c9d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ env: FUEL_CORE_VERSION: 0.23.0 FUEL_CORE_PATCH_BRANCH: RUST_VERSION: 1.76.0 - FORC_VERSION: 0.51.1 + FORC_VERSION: 0.52.0 FORC_PATCH_BRANCH: "" FORC_PATCH_REVISION: "" @@ -98,14 +98,14 @@ jobs: !packages/fuels/tests/.gitignore # TODO: To be removed once experimental encoding is the default - - name: Build Sway test projects w experimental logs + - name: Build Sway test projects w experimental encoding run: forc build --terse --error-on-warnings --json-abi-with-callpaths --experimental-new-encoding working-directory: packages/fuels - uses: actions/upload-artifact@v2 with: retention-days: 2 - name: sway-examples-w-experimental-logs + name: sway-examples-w-experimental-encoding # cache only the sway build artifacts, skip all src files path: | packages/fuels/tests @@ -202,9 +202,9 @@ jobs: - command: check_doc_unresolved_links args: # TODO: To be removed once experimental encoding is the default - - command: test_experimental_logs - args: - download_sway_artifacts: sway-examples-w-experimental-logs + - cargo_command: nextest + args: run --all-targets --features "experimental" --workspace + download_sway_artifacts: sway-examples-w-experimental-encoding install_fuel_core: true steps: - name: Checkout repository @@ -242,9 +242,8 @@ jobs: name: ${{ matrix.download_sway_artifacts }} path: packages/fuels/tests/ - # TODO: `test_experimental_logs` to be removed once experimental encoding is the default. - name: Install nextest - if: ${{ matrix.cargo_command == 'nextest' || matrix.command == 'test_experimental_logs' }} + if: ${{ matrix.cargo_command == 'nextest' }} uses: taiki-e/install-action@nextest - name: Install cargo-machete @@ -272,6 +271,7 @@ jobs: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh cd packages/wasm-tests wasm-pack test --node + wasm-pack test --node --features experimental - name: Check for invalid documentation anchors if: ${{ matrix.command == 'check_doc_anchors_valid' }} @@ -282,11 +282,6 @@ jobs: run: | ! cargo doc --document-private-items |& grep -A 6 "warning: unresolved link to" - # TODO: To be removed once experimental encoding is the default. - - name: Test experimental logs - if: ${{ matrix.command == 'test_experimental_logs' }} - run: RUSTFLAGS='--cfg experimental' cargo nextest run --test logs - publish: needs: - cargo-verifications diff --git a/docs/src/types/custom_types.md b/docs/src/types/custom_types.md index c151db9f0c..336318298d 100644 --- a/docs/src/types/custom_types.md +++ b/docs/src/types/custom_types.md @@ -57,7 +57,18 @@ impl MyContract for Contract { Your Rust code would look like this: ```rust,ignore -{{#include ../../../packages/fuels/tests/types_contracts.rs:generic}} + // simple struct with a single generic param + let arg1 = SimpleGeneric { + single_generic_param: 123u64, + }; + + let result = contract_methods + .struct_w_generic(arg1.clone()) + .call() + .await? + .value; + + assert_eq!(result, arg1); ``` ### Unused generic type parameters diff --git a/examples/contracts/Cargo.toml b/examples/contracts/Cargo.toml index 0a02a9290c..dabd063d66 100644 --- a/examples/contracts/Cargo.toml +++ b/examples/contracts/Cargo.toml @@ -17,3 +17,4 @@ tokio = { workspace = true, features = ["full"] } [features] fuel-core-lib = ["fuels/fuel-core-lib"] rocksdb = ["fuels/rocksdb"] +experimental = ["fuels/experimental"] diff --git a/examples/contracts/src/lib.rs b/examples/contracts/src/lib.rs index 1e1b7d8e23..2b12d035c2 100644 --- a/examples/contracts/src/lib.rs +++ b/examples/contracts/src/lib.rs @@ -107,7 +107,13 @@ mod tests { .await?; // ANCHOR_END: contract_call_cost_estimation - assert_eq!(transaction_cost.gas_used, 791); + let expected_gas = if cfg!(feature = "experimental") { + 2087 + } else { + 796 + }; + + assert_eq!(transaction_cost.gas_used, expected_gas); Ok(()) } @@ -602,7 +608,12 @@ mod tests { .await?; // ANCHOR_END: multi_call_cost_estimation - assert_eq!(transaction_cost.gas_used, 1162); + #[cfg(not(feature = "experimental"))] + let expected_gas = 1172; + #[cfg(feature = "experimental")] + let expected_gas = 3513; + + assert_eq!(transaction_cost.gas_used, expected_gas); Ok(()) } @@ -677,6 +688,7 @@ mod tests { } #[tokio::test] + #[cfg(not(feature = "experimental"))] async fn low_level_call_example() -> Result<()> { use fuels::{ core::codec::{calldata, fn_selector}, diff --git a/examples/debugging/Cargo.toml b/examples/debugging/Cargo.toml index 68d1780837..5c4f7c89f5 100644 --- a/examples/debugging/Cargo.toml +++ b/examples/debugging/Cargo.toml @@ -15,3 +15,6 @@ fuels = { workspace = true } rand = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } + +[features] +experimental = ["fuels/experimental"] diff --git a/examples/debugging/src/lib.rs b/examples/debugging/src/lib.rs index bae1cceca6..be39d8dbd6 100644 --- a/examples/debugging/src/lib.rs +++ b/examples/debugging/src/lib.rs @@ -1,19 +1,20 @@ #[cfg(test)] mod tests { + #[cfg(not(feature = "experimental"))] use std::collections::HashMap; + #[cfg(not(feature = "experimental"))] use fuel_abi_types::abi::program::ProgramABI; - use fuels::{ - core::{ - codec::{calldata, fn_selector, resolve_fn_selector, ABIDecoder}, - traits::Parameterize, - }, - macros::abigen, - types::{errors::Result, param_types::ParamType, SizedAsciiString}, - }; + #[cfg(not(feature = "experimental"))] + use fuels::core::codec::{calldata, fn_selector}; + #[cfg(not(feature = "experimental"))] + use fuels::types::{errors::Result, param_types::ParamType, SizedAsciiString}; + #[cfg(not(feature = "experimental"))] #[test] fn get_a_fn_selector() { + use fuels::core::{codec::resolve_fn_selector, traits::Parameterize}; + // ANCHOR: example_fn_selector // fn some_fn_name(arg1: Vec, arg2: u8) let fn_name = "some_fn_name"; @@ -25,8 +26,11 @@ mod tests { // ANCHOR_END: example_fn_selector } + #[cfg(not(feature = "experimental"))] #[test] fn a_fn_selector_from_json_abi() -> Result<()> { + use fuels::core::codec::resolve_fn_selector; + let json_abi_file = "../../packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json"; let abi_file_contents = std::fs::read_to_string(json_abi_file)?; @@ -60,6 +64,7 @@ mod tests { Ok(()) } + #[cfg(not(feature = "experimental"))] #[test] fn test_macros() -> Result<()> { let function_selector = fn_selector!(initialize_counter(u64)); @@ -70,103 +75,4 @@ 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/packages/fuels-core/Cargo.toml b/packages/fuels-core/Cargo.toml index c9b2dfe5c1..02f0a559ac 100644 --- a/packages/fuels-core/Cargo.toml +++ b/packages/fuels-core/Cargo.toml @@ -37,3 +37,4 @@ tokio = { workspace = true, features = ["test-util", "macros"] } [features] default = ["std"] std = ["dep:fuel-core-client"] +experimental = [] diff --git a/packages/fuels-core/src/codec.rs b/packages/fuels-core/src/codec.rs index 58583b3547..b33c519707 100644 --- a/packages/fuels-core/src/codec.rs +++ b/packages/fuels-core/src/codec.rs @@ -34,7 +34,10 @@ mod tests { #[test] fn can_convert_bytes_into_tuple() -> Result<()> { + #[cfg(not(feature = "experimental"))] let tuple_in_bytes: Vec = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2]; + #[cfg(feature = "experimental")] + let tuple_in_bytes: Vec = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2]; let the_tuple: (u64, u32) = try_from_bytes(&tuple_in_bytes, DecoderConfig::default())?; diff --git a/packages/fuels-core/src/codec/abi_decoder.rs b/packages/fuels-core/src/codec/abi_decoder.rs index 4d15a5b8af..c32055f752 100644 --- a/packages/fuels-core/src/codec/abi_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder.rs @@ -1,14 +1,15 @@ +#[cfg(not(feature = "experimental"))] mod bounded_decoder; mod decode_as_debug_str; -#[cfg(experimental)] +#[cfg(feature = "experimental")] mod experimental_bounded_decoder; -#[cfg(experimental)] -use crate::codec::abi_decoder::experimental_bounded_decoder::ExperimentalBoundedDecoder; +#[cfg(not(feature = "experimental"))] +use crate::codec::abi_decoder::bounded_decoder::BoundedDecoder; +#[cfg(feature = "experimental")] +use crate::codec::abi_decoder::experimental_bounded_decoder::BoundedDecoder; use crate::{ - codec::abi_decoder::{ - bounded_decoder::BoundedDecoder, decode_as_debug_str::decode_as_debug_str, - }, + codec::abi_decoder::decode_as_debug_str::decode_as_debug_str, types::{errors::Result, param_types::ParamType, Token}, }; @@ -110,20 +111,6 @@ impl ABIDecoder { 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) - } - - #[cfg(experimental)] - pub fn experimental_decode_multiple( - &self, - param_types: &[ParamType], - bytes: &[u8], - ) -> Result> { - ExperimentalBoundedDecoder::new(self.config).decode_multiple(param_types, bytes) - } } #[cfg(test)] @@ -141,44 +128,45 @@ mod tests { }; #[test] - fn decode_int() -> Result<()> { - let data = [0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff]; - - let decoded = ABIDecoder::default().decode(&ParamType::U32, &data)?; - - assert_eq!(decoded, Token::U32(u32::MAX)); - - Ok(()) - } - - #[test] - fn decode_multiple_int() -> Result<()> { + fn decode_multiple_uint() -> Result<()> { let types = vec![ - ParamType::U32, ParamType::U8, ParamType::U16, + ParamType::U32, ParamType::U64, ParamType::U128, ParamType::U256, ]; + + #[cfg(not(feature = "experimental"))] let data = [ - 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, // u32 - 0xff, // u8 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, // u16 - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // u64 - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, // u128 - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, // u256 + 255, // u8 + 0, 0, 0, 0, 0, 0, 255, 255, // u16 + 0, 0, 0, 0, 255, 255, 255, 255, // u32 + 255, 255, 255, 255, 255, 255, 255, 255, // u64 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, // u128 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // u256 + ]; + #[cfg(feature = "experimental")] + let data = [ + 255, // u8 + 255, 255, // u16 + 255, 255, 255, 255, // u32 + 255, 255, 255, 255, 255, 255, 255, 255, // u64 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, // u128 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // u256 ]; let decoded = ABIDecoder::default().decode_multiple(&types, &data)?; let expected = vec![ - Token::U32(u32::MAX), Token::U8(u8::MAX), Token::U16(u16::MAX), + Token::U32(u32::MAX), Token::U64(u64::MAX), Token::U128(u128::MAX), Token::U256(U256::MAX), @@ -191,7 +179,7 @@ mod tests { #[test] fn decode_bool() -> Result<()> { let types = vec![ParamType::Bool, ParamType::Bool]; - let data = [0x01, 0x0]; + let data = [1, 0]; let decoded = ABIDecoder::default().decode_multiple(&types, &data)?; @@ -205,9 +193,8 @@ mod tests { #[test] fn decode_b256() -> Result<()> { let data = [ - 0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, 0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44, - 0xe4, 0xcb, 0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, 0xa8, 0xf8, 0x27, 0x43, - 0xf3, 0x1e, 0x93, 0xb, + 213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34, + 152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11, ]; let decoded = ABIDecoder::default().decode(&ParamType::B256, &data)?; @@ -220,11 +207,17 @@ mod tests { #[test] fn decode_string_array() -> Result<()> { let types = vec![ParamType::StringArray(23), ParamType::StringArray(5)]; + #[cfg(not(feature = "experimental"))] let data = [ - 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // This is - 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // a full s - 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x00, // entence - 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00, // Hello + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, 0, //This is a full sentence + 72, 101, 108, 108, 111, 0, 0, 0, // Hello + ]; + #[cfg(feature = "experimental")] + let data = [ + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, //This is a full sentence + 72, 101, 108, 108, 111, // Hello ]; let decoded = ABIDecoder::default().decode_multiple(&types, &data)?; @@ -244,30 +237,80 @@ mod tests { #[test] fn decode_string_slice() -> Result<()> { - let types = vec![ParamType::StringSlice]; + #[cfg(not(feature = "experimental"))] + let data = [ + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, //This is a full sentence + ]; + #[cfg(feature = "experimental")] let data = [ - 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // This is - 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // a full s - 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, // entence + 0, 0, 0, 0, 0, 0, 0, 23, // [length] + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, //This is a full sentence ]; - let decoded = ABIDecoder::default().decode_multiple(&types, &data)?; + let decoded = ABIDecoder::default().decode(&ParamType::StringSlice, &data)?; - let expected = vec![Token::StringSlice(StaticStringToken::new( + let expected = Token::StringSlice(StaticStringToken::new( "This is a full sentence".into(), None, - ))]; + )); + + assert_eq!(decoded, expected); + + Ok(()) + } + + #[test] + fn decode_string() -> Result<()> { + #[cfg(not(feature = "experimental"))] + let data = [ + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, //This is a full sentence + ]; + #[cfg(feature = "experimental")] + let data = [ + 0, 0, 0, 0, 0, 0, 0, 23, // [length] + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, //This is a full sentence + ]; + + let decoded = ABIDecoder::default().decode(&ParamType::String, &data)?; + + let expected = Token::String("This is a full sentence".to_string()); assert_eq!(decoded, expected); Ok(()) } + #[test] + fn decode_tuple() -> Result<()> { + let param_type = ParamType::Tuple(vec![ParamType::U32, ParamType::Bool]); + #[cfg(not(feature = "experimental"))] + let data = [ + 0, 0, 0, 0, 0, 0, 0, 255, //u32 + 1, 0, 0, 0, 0, 0, 0, 0, //bool + ]; + #[cfg(feature = "experimental")] + let data = [ + 0, 0, 0, 255, //u32 + 1, //bool + ]; + + let result = ABIDecoder::default().decode(¶m_type, &data)?; + + let expected = Token::Tuple(vec![Token::U32(255), Token::Bool(true)]); + + assert_eq!(result, expected); + + Ok(()) + } + #[test] fn decode_array() -> Result<()> { - // Create a parameter type for u8[2]. let types = vec![ParamType::Array(Box::new(ParamType::U8), 2)]; - let data = [0xff, 0x2a]; + let data = [255, 42]; let decoded = ABIDecoder::default().decode_multiple(&types, &data)?; @@ -284,9 +327,11 @@ mod tests { // bar: bool, // } - let data = [ - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - ]; + #[cfg(not(feature = "experimental"))] + let data = [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]; + #[cfg(feature = "experimental")] + let data = [1, 1]; + let param_type = ParamType::Struct { name: "".to_string(), fields: to_named(&[ParamType::U8, ParamType::Bool]), @@ -304,10 +349,30 @@ mod tests { #[test] fn decode_bytes() -> Result<()> { - let data = [0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05]; + #[cfg(not(feature = "experimental"))] + let data = [255, 0, 1, 2, 3, 4, 5]; + #[cfg(feature = "experimental")] + let data = [0, 0, 0, 0, 0, 0, 0, 7, 255, 0, 1, 2, 3, 4, 5]; + let decoded = ABIDecoder::default().decode(&ParamType::Bytes, &data)?; - let expected = Token::Bytes(data.to_vec()); + let expected = Token::Bytes([255, 0, 1, 2, 3, 4, 5].to_vec()); + + assert_eq!(decoded, expected); + + Ok(()) + } + + #[test] + fn decode_raw_slice() -> Result<()> { + #[cfg(not(feature = "experimental"))] + let data = [255, 0, 1, 2, 3, 4, 5]; + #[cfg(feature = "experimental")] + let data = [0, 0, 0, 0, 0, 0, 0, 7, 255, 0, 1, 2, 3, 4, 5]; + + let decoded = ABIDecoder::default().decode(&ParamType::RawSlice, &data)?; + + let expected = Token::RawSlice([255, 0, 1, 2, 3, 4, 5].to_vec()); assert_eq!(decoded, expected); @@ -330,9 +395,10 @@ mod tests { }]; // "0" discriminant and 42 enum value - let data = [ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a, - ]; + #[cfg(not(feature = "experimental"))] + let data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42]; + #[cfg(feature = "experimental")] + let data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42]; let decoded = ABIDecoder::default().decode_multiple(&types, &data)?; @@ -342,6 +408,7 @@ mod tests { Ok(()) } + #[cfg(not(feature = "experimental"))] #[test] fn decoder_will_skip_enum_padding_and_decode_next_arg() -> Result<()> { // struct MyStruct { @@ -426,10 +493,12 @@ mod tests { generics: vec![], }; + #[cfg(not(feature = "experimental"))] let data = [ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0, 0, 0, 0, 0, 0, 0, 10, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, ]; + #[cfg(feature = "experimental")] + let data = [0, 10, 1, 1, 2]; let decoded = ABIDecoder::default().decode(&nested_struct, &data)?; @@ -483,20 +552,24 @@ mod tests { let types = [nested_struct, u8_arr, b256]; + #[cfg(not(feature = "experimental"))] + let bytes = [ + 0, 0, 0, 0, 0, 0, 0, 10, // u16 + 1, 0, 0, 0, 0, 0, 0, 0, // bool + 1, 2, // array[u8;2] + 1, 2, // array[u8;2] + 213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34, + 152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11, // b256 + ]; + + #[cfg(feature = "experimental")] let bytes = [ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, // u16 - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // bool - 0x1, 0x2, // array[u8] - 0x1, 0x2, // array[u8] - 0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, // b256 start - 0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44, 0xe4, 0xcb, // - 0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, // - 0xa8, 0xf8, 0x27, 0x43, 0xf3, 0x1e, 0x93, - 0xb, // b256 end - // 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, // "foo" - // 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // - // 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // - // 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, // + 0, 10, // u16 + 1, // bool + 1, 2, // array[u8;2] + 1, 2, // array[u8;2] + 213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34, + 152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11, // b256 ]; let decoded = ABIDecoder::default().decode_multiple(&types, &bytes)?; @@ -513,9 +586,8 @@ mod tests { let u8_arr = Token::Array(vec![Token::U8(1), Token::U8(2)]); let b256 = Token::B256([ - 0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, 0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44, - 0xe4, 0xcb, 0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, 0xa8, 0xf8, 0x27, 0x43, - 0xf3, 0x1e, 0x93, 0xb, + 213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34, + 152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11, ]); let expected: Vec = vec![foo, u8_arr, b256]; @@ -525,6 +597,7 @@ mod tests { Ok(()) } + #[cfg(not(feature = "experimental"))] #[test] fn units_in_structs_are_decoded_as_one_word() -> Result<()> { let data = [ @@ -566,7 +639,7 @@ mod tests { #[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 = to_named(&[ParamType::U32]); + let types = to_named(&[ParamType::U64]); let enum_variants = EnumVariants::new(types)?; let enum_type = ParamType::Enum { name: "".to_string(), @@ -655,12 +728,13 @@ mod tests { } #[test] - pub fn capacity_maloc() { + pub fn capacity_malloc() { let param_type = Array(Box::new(U8), usize::MAX); let result = ABIDecoder::default().decode(¶m_type, &[]); assert!(matches!(result, Err(Error::Codec(_)))); } + #[cfg(not(feature = "experimental"))] #[test] fn decoding_enum_with_more_than_one_heap_type_variant_fails() -> Result<()> { let mut param_types = vec![ @@ -700,6 +774,7 @@ mod tests { Ok(()) } + #[cfg(not(feature = "experimental"))] #[test] fn enums_w_too_deeply_nested_heap_types_not_allowed() { let variants = to_named(&[ @@ -785,28 +860,39 @@ mod tests { max_tokens: 3, ..Default::default() }; + { + let data = [0; 3 * WORD_SIZE]; + let inner_param_types = vec![ParamType::U64; 3]; + for param_type in [ + ParamType::Struct { + name: "".to_string(), + fields: to_named(&inner_param_types), + generics: vec![], + }, + ParamType::Tuple(inner_param_types.clone()), + ParamType::Array(Box::new(ParamType::U64), 3), + ] { + assert_decoding_failed_w_data( + config, + ¶m_type, + "token limit `3` reached while decoding. Try increasing it", + &data, + ); + } + } + { + let data = [0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3]; - let data = [0; 3 * WORD_SIZE]; - let inner_param_types = vec![ParamType::U8; 3]; - for param_type in [ - ParamType::Struct { - name: "".to_string(), - fields: to_named(&inner_param_types), - generics: vec![], - }, - 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, + &ParamType::Vector(Box::new(ParamType::U8)), "token limit `3` reached while decoding. Try increasing it", &data, ); } } + #[cfg(not(feature = "experimental"))] #[test] fn vectors_of_zst_are_not_supported() { let param_type = ParamType::Vector(Box::new(ParamType::StringArray(0))); 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 index 15c4d9e9c3..75f1082a1f 100644 --- 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 @@ -104,6 +104,7 @@ mod tests { }, }; + #[cfg(not(feature = "experimental"))] #[test] fn param_type_decode_debug() -> Result<()> { let decoder = ABIDecoder::default(); @@ -259,4 +260,166 @@ mod tests { Ok(()) } + + #[cfg(feature = "experimental")] + #[test] + fn param_type_decode_debug() -> Result<()> { + let decoder = ABIDecoder::default(); + { + assert_eq!( + format!("{:?}", true), + decoder.decode_as_debug_str(&bool::param_type(), &[1])? + ); + + assert_eq!( + format!("{:?}", 128u8), + decoder.decode_as_debug_str(&u8::param_type(), &[128])? + ); + + assert_eq!( + format!("{:?}", 256u16), + decoder.decode_as_debug_str(&u16::param_type(), &[1, 0])? + ); + + assert_eq!( + format!("{:?}", 512u32), + decoder.decode_as_debug_str(&u32::param_type(), &[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(), + &[ + 0, 0, 0, 0, 0, 0, 0, 32, 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(), + &[ + 0, 0, 0, 0, 0, 0, 0, 32, 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(), + &[0, 0, 0, 0, 0, 0, 0, 4, 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(), + &[0, 0, 0, 0, 0, 0, 0, 4, 70, 117, 101, 108] + )? + ); + } + { + assert_eq!( + format!("{:?}", (1, 2)), + decoder.decode_as_debug_str(&<(u8, u8)>::param_type(), &[1, 2])? + ); + + 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 4660c863aa..3a8bbb6d2a 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 @@ -1,7 +1,10 @@ use std::{iter::repeat, str}; use crate::{ - codec::DecoderConfig, + codec::{ + utils::{CodecDirection, CounterWithLimit}, + DecoderConfig, + }, constants::WORD_SIZE, types::{ errors::{error, Result}, @@ -12,7 +15,7 @@ use crate::{ /// Is used to decode bytes into `Token`s from which types implementing `Tokenizable` can be /// instantiated. Implements decoding limits to control resource usage. -pub(crate) struct ExperimentalBoundedDecoder { +pub(crate) struct BoundedDecoder { depth_tracker: CounterWithLimit, token_tracker: CounterWithLimit, } @@ -27,10 +30,12 @@ const B256_BYTES_SIZE: usize = 4 * WORD_SIZE; const LENGTH_BYTES_SIZE: usize = WORD_SIZE; const DISCRIMINANT_BYTES_SIZE: usize = WORD_SIZE; -impl ExperimentalBoundedDecoder { +impl BoundedDecoder { 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", CodecDirection::Decoding); + let token_tracker = + CounterWithLimit::new(config.max_tokens, "token", CodecDirection::Decoding); Self { depth_tracker, token_tracker, @@ -298,40 +303,6 @@ struct Decoded { bytes_read: usize, } -struct CounterWithLimit { - count: usize, - max: usize, - name: String, -} - -impl CounterWithLimit { - fn new(max: usize, name: impl Into) -> Self { - Self { - count: 0, - max, - name: name.into(), - } - } - - fn increase(&mut self) -> Result<()> { - self.count += 1; - if self.count > self.max { - return Err(error!( - Codec, - "{} limit `{}` reached while decoding. Try increasing it", self.name, self.max - )); - } - - Ok(()) - } - - fn decrease(&mut self) { - if self.count > 0 { - self.count -= 1; - } - } -} - fn peek_u8(bytes: &[u8]) -> Result { let slice = peek_fixed::(bytes)?; Ok(u8::from_be_bytes(*slice)) @@ -376,7 +347,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!( + (len <= data.len()).then(|| &data[..len]).ok_or(error!( Codec, "tried to read `{len}` bytes but only had `{}` remaining!", data.len() diff --git a/packages/fuels-core/src/codec/abi_encoder.rs b/packages/fuels-core/src/codec/abi_encoder.rs index a2b1df39ed..ce292fa614 100644 --- a/packages/fuels-core/src/codec/abi_encoder.rs +++ b/packages/fuels-core/src/codec/abi_encoder.rs @@ -1,6 +1,11 @@ mod bounded_encoder; +#[cfg(feature = "experimental")] +mod experimental_bounded_encoder; + use std::default::Default; +#[cfg(feature = "experimental")] +use crate::codec::abi_encoder::experimental_bounded_encoder::ExperimentalBoundedEncoder; use crate::{ codec::abi_encoder::bounded_encoder::BoundedEncoder, types::{errors::Result, unresolved_bytes::UnresolvedBytes, Token}, @@ -44,7 +49,12 @@ 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, false).encode(args) + #[cfg(not(feature = "experimental"))] + let res = BoundedEncoder::new(self.config, false).encode(args); + #[cfg(feature = "experimental")] + let res = ExperimentalBoundedEncoder::new(self.config, false).encode(args); + + res } } @@ -69,13 +79,13 @@ impl ConfigurablesEncoder { mod tests { use std::slice; + #[cfg(not(feature = "experimental"))] use itertools::chain; - use sha2::{Digest, Sha256}; use super::*; + #[cfg(not(feature = "experimental"))] + use crate::constants::WORD_SIZE; use crate::{ - codec::first_four_bytes_of_sha256_hash, - constants::WORD_SIZE, to_named, types::{ errors::Error, @@ -84,464 +94,255 @@ mod tests { }, }; + #[cfg(not(feature = "experimental"))] const VEC_METADATA_SIZE: usize = 3 * WORD_SIZE; + #[cfg(not(feature = "experimental"))] const DISCRIMINANT_SIZE: usize = WORD_SIZE; #[test] - fn encode_function_signature() { - let fn_signature = "entry_one(u64)"; + fn encode_multiple_uint() -> Result<()> { + let tokens = [ + Token::U8(u8::MAX), + Token::U16(u16::MAX), + Token::U32(u32::MAX), + Token::U64(u64::MAX), + Token::U128(u128::MAX), + Token::U256(U256::MAX), + ]; - let result = first_four_bytes_of_sha256_hash(fn_signature); + let result = ABIEncoder::default().encode(&tokens)?.resolve(0); + + #[cfg(not(feature = "experimental"))] + let expected = [ + 255, 0, 0, 0, 0, 0, 0, 0, // u8 + 0, 0, 0, 0, 0, 0, 255, 255, // u16 + 0, 0, 0, 0, 255, 255, 255, 255, // u32 + 255, 255, 255, 255, 255, 255, 255, 255, // u64 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, // u128 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // u256 + ]; + #[cfg(feature = "experimental")] + let expected = [ + 255, // u8 + 255, 255, // u16 + 255, 255, 255, 255, // u32 + 255, 255, 255, 255, 255, 255, 255, 255, // u64 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, // u128 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // u256 + ]; - println!("Encoded function selector for ({fn_signature}): {result:#0x?}"); + assert_eq!(result, expected); - assert_eq!(result, [0x0, 0x0, 0x0, 0x0, 0x0c, 0x36, 0xcb, 0x9c]); + Ok(()) } #[test] - fn encode_function_with_u32_type() -> Result<()> { - // @todo eventually we must update the json abi examples in here. - // They're in the old format. - // - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"arg","type":"u32"}], - // "name":"entry_one", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "entry_one(u32)"; - let arg = Token::U32(u32::MAX); - - let args: Vec = vec![arg]; + fn encode_bool() -> Result<()> { + let token = Token::Bool(true); - let expected_encoded_abi = [0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff]; - - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xb7, 0x9e, 0xf7, 0x43]; - - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + let result = ABIEncoder::default().encode(&[token])?.resolve(0); - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); + #[cfg(not(feature = "experimental"))] + let expected = [0, 0, 0, 0, 0, 0, 0, 1]; + #[cfg(feature = "experimental")] + let expected = [1]; - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); + assert_eq!(result, expected); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); Ok(()) } #[test] - fn encode_function_with_u32_type_multiple_args() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"first","type":"u32"},{"name":"second","type":"u32"}], - // "name":"takes_two", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "takes_two(u32,u32)"; - let first = Token::U32(u32::MAX); - let second = Token::U32(u32::MAX); - - let args: Vec = vec![first, second]; - - let expected_encoded_abi = [ - 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, + fn encode_b256() -> Result<()> { + let data = [ + 213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34, + 152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11, ]; + let token = Token::B256(data); - let expected_fn_selector = [0x0, 0x0, 0x0, 0x0, 0xa7, 0x07, 0xb0, 0x8e]; - - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); + let result = ABIEncoder::default().encode(&[token])?.resolve(0); - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); + assert_eq!(result, data); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_fn_selector); Ok(()) } #[test] - fn encode_function_with_u64_type() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"arg","type":"u64"}], - // "name":"entry_one", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "entry_one(u64)"; - let arg = Token::U64(u64::MAX); - - let args: Vec = vec![arg]; + fn encode_bytes() -> Result<()> { + let token = Token::Bytes([255, 0, 1, 2, 3, 4, 5].to_vec()); - let expected_encoded_abi = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; - - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x0c, 0x36, 0xcb, 0x9c]; - - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + let result = ABIEncoder::default().encode(&[token])?.resolve(0); - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); + #[cfg(not(feature = "experimental"))] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 24, // ptr + 0, 0, 0, 0, 0, 0, 0, 8, // cap + 0, 0, 0, 0, 0, 0, 0, 7, // len + 255, 0, 1, 2, 3, 4, 5, 0, // data + ]; + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 7, // len + 255, 0, 1, 2, 3, 4, 5, // data + ]; - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); + assert_eq!(result, expected); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); Ok(()) } #[test] - fn encode_function_with_bool_type() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"arg","type":"bool"}], - // "name":"bool_check", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "bool_check(bool)"; - let arg = Token::Bool(true); - - let args: Vec = vec![arg]; - - let expected_encoded_abi = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1]; - - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x66, 0x8f, 0xff, 0x58]; - - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + fn encode_string() -> Result<()> { + let token = Token::String("This is a full sentence".to_string()); - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); - - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); - - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); - Ok(()) - } + let result = ABIEncoder::default().encode(&[token])?.resolve(0); - #[test] - fn encode_function_with_two_different_type() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"first","type":"u32"},{"name":"second","type":"bool"}], - // "name":"takes_two_types", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "takes_two_types(u32,bool)"; - let first = Token::U32(u32::MAX); - let second = Token::Bool(true); - - let args: Vec = vec![first, second]; - - let expected_encoded_abi = [ - 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, // u32::MAX - 0x1, // true - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + #[cfg(not(feature = "experimental"))] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 24, // ptr + 0, 0, 0, 0, 0, 0, 0, 24, // cap + 0, 0, 0, 0, 0, 0, 0, 23, // len + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, 0, //This is a full sentence ]; - - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xf5, 0x40, 0x73, 0x2b]; - - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); - - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); - - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); - Ok(()) - } - - #[test] - fn encode_function_with_bits256_type() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"arg","type":"b256"}], - // "name":"takes_bits256", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "takes_bits256(b256)"; - - let mut hasher = Sha256::new(); - hasher.update("test string".as_bytes()); - - let arg = hasher.finalize(); - - let arg = Token::B256(arg.into()); - - let args: Vec = vec![arg]; - - let expected_encoded_abi = [ - 0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, 0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44, - 0xe4, 0xcb, 0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, 0xa8, 0xf8, 0x27, 0x43, - 0xf3, 0x1e, 0x93, 0xb, + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 23, // len + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, //This is a full sentence ]; - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x01, 0x49, 0x42, 0x96]; - - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); - - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); + assert_eq!(result, expected); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); Ok(()) } #[test] - fn encode_function_with_array_type() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"arg","type":"u8[3]"}], - // "name":"takes_integer_array", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "takes_integer_array(u8[3])"; - - // Keeping the construction of the arguments array separate for better readability. - let first = Token::U8(1); - let second = Token::U8(2); - let third = Token::U8(3); - - let arg = vec![first, second, third]; - let arg_array = Token::Array(arg); - - let args: Vec = vec![arg_array]; + fn encode_raw_slice() -> Result<()> { + let token = Token::RawSlice([255, 0, 1, 2, 3, 4, 5].to_vec()); - let expected_encoded_abi = [0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0]; - - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x2c, 0x5a, 0x10, 0x2e]; - - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + let result = ABIEncoder::default().encode(&[token])?.resolve(0); - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); + #[cfg(not(feature = "experimental"))] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 16, // cap + 0, 0, 0, 0, 0, 0, 0, 7, // len + 255, 0, 1, 2, 3, 4, 5, 0, // data + ]; + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 7, // len + 255, 0, 1, 2, 3, 4, 5, // data + ]; - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); + assert_eq!(result, expected); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); Ok(()) } #[test] - fn encode_function_with_string_array_type() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"arg","type":"str[23]"}], - // "name":"takes_string", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "takes_string(str[23])"; - - let args: Vec = vec![Token::StringArray(StaticStringToken::new( + fn encode_string_array() -> Result<()> { + let token = Token::StringArray(StaticStringToken::new( "This is a full sentence".into(), Some(23), - ))]; - - let expected_encoded_abi = [ - 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, - 0x20, 0x73, 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x0, - ]; - - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xd5, 0x6e, 0x76, 0x51]; + )); - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + let result = ABIEncoder::default().encode(&[token])?.resolve(0); - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); + #[cfg(not(feature = "experimental"))] + let expected = [ + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, 0, //This is a full sentence + ]; + #[cfg(feature = "experimental")] + let expected = [ + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, //This is a full sentence + ]; - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); + assert_eq!(result, expected); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); Ok(()) } #[test] - fn encode_function_with_string_slice_type() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"arg","type":"str"}], - // "name":"takes_string", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "takes_string(str)"; - - let args: Vec = vec![Token::StringSlice(StaticStringToken::new( + fn encode_string_slice() -> Result<()> { + let token = Token::StringSlice(StaticStringToken::new( "This is a full sentence".into(), None, - ))]; - - let expected_encoded_abi = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, // str at data index 16 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, // str of lenght 23 - 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // - 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // - 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, // - ]; - - let expected_function_selector = [0, 0, 0, 0, 239, 77, 222, 230]; + )); - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + let result = ABIEncoder::default().encode(&[token])?.resolve(0); - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); + #[cfg(not(feature = "experimental"))] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 16, // ptr + 0, 0, 0, 0, 0, 0, 0, 23, // len + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, //This is a full sentence + ]; + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 23, // len + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, //This is a full sentence + ]; - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); + assert_eq!(result, expected); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); Ok(()) } #[test] - fn encode_function_with_struct() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"arg","type":"MyStruct"}], - // "name":"takes_my_struct", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "takes_my_struct(MyStruct)"; - - // struct MyStruct { - // foo: u8, - // bar: bool, - // } - - let foo = Token::U8(1); - let bar = Token::Bool(true); - - // Create the custom struct token using the array of tuples above - let arg = Token::Struct(vec![foo, bar]); - - let args: Vec = vec![arg]; - - let expected_encoded_abi = [ - 0x1, // 1u8 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // padding - 0x1, // true - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // padding - ]; - - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xa8, 0x1e, 0x8d, 0xd7]; + fn encode_tuple() -> Result<()> { + let token = Token::Tuple(vec![Token::U32(255), Token::Bool(true)]); - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + let result = ABIEncoder::default().encode(&[token])?.resolve(0); - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); + #[cfg(not(feature = "experimental"))] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 255, //u32 + 1, 0, 0, 0, 0, 0, 0, 0, //bool + ]; + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 255, //u32 + 1, //bool + ]; - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); + assert_eq!(result, expected); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); Ok(()) } #[test] - fn encode_function_with_enum() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"arg","type":"MyEnum"}], - // "name":"takes_my_enum", - // "outputs": [] - // } - // ] - // "#; - - let fn_signature = "takes_my_enum(MyEnum)"; - - // enum MyEnum { - // x: u32, - // y: 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, - // whose value is 42 of the type ParamType::U32 and that the Enum could - // have held any of the other types present in `params`. - - let enum_selector = Box::new((0, Token::U32(42), params)); - - let arg = Token::Enum(enum_selector); - - let args: Vec = vec![arg]; - - let expected_encoded_abi = [ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a, - ]; + fn encode_array() -> Result<()> { + let token = Token::Tuple(vec![Token::U32(255), Token::U32(128)]); - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x35, 0x5c, 0xa6, 0xfa]; + let result = ABIEncoder::default().encode(&[token])?.resolve(0); - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + #[cfg(not(feature = "experimental"))] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 255, //u32 + 0, 0, 0, 0, 0, 0, 0, 128, //u32 + ]; + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 255, //u32 + 0, 0, 0, 128, //u32 + ]; - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); + assert_eq!(result, expected); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); Ok(()) } // The encoding follows the ABI specs defined [here](https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/abi.md) + #[cfg(not(feature = "experimental"))] #[test] fn enums_are_sized_to_fit_the_biggest_variant() -> Result<()> { // Our enum has two variants: B256, and U64. So the enum will set aside @@ -571,7 +372,7 @@ mod tests { } #[test] - fn encoding_enums_with_deeply_nested_types() -> Result<()> { + fn encode_enum_with_deeply_nested_types() -> Result<()> { /* enum DeeperEnum { v1: bool, @@ -622,121 +423,65 @@ mod tests { let top_level_enum_token = Token::Enum(Box::new((0, struct_a_token, top_level_enum_variants))); - let encoded = ABIEncoder::default() + let result = ABIEncoder::default() .encode(slice::from_ref(&top_level_enum_token))? .resolve(0); - let correct_encoding: Vec = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // TopLevelEnum::v1 discriminant - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // DeeperEnum::v2 discriminant - b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', // str[10] - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // DeeperEnum padding - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x44, // StructA.some_number - ] - .into(); + #[cfg(not(feature = "experimental"))] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, // TopLevelEnum::v1 discriminant + 0, 0, 0, 0, 0, 0, 0, 1, // DeeperEnum::v2 discriminant + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, // str[10] + 0, 0, 0, 0, 0, 0, // DeeperEnum padding + 0, 0, 0, 0, 0, 0, 44, 68, // StructA.some_number + ]; + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, // TopLevelEnum::v1 discriminant + 0, 0, 0, 0, 0, 0, 0, 1, // DeeperEnum::v2 discriminant + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, // str[10] + 0, 0, 44, 68, // StructA.some_number + ]; + + assert_eq!(result, expected); - assert_eq!(hex::encode(correct_encoding), hex::encode(encoded)); Ok(()) } #[test] - fn encode_function_with_nested_structs() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type":"function", - // "inputs": [{"name":"arg","type":"Foo"}], - // "name":"takes_my_nested_struct", - // "outputs": [] - // } - // ] - // "#; - - // struct Foo { - // x: u16, - // y: Bar, - // } - // - // struct Bar { - // a: bool, - // b: u8[2], - // } - - let fn_signature = "takes_my_nested_struct(Foo)"; - - let args: Vec = vec![Token::Struct(vec![ + fn encode_nested_structs() -> Result<()> { + let token = Token::Struct(vec![ Token::U16(10), Token::Struct(vec![ Token::Bool(true), Token::Array(vec![Token::U8(1), Token::U8(2)]), ]), - ])]; - - let expected_encoded_abi = [ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, // 10u16 - 0x1, // true - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // padding - 0x1, 0x2, // [1u8, 2u8] - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // padding - ]; - - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xea, 0x0a, 0xfd, 0x23]; + ]); - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + let result = ABIEncoder::default().encode(&[token])?.resolve(0); - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); + #[cfg(not(feature = "experimental"))] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 10, // u16 + 1, // bool + 0, 0, 0, 0, 0, 0, 0, // padding + 1, 2, // [u8, u8] + 0, 0, 0, 0, 0, 0, // padding + ]; + #[cfg(feature = "experimental")] + let expected = [ + 0, 10, // u16 + 1, // bool + 1, 2, // [u8, u8] + ]; - println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); + assert_eq!(result, expected); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); Ok(()) } #[test] - fn encode_comprehensive_function() -> Result<()> { - // let json_abi = - // r#" - // [ - // { - // "type": "contract", - // "inputs": [ - // { - // "name": "arg", - // "type": "Foo" - // }, - // { - // "name": "arg2", - // "type": "u8[2]" - // }, - // { - // "name": "arg3", - // "type": "b256" - // }, - // { - // "name": "arg", - // "type": "str[23]" - // } - // ], - // "name": "long_function", - // "outputs": [] - // } - // ] - // "#; - - // struct Foo { - // x: u16, - // y: Bar, - // } - // - // struct Bar { - // a: bool, - // b: u8[2], - // } - - let fn_signature = "long_function(Foo,u8[2],b256,str[23])"; - + fn encode_comprehensive() -> Result<()> { let foo = Token::Struct(vec![ Token::U16(10), Token::Struct(vec![ @@ -744,49 +489,52 @@ mod tests { Token::Array(vec![Token::U8(1), Token::U8(2)]), ]), ]); - - let u8_arr = Token::Array(vec![Token::U8(1), Token::U8(2)]); - - let mut hasher = Sha256::new(); - hasher.update("test string".as_bytes()); - - let b256 = Token::B256(hasher.finalize().into()); - - let s = Token::StringArray(StaticStringToken::new( + let arr_u8 = Token::Array(vec![Token::U8(1), Token::U8(2)]); + let b256 = Token::B256([255; 32]); + let str_arr = Token::StringArray(StaticStringToken::new( "This is a full sentence".into(), Some(23), )); - - let args: Vec = vec![foo, u8_arr, b256, s]; - - let expected_encoded_abi = [ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, // foo.x == 10u16 - 0x1, // foo.y.a == true - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // foo.y.a padding - 0x1, // foo.y.b.0 == 1u8 - 0x2, // foo.y.b.1 == 2u8 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // foo.y.a - 0x1, // u8[2].0 == 1u8 - 0x2, // u8[2].0 == 2u8 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, - 0x18, // b256 - 0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44, 0xe4, 0xcb, // b256 - 0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, // b256 - 0xa8, 0xf8, 0x27, 0x43, 0xf3, 0x1e, 0x93, 0xb, // b256 - 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // str[23] - 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // str[23] - 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, // str[23] - 0x0, + let tokens = vec![foo, arr_u8, b256, str_arr]; + + let result = ABIEncoder::default().encode(&tokens)?.resolve(0); + + #[cfg(not(feature = "experimental"))] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 10, // foo.x == 10u16 + 1, // foo.y.a == true + 0, 0, 0, 0, 0, 0, 0, // foo.y.a padding + 1, // foo.y.b.0 == 1u8 + 2, // foo.y.b.1 == 2u8 + 0, 0, 0, 0, 0, 0, // foo.y.a + 1, // u8[2].0 == 1u8 + 2, // u8[2].0 == 2u8 + 0, 0, 0, 0, 0, 0, // padding + 255, 255, 255, 255, 255, 255, 255, 255, // b256 + 255, 255, 255, 255, 255, 255, 255, 255, // b256 + 255, 255, 255, 255, 255, 255, 255, 255, // b256 + 255, 255, 255, 255, 255, 255, 255, 255, // b256 + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, 0, // str[23] + ]; + #[cfg(feature = "experimental")] + let expected = [ + 0, 10, // foo.x == 10u16 + 1, // foo.y.a == true + 1, // foo.y.b.0 == 1u8 + 2, // foo.y.b.1 == 2u8 + 1, // u8[2].0 == 1u8 + 2, // u8[2].0 == 2u8 + 255, 255, 255, 255, 255, 255, 255, 255, // b256 + 255, 255, 255, 255, 255, 255, 255, 255, // b256 + 255, 255, 255, 255, 255, 255, 255, 255, // b256 + 255, 255, 255, 255, 255, 255, 255, 255, // b256 + 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, + 116, 101, 110, 99, 101, // str[23] ]; - let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x10, 0x93, 0xb2, 0x12]; - - let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - - let encoded = ABIEncoder::default().encode(&args)?.resolve(0); + assert_eq!(result, expected); - assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); - assert_eq!(encoded_function_selector, expected_function_selector); Ok(()) } @@ -802,9 +550,11 @@ mod tests { .resolve(0); assert_eq!(actual, expected); + Ok(()) } + #[cfg(not(feature = "experimental"))] #[test] fn units_in_composite_types_are_encoded_in_one_word() -> Result<()> { let expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5]; @@ -817,6 +567,7 @@ mod tests { Ok(()) } + #[cfg(not(feature = "experimental"))] #[test] fn enums_with_units_are_correctly_padded() -> Result<()> { let discriminant = vec![0, 0, 0, 0, 0, 0, 0, 1]; @@ -834,6 +585,7 @@ mod tests { Ok(()) } + #[cfg(not(feature = "experimental"))] #[test] fn vector_has_ptr_cap_len_and_then_data() -> Result<()> { // arrange @@ -858,6 +610,7 @@ mod tests { Ok(()) } + #[cfg(not(feature = "experimental"))] #[test] fn data_from_two_vectors_aggregated_at_the_end() -> Result<()> { // arrange @@ -894,7 +647,7 @@ mod tests { } #[test] - fn a_vec_in_an_enum() -> Result<()> { + fn vec_in_enum() -> Result<()> { // arrange let offset = 40; let types = to_named(&[ParamType::B256, ParamType::Vector(Box::new(ParamType::U64))]); @@ -908,26 +661,34 @@ mod tests { .resolve(offset as u64); // assert - let discriminant = vec![0, 0, 0, 0, 0, 0, 0, 1]; - - const PADDING: usize = std::mem::size_of::<[u8; 32]>() - VEC_METADATA_SIZE; - - let vec1_ptr = ((DISCRIMINANT_SIZE + PADDING + VEC_METADATA_SIZE + offset) as u64) - .to_be_bytes() - .to_vec(); - let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1]; - let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1]; - let vec1_data = [0, 0, 0, 0, 0, 0, 0, 5]; - - let expected = chain!( - discriminant, - vec![0; PADDING], - vec1_ptr, - vec1_cap, - vec1_len, - vec1_data - ) - .collect::>(); + #[cfg(not(feature = "experimental"))] + let expected = { + let discriminant = vec![0, 0, 0, 0, 0, 0, 0, 1]; + + const PADDING: usize = std::mem::size_of::<[u8; 32]>() - VEC_METADATA_SIZE; + + let vec1_ptr = ((DISCRIMINANT_SIZE + PADDING + VEC_METADATA_SIZE + offset) as u64) + .to_be_bytes() + .to_vec(); + let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1]; + let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1]; + let vec1_data = [0, 0, 0, 0, 0, 0, 0, 5]; + + chain!( + discriminant, + vec![0; PADDING], + vec1_ptr, + vec1_cap, + vec1_len, + vec1_data + ) + .collect::>() + }; + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 1, // enum dicsriminant + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, // vec[len, u64] + ]; assert_eq!(result, expected); @@ -935,7 +696,7 @@ mod tests { } #[test] - fn an_enum_in_a_vec() -> Result<()> { + fn enum_in_vec() -> Result<()> { // arrange let offset = 40; let types = to_named(&[ParamType::B256, ParamType::U8]); @@ -951,15 +712,24 @@ mod tests { .resolve(offset as u64); // assert - const PADDING: usize = std::mem::size_of::<[u8; 32]>() - WORD_SIZE; - - let vec1_ptr = ((VEC_METADATA_SIZE + offset) as u64).to_be_bytes().to_vec(); - let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1]; - let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1]; - let discriminant = 1u64.to_be_bytes(); - let vec1_data = chain!(discriminant, [0; PADDING], 8u64.to_be_bytes()).collect::>(); - - let expected = chain!(vec1_ptr, vec1_cap, vec1_len, vec1_data).collect::>(); + #[cfg(not(feature = "experimental"))] + let expected = { + const PADDING: usize = std::mem::size_of::<[u8; 32]>() - WORD_SIZE; + + let vec1_ptr = ((VEC_METADATA_SIZE + offset) as u64).to_be_bytes().to_vec(); + let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1]; + let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1]; + let discriminant = 1u64.to_be_bytes(); + let vec1_data = + chain!(discriminant, [0; PADDING], 8u64.to_be_bytes()).collect::>(); + + chain!(vec1_ptr, vec1_cap, vec1_len, vec1_data).collect::>() + }; + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 1, // vec len + 0, 0, 0, 0, 0, 0, 0, 1, 8, // enum discriminant and u8 value + ]; assert_eq!(result, expected); @@ -967,7 +737,7 @@ mod tests { } #[test] - fn a_vec_in_a_struct() -> Result<()> { + fn vec_in_struct() -> Result<()> { // arrange let offset = 40; let token = Token::Struct(vec![Token::Vector(vec![Token::U64(5)]), Token::U8(9)]); @@ -978,15 +748,22 @@ mod tests { .resolve(offset as u64); // assert - let vec1_ptr = ((VEC_METADATA_SIZE + WORD_SIZE + offset) as u64) - .to_be_bytes() - .to_vec(); - let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1]; - let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1]; - let vec1_data = [0, 0, 0, 0, 0, 0, 0, 5]; - - let expected = - chain!(vec1_ptr, vec1_cap, vec1_len, [9], [0; 7], vec1_data).collect::>(); + #[cfg(not(feature = "experimental"))] + let expected = { + let vec1_ptr = ((VEC_METADATA_SIZE + WORD_SIZE + offset) as u64) + .to_be_bytes() + .to_vec(); + let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1]; + let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1]; + let vec1_data = [0, 0, 0, 0, 0, 0, 0, 5]; + + chain!(vec1_ptr, vec1_cap, vec1_len, [9], [0; 7], vec1_data).collect::>() + }; + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, // vec[len, u64] + 9, // u8 + ]; assert_eq!(result, expected); @@ -994,7 +771,7 @@ mod tests { } #[test] - fn a_vec_in_a_vec() -> Result<()> { + fn vec_in_vec() -> Result<()> { // arrange let offset = 40; let token = Token::Vector(vec![Token::Vector(vec![Token::U8(5), Token::U8(6)])]); @@ -1005,107 +782,36 @@ mod tests { .resolve(offset as u64); // assert - let vec1_data_offset = (VEC_METADATA_SIZE + offset) as u64; - let vec1_ptr = vec1_data_offset.to_be_bytes().to_vec(); - let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1]; - let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1]; - - let vec2_ptr = (vec1_data_offset + VEC_METADATA_SIZE as u64) - .to_be_bytes() - .to_vec(); - let vec2_cap = [0, 0, 0, 0, 0, 0, 0, 2]; - let vec2_len = [0, 0, 0, 0, 0, 0, 0, 2]; - let vec2_data = [5, 6]; - - let vec1_data = chain!(vec2_ptr, vec2_cap, vec2_len, vec2_data).collect::>(); - - let expected = chain!(vec1_ptr, vec1_cap, vec1_len, vec1_data).collect::>(); + #[cfg(not(feature = "experimental"))] + let expected = { + let vec1_data_offset = (VEC_METADATA_SIZE + offset) as u64; + let vec1_ptr = vec1_data_offset.to_be_bytes().to_vec(); + let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1]; + let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1]; + + let vec2_ptr = (vec1_data_offset + VEC_METADATA_SIZE as u64) + .to_be_bytes() + .to_vec(); + let vec2_cap = [0, 0, 0, 0, 0, 0, 0, 2]; + let vec2_len = [0, 0, 0, 0, 0, 0, 0, 2]; + let vec2_data = [5, 6]; + + let vec1_data = chain!(vec2_ptr, vec2_cap, vec2_len, vec2_data).collect::>(); + + chain!(vec1_ptr, vec1_cap, vec1_len, vec1_data).collect::>() + }; + #[cfg(feature = "experimental")] + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 1, // vec1 len + 0, 0, 0, 0, 0, 0, 0, 2, 5, 6, // vec2 [len, u8, u8] + ]; assert_eq!(result, expected); Ok(()) } - #[test] - fn encoding_bytes() -> Result<()> { - // arrange - let token = Token::Bytes(vec![1, 2, 3]); - let offset = 40; - - // act - let encoded_bytes = ABIEncoder::default().encode(&[token])?.resolve(offset); - - // assert - let ptr = [0, 0, 0, 0, 0, 0, 0, 64]; - let cap = [0, 0, 0, 0, 0, 0, 0, 8]; - let len = [0, 0, 0, 0, 0, 0, 0, 3]; - let data = [1, 2, 3, 0, 0, 0, 0, 0]; - - let expected_encoded_bytes = [ptr, cap, len, data].concat(); - - assert_eq!(expected_encoded_bytes, encoded_bytes); - - Ok(()) - } - - #[test] - fn encoding_raw_slices() -> Result<()> { - // arrange - let token = Token::RawSlice(vec![1, 2, 3]); - let offset = 40; - - // act - let encoded_bytes = ABIEncoder::default().encode(&[token])?.resolve(offset); - - // assert - let ptr = [0, 0, 0, 0, 0, 0, 0, 56].to_vec(); - let len = [0, 0, 0, 0, 0, 0, 0, 3].to_vec(); - let data = [1, 2, 3].to_vec(); - let padding = [0, 0, 0, 0, 0].to_vec(); - - let expected_encoded_bytes = [ptr, len, data, padding].concat(); - - assert_eq!(expected_encoded_bytes, encoded_bytes); - - Ok(()) - } - - #[test] - fn encoding_std_string() -> Result<()> { - // arrange - let string = String::from("This "); - let token = Token::String(string); - let offset = 40; - - // act - let encoded_std_string = ABIEncoder::default().encode(&[token])?.resolve(offset); - - // assert - let ptr = [0, 0, 0, 0, 0, 0, 0, 64]; - let cap = [0, 0, 0, 0, 0, 0, 0, 8]; - let len = [0, 0, 0, 0, 0, 0, 0, 5]; - let data = [0x54, 0x68, 0x69, 0x73, 0x20, 0, 0, 0]; - - let expected_encoded_std_string = [ptr, cap, len, data].concat(); - - assert_eq!(expected_encoded_std_string, encoded_std_string); - - Ok(()) - } - - #[test] - fn encoding_large_unsigned_integers() -> Result<()> { - let token = Token::U128(u128::MAX); - let expected_encoding = [255; 16]; - let result = ABIEncoder::default().encode(&[token])?.resolve(0); - assert_eq!(result, expected_encoding); - let token = Token::U256(U256::MAX); - let expected_encoding = [255; 32]; - let result = ABIEncoder::default().encode(&[token])?.resolve(0); - assert_eq!(result, expected_encoding); - Ok(()) - } - + #[cfg(not(feature = "experimental"))] #[test] fn capacity_overflow_is_caught() -> Result<()> { let token = Token::Enum(Box::new(( 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 9abd009790..7d2087d5f6 100644 --- a/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs +++ b/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs @@ -36,8 +36,6 @@ impl BoundedEncoder { } } - /// 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(&mut self, args: &[Token]) -> Result { // Checking that the tokens can be encoded is not done here, because it would require // going through the whole array of tokens, which can be pretty inefficient. diff --git a/packages/fuels-core/src/codec/abi_encoder/experimental_bounded_encoder.rs b/packages/fuels-core/src/codec/abi_encoder/experimental_bounded_encoder.rs new file mode 100644 index 0000000000..7c1db32868 --- /dev/null +++ b/packages/fuels-core/src/codec/abi_encoder/experimental_bounded_encoder.rs @@ -0,0 +1,133 @@ +use crate::{ + codec::{ + utils::{CodecDirection, CounterWithLimit}, + EncoderConfig, + }, + types::{ + errors::Result, + unresolved_bytes::{Data, UnresolvedBytes}, + EnumSelector, StaticStringToken, Token, U256, + }, +}; + +pub(crate) struct ExperimentalBoundedEncoder { + depth_tracker: CounterWithLimit, + token_tracker: CounterWithLimit, +} + +impl ExperimentalBoundedEncoder { + pub(crate) fn new(config: EncoderConfig, _unused: bool) -> Self { + let depth_tracker = + CounterWithLimit::new(config.max_depth, "depth", CodecDirection::Encoding); + let token_tracker = + CounterWithLimit::new(config.max_tokens, "token", CodecDirection::Encoding); + Self { + depth_tracker, + token_tracker, + } + } + + pub fn encode(&mut self, args: &[Token]) -> Result { + let data = vec![Data::Inline(self.encode_tokens(args)?)]; + + Ok(UnresolvedBytes::new(data)) + } + + fn encode_tokens(&mut self, tokens: &[Token]) -> Result> { + let mut data = vec![]; + + for token in tokens.iter() { + let new_data = self.encode_token(token)?; + data.extend(new_data); + } + + Ok(data) + } + + fn run_w_depth_tracking( + &mut self, + encoder: impl FnOnce(&mut Self) -> Result>, + ) -> Result> { + self.depth_tracker.increase()?; + let res = encoder(self); + self.depth_tracker.decrease(); + + res + } + + fn encode_token(&mut self, arg: &Token) -> Result> { + self.token_tracker.increase()?; + let encoded_token = match arg { + Token::Unit => vec![], + Token::Bool(arg_bool) => vec![u8::from(*arg_bool)], + Token::U8(arg_u8) => vec![*arg_u8], + Token::U16(arg_u16) => arg_u16.to_be_bytes().to_vec(), + Token::U32(arg_u32) => arg_u32.to_be_bytes().to_vec(), + Token::U64(arg_u64) => arg_u64.to_be_bytes().to_vec(), + Token::U128(arg_u128) => arg_u128.to_be_bytes().to_vec(), + Token::U256(arg_u256) => Self::encode_u256(*arg_u256), + Token::B256(arg_bits256) => arg_bits256.to_vec(), + Token::Bytes(data) => Self::encode_bytes(data.to_vec())?, + Token::String(string) => Self::encode_bytes(string.clone().into_bytes())?, + Token::RawSlice(data) => Self::encode_bytes(data.clone())?, + Token::StringArray(arg_string) => Self::encode_string_array(arg_string)?, + Token::StringSlice(arg_string) => Self::encode_string_slice(arg_string)?, + Token::Tuple(arg_tuple) => { + self.run_w_depth_tracking(|ctx| ctx.encode_tokens(arg_tuple))? + } + Token::Array(arg_array) => { + self.run_w_depth_tracking(|ctx| ctx.encode_tokens(arg_array))? + } + Token::Vector(data) => self.run_w_depth_tracking(|ctx| ctx.encode_vector(data))?, + Token::Struct(arg_struct) => { + self.run_w_depth_tracking(|ctx| ctx.encode_tokens(arg_struct))? + } + Token::Enum(arg_enum) => self.run_w_depth_tracking(|ctx| ctx.encode_enum(arg_enum))?, + }; + + Ok(encoded_token) + } + + fn encode_u256(arg_u256: U256) -> Vec { + let mut bytes = [0u8; 32]; + arg_u256.to_big_endian(&mut bytes); + + bytes.to_vec() + } + + fn encode_bytes(data: Vec) -> Result> { + let len = data.len(); + + Ok([Self::encode_length(len as u64), data].concat()) + } + + fn encode_string_array(arg_string: &StaticStringToken) -> Result> { + Ok(arg_string.get_encodable_str()?.as_bytes().to_vec()) + } + + fn encode_string_slice(arg_string: &StaticStringToken) -> Result> { + Self::encode_bytes(arg_string.get_encodable_str()?.as_bytes().to_vec()) + } + + fn encode_vector(&mut self, data: &[Token]) -> Result> { + let encoded_data = self.encode_tokens(data)?; + + Ok([Self::encode_length(data.len() as u64), encoded_data].concat()) + } + + fn encode_enum(&mut self, selector: &EnumSelector) -> Result> { + let (discriminant, token_within_enum, _) = selector; + let encoded_discriminant = Self::encode_discriminant(*discriminant); + let encoded_token = self.encode_token(token_within_enum)?; + + Ok([encoded_discriminant, encoded_token].concat()) + } + + fn encode_length(len: u64) -> Vec { + len.to_be_bytes().to_vec() + } + + fn encode_discriminant(discriminant: u64) -> Vec { + discriminant.to_be_bytes().to_vec() + } +} diff --git a/packages/fuels-core/src/codec/function_selector.rs b/packages/fuels-core/src/codec/function_selector.rs index 77c179c1a8..1b5a55be88 100644 --- a/packages/fuels-core/src/codec/function_selector.rs +++ b/packages/fuels-core/src/codec/function_selector.rs @@ -1,10 +1,13 @@ +#[cfg(not(feature = "experimental"))] use sha2::{Digest, Sha256}; -use crate::types::{ - param_types::{NamedParamType, ParamType}, - ByteArray, -}; +#[cfg(not(feature = "experimental"))] +use crate::types::param_types::NamedParamType; +use crate::types::param_types::ParamType; +#[cfg(not(feature = "experimental"))] +use crate::types::ByteArray; +#[cfg(not(feature = "experimental"))] /// Given a function name and its inputs will return a ByteArray representing /// the function selector as specified in the Fuel specs. pub fn resolve_fn_selector(name: &str, inputs: &[ParamType]) -> ByteArray { @@ -13,16 +16,29 @@ pub fn resolve_fn_selector(name: &str, inputs: &[ParamType]) -> ByteArray { first_four_bytes_of_sha256_hash(&fn_signature) } +#[cfg(feature = "experimental")] +//TODO: remove `_inputs` once the new encoding stabilizes +//https://github.com/FuelLabs/fuels-rs/issues/1318 +pub fn resolve_fn_selector(name: &str, _inputs: &[ParamType]) -> Vec { + let bytes = name.as_bytes().to_vec(); + let len = bytes.len() as u64; + + [len.to_be_bytes().to_vec(), bytes].concat() +} + +#[cfg(not(feature = "experimental"))] fn resolve_fn_signature(name: &str, inputs: &[ParamType]) -> String { let fn_args = resolve_args(inputs); format!("{name}({fn_args})") } +#[cfg(not(feature = "experimental"))] fn resolve_args(args: &[ParamType]) -> String { args.iter().map(resolve_arg).collect::>().join(",") } +#[cfg(not(feature = "experimental"))] fn resolve_named_args(args: &[NamedParamType]) -> String { args.iter() .map(|(_, param_type)| resolve_arg(param_type)) @@ -30,6 +46,7 @@ fn resolve_named_args(args: &[NamedParamType]) -> String { .join(",") } +#[cfg(not(feature = "experimental"))] fn resolve_arg(arg: &ParamType) -> String { match &arg { ParamType::U8 => "u8".to_owned(), @@ -89,6 +106,7 @@ fn resolve_arg(arg: &ParamType) -> String { } } +#[cfg(not(feature = "experimental"))] /// Hashes an encoded function selector using SHA256 and returns the first 4 bytes. /// The function selector has to have been already encoded following the ABI specs defined /// [here](https://github.com/FuelLabs/fuel-specs/blob/1be31f70c757d8390f74b9e1b3beb096620553eb/specs/protocol/abi.md) @@ -126,6 +144,7 @@ macro_rules! calldata { pub use calldata; +#[cfg(not(feature = "experimental"))] #[cfg(test)] mod tests { use super::*; @@ -287,4 +306,158 @@ mod tests { assert_eq!(selector, "complex_test(s((a[b256;2],str[2]),(a[e(s(str[2]))>(a[s(s(str[2]));2])>((s(s(str[2]))>(a[s(s(str[2]));2]),s(s(str[2]))>(a[s(s(str[2]));2])))>(u64,s(s(str[2]))>(a[s(s(str[2]));2])>((s(s(str[2]))>(a[s(s(str[2]));2]),s(s(str[2]))>(a[s(s(str[2]));2]))));1],u32)))"); } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_signature() { + let fn_signature = "entry_one(u64)"; + + let result = first_four_bytes_of_sha256_hash(fn_signature); + + assert_eq!(result, [0x0, 0x0, 0x0, 0x0, 0x0c, 0x36, 0xcb, 0x9c]); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_u32_type() { + let fn_signature = "entry_one(u32)"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xb7, 0x9e, 0xf7, 0x43]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_u32_type_multiple_args() { + let fn_signature = "takes_two(u32,u32)"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_fn_selector = [0x0, 0x0, 0x0, 0x0, 0xa7, 0x07, 0xb0, 0x8e]; + + assert_eq!(encoded_function_selector, expected_fn_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_u64_type() { + let fn_signature = "entry_one(u64)"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x0c, 0x36, 0xcb, 0x9c]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_bool_type() { + let fn_signature = "bool_check(bool)"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x66, 0x8f, 0xff, 0x58]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_two_different_type() { + let fn_signature = "takes_two_types(u32,bool)"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xf5, 0x40, 0x73, 0x2b]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_bits256_type() { + let fn_signature = "takes_bits256(b256)"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x01, 0x49, 0x42, 0x96]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_array_type() { + let fn_signature = "takes_integer_array(u8[3])"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x2c, 0x5a, 0x10, 0x2e]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_string_array_type() { + let fn_signature = "takes_string(str[23])"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xd5, 0x6e, 0x76, 0x51]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_string_slice_type() { + let fn_signature = "takes_string(str)"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0, 0, 0, 0, 239, 77, 222, 230]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_struct() { + let fn_signature = "takes_my_struct(MyStruct)"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xa8, 0x1e, 0x8d, 0xd7]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_function_with_enum() { + let fn_signature = "takes_my_enum(MyEnum)"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x35, 0x5c, 0xa6, 0xfa]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } + + #[test] + #[cfg(not(feature = "experimental"))] + fn encode_comprehensive_function() { + let fn_signature = "long_function(Foo,u8[2],b256,str[23])"; + + let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); + + let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x10, 0x93, 0xb2, 0x12]; + + assert_eq!(encoded_function_selector, expected_function_selector); + } } diff --git a/packages/fuels-core/src/codec/logs.rs b/packages/fuels-core/src/codec/logs.rs index 23ac108931..7ca63bc1c4 100644 --- a/packages/fuels-core/src/codec/logs.rs +++ b/packages/fuels-core/src/codec/logs.rs @@ -7,8 +7,6 @@ use std::{ use fuel_tx::{ContractId, Receipt}; -#[cfg(not(experimental))] -use crate::types::param_types::ParamType; use crate::{ codec::{ABIDecoder, DecoderConfig}, traits::{Parameterize, Tokenizable}, @@ -33,20 +31,18 @@ impl LogFormatter { decoder_config: DecoderConfig, bytes: &[u8], ) -> Result { - #[cfg(not(experimental))] - let token = { - Self::can_decode_log_with_type::()?; - ABIDecoder::new(decoder_config).decode(&T::param_type(), bytes)? - }; + #[cfg(not(feature = "experimental"))] + Self::can_decode_log_with_type::()?; - #[cfg(experimental)] - let token = ABIDecoder::new(decoder_config).experimental_decode(&T::param_type(), bytes)?; + let token = ABIDecoder::new(decoder_config).decode(&T::param_type(), bytes)?; Ok(format!("{:?}", T::from_token(token)?)) } - #[cfg(not(experimental))] + #[cfg(not(feature = "experimental"))] fn can_decode_log_with_type() -> Result<()> { + use crate::types::param_types::ParamType; + match T::param_type() { // 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 @@ -195,11 +191,6 @@ impl LogDecoder { .extract_log_id_and_data() .filter_map(|(log_id, bytes)| { target_ids.contains(&log_id).then(|| { - #[cfg(experimental)] - let token = ABIDecoder::new(self.decoder_config) - .experimental_decode(&T::param_type(), &bytes)?; - - #[cfg(not(experimental))] let token = ABIDecoder::new(self.decoder_config).decode(&T::param_type(), &bytes)?; diff --git a/packages/fuels-core/src/types.rs b/packages/fuels-core/src/types.rs index 502077f2c0..836b57246e 100644 --- a/packages/fuels-core/src/types.rs +++ b/packages/fuels-core/src/types.rs @@ -1,110 +1,26 @@ -use std::fmt; - use fuel_types::bytes::padded_len; pub use fuel_types::{ Address, AssetId, BlockHeight, Bytes32, Bytes4, Bytes64, Bytes8, ChainId, ContractId, MessageId, Nonce, Salt, Word, }; -pub use crate::types::{core::*, wrappers::*}; -use crate::types::{ - errors::{error, Error, Result}, - param_types::EnumVariants, -}; +pub use crate::types::{core::*, token::*, wrappers::*}; pub mod bech32; mod core; pub mod errors; pub mod param_types; +mod token; pub mod transaction_builders; pub mod tx_status; pub mod unresolved_bytes; mod wrappers; pub type ByteArray = [u8; 8]; +#[cfg(not(feature = "experimental"))] pub type Selector = ByteArray; -pub type EnumSelector = (u64, Token, EnumVariants); - -#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] -pub struct StaticStringToken { - pub(crate) data: String, - expected_len: Option, -} - -impl StaticStringToken { - pub fn new(data: String, expected_len: Option) -> Self { - StaticStringToken { data, expected_len } - } - - fn validate(&self) -> Result<()> { - if !self.data.is_ascii() { - 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!( - Codec, - "string data has len {}, but the expected len is {}", - self.data.len(), - expected_len - )); - } - } - - Ok(()) - } - - pub fn get_encodable_str(&self) -> Result<&str> { - self.validate()?; - Ok(self.data.as_str()) - } -} - -impl TryFrom for String { - type Error = Error; - fn try_from(string_token: StaticStringToken) -> Result { - string_token.validate()?; - Ok(string_token.data) - } -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub enum Token { - // Used for unit type variants in Enum. An "empty" enum is not represented as Enum, - // because this way we can have both unit and non-unit type variants. - Unit, - U8(u8), - U16(u16), - U32(u32), - U64(u64), - U128(u128), - U256(U256), - Bool(bool), - B256([u8; 32]), - Array(Vec), - Vector(Vec), - StringSlice(StaticStringToken), - StringArray(StaticStringToken), - Struct(Vec), - Enum(Box), - Tuple(Vec), - RawSlice(Vec), - Bytes(Vec), - String(String), -} - -impl fmt::Display for Token { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{self:?}") - } -} - -impl Default for Token { - fn default() -> Self { - Token::U8(0) - } -} +#[cfg(feature = "experimental")] +pub type Selector = Vec; /// Converts a u16 to a right aligned array of 8 bytes. pub fn pad_u16(value: u16) -> ByteArray { diff --git a/packages/fuels-core/src/types/core/bytes.rs b/packages/fuels-core/src/types/core/bytes.rs index 02e6dc6519..2164cbab89 100644 --- a/packages/fuels-core/src/types/core/bytes.rs +++ b/packages/fuels-core/src/types/core/bytes.rs @@ -19,8 +19,8 @@ impl Bytes { } impl From for Vec { - fn from(raw_slice: Bytes) -> Vec { - raw_slice.0 + fn from(bytes: Bytes) -> Vec { + bytes.0 } } diff --git a/packages/fuels-core/src/types/param_types/param_type.rs b/packages/fuels-core/src/types/param_types/param_type.rs index 9d7a2238a3..ddca1530e4 100644 --- a/packages/fuels-core/src/types/param_types/param_type.rs +++ b/packages/fuels-core/src/types/param_types/param_type.rs @@ -50,6 +50,7 @@ impl ParamType { // Depending on the type, the returned value will be stored // either in `Return` or `ReturnData`. pub fn get_return_location(&self) -> ReturnLocation { + #[cfg(not(feature = "experimental"))] match self { Self::Unit | Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::Bool => { ReturnLocation::Return @@ -57,6 +58,9 @@ impl ParamType { _ => ReturnLocation::ReturnData, } + + #[cfg(feature = "experimental")] + ReturnLocation::ReturnData } /// Given a [ParamType], return the number of elements of that [ParamType] that can fit in diff --git a/packages/fuels-core/src/types/token.rs b/packages/fuels-core/src/types/token.rs new file mode 100644 index 0000000000..00cd94567e --- /dev/null +++ b/packages/fuels-core/src/types/token.rs @@ -0,0 +1,90 @@ +use std::fmt; + +use crate::types::{ + core::U256, + errors::{error, Error, Result}, + param_types::EnumVariants, +}; + +pub type EnumSelector = (u64, Token, EnumVariants); + +#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] +pub struct StaticStringToken { + pub(crate) data: String, + expected_len: Option, +} + +impl StaticStringToken { + pub fn new(data: String, expected_len: Option) -> Self { + StaticStringToken { data, expected_len } + } + + fn validate(&self) -> Result<()> { + if !self.data.is_ascii() { + 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!( + Codec, + "string data has len {}, but the expected len is {}", + self.data.len(), + expected_len + )); + } + } + + Ok(()) + } + + pub fn get_encodable_str(&self) -> Result<&str> { + self.validate()?; + Ok(self.data.as_str()) + } +} + +impl TryFrom for String { + type Error = Error; + fn try_from(string_token: StaticStringToken) -> Result { + string_token.validate()?; + Ok(string_token.data) + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum Token { + // Used for unit type variants in Enum. An "empty" enum is not represented as Enum, + // because this way we can have both unit and non-unit type variants. + Unit, + Bool(bool), + U8(u8), + U16(u16), + U32(u32), + U64(u64), + U128(u128), + U256(U256), + B256([u8; 32]), + Bytes(Vec), + String(String), + RawSlice(Vec), + StringArray(StaticStringToken), + StringSlice(StaticStringToken), + Tuple(Vec), + Array(Vec), + Vector(Vec), + Struct(Vec), + Enum(Box), +} + +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{self:?}") + } +} + +impl Default for Token { + fn default() -> Self { + Token::U8(0) + } +} diff --git a/packages/fuels-programs/Cargo.toml b/packages/fuels-programs/Cargo.toml index 64116ac7fd..9dc5ff1ff8 100644 --- a/packages/fuels-programs/Cargo.toml +++ b/packages/fuels-programs/Cargo.toml @@ -29,3 +29,4 @@ tempfile = "3.8.1" [features] default = ["std"] std = ["fuels-core/std", "fuels-accounts/std"] +experimental = ["fuels-core/experimental"] diff --git a/packages/fuels-programs/src/call_utils.rs b/packages/fuels-programs/src/call_utils.rs index 9e311d8793..7d359dc40c 100644 --- a/packages/fuels-programs/src/call_utils.rs +++ b/packages/fuels-programs/src/call_utils.rs @@ -244,6 +244,7 @@ pub(crate) fn get_instructions( }) } +#[cfg(not(feature = "experimental"))] /// Returns script data, consisting of the following items in the given order: /// 1. Amount to be forwarded `(1 * `[`WORD_SIZE`]`)` /// 2. Asset ID to be forwarded ([`AssetId::LEN`]) @@ -323,6 +324,70 @@ pub(crate) fn build_script_data_from_contract_calls( Ok((script_data, param_offsets)) } +#[cfg(feature = "experimental")] +/// Returns script data, consisting of the following items in the given order: +/// 1. Amount to be forwarded `(1 * `[`WORD_SIZE`]`)` +/// 2. Asset ID to be forwarded ([`AssetId::LEN`]) +/// 3. Contract ID ([`ContractId::LEN`]); +/// 4. Function selector offset `(1 * `[`WORD_SIZE`]`)` +/// 5. Calldata offset `(1 * `[`WORD_SIZE`]`)` +/// 6. Encoded function selector - method name +/// 7. Encoded arguments +/// 8. Gas to be forwarded `(1 * `[`WORD_SIZE`]`)` - Optional +pub(crate) fn build_script_data_from_contract_calls( + calls: &[ContractCall], + data_offset: usize, +) -> Result<(Vec, Vec)> { + let mut script_data = vec![]; + let mut param_offsets = vec![]; + + // The data for each call is ordered into segments + let mut segment_offset = data_offset; + + for call in calls { + let amount_offset = segment_offset; + let asset_id_offset = amount_offset + WORD_SIZE; + let call_data_offset = asset_id_offset + AssetId::LEN; + let encoded_selector_offset = call_data_offset + ContractId::LEN + 2 * WORD_SIZE; + let encoded_args_offset = encoded_selector_offset + call.encoded_selector.len(); + + script_data.extend(call.call_parameters.amount().to_be_bytes()); // 1. Amount + script_data.extend(call.call_parameters.asset_id().iter()); // 2. Asset ID + script_data.extend(call.contract_id.hash().as_ref()); // 3. Contract ID + script_data.extend((encoded_selector_offset as Word).to_be_bytes()); // 4. Fun. selector offset + script_data.extend((encoded_args_offset as Word).to_be_bytes()); // 5. Calldata offset + script_data.extend(call.encoded_selector.clone()); // 6. Encoded function selector + + let encoded_args = call + .encoded_args + .as_ref() + .map(|ub| ub.resolve(encoded_args_offset as Word)) + .map_err(|e| error!(Codec, "cannot encode contract call arguments: {e}"))?; + let encoded_args_len = encoded_args.len(); + + script_data.extend(encoded_args); // 7. Encoded arguments + + let gas_forwarded_offset = call.call_parameters.gas_forwarded().map(|gf| { + script_data.extend((gf as Word).to_be_bytes()); // 8. Gas to be forwarded - Optional + + encoded_args_offset + encoded_args_len + }); + + param_offsets.push(CallOpcodeParamsOffset { + amount_offset, + asset_id_offset, + gas_forwarded_offset, + call_data_offset, + }); + + // the data segment that holds the parameters for the next call + // begins at the original offset + the data we added so far + segment_offset = data_offset + script_data.len(); + } + + Ok((script_data, param_offsets)) +} + /// Returns the VM instructions for calling a contract method /// We use the [`Opcode`] to call a contract: [`CALL`](Opcode::CALL) /// pointing at the following registers: @@ -336,7 +401,7 @@ pub(crate) fn build_script_data_from_contract_calls( /// non-reserved register. pub(crate) fn get_single_call_instructions( offsets: &CallOpcodeParamsOffset, - output_param_type: &ParamType, + _output_param_type: &ParamType, ) -> Result> { let call_data_offset = offsets .call_data_offset @@ -371,16 +436,18 @@ pub(crate) fn get_single_call_instructions( op::call(0x10, 0x11, 0x12, 0x13), ]); } - // If `gas_forwarded` was not set use `REG_CGAS` + // if `gas_forwarded` was not set use `REG_CGAS` None => instructions.push(op::call(0x10, 0x11, 0x12, RegId::CGAS)), }; - instructions.extend(extract_heap_data(output_param_type)?); + #[cfg(not(feature = "experimental"))] + instructions.extend(extract_heap_data(_output_param_type)?); #[allow(clippy::iter_cloned_collect)] Ok(instructions.into_iter().collect::>()) } +#[cfg(not(feature = "experimental"))] fn extract_heap_data(param_type: &ParamType) -> Result> { match param_type { ParamType::Enum { enum_variants, .. } => { @@ -416,6 +483,7 @@ fn extract_heap_data(param_type: &ParamType) -> Result Vec { mod test { use std::slice; + #[cfg(not(feature = "experimental"))] + use fuel_types::bytes::WORD_SIZE; use fuels_accounts::wallet::WalletUnlocked; - use fuels_core::{ - codec::ABIEncoder, - types::{ - bech32::Bech32ContractId, - coin::{Coin, CoinStatus}, - coin_type::CoinType, - Token, - }, + use fuels_core::types::{ + bech32::Bech32ContractId, + coin::{Coin, CoinStatus}, + coin_type::CoinType, }; + #[cfg(not(feature = "experimental"))] + use fuels_core::{codec::ABIEncoder, types::Token}; use rand::Rng; use super::*; @@ -609,7 +677,10 @@ mod test { ContractCall { contract_id: random_bech32_contract_id(), encoded_args: Ok(Default::default()), + #[cfg(not(feature = "experimental"))] encoded_selector: [0; 8], + #[cfg(feature = "experimental")] + encoded_selector: [0; 8].to_vec(), call_parameters: Default::default(), compute_custom_input_offset: false, variable_outputs: vec![], @@ -629,6 +700,7 @@ mod test { Bech32ContractId::new("fuel", rand::thread_rng().gen::<[u8; 32]>()) } + #[cfg(not(feature = "experimental"))] #[tokio::test] async fn test_script_data() { // Arrange @@ -658,7 +730,10 @@ mod test { let calls: Vec = (0..NUM_CALLS) .map(|i| ContractCall { contract_id: contract_ids[i].clone(), + #[cfg(not(feature = "experimental"))] encoded_selector: selectors[i], + #[cfg(feature = "experimental")] + encoded_selector: selectors[i].to_vec(), encoded_args: Ok(args[i].clone()), call_parameters: CallParameters::new(i as u64, asset_ids[i], i as u64), compute_custom_input_offset: i == 1, @@ -953,8 +1028,10 @@ mod test { const BASE_INSTRUCTION_COUNT: usize = 5; // 2 instructions (movi and lw) added in get_single_call_instructions when gas_offset is set const GAS_OFFSET_INSTRUCTION_COUNT: usize = 2; + #[cfg(not(feature = "experimental"))] // 4 instructions (lw, lw, muli, retd) added by extract_data_receipt const EXTRACT_DATA_RECEIPT_INSTRUCTION_COUNT: usize = 4; + #[cfg(not(feature = "experimental"))] // 4 instructions (movi, lw, jnef, retd) added by extract_heap_data const EXTRACT_HEAP_DATA_INSTRUCTION_COUNT: usize = 4; @@ -976,6 +1053,7 @@ mod test { ); } + #[cfg(not(feature = "experimental"))] #[test] fn test_with_heap_type() { let output_params = vec![ @@ -995,6 +1073,7 @@ mod test { } } + #[cfg(not(feature = "experimental"))] #[test] fn test_with_gas_offset_and_heap_type() { let mut call = ContractCall::new_with_random_id(); @@ -1011,6 +1090,7 @@ mod test { ); } + #[cfg(not(feature = "experimental"))] #[test] fn test_with_enum_with_heap_and_non_heap_variant() { let variant_sets = vec![ diff --git a/packages/fuels-programs/src/contract.rs b/packages/fuels-programs/src/contract.rs index 08c90ce8bd..d0d388370d 100644 --- a/packages/fuels-programs/src/contract.rs +++ b/packages/fuels-programs/src/contract.rs @@ -726,7 +726,10 @@ pub fn method_hash( let tx_policies = TxPolicies::default(); let call_parameters = CallParameters::default(); + #[cfg(not(feature = "experimental"))] let compute_custom_input_offset = should_compute_custom_input_offset(args); + #[cfg(feature = "experimental")] + let compute_custom_input_offset = true; let unresolved_bytes = ABIEncoder::new(encoder_config).encode(args); let contract_call = ContractCall { @@ -756,6 +759,7 @@ pub fn method_hash( // If the data passed into the contract method is an integer or a // boolean, then the data itself should be passed. Otherwise, it // should simply pass a pointer to the data in memory. +#[cfg(not(feature = "experimental"))] fn should_compute_custom_input_offset(args: &[Token]) -> bool { args.len() > 1 || args.iter().any(|t| { @@ -922,6 +926,7 @@ impl MultiContractCallHandler { } else { provider.send_transaction_and_await_commit(tx).await? }; + let receipts = tx_status.take_receipts_checked(Some(&self.log_decoder))?; self.get_response(receipts) diff --git a/packages/fuels-programs/src/receipt_parser.rs b/packages/fuels-programs/src/receipt_parser.rs index 1c68d02b7d..4c61ca21c7 100644 --- a/packages/fuels-programs/src/receipt_parser.rs +++ b/packages/fuels-programs/src/receipt_parser.rs @@ -43,6 +43,7 @@ impl ReceiptParser { // During a script execution, the script's contract id is the **null** contract id .unwrap_or_else(ContractId::zeroed); + #[cfg(not(feature = "experimental"))] output_param.validate_is_decodable(self.decoder.config.max_depth)?; let data = self @@ -64,7 +65,11 @@ impl ReceiptParser { output_param: &ParamType, contract_id: &ContractId, ) -> Option> { + #[cfg(not(feature = "experimental"))] let extra_receipts_needed = output_param.is_extra_receipt_needed(true); + #[cfg(feature = "experimental")] + let extra_receipts_needed = false; + match output_param.get_return_location() { ReturnLocation::ReturnData if extra_receipts_needed && matches!(output_param, ParamType::Enum { .. }) => @@ -319,7 +324,13 @@ mod tests { let contract_id = target_contract(); let mut receipts = expected_receipts.clone(); + #[cfg(not(feature = "experimental"))] receipts.push(get_return_receipt(contract_id, RECEIPT_VAL)); + #[cfg(feature = "experimental")] // all data is returned as RETD + receipts.push(get_return_data_receipt( + contract_id, + &RECEIPT_VAL.to_be_bytes(), + )); let mut parser = ReceiptParser::new(&receipts, Default::default()); let token = parser @@ -332,6 +343,7 @@ mod tests { Ok(()) } + #[cfg(not(feature = "experimental"))] #[tokio::test] async fn receipt_parser_extract_return_data_heap() -> Result<()> { let expected_receipts = get_relevant_receipts(); diff --git a/packages/fuels/Cargo.toml b/packages/fuels/Cargo.toml index 4a213e9a64..99b296b25f 100644 --- a/packages/fuels/Cargo.toml +++ b/packages/fuels/Cargo.toml @@ -40,6 +40,7 @@ tai64 = { workspace = true } [features] default = ["std", "fuels-test-helpers?/fuels-accounts", "coin-cache"] coin-cache = ["fuels-accounts/coin-cache"] +experimental = ["fuels-core/experimental", "fuels-programs/experimental"] # The crates enabled via `dep:` below are not currently wasm compatible, as # such they are only available if `std` is enabled. The `dep:` syntax was diff --git a/packages/fuels/Forc.toml b/packages/fuels/Forc.toml index 525eb44489..7ef5e0901f 100644 --- a/packages/fuels/Forc.toml +++ b/packages/fuels/Forc.toml @@ -60,8 +60,8 @@ members = [ 'tests/types/contracts/enum_encoding', 'tests/types/contracts/enum_inside_struct', 'tests/types/contracts/evm_address', - 'tests/types/contracts/generics', 'tests/types/contracts/heap_type_in_enums', + 'tests/types/contracts/heap_types', 'tests/types/contracts/identity', 'tests/types/contracts/native_types', 'tests/types/contracts/nested_structs', @@ -95,6 +95,7 @@ members = [ 'tests/types/scripts/options_results', 'tests/types/scripts/script_bytes', 'tests/types/scripts/script_generics', + 'tests/types/scripts/script_heap_types', #TODO: Decide what to do with this test project once # https://github.com/FuelLabs/sway/issues/5145 is resolved # 'tests/types/scripts/script_raw_slice', diff --git a/packages/fuels/tests/bindings.rs b/packages/fuels/tests/bindings.rs index 872364a45f..8ca5011778 100644 --- a/packages/fuels/tests/bindings.rs +++ b/packages/fuels/tests/bindings.rs @@ -1,10 +1,14 @@ -use std::{slice, str::FromStr}; +#[cfg(not(feature = "experimental"))] +use std::slice; +use std::str::FromStr; +use fuels::prelude::*; +#[cfg(not(feature = "experimental"))] use fuels::{ core::{codec::ABIEncoder, traits::Tokenizable}, - prelude::*, types::{Bits256, EvmAddress}, }; +#[cfg(not(feature = "experimental"))] use sha2::{Digest, Sha256}; pub fn null_contract_id() -> Bech32ContractId { @@ -13,6 +17,7 @@ pub fn null_contract_id() -> Bech32ContractId { .unwrap() } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn compile_bindings_from_contract_file() { // Generates the bindings from an ABI definition in a JSON file @@ -44,6 +49,7 @@ async fn compile_bindings_from_contract_file() { assert_eq!("000000005f68ee3d000000000000002a", encoded); } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn compile_bindings_from_inline_contract() -> Result<()> { // Generates the bindings from the an ABI definition inline. @@ -104,6 +110,7 @@ async fn compile_bindings_from_inline_contract() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn compile_bindings_array_input() -> Result<()> { // Generates the bindings from the an ABI definition inline. @@ -181,6 +188,7 @@ async fn compile_bindings_array_input() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn compile_bindings_bool_array_input() -> Result<()> { // Generates the bindings from the an ABI definition inline. @@ -255,6 +263,7 @@ async fn compile_bindings_bool_array_input() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn compile_bindings_string_input() -> Result<()> { // Generates the bindings from the an ABI definition inline. @@ -325,6 +334,7 @@ async fn compile_bindings_string_input() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn compile_bindings_b256_input() -> Result<()> { // Generates the bindings from the an ABI definition inline. @@ -396,6 +406,7 @@ async fn compile_bindings_b256_input() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn compile_bindings_evm_address_input() -> Result<()> { abigen!(Contract( @@ -466,6 +477,7 @@ async fn compile_bindings_evm_address_input() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn compile_bindings_struct_input() -> Result<()> { // Generates the bindings from the an ABI definition inline. @@ -568,6 +580,7 @@ async fn compile_bindings_struct_input() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn compile_bindings_nested_struct_input() -> Result<()> { // Generates the bindings from the an ABI definition inline. @@ -676,6 +689,7 @@ async fn compile_bindings_nested_struct_input() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn compile_bindings_enum_input() -> Result<()> { // Generates the bindings from the an ABI definition inline. @@ -763,6 +777,7 @@ async fn compile_bindings_enum_input() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn shared_types() -> Result<()> { setup_program_test!( diff --git a/packages/fuels/tests/contracts.rs b/packages/fuels/tests/contracts.rs index e9f17dfff7..d4e44a3834 100644 --- a/packages/fuels/tests/contracts.rs +++ b/packages/fuels/tests/contracts.rs @@ -1,5 +1,7 @@ +#[cfg(not(feature = "experimental"))] +use fuels::core::codec::{calldata, fn_selector}; use fuels::{ - core::codec::{calldata, fn_selector, DecoderConfig, EncoderConfig}, + core::codec::{DecoderConfig, EncoderConfig}, prelude::*, types::{errors::transaction::Reason, Bits256}, }; @@ -274,8 +276,15 @@ async fn test_contract_call_fee_estimation() -> Result<()> { let tolerance = Some(0.2); let block_horizon = Some(1); - let expected_gas_used = 949; + #[cfg(not(feature = "experimental"))] + let expected_gas_used = 955; + #[cfg(feature = "experimental")] + let expected_gas_used = 960; + + #[cfg(not(feature = "experimental"))] let expected_metered_bytes_size = 784; + #[cfg(feature = "experimental")] + let expected_metered_bytes_size = 824; let estimated_transaction_cost = contract_instance .methods() @@ -1218,6 +1227,7 @@ async fn multi_call_from_calls_with_different_account_types() -> Result<()> { } #[tokio::test] +#[cfg(not(feature = "experimental"))] async fn low_level_call() -> Result<()> { use fuels::types::SizedAsciiString; @@ -1696,7 +1706,7 @@ async fn test_arguments_with_gas_forwarded() -> Result<()> { let response = contract_instance .methods() .get_single(x) - .call_params(CallParameters::default().with_gas_forwarded(1024))? + .call_params(CallParameters::default().with_gas_forwarded(4096))? .call() .await?; @@ -1706,7 +1716,7 @@ async fn test_arguments_with_gas_forwarded() -> Result<()> { contract_instance_2 .methods() .u32_vec(vec_input.clone()) - .call_params(CallParameters::default().with_gas_forwarded(1024))? + .call_params(CallParameters::default().with_gas_forwarded(4096))? .call() .await?; } diff --git a/packages/fuels/tests/contracts/lib_contract/src/main.sw b/packages/fuels/tests/contracts/lib_contract/src/main.sw index ae4cf896e4..bedcd0610f 100644 --- a/packages/fuels/tests/contracts/lib_contract/src/main.sw +++ b/packages/fuels/tests/contracts/lib_contract/src/main.sw @@ -2,22 +2,6 @@ contract; use lib_contract::LibContract; -impl AbiEncode for str[21] { - fn abi_encode(self, ref mut buffer: Buffer) { - let s = from_str_array(self); - - let len = s.len(); - let ptr = s.as_ptr(); - - let mut i = 0; - while i < len { - let byte = ptr.add::(i).read::(); - buffer.push(byte); - i += 1; - } - } -} - impl LibContract for Contract { fn increment(value: u64) -> u64 { value + 1 diff --git a/packages/fuels/tests/from_token.rs b/packages/fuels/tests/from_token.rs index 663453e59b..73fef9c9b2 100644 --- a/packages/fuels/tests/from_token.rs +++ b/packages/fuels/tests/from_token.rs @@ -1,6 +1,8 @@ use std::str::FromStr; -use fuels::{core::traits::Tokenizable, prelude::*, types::Token}; +use fuels::prelude::*; +#[cfg(not(feature = "experimental"))] +use fuels::{core::traits::Tokenizable, types::Token}; pub fn null_contract_id() -> Bech32ContractId { // a bech32 contract address that decodes to [0u8;32] @@ -8,6 +10,7 @@ pub fn null_contract_id() -> Bech32ContractId { .unwrap() } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn create_struct_from_decoded_tokens() -> Result<()> { // Generates the bindings from the an ABI definition inline. @@ -102,6 +105,7 @@ async fn create_struct_from_decoded_tokens() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn create_nested_struct_from_decoded_tokens() -> Result<()> { // Generates the bindings from the an ABI definition inline. diff --git a/packages/fuels/tests/logs.rs b/packages/fuels/tests/logs.rs index 746e9b96bc..5b92fc9302 100644 --- a/packages/fuels/tests/logs.rs +++ b/packages/fuels/tests/logs.rs @@ -1453,7 +1453,7 @@ async fn can_configure_decoder_for_script_log_decoding() -> Result<()> { // 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 #[tokio::test] -#[cfg(not(experimental))] +#[cfg(not(feature = "experimental"))] async fn string_slice_log() -> Result<()> { setup_program_test!( Wallets("wallet"), @@ -1486,7 +1486,7 @@ async fn string_slice_log() -> Result<()> { } #[tokio::test] -#[cfg(experimental)] +#[cfg(feature = "experimental")] async fn contract_experimental_log() -> Result<()> { use fuels_core::types::AsciiString; @@ -1545,7 +1545,7 @@ async fn contract_experimental_log() -> Result<()> { } #[tokio::test] -#[cfg(experimental)] +#[cfg(feature = "experimental")] async fn script_experimental_log() -> Result<()> { use fuels_core::types::AsciiString; diff --git a/packages/fuels/tests/predicates.rs b/packages/fuels/tests/predicates.rs index 393f002e22..e1ce244b03 100644 --- a/packages/fuels/tests/predicates.rs +++ b/packages/fuels/tests/predicates.rs @@ -797,9 +797,16 @@ async fn predicate_can_access_manually_added_witnesses() -> Result<()> { .build(&provider) .await?; + #[cfg(not(feature = "experimental"))] let witness = ABIEncoder::default() .encode(&[64u8.into_token()])? .resolve(0); + + #[cfg(feature = "experimental")] + let witness = ABIEncoder::default() + .encode(&[64u64.into_token()])? // u64 because this is VM memory + .resolve(0); + let witness2 = ABIEncoder::default() .encode(&[4096u64.into_token()])? .resolve(0); @@ -869,9 +876,16 @@ async fn tx_id_not_changed_after_adding_witnesses() -> Result<()> { let tx_id = tx.id(provider.chain_id()); + #[cfg(not(feature = "experimental"))] let witness = ABIEncoder::default() .encode(&[64u8.into_token()])? .resolve(0); + + #[cfg(feature = "experimental")] + let witness = ABIEncoder::default() + .encode(&[64u64.into_token()])? // u64 because this is VM memory + .resolve(0); + let witness2 = ABIEncoder::default() .encode(&[4096u64.into_token()])? .resolve(0); diff --git a/packages/fuels/tests/predicates/signatures/src/main.sw b/packages/fuels/tests/predicates/signatures/src/main.sw index fc7fbb1910..c9a29fbcdc 100644 --- a/packages/fuels/tests/predicates/signatures/src/main.sw +++ b/packages/fuels/tests/predicates/signatures/src/main.sw @@ -5,10 +5,11 @@ use std::{b512::B512, constants::ZERO_B256, ecr::ec_recover_address, inputs::inp fn extract_public_key_and_match(signature: B512, expected_public_key: b256) -> u64 { if let Result::Ok(pub_key_sig) = ec_recover_address(signature, ZERO_B256) { - if pub_key_sig.value == expected_public_key { + if pub_key_sig == Address::from(expected_public_key) { return 1; } } + 0 } diff --git a/packages/fuels/tests/providers.rs b/packages/fuels/tests/providers.rs index 945d5d4ed7..688bc51726 100644 --- a/packages/fuels/tests/providers.rs +++ b/packages/fuels/tests/providers.rs @@ -324,7 +324,10 @@ async fn test_gas_forwarded_defaults_to_tx_limit() -> Result<()> { ); // The gas used by the script to call a contract and forward remaining gas limit. - let gas_used_by_script = 360; + #[cfg(not(feature = "experimental"))] + let gas_used_by_script = 364; + #[cfg(feature = "experimental")] + let gas_used_by_script = 876; let gas_limit = 225_883; let response = contract_instance .methods() diff --git a/packages/fuels/tests/scripts.rs b/packages/fuels/tests/scripts.rs index 40e58fcce8..53b315c17f 100644 --- a/packages/fuels/tests/scripts.rs +++ b/packages/fuels/tests/scripts.rs @@ -97,6 +97,9 @@ async fn test_basic_script_with_tx_policies() -> Result<()> { } #[tokio::test] +// Remove this test once the new encoding lands as the max_input will be irrelevant +// for direct script calls as all script_data is `inline` +// TODO: https://github.com/FuelLabs/fuels-rs/issues/1317 async fn test_script_call_with_non_default_max_input() -> Result<()> { use fuels::{ test_helpers::ChainConfig, @@ -144,6 +147,7 @@ async fn test_script_call_with_non_default_max_input() -> Result<()> { let result = script_instance.main(a, b).call().await?; assert_eq!(result.value, "heyoo"); + Ok(()) } diff --git a/packages/fuels/tests/types/contracts/heap_types/Forc.toml b/packages/fuels/tests/types/contracts/heap_types/Forc.toml new file mode 100644 index 0000000000..df2dc22cd2 --- /dev/null +++ b/packages/fuels/tests/types/contracts/heap_types/Forc.toml @@ -0,0 +1,5 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "heap_types" diff --git a/packages/fuels/tests/types/contracts/heap_types/src/main.sw b/packages/fuels/tests/types/contracts/heap_types/src/main.sw new file mode 100644 index 0000000000..fdac828583 --- /dev/null +++ b/packages/fuels/tests/types/contracts/heap_types/src/main.sw @@ -0,0 +1,42 @@ +contract; + +use std::bytes::Bytes; +use std::string::String; + +#[allow(dead_code)] +struct StructGenerics { + one: T, + two: K, + three: U, +} + +#[allow(dead_code)] +enum EnumGeneric { + One: H, + Two: I, +} + +abi HeapTypesContract { + fn nested_heap_types() -> EnumGeneric>, String>; +} + +impl HeapTypesContract for Contract { + fn nested_heap_types() -> EnumGeneric>, String> { + let mut some_vec = Vec::new(); + some_vec.push(2u8); + some_vec.push(4u8); + some_vec.push(8u8); + + let struct_generics = StructGenerics { + one: Bytes::from(some_vec), + two: String::from_ascii_str("fuel"), + three: some_vec.as_raw_slice(), + }; + + let mut enum_vec = Vec::new(); + enum_vec.push(struct_generics); + enum_vec.push(struct_generics); + + EnumGeneric::One(enum_vec) + } +} diff --git a/packages/fuels/tests/types/contracts/type_inside_enum/src/main.sw b/packages/fuels/tests/types/contracts/type_inside_enum/src/main.sw index 019109c243..d9430cd347 100644 --- a/packages/fuels/tests/types/contracts/type_inside_enum/src/main.sw +++ b/packages/fuels/tests/types/contracts/type_inside_enum/src/main.sw @@ -3,7 +3,7 @@ contract; #[allow(dead_code)] enum SomeEnum { SomeStr: str[4], - SomeArr: [u64; 7], + SomeArr: [u64; 4], } #[allow(dead_code)] diff --git a/packages/fuels/tests/types/scripts/script_heap_types/Forc.toml b/packages/fuels/tests/types/scripts/script_heap_types/Forc.toml new file mode 100644 index 0000000000..3c4d838276 --- /dev/null +++ b/packages/fuels/tests/types/scripts/script_heap_types/Forc.toml @@ -0,0 +1,5 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "script_heap_types" diff --git a/packages/fuels/tests/types/scripts/script_heap_types/src/main.sw b/packages/fuels/tests/types/scripts/script_heap_types/src/main.sw new file mode 100644 index 0000000000..e1e1199c9b --- /dev/null +++ b/packages/fuels/tests/types/scripts/script_heap_types/src/main.sw @@ -0,0 +1,36 @@ +script; + +use std::bytes::Bytes; +use std::string::String; + +#[allow(dead_code)] +struct StructGenerics { + one: T, + two: K, + three: U, +} + +#[allow(dead_code)] +enum EnumGeneric { + One: H, + Two: I, +} + +fn main() -> EnumGeneric>>, String> { + let mut some_vec = Vec::new(); + some_vec.push(2u8); + some_vec.push(4u8); + some_vec.push(8u8); + + let struct_generics = StructGenerics { + one: Bytes::from(some_vec), + two: String::from_ascii_str("fuel"), + three: some_vec, + }; + + let mut enum_vec = Vec::new(); + enum_vec.push(struct_generics); + enum_vec.push(struct_generics); + + EnumGeneric::One(enum_vec) +} diff --git a/packages/fuels/tests/types_contracts.rs b/packages/fuels/tests/types_contracts.rs index 0f181ae561..3efd7d6950 100644 --- a/packages/fuels/tests/types_contracts.rs +++ b/packages/fuels/tests/types_contracts.rs @@ -33,6 +33,7 @@ async fn test_methods_typeless_argument() -> Result<()> { .await?; assert_eq!(response.value, 63); + Ok(()) } @@ -92,6 +93,7 @@ async fn call_with_structs() -> Result<()> { let response = contract_methods.increment_counter(10).call().await?; assert_eq!(52, response.value); + Ok(()) } @@ -756,7 +758,7 @@ async fn type_inside_enum() -> Result<()> { assert_eq!(response.value, enum_string); // Array inside enum - let enum_array = SomeEnum::SomeArr([1, 2, 3, 4, 5, 6, 7]); + let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]); let response = contract_methods .arr_inside_enum(enum_array.clone()) .call() @@ -1406,141 +1408,6 @@ async fn test_identity_with_two_contracts() -> Result<()> { Ok(()) } -#[tokio::test] -async fn generics_test() -> Result<()> { - setup_program_test!( - Wallets("wallet"), - Abigen(Contract( - name = "TypesContract", - project = "packages/fuels/tests/types/contracts/generics" - )), - Deploy( - name = "contract_instance", - contract = "TypesContract", - wallet = "wallet" - ), - ); - let contract_methods = contract_instance.methods(); - - { - // ANCHOR: generic - // simple struct with a single generic param - let arg1 = SimpleGeneric { - single_generic_param: 123u64, - }; - - let result = contract_methods - .struct_w_generic(arg1.clone()) - .call() - .await? - .value; - - assert_eq!(result, arg1); - // ANCHOR_END: generic - } - { - // struct that delegates the generic param internally - let arg1 = PassTheGenericOn { - one: SimpleGeneric { - single_generic_param: "abc".try_into()?, - }, - }; - - let result = contract_methods - .struct_delegating_generic(arg1.clone()) - .call() - .await? - .value; - - assert_eq!(result, arg1); - } - { - // struct that has the generic in an array - let arg1 = StructWArrayGeneric { a: [1u32, 2u32] }; - - let result = contract_methods - .struct_w_generic_in_array(arg1.clone()) - .call() - .await? - .value; - - assert_eq!(result, arg1); - } - { - // struct that has the generic in a tuple - let arg1 = StructWTupleGeneric { a: (1, 2) }; - - let result = contract_methods - .struct_w_generic_in_tuple(arg1.clone()) - .call() - .await? - .value; - - assert_eq!(result, arg1); - } - { - // enum with generic in variant - let arg1 = EnumWGeneric::B(10); - let result = contract_methods - .enum_w_generic(arg1.clone()) - .call() - .await? - .value; - - assert_eq!(result, arg1); - } - { - contract_methods - .unused_generic_args( - StructOneUnusedGenericParam::default(), - EnumOneUnusedGenericParam::One, - ) - .call() - .await?; - - let (the_struct, the_enum) = contract_methods - .used_and_unused_generic_args( - StructUsedAndUnusedGenericParams::new(10u8), - EnumUsedAndUnusedGenericParams::Two(11u8), - ) - .call() - .await? - .value; - - assert_eq!(the_struct.field, 12u8); - if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum { - assert_eq!(val, 13) - } else { - panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two"); - } - } - { - // complex case - let pass_through = PassTheGenericOn { - one: SimpleGeneric { - single_generic_param: "ab".try_into()?, - }, - }; - let w_arr_generic = StructWArrayGeneric { - a: [pass_through.clone(), pass_through], - }; - - let arg1 = MegaExample { - a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?), - b: vec![( - [EnumWGeneric::B(StructWTupleGeneric { - a: (w_arr_generic.clone(), w_arr_generic), - })], - 10u32, - )], - }; - - contract_methods.complex_test(arg1.clone()).call().await?; - } - - Ok(()) -} - #[tokio::test] async fn test_vector() -> Result<()> { setup_program_test!( @@ -1800,6 +1667,7 @@ async fn test_base_type_in_vec_output() -> Result<()> { Ok(()) } + #[tokio::test] async fn test_composite_types_in_vec_output() -> Result<()> { setup_program_test!( @@ -1869,6 +1737,7 @@ async fn test_composite_types_in_vec_output() -> Result<()> { Ok(()) } +#[cfg(not(feature = "experimental"))] #[tokio::test] async fn test_nested_vector_methods_fail() -> Result<()> { // This is just an E2E test of the method `ParamType::contains_nested_heap_types`, hence it's @@ -1955,8 +1824,8 @@ async fn test_bytes_as_input() -> Result<()> { #[tokio::test] async fn test_contract_raw_slice() -> Result<()> { - let wallet = launch_provider_and_get_wallet().await?; setup_program_test!( + Wallets("wallet"), Abigen(Contract( name = "RawSliceContract", project = "packages/fuels/tests/types/contracts/raw_slice" @@ -2000,8 +1869,8 @@ async fn test_contract_raw_slice() -> Result<()> { #[tokio::test] async fn test_contract_returning_string_slice() -> Result<()> { - let wallet = launch_provider_and_get_wallet().await?; setup_program_test!( + Wallets("wallet"), Abigen(Contract( name = "StringSliceContract", project = "packages/fuels/tests/types/contracts/string_slice" @@ -2025,8 +1894,8 @@ async fn test_contract_returning_string_slice() -> Result<()> { #[tokio::test] async fn test_contract_std_lib_string() -> Result<()> { - let wallet = launch_provider_and_get_wallet().await?; setup_program_test!( + Wallets("wallet"), Abigen(Contract( name = "StdLibString", project = "packages/fuels/tests/types/contracts/std_lib_string" @@ -2055,8 +1924,8 @@ async fn test_contract_std_lib_string() -> Result<()> { #[tokio::test] async fn test_heap_type_in_enums() -> Result<()> { - let wallet = launch_provider_and_get_wallet().await?; setup_program_test!( + Wallets("wallet"), Abigen(Contract( name = "HeapTypeInEnum", project = "packages/fuels/tests/types/contracts/heap_type_in_enums" @@ -2069,78 +1938,153 @@ async fn test_heap_type_in_enums() -> Result<()> { ); let contract_methods = contract_instance.methods(); - let resp = contract_methods.returns_bytes_result(true).call().await?; - let expected = Ok(Bytes(vec![1, 1, 1, 1])); - assert_eq!(resp.value, expected); + { + let resp = contract_methods.returns_bytes_result(true).call().await?; + let expected = Ok(Bytes(vec![1, 1, 1, 1])); - let resp = contract_methods.returns_bytes_result(false).call().await?; - let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8])); - assert_eq!(resp.value, expected); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_bytes_result(false).call().await?; + let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8])); - let resp = contract_methods.returns_vec_result(true).call().await?; - let expected = Ok(vec![2, 2, 2, 2, 2]); - assert_eq!(resp.value, expected); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_vec_result(true).call().await?; + let expected = Ok(vec![2, 2, 2, 2, 2]); - let resp = contract_methods.returns_vec_result(false).call().await?; - let expected = Err(TestError::Else(7777)); - assert_eq!(resp.value, expected); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_vec_result(false).call().await?; + let expected = Err(TestError::Else(7777)); - let resp = contract_methods.returns_string_result(true).call().await?; - let expected = Ok("Hello World".to_string()); - assert_eq!(resp.value, expected); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_string_result(true).call().await?; + let expected = Ok("Hello World".to_string()); - let resp = contract_methods.returns_string_result(false).call().await?; - let expected = Err(TestError::Else(3333)); - assert_eq!(resp.value, expected); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_string_result(false).call().await?; + let expected = Err(TestError::Else(3333)); - let resp = contract_methods.returns_str_result(true).call().await?; - let expected = Ok("Hello World".try_into()?); - assert_eq!(resp.value, expected); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_str_result(true).call().await?; + let expected = Ok("Hello World".try_into()?); - let resp = contract_methods.returns_string_result(false).call().await?; - let expected = Err(TestError::Else(3333)); - assert_eq!(resp.value, expected); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_string_result(false).call().await?; + let expected = Err(TestError::Else(3333)); - let resp = contract_methods.returns_bytes_option(true).call().await?; - let expected = Some(Bytes(vec![1, 1, 1, 1])); - assert_eq!(resp.value, expected); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_bytes_option(true).call().await?; + let expected = Some(Bytes(vec![1, 1, 1, 1])); - let resp = contract_methods.returns_bytes_option(false).call().await?; - assert!(resp.value.is_none()); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_bytes_option(false).call().await?; - let resp = contract_methods.returns_vec_option(true).call().await?; - let expected = Some(vec![2, 2, 2, 2, 2]); - assert_eq!(resp.value, expected); + assert!(resp.value.is_none()); + } + { + let resp = contract_methods.returns_vec_option(true).call().await?; + let expected = Some(vec![2, 2, 2, 2, 2]); - let resp = contract_methods.returns_vec_option(false).call().await?; - assert!(resp.value.is_none()); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_vec_option(false).call().await?; - let resp = contract_methods.returns_string_option(true).call().await?; - let expected = Some("Hello World".to_string()); - assert_eq!(resp.value, expected); + assert!(resp.value.is_none()); + } + { + let resp = contract_methods.returns_string_option(true).call().await?; + let expected = Some("Hello World".to_string()); - let resp = contract_methods.returns_string_option(false).call().await?; - assert!(resp.value.is_none()); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_string_option(false).call().await?; - let resp = contract_methods.returns_str_option(true).call().await?; - let expected = Some("Hello World".try_into()?); - assert_eq!(resp.value, expected); + assert!(resp.value.is_none()); + } + { + let resp = contract_methods.returns_str_option(true).call().await?; + let expected = Some("Hello World".try_into()?); - let resp = contract_methods.returns_string_option(false).call().await?; - assert!(resp.value.is_none()); + assert_eq!(resp.value, expected); + } + { + let resp = contract_methods.returns_string_option(false).call().await?; + + assert!(resp.value.is_none()); + } + + #[cfg(not(feature = "experimental"))] + { + // If the LW(RET) instruction was not executed only conditionally, then the FuelVM would OOM. + let _ = contract_methods + .would_raise_a_memory_overflow() + .call() + .await?; + + let resp = contract_methods + .returns_a_heap_type_too_deep() + .call() + .await + .expect_err("should fail because it has a deeply nested heap type"); + let expected = "codec: enums currently support only one level deep heap types".to_string(); + + assert_eq!(resp.to_string(), expected); + } - // If the LW(RET) instruction was not executed only conditionally, then the FuelVM would OOM. - let _ = contract_methods - .would_raise_a_memory_overflow() + Ok(()) +} + +#[cfg(feature = "experimental")] +#[tokio::test] +async fn nested_heap_types() -> Result<()> { + setup_program_test!( + Wallets("wallet"), + Abigen(Contract( + name = "HeapTypeInEnum", + project = "packages/fuels/tests/types/contracts/heap_types" + )), + Deploy( + name = "contract_instance", + contract = "HeapTypeInEnum", + wallet = "wallet" + ), + ); + + let arr = [2u8, 4, 8]; + let struct_generics = StructGenerics { + one: Bytes(arr.to_vec()), + two: String::from("fuel"), + three: RawSlice(arr.to_vec()), + }; + + let enum_vec = [struct_generics.clone(), struct_generics].to_vec(); + let expected = EnumGeneric::One(enum_vec); + + let result = contract_instance + .methods() + .nested_heap_types() .call() .await?; - let resp = contract_methods - .returns_a_heap_type_too_deep() - .call() - .await - .expect_err("should fail because it has a deeply nested heap type"); - let expected = "codec: enums currently support only one level deep heap types".to_string(); - assert_eq!(resp.to_string(), expected); + assert_eq!(result.value, expected); + Ok(()) } diff --git a/packages/fuels/tests/types_scripts.rs b/packages/fuels/tests/types_scripts.rs index 9bc436bff0..0dfb184786 100644 --- a/packages/fuels/tests/types_scripts.rs +++ b/packages/fuels/tests/types_scripts.rs @@ -21,10 +21,12 @@ async fn main_function_generic_arguments() -> Result<()> { twix: bam_comp, mars: 1000, }; + let result = script_instance .main(bim.clone(), bam.clone()) .call() .await?; + let expected = ( GenericSnack { twix: GenericBimbam { @@ -34,7 +36,9 @@ async fn main_function_generic_arguments() -> Result<()> { }, GenericBimbam { val: 255_u8 }, ); + assert_eq!(result.value, expected); + Ok(()) } @@ -52,14 +56,23 @@ async fn main_function_option_result() -> Result<()> { wallet = "wallet" ) ); + { + let result = script_instance.main(Some(42), None).call().await?; + + assert_eq!(result.value, Ok(Some(true))); + } + { + let result = script_instance.main(Some(987), None).call().await?; + + assert_eq!(result.value, Ok(None)); + } + { + let expected_error = Err(TestError::ZimZam("error".try_into().unwrap())); + let result = script_instance.main(None, Some(987)).call().await?; + + assert_eq!(result.value, expected_error); + } - let result = script_instance.main(Some(42), None).call().await?; - assert_eq!(result.value, Ok(Some(true))); - let result = script_instance.main(Some(987), None).call().await?; - assert_eq!(result.value, Ok(None)); - let expected_error = Err(TestError::ZimZam("error".try_into().unwrap())); - let result = script_instance.main(None, Some(987)).call().await?; - assert_eq!(result.value, expected_error); Ok(()) } @@ -309,3 +322,36 @@ async fn script_handles_std_string() -> Result<()> { Ok(()) } + +#[cfg(feature = "experimental")] +#[tokio::test] +async fn nested_heap_types() -> Result<()> { + setup_program_test!( + Wallets("wallet"), + Abigen(Script( + name = "MyScript", + project = "packages/fuels/tests/types/scripts/script_heap_types", + )), + LoadScript( + name = "script_instance", + script = "MyScript", + wallet = "wallet" + ) + ); + + let arr = [2u8, 4, 8]; + let struct_generics = StructGenerics { + one: Bytes(arr.to_vec()), + two: String::from("fuel"), + three: arr.to_vec(), + }; + + let enum_vec = [struct_generics.clone(), struct_generics].to_vec(); + let expected = EnumGeneric::One(enum_vec); + + let result = script_instance.main().call().await?; + + assert_eq!(result.value, expected); + + Ok(()) +} diff --git a/packages/wasm-tests/Cargo.toml b/packages/wasm-tests/Cargo.toml index 7da0e53c4d..f332cffca8 100644 --- a/packages/wasm-tests/Cargo.toml +++ b/packages/wasm-tests/Cargo.toml @@ -18,5 +18,9 @@ crate-type = ['cdylib'] # that our examples don't inherit `fuels` with disabled features). # Cargo wouldn't respect any attempts here to disable them again. fuels = { path = "../fuels", default-features = false } +fuels-core = { path = "../fuels-core", default-features = false } getrandom = { version = "0.2.11", features = ["js"] } wasm-bindgen-test = "0.3.39" + +[features] +experimental = ["fuels-core/experimental"] diff --git a/packages/wasm-tests/src/lib.rs b/packages/wasm-tests/src/lib.rs index ad34baf7dd..c6f7f4f250 100644 --- a/packages/wasm-tests/src/lib.rs +++ b/packages/wasm-tests/src/lib.rs @@ -116,6 +116,22 @@ mod tests { .encode(&[original.clone().into_token()])? .resolve(0); + #[cfg(not(feature = "experimental"))] + let expected_bytes = [ + 0, 0, 0, 0, 0, 0, 0, 1, // enum discriminant + 0, 0, 0, 0, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, // SomeStruct + ] + .to_vec(); + + #[cfg(feature = "experimental")] + let expected_bytes = [ + 0, 0, 0, 0, 0, 0, 0, 1, // enum discriminant + 0, 0, 0, 123, 0, // SomeStruct + ] + .to_vec(); + + assert_eq!(expected_bytes, bytes); + let reconstructed = bytes.try_into().unwrap(); assert_eq!(original, reconstructed);