diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e871ab05af..5d1e861535 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ env: FUEL_CORE_PATCH_REVISION: "" RUST_VERSION: 1.85.0 FORC_VERSION: 0.68.0 - FORC_PATCH_BRANCH: "ironcev/error-codes-in-abi-json" + FORC_PATCH_BRANCH: "xunilrj/dynamic-types-configurables" FORC_PATCH_REVISION: "" NEXTEST_HIDE_PROGRESS_BAR: "true" NEXTEST_STATUS_LEVEL: "fail" diff --git a/Cargo.toml b/Cargo.toml index d2704574ef..28ee5969ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "examples/predicates", "examples/providers", "examples/rust_bindings", + "examples/scripts", "examples/types", "examples/wallets", "packages/fuels", diff --git a/docs/src/deploying/configurable-constants.md b/docs/src/deploying/configurable-constants.md index 3067aeac26..42bc9a5b70 100644 --- a/docs/src/deploying/configurable-constants.md +++ b/docs/src/deploying/configurable-constants.md @@ -9,5 +9,25 @@ In Sway, you can define `configurable` constants which can be changed during the Each of the configurable constants will get a dedicated `with` method in the SDK. For example, the constant `STR_4` will get the `with_STR_4` method which accepts the same type as defined in the contract code. Below is an example where we chain several `with` methods and deploy the contract with the new constants. ```rust,ignore -{{#include ../../../e2e/tests/configurables.rs:contract_configurables}} +{{#include ../../../examples/contracts/src/lib.rs:contract_configurables}} ``` + +In addition to writing, you are able to read the configurable constants directly from the binary: + +```rust,ignore +{{#include ../../../examples/contracts/src/lib.rs:contract_configurables_reader}} +``` + +If you need to manually read the configurable constants, you can use helper functions provided by the SDK. + +```rust,ignore +{{#include ../../../e2e/tests/configurables.rs:manual_configurables}} +``` + +Similarly, you can read the configurable constants at runtime. + +```rust,ignore +{{#include ../../../e2e/tests/configurables.rs:manual_runtime_configurables}} +``` + +> **Note:** when manually reading configurable constants make sure to call the appropriate method when dealing with static or dynamic configurables. For dynamic configurables use the `indirect` methods. diff --git a/docs/src/predicates/index.md b/docs/src/predicates/index.md index bbe033fe52..7422cc61f0 100644 --- a/docs/src/predicates/index.md +++ b/docs/src/predicates/index.md @@ -54,3 +54,9 @@ Each configurable constant will get a dedicated `with` method in the SDK. For ex ```rust,ignore {{#include ../../../e2e/tests/predicates.rs:predicate_configurables}} ``` + +In addition to writing, you are able to read the configurable constants directly from the binary: + +```rust,ignore +{{#include ../../../examples/predicates/src/lib.rs:predicate_configurables_reader}} +``` diff --git a/docs/src/running-scripts.md b/docs/src/running-scripts.md index e94449541d..6b317c9abc 100644 --- a/docs/src/running-scripts.md +++ b/docs/src/running-scripts.md @@ -65,5 +65,11 @@ Same as contracts, you can define `configurable` constants in `scripts` which ca Each configurable constant will get a dedicated `with` method in the SDK. For example, the constant `STR_4` will get the `with_STR_4` method which accepts the same type defined in sway. Below is an example where we chain several `with` methods and execute the script with the new constants. ```rust,ignore -{{#include ../../e2e/tests/configurables.rs:script_configurables}} +{{#include ../../examples/scripts/src/lib.rs:script_configurables}} +``` + +In addition to writing, you are able to read the configurable constants directly from the binary: + +```rust,ignore +{{#include ../../examples/scripts/src/lib.rs:script_configurables_reader}} ``` diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml index 836dd4eb48..0122776fff 100644 --- a/e2e/Cargo.toml +++ b/e2e/Cargo.toml @@ -22,6 +22,8 @@ fuel-tx = { workspace = true } # used in test assertions tai64 = { workspace = true } tempfile = { workspace = true } +tokio = { workspace = true, features = ["test-util"] } +test-case = { workspace = true } [build-dependencies] anyhow = { workspace = true, features = ["std"] } diff --git a/e2e/Forc.toml b/e2e/Forc.toml index 78548928f9..4b018e06fc 100644 --- a/e2e/Forc.toml +++ b/e2e/Forc.toml @@ -9,6 +9,7 @@ members = [ 'sway/contracts/auth_testing_abi', 'sway/contracts/auth_testing_contract', 'sway/contracts/block_timestamp', + 'sway/contracts/dyn_configurables', 'sway/contracts/configurables', 'sway/contracts/contract_test', 'sway/contracts/huge_contract', @@ -40,6 +41,7 @@ members = [ 'sway/logs/script_with_contract_logs', 'sway/predicates/basic_predicate', 'sway/predicates/predicate_blobs', + 'sway/predicates/predicate_dyn_configurables', 'sway/predicates/predicate_configurables', 'sway/predicates/predicate_witnesses', 'sway/predicates/signatures', @@ -53,6 +55,7 @@ members = [ 'sway/scripts/script_array', 'sway/scripts/script_asserts', 'sway/scripts/script_blobs', + 'sway/scripts/script_dyn_configurables', 'sway/scripts/script_configurables', 'sway/scripts/script_enum', 'sway/scripts/script_needs_custom_decoder', @@ -119,5 +122,6 @@ members = [ 'sway/types/scripts/script_vectors', ] +#TODO: [issue](https://github.com/FuelLabs/fuels-rs/issues/1610) [patch.'https://github.com/fuellabs/sway'] -std = { git = "https://github.com/fuellabs/sway", branch = "ironcev/error-codes-in-abi-json" } +std = { git = "https://github.com/fuellabs/sway", branch = "xunilrj/dynamic-types-configurables" } diff --git a/e2e/sway/contracts/configurables/src/main.sw b/e2e/sway/contracts/configurables/src/main.sw index 90e1bcec99..06f0fae8de 100644 --- a/e2e/sway/contracts/configurables/src/main.sw +++ b/e2e/sway/contracts/configurables/src/main.sw @@ -19,7 +19,7 @@ configurable { U64: u64 = 63, U256: u256 = 0x0000000000000000000000000000000000000000000000000000000000000008u256, B256: b256 = 0x0101010101010101010101010101010101010101010101010101010101010101, - STR_4: str[4] = __to_str_array("fuel"), + STR: str = "fuel", TUPLE: (u8, bool) = (8, true), ARRAY: [u32; 3] = [253, 254, 255], STRUCT: StructWithGeneric = StructWithGeneric { @@ -31,11 +31,11 @@ configurable { //U128: u128 = 128, //TODO: add once https://github.com/FuelLabs/sway/issues/5356 is done abi TestContract { - fn return_configurables() -> (bool, u8, u16, u32, u64, u256, b256, str[4], (u8, bool), [u32; 3], StructWithGeneric, EnumWithGeneric); + fn return_configurables() -> (bool, u8, u16, u32, u64, u256, b256, str, (u8, bool), [u32; 3], StructWithGeneric, EnumWithGeneric); } impl TestContract for Contract { - fn return_configurables() -> (bool, u8, u16, u32, u64, u256, b256, str[4], (u8, bool), [u32; 3], StructWithGeneric, EnumWithGeneric) { - (BOOL, U8, U16, U32, U64, U256, B256, STR_4, TUPLE, ARRAY, STRUCT, ENUM) + fn return_configurables() -> (bool, u8, u16, u32, u64, u256, b256, str, (u8, bool), [u32; 3], StructWithGeneric, EnumWithGeneric) { + (BOOL, U8, U16, U32, U64, U256, B256, STR, TUPLE, ARRAY, STRUCT, ENUM) } } diff --git a/e2e/sway/contracts/dyn_configurables/Forc.toml b/e2e/sway/contracts/dyn_configurables/Forc.toml new file mode 100644 index 0000000000..6989dd30ae --- /dev/null +++ b/e2e/sway/contracts/dyn_configurables/Forc.toml @@ -0,0 +1,5 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "dyn_configurables" diff --git a/e2e/sway/contracts/dyn_configurables/src/main.sw b/e2e/sway/contracts/dyn_configurables/src/main.sw new file mode 100644 index 0000000000..307681cb68 --- /dev/null +++ b/e2e/sway/contracts/dyn_configurables/src/main.sw @@ -0,0 +1,20 @@ +contract; + +configurable { + BOOL: bool = true, + U8: u8 = 8, + STR: str = "sway", + STR_2: str = "forc", + STR_3: str = "fuel", + LAST_U8: u8 = 16, +} + +abi TestContract { + fn return_configurables() -> (bool, u8, str, str, str, u8); +} + +impl TestContract for Contract { + fn return_configurables() -> (bool, u8, str, str, str, u8) { + (BOOL, U8, STR, STR_2, STR_3, LAST_U8) + } +} diff --git a/e2e/sway/predicates/predicate_dyn_configurables/Forc.toml b/e2e/sway/predicates/predicate_dyn_configurables/Forc.toml new file mode 100644 index 0000000000..3609da1605 --- /dev/null +++ b/e2e/sway/predicates/predicate_dyn_configurables/Forc.toml @@ -0,0 +1,5 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "predicate_dyn_configurables" diff --git a/e2e/sway/predicates/predicate_dyn_configurables/src/main.sw b/e2e/sway/predicates/predicate_dyn_configurables/src/main.sw new file mode 100644 index 0000000000..46a41f56bb --- /dev/null +++ b/e2e/sway/predicates/predicate_dyn_configurables/src/main.sw @@ -0,0 +1,21 @@ +predicate; + +configurable { + BOOL: bool = true, + U8: u8 = 8, + STR: str = "sway", + STR_2: str = "forc", + STR_3: str = "fuel", + LAST_U8: u8 = 16, +} + +fn main( + some_bool: bool, + some_u8: u8, + some_str: str, + some_str_2: str, + some_str_3: str, + some_last_u8: u8, +) -> bool { + some_bool == BOOL && some_u8 == U8 && some_str == STR && some_str_2 == STR_2 && some_str_3 == STR_3 && some_last_u8 == LAST_U8 +} diff --git a/e2e/sway/scripts/script_dyn_configurables/Forc.toml b/e2e/sway/scripts/script_dyn_configurables/Forc.toml new file mode 100644 index 0000000000..b1bba29550 --- /dev/null +++ b/e2e/sway/scripts/script_dyn_configurables/Forc.toml @@ -0,0 +1,5 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "script_dyn_configurables" diff --git a/e2e/sway/scripts/script_dyn_configurables/src/main.sw b/e2e/sway/scripts/script_dyn_configurables/src/main.sw new file mode 100644 index 0000000000..886434d49a --- /dev/null +++ b/e2e/sway/scripts/script_dyn_configurables/src/main.sw @@ -0,0 +1,14 @@ +script; + +configurable { + BOOL: bool = true, + U8: u8 = 8, + STR: str = "sway", + STR_2: str = "forc", + STR_3: str = "fuel", + LAST_U8: u8 = 16, +} + +fn main() -> (bool, u8, str, str, str, u8) { + (BOOL, U8, STR, STR_2, STR_3, LAST_U8) +} diff --git a/e2e/sway/scripts/script_struct/src/main.sw b/e2e/sway/scripts/script_struct/src/main.sw index a101d4eef0..fe8766c96c 100644 --- a/e2e/sway/scripts/script_struct/src/main.sw +++ b/e2e/sway/scripts/script_struct/src/main.sw @@ -5,6 +5,7 @@ configurable { number: 10, boolean: true, }, + A_STR: str = "fuel", A_NUMBER: u64 = 11, } @@ -15,5 +16,6 @@ struct MyStruct { fn main(arg: MyStruct) -> u64 { let _calc = MY_STRUCT.number + A_NUMBER; + let _b = A_STR; if arg.boolean { arg.number } else { 0 } } diff --git a/e2e/tests/configurables.rs b/e2e/tests/configurables.rs index c26303b8b3..cf92c69892 100644 --- a/e2e/tests/configurables.rs +++ b/e2e/tests/configurables.rs @@ -1,11 +1,18 @@ use fuels::{ - core::codec::EncoderConfig, + core::{ + ConfigurablesReader, + codec::EncoderConfig, + traits::{Parameterize, Tokenizable}, + }, prelude::*, - types::{Bits256, SizedAsciiString, U256}, + types::{AsciiString, Bits256, SizedAsciiString, U256}, }; +use test_case::test_case; +#[test_case(true ; "regular")] +#[test_case(false ; "use loader")] #[tokio::test] -async fn contract_default_configurables() -> Result<()> { +async fn contract_default_configurables(is_regular: bool) -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/configurables/out/release/configurables-abi.json" @@ -13,13 +20,23 @@ async fn contract_default_configurables() -> Result<()> { let wallet = launch_provider_and_get_wallet().await?; - let contract_id = Contract::load_from( + let contract = Contract::load_from( "sway/contracts/configurables/out/release/configurables.bin", LoadConfiguration::default(), - )? - .deploy_if_not_exists(&wallet, TxPolicies::default()) - .await? - .contract_id; + )?; + + let contract_id = if is_regular { + contract + .deploy_if_not_exists(&wallet, TxPolicies::default()) + .await? + .contract_id + } else { + contract + .convert_to_loader(124)? + .deploy_if_not_exists(&wallet, TxPolicies::default()) + .await? + .contract_id + }; let contract_instance = MyContract::new(contract_id, wallet.clone()); @@ -52,25 +69,29 @@ async fn contract_default_configurables() -> Result<()> { Ok(()) } +#[test_case(true ; "regular")] +#[test_case(false ; "use loader")] #[tokio::test] -async fn script_default_configurables() -> Result<()> { - setup_program_test!( - Wallets("wallet"), - Abigen(Script( - name = "MyScript", - project = "e2e/sway/scripts/script_configurables" - )), - LoadScript( - name = "script_instance", - script = "MyScript", - wallet = "wallet" - ) - ); - - let mut script_instance = script_instance; - script_instance.convert_into_loader().await?; +async fn script_default_configurables(is_regular: bool) -> Result<()> { + abigen!(Script( + name = "MyScript", + abi = "e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json" + )); - let response = script_instance.main().call().await?; + let wallet = launch_provider_and_get_wallet().await?; + let bin_path = "sway/scripts/script_configurables/out/release/script_configurables.bin"; + let mut script_instance = MyScript::new(wallet, bin_path); + + let response = if is_regular { + script_instance.main().call().await? + } else { + script_instance + .convert_into_loader() + .await? + .main() + .call() + .await? + }; let expected_value = ( true, @@ -95,9 +116,10 @@ async fn script_default_configurables() -> Result<()> { Ok(()) } +#[test_case(true ; "regular")] +#[test_case(false ; "use loader")] #[tokio::test] -async fn contract_configurables() -> Result<()> { - // ANCHOR: contract_configurables +async fn contract_configurables(is_regular: bool) -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/configurables/out/release/configurables-abi.json" @@ -105,7 +127,7 @@ async fn contract_configurables() -> Result<()> { let wallet = launch_provider_and_get_wallet().await?; - let str_4: SizedAsciiString<4> = "FUEL".try_into()?; + let str: AsciiString = "FUEL".try_into()?; let new_struct = StructWithGeneric { field_1: 16u8, field_2: 32, @@ -120,22 +142,31 @@ async fn contract_configurables() -> Result<()> { .with_U64(63)? .with_U256(U256::from(8))? .with_B256(Bits256([2; 32]))? - .with_STR_4(str_4.clone())? + .with_STR(str.clone())? .with_TUPLE((7, false))? .with_ARRAY([252, 253, 254])? .with_STRUCT(new_struct.clone())? .with_ENUM(new_enum.clone())?; - let contract_id = Contract::load_from( + let contract = Contract::load_from( "sway/contracts/configurables/out/release/configurables.bin", LoadConfiguration::default().with_configurables(configurables), - )? - .deploy_if_not_exists(&wallet, TxPolicies::default()) - .await? - .contract_id; + )?; + + let contract_id = if is_regular { + contract + .deploy_if_not_exists(&wallet, TxPolicies::default()) + .await? + .contract_id + } else { + contract + .convert_to_loader(124)? + .deploy_if_not_exists(&wallet, TxPolicies::default()) + .await? + .contract_id + }; let contract_instance = MyContract::new(contract_id, wallet.clone()); - // ANCHOR_END: contract_configurables let response = contract_instance .methods() @@ -151,7 +182,7 @@ async fn contract_configurables() -> Result<()> { 63, U256::from(8), Bits256([2; 32]), - str_4, + str, (7, false), [252, 253, 254], new_struct, @@ -163,45 +194,41 @@ async fn contract_configurables() -> Result<()> { Ok(()) } +#[test_case(true ; "regular")] +#[test_case(false ; "use loader")] #[tokio::test] -async fn contract_manual_configurables() -> Result<()> { - setup_program_test!( - Abigen(Contract( - name = "MyContract", - project = "e2e/sway/contracts/configurables" - )), - Wallets("wallet") - ); +async fn contract_dyn_configurables(is_regular: bool) -> Result<()> { + abigen!(Contract( + name = "MyContract", + abi = "e2e/sway/contracts/dyn_configurables/out/release/dyn_configurables-abi.json" + )); - let str_4: SizedAsciiString<4> = "FUEL".try_into()?; - let new_struct = StructWithGeneric { - field_1: 16u8, - field_2: 32, - }; - let new_enum = EnumWithGeneric::VariantTwo; + let wallet = launch_provider_and_get_wallet().await?; let configurables = MyContractConfigurables::default() .with_BOOL(false)? - .with_U8(7)? - .with_U16(15)? - .with_U32(31)? - .with_U64(63)? - .with_U256(U256::from(8))? - .with_B256(Bits256([2; 32]))? - .with_STR_4(str_4.clone())? - .with_TUPLE((7, false))? - .with_ARRAY([252, 253, 254])? - .with_STRUCT(new_struct.clone())? - .with_ENUM(new_enum.clone())?; + .with_U8(6)? + .with_STR("sway-sway".try_into()?)? + .with_STR_3("fuel-fuel".try_into()?)? + .with_LAST_U8(12)?; - let contract_id = Contract::load_from( - "sway/contracts/configurables/out/release/configurables.bin", - LoadConfiguration::default(), - )? - .with_configurables(configurables) - .deploy_if_not_exists(&wallet, TxPolicies::default()) - .await? - .contract_id; + let contract = Contract::load_from( + "sway/contracts/dyn_configurables/out/release/dyn_configurables.bin", + LoadConfiguration::default().with_configurables(configurables), + )?; + + let contract_id = if is_regular { + contract + .deploy_if_not_exists(&wallet, TxPolicies::default()) + .await? + .contract_id + } else { + contract + .convert_to_loader(124)? + .deploy_if_not_exists(&wallet, TxPolicies::default()) + .await? + .contract_id + }; let contract_instance = MyContract::new(contract_id, wallet.clone()); @@ -213,17 +240,11 @@ async fn contract_manual_configurables() -> Result<()> { let expected_value = ( false, - 7, - 15, - 31, - 63, - U256::from(8), - Bits256([2; 32]), - str_4, - (7, false), - [252, 253, 254], - new_struct, - new_enum, + 6, + "sway-sway".try_into()?, + "forc".try_into()?, + "fuel-fuel".try_into()?, + 12, ); assert_eq!(response.value, expected_value); @@ -231,9 +252,10 @@ async fn contract_manual_configurables() -> Result<()> { Ok(()) } +#[test_case(true ; "regular")] +#[test_case(false ; "use loader")] #[tokio::test] -async fn script_configurables() -> Result<()> { - // ANCHOR: script_configurables +async fn script_configurables(is_regular: bool) -> Result<()> { abigen!(Script( name = "MyScript", abi = "e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json" @@ -241,7 +263,7 @@ async fn script_configurables() -> Result<()> { let wallet = launch_provider_and_get_wallet().await?; let bin_path = "sway/scripts/script_configurables/out/release/script_configurables.bin"; - let instance = MyScript::new(wallet, bin_path); + let script_instance = MyScript::new(wallet, bin_path); let str_4: SizedAsciiString<4> = "FUEL".try_into()?; let new_struct = StructWithGeneric { @@ -267,12 +289,18 @@ async fn script_configurables() -> Result<()> { .with_STRUCT(new_struct.clone())? .with_ENUM(new_enum.clone())?; - let response = instance - .with_configurables(configurables) - .main() - .call() - .await?; - // ANCHOR_END: script_configurables + let mut script_instance = script_instance.with_configurables(configurables); + + let response = if is_regular { + script_instance.main().call().await? + } else { + script_instance + .convert_into_loader() + .await? + .main() + .call() + .await? + }; let expected_value = ( false, @@ -294,6 +322,53 @@ async fn script_configurables() -> Result<()> { Ok(()) } +#[test_case(true ; "regular")] +#[test_case(false ; "use loader")] +#[tokio::test] +async fn script_dyn_configurables(is_regular: bool) -> Result<()> { + abigen!(Script( + name = "MyScript", + abi = "e2e/sway/scripts/script_dyn_configurables/out/release/script_dyn_configurables-abi.json" + )); + + let wallet = launch_provider_and_get_wallet().await?; + let bin_path = "sway/scripts/script_dyn_configurables/out/release/script_dyn_configurables.bin"; + let script_instance = MyScript::new(wallet, bin_path); + + let configurables = MyScriptConfigurables::default() + .with_BOOL(false)? + .with_U8(6)? + .with_STR("sway-sway".try_into()?)? + .with_STR_3("fuel-fuel".try_into()?)? + .with_LAST_U8(12)?; + + let mut script_instance = script_instance.with_configurables(configurables); + + let response = if is_regular { + script_instance.main().call().await? + } else { + script_instance + .convert_into_loader() + .await? + .main() + .call() + .await? + }; + + let expected_value = ( + false, + 6, + "sway-sway".try_into()?, + "forc".try_into()?, + "fuel-fuel".try_into()?, + 12, + ); + + assert_eq!(response.value, expected_value); + + Ok(()) +} + #[tokio::test] async fn configurable_encoder_config_is_applied() { abigen!(Script( @@ -329,3 +404,143 @@ async fn configurable_encoder_config_is_applied() { ) } } + +#[tokio::test] +async fn contract_configurables_reader() -> Result<()> { + abigen!(Contract( + name = "MyContract", + abi = "e2e/sway/contracts/dyn_configurables/out/release/dyn_configurables-abi.json" + )); + + let configurables_reader = MyContractConfigurablesReader::load_from( + "sway/contracts/dyn_configurables/out/release/dyn_configurables.bin", + )?; + + let some_bool = configurables_reader.BOOL()?; + let some_u8 = configurables_reader.U8()?; + let some_str = configurables_reader.STR()?; + let some_str2 = configurables_reader.STR_2()?; + let some_str3 = configurables_reader.STR_3()?; + let some_last_u8 = configurables_reader.LAST_U8()?; + + assert!(some_bool); + assert_eq!(some_u8, 8); + assert_eq!(some_str, "sway"); + assert_eq!(some_str2, "forc"); + assert_eq!(some_str3, "fuel"); + assert_eq!(some_last_u8, 16); + + Ok(()) +} + +#[tokio::test] +async fn contract_configurables_reader_manual() -> Result<()> { + // ANCHOR: manual_configurables + let configurables_reader = ConfigurablesReader::load_from( + "sway/contracts/dyn_configurables/out/release/dyn_configurables.bin", + )?; + + let some_bool: bool = configurables_reader.try_from_direct(2648)?; + let some_u8: u8 = configurables_reader.try_from_direct(2688)?; + let some_str: AsciiString = configurables_reader.try_from_indirect(2664)?; + let some_str2: AsciiString = configurables_reader.try_from_indirect(2672)?; + let some_str3: AsciiString = configurables_reader.try_from_indirect(2680)?; + let some_last_u8: u8 = configurables_reader.try_from_direct(2656)?; + // ANCHOR_END: manual_configurables + + assert!(some_bool); + assert_eq!(some_u8, 8); + assert_eq!(some_str, "sway"); + assert_eq!(some_str2, "forc"); + assert_eq!(some_str3, "fuel"); + assert_eq!(some_last_u8, 16); + + Ok(()) +} + +#[tokio::test] +async fn contract_configurables_reader_runtime() -> Result<()> { + // ANCHOR: manual_runtime_configurables + let configurables_reader = ConfigurablesReader::load_from( + "sway/contracts/dyn_configurables/out/release/dyn_configurables.bin", + )?; + + let some_bool = configurables_reader.decode_direct(2648, &bool::param_type())?; + let some_u8 = configurables_reader.decode_direct(2688, &u8::param_type())?; + let some_str = configurables_reader.decode_indirect(2664, &AsciiString::param_type())?; + let some_str2 = configurables_reader.decode_indirect(2672, &AsciiString::param_type())?; + let some_str3 = configurables_reader.decode_indirect(2680, &AsciiString::param_type())?; + let some_last_u8 = configurables_reader.decode_direct(2656, &u8::param_type())?; + // ANCHOR_END: manual_runtime_configurables + + assert_eq!(some_bool, true.into_token()); + assert_eq!(some_u8, 8u8.into_token()); + assert_eq!(some_str, AsciiString::new("sway".to_string())?.into_token()); + assert_eq!( + some_str2, + AsciiString::new("forc".to_string())?.into_token() + ); + assert_eq!( + some_str3, + AsciiString::new("fuel".to_string())?.into_token() + ); + assert_eq!(some_last_u8, 16u8.into_token()); + + Ok(()) +} + +#[tokio::test] +async fn script_configurables_reader() -> Result<()> { + abigen!(Script( + name = "MyScript", + abi = "e2e/sway/scripts/script_dyn_configurables/out/release/script_dyn_configurables-abi.json" + )); + + let configurables_reader = MyScriptConfigurablesReader::load_from( + "sway/scripts/script_dyn_configurables/out/release/script_dyn_configurables.bin", + )?; + + let some_bool = configurables_reader.BOOL()?; + let some_u8 = configurables_reader.U8()?; + let some_str = configurables_reader.STR()?; + let some_str2 = configurables_reader.STR_2()?; + let some_str3 = configurables_reader.STR_3()?; + let some_last_u8 = configurables_reader.LAST_U8()?; + + assert!(some_bool); + assert_eq!(some_u8, 8); + assert_eq!(some_str, "sway"); + assert_eq!(some_str2, "forc"); + assert_eq!(some_str3, "fuel"); + assert_eq!(some_last_u8, 16); + + Ok(()) +} + +#[tokio::test] +async fn predicate_configurables_reader() -> Result<()> { + abigen!(Predicate( + name = "MyPredicate", + abi = "e2e/sway/predicates/predicate_dyn_configurables/out/release/predicate_dyn_configurables-abi.json" + )); + + let configurables_reader = MyPredicateConfigurablesReader::load_from( + "sway/predicates/predicate_dyn_configurables/out/release/predicate_dyn_configurables.bin", + )?; + + let some_bool = configurables_reader.BOOL()?; + let some_u8 = configurables_reader.U8()?; + let some_str = configurables_reader.STR()?; + let some_str2 = configurables_reader.STR_2()?; + let some_str3 = configurables_reader.STR_3()?; + let some_last_u8 = configurables_reader.LAST_U8()?; + + assert!(some_bool); + assert_eq!(some_u8, 8); + assert_eq!(some_str, "sway"); + assert_eq!(some_str2, "forc"); + assert_eq!(some_str3, "fuel"); + assert_eq!(some_last_u8, 16); + + Ok(()) +} diff --git a/e2e/tests/debug_utils.rs b/e2e/tests/debug_utils.rs index 68cc6e63e6..2ebe9bbc22 100644 --- a/e2e/tests/debug_utils.rs +++ b/e2e/tests/debug_utils.rs @@ -370,6 +370,10 @@ async fn can_debug_sway_script() -> Result<()> { .unwrap(), vec![ ("A_NUMBER".to_owned(), "11".to_owned()), + ( + "A_STR".to_owned(), + "AsciiString { data: \"fuel\" }".to_owned() + ), ( "MY_STRUCT".to_owned(), "MyStruct { number: 10, boolean: true }".to_owned() @@ -458,7 +462,7 @@ async fn can_detect_a_loader_script_w_data_section() -> Result<()> { .unwrap(); let expected_blob_id = executable.blob().id(); - let script = executable.code(); + let script = executable.code()?; let ScriptType::Loader { script, blob_id } = ScriptType::detect(&script, &script_data).unwrap() else { @@ -482,6 +486,10 @@ async fn can_detect_a_loader_script_w_data_section() -> Result<()> { .unwrap(), vec![ ("A_NUMBER".to_owned(), "11".to_owned()), + ( + "A_STR".to_owned(), + "AsciiString { data: \"fuel\" }".to_owned() + ), ( "MY_STRUCT".to_owned(), "MyStruct { number: 10, boolean: true }".to_owned() @@ -505,7 +513,7 @@ async fn can_detect_a_loader_script_wo_data_section() -> Result<()> { .unwrap(); let expected_blob_id = executable.blob().id(); - let script = executable.code(); + let script = executable.code()?; let ScriptType::Loader { blob_id, .. } = ScriptType::detect(&script, &[]).unwrap() else { panic!("expected a loader script") diff --git a/e2e/tests/predicates.rs b/e2e/tests/predicates.rs index 33209c6b30..590a7fcac0 100644 --- a/e2e/tests/predicates.rs +++ b/e2e/tests/predicates.rs @@ -11,6 +11,7 @@ use fuels::{ types::{coin::Coin, coin_type::CoinType, input::Input, message::Message, output::Output}, }; use rand::thread_rng; +use test_case::test_case; async fn assert_address_balance( address: &Bech32Address, @@ -733,7 +734,7 @@ async fn predicate_configurables() -> Result<()> { "sway/predicates/predicate_configurables/out/release/predicate_configurables.bin", )? .with_data(predicate_data) - .with_configurables(configurables); + .with_configurables(configurables)?; // ANCHOR_END: predicate_configurables let num_coins = 4; @@ -771,6 +772,91 @@ async fn predicate_configurables() -> Result<()> { Ok(()) } +#[test_case(true ; "regular")] +#[test_case(false ; "use loader")] +#[tokio::test] +async fn predicate_dyn_configurables(is_regular: bool) -> Result<()> { + abigen!(Predicate( + name = "MyPredicate", + abi = "e2e/sway/predicates/predicate_dyn_configurables/out/release/predicate_dyn_configurables-abi.json" + )); + + let configurables = MyPredicateConfigurables::default() + .with_BOOL(false)? + .with_U8(6)? + .with_STR("sway-sway".try_into()?)? + .with_STR_3("fuel-fuel".try_into()?)? + .with_LAST_U8(12)?; + + let predicate_data = MyPredicateEncoder::default().encode_data( + false, + 6u8, + "sway-sway".try_into()?, + "forc".try_into()?, + "fuel-fuel".try_into()?, + 12, + )?; + + let executable = Executable::load_from( + "sway/predicates/predicate_dyn_configurables/out/release/predicate_dyn_configurables.bin", + )? + .with_configurables(configurables); + + let (maybe_loader, code) = if is_regular { + (None, executable.code()?) + } else { + let loader = executable.convert_to_loader()?; + let code = loader.code()?; + + (Some(loader), code) + }; + + let mut predicate: Predicate = Predicate::from_code(code).with_data(predicate_data); + + let num_coins = 4; + let num_messages = 8; + let amount = 16; + let (provider, predicate_balance, receiver, receiver_balance, asset_id, extra_wallet) = + setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; + + if let Some(loader) = maybe_loader { + loader.upload_blob(extra_wallet).await?; + } + predicate.set_provider(provider.clone()); + + let amount_to_send = 36; + let transfer_fee = predicate + .transfer( + receiver.address(), + amount_to_send, + asset_id, + TxPolicies::default(), + ) + .await? + .tx_status + .total_fee; + + // The predicate has spent the funds + assert_address_balance( + predicate.address(), + &provider, + asset_id, + predicate_balance - amount_to_send - transfer_fee, + ) + .await; + + // Funds were transferred + assert_address_balance( + receiver.address(), + &provider, + asset_id, + receiver_balance + amount_to_send, + ) + .await; + + Ok(()) +} + #[tokio::test] async fn predicate_adjust_fee_persists_message_w_data() -> Result<()> { abigen!(Predicate( @@ -1128,7 +1214,7 @@ async fn predicate_blobs() -> Result<()> { .convert_to_loader()? .with_configurables(configurables); - let mut predicate: Predicate = Predicate::from_code(loader.code()).with_data(predicate_data); + let mut predicate: Predicate = Predicate::from_code(loader.code()?).with_data(predicate_data); // ANCHOR_END: preparing_the_predicate let num_coins = 4; @@ -1209,7 +1295,7 @@ async fn predicate_configurables_in_blobs() -> Result<()> { .convert_to_loader()? .with_configurables(configurables); - let mut predicate: Predicate = Predicate::from_code(loader.code()).with_data(predicate_data); + let mut predicate: Predicate = Predicate::from_code(loader.code()?).with_data(predicate_data); let num_coins = 4; let num_messages = 8; @@ -1287,6 +1373,7 @@ async fn predicate_transfer_respects_maturity_and_expiration() -> Result<()> { } let transaction_fee = { provider.produce_blocks(15, None).await?; + predicate .transfer(receiver.address(), amount_to_send, asset_id, tx_policies) .await @@ -1373,7 +1460,7 @@ async fn predicate_tx_input_output() -> Result<()> { "sway/predicates/predicate_tx_input_output/out/release/predicate_tx_input_output.bin", )? .with_data(predicate_data) - .with_configurables(configurables); + .with_configurables(configurables)?; predicate.set_provider(provider.clone()); let asset_id = AssetId::zeroed(); diff --git a/e2e/tests/scripts.rs b/e2e/tests/scripts.rs index c426aa62df..d160743b50 100644 --- a/e2e/tests/scripts.rs +++ b/e2e/tests/scripts.rs @@ -444,7 +444,7 @@ async fn can_be_run_in_blobs_builder() -> Result<()> { let data = encoder.encode(&[token])?; let mut tb = ScriptTransactionBuilder::default() - .with_script(loader.code()) + .with_script(loader.code()?) .with_script_data(data); wallet.adjust_for_fee(&mut tb, 0).await?; @@ -666,16 +666,14 @@ async fn loader_can_be_presented_as_a_normal_script_with_shifted_configurables() .unwrap_or_else(|| regular.data_offset_in_code().unwrap()); let shifted_configurables = configurables - .with_shifted_offsets(-(offset as i64)) - .unwrap() - .with_shifted_offsets(loader.configurables_offset_in_code() as i64) - .unwrap(); + .with_shifted_offsets(-(offset as i64))? + .with_shifted_offsets(loader.configurables_offset_in_code()? as i64)?; let loader_posing_as_normal_script = - Executable::from_bytes(loader.code()).with_configurables(shifted_configurables); + Executable::from_bytes(loader.code()?).with_configurables(shifted_configurables); let mut tb = ScriptTransactionBuilder::default() - .with_script(loader_posing_as_normal_script.code()) + .with_script(loader_posing_as_normal_script.code()?) .with_script_data(data); wallet.adjust_for_fee(&mut tb, 0).await?; diff --git a/e2e/tests/wallets.rs b/e2e/tests/wallets.rs index 124a960014..1896b4384c 100644 --- a/e2e/tests/wallets.rs +++ b/e2e/tests/wallets.rs @@ -529,6 +529,7 @@ async fn wallet_transfer_respects_maturity_and_expiration() -> Result<()> { } let transaction_fee = { provider.produce_blocks(15, None).await?; + wallet .transfer(&receiver, amount_to_send, asset_id, tx_policies) .await diff --git a/examples/contracts/src/lib.rs b/examples/contracts/src/lib.rs index 47211c9e65..521b9b2580 100644 --- a/examples/contracts/src/lib.rs +++ b/examples/contracts/src/lib.rs @@ -120,7 +120,7 @@ mod tests { .await?; // ANCHOR_END: contract_call_cost_estimation - let expected_script_gas = 2498; + let expected_script_gas = 2488; let expected_total_gas = 8750; assert_eq!(transaction_cost.script_gas, expected_script_gas); @@ -686,7 +686,7 @@ mod tests { .await?; // ANCHOR_END: multi_call_cost_estimation - let expected_script_gas = 4029; + let expected_script_gas = 4033; let expected_total_gas = 10858; assert_eq!(transaction_cost.script_gas, expected_script_gas); @@ -1275,4 +1275,103 @@ mod tests { // ANCHOR_END: decoding_script_transactions Ok(()) } + + #[tokio::test] + async fn contract_configurables() -> Result<()> { + use fuels::prelude::*; + + // ANCHOR: contract_configurables + abigen!(Contract( + name = "MyContract", + abi = "e2e/sway/contracts/configurables/out/release/configurables-abi.json" + )); + + let wallet = launch_provider_and_get_wallet().await?; + + let str: fuels::types::AsciiString = "FUEL".try_into()?; + let new_struct = StructWithGeneric { + field_1: 16u8, + field_2: 32, + }; + let new_enum = EnumWithGeneric::VariantTwo; + + let configurables = MyContractConfigurables::default() + .with_BOOL(false)? + .with_U8(7)? + .with_U16(15)? + .with_U32(31)? + .with_U64(63)? + .with_U256(fuels::types::U256::from(8))? + .with_B256(Bits256([2; 32]))? + .with_STR(str.clone())? + .with_TUPLE((7, false))? + .with_ARRAY([252, 253, 254])? + .with_STRUCT(new_struct.clone())? + .with_ENUM(new_enum.clone())?; + + let contract_id = Contract::load_from( + "../../e2e/sway/contracts/configurables/out/release/configurables.bin", + LoadConfiguration::default().with_configurables(configurables), + )? + .deploy_if_not_exists(&wallet, TxPolicies::default()) + .await? + .contract_id; + + let contract_instance = MyContract::new(contract_id, wallet.clone()); + // ANCHOR_END: contract_configurables + + let response = contract_instance + .methods() + .return_configurables() + .call() + .await?; + + let expected_value = ( + false, + 7, + 15, + 31, + 63, + fuels::types::U256::from(8), + Bits256([2; 32]), + str, + (7, false), + [252, 253, 254], + new_struct, + new_enum, + ); + + assert_eq!(response.value, expected_value); + + Ok(()) + } + + #[tokio::test] + async fn contract_configurables_reader() -> Result<()> { + use fuels::prelude::*; + + // ANCHOR: contract_configurables_reader + abigen!(Contract( + name = "MyContract", + abi = "e2e/sway/contracts/configurables/out/release/configurables-abi.json" + )); + + let configurables_reader = MyContractConfigurablesReader::load_from( + "../../e2e/sway/contracts/configurables/out/release/configurables.bin", + )?; + + let some_bool = configurables_reader.BOOL()?; + let some_u8 = configurables_reader.U8()?; + let some_str = configurables_reader.STR()?; + let some_array = configurables_reader.ARRAY()?; + // ANCHOR_END: contract_configurables_reader + + let str: fuels::types::AsciiString = "fuel".try_into()?; + assert!(some_bool); + assert_eq!(some_u8, 8); + assert_eq!(some_str, str); + assert_eq!(some_array, [253, 254, 255]); + + Ok(()) + } } diff --git a/examples/predicates/src/lib.rs b/examples/predicates/src/lib.rs index 2e289be088..ebc9878e4c 100644 --- a/examples/predicates/src/lib.rs +++ b/examples/predicates/src/lib.rs @@ -179,4 +179,30 @@ mod tests { // ANCHOR_END: predicate_data_unlock Ok(()) } + + #[tokio::test] + async fn predicate_configurables_reader() -> Result<()> { + use fuels::prelude::*; + + // ANCHOR: predicate_configurables_reader + abigen!(Predicate( + name = "MyPredicate", + abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json" + )); + + let configurables_reader = MyPredicateConfigurablesReader::load_from( + "../../e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables.bin", + )?; + + let some_bool = configurables_reader.BOOL()?; + let some_u8 = configurables_reader.U8()?; + let some_array = configurables_reader.ARRAY()?; + // ANCHOR_END: predicate_configurables_reader + + assert!(some_bool); + assert_eq!(some_u8, 8); + assert_eq!(some_array, [253, 254, 255]); + + Ok(()) + } } diff --git a/examples/scripts/Cargo.toml b/examples/scripts/Cargo.toml new file mode 100644 index 0000000000..726f516062 --- /dev/null +++ b/examples/scripts/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "fuels-example-scripts" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +publish = false +repository = { workspace = true } +description = "Fuel Rust SDK scripts examples." + +[dev-dependencies] +fuels = { workspace = true, features = ["default"] } +tokio = { workspace = true, features = ["full"] } diff --git a/examples/scripts/src/lib.rs b/examples/scripts/src/lib.rs new file mode 100644 index 0000000000..4594a11300 --- /dev/null +++ b/examples/scripts/src/lib.rs @@ -0,0 +1,99 @@ +#[cfg(test)] +mod tests { + use fuels::prelude::Result; + + #[tokio::test] + async fn script_configurables() -> Result<()> { + use fuels::{ + prelude::*, + types::{Bits256, SizedAsciiString, U256}, + }; + + // ANCHOR: script_configurables + abigen!(Script( + name = "MyScript", + abi = "e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json" + )); + + let wallet = launch_provider_and_get_wallet().await?; + let bin_path = + "../../e2e/sway/scripts/script_configurables/out/release/script_configurables.bin"; + let script_instance = MyScript::new(wallet, bin_path); + + let str_4: SizedAsciiString<4> = "FUEL".try_into()?; + let new_struct = StructWithGeneric { + field_1: 16u8, + field_2: 32, + }; + let new_enum = EnumWithGeneric::VariantTwo; + + let configurables = MyScriptConfigurables::default() + .with_BOOL(false)? + .with_U8(7)? + .with_U16(15)? + .with_U32(31)? + .with_U64(63)? + .with_U256(U256::from(8))? + .with_B256(Bits256([2; 32]))? + .with_STR_4(str_4.clone())? + .with_TUPLE((7, false))? + .with_ARRAY([252, 253, 254])? + .with_STRUCT(new_struct.clone())? + .with_ENUM(new_enum.clone())?; + + let response = script_instance + .with_configurables(configurables) + .main() + .call() + .await?; + // ANCHOR_END: script_configurables + + let expected_value = ( + false, + 7, + 15, + 31, + 63, + U256::from(8), + Bits256([2; 32]), + str_4, + (7, false), + [252, 253, 254], + new_struct, + new_enum, + ); + + assert_eq!(response.value, expected_value); + + Ok(()) + } + + #[tokio::test] + async fn script_configurables_reader() -> Result<()> { + use fuels::prelude::*; + + // ANCHOR: script_configurables_reader + abigen!(Script( + name = "MyScript", + abi = "e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json" + )); + + let configurables_reader = MyScriptConfigurablesReader::load_from( + "../../e2e/sway/scripts/script_configurables/out/release/script_configurables.bin", + )?; + + let some_bool = configurables_reader.BOOL()?; + let some_u8 = configurables_reader.U8()?; + let some_str_4 = configurables_reader.STR_4()?; + let some_array = configurables_reader.ARRAY()?; + // ANCHOR_END: script_configurables_reader + + let str_4: fuels::types::SizedAsciiString<4> = "fuel".try_into()?; + assert!(some_bool); + assert_eq!(some_u8, 8); + assert_eq!(some_str_4, str_4); + assert_eq!(some_array, [253, 254, 255]); + + Ok(()) + } +} diff --git a/packages/fuels-accounts/src/predicate.rs b/packages/fuels-accounts/src/predicate.rs index 64b164b782..02aa3bfc11 100644 --- a/packages/fuels-accounts/src/predicate.rs +++ b/packages/fuels-accounts/src/predicate.rs @@ -72,12 +72,13 @@ impl Predicate { } } - pub fn with_configurables(mut self, configurables: impl Into) -> Self { + pub fn with_configurables(mut self, configurables: impl Into) -> Result { let configurables: Configurables = configurables.into(); - configurables.update_constants_in(&mut self.code); + configurables.update_constants_in(&mut self.code)?; let address = Self::calculate_address(&self.code); self.address = address; - self + + Ok(self) } } 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 17ecabd997..90129b6af5 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 @@ -8,7 +8,9 @@ use crate::{ program_bindings::{ abigen::{ bindings::function_generator::FunctionGenerator, - configurables::generate_code_for_configurable_constants, + configurables::{ + generate_code_for_configurable_constants, generate_code_for_configurable_reader, + }, logs::{generate_id_error_codes_pairs, log_formatters_instantiation_code}, }, generated_code::GeneratedCode, @@ -39,6 +41,10 @@ pub(crate) fn contract_bindings( let constant_configuration_code = generate_code_for_configurable_constants(&configuration_struct_name, &abi.configurables)?; + let configuration_reader_name = ident(&format!("{name}ConfigurablesReader")); + let constant_configuration_reader_code = + generate_code_for_configurable_reader(&configuration_reader_name, &abi.configurables)?; + let code = quote! { #[derive(Debug, Clone)] pub struct #name { @@ -127,13 +133,19 @@ pub(crate) fn contract_bindings( } #constant_configuration_code + #constant_configuration_reader_code }; // All publicly available types generated above should be listed here. - let type_paths = [name, &methods_name, &configuration_struct_name] - .map(|type_name| TypePath::new(type_name).expect("We know the given types are not empty")) - .into_iter() - .collect(); + let type_paths = [ + name, + &methods_name, + &configuration_struct_name, + &configuration_reader_name, + ] + .map(|type_name| TypePath::new(type_name).expect("We know the given types are not empty")) + .into_iter() + .collect(); Ok(GeneratedCode::new(code, type_paths, no_std)) } 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 e35dd9e81d..7c793262a7 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 @@ -7,7 +7,9 @@ use crate::{ program_bindings::{ abigen::{ bindings::{function_generator::FunctionGenerator, utils::extract_main_fn}, - configurables::generate_code_for_configurable_constants, + configurables::{ + generate_code_for_configurable_constants, generate_code_for_configurable_reader, + }, }, generated_code::GeneratedCode, }, @@ -27,6 +29,10 @@ pub(crate) fn predicate_bindings( let constant_configuration_code = generate_code_for_configurable_constants(&configuration_struct_name, &abi.configurables)?; + let configuration_reader_name = ident(&format!("{name}ConfigurablesReader")); + let constant_configuration_reader_code = + generate_code_for_configurable_reader(&configuration_reader_name, &abi.configurables)?; + let code = quote! { #[derive(Default)] pub struct #encoder_struct_name{ @@ -44,12 +50,17 @@ pub(crate) fn predicate_bindings( } #constant_configuration_code + #constant_configuration_reader_code }; // All publicly available types generated above should be listed here. - let type_paths = [&encoder_struct_name, &configuration_struct_name] - .map(|type_name| TypePath::new(type_name).expect("We know the given types are not empty")) - .into_iter() - .collect(); + let type_paths = [ + &encoder_struct_name, + &configuration_struct_name, + &configuration_reader_name, + ] + .map(|type_name| TypePath::new(type_name).expect("We know the given types are not empty")) + .into_iter() + .collect(); Ok(GeneratedCode::new(code, type_paths, no_std)) } 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 9796d37c39..0da9e23510 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 @@ -9,7 +9,9 @@ use crate::{ program_bindings::{ abigen::{ bindings::{function_generator::FunctionGenerator, utils::extract_main_fn}, - configurables::generate_code_for_configurable_constants, + configurables::{ + generate_code_for_configurable_constants, generate_code_for_configurable_reader, + }, logs::{generate_id_error_codes_pairs, log_formatters_instantiation_code}, }, generated_code::GeneratedCode, @@ -41,6 +43,10 @@ pub(crate) fn script_bindings( let constant_configuration_code = generate_code_for_configurable_constants(&configuration_struct_name, &abi.configurables)?; + let configuration_reader_name = ident(&format!("{name}ConfigurablesReader")); + let constant_configuration_reader_code = + generate_code_for_configurable_reader(&configuration_reader_name, &abi.configurables)?; + let code = quote! { #[derive(Debug,Clone)] pub struct #name{ @@ -85,7 +91,7 @@ pub(crate) fn script_bindings( self } - pub fn code(&self) -> ::std::vec::Vec { + pub fn code(&self) -> ::fuels::types::errors::Result<::std::vec::Vec> { let regular = ::fuels::programs::executable::Executable::from_bytes(self.unconfigured_binary.clone()).with_configurables(self.configurables.clone()); if self.converted_into_loader { @@ -135,10 +141,11 @@ pub(crate) fn script_bindings( } #constant_configuration_code + #constant_configuration_reader_code }; // All publicly available types generated above should be listed here. - let type_paths = [name, &configuration_struct_name] + let type_paths = [name, &configuration_struct_name, &configuration_reader_name] .map(|type_name| TypePath::new(type_name).expect("We know the given types are not empty")) .into_iter() .collect(); 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 82edbb9181..3516a1e3d7 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs @@ -10,18 +10,20 @@ use crate::{ #[derive(Debug)] pub(crate) struct ResolvedConfigurable { - pub name: Ident, + pub name: String, pub ttype: ResolvedType, pub offset: u64, + pub indirect: bool, } impl ResolvedConfigurable { pub fn new(configurable: &FullConfigurable) -> Result { let type_application = &configurable.application; Ok(ResolvedConfigurable { - name: safe_ident(&format!("with_{}", configurable.name)), + name: configurable.name.clone(), ttype: TypeResolver::default().resolve(type_application)?, offset: configurable.offset, + indirect: configurable.indirect, }) } } @@ -48,9 +50,10 @@ pub(crate) fn generate_code_for_configurable_constants( fn generate_struct_decl(configurable_struct_name: &Ident) -> TokenStream { quote! { - #[derive(Clone, Debug, Default)] + #[derive(Clone, Debug)] pub struct #configurable_struct_name { offsets_with_data: ::std::vec::Vec<(u64, ::std::vec::Vec)>, + indirect_configurables: ::std::vec::Vec, encoder: ::fuels::core::codec::ABIEncoder, } } @@ -61,6 +64,7 @@ fn generate_struct_impl( resolved_configurables: &[ResolvedConfigurable], ) -> TokenStream { let builder_methods = generate_builder_methods(resolved_configurables); + let indirect_configurables = generate_indirect_configurables(resolved_configurables); quote! { impl #configurable_struct_name { @@ -73,6 +77,16 @@ fn generate_struct_impl( #builder_methods } + + impl ::std::default::Default for #configurable_struct_name { + fn default() -> Self { + Self { + offsets_with_data: ::std::default::Default::default(), + indirect_configurables: #indirect_configurables, + encoder: ::std::default::Default::default(), + } + } + } } } @@ -82,8 +96,10 @@ fn generate_builder_methods(resolved_configurables: &[ResolvedConfigurable]) -> name, ttype, offset, + .. }| { let encoder_code = generate_encoder_code(ttype); + let name = safe_ident(&format!("with_{name}")); quote! { #[allow(non_snake_case)] // Generate the `with_XXX` methods for setting the configurables @@ -101,6 +117,18 @@ fn generate_builder_methods(resolved_configurables: &[ResolvedConfigurable]) -> } } +fn generate_indirect_configurables(resolved_configurables: &[ResolvedConfigurable]) -> TokenStream { + let indirect_configurables = resolved_configurables.iter().filter_map( + |ResolvedConfigurable { + offset, indirect, .. + }| { indirect.then_some(quote! { #offset }) }, + ); + + quote! { + vec![#(#indirect_configurables),*] + } +} + fn generate_encoder_code(ttype: &ResolvedType) -> TokenStream { quote! { self.encoder.encode(&[ @@ -113,8 +141,89 @@ fn generate_from_impl(configurable_struct_name: &Ident) -> TokenStream { quote! { impl From<#configurable_struct_name> for ::fuels::core::Configurables { fn from(config: #configurable_struct_name) -> Self { - ::fuels::core::Configurables::new(config.offsets_with_data) + ::fuels::core::Configurables::new(config.offsets_with_data, config.indirect_configurables) + } + } + } +} + +pub(crate) fn generate_code_for_configurable_reader( + configurable_struct_name: &Ident, + configurables: &[FullConfigurable], +) -> Result { + let resolved_configurables = configurables + .iter() + .map(ResolvedConfigurable::new) + .collect::>>()?; + + let struct_decl = generate_struct_decl_reader(configurable_struct_name); + let struct_impl = + generate_struct_impl_reader(configurable_struct_name, &resolved_configurables); + + Ok(quote! { + #struct_decl + #struct_impl + }) +} + +fn generate_struct_decl_reader(struct_name: &Ident) -> TokenStream { + quote! { + #[derive(Clone, Debug)] + pub struct #struct_name { + reader: ::fuels::core::ConfigurablesReader, + } + } +} + +fn generate_struct_impl_reader( + struct_name: &Ident, + resolved_configurables: &[ResolvedConfigurable], +) -> TokenStream { + let methods = generate_methods_reader(resolved_configurables); + + quote! { + impl #struct_name { + pub fn load_from( + binary_filepath: impl ::std::convert::AsRef<::std::path::Path>, + ) -> ::fuels::prelude::Result { + let reader = ::fuels::core::ConfigurablesReader::load_from(binary_filepath)?; + + ::fuels::prelude::Result::Ok(Self{reader}) } + + #methods } } } + +fn generate_methods_reader(resolved_configurables: &[ResolvedConfigurable]) -> TokenStream { + let methods = resolved_configurables.iter().map( + |ResolvedConfigurable { + name, + ttype, + offset, + indirect, + }| { + let name = safe_ident(name); + + let reader_code = if *indirect { + quote! { self.reader.try_from_indirect(#offset as usize) } + } else { + quote! { self.reader.try_from_direct(#offset as usize) } + }; + + quote! { + // Generate the `XXX` methods for getting the configurables + #[allow(non_snake_case)] + pub fn #name(&self) -> ::fuels::prelude::Result<#ttype> { + #reader_code + } + + } + }, + ); + + quote! { + #(#methods)* + } +} diff --git a/packages/fuels-core/Cargo.toml b/packages/fuels-core/Cargo.toml index 2669843f75..2790671ff8 100644 --- a/packages/fuels-core/Cargo.toml +++ b/packages/fuels-core/Cargo.toml @@ -37,6 +37,7 @@ auto_impl = { workspace = true } [dev-dependencies] fuel-tx = { workspace = true, features = ["test-helpers", "random"] } tokio = { workspace = true, features = ["test-util", "macros"] } +pretty_assertions = { workspace = true, features = ["alloc"] } [features] default = ["std"] diff --git a/packages/fuels-core/src/codec/abi_formatter.rs b/packages/fuels-core/src/codec/abi_formatter.rs index 7b7b1dff01..a65c499d1e 100644 --- a/packages/fuels-core/src/codec/abi_formatter.rs +++ b/packages/fuels-core/src/codec/abi_formatter.rs @@ -4,11 +4,18 @@ use fuel_abi_types::abi::unified_program::UnifiedProgramABI; use itertools::Itertools; use super::{ABIDecoder, DecoderConfig}; -use crate::{Result, error, types::param_types::ParamType}; +use crate::{Result, error, offsets::extract_offset_at, types::param_types::ParamType}; + +struct FormatterConfigurable { + name: String, + param_type: ParamType, + offset: u64, + indirect: bool, +} pub struct ABIFormatter { functions: HashMap>, - configurables: Vec<(String, ParamType)>, + configurables: Vec, decoder: ABIDecoder, } @@ -58,7 +65,12 @@ impl ABIFormatter { let param_type = ParamType::try_from_type_application(&c.application, &type_lookup)?; - Ok((c.name, param_type)) + Ok(FormatterConfigurable { + name: c.name, + param_type, + offset: c.offset, + indirect: c.indirect, + }) }) .collect::>>()?; @@ -83,26 +95,31 @@ impl ABIFormatter { self.decoder.decode_multiple_as_debug_str(args, data) } - pub fn decode_configurables( - &self, - configurable_data: R, - ) -> Result> { - let param_types = self + pub fn decode_configurables(&self, configurable_data: &[u8]) -> Result> { + let min_offset = self .configurables .iter() - .map(|(_, param_type)| param_type) - .cloned() - .collect::>(); - - let decoded = self - .decoder - .decode_multiple_as_debug_str(¶m_types, configurable_data)? - .into_iter() - .zip(&self.configurables) - .map(|(value, (name, _))| (name.clone(), value)) - .collect(); + .map(|c| c.offset) + .min() + .unwrap_or_default() as usize; - Ok(decoded) + self.configurables + .iter() + .map(|c| { + let offset = (c.offset as usize).saturating_sub(min_offset); + + let decoded_string = if c.indirect { + let dyn_offset = extract_offset_at(configurable_data, offset)?; + self.decoder + .decode_as_debug_str(&c.param_type, &configurable_data[dyn_offset..])? + } else { + self.decoder + .decode_as_debug_str(&c.param_type, &configurable_data[offset..])? + }; + + Ok((c.name.clone(), decoded_string)) + }) + .collect() } } diff --git a/packages/fuels-core/src/lib.rs b/packages/fuels-core/src/lib.rs index 1723a91702..d6e3fe5188 100644 --- a/packages/fuels-core/src/lib.rs +++ b/packages/fuels-core/src/lib.rs @@ -3,53 +3,308 @@ pub mod traits; pub mod types; mod utils; +use std::{ + collections::{BTreeMap, BTreeSet}, + path::Path, +}; + +use codec::{ABIDecoder, ABIEncoder, DecoderConfig, try_from_bytes}; +use offsets::{extract_data_offset, extract_offset_at}; +use traits::{Parameterize, Tokenizable}; +use types::{Token, param_types::ParamType}; pub use utils::*; use crate::types::errors::Result; +type OffsetWithData = (u64, Vec); + +#[derive(Debug, Clone)] +pub struct ConfigurablesReader { + binary: Vec, + decoder_config: DecoderConfig, +} + +impl ConfigurablesReader { + pub fn load(binary: Vec) -> Self { + Self { + binary, + decoder_config: DecoderConfig::default(), + } + } + + pub fn load_from(binary_filepath: impl AsRef) -> Result { + let binary_filepath = binary_filepath.as_ref(); + + let binary = std::fs::read(binary_filepath).map_err(|e| { + std::io::Error::new( + e.kind(), + format!("failed to read binary: {binary_filepath:?}: {e}"), + ) + })?; + + Ok(Self { + binary, + decoder_config: DecoderConfig::default(), + }) + } + + pub fn with_decoder_config(mut self, decoder_config: DecoderConfig) -> Self { + self.decoder_config = decoder_config; + + self + } + + /// Decodes direct (static) configurables from a binary starting at `offset` + /// into a concrete type. + pub fn try_from_direct(&self, offset: usize) -> Result { + check_binary_len(&self.binary, offset)?; + + try_from_bytes(&self.binary[offset..], self.decoder_config) + } + + /// Decodes indirect (dynamic) configurables from a binary into a concrete type. + /// As indirect configurables use a pointer to point to the dynamic data, + /// this method will first read the dynamic data offset and then decode the data + /// starting at the dynamic offset. + pub fn try_from_indirect(&self, offset: usize) -> Result { + let data_offset = extract_data_offset(&self.binary)?; + let dyn_offset = extract_offset_at(&self.binary, offset)?; + + check_binary_len(&self.binary, data_offset + dyn_offset)?; + + try_from_bytes( + &self.binary[data_offset + dyn_offset..], + self.decoder_config, + ) + } + + /// Decodes direct (static) configurables from a binary starting at `offset`. + pub fn decode_direct(&self, offset: usize, param_type: &ParamType) -> Result { + check_binary_len(&self.binary, offset)?; + + ABIDecoder::new(self.decoder_config).decode(param_type, &self.binary[offset..]) + } + + /// Decodes indirect (dynamic) configurables from a binary. + /// As indirect configurables use a pointer to point to the dynamic data, + /// this method will first read the dynamic data offset and then decode the data + /// starting at the dynamic offset. + pub fn decode_indirect(&self, offset: usize, param_type: &ParamType) -> Result { + let data_offset = extract_data_offset(&self.binary)?; + let dyn_offset = extract_offset_at(&self.binary, offset)?; + + check_binary_len(&self.binary, data_offset + dyn_offset)?; + + ABIDecoder::new(self.decoder_config) + .decode(param_type, &self.binary[data_offset + dyn_offset..]) + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +struct OverrideConfigurables { + direct: BTreeMap>, + indirect: BTreeMap>, +} + +impl OverrideConfigurables { + fn new(direct: BTreeMap>, indirect: BTreeMap>) -> Self { + Self { direct, indirect } + } + + fn with_overrides(mut self, configurables: OverrideConfigurables) -> Self { + self.direct.extend(configurables.direct); + self.indirect.extend(configurables.indirect); + + self + } + + fn update_binary(&self, binary: &mut Vec) -> Result<()> { + self.apply_direct(binary)?; + self.apply_indirect(binary)?; + + Ok(()) + } + + fn apply_direct(&self, binary: &mut [u8]) -> Result<()> { + for (static_offset, data) in self.direct.iter() { + Self::write(binary, *static_offset as usize, data)?; + } + + Ok(()) + } + + fn apply_indirect(&self, binary: &mut Vec) -> Result<()> { + if self.indirect.is_empty() { + return Ok(()); + } + + let data_offset = extract_data_offset(binary)?; + let start_of_dyn_section = self + .dynamic_section_start(binary, data_offset)? + .unwrap_or(binary.len()); + + let mut new_dyn_section: Vec = vec![]; + let mut save_to_dyn = |data| { + let ptr = start_of_dyn_section + .saturating_add(new_dyn_section.len()) + .saturating_sub(data_offset); + let ptr_encoded = ABIEncoder::default().encode(&[(ptr as u64).into_token()])?; + + new_dyn_section.extend(data); + + Result::Ok(ptr_encoded) + }; + + for (static_offset, data) in self.indirect.iter() { + let ptr = save_to_dyn(data)?; + Self::write(binary, *static_offset as usize, &ptr)?; + } + + binary.truncate(start_of_dyn_section); + binary.extend(new_dyn_section); + + Ok(()) + } + + fn write(binary: &mut [u8], offset: usize, data: &[u8]) -> Result<()> { + check_binary_len(binary, offset + data.len())?; + + binary[offset..offset + data.len()].copy_from_slice(data); + + Ok(()) + } + + /// Each indirect configurable holds an `offset` relative to the `data_offset`. When adding the + /// indirect's `offset` and the `data_offset` we get a pointer to the dynamic data. + /// This function finds the minimal pointer i.e. the beginning of the dynamic data. + fn dynamic_section_start(&self, binary: &[u8], data_offset: usize) -> Result> { + let mut min = None; + + for static_offset in self.indirect.keys() { + let offset = + extract_offset_at(binary, *static_offset as usize)?.saturating_add(data_offset); + + min = min + .map(|current_min| std::cmp::min(current_min, offset)) + .or(Some(offset)); + } + + Ok(min) + } +} + #[derive(Debug, Clone, Default, PartialEq)] pub struct Configurables { - offsets_with_data: Vec<(u64, Vec)>, + offsets_with_data: Vec, + indirect_offsets: BTreeSet, } impl Configurables { - pub fn new(offsets_with_data: Vec<(u64, Vec)>) -> Self { - Self { offsets_with_data } + pub fn new(offsets_with_data: Vec, indirect_configurables: Vec) -> Self { + let indirect_offsets = indirect_configurables.into_iter().collect(); + + Self { + offsets_with_data, + indirect_offsets, + } + } + + fn to_overrides(&self) -> OverrideConfigurables { + let (indirect_configurables, direct_configurables) = self + .offsets_with_data + .iter() + .cloned() + .partition(|(offset, _)| self.indirect_offsets.contains(offset)); + + OverrideConfigurables::new(direct_configurables, indirect_configurables) + } + + fn read_out_indirect_configurables(&self, binary: &[u8]) -> Result { + if self.indirect_offsets.is_empty() { + return Ok(OverrideConfigurables::default()); + } + + let data_offset = extract_data_offset(binary)?; + let mut indirect_configurables = BTreeMap::new(); + + let mut peekable_indirect_offset = self.indirect_offsets.iter().peekable(); + + while let Some(current) = peekable_indirect_offset.next() { + let data_start = + extract_offset_at(binary, *current as usize)?.saturating_add(data_offset); + + let data_end = if let Some(next) = peekable_indirect_offset.peek() { + extract_offset_at(binary, **next as usize)?.saturating_add(data_offset) + } else { + binary.len() + }; + + indirect_configurables.insert(*current, binary[data_start..data_end].to_vec()); + } + + Ok(OverrideConfigurables::new( + BTreeMap::default(), + indirect_configurables, + )) } pub fn with_shifted_offsets(self, shift: i64) -> Result { - let new_offsets_with_data = self + let offsets_with_data = self .offsets_with_data .into_iter() - .map(|(offset, data)| { - let new_offset = if shift.is_negative() { - offset.checked_sub(shift.unsigned_abs()) - } else { - offset.checked_add(shift.unsigned_abs()) - }; - - let new_offset = new_offset.ok_or_else(|| { - crate::error!( - Other, - "Overflow occurred while shifting offset: {offset} + {shift}" - ) - })?; - - Ok((new_offset, data.clone())) - }) + .map(|(offset, data)| Ok((Self::shift_offset(offset, shift)?, data))) .collect::>>()?; + let indirect_offsets = self + .indirect_offsets + .into_iter() + .map(|offset| Self::shift_offset(offset, shift)) + .collect::>>()?; + Ok(Self { - offsets_with_data: new_offsets_with_data, + offsets_with_data, + indirect_offsets, + }) + } + + fn shift_offset(offset: u64, shift: i64) -> Result { + if shift.is_negative() { + offset.checked_sub(shift.unsigned_abs()) + } else { + offset.checked_add(shift.unsigned_abs()) + } + .ok_or_else(|| { + crate::error!( + Other, + "overflow occurred while shifting configurable's offset: {offset} + {shift}" + ) }) } - pub fn update_constants_in(&self, binary: &mut [u8]) { - for (offset, data) in &self.offsets_with_data { - let offset = *offset as usize; - binary[offset..offset + data.len()].copy_from_slice(data) + pub fn update_constants_in(&self, binary: &mut Vec) -> Result<()> { + if self.offsets_with_data.is_empty() { + return Ok(()); } + + self.read_out_indirect_configurables(binary)? + .with_overrides(self.to_overrides()) + .update_binary(binary)?; + + Ok(()) + } +} + +fn check_binary_len(binary: &[u8], offset: usize) -> Result<()> { + if binary.len() < offset { + return Err(crate::error!( + Other, + "configurables: given binary with len: `{}` is too short for offset:`{}`", + binary.len(), + offset + )); } + + Ok(()) } #[cfg(test)] @@ -58,65 +313,443 @@ mod tests { #[test] fn test_with_shifted_offsets_positive_shift() { - let offsets_with_data = vec![(10u64, vec![1, 2, 3])]; - let configurables = Configurables::new(offsets_with_data.clone()); + let offsets_with_data = vec![(10, vec![1, 2, 3]), (20, vec![4, 5, 6])]; + let indirect_configurables = vec![20, 10]; + let configurables = Configurables::new(offsets_with_data.clone(), indirect_configurables); + let shifted_configurables = configurables.with_shifted_offsets(5).unwrap(); - let expected_offsets_with_data = vec![(15u64, vec![1, 2, 3])]; + let expected_offsets_with_data = vec![(15, vec![1, 2, 3]), (25, vec![4, 5, 6])]; + let expected_indirect_configurables = vec![15, 25]; + assert_eq!( shifted_configurables.offsets_with_data, expected_offsets_with_data ); + assert_eq!( + shifted_configurables.indirect_offsets, + expected_indirect_configurables.into_iter().collect() + ); } #[test] fn test_with_shifted_offsets_negative_shift() { - let offsets_with_data = vec![(10u64, vec![4, 5, 6])]; - let configurables = Configurables::new(offsets_with_data.clone()); + let offsets_with_data = vec![(10, vec![4, 5, 6]), (30, vec![7, 8, 9])]; + let indirect_configurables = vec![30, 10]; + let configurables = Configurables::new(offsets_with_data.clone(), indirect_configurables); + let shifted_configurables = configurables.with_shifted_offsets(-5).unwrap(); - let expected_offsets_with_data = vec![(5u64, vec![4, 5, 6])]; + let expected_offsets_with_data = vec![(5, vec![4, 5, 6]), (25, vec![7, 8, 9])]; + let expected_indirect_configurables = vec![5, 25]; + assert_eq!( shifted_configurables.offsets_with_data, expected_offsets_with_data ); + assert_eq!( + shifted_configurables.indirect_offsets, + expected_indirect_configurables.into_iter().collect() + ); } #[test] fn test_with_shifted_offsets_zero_shift() { - let offsets_with_data = vec![(20u64, vec![7, 8, 9])]; - let configurables = Configurables::new(offsets_with_data.clone()); + let offsets_with_data = vec![(20, vec![7, 8, 9]), (40, vec![10, 11, 12])]; + let indirect_configurables = vec![40, 20]; + let configurables = Configurables::new(offsets_with_data.clone(), indirect_configurables); + let shifted_configurables = configurables.with_shifted_offsets(0).unwrap(); let expected_offsets_with_data = offsets_with_data; + let expected_indirect_configurables = vec![20, 40]; + assert_eq!( shifted_configurables.offsets_with_data, expected_offsets_with_data ); + assert_eq!( + shifted_configurables.indirect_offsets, + expected_indirect_configurables.into_iter().collect() + ); } #[test] fn test_with_shifted_offsets_overflow() { - let offsets_with_data = vec![(u64::MAX - 1, vec![1, 2, 3])]; - let configurables = Configurables::new(offsets_with_data); + let offsets_with_data = vec![(u64::MAX - 1, vec![1, 2, 3]), (u64::MAX - 2, vec![4, 5, 6])]; + let indirect_configurables = vec![u64::MAX - 1, u64::MAX - 2]; + let configurables = Configurables::new(offsets_with_data, indirect_configurables); + let result = configurables.with_shifted_offsets(10); + assert!(result.is_err()); if let Err(e) = result { assert!( e.to_string() - .contains("Overflow occurred while shifting offset") + .contains("overflow occurred while shifting configurable's offset") ); } } #[test] fn test_with_shifted_offsets_underflow() { - let offsets_with_data = vec![(5, vec![4, 5, 6])]; - let configurables = Configurables::new(offsets_with_data); + let offsets_with_data = vec![(5, vec![4, 5, 6]), (15, vec![7, 8, 9])]; + let indirect_configurables = vec![15, 5]; + let configurables = Configurables::new(offsets_with_data, indirect_configurables); + let result = configurables.with_shifted_offsets(-10); + assert!(result.is_err()); if let Err(e) = result { assert!( e.to_string() - .contains("Overflow occurred while shifting offset") + .contains("overflow occurred while shifting configurable's offset") ); } } + + #[rustfmt::skip] + const TEST_BINARY: [u8; 55] = [ + // Header (8 bytes): produced by the Sway compiler. + 0, 1, 2, 3, 4, 5, 6, 7, + + // Data offset (8 bytes) + 0, 0, 0, 0, 0, 0, 0, 16, + + // Direct configurables (6 bytes): + // u16(4370) + 17, 18, + // Additional direct configurable values + 19, 20, 21, 22, + + // First indirect pointer + // 30 + data_offset (16) = 46, so its data is at location 46 below. + 0, 0, 0, 0, 0, 0, 0, 30, + + // Second indirect pointer + // 33 + 16 = 49, so its data is at location 49 below. + 0, 0, 0, 0, 0, 0, 0, 33, + + // Third indirect pointer + // 36 + 16 = 52, so its data is at location 52 below. + 0, 0, 0, 0, 0, 0, 0, 36, + + // Dynamic section (9 bytes): + // Data for first indirect configurable (location 46) + 50, 51, 52, + // Data for second indirect configurable (location 49) + 53, 54, 55, + // Data for third indirect configurable (location 52) + 56, 57, 58, + ]; + + fn setup_configurables() -> Configurables { + let offsets_with_data = vec![(16, vec![34, 36]), (18, vec![38, 40]), (20, vec![42, 44])]; + + let indirect_configurables = vec![22, 30, 38]; + + Configurables::new(offsets_with_data, indirect_configurables) + } + + #[test] + fn try_from_direct() { + let configurables_reader = ConfigurablesReader::load(TEST_BINARY.to_vec()); + // try_from_direct(16) reads the direct configurable data starting at byte 16. + // It decodes [17, 18] as a big-endian u16 which is 4370. + let value: u16 = configurables_reader.try_from_direct(16).unwrap(); + + assert_eq!(4370, value); + } + + #[test] + fn try_from_indirect() { + let configurables_reader = ConfigurablesReader::load(TEST_BINARY.to_vec()); + // try_from_indirect(22) reads the first indirect pointer at offset 22. + // The pointer's value is 30, and 30 + data_offset (16) = 46. + // The dynamic section at location 46 is [50, 51, 52]. + let value: [u8; 3] = configurables_reader.try_from_indirect(22).unwrap(); + + assert_eq!([50, 51, 52], value); + } + + #[test] + fn decode_direct() { + let configurables_reader = ConfigurablesReader::load(TEST_BINARY.to_vec()); + let token = configurables_reader + .decode_direct(16, &u16::param_type()) + .unwrap(); + + assert_eq!(4370u16.into_token(), token); + } + + #[test] + fn decode_indirect() { + let configurables_reader = ConfigurablesReader::load(TEST_BINARY.to_vec()); + let token = configurables_reader + .decode_indirect(22, &<[u8; 3]>::param_type()) + .unwrap(); + + assert_eq!([50u8, 51, 52].into_token(), token); + } + + #[test] + fn update_first_indirect_configurable_less_data() { + let mut binary = TEST_BINARY.to_vec(); + let mut configurables = setup_configurables(); + + // Modify first indirect configurable with less data + configurables.offsets_with_data.push((22, vec![100, 102])); + + configurables.update_constants_in(&mut binary).unwrap(); + + #[rustfmt::skip] + let expected_binary: [u8; 54] = [ + // Header (bytes 0..8) + 0, 1, 2, 3, 4, 5, 6, 7, + // Data offset (bytes 8..16): 16 + 0, 0, 0, 0, 0, 0, 0, 16, + // Direct configurables (bytes 16..22): updated to [34,36,38,40,42,44] + 34, 36, 38, 40, 42, 44, + // First indirect pointer (bytes 22..30): remains value 30 + 0, 0, 0, 0, 0, 0, 0, 30, + // Second indirect pointer (bytes 30..38): updated to value 32 + 0, 0, 0, 0, 0, 0, 0, 32, + // Third indirect pointer (bytes 38..46): updated to value 35 + 0, 0, 0, 0, 0, 0, 0, 35, + // Dynamic section (bytes 46..54): + // For pointer 30: updated data [100,102] + 100, 102, + // For pointer 32: data remains [53,54,55] + 53, 54, 55, + // For pointer 35: data remains [56,57,58] + 56, 57, 58, + ]; + + pretty_assertions::assert_eq!(&expected_binary, binary.as_slice()); + } + + #[test] + fn update_first_indirect_configurable_more_data() { + let mut binary = TEST_BINARY.to_vec(); + let mut configurables = setup_configurables(); + + // Modify first indirect configurable with more data + configurables + .offsets_with_data + .push((22, vec![100, 102, 103, 104])); + + configurables.update_constants_in(&mut binary).unwrap(); + + #[rustfmt::skip] + let expected_binary: [u8; 56] = [ + // Header (bytes 0..8) + 0, 1, 2, 3, 4, 5, 6, 7, + // Data offset (bytes 8..16): 16 + 0, 0, 0, 0, 0, 0, 0, 16, + // Direct configurables (bytes 16..22): updated to [34,36,38,40,42,44] + 34, 36, 38, 40, 42, 44, + // First indirect pointer (bytes 22..30): remains value 30 + 0, 0, 0, 0, 0, 0, 0, 30, + // Second indirect pointer (bytes 30..38): updated to value 34 + 0, 0, 0, 0, 0, 0, 0, 34, + // Third indirect pointer (bytes 38..46): updated to value 37 + 0, 0, 0, 0, 0, 0, 0, 37, + // Dynamic section (bytes 46..56): + // For pointer 30: updated data [100,102,103,104] (4 bytes) + 100, 102, 103, 104, + // For pointer 34: second indirect data remains [53,54,55] (3 bytes) + 53, 54, 55, + // For pointer 37: third indirect data remains [56,57,58] (3 bytes) + 56, 57, 58, + ]; + + pretty_assertions::assert_eq!(&expected_binary, binary.as_slice()); + } + + #[test] + fn update_second_indirect_configurable_less_data() { + let mut binary = TEST_BINARY.to_vec(); + let mut configurables = setup_configurables(); + + // Modify second indirect configurable with less data + configurables.offsets_with_data.push((30, vec![106, 108])); + + configurables.update_constants_in(&mut binary).unwrap(); + + #[rustfmt::skip] + let expected_binary: [u8; 54] = [ + // Header (bytes 0..8) + 0, 1, 2, 3, 4, 5, 6, 7, + // Data offset (bytes 8..16): 16 + 0, 0, 0, 0, 0, 0, 0, 16, + // Direct configurables (bytes 16..22): [34,36,38,40,42,44] + 34, 36, 38, 40, 42, 44, + // First indirect pointer (bytes 22..30): remains value 30 + 0, 0, 0, 0, 0, 0, 0, 30, + // Second indirect pointer (bytes 30..38): remains value 33 + 0, 0, 0, 0, 0, 0, 0, 33, + // Third indirect pointer (bytes 38..46): remains value 35 + 0, 0, 0, 0, 0, 0, 0, 35, + // Dynamic section (bytes 46..54): + // For pointer 30: data remains [50,51,52] + 50, 51, 52, + // For pointer 33: updated data [106,108] (2 bytes) + 106, 108, + // For pointer 35: data remains [56,57,58] + 56, 57, 58, + ]; + + pretty_assertions::assert_eq!(&expected_binary, binary.as_slice()); + } + + #[test] + fn update_second_indirect_configurable_more_data() { + let mut binary = TEST_BINARY.to_vec(); + let mut configurables = setup_configurables(); + + // Modify second indirect configurable with more data + configurables + .offsets_with_data + .push((30, vec![106, 108, 110, 112])); + + configurables.update_constants_in(&mut binary).unwrap(); + + #[rustfmt::skip] + let expected_binary: [u8; 56] = [ + // Header (bytes 0..8) + 0, 1, 2, 3, 4, 5, 6, 7, + // Data offset (bytes 8..16): 16 + 0, 0, 0, 0, 0, 0, 0, 16, + // Direct configurables (bytes 16..22): [34,36,38,40,42,44] + 34, 36, 38, 40, 42, 44, + // First indirect pointer (bytes 22..30): remains value 30 + 0, 0, 0, 0, 0, 0, 0, 30, + // Second indirect pointer (bytes 30..38): remains value 33 + 0, 0, 0, 0, 0, 0, 0, 33, + // Third indirect pointer (bytes 38..46): updated to value 37 + 0, 0, 0, 0, 0, 0, 0, 37, + // Dynamic section (bytes 46..56): + // For pointer 30: data remains [50,51,52] + 50, 51, 52, + // For pointer 33: updated data [106,108,110,112] (4 bytes) + 106, 108, 110, 112, + // For pointer 37: data remains [56,57,58] + 56, 57, 58, + ]; + + pretty_assertions::assert_eq!(&expected_binary, binary.as_slice()); + } + + #[test] + fn update_last_indirect_configurable_less_data() { + let mut binary = TEST_BINARY.to_vec(); + let mut configurables = setup_configurables(); + + // Modify last indirect configurable with less data + configurables.offsets_with_data.push((38, vec![112, 114])); + + configurables.update_constants_in(&mut binary).unwrap(); + + #[rustfmt::skip] + let expected_binary: [u8; 54] = [ + // Header (bytes 0..8) + 0, 1, 2, 3, 4, 5, 6, 7, + // Data offset (bytes 8..16): 16 + 0, 0, 0, 0, 0, 0, 0, 16, + // Direct configurables (bytes 16..22): [34,36,38,40,42,44] + 34, 36, 38, 40, 42, 44, + // First indirect pointer (bytes 22..30): remains value 30 + 0, 0, 0, 0, 0, 0, 0, 30, + // Second indirect pointer (bytes 30..38): remains value 33 + 0, 0, 0, 0, 0, 0, 0, 33, + // Third indirect pointer (bytes 38..46): remains value 36 + 0, 0, 0, 0, 0, 0, 0, 36, + // Dynamic section (bytes 46..54): + // For pointer 30: data remains [50,51,52] + 50, 51, 52, + // For pointer 33: data remains [53,54,55] + 53, 54, 55, + // For pointer 36: updated data [112,114] + 112, 114, + ]; + + pretty_assertions::assert_eq!(&expected_binary, binary.as_slice()); + } + + #[test] + fn update_last_indirect_configurable_more_data() { + let mut binary = TEST_BINARY.to_vec(); + let mut configurables = setup_configurables(); + + // Modify last indirect configurable with more data + configurables + .offsets_with_data + .push((38, vec![112, 114, 116, 118])); + + configurables.update_constants_in(&mut binary).unwrap(); + + #[rustfmt::skip] + let expected_binary: [u8; 56] = [ + // Header (bytes 0..8) + 0, 1, 2, 3, 4, 5, 6, 7, + // Data offset (bytes 8..16): 16 + 0, 0, 0, 0, 0, 0, 0, 16, + // Direct configurables (bytes 16..22): [34,36,38,40,42,44] + 34, 36, 38, 40, 42, 44, + // First indirect pointer (bytes 22..30): remains value 30 + 0, 0, 0, 0, 0, 0, 0, 30, + // Second indirect pointer (bytes 30..38): remains value 33 + 0, 0, 0, 0, 0, 0, 0, 33, + // Third indirect pointer (bytes 38..46): remains value 36 + 0, 0, 0, 0, 0, 0, 0, 36, + // Dynamic section (bytes 46..56): + // For pointer 30: data remains [50,51,52] + 50, 51, 52, + // For pointer 33: data remains [53,54,55] + 53, 54, 55, + // For pointer 36: updated data [112,114,116,118] + 112, 114, 116, 118, + ]; + + pretty_assertions::assert_eq!(&expected_binary, binary.as_slice()); + } + + #[test] + fn update_all_indirect_configurables() { + let mut binary = TEST_BINARY.to_vec(); + let mut configurables = setup_configurables(); + + // Modify all indirect configurables + configurables.offsets_with_data.push((22, vec![100, 101])); + configurables + .offsets_with_data + .push((30, vec![102, 103, 104, 105])); + configurables + .offsets_with_data + .push((38, vec![106, 107, 108])); + + configurables.update_constants_in(&mut binary).unwrap(); + + #[rustfmt::skip] + let expected_binary: [u8; 55] = [ + // Header (bytes 0..8) + 0, 1, 2, 3, 4, 5, 6, 7, + // Data offset (bytes 8..16): 16 + 0, 0, 0, 0, 0, 0, 0, 16, + // Direct configurables (bytes 16..22): updated to [34,36,38,40,42,44] + 34, 36, 38, 40, 42, 44, + // First indirect pointer (bytes 22..30): remains value 30 + 0, 0, 0, 0, 0, 0, 0, 30, + // Second indirect pointer (bytes 30..38): updated to value 32 + 0, 0, 0, 0, 0, 0, 0, 32, + // Third indirect pointer (bytes 38..46): remains value 36 + 0, 0, 0, 0, 0, 0, 0, 36, + // Dynamic section (bytes 46..55): + // For pointer 30: updated data [100,101] + 100, 101, + // For pointer 32: updated data [102,103,104,105] + 102, 103, 104, 105, + // For pointer 36: updated data [106, 107, 108] + 106, 107, 108 + ]; + + pretty_assertions::assert_eq!(&expected_binary, binary.as_slice()); + } } diff --git a/packages/fuels-core/src/utils/offsets.rs b/packages/fuels-core/src/utils/offsets.rs index d94a4af4af..0fc328f460 100644 --- a/packages/fuels-core/src/utils/offsets.rs +++ b/packages/fuels-core/src/utils/offsets.rs @@ -26,5 +26,27 @@ pub fn call_script_data_offset( "call script data len overflow: {calls_instructions_len}" ) })?; + Ok(base_offset_script(consensus_parameters) + padded_len) } + +pub fn extract_data_offset(binary: &[u8]) -> Result { + extract_offset_at(binary, 8) +} + +pub fn extract_offset_at(binary: &[u8], offset: usize) -> Result { + if binary.len() < offset + 8 { + return Err(crate::error!( + Other, + "given binary with len: `{}` is too short to contain an offset at:`{}`", + binary.len(), + offset + )); + } + + let data_offset: [u8; 8] = binary[offset..offset + 8] + .try_into() + .expect("checked above"); + + Ok(u64::from_be_bytes(data_offset) as usize) +} diff --git a/packages/fuels-programs/src/calls/call_handler.rs b/packages/fuels-programs/src/calls/call_handler.rs index 119ad200e5..61f0afaff4 100644 --- a/packages/fuels-programs/src/calls/call_handler.rs +++ b/packages/fuels-programs/src/calls/call_handler.rs @@ -364,7 +364,7 @@ where T: Parameterize + Tokenizable + Debug, { pub fn new_script_call( - script_binary: Vec, + script_binary: Result>, encoded_args: Result>, account: A, log_decoder: LogDecoder, diff --git a/packages/fuels-programs/src/calls/script_call.rs b/packages/fuels-programs/src/calls/script_call.rs index f6d6422206..f7b4e64cfd 100644 --- a/packages/fuels-programs/src/calls/script_call.rs +++ b/packages/fuels-programs/src/calls/script_call.rs @@ -13,7 +13,7 @@ use crate::calls::utils::{generate_contract_inputs, generate_contract_outputs, s #[derive(Debug, Clone)] /// Contains all data relevant to a single script call pub struct ScriptCall { - pub script_binary: Vec, + pub script_binary: Result>, pub encoded_args: Result>, pub inputs: Vec, pub outputs: Vec, diff --git a/packages/fuels-programs/src/calls/traits/transaction_tuner.rs b/packages/fuels-programs/src/calls/traits/transaction_tuner.rs index d5d75fe9be..daefff610e 100644 --- a/packages/fuels-programs/src/calls/traits/transaction_tuner.rs +++ b/packages/fuels-programs/src/calls/traits/transaction_tuner.rs @@ -66,11 +66,12 @@ impl TransactionTuner for ScriptCall { _account: &T, ) -> Result { let (inputs, outputs) = self.prepare_inputs_outputs()?; + let script_binary = self.script_binary.clone()?; Ok(ScriptTransactionBuilder::default() .with_variable_output_policy(variable_output_policy) .with_tx_policies(tx_policies) - .with_script(self.script_binary.clone()) + .with_script(script_binary) .with_script_data(self.compute_script_data()?) .with_inputs(inputs) .with_outputs(outputs) diff --git a/packages/fuels-programs/src/contract.rs b/packages/fuels-programs/src/contract.rs index 7092049a42..835f370088 100644 --- a/packages/fuels-programs/src/contract.rs +++ b/packages/fuels-programs/src/contract.rs @@ -243,16 +243,6 @@ mod tests { Ok(()) } - macro_rules! getters_work { - ($contract: ident, $contract_id: expr, $state_root: expr, $code_root: expr, $salt: expr, $code: expr) => { - assert_eq!($contract.contract_id(), $contract_id); - assert_eq!($contract.state_root(), $state_root); - assert_eq!($contract.code_root(), $code_root); - assert_eq!($contract.salt(), $salt); - assert_eq!($contract.code(), $code); - }; - } - #[test] fn regular_contract_has_expected_getters() -> Result<()> { let contract_binary = b"some fake contract code"; @@ -267,18 +257,25 @@ mod tests { "69ca130191e9e469f1580229760b327a0729237f1aff65cf1d076b2dd8360031".parse()?; let expected_salt = Salt::zeroed(); - getters_work!( - contract, - expected_contract_id, - expected_state_root, - expected_code_root, - expected_salt, - contract_binary - ); + assert_eq!(contract.contract_id(), expected_contract_id); + assert_eq!(contract.state_root(), expected_state_root); + assert_eq!(contract.code_root(), expected_code_root); + assert_eq!(contract.salt(), expected_salt); + assert_eq!(contract.code(), contract_binary); Ok(()) } + macro_rules! getters_work { + ($contract: ident, $contract_id: expr, $state_root: expr, $code_root: expr, $salt: expr, $code: expr) => { + assert_eq!($contract.contract_id(), $contract_id); + assert_eq!($contract.state_root(), $state_root); + assert_eq!($contract.code_root(), $code_root); + assert_eq!($contract.salt(), $salt); + assert_eq!($contract.code(), $code); + }; + } + #[test] fn regular_can_be_turned_into_loader_and_back() -> Result<()> { let contract_binary = b"some fake contract code"; @@ -303,7 +300,7 @@ mod tests { let loader = original.clone().convert_to_loader(1024)?; let loader_asm = loader_contract_asm(&loader.blob_ids()).unwrap(); - let manual_loader = original.with_code(loader_asm); + let manual_loader = original.with_code(loader_asm)?; getters_work!( loader, @@ -348,7 +345,7 @@ mod tests { let loader = Contract::loader_from_blob_ids(blob_ids.clone(), Salt::default(), vec![])?; let loader_asm = loader_contract_asm(&blob_ids).unwrap(); - let manual_loader = original_contract.with_code(loader_asm); + let manual_loader = original_contract.with_code(loader_asm)?; getters_work!( loader, diff --git a/packages/fuels-programs/src/contract/regular.rs b/packages/fuels-programs/src/contract/regular.rs index 8fc2c00e19..2cd0839a0c 100644 --- a/packages/fuels-programs/src/contract/regular.rs +++ b/packages/fuels-programs/src/contract/regular.rs @@ -31,7 +31,7 @@ pub struct DeployResponse { // In a mod so that we eliminate the footgun of getting the private `code` field without applying // configurables mod code_types { - use fuels_core::Configurables; + use fuels_core::{Configurables, types::errors::Result}; #[derive(Debug, Clone, PartialEq)] pub struct Regular { @@ -47,20 +47,29 @@ mod code_types { } } - pub(crate) fn with_code(self, code: Vec) -> Self { - Self { code, ..self } + pub(crate) fn with_code(self, code: Vec) -> Result { + let mut new_code = code.clone(); + self.configurables.update_constants_in(&mut new_code)?; + + Ok(Self { code, ..self }) } - pub(crate) fn with_configurables(self, configurables: Configurables) -> Self { - Self { + pub(crate) fn with_configurables(self, configurables: Configurables) -> Result { + let mut new_code = self.code.clone(); + configurables.update_constants_in(&mut new_code)?; + + Ok(Self { configurables, ..self - } + }) } pub(crate) fn code(&self) -> Vec { let mut code = self.code.clone(); - self.configurables.update_constants_in(&mut code); + self.configurables + .update_constants_in(&mut code) + .expect("is ok as it is checked in `with_code` and `with_configurables`"); + code } } @@ -68,19 +77,19 @@ mod code_types { pub use code_types::*; impl Contract { - pub fn with_code(self, code: Vec) -> Self { - Self { - code: self.code.with_code(code), + pub fn with_code(self, code: Vec) -> Result { + Ok(Self { + code: self.code.with_code(code)?, salt: self.salt, storage_slots: self.storage_slots, - } + }) } - pub fn with_configurables(self, configurables: impl Into) -> Self { - Self { - code: self.code.with_configurables(configurables.into()), + pub fn with_configurables(self, configurables: impl Into) -> Result { + Ok(Self { + code: self.code.with_configurables(configurables.into())?, ..self - } + }) } pub fn code(&self) -> Vec { diff --git a/packages/fuels-programs/src/contract/storage.rs b/packages/fuels-programs/src/contract/storage.rs index ee1201b6c1..136b980881 100644 --- a/packages/fuels-programs/src/contract/storage.rs +++ b/packages/fuels-programs/src/contract/storage.rs @@ -147,6 +147,7 @@ pub(crate) fn expected_storage_slots_filepath(contract_binary: &Path) -> Option< Some(dir.join(format!("{binary_filename}-storage_slots.json"))) } + pub(crate) fn validate_path_and_extension(file_path: &Path, extension: &str) -> Result<()> { if !file_path.exists() { return Err(error!(IO, "file {file_path:?} does not exist")); diff --git a/packages/fuels-programs/src/executable.rs b/packages/fuels-programs/src/executable.rs index 021c4800b6..6b2488ead0 100644 --- a/packages/fuels-programs/src/executable.rs +++ b/packages/fuels-programs/src/executable.rs @@ -90,10 +90,11 @@ impl Executable { /// # Returns /// /// The bytecode of the executable with configurables updated. - pub fn code(&self) -> Vec { + pub fn code(&self) -> Result> { let mut code = self.state.code.clone(); - self.state.configurables.update_constants_in(&mut code); - code + self.state.configurables.update_constants_in(&mut code)?; + + Ok(code) } /// Converts the `Executable` into an `Executable`. @@ -133,26 +134,25 @@ impl Executable { } #[deprecated(note = "Use `configurables_offset_in_code` instead")] - pub fn data_offset_in_code(&self) -> usize { - self.loader_code().configurables_section_offset() + pub fn data_offset_in_code(&self) -> Result { + Ok(self.loader_code()?.configurables_section_offset()) } - pub fn configurables_offset_in_code(&self) -> usize { - self.loader_code().configurables_section_offset() + pub fn configurables_offset_in_code(&self) -> Result { + Ok(self.loader_code()?.configurables_section_offset()) } - fn loader_code(&self) -> LoaderCode { + fn loader_code(&self) -> Result { let mut code = self.state.code.clone(); - self.state.configurables.update_constants_in(&mut code); + self.state.configurables.update_constants_in(&mut code)?; LoaderCode::from_normal_binary(code) - .expect("checked before turning into a Executable") } /// Returns the code of the loader executable with configurables applied. - pub fn code(&self) -> Vec { - self.loader_code().as_bytes().to_vec() + pub fn code(&self) -> Result> { + Ok(self.loader_code()?.as_bytes().to_vec()) } /// A Blob containing the original executable code minus the data section. @@ -202,7 +202,7 @@ fn validate_loader_can_be_made_from_code( mut code: Vec, configurables: Configurables, ) -> Result<()> { - configurables.update_constants_in(&mut code); + configurables.update_constants_in(&mut code)?; let _ = LoaderCode::from_normal_binary(code)?; @@ -272,7 +272,7 @@ mod tests { // Given: An Executable and some configurables let code = vec![1u8, 2, 3, 4]; let executable = Executable::::from_bytes(code); - let configurables = Configurables::new(vec![(2, vec![1])]); + let configurables = Configurables::new(vec![(2, vec![1])], vec![]); // When: Setting new configurables let new_executable = executable.with_configurables(configurables.clone()); @@ -285,12 +285,12 @@ mod tests { fn test_executable_regular_code() { // Given: An Executable with some code and configurables let code = vec![1u8, 2, 3, 4]; - let configurables = Configurables::new(vec![(1, vec![1])]); + let configurables = Configurables::new(vec![(1, vec![1])], vec![]); let executable = Executable::::from_bytes(code.clone()).with_configurables(configurables); // When: Retrieving the code after applying configurables - let modified_code = executable.code(); + let modified_code = executable.code().unwrap(); assert_eq!(modified_code, vec![1, 1, 3, 4]); } @@ -330,7 +330,7 @@ mod tests { assert_eq!(blob.as_ref(), data_stripped_code); // And: Loader code should match expected binary - let loader_code = loader.code(); + let loader_code = loader.code().unwrap(); assert_eq!( loader_code, @@ -373,7 +373,7 @@ mod tests { assert_eq!(blob.as_ref(), configurable_stripped_code); // And: Loader code should match expected binary - let loader_code = loader.code(); + let loader_code = loader.code().unwrap(); assert_eq!( loader_code, LoaderCode::from_normal_binary(code).unwrap().as_bytes() diff --git a/packages/fuels/src/lib.rs b/packages/fuels/src/lib.rs index c83e8ebb68..12cc58abcd 100644 --- a/packages/fuels/src/lib.rs +++ b/packages/fuels/src/lib.rs @@ -38,7 +38,7 @@ pub mod programs { } pub mod core { - pub use fuels_core::{Configurables, codec, constants, offsets, traits}; + pub use fuels_core::{Configurables, ConfigurablesReader, codec, constants, offsets, traits}; } pub mod crypto {