diff --git a/docs/src/calling-contracts/low-level-calls.md b/docs/src/calling-contracts/low-level-calls.md index 78d4a305bd..cbae4b26dc 100644 --- a/docs/src/calling-contracts/low-level-calls.md +++ b/docs/src/calling-contracts/low-level-calls.md @@ -28,3 +28,5 @@ you would construct the function selector and the calldata as such, and provide ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:low_level_call}} ``` + +> Note: the `calldata!` macro uses the default `EncoderConfig` configuration under the hood. diff --git a/docs/src/codec/encoding.md b/docs/src/codec/encoding.md index 86905f71c8..5a2df9a2cf 100644 --- a/docs/src/codec/encoding.md +++ b/docs/src/codec/encoding.md @@ -18,3 +18,27 @@ There is also a shortcut-macro that can encode multiple types which implement [` > Note: > The above example will call `.resolve(0)`. Don't use it if you're encoding heap types. + +## Configuring the encoder + +The encoder can be configured to limit its resource expenditure: + +```rust,ignore +{{#include ../../../examples/codec/src/lib.rs:configuring_the_encoder}} +``` + +The default values for the `EncoderConfig` are: + +```rust,ignore +{{#include ../../../packages/fuels-core/src/codec/abi_encoder.rs:default_encoder_config}} +``` + +## Configuring the encoder for contract/script calls + +You can also configure the encoder used to encode the arguments of the contract method: + +```rust,ignore +{{#include ../../../examples/contracts/src/lib.rs:contract_encoder_config}} +``` + +The same method is available for script calls. diff --git a/examples/codec/src/lib.rs b/examples/codec/src/lib.rs index 8d5fff8e3d..19b05d764a 100644 --- a/examples/codec/src/lib.rs +++ b/examples/codec/src/lib.rs @@ -1,6 +1,9 @@ #[cfg(test)] mod tests { - use fuels::{core::codec::DecoderConfig, types::errors::Result}; + use fuels::{ + core::codec::{DecoderConfig, EncoderConfig}, + types::errors::Result, + }; #[test] fn encoding_a_type() -> Result<()> { @@ -17,7 +20,7 @@ mod tests { } let instance = MyStruct { field: 101 }; - let encoded: UnresolvedBytes = ABIEncoder::encode(&[instance.into_token()])?; + let encoded: UnresolvedBytes = ABIEncoder::default().encode(&[instance.into_token()])?; let load_memory_address: u64 = 0x100; let _: Vec = encoded.resolve(load_memory_address); //ANCHOR_END: encoding_example @@ -98,4 +101,19 @@ mod tests { Ok(()) } + + #[test] + fn configuring_the_encoder() -> Result<()> { + // ANCHOR: configuring_the_encoder + use fuels::core::codec::ABIEncoder; + + ABIEncoder::new(EncoderConfig { + max_depth: 5, + max_tokens: 100, + max_total_enum_width: 10_000, + }); + // ANCHOR_END: configuring_the_encoder + + Ok(()) + } } diff --git a/examples/contracts/src/lib.rs b/examples/contracts/src/lib.rs index 5a54b063bf..3869d53b27 100644 --- a/examples/contracts/src/lib.rs +++ b/examples/contracts/src/lib.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod tests { + use fuels::core::codec::EncoderConfig; use fuels::{ core::codec::DecoderConfig, prelude::{Config, LoadConfiguration, StorageConfiguration}, @@ -677,7 +678,7 @@ mod tests { // Perform contract call with wallet_2 let response = contract_instance - .with_account(wallet_2)? // Connect wallet_2 + .with_account(wallet_2) // Connect wallet_2 .methods() // Get contract methods .get_msg_amount() // Our contract method .call() // Perform the contract call. @@ -830,7 +831,7 @@ mod tests { .initialize_counter(42) .with_decoder_config(DecoderConfig { max_depth: 10, - max_tokens: 20_00, + max_tokens: 2_000, }) .call() .await?; @@ -910,4 +911,37 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn configure_encoder_config() -> Result<()> { + use fuels::prelude::*; + + setup_program_test!( + Wallets("wallet"), + Abigen(Contract( + name = "MyContract", + project = "packages/fuels/tests/contracts/contract_test" + )), + Deploy( + name = "contract_instance", + contract = "MyContract", + wallet = "wallet" + ) + ); + + // ANCHOR: contract_encoder_config + let _ = contract_instance + .with_encoder_config(EncoderConfig { + max_depth: 10, + max_tokens: 2_000, + max_total_enum_width: 10_000, + }) + .methods() + .initialize_counter(42) + .call() + .await?; + // ANCHOR_END: contract_encoder_config + + Ok(()) + } } diff --git a/examples/predicates/src/lib.rs b/examples/predicates/src/lib.rs index b91d7927aa..689ac99090 100644 --- a/examples/predicates/src/lib.rs +++ b/examples/predicates/src/lib.rs @@ -65,7 +65,7 @@ mod tests { abi = "packages/fuels/tests/predicates/signatures/out/debug/signatures-abi.json" )); - let predicate_data = MyPredicateEncoder::encode_data(signatures); + let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?; let code_path = "../../packages/fuels/tests/predicates/signatures/out/debug/signatures.bin"; let predicate: Predicate = Predicate::load_from(code_path)? @@ -134,7 +134,7 @@ mod tests { // ANCHOR_END: predicate_data_setup // ANCHOR: with_predicate_data - let predicate_data = MyPredicateEncoder::encode_data(4096, 4096); + let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?; let code_path = "../../packages/fuels/tests/predicates/basic_predicate/out/debug/basic_predicate.bin"; diff --git a/examples/rust_bindings/src/rust_bindings_formatted.rs b/examples/rust_bindings/src/rust_bindings_formatted.rs index 3c6b02f5ab..b1a0d70676 100644 --- a/examples/rust_bindings/src/rust_bindings_formatted.rs +++ b/examples/rust_bindings/src/rust_bindings_formatted.rs @@ -41,12 +41,12 @@ pub mod abigen_bindings { pub fn account(&self) -> T { self.account.clone() } - pub fn with_account(&self, account: U) -> Result> { - ::core::result::Result::Ok(MyContract { + pub fn with_account(&self, account: U) -> MyContract { + MyContract { contract_id: self.contract_id.clone(), account, log_decoder: self.log_decoder.clone(), - }) + } } pub async fn get_balances(&self) -> Result<::std::collections::HashMap> { ViewOnlyAccount::try_provider(&self.account)? @@ -77,8 +77,8 @@ pub mod abigen_bindings { &[Tokenizable::into_token(value)], self.log_decoder.clone(), false, + ABIEncoder::new(EncoderConfig::default()), ) - .expect("method not found (this should never happen)") } #[doc = "Calls the contract's `increment_counter` function"] pub fn increment_counter(&self, value: u64) -> ContractCallHandler { @@ -89,8 +89,8 @@ pub mod abigen_bindings { &[value.into_token()], self.log_decoder.clone(), false, + ABIEncoder::new(EncoderConfig::default()), ) - .expect("method not found (this should never happen)") } } impl contract::SettableContract for MyContract { @@ -120,4 +120,3 @@ pub mod abigen_bindings { pub use abigen_bindings::my_contract_mod::MyContract; pub use abigen_bindings::my_contract_mod::MyContractConfigurables; pub use abigen_bindings::my_contract_mod::MyContractMethods; - diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/contract.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/contract.rs index 38432feda6..454a3493a7 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/contract.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/contract.rs @@ -37,10 +37,12 @@ pub(crate) fn contract_bindings( generate_code_for_configurable_constants(&configuration_struct_name, &abi.configurables)?; let code = quote! { + #[derive(Debug, Clone)] pub struct #name { contract_id: ::fuels::types::bech32::Bech32ContractId, account: T, - log_decoder: ::fuels::core::codec::LogDecoder + log_decoder: ::fuels::core::codec::LogDecoder, + encoder_config: ::fuels::core::codec::EncoderConfig, } impl #name @@ -51,7 +53,8 @@ pub(crate) fn contract_bindings( ) -> Self { let contract_id: ::fuels::types::bech32::Bech32ContractId = contract_id.into(); let log_decoder = ::fuels::core::codec::LogDecoder::new(#log_formatters); - Self { contract_id, account, log_decoder } + let encoder_config = ::fuels::core::codec::EncoderConfig::default(); + Self { contract_id, account, log_decoder, encoder_config } } pub fn contract_id(&self) -> &::fuels::types::bech32::Bech32ContractId { @@ -62,8 +65,21 @@ pub(crate) fn contract_bindings( self.account.clone() } - pub fn with_account(&self, account: U) -> ::fuels::types::errors::Result<#name> { - ::core::result::Result::Ok(#name { contract_id: self.contract_id.clone(), account, log_decoder: self.log_decoder.clone()}) + pub fn with_account(self, account: U) + -> #name { + #name { + contract_id: self.contract_id, + account, + log_decoder: self.log_decoder, + encoder_config: self.encoder_config + } + } + + pub fn with_encoder_config(mut self, encoder_config: ::fuels::core::codec::EncoderConfig) + -> #name:: { + self.encoder_config = encoder_config; + + self } pub async fn get_balances(&self) -> ::fuels::types::errors::Result<::std::collections::HashMap<::fuels::types::AssetId, u64>> { @@ -77,7 +93,8 @@ pub(crate) fn contract_bindings( #methods_name { contract_id: self.contract_id.clone(), account: self.account.clone(), - log_decoder: self.log_decoder.clone() + log_decoder: self.log_decoder.clone(), + encoder_config: self.encoder_config.clone(), } } } @@ -86,7 +103,8 @@ pub(crate) fn contract_bindings( pub struct #methods_name { contract_id: ::fuels::types::bech32::Bech32ContractId, account: T, - log_decoder: ::fuels::core::codec::LogDecoder + log_decoder: ::fuels::core::codec::LogDecoder, + encoder_config: ::fuels::core::codec::EncoderConfig, } impl #methods_name { @@ -157,8 +175,8 @@ pub(crate) fn expand_fn(abi_fun: &FullABIFunction) -> Result { &#arg_tokens, self.log_decoder.clone(), #is_payable, + self.encoder_config.clone(), ) - .expect("method not found (this should never happen)") }; generator.set_body(body); @@ -355,8 +373,8 @@ mod tests { ], self.log_decoder.clone(), false, + self.encoder_config.clone(), ) - .expect("method not found (this should never happen)") } }; @@ -411,8 +429,8 @@ mod tests { &[::fuels::core::traits::Tokenizable::into_token(bimbam)], self.log_decoder.clone(), false, + self.encoder_config.clone(), ) - .expect("method not found (this should never happen)") } }; @@ -523,8 +541,8 @@ mod tests { )], self.log_decoder.clone(), false, + self.encoder_config.clone(), ) - .expect("method not found (this should never happen)") } }; diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs index ec3480a81e..340c31e374 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs @@ -18,7 +18,6 @@ pub(crate) struct FunctionGenerator { output_type: TokenStream, body: TokenStream, doc: Option, - is_method: bool, } impl FunctionGenerator { @@ -38,7 +37,6 @@ impl FunctionGenerator { output_type: output_type.to_token_stream(), body: Default::default(), doc: None, - is_method: true, }) } @@ -47,11 +45,6 @@ impl FunctionGenerator { self } - pub fn make_fn_associated(&mut self) -> &mut Self { - self.is_method = false; - self - } - pub fn set_body(&mut self, body: TokenStream) -> &mut Self { self.body = body; self @@ -110,9 +103,7 @@ impl FunctionGenerator { let output_type = self.output_type(); let body = &self.body; - let self_param = self.is_method.then_some(quote! {&self,}); - - let params = quote! { #self_param #(#arg_declarations),* }; + let params = quote! { &self, #(#arg_declarations),* }; quote! { #doc diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/predicate.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/predicate.rs index 2aa82382ef..c0cb4788cd 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/predicate.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/predicate.rs @@ -27,10 +27,19 @@ pub(crate) fn predicate_bindings( generate_code_for_configurable_constants(&configuration_struct_name, &abi.configurables)?; let code = quote! { - pub struct #encoder_struct_name; + #[derive(Default)] + pub struct #encoder_struct_name{ + encoder: ::fuels::core::codec::ABIEncoder, + } impl #encoder_struct_name { #encode_function + + pub fn new(encoder_config: ::fuels::core::codec::EncoderConfig) -> Self { + Self { + encoder: ::fuels::core::codec::ABIEncoder::new(encoder_config) + } + } } #constant_configuration_code @@ -51,14 +60,16 @@ fn expand_fn(abi: &FullProgramABI) -> Result { let arg_tokens = generator.tokenized_args(); let body = quote! { - ::fuels::core::codec::ABIEncoder::encode(&#arg_tokens).expect("Cannot encode predicate data") + self.encoder.encode(&#arg_tokens) + }; + let output_type = quote! { + ::fuels::types::errors::Result<::fuels::types::unresolved_bytes::UnresolvedBytes> }; generator .set_doc("Run the predicate's encode function with the provided arguments".to_string()) .set_name("encode_data".to_string()) - .set_output_type(quote! { ::fuels::types::unresolved_bytes::UnresolvedBytes}) - .make_fn_associated() + .set_output_type(output_type) .set_body(body); Ok(generator.generate()) diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs index be597f95ac..5bde162baf 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs @@ -1,6 +1,7 @@ use fuel_abi_types::abi::full_program::FullProgramABI; use proc_macro2::{Ident, TokenStream}; use quote::quote; +use std::default::Default; use crate::{ error::Result, @@ -36,11 +37,12 @@ pub(crate) fn script_bindings( generate_code_for_configurable_constants(&configuration_struct_name, &abi.configurables)?; let code = quote! { - #[derive(Debug)] + #[derive(Debug,Clone)] pub struct #name{ account: T, binary: ::std::vec::Vec, - log_decoder: ::fuels::core::codec::LogDecoder + log_decoder: ::fuels::core::codec::LogDecoder, + encoder_config: ::fuels::core::codec::EncoderConfig, } impl #name @@ -51,12 +53,18 @@ pub(crate) fn script_bindings( Self { account, binary, - log_decoder: ::fuels::core::codec::LogDecoder::new(#log_formatters_lookup) + log_decoder: ::fuels::core::codec::LogDecoder::new(#log_formatters_lookup), + encoder_config: ::fuels::core::codec::EncoderConfig::default(), } } - pub fn with_account(self, account: U) -> ::fuels::types::errors::Result<#name> { - ::core::result::Result::Ok(#name { account, binary: self.binary, log_decoder: self.log_decoder}) + pub fn with_account(self, account: U) -> #name { + #name { + account, + binary: self.binary, + log_decoder: self.log_decoder, + encoder_config: self.encoder_config, + } } pub fn with_configurables(mut self, configurables: impl Into<::fuels::core::Configurables>) @@ -67,6 +75,14 @@ pub(crate) fn script_bindings( self } + pub fn with_encoder_config(mut self, encoder_config: ::fuels::core::codec::EncoderConfig) + -> Self + { + self.encoder_config = encoder_config; + + self + } + pub fn log_decoder(&self) -> ::fuels::core::codec::LogDecoder { self.log_decoder.clone() } @@ -92,7 +108,7 @@ fn expand_fn(abi: &FullProgramABI) -> Result { let arg_tokens = generator.tokenized_args(); let body = quote! { - let encoded_args = ::fuels::core::codec::ABIEncoder::encode(&#arg_tokens).expect("Cannot encode script arguments"); + let encoded_args = ::fuels::core::codec::ABIEncoder::new(self.encoder_config).encode(&#arg_tokens); let provider = ::fuels::accounts::ViewOnlyAccount::try_provider(&self.account).expect("Provider not set up") .clone(); ::fuels::programs::script_calls::ScriptCallHandler::new( diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs b/packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs index 765ada0288..416868b03b 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs @@ -50,7 +50,8 @@ fn generate_struct_decl(configurable_struct_name: &Ident) -> TokenStream { quote! { #[derive(Clone, Debug, Default)] pub struct #configurable_struct_name { - offsets_with_data: ::std::vec::Vec<(u64, ::std::vec::Vec)> + offsets_with_data: ::std::vec::Vec<(u64, ::std::vec::Vec)>, + encoder: ::fuels::core::codec::ABIEncoder, } } } @@ -63,8 +64,11 @@ fn generate_struct_impl( quote! { impl #configurable_struct_name { - pub fn new() -> Self { - ::std::default::Default::default() + pub fn new(encoder_config: ::fuels::core::codec::EncoderConfig) -> Self { + Self { + encoder: ::fuels::core::codec::ABIEncoder::new(encoder_config), + ..::std::default::Default::default() + } } #builder_methods @@ -82,9 +86,11 @@ fn generate_builder_methods(resolved_configurables: &[ResolvedConfigurable]) -> let encoder_code = generate_encoder_code(ttype); quote! { #[allow(non_snake_case)] - pub fn #name(mut self, value: #ttype) -> Self{ - self.offsets_with_data.push((#offset, #encoder_code)); - self + // Generate the `with_XXX` methods for setting the configurables + pub fn #name(mut self, value: #ttype) -> ::fuels::prelude::Result { + let encoded = #encoder_code?.resolve(0); + self.offsets_with_data.push((#offset, encoded)); + ::fuels::prelude::Result::Ok(self) } } }, @@ -97,11 +103,9 @@ fn generate_builder_methods(resolved_configurables: &[ResolvedConfigurable]) -> fn generate_encoder_code(ttype: &ResolvedType) -> TokenStream { quote! { - ::fuels::core::codec::ABIEncoder::encode(&[ + self.encoder.encode(&[ <#ttype as ::fuels::core::traits::Tokenizable>::into_token(value) ]) - .expect("Cannot encode configurable data") - .resolve(0) } } diff --git a/packages/fuels-code-gen/src/program_bindings/custom_types.rs b/packages/fuels-code-gen/src/program_bindings/custom_types.rs index 89f097d235..b6d3a500d6 100644 --- a/packages/fuels-code-gen/src/program_bindings/custom_types.rs +++ b/packages/fuels-code-gen/src/program_bindings/custom_types.rs @@ -776,7 +776,7 @@ mod tests { panic, }; - use ::std::{string::ToString, format, vec}; + use ::std::{string::ToString, format, vec, default::Default}; pub use super::super::shared_types::some_shared_lib::SharedStruct; } }; diff --git a/packages/fuels-code-gen/src/program_bindings/generated_code.rs b/packages/fuels-code-gen/src/program_bindings/generated_code.rs index 54a0a0955d..d6b7025b14 100644 --- a/packages/fuels-code-gen/src/program_bindings/generated_code.rs +++ b/packages/fuels-code-gen/src/program_bindings/generated_code.rs @@ -41,7 +41,7 @@ impl GeneratedCode { panic, }; - use #lib::{string::ToString, format, vec}; + use #lib::{string::ToString, format, vec, default::Default}; } } @@ -186,7 +186,7 @@ mod tests { panic, }; - use ::std::{string::ToString, format, vec}; + use ::std::{string::ToString, format, vec, default::Default}; struct SomeType; } @@ -242,7 +242,7 @@ mod tests { marker::Sized, panic, }; - use ::std::{string::ToString, format, vec}; + use ::std::{string::ToString, format, vec, default::Default}; }; let expected_code = quote! { diff --git a/packages/fuels-core/src/codec.rs b/packages/fuels-core/src/codec.rs index 644ea96883..58583b3547 100644 --- a/packages/fuels-core/src/codec.rs +++ b/packages/fuels-core/src/codec.rs @@ -2,6 +2,7 @@ mod abi_decoder; mod abi_encoder; mod function_selector; mod logs; +mod utils; pub use abi_decoder::*; pub use abi_encoder::*; diff --git a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs index d7556c2ff6..bdae9b0a87 100644 --- a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs @@ -2,7 +2,10 @@ use std::{convert::TryInto, str}; use crate::{ checked_round_up_to_word_alignment, - codec::DecoderConfig, + codec::{ + utils::{CodecDirection, CounterWithLimit}, + DecoderConfig, + }, constants::WORD_SIZE, types::{ enum_variants::EnumVariants, @@ -26,8 +29,10 @@ const B256_BYTES_SIZE: usize = 4 * WORD_SIZE; impl BoundedDecoder { pub(crate) fn new(config: DecoderConfig) -> Self { - let depth_tracker = CounterWithLimit::new(config.max_depth, "Depth"); - 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, @@ -370,40 +375,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 { - Err(error!( - InvalidType, - "{} limit ({}) reached while decoding. Try increasing it.", self.name, self.max - )) - } else { - Ok(()) - } - } - - fn decrease(&mut self) { - if self.count > 0 { - self.count -= 1; - } - } -} - fn peek_u128(bytes: &[u8]) -> Result { let slice = peek_fixed::(bytes)?; Ok(u128::from_be_bytes(*slice)) diff --git a/packages/fuels-core/src/codec/abi_encoder.rs b/packages/fuels-core/src/codec/abi_encoder.rs index b20e2795cc..c685761eaa 100644 --- a/packages/fuels-core/src/codec/abi_encoder.rs +++ b/packages/fuels-core/src/codec/abi_encoder.rs @@ -1,255 +1,65 @@ -use fuel_types::bytes::padded_len_usize; +mod bounded_encoder; +use std::default::Default; use crate::{ - checked_round_up_to_word_alignment, - types::{ - errors::Result, - pad_u16, pad_u32, - unresolved_bytes::{Data, UnresolvedBytes}, - EnumSelector, StaticStringToken, Token, U256, - }, + codec::abi_encoder::bounded_encoder::BoundedEncoder, + types::{errors::Result, unresolved_bytes::UnresolvedBytes, Token}, }; -/// Insert zero following the padding strategy -#[derive(Clone, Copy)] -pub enum InsertPadding { - /// Zeros are inserted on the left until it fills an integer quantity of words - Left, - /// Zeros are inserted on the right until it fills an integer quantity of words - Right, +#[derive(Debug, Clone, Copy)] +pub struct EncoderConfig { + /// Entering a struct, array, tuple, enum or vector increases the depth. Encoding will fail if + /// the current depth becomes greater than `max_depth` configured here. + pub max_depth: usize, + /// Every encoded argument will increase the token count. Encoding will fail if the current + /// token count becomes greater than `max_tokens` configured here. + pub max_tokens: usize, + /// The total memory size of the top-level token must fit in the available memory of the + /// system. + pub max_total_enum_width: usize, } -pub struct ABIEncoder; - -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(args: &[Token]) -> Result { - let data = if args.len() == 1 { - match args[0] { - Token::Bool(arg_bool) => vec![Self::encode_bool_as_u64(arg_bool)], - Token::U8(arg_u8) => vec![Self::encode_u8_as_u64(arg_u8)], - _ => Self::encode_tokens(args, true)?, - } - } else { - Self::encode_tokens(args, true)? - }; - - Ok(UnresolvedBytes::new(data)) - } - - fn encode_tokens(tokens: &[Token], word_aligned: bool) -> Result> { - let mut offset_in_bytes = 0; - let mut data = vec![]; - - for token in tokens.iter() { - let mut new_data = Self::encode_token(token)?; - offset_in_bytes += new_data.iter().map(|x| x.size_in_bytes()).sum::(); - - data.append(&mut new_data); - - if word_aligned { - let padding = vec![ - 0u8; - checked_round_up_to_word_alignment(offset_in_bytes)? - - offset_in_bytes - ]; - if !padding.is_empty() { - offset_in_bytes += padding.len(); - data.push(Data::Inline(padding)); - } - } - } - - Ok(data) - } - - fn encode_token(arg: &Token) -> Result> { - let encoded_token = match arg { - Token::Bool(arg_bool) => vec![Self::encode_bool_as_byte(*arg_bool)], - Token::U8(arg_u8) => vec![Self::encode_u8_as_byte(*arg_u8)], - Token::U16(arg_u16) => vec![Self::encode_u16(*arg_u16)], - Token::U32(arg_u32) => vec![Self::encode_u32(*arg_u32)], - Token::U64(arg_u64) => vec![Self::encode_u64(*arg_u64)], - Token::U128(arg_u128) => vec![Self::encode_u128(*arg_u128)], - Token::U256(arg_u256) => vec![Self::encode_u256(*arg_u256)], - Token::B256(arg_bits256) => vec![Self::encode_b256(arg_bits256)], - Token::Array(arg_array) => Self::encode_array(arg_array)?, - Token::Vector(data) => Self::encode_vector(data)?, - Token::StringSlice(arg_string) => Self::encode_string_slice(arg_string)?, - Token::StringArray(arg_string) => vec![Self::encode_string_array(arg_string)?], - Token::Struct(arg_struct) => Self::encode_struct(arg_struct)?, - Token::Enum(arg_enum) => Self::encode_enum(arg_enum)?, - Token::Tuple(arg_tuple) => Self::encode_tuple(arg_tuple)?, - Token::Unit => vec![Self::encode_unit()], - Token::RawSlice(data) => Self::encode_raw_slice(data.to_vec())?, - Token::Bytes(data) => Self::encode_bytes(data.to_vec())?, - // `String` in Sway has the same memory layout as the bytes type - Token::String(string) => Self::encode_bytes(string.clone().into_bytes())?, - }; - - Ok(encoded_token) - } - - fn encode_unit() -> Data { - Data::Inline(vec![0u8]) - } - - fn encode_tuple(arg_tuple: &[Token]) -> Result> { - Self::encode_tokens(arg_tuple, true) - } - - fn encode_struct(subcomponents: &[Token]) -> Result> { - Self::encode_tokens(subcomponents, true) - } - - fn encode_array(arg_array: &[Token]) -> Result> { - Self::encode_tokens(arg_array, false) - } - - fn encode_b256(arg_bits256: &[u8; 32]) -> Data { - Data::Inline(arg_bits256.to_vec()) - } - - fn encode_bool_as_byte(arg_bool: bool) -> Data { - Data::Inline(vec![u8::from(arg_bool)]) - } - - fn encode_bool_as_u64(arg_bool: bool) -> Data { - Data::Inline(vec![0, 0, 0, 0, 0, 0, 0, u8::from(arg_bool)]) - } - - fn encode_u128(arg_u128: u128) -> Data { - Data::Inline(arg_u128.to_be_bytes().to_vec()) - } - - fn encode_u256(arg_u256: U256) -> Data { - let mut bytes = [0u8; 32]; - arg_u256.to_big_endian(&mut bytes); - Data::Inline(bytes.to_vec()) - } - - fn encode_u64(arg_u64: u64) -> Data { - Data::Inline(arg_u64.to_be_bytes().to_vec()) - } - - fn encode_u32(arg_u32: u32) -> Data { - Data::Inline(pad_u32(arg_u32).to_vec()) - } - - fn encode_u16(arg_u16: u16) -> Data { - Data::Inline(pad_u16(arg_u16).to_vec()) - } - - fn encode_u8_as_byte(arg_u8: u8) -> Data { - Data::Inline(vec![arg_u8]) - } - - fn encode_u8_as_u64(arg_u8: u8) -> Data { - Data::Inline(vec![0, 0, 0, 0, 0, 0, 0, arg_u8]) - } - - fn encode_enum(selector: &EnumSelector) -> Result> { - let (discriminant, token_within_enum, variants) = selector; - - let mut encoded_enum = vec![Self::encode_discriminant(*discriminant)]; - - // Enums that contain only Units as variants have only their discriminant encoded. - if !variants.only_units_inside() { - let variant_param_type = variants.param_type_of_variant(*discriminant)?; - let padding_amount = variants.compute_padding_amount_in_bytes(variant_param_type)?; - - encoded_enum.push(Data::Inline(vec![0; padding_amount])); - - let token_data = Self::encode_token(token_within_enum)?; - encoded_enum.extend(token_data); +// ANCHOR: default_encoder_config +impl Default for EncoderConfig { + fn default() -> Self { + Self { + max_depth: 45, + max_tokens: 10_000, + max_total_enum_width: 10_000, } - - Ok(encoded_enum) - } - - fn encode_discriminant(discriminant: u64) -> Data { - Self::encode_u64(discriminant) - } - - fn encode_vector(data: &[Token]) -> Result> { - let encoded_data = Self::encode_tokens(data, false)?; - let cap = data.len() as u64; - let len = data.len() as u64; - - // A vector is expected to be encoded as 3 WORDs -- a ptr, a cap and a - // len. This means that we must place the encoded vector elements - // somewhere else. Hence the use of Data::Dynamic which will, when - // resolved, leave behind in its place only a pointer to the actual - // data. - Ok(vec![ - Data::Dynamic(encoded_data), - Self::encode_u64(cap), - Self::encode_u64(len), - ]) } +} +// ANCHOR_END: default_encoder_config - fn encode_raw_slice(mut data: Vec) -> Result> { - let len = data.len(); - - zeropad_to_word_alignment(&mut data); - - let encoded_data = vec![Data::Inline(data)]; - - Ok(vec![ - Data::Dynamic(encoded_data), - Self::encode_u64(len as u64), - ]) - } - - fn encode_string_slice(arg_string: &StaticStringToken) -> Result> { - let encodable_str = arg_string.get_encodable_str()?; - - let encoded_data = Data::Inline(encodable_str.as_bytes().to_vec()); - let len = Self::encode_u64(encodable_str.len() as u64); - - Ok(vec![Data::Dynamic(vec![encoded_data]), len]) - } +#[derive(Default, Clone, Debug)] +pub struct ABIEncoder { + pub config: EncoderConfig, +} - fn encode_string_array(arg_string: &StaticStringToken) -> Result { - Ok(Data::Inline(crate::types::pad_string( - arg_string.get_encodable_str()?, - ))) +impl ABIEncoder { + pub fn new(config: EncoderConfig) -> Self { + Self { config } } - fn encode_bytes(mut data: Vec) -> Result> { - let len = data.len(); - - zeropad_to_word_alignment(&mut data); - - let cap = data.len() as u64; - let encoded_data = vec![Data::Inline(data)]; - - Ok(vec![ - Data::Dynamic(encoded_data), - Self::encode_u64(cap), - Self::encode_u64(len as u64), - ]) + /// Encodes `Token`s in `args` following the ABI specs defined + /// [here](https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/abi.md) + pub fn encode(&self, args: &[Token]) -> Result { + BoundedEncoder::new(self.config).encode(args) } } -fn zeropad_to_word_alignment(data: &mut Vec) { - let padded_length = padded_len_usize(data.len()); - data.resize(padded_length, 0); -} - #[cfg(test)] mod tests { - use std::slice; - use itertools::chain; use sha2::{Digest, Sha256}; + use std::slice; use super::*; + use crate::types::errors::Error; use crate::{ codec::first_four_bytes_of_sha256_hash, constants::WORD_SIZE, - types::{enum_variants::EnumVariants, param_types::ParamType}, + types::{enum_variants::EnumVariants, param_types::ParamType, StaticStringToken, U256}, }; const VEC_METADATA_SIZE: usize = 3 * WORD_SIZE; @@ -294,7 +104,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -330,7 +140,7 @@ mod tests { 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::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -364,7 +174,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -398,7 +208,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -437,7 +247,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -481,7 +291,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -522,7 +332,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -561,7 +371,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -603,7 +413,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -652,7 +462,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -702,7 +512,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); assert_eq!(encoded_function_selector, expected_function_selector); @@ -719,7 +529,9 @@ mod tests { let enum_variants = EnumVariants::new(types)?; let enum_selector = Box::new((1, Token::U64(42), enum_variants)); - let encoded = ABIEncoder::encode(slice::from_ref(&Token::Enum(enum_selector)))?.resolve(0); + let encoded = ABIEncoder::default() + .encode(slice::from_ref(&Token::Enum(enum_selector)))? + .resolve(0); let enum_discriminant_enc = vec![0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1]; let u64_enc = vec![0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a]; @@ -786,7 +598,9 @@ mod tests { let top_level_enum_token = Token::Enum(Box::new((0, struct_a_token, top_level_enum_variants))); - let encoded = ABIEncoder::encode(slice::from_ref(&top_level_enum_token))?.resolve(0); + let encoded = 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 @@ -847,7 +661,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}"); @@ -945,7 +759,7 @@ mod tests { let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature); - let encoded = ABIEncoder::encode(&args)?.resolve(0); + let encoded = ABIEncoder::default().encode(&args)?.resolve(0); assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded)); assert_eq!(encoded_function_selector, expected_function_selector); @@ -959,7 +773,9 @@ mod tests { let types = vec![ParamType::Unit, ParamType::Unit]; let enum_selector = Box::new((1, Token::Unit, EnumVariants::new(types)?)); - let actual = ABIEncoder::encode(&[Token::Enum(enum_selector)])?.resolve(0); + let actual = ABIEncoder::default() + .encode(&[Token::Enum(enum_selector)])? + .resolve(0); assert_eq!(actual, expected); Ok(()) @@ -969,8 +785,9 @@ mod tests { 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]; - let actual = - ABIEncoder::encode(&[Token::Struct(vec![Token::Unit, Token::U32(5)])])?.resolve(0); + let actual = ABIEncoder::default() + .encode(&[Token::Struct(vec![Token::Unit, Token::U32(5)])])? + .resolve(0); assert_eq!(actual, expected); Ok(()) @@ -985,7 +802,9 @@ mod tests { let types = vec![ParamType::B256, ParamType::Unit]; let enum_selector = Box::new((1, Token::Unit, EnumVariants::new(types)?)); - let actual = ABIEncoder::encode(&[Token::Enum(enum_selector)])?.resolve(0); + let actual = ABIEncoder::default() + .encode(&[Token::Enum(enum_selector)])? + .resolve(0); assert_eq!(actual, expected); Ok(()) @@ -998,7 +817,9 @@ mod tests { let token = Token::Vector(vec![Token::U64(5)]); // act - let result = ABIEncoder::encode(&[token])?.resolve(offset as u64); + let result = ABIEncoder::default() + .encode(&[token])? + .resolve(offset as u64); // assert let ptr = [0, 0, 0, 0, 0, 0, 0, 3 * WORD_SIZE as u8 + offset]; @@ -1021,7 +842,9 @@ mod tests { let vec_2 = Token::Vector(vec![Token::U64(6)]); // act - let result = ABIEncoder::encode(&[vec_1, vec_2])?.resolve(offset as u64); + let result = ABIEncoder::default() + .encode(&[vec_1, vec_2])? + .resolve(offset as u64); // assert let vec1_data_offset = 6 * WORD_SIZE as u8 + offset; @@ -1056,7 +879,9 @@ mod tests { let token = Token::Enum(Box::new(selector)); // act - let result = ABIEncoder::encode(&[token])?.resolve(offset as u64); + let result = ABIEncoder::default() + .encode(&[token])? + .resolve(offset as u64); // assert let discriminant = vec![0, 0, 0, 0, 0, 0, 0, 1]; @@ -1097,7 +922,9 @@ mod tests { let vec_token = Token::Vector(vec![enum_token]); // act - let result = ABIEncoder::encode(&[vec_token])?.resolve(offset as u64); + let result = ABIEncoder::default() + .encode(&[vec_token])? + .resolve(offset as u64); // assert const PADDING: usize = std::mem::size_of::<[u8; 32]>() - WORD_SIZE; @@ -1122,7 +949,9 @@ mod tests { let token = Token::Struct(vec![Token::Vector(vec![Token::U64(5)]), Token::U8(9)]); // act - let result = ABIEncoder::encode(&[token])?.resolve(offset as u64); + let result = ABIEncoder::default() + .encode(&[token])? + .resolve(offset as u64); // assert let vec1_ptr = ((VEC_METADATA_SIZE + WORD_SIZE + offset) as u64) @@ -1147,7 +976,9 @@ mod tests { let token = Token::Vector(vec![Token::Vector(vec![Token::U8(5), Token::U8(6)])]); // act - let result = ABIEncoder::encode(&[token])?.resolve(offset as u64); + let result = ABIEncoder::default() + .encode(&[token])? + .resolve(offset as u64); // assert let vec1_data_offset = (VEC_METADATA_SIZE + offset) as u64; @@ -1178,7 +1009,7 @@ mod tests { let offset = 40; // act - let encoded_bytes = ABIEncoder::encode(&[token])?.resolve(offset); + let encoded_bytes = ABIEncoder::default().encode(&[token])?.resolve(offset); // assert let ptr = [0, 0, 0, 0, 0, 0, 0, 64]; @@ -1200,7 +1031,7 @@ mod tests { let offset = 40; // act - let encoded_bytes = ABIEncoder::encode(&[token])?.resolve(offset); + let encoded_bytes = ABIEncoder::default().encode(&[token])?.resolve(offset); // assert let ptr = [0, 0, 0, 0, 0, 0, 0, 56].to_vec(); @@ -1223,7 +1054,7 @@ mod tests { let offset = 40; // act - let encoded_std_string = ABIEncoder::encode(&[token])?.resolve(offset); + let encoded_std_string = ABIEncoder::default().encode(&[token])?.resolve(offset); // assert let ptr = [0, 0, 0, 0, 0, 0, 0, 64]; @@ -1242,12 +1073,103 @@ mod tests { fn encoding_large_unsigned_integers() -> Result<()> { let token = Token::U128(u128::MAX); let expected_encoding = [255; 16]; - let result = ABIEncoder::encode(&[token])?.resolve(0); + 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::encode(&[token])?.resolve(0); + let result = ABIEncoder::default().encode(&[token])?.resolve(0); assert_eq!(result, expected_encoding); Ok(()) } + + #[test] + fn capacity_overflow_is_caught() -> Result<()> { + let token = Token::Enum(Box::new(( + 1, + Token::String("".to_string()), + EnumVariants::new(vec![ + ParamType::StringArray(18446742977385549567), + ParamType::U8, + ])?, + ))); + let capacity_overflow_error = ABIEncoder::default().encode(&[token]).unwrap_err(); + assert!(capacity_overflow_error + .to_string() + .contains("Try increasing encoder max memory")); + Ok(()) + } + + #[test] + fn max_depth_surpassed() { + const MAX_DEPTH: usize = 2; + let config = EncoderConfig { + max_depth: MAX_DEPTH, + ..Default::default() + }; + let msg = "Depth limit (2) reached while encoding. Try increasing it.".to_string(); + + [nested_struct, nested_enum, nested_tuple, nested_array] + .iter() + .map(|fun| fun(MAX_DEPTH + 1)) + .for_each(|token| { + assert_decoding_failed(config, token, &msg); + }) + } + + fn assert_decoding_failed(config: EncoderConfig, token: Token, msg: &str) { + let encoder = ABIEncoder::new(config); + + let err = encoder.encode(&[token]); + + let Err(Error::InvalidType(actual_msg)) = err else { + panic!("Unexpected an InvalidType error! Got: {err:?}"); + }; + assert_eq!(actual_msg, msg); + } + + fn nested_struct(depth: usize) -> Token { + let fields = if depth == 1 { + vec![Token::U8(255), Token::String("bloopblip".to_string())] + } else { + vec![nested_struct(depth - 1)] + }; + + Token::Struct(fields) + } + + fn nested_enum(depth: usize) -> Token { + if depth == 0 { + return Token::U8(255); + } + + let inner_enum = nested_enum(depth - 1); + + // Create a basic EnumSelector for the current level (the `EnumVariants` is not + // actually accurate but it's not used for encoding) + let selector = ( + 0u64, + inner_enum, + EnumVariants::new(vec![ParamType::U64]).unwrap(), + ); + + Token::Enum(Box::new(selector)) + } + + fn nested_array(depth: usize) -> Token { + if depth == 1 { + Token::Array(vec![Token::U8(255)]) + } else { + Token::Array(vec![nested_array(depth - 1)]) + } + } + + fn nested_tuple(depth: usize) -> Token { + let fields = if depth == 1 { + vec![Token::U8(255), Token::String("bloopblip".to_string())] + } else { + vec![nested_tuple(depth - 1)] + }; + + Token::Tuple(fields) + } } diff --git a/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs b/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs new file mode 100644 index 0000000000..28ca327618 --- /dev/null +++ b/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs @@ -0,0 +1,277 @@ +use crate::codec::utils::CodecDirection; +use crate::{ + checked_round_up_to_word_alignment, + codec::{utils::CounterWithLimit, EncoderConfig}, + error, + types::{ + errors::Result, + pad_u16, pad_u32, + unresolved_bytes::{Data, UnresolvedBytes}, + EnumSelector, StaticStringToken, Token, U256, + }, +}; +use fuel_types::bytes::padded_len_usize; + +pub(crate) struct BoundedEncoder { + depth_tracker: CounterWithLimit, + token_tracker: CounterWithLimit, + max_total_enum_width: usize, +} + +impl BoundedEncoder { + pub(crate) fn new(config: EncoderConfig) -> 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, + max_total_enum_width: config.max_total_enum_width, + } + } + + /// 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. + let data = if args.len() == 1 { + match args[0] { + Token::Bool(arg_bool) => vec![Self::encode_bool_as_u64(arg_bool)], + Token::U8(arg_u8) => vec![Self::encode_u8_as_u64(arg_u8)], + _ => self.encode_tokens(args, true)?, + } + } else { + self.encode_tokens(args, true)? + }; + + Ok(UnresolvedBytes::new(data)) + } + + fn encode_tokens(&mut self, tokens: &[Token], word_aligned: bool) -> Result> { + let mut offset_in_bytes = 0; + let mut data = vec![]; + + for token in tokens.iter() { + self.token_tracker.increase()?; + let mut new_data = self.encode_token(token)?; + offset_in_bytes += new_data.iter().map(|x| x.size_in_bytes()).sum::(); + + data.append(&mut new_data); + + if word_aligned { + let padding = vec![ + 0u8; + checked_round_up_to_word_alignment(offset_in_bytes)? + - offset_in_bytes + ]; + if !padding.is_empty() { + offset_in_bytes += padding.len(); + data.push(Data::Inline(padding)); + } + } + } + + 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> { + let encoded_token = match arg { + Token::Unit => vec![Self::encode_unit()], + Token::U8(arg_u8) => vec![Self::encode_u8_as_byte(*arg_u8)], + Token::U16(arg_u16) => vec![Self::encode_u16(*arg_u16)], + Token::U32(arg_u32) => vec![Self::encode_u32(*arg_u32)], + Token::U64(arg_u64) => vec![Self::encode_u64(*arg_u64)], + Token::U128(arg_u128) => vec![Self::encode_u128(*arg_u128)], + Token::U256(arg_u256) => vec![Self::encode_u256(*arg_u256)], + Token::Bool(arg_bool) => vec![Self::encode_bool_as_byte(*arg_bool)], + Token::B256(arg_bits256) => vec![Self::encode_b256(arg_bits256)], + Token::RawSlice(data) => Self::encode_raw_slice(data.to_vec())?, + Token::StringSlice(arg_string) => Self::encode_string_slice(arg_string)?, + Token::StringArray(arg_string) => vec![Self::encode_string_array(arg_string)?], + Token::Array(arg_array) => { + self.run_w_depth_tracking(|ctx| ctx.encode_array(arg_array))? + } + Token::Struct(arg_struct) => { + self.run_w_depth_tracking(|ctx| ctx.encode_struct(arg_struct))? + } + Token::Enum(arg_enum) => self.run_w_depth_tracking(|ctx| ctx.encode_enum(arg_enum))?, + Token::Tuple(arg_tuple) => { + self.run_w_depth_tracking(|ctx| ctx.encode_tuple(arg_tuple))? + } + Token::Vector(data) => self.run_w_depth_tracking(|ctx| ctx.encode_vector(data))?, + Token::Bytes(data) => Self::encode_bytes(data.to_vec())?, + // `String` in Sway has the same memory layout as the bytes type + Token::String(string) => Self::encode_bytes(string.clone().into_bytes())?, + }; + + Ok(encoded_token) + } + + fn encode_unit() -> Data { + Data::Inline(vec![0u8]) + } + + fn encode_tuple(&mut self, arg_tuple: &[Token]) -> Result> { + self.encode_tokens(arg_tuple, true) + } + + fn encode_struct(&mut self, subcomponents: &[Token]) -> Result> { + self.encode_tokens(subcomponents, true) + } + + fn encode_array(&mut self, arg_array: &[Token]) -> Result> { + self.encode_tokens(arg_array, false) + } + + fn encode_b256(arg_bits256: &[u8; 32]) -> Data { + Data::Inline(arg_bits256.to_vec()) + } + + fn encode_bool_as_byte(arg_bool: bool) -> Data { + Data::Inline(vec![u8::from(arg_bool)]) + } + + fn encode_bool_as_u64(arg_bool: bool) -> Data { + Data::Inline(vec![0, 0, 0, 0, 0, 0, 0, u8::from(arg_bool)]) + } + + fn encode_u128(arg_u128: u128) -> Data { + Data::Inline(arg_u128.to_be_bytes().to_vec()) + } + + fn encode_u256(arg_u256: U256) -> Data { + let mut bytes = [0u8; 32]; + arg_u256.to_big_endian(&mut bytes); + Data::Inline(bytes.to_vec()) + } + + fn encode_u64(arg_u64: u64) -> Data { + Data::Inline(arg_u64.to_be_bytes().to_vec()) + } + + fn encode_u32(arg_u32: u32) -> Data { + Data::Inline(pad_u32(arg_u32).to_vec()) + } + + fn encode_u16(arg_u16: u16) -> Data { + Data::Inline(pad_u16(arg_u16).to_vec()) + } + + fn encode_u8_as_byte(arg_u8: u8) -> Data { + Data::Inline(vec![arg_u8]) + } + + fn encode_u8_as_u64(arg_u8: u8) -> Data { + Data::Inline(vec![0, 0, 0, 0, 0, 0, 0, arg_u8]) + } + + fn encode_enum(&mut self, selector: &EnumSelector) -> Result> { + let (discriminant, token_within_enum, variants) = selector; + + let mut encoded_enum = vec![Self::encode_discriminant(*discriminant)]; + + // Enums that contain only Units as variants have only their discriminant encoded. + if !variants.only_units_inside() { + let variant_param_type = variants.param_type_of_variant(*discriminant)?; + let enum_width_in_bytes = variants.compute_enum_width_in_bytes()?; + + if enum_width_in_bytes > self.max_total_enum_width { + return Err(error!( + InvalidData, + "Cannot encode Enum with variants {variants:?}: it is {enum_width_in_bytes} bytes wide. Try increasing encoder max memory." + )); + } + let padding_amount = variants.compute_padding_amount_in_bytes(variant_param_type)?; + + encoded_enum.push(Data::Inline(vec![0; padding_amount])); + + let token_data = self.encode_token(token_within_enum)?; + encoded_enum.extend(token_data); + } + + Ok(encoded_enum) + } + + fn encode_discriminant(discriminant: u64) -> Data { + Self::encode_u64(discriminant) + } + + fn encode_vector(&mut self, data: &[Token]) -> Result> { + let encoded_data = self.encode_tokens(data, false)?; + let cap = data.len() as u64; + let len = data.len() as u64; + + // A vector is expected to be encoded as 3 WORDs -- a ptr, a cap and a + // len. This means that we must place the encoded vector elements + // somewhere else. Hence the use of Data::Dynamic which will, when + // resolved, leave behind in its place only a pointer to the actual + // data. + Ok(vec![ + Data::Dynamic(encoded_data), + Self::encode_u64(cap), + Self::encode_u64(len), + ]) + } + + fn encode_raw_slice(mut data: Vec) -> Result> { + let len = data.len(); + + zeropad_to_word_alignment(&mut data); + + let encoded_data = vec![Data::Inline(data)]; + + Ok(vec![ + Data::Dynamic(encoded_data), + Self::encode_u64(len as u64), + ]) + } + + fn encode_string_slice(arg_string: &StaticStringToken) -> Result> { + let encodable_str = arg_string.get_encodable_str()?; + + let encoded_data = Data::Inline(encodable_str.as_bytes().to_vec()); + let len = Self::encode_u64(encodable_str.len() as u64); + + Ok(vec![Data::Dynamic(vec![encoded_data]), len]) + } + + fn encode_string_array(arg_string: &StaticStringToken) -> Result { + Ok(Data::Inline(crate::types::pad_string( + arg_string.get_encodable_str()?, + ))) + } + + fn encode_bytes(mut data: Vec) -> Result> { + let len = data.len(); + + zeropad_to_word_alignment(&mut data); + + let cap = data.len() as u64; + let encoded_data = vec![Data::Inline(data)]; + + Ok(vec![ + Data::Dynamic(encoded_data), + Self::encode_u64(cap), + Self::encode_u64(len as u64), + ]) + } +} + +fn zeropad_to_word_alignment(data: &mut Vec) { + let padded_length = padded_len_usize(data.len()); + data.resize(padded_length, 0); +} diff --git a/packages/fuels-core/src/codec/function_selector.rs b/packages/fuels-core/src/codec/function_selector.rs index b77748da67..4877ac15eb 100644 --- a/packages/fuels-core/src/codec/function_selector.rs +++ b/packages/fuels-core/src/codec/function_selector.rs @@ -105,10 +105,11 @@ macro_rules! fn_selector { pub use fn_selector; +/// This uses the default `EncoderConfig` configuration. #[macro_export] macro_rules! calldata { ( $($arg: expr),* ) => { - ::fuels::core::codec::ABIEncoder::encode(&[$(::fuels::core::traits::Tokenizable::into_token($arg)),*]) + ::fuels::core::codec::ABIEncoder::default().encode(&[$(::fuels::core::traits::Tokenizable::into_token($arg)),*]) .map(|ub| ub.resolve(0)) } } diff --git a/packages/fuels-core/src/codec/utils.rs b/packages/fuels-core/src/codec/utils.rs new file mode 100644 index 0000000000..a87d070574 --- /dev/null +++ b/packages/fuels-core/src/codec/utils.rs @@ -0,0 +1,54 @@ +use crate::types::errors::{error, Result}; + +pub(crate) struct CounterWithLimit { + count: usize, + max: usize, + name: String, + direction: CodecDirection, +} + +#[derive(Debug)] +pub(crate) enum CodecDirection { + Encoding, + Decoding, +} + +impl std::fmt::Display for CodecDirection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CodecDirection::Encoding => write!(f, "encoding"), + CodecDirection::Decoding => write!(f, "decoding"), + } + } +} + +impl CounterWithLimit { + pub(crate) fn new(max: usize, name: impl Into, direction: CodecDirection) -> Self { + Self { + count: 0, + max, + direction, + name: name.into(), + } + } + + pub(crate) fn increase(&mut self) -> Result<()> { + self.count += 1; + if self.count > self.max { + return Err(error!( + InvalidType, + "{} limit ({}) reached while {}. Try increasing it.", + self.name, + self.max, + self.direction + )); + } + Ok(()) + } + + pub(crate) fn decrease(&mut self) { + if self.count > 0 { + self.count -= 1; + } + } +} diff --git a/packages/fuels-core/src/types/enum_variants.rs b/packages/fuels-core/src/types/enum_variants.rs index fc2f9d0a58..268c3643ac 100644 --- a/packages/fuels-core/src/types/enum_variants.rs +++ b/packages/fuels-core/src/types/enum_variants.rs @@ -67,6 +67,7 @@ impl EnumVariants { /// biggest variant) and returns it. pub fn compute_padding_amount_in_bytes(&self, variant_param_type: &ParamType) -> Result { let enum_width = self.compute_enum_width_in_bytes()?; + // No need to use checked arithmetics since we called `compute_enum_width_in_bytes` let biggest_variant_width = enum_width - ENUM_DISCRIMINANT_BYTE_WIDTH; let variant_width = variant_param_type.compute_encoding_in_bytes()?; Ok(biggest_variant_width - variant_width) diff --git a/packages/fuels-programs/src/call_utils.rs b/packages/fuels-programs/src/call_utils.rs index d9a58f0e86..c2dea89152 100644 --- a/packages/fuels-programs/src/call_utils.rs +++ b/packages/fuels-programs/src/call_utils.rs @@ -7,6 +7,7 @@ use fuel_types::{Address, Word}; use fuels_accounts::Account; use fuels_core::{ constants::WORD_SIZE, + error, offsets::call_script_data_offset, types::{ bech32::{Bech32Address, Bech32ContractId}, @@ -109,7 +110,7 @@ pub(crate) async fn transaction_builder_from_contract_calls( let data_offset = call_script_data_offset(consensus_parameters, calls_instructions_len); let (script_data, call_param_offsets) = - build_script_data_from_contract_calls(calls, data_offset); + build_script_data_from_contract_calls(calls, data_offset)?; let script = get_instructions(calls, call_param_offsets)?; let required_asset_amounts = calculate_required_asset_amounts(calls); @@ -250,7 +251,7 @@ pub(crate) fn get_instructions( pub(crate) fn build_script_data_from_contract_calls( calls: &[ContractCall], data_offset: usize, -) -> (Vec, Vec) { +) -> Result<(Vec, Vec)> { let mut script_data = vec![]; let mut param_offsets = vec![]; @@ -303,7 +304,11 @@ pub(crate) fn build_script_data_from_contract_calls( segment_offset }; - let bytes = call.encoded_args.resolve(encoded_args_start_offset as Word); + let bytes = call + .encoded_args + .as_ref() + .map(|ub| ub.resolve(encoded_args_start_offset as Word)) + .map_err(|e| error!(InvalidData, "Cannot encode contract call arguments: {e}"))?; script_data.extend(bytes); // the data segment that holds the parameters for the next call @@ -311,7 +316,7 @@ pub(crate) fn build_script_data_from_contract_calls( segment_offset = data_offset + script_data.len(); } - (script_data, param_offsets) + Ok((script_data, param_offsets)) } /// Returns the VM instructions for calling a contract method @@ -599,7 +604,7 @@ mod test { pub fn new_with_random_id() -> Self { ContractCall { contract_id: random_bech32_contract_id(), - encoded_args: Default::default(), + encoded_args: Ok(Default::default()), encoded_selector: [0; 8], call_parameters: Default::default(), compute_custom_input_offset: false, @@ -643,14 +648,14 @@ mod test { // Call 2 has multiple inputs, compute_custom_input_offset will be true let args = [Token::U8(1), Token::U16(2), Token::U8(3)] - .map(|token| ABIEncoder::encode(&[token]).unwrap()) + .map(|token| ABIEncoder::default().encode(&[token]).unwrap()) .to_vec(); let calls: Vec = (0..NUM_CALLS) .map(|i| ContractCall { contract_id: contract_ids[i].clone(), encoded_selector: selectors[i], - encoded_args: args[i].clone(), + 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, variable_outputs: vec![], @@ -662,7 +667,8 @@ mod test { .collect(); // Act - let (script_data, param_offsets) = build_script_data_from_contract_calls(&calls, 0); + let (script_data, param_offsets) = + build_script_data_from_contract_calls(&calls, 0).unwrap(); // Assert assert_eq!(param_offsets.len(), NUM_CALLS); diff --git a/packages/fuels-programs/src/contract.rs b/packages/fuels-programs/src/contract.rs index 6b4f50f43c..88e6780201 100644 --- a/packages/fuels-programs/src/contract.rs +++ b/packages/fuels-programs/src/contract.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + default::Default, fmt::Debug, fs, marker::PhantomData, @@ -10,6 +11,7 @@ use fuel_tx::{ AssetId, Bytes32, Contract as FuelContract, ContractId, Output, Receipt, Salt, StorageSlot, }; use fuels_accounts::{provider::TransactionCost, Account}; +use fuels_core::codec::EncoderConfig; use fuels_core::{ codec::{ABIEncoder, DecoderConfig, LogDecoder}, constants::{BASE_ASSET_ID, DEFAULT_CALL_PARAMS_AMOUNT}, @@ -400,7 +402,7 @@ fn validate_path_and_extension(file_path: &Path, extension: &str) -> Result<()> /// Contains all data relevant to a single contract call pub struct ContractCall { pub contract_id: Bech32ContractId, - pub encoded_args: UnresolvedBytes, + pub encoded_args: Result, pub encoded_selector: Selector, pub call_parameters: CallParameters, pub compute_custom_input_offset: bool, @@ -710,7 +712,8 @@ pub fn method_hash( args: &[Token], log_decoder: LogDecoder, is_payable: bool, -) -> Result> { + encoder_config: EncoderConfig, +) -> ContractCallHandler { let encoded_selector = signature; let tx_policies = TxPolicies::default(); @@ -718,7 +721,7 @@ pub fn method_hash( let compute_custom_input_offset = should_compute_custom_input_offset(args); - let unresolved_bytes = ABIEncoder::encode(args)?; + let unresolved_bytes = ABIEncoder::new(encoder_config).encode(args); let contract_call = ContractCall { contract_id, encoded_selector, @@ -732,7 +735,7 @@ pub fn method_hash( custom_assets: Default::default(), }; - Ok(ContractCallHandler { + ContractCallHandler { contract_call, tx_policies, cached_tx_id: None, @@ -740,7 +743,7 @@ pub fn method_hash( datatype: PhantomData, log_decoder, decoder_config: Default::default(), - }) + } } // If the data passed into the contract method is an integer or a diff --git a/packages/fuels-programs/src/script_calls.rs b/packages/fuels-programs/src/script_calls.rs index 09fdfeeaed..0551093eb6 100644 --- a/packages/fuels-programs/src/script_calls.rs +++ b/packages/fuels-programs/src/script_calls.rs @@ -8,6 +8,7 @@ use fuels_accounts::{ }; use fuels_core::{ codec::{DecoderConfig, LogDecoder}, + error, offsets::base_offset_script, traits::{Parameterize, Tokenizable}, types::{ @@ -39,7 +40,7 @@ use crate::{ /// Contains all data relevant to a single script call pub struct ScriptCall { pub script_binary: Vec, - pub encoded_args: UnresolvedBytes, + pub encoded_args: Result, pub inputs: Vec, pub outputs: Vec, pub external_contracts: Vec, @@ -95,7 +96,7 @@ where { pub fn new( script_binary: Vec, - encoded_args: UnresolvedBytes, + encoded_args: Result, account: T, provider: Provider, log_decoder: LogDecoder, @@ -166,8 +167,11 @@ where let consensus_parameters = self.provider.consensus_parameters(); let script_offset = base_offset_script(consensus_parameters) + padded_len_usize(self.script_call.script_binary.len()); - - Ok(self.script_call.encoded_args.resolve(script_offset as u64)) + self.script_call + .encoded_args + .as_ref() + .map(|ub| ub.resolve(script_offset as u64)) + .map_err(|e| error!(InvalidData, "Cannot encode script call arguments: {e}")) } async fn prepare_inputs_outputs(&self) -> Result<(Vec, Vec)> { diff --git a/packages/fuels/tests/bindings.rs b/packages/fuels/tests/bindings.rs index 5db87fd589..872364a45f 100644 --- a/packages/fuels/tests/bindings.rs +++ b/packages/fuels/tests/bindings.rs @@ -34,7 +34,7 @@ async fn compile_bindings_from_contract_file() { .methods() .takes_int_returns_bool(42); - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), @@ -93,7 +93,7 @@ async fn compile_bindings_from_inline_contract() -> Result<()> { let call_handler = contract_instance.methods().takes_ints_returns_bool(42_u32); - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), @@ -166,7 +166,7 @@ async fn compile_bindings_array_input() -> Result<()> { let input = [1, 2, 3]; let call_handler = contract_instance.methods().takes_array(input); - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), @@ -243,7 +243,7 @@ async fn compile_bindings_bool_array_input() -> Result<()> { let input = [true, false, true]; let call_handler = contract_instance.methods().takes_array(input); - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), @@ -310,7 +310,7 @@ async fn compile_bindings_string_input() -> Result<()> { ); // ANCHOR_END: contract_takes_string - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), @@ -381,7 +381,7 @@ async fn compile_bindings_b256_input() -> Result<()> { let call_handler = contract_instance.methods().takes_b256(Bits256(arg)); // ANCHOR_END: 256_arg - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), @@ -451,7 +451,7 @@ async fn compile_bindings_evm_address_input() -> Result<()> { let call_handler = contract_instance.methods().takes_evm_address(arg); // ANCHOR_END: evm_address_arg - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), @@ -556,7 +556,7 @@ async fn compile_bindings_struct_input() -> Result<()> { let call_handler = contract_instance.methods().takes_struct(input); - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), @@ -660,7 +660,8 @@ async fn compile_bindings_nested_struct_input() -> Result<()> { let call_handler = contract_instance .methods() .takes_nested_struct(input.clone()); - let encoded_args = ABIEncoder::encode(slice::from_ref(&input.into_token())) + let encoded_args = ABIEncoder::default() + .encode(slice::from_ref(&input.into_token())) .unwrap() .resolve(0); @@ -749,7 +750,7 @@ async fn compile_bindings_enum_input() -> Result<()> { let call_handler = contract_instance.methods().takes_enum(variant); - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), diff --git a/packages/fuels/tests/configurables.rs b/packages/fuels/tests/configurables.rs index a417e9a405..5a245220d1 100644 --- a/packages/fuels/tests/configurables.rs +++ b/packages/fuels/tests/configurables.rs @@ -1,4 +1,5 @@ use fuels::{prelude::*, types::SizedAsciiString}; +use fuels_core::codec::EncoderConfig; #[tokio::test] async fn contract_uses_default_configurables() -> Result<()> { @@ -92,10 +93,10 @@ async fn contract_configurables() -> Result<()> { }; let new_enum = EnumWithGeneric::VariantTwo; - let configurables = MyContractConfigurables::new() - .with_STR_4(new_str.clone()) - .with_STRUCT(new_struct.clone()) - .with_ENUM(new_enum.clone()); + let configurables = MyContractConfigurables::default() + .with_STR_4(new_str.clone())? + .with_STRUCT(new_struct.clone())? + .with_ENUM(new_enum.clone())?; let contract_id = Contract::load_from( "tests/contracts/configurables/out/debug/configurables.bin", @@ -143,10 +144,13 @@ async fn script_configurables() -> Result<()> { }; let new_enum = EnumWithGeneric::VariantTwo; - let configurables = MyScriptConfigurables::new() - .with_STR_4(new_str.clone()) - .with_STRUCT(new_struct.clone()) - .with_ENUM(new_enum.clone()); + let configurables = MyScriptConfigurables::new(EncoderConfig { + max_tokens: 5, + ..Default::default() + }) + .with_STR_4(new_str.clone())? + .with_STRUCT(new_struct.clone())? + .with_ENUM(new_enum.clone())?; let response = instance .with_configurables(configurables) @@ -168,3 +172,29 @@ async fn script_configurables() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_configurable_encoder_config_is_applied() { + abigen!(Script(name="MyScript", abi="packages/fuels/tests/scripts/script_configurables/out/debug/script_configurables-abi.json")); + + let new_struct = StructWithGeneric { + field_1: 16u8, + field_2: 32, + }; + + let _configurables = MyScriptConfigurables::default() + .with_STRUCT(new_struct.clone()) + .expect("No encoder config, it works"); + + let encoder_config = EncoderConfig { + max_tokens: 1, + ..Default::default() + }; + // Fails when an encoder config is set + let configurables_error = MyScriptConfigurables::new(encoder_config) + .with_STRUCT(new_struct) + .unwrap_err(); + assert!(configurables_error + .to_string() + .contains("Token limit (1) reached while encoding. Try increasing it."),) +} diff --git a/packages/fuels/tests/contracts.rs b/packages/fuels/tests/contracts.rs index e66ca24749..231b26b362 100644 --- a/packages/fuels/tests/contracts.rs +++ b/packages/fuels/tests/contracts.rs @@ -1,14 +1,14 @@ +use std::default::Default; #[allow(unused_imports)] use std::future::Future; use std::vec; use fuels::{ accounts::{predicate::Predicate, Account}, - core::codec::{calldata, fn_selector}, + core::codec::{calldata, fn_selector, DecoderConfig, EncoderConfig}, prelude::*, types::Bits256, }; -use fuels_core::codec::DecoderConfig; #[tokio::test] async fn test_multiple_args() -> Result<()> { @@ -635,7 +635,7 @@ async fn test_connect_wallet() -> Result<()> { // pay for call with wallet_2 contract_instance - .with_account(wallet_2.clone())? + .with_account(wallet_2.clone()) .methods() .initialize_counter(42) .with_tx_policies(tx_policies) @@ -1458,7 +1458,7 @@ async fn can_configure_decoding_of_contract_return() -> Result<()> { let methods = contract_instance.methods(); { // Single call: Will not work if max_tokens not big enough - methods.i_return_a_1k_el_array().with_decoder_config(DecoderConfig{max_tokens: 100, ..Default::default()}).call().await.expect_err( + methods.i_return_a_1k_el_array().with_decoder_config(DecoderConfig {max_tokens: 100, ..Default::default()}).call().await.expect_err( "Should have failed because there are more tokens than what is supported by default.", ); } @@ -1639,7 +1639,7 @@ async fn heap_types_correctly_offset_in_create_transactions_w_storage_slots() -> ); let provider = wallet.try_provider()?.clone(); - let data = MyPredicateEncoder::encode_data(18, 24, vec![2, 4, 42]); + let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?; let predicate = Predicate::load_from( "tests/types/predicates/predicate_vector/out/debug/predicate_vector.bin", )? @@ -1775,3 +1775,56 @@ async fn contract_custom_call_build_without_signatures() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn contract_encoder_config_is_applied() { + setup_program_test!( + Abigen(Contract( + name = "TestContract", + project = "packages/fuels/tests/contracts/contract_test" + )), + Wallets("wallet") + ); + let contract_id = Contract::load_from( + "tests/contracts/contract_test/out/debug/contract_test.bin", + LoadConfiguration::default(), + ) + .expect("Contract can be loaded") + .deploy(&wallet, TxPolicies::default()) + .await + .expect("Contract can be deployed"); + + let instance = TestContract::new(contract_id.clone(), wallet.clone()); + + let _encoding_ok = instance + .methods() + .get(0, 1) + .call() + .await + .expect("Should not fail as it uses the default encoder config"); + + let encoder_config = EncoderConfig { + max_tokens: 1, + ..Default::default() + }; + let instance_with_encoder_config = instance.with_encoder_config(encoder_config); + // uses 2 tokens when 1 is the limit + let encoding_error = instance_with_encoder_config + .methods() + .get(0, 1) + .call() + .await + .unwrap_err(); + assert!(encoding_error + .to_string() + .contains("Cannot encode contract call arguments: Invalid type: Token limit (1) reached while encoding.")); + let encoding_error = instance_with_encoder_config + .methods() + .get(0, 1) + .simulate() + .await + .unwrap_err(); + assert!(encoding_error + .to_string() + .contains("Cannot encode contract call arguments: Invalid type: Token limit (1) reached while encoding.")); +} diff --git a/packages/fuels/tests/from_token.rs b/packages/fuels/tests/from_token.rs index 51c653f2dc..663453e59b 100644 --- a/packages/fuels/tests/from_token.rs +++ b/packages/fuels/tests/from_token.rs @@ -91,7 +91,7 @@ async fn create_struct_from_decoded_tokens() -> Result<()> { let call_handler = contract_instance.methods().takes_struct(struct_from_tokens); - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), @@ -206,7 +206,7 @@ async fn create_nested_struct_from_decoded_tokens() -> Result<()> { .methods() .takes_nested_struct(nested_struct_from_tokens); - let encoded_args = call_handler.contract_call.encoded_args.resolve(0); + let encoded_args = call_handler.contract_call.encoded_args.unwrap().resolve(0); let encoded = format!( "{}{}", hex::encode(call_handler.contract_call.encoded_selector), diff --git a/packages/fuels/tests/predicates.rs b/packages/fuels/tests/predicates.rs index 817f7f7f45..c6ac94b23c 100644 --- a/packages/fuels/tests/predicates.rs +++ b/packages/fuels/tests/predicates.rs @@ -8,11 +8,13 @@ use fuels::{ transaction_builders::{BuildableTransaction, ScriptTransactionBuilder}, }, }; +use fuels_core::codec::EncoderConfig; use fuels_core::{ codec::ABIEncoder, traits::Tokenizable, types::{coin_type::CoinType, input::Input}, }; +use std::default::Default; async fn assert_address_balance( address: &Bech32Address, @@ -146,7 +148,7 @@ async fn spend_predicate_coins_messages_basic() -> Result<()> { abi = "packages/fuels/tests/predicates/basic_predicate/out/debug/basic_predicate-abi.json" )); - let predicate_data = MyPredicateEncoder::encode_data(4097, 4097); + let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?; let mut predicate: Predicate = Predicate::load_from("tests/predicates/basic_predicate/out/debug/basic_predicate.bin")? @@ -197,7 +199,7 @@ async fn pay_with_predicate() -> Result<()> { ) ); - let predicate_data = MyPredicateEncoder::encode_data(32768); + let predicate_data = MyPredicateEncoder::default().encode_data(32768)?; let mut predicate: Predicate = Predicate::load_from("tests/types/predicates/u64/out/debug/u64.bin")? @@ -251,7 +253,7 @@ async fn pay_with_predicate_vector_data() -> Result<()> { ) ); - let predicate_data = MyPredicateEncoder::encode_data(12, 30, vec![2, 4, 42]); + let predicate_data = MyPredicateEncoder::default().encode_data(12, 30, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "tests/types/predicates/predicate_vector/out/debug/predicate_vector.bin", @@ -300,7 +302,7 @@ async fn predicate_contract_transfer() -> Result<()> { "packages/fuels/tests/types/predicates/predicate_vector/out/debug/predicate_vector-abi.json" )); - let predicate_data = MyPredicateEncoder::encode_data(2, 40, vec![2, 4, 42]); + let predicate_data = MyPredicateEncoder::default().encode_data(2, 40, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "tests/types/predicates/predicate_vector/out/debug/predicate_vector.bin", @@ -357,7 +359,7 @@ async fn predicate_transfer_to_base_layer() -> Result<()> { "packages/fuels/tests/types/predicates/predicate_vector/out/debug/predicate_vector-abi.json" )); - let predicate_data = MyPredicateEncoder::encode_data(22, 20, vec![2, 4, 42]); + let predicate_data = MyPredicateEncoder::default().encode_data(22, 20, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "tests/types/predicates/predicate_vector/out/debug/predicate_vector.bin", @@ -404,7 +406,7 @@ async fn predicate_transfer_with_signed_resources() -> Result<()> { "packages/fuels/tests/types/predicates/predicate_vector/out/debug/predicate_vector-abi.json" )); - let predicate_data = MyPredicateEncoder::encode_data(2, 40, vec![2, 4, 42]); + let predicate_data = MyPredicateEncoder::default().encode_data(2, 40, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "tests/types/predicates/predicate_vector/out/debug/predicate_vector.bin", @@ -489,7 +491,7 @@ async fn contract_tx_and_call_params_with_predicate() -> Result<()> { ) ); - let predicate_data = MyPredicateEncoder::encode_data(22, 20, vec![2, 4, 42]); + let predicate_data = MyPredicateEncoder::default().encode_data(22, 20, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "tests/types/predicates/predicate_vector/out/debug/predicate_vector.bin", @@ -566,7 +568,7 @@ async fn diff_asset_predicate_payment() -> Result<()> { ) ); - let predicate_data = MyPredicateEncoder::encode_data(28, 14, vec![2, 4, 42]); + let predicate_data = MyPredicateEncoder::default().encode_data(28, 14, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "tests/types/predicates/predicate_vector/out/debug/predicate_vector.bin", @@ -617,11 +619,12 @@ async fn predicate_configurables() -> Result<()> { }; let new_enum = EnumWithGeneric::VariantTwo; - let configurables = MyPredicateConfigurables::new() - .with_STRUCT(new_struct.clone()) - .with_ENUM(new_enum.clone()); + let configurables = MyPredicateConfigurables::default() + .with_STRUCT(new_struct.clone())? + .with_ENUM(new_enum.clone())?; - let predicate_data = MyPredicateEncoder::encode_data(8u8, true, new_struct, new_enum); + let predicate_data = + MyPredicateEncoder::default().encode_data(8u8, true, new_struct, new_enum)?; let mut predicate: Predicate = Predicate::load_from( "tests/predicates/predicate_configurables/out/debug/predicate_configurables.bin", @@ -669,7 +672,7 @@ async fn predicate_adjust_fee_persists_message_w_data() -> Result<()> { abi = "packages/fuels/tests/predicates/basic_predicate/out/debug/basic_predicate-abi.json" )); - let predicate_data = MyPredicateEncoder::encode_data(4097, 4097); + let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?; let mut predicate: Predicate = Predicate::load_from("tests/predicates/basic_predicate/out/debug/basic_predicate.bin")? @@ -708,7 +711,7 @@ async fn predicate_transfer_non_base_asset() -> Result<()> { abi = "packages/fuels/tests/predicates/basic_predicate/out/debug/basic_predicate-abi.json" )); - let predicate_data = MyPredicateEncoder::encode_data(32, 32); + let predicate_data = MyPredicateEncoder::default().encode_data(32, 32)?; let mut predicate: Predicate = Predicate::load_from("tests/predicates/basic_predicate/out/debug/basic_predicate.bin")? @@ -770,7 +773,7 @@ async fn predicate_can_access_manually_added_witnesses() -> Result<()> { abi = "packages/fuels/tests/predicates/predicate_witnesses/out/debug/predicate_witnesses-abi.json" )); - let predicate_data = MyPredicateEncoder::encode_data(0, 1); + let predicate_data = MyPredicateEncoder::default().encode_data(0, 1)?; let mut predicate: Predicate = Predicate::load_from( "tests/predicates/predicate_witnesses/out/debug/predicate_witnesses.bin", @@ -800,8 +803,12 @@ async fn predicate_can_access_manually_added_witnesses() -> Result<()> { .build(&provider) .await?; - let witness = ABIEncoder::encode(&[64u8.into_token()])?.resolve(0); - let witness2 = ABIEncoder::encode(&[4096u64.into_token()])?.resolve(0); + let witness = ABIEncoder::default() + .encode(&[64u8.into_token()])? + .resolve(0); + let witness2 = ABIEncoder::default() + .encode(&[4096u64.into_token()])? + .resolve(0); tx.append_witness(witness.into())?; tx.append_witness(witness2.into())?; @@ -836,7 +843,7 @@ async fn tx_id_not_changed_after_adding_witnesses() -> Result<()> { abi = "packages/fuels/tests/predicates/predicate_witnesses/out/debug/predicate_witnesses-abi.json" )); - let predicate_data = MyPredicateEncoder::encode_data(0, 1); + let predicate_data = MyPredicateEncoder::default().encode_data(0, 1)?; let mut predicate: Predicate = Predicate::load_from( "tests/predicates/predicate_witnesses/out/debug/predicate_witnesses.bin", @@ -868,8 +875,12 @@ async fn tx_id_not_changed_after_adding_witnesses() -> Result<()> { let tx_id = tx.id(provider.chain_id()); - let witness = ABIEncoder::encode(&[64u8.into_token()])?.resolve(0); - let witness2 = ABIEncoder::encode(&[4096u64.into_token()])?.resolve(0); + let witness = ABIEncoder::default() + .encode(&[64u8.into_token()])? + .resolve(0); + let witness2 = ABIEncoder::default() + .encode(&[4096u64.into_token()])? + .resolve(0); tx.append_witness(witness.into())?; tx.append_witness(witness2.into())?; @@ -882,3 +893,25 @@ async fn tx_id_not_changed_after_adding_witnesses() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_predicate_encoder_config_is_applied() -> Result<()> { + let encoder_config = EncoderConfig { + max_tokens: 1, + ..Default::default() + }; + abigen!(Predicate( + name = "MyPredicate", + abi = "packages/fuels/tests/predicates/basic_predicate/out/debug/basic_predicate-abi.json" + )); + let _encoding_ok = MyPredicateEncoder::default() + .encode_data(4097, 4097) + .expect("Should not fail as it uses the default encoder config"); + let encoding_error = MyPredicateEncoder::new(encoder_config) + .encode_data(4097, 4097) + .unwrap_err(); + assert!(encoding_error + .to_string() + .contains("Token limit (1) reached while encoding")); + Ok(()) +} diff --git a/packages/fuels/tests/scripts.rs b/packages/fuels/tests/scripts.rs index c210133cdf..c900b029f5 100644 --- a/packages/fuels/tests/scripts.rs +++ b/packages/fuels/tests/scripts.rs @@ -1,5 +1,5 @@ use fuels::{prelude::*, types::Bits256}; -use fuels_core::codec::DecoderConfig; +use fuels_core::codec::{DecoderConfig, EncoderConfig}; #[tokio::test] async fn main_function_arguments() -> Result<()> { @@ -387,3 +387,44 @@ async fn test_script_transaction_builder() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_script_encoder_config_is_applied() { + abigen!(Script( + name = "MyScript", + abi = "packages/fuels/tests/scripts/basic_script/out/debug/basic_script-abi.json" + )); + let wallet = launch_provider_and_get_wallet().await.expect(""); + let bin_path = "../fuels/tests/scripts/basic_script/out/debug/basic_script.bin"; + + let script_instance_without_encoder_config = MyScript::new(wallet.clone(), bin_path); + let _encoding_ok = script_instance_without_encoder_config + .main(1, 2) + .call() + .await + .expect("Should not fail as it uses the default encoder config"); + + let encoder_config = EncoderConfig { + max_tokens: 1, + ..Default::default() + }; + let script_instance_with_encoder_config = + MyScript::new(wallet.clone(), bin_path).with_encoder_config(encoder_config); + // uses 2 tokens when 1 is the limit + let encoding_error = script_instance_with_encoder_config + .main(1, 2) + .call() + .await + .unwrap_err(); + assert!(encoding_error + .to_string() + .contains("Cannot encode script call arguments: Invalid type: Token limit (1) reached while encoding.")); + let encoding_error = script_instance_with_encoder_config + .main(1, 2) + .simulate() + .await + .unwrap_err(); + assert!(encoding_error + .to_string() + .contains("Cannot encode script call arguments: Invalid type: Token limit (1) reached while encoding.")); +} diff --git a/packages/fuels/tests/types_predicates.rs b/packages/fuels/tests/types_predicates.rs index 5d6c71f1f7..c089642955 100644 --- a/packages/fuels/tests/types_predicates.rs +++ b/packages/fuels/tests/types_predicates.rs @@ -1,3 +1,4 @@ +use std::default::Default; use std::path::Path; use fuels::{ @@ -133,7 +134,7 @@ async fn spend_predicate_coins_messages_single_u64() -> Result<()> { abi = "packages/fuels/tests/types/predicates/u64/out/debug/u64-abi.json" )); - let data = MyPredicateEncoder::encode_data(32768); + let data = MyPredicateEncoder::default().encode_data(32768)?; assert_predicate_spendable(data, "tests/types/predicates/u64").await?; @@ -151,7 +152,7 @@ async fn spend_predicate_coins_messages_address() -> Result<()> { .parse() .unwrap(); - let data = MyPredicateEncoder::encode_data(addr); + let data = MyPredicateEncoder::default().encode_data(addr)?; assert_predicate_spendable(data, "tests/types/predicates/address").await?; @@ -165,7 +166,8 @@ async fn spend_predicate_coins_messages_enums() -> Result<()> { abi = "packages/fuels/tests/types/predicates/enums/out/debug/enums-abi.json" )); - let data = MyPredicateEncoder::encode_data(TestEnum::A(32), AnotherTestEnum::B(32)); + let data = + MyPredicateEncoder::default().encode_data(TestEnum::A(32), AnotherTestEnum::B(32))?; assert_predicate_spendable(data, "tests/types/predicates/enums").await?; @@ -179,13 +181,13 @@ async fn spend_predicate_coins_messages_structs() -> Result<()> { abi = "packages/fuels/tests/types/predicates/structs/out/debug/structs-abi.json" )); - let data = MyPredicateEncoder::encode_data( + let data = MyPredicateEncoder::default().encode_data( TestStruct { value: 192 }, AnotherTestStruct { value: 64, number: 128, }, - ); + )?; assert_predicate_spendable(data, "tests/types/predicates/structs").await?; @@ -199,8 +201,8 @@ async fn spend_predicate_coins_messages_tuple() -> Result<()> { abi = "packages/fuels/tests/types/predicates/predicate_tuples/out/debug/predicate_tuples-abi.json" )); - let data = - MyPredicateEncoder::encode_data((16, TestStruct { value: 32 }, TestEnum::Value(64)), 128); + let data = MyPredicateEncoder::default() + .encode_data((16, TestStruct { value: 32 }, TestEnum::Value(64)), 128)?; assert_predicate_spendable(data, "tests/types/predicates/predicate_tuples").await?; @@ -215,7 +217,7 @@ async fn spend_predicate_coins_messages_vector() -> Result<()> { "packages/fuels/tests/types/predicates/predicate_vector/out/debug/predicate_vector-abi.json" )); - let data = MyPredicateEncoder::encode_data(18, 24, vec![2, 4, 42]); + let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?; assert_predicate_spendable(data, "tests/types/predicates/predicate_vector").await?; @@ -246,7 +248,7 @@ async fn spend_predicate_coins_messages_vectors() -> Result<()> { let vec_in_array = [vec![0, 64, 2], vec![0, 1, 2]]; - let data = MyPredicateEncoder::encode_data( + let data = MyPredicateEncoder::default().encode_data( u32_vec, vec_in_vec, struct_in_vec, @@ -258,7 +260,7 @@ async fn spend_predicate_coins_messages_vectors() -> Result<()> { tuple_in_vec, vec_in_tuple, vec_in_a_vec_in_a_struct_in_a_vec, - ); + )?; assert_predicate_spendable(data, "tests/types/predicates/predicate_vectors").await?; @@ -269,10 +271,10 @@ async fn spend_predicate_coins_messages_vectors() -> Result<()> { async fn spend_predicate_coins_messages_generics() -> Result<()> { abigen!(Predicate(name="MyPredicate", abi="packages/fuels/tests/types/predicates/predicate_generics/out/debug/predicate_generics-abi.json")); - let data = MyPredicateEncoder::encode_data( + let data = MyPredicateEncoder::default().encode_data( GenericStruct { value: 64u8 }, GenericEnum::Generic(GenericStruct { value: 64u16 }), - ); + )?; assert_predicate_spendable(data, "tests/types/predicates/predicate_generics").await?; @@ -295,7 +297,7 @@ async fn spend_predicate_coins_messages_bytes_hash() -> Result<()> { "0x173d69ea3d0aa050d01ff7cc60ccd4579b567c465cd115c6876c2da4a332fb99", )?; - let data = MyPredicateEncoder::encode_data(bytes, bits256); + let data = MyPredicateEncoder::default().encode_data(bytes, bits256)?; assert_predicate_spendable(data, "tests/types/predicates/predicate_bytes_hash").await?; @@ -315,7 +317,7 @@ async fn spend_predicate_coins_messages_bytes() -> Result<()> { inner_enum: SomeEnum::Second(bytes), }; - let data = MyPredicateEncoder::encode_data(wrapper); + let data = MyPredicateEncoder::default().encode_data(wrapper)?; assert_predicate_spendable(data, "tests/types/predicates/predicate_bytes").await?; @@ -335,7 +337,7 @@ async fn spend_predicate_coins_messages_raw_slice() -> Result<()> { inner_enum: SomeEnum::Second(raw_slice), }; - let data = MyPredicateEncoder::encode_data(wrapper); + let data = MyPredicateEncoder::default().encode_data(wrapper)?; assert_predicate_spendable(data, "tests/types/predicates/predicate_raw_slice").await?; @@ -357,7 +359,7 @@ async fn predicate_handles_u128() -> Result<()> { abi = "packages/fuels/tests/types/predicates/predicate_u128/out/debug/predicate_u128-abi.json" )); - let data = MyPredicateEncoder::encode_data(u128_from((8, 2))); + let data = MyPredicateEncoder::default().encode_data(u128_from((8, 2)))?; assert_predicate_spendable(data, "tests/types/predicates/predicate_u128").await?; Ok(()) @@ -383,7 +385,7 @@ async fn predicate_handles_u256() -> Result<()> { abi = "packages/fuels/tests/types/predicates/predicate_u256/out/debug/predicate_u256-abi.json" )); - let data = MyPredicateEncoder::encode_data(u256_from((10, 11, 12, 13))); + let data = MyPredicateEncoder::default().encode_data(u256_from((10, 11, 12, 13)))?; assert_predicate_spendable(data, "tests/types/predicates/predicate_u256").await?; Ok(()) @@ -396,7 +398,7 @@ async fn predicate_handles_std_string() -> Result<()> { abi = "packages/fuels/tests/types/predicates/predicate_std_lib_string/out/debug/predicate_std_lib_string-abi.json" )); - let data = MyPredicateEncoder::encode_data(10, 11, String::from("Hello World")); + let data = MyPredicateEncoder::default().encode_data(10, 11, String::from("Hello World"))?; assert_predicate_spendable(data, "tests/types/predicates/predicate_std_lib_string").await?; Ok(()) diff --git a/packages/wasm-tests/src/lib.rs b/packages/wasm-tests/src/lib.rs index c2cf8b2f49..ad34baf7dd 100644 --- a/packages/wasm-tests/src/lib.rs +++ b/packages/wasm-tests/src/lib.rs @@ -2,7 +2,7 @@ extern crate alloc; #[cfg(test)] mod tests { - use std::str::FromStr; + use std::{default::Default, str::FromStr}; use fuels::{ accounts::predicate::Predicate, @@ -112,7 +112,9 @@ mod tests { let original = SomeEnum::V2(SomeStruct { a: 123, b: false }); - let bytes = ABIEncoder::encode(&[original.clone().into_token()])?.resolve(0); + let bytes = ABIEncoder::default() + .encode(&[original.clone().into_token()])? + .resolve(0); let reconstructed = bytes.try_into().unwrap(); @@ -184,8 +186,8 @@ mod tests { ]; let value = 128; - let predicate_data = MyPredicateEncoder::encode_data(value); - let configurables = MyPredicateConfigurables::new().with_U64(value); + let predicate_data = MyPredicateEncoder::default().encode_data(value)?; + let configurables = MyPredicateConfigurables::default().with_U64(value)?; let predicate: Predicate = Predicate::from_code(code.clone()) .with_data(predicate_data)