Skip to content

Commit

Permalink
fix!: encoding capacity overflow (#1249)
Browse files Browse the repository at this point in the history
- This PR closes #1228 by adding the `EncoderConfig` similarly to what
was done in #1066.



BREAKING CHANGE: 
- `Configurables` structs now need to be instantiated through a
`::new(encoder_config)` or `::default()` method.
- `Configurables::with_some_string_config(some_string)` methods now
return a `Result<Configurables>` instead of `Configurables`.
- `Predicates::encode_data` now returns a `Result<UnresolvedBytes>`
instead of `UnresolvedBytes`.
- `PredicateEncoder` structs must be instantiated through a
`::new(encoder_config)` or `::default()` method.

---------

Co-authored-by: MujkicA <[email protected]>
Co-authored-by: Rodrigo Araújo <[email protected]>
Co-authored-by: hal3e <[email protected]>
Co-authored-by: Ahmed Sagdati <[email protected]>
  • Loading branch information
5 people authored Feb 13, 2024
1 parent 9d5051c commit 4cafbdf
Show file tree
Hide file tree
Showing 31 changed files with 955 additions and 436 deletions.
2 changes: 2 additions & 0 deletions docs/src/calling-contracts/low-level-calls.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
24 changes: 24 additions & 0 deletions docs/src/codec/encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
22 changes: 20 additions & 2 deletions examples/codec/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<()> {
Expand All @@ -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<u8> = encoded.resolve(load_memory_address);
//ANCHOR_END: encoding_example
Expand Down Expand Up @@ -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(())
}
}
38 changes: 36 additions & 2 deletions examples/contracts/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#[cfg(test)]
mod tests {
use fuels::core::codec::EncoderConfig;
use fuels::{
core::codec::DecoderConfig,
prelude::{Config, LoadConfiguration, StorageConfiguration},
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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?;
Expand Down Expand Up @@ -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(())
}
}
4 changes: 2 additions & 2 deletions examples/predicates/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?
Expand Down Expand Up @@ -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";

Expand Down
11 changes: 5 additions & 6 deletions examples/rust_bindings/src/rust_bindings_formatted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ pub mod abigen_bindings {
pub fn account(&self) -> T {
self.account.clone()
}
pub fn with_account<U: Account>(&self, account: U) -> Result<MyContract<U>> {
::core::result::Result::Ok(MyContract {
pub fn with_account<U: Account>(&self, account: U) -> MyContract<U> {
MyContract {
contract_id: self.contract_id.clone(),
account,
log_decoder: self.log_decoder.clone(),
})
}
}
pub async fn get_balances(&self) -> Result<::std::collections::HashMap<AssetId, u64>> {
ViewOnlyAccount::try_provider(&self.account)?
Expand Down Expand Up @@ -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<T, u64> {
Expand All @@ -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<T: Account> contract::SettableContract for MyContract<T> {
Expand Down Expand Up @@ -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;

Original file line number Diff line number Diff line change
Expand Up @@ -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<T: ::fuels::accounts::Account> {
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<T: ::fuels::accounts::Account> #name<T>
Expand All @@ -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 {
Expand All @@ -62,8 +65,21 @@ pub(crate) fn contract_bindings(
self.account.clone()
}

pub fn with_account<U: ::fuels::accounts::Account>(&self, account: U) -> ::fuels::types::errors::Result<#name<U>> {
::core::result::Result::Ok(#name { contract_id: self.contract_id.clone(), account, log_decoder: self.log_decoder.clone()})
pub fn with_account<U: ::fuels::accounts::Account>(self, account: U)
-> #name<U> {
#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::<T> {
self.encoder_config = encoder_config;

self
}

pub async fn get_balances(&self) -> ::fuels::types::errors::Result<::std::collections::HashMap<::fuels::types::AssetId, u64>> {
Expand All @@ -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(),
}
}
}
Expand All @@ -86,7 +103,8 @@ pub(crate) fn contract_bindings(
pub struct #methods_name<T: ::fuels::accounts::Account> {
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<T: ::fuels::accounts::Account> #methods_name<T> {
Expand Down Expand Up @@ -157,8 +175,8 @@ pub(crate) fn expand_fn(abi_fun: &FullABIFunction) -> Result<TokenStream> {
&#arg_tokens,
self.log_decoder.clone(),
#is_payable,
self.encoder_config.clone(),
)
.expect("method not found (this should never happen)")
};
generator.set_body(body);

Expand Down Expand Up @@ -355,8 +373,8 @@ mod tests {
],
self.log_decoder.clone(),
false,
self.encoder_config.clone(),
)
.expect("method not found (this should never happen)")
}
};

Expand Down Expand Up @@ -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)")
}
};

Expand Down Expand Up @@ -523,8 +541,8 @@ mod tests {
)],
self.log_decoder.clone(),
false,
self.encoder_config.clone(),
)
.expect("method not found (this should never happen)")
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ pub(crate) struct FunctionGenerator {
output_type: TokenStream,
body: TokenStream,
doc: Option<String>,
is_method: bool,
}

impl FunctionGenerator {
Expand All @@ -38,7 +37,6 @@ impl FunctionGenerator {
output_type: output_type.to_token_stream(),
body: Default::default(),
doc: None,
is_method: true,
})
}

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,14 +60,16 @@ fn expand_fn(abi: &FullProgramABI) -> Result<TokenStream> {
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())
Expand Down
Loading

0 comments on commit 4cafbdf

Please sign in to comment.