From 53615b43997f3a040240640a03c000f4ba815f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaya=20G=C3=B6kalp?= Date: Sun, 6 Oct 2024 07:40:30 -0700 Subject: [PATCH] feat: generate a loader abi of loaders generated for predicates and scripts (#6611) ## Description Thanks a lot to @hal3e for helping debug this. With this PR, there will be an additional abi in the `out` folder for predicates and scripts that are deployed as loaders following the `{package_name}-loader-abi.json` naming convention. This PR adds `loader-abi.json` file generation capability to forc. To do so forc needs to have the old (original) and new (loader) data offsets of the script/predicate. After getting it the difference should be applied to all configurable slots to find out the new locations of the configurable slots inside the loader binary. Basically enables backwards compatibility with the older sdk releases. Since forc can now generate a loader abi with correct configurable offsets, it can be used to load the script as it is, (without the newer loader stuff) and everything will be still working. --- Cargo.lock | 15 +- forc-plugins/forc-client/Cargo.toml | 1 + forc-plugins/forc-client/src/op/deploy.rs | 143 +++++++- .../deployed_script-loader-abi.json | 312 ++++++++++++++++++ .../data/deployed_script/deployed_script.bin | Bin 0 -> 8784 bytes forc-plugins/forc-client/tests/deploy.rs | 205 ++++++++++++ 6 files changed, 669 insertions(+), 7 deletions(-) create mode 100644 forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json create mode 100644 forc-plugins/forc-client/test/data/deployed_script/deployed_script.bin diff --git a/Cargo.lock b/Cargo.lock index ccc06355650..cb617fba612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2744,6 +2744,7 @@ dependencies = [ "hex", "k256", "portpicker", + "pretty_assertions", "rand", "regex", "rexpect 0.5.0", @@ -5952,12 +5953,12 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", - "yansi", + "yansi 1.0.1", ] [[package]] @@ -7350,7 +7351,7 @@ dependencies = [ "thiserror", "tokio", "version_check", - "yansi", + "yansi 0.5.1", ] [[package]] @@ -9469,6 +9470,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/forc-plugins/forc-client/Cargo.toml b/forc-plugins/forc-client/Cargo.toml index f63bb0b99c9..ee1b03ea173 100644 --- a/forc-plugins/forc-client/Cargo.toml +++ b/forc-plugins/forc-client/Cargo.toml @@ -48,6 +48,7 @@ tracing.workspace = true [dev-dependencies] portpicker = "0.1.1" +pretty_assertions = "1.4.1" rexpect = "0.5" tempfile = "3" diff --git a/forc-plugins/forc-client/src/op/deploy.rs b/forc-plugins/forc-client/src/op/deploy.rs index af3c1e6e737..ddaf6a8c54d 100644 --- a/forc-plugins/forc-client/src/op/deploy.rs +++ b/forc-plugins/forc-client/src/op/deploy.rs @@ -18,18 +18,22 @@ use forc_pkg::{manifest::GenericManifestFile, MemberFilter}; use forc_tracing::{println_action_green, println_warning}; use forc_util::default_output_directory; use forc_wallet::utils::default_wallet_path; +use fuel_abi_types::abi::program::Configurable; use fuel_core_client::client::types::{ChainInfo, TransactionStatus}; use fuel_core_client::client::FuelClient; use fuel_crypto::fuel_types::ChainId; use fuel_tx::{Salt, Transaction}; -use fuel_vm::prelude::*; +use fuel_vm::{consts::WORD_SIZE, fuel_asm::op, prelude::*}; use fuels::{ macros::abigen, programs::{ contract::{LoadConfiguration, StorageConfiguration}, executable::Executable, }, - types::{bech32::Bech32ContractId, transaction_builders::Blob}, + types::{ + bech32::Bech32ContractId, + transaction_builders::{Blob, BlobId}, + }, }; use fuels_accounts::{provider::Provider, Account, ViewOnlyAccount}; use fuels_core::types::{transaction::TxPolicies, transaction_builders::CreateTransactionBuilder}; @@ -43,8 +47,8 @@ use std::{ sync::Arc, time::Duration, }; -use sway_core::language::parsed::TreeType; use sway_core::BuildTarget; +use sway_core::{asm_generation::ProgramABI, language::parsed::TreeType}; /// Default maximum contract size allowed for a single contract. If the target /// contract size is bigger than this amount, forc-deploy will automatically @@ -356,6 +360,103 @@ pub async fn deploy(command: cmd::Deploy) -> Result> { Ok(deployed_packages) } +/// Calculates the loader data offset. Returns a `None` if the original `binary` +/// does not have a data section (no configurables and no args). Otherwise +/// returns the new offset of the data section. +fn loader_data_offset(binary: &[u8], blob_id: &BlobId) -> Result> { + // The following code is taken from SDK, and once they expose the offsets + // we will no longer need to maintain this duplicate version here. + + // The final code is going to have this structure (if the data section is non-empty): + // 1. loader instructions + // 2. blob id + // 3. length_of_data_section + // 4. the data_section (updated with configurables as needed) + const BLOB_ID_SIZE: u16 = 32; + const REG_ADDRESS_OF_DATA_AFTER_CODE: u8 = 0x10; + const REG_START_OF_LOADED_CODE: u8 = 0x11; + const REG_GENERAL_USE: u8 = 0x12; + let get_instructions = |num_of_instructions| { + // There are 3 main steps: + // 1. Load the blob content into memory + // 2. Load the data section right after the blob + // 3. Jump to the beginning of the memory where the blob was loaded + [ + // 1. Load the blob content into memory + // Find the start of the hardcoded blob ID, which is located after the loader code ends. + op::move_(REG_ADDRESS_OF_DATA_AFTER_CODE, RegId::PC), + // hold the address of the blob ID. + op::addi( + REG_ADDRESS_OF_DATA_AFTER_CODE, + REG_ADDRESS_OF_DATA_AFTER_CODE, + num_of_instructions * Instruction::SIZE as u16, + ), + // The code is going to be loaded from the current value of SP onwards, save + // the location into REG_START_OF_LOADED_CODE so we can jump into it at the end. + op::move_(REG_START_OF_LOADED_CODE, RegId::SP), + // REG_GENERAL_USE to hold the size of the blob. + op::bsiz(REG_GENERAL_USE, REG_ADDRESS_OF_DATA_AFTER_CODE), + // Push the blob contents onto the stack. + op::ldc(REG_ADDRESS_OF_DATA_AFTER_CODE, 0, REG_GENERAL_USE, 1), + // Move on to the data section length + op::addi( + REG_ADDRESS_OF_DATA_AFTER_CODE, + REG_ADDRESS_OF_DATA_AFTER_CODE, + BLOB_ID_SIZE, + ), + // load the size of the data section into REG_GENERAL_USE + op::lw(REG_GENERAL_USE, REG_ADDRESS_OF_DATA_AFTER_CODE, 0), + // after we have read the length of the data section, we move the pointer to the actual + // data by skipping WORD_SIZE B. + op::addi( + REG_ADDRESS_OF_DATA_AFTER_CODE, + REG_ADDRESS_OF_DATA_AFTER_CODE, + WORD_SIZE as u16, + ), + // load the data section of the executable + op::ldc(REG_ADDRESS_OF_DATA_AFTER_CODE, 0, REG_GENERAL_USE, 2), + // Jump into the memory where the contract is loaded. + // What follows is called _jmp_mem by the sway compiler. + // Subtract the address contained in IS because jmp will add it back. + op::sub( + REG_START_OF_LOADED_CODE, + REG_START_OF_LOADED_CODE, + RegId::IS, + ), + // jmp will multiply by 4, so we need to divide to cancel that out. + op::divi(REG_START_OF_LOADED_CODE, REG_START_OF_LOADED_CODE, 4), + // Jump to the start of the contract we loaded. + op::jmp(REG_START_OF_LOADED_CODE), + ] + }; + + let offset = extract_data_offset(binary)?; + + if binary.len() < offset { + anyhow::bail!("data sectio offset is out of bounds"); + } + + let data_section = binary[offset..].to_vec(); + + if !data_section.is_empty() { + let num_of_instructions = u16::try_from(get_instructions(0).len()) + .expect("to never have more than u16::MAX instructions"); + + let instruction_bytes = get_instructions(num_of_instructions) + .into_iter() + .flat_map(|instruction| instruction.to_bytes()); + + let blob_bytes = blob_id.iter().copied(); + + let loader_offset = + instruction_bytes.count() + blob_bytes.count() + data_section.len().to_be_bytes().len(); + + Ok(Some(loader_offset)) + } else { + Ok(None) + } +} + /// Builds and deploys executable (script and predicate) package(s) as blobs, /// and generates a loader for each of them. pub async fn deploy_executables( @@ -387,6 +488,29 @@ pub async fn deploy_executables( "Saved", &format!("loader bytecode at {}", bin_path.display()), ); + if let Some(loader_data_section_offset) = + loader_data_offset(&pkg.bytecode.bytes, &BlobId::default())? + { + if let ProgramABI::Fuel(mut fuel_abi) = pkg.program_abi.clone() { + println_action_green("Generating", "loader abi for the uploaded executable."); + let json_abi_path = out_dir.join(format!("{pkg_name}-loader-abi.json")); + let original_data_section = extract_data_offset(&pkg.bytecode.bytes).unwrap(); + let offset_shift = original_data_section - loader_data_section_offset; + // if there are configurables in the abi we need to shift them by `offset_shift`. + let configurables = fuel_abi.configurables.clone().map(|configs| { + configs + .into_iter() + .map(|config| Configurable { + offset: config.offset - offset_shift as u64, + ..config.clone() + }) + .collect() + }); + fuel_abi.configurables = configurables; + let json_string = serde_json::to_string_pretty(&fuel_abi)?; + std::fs::write(json_abi_path, json_string)?; + } + } // If the executable is a predicate, we also want to display and save the predicate root. if pkg .descriptor @@ -419,6 +543,19 @@ pub async fn deploy_executables( Ok(deployed_executable) } +fn extract_data_offset(binary: &[u8]) -> Result { + if binary.len() < 16 { + anyhow::bail!( + "given binary is too short to contain a data offset, len: {}", + binary.len() + ); + } + + let data_offset: [u8; 8] = binary[8..16].try_into().expect("checked above"); + + Ok(u64::from_be_bytes(data_offset) as usize) +} + /// Builds and deploys contract(s). If the given path corresponds to a workspace, all deployable members /// will be built and deployed. /// diff --git a/forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json b/forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json new file mode 100644 index 00000000000..81b65c43cd7 --- /dev/null +++ b/forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json @@ -0,0 +1,312 @@ +{ + "programType": "script", + "specVersion": "1", + "encodingVersion": "1", + "concreteTypes": [ + { + "type": "((bool, u8, u16, u32, u64, u256, b256, str[4], (u8, bool), [u32; 3], struct StructWithGeneric, enum EnumWithGeneric), bool, u64, u8)", + "concreteTypeId": "25fbba860b8a1983ebcfa3f135136266a7edb7ca3a7e1f8ec988135c12a9f873", + "metadataTypeId": 2 + }, + { + "type": "(u8, bool)", + "concreteTypeId": "e0128f7be9902d1fe16326cafe703b52038064a7997b03ebfc1c9dd607e1536c", + "metadataTypeId": 1 + }, + { + "type": "[u32; 3]", + "concreteTypeId": "d9fac01ab38fe10950758ae9604da330d6406a71fda3ef1ea818121261132d56", + "metadataTypeId": 4 + }, + { + "type": "b256", + "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b" + }, + { + "type": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "type": "enum EnumWithGeneric", + "concreteTypeId": "37cd1cba311039a851ac8bfa614cc41359b4ad95c8656fcef2e8f504fe7a1272", + "metadataTypeId": 5, + "typeArguments": [ + "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + ] + }, + { + "type": "str[4]", + "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a" + }, + { + "type": "struct StructWithGeneric", + "concreteTypeId": "563310524b4f4447a10d0e50556310253dfb3b5eb4b29c3773222b737c8b7075", + "metadataTypeId": 7, + "typeArguments": [ + "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + ] + }, + { + "type": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + }, + { + "type": "u256", + "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e" + }, + { + "type": "u32", + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc" + }, + { + "type": "u64", + "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + }, + { + "type": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + ], + "metadataTypes": [ + { + "type": "()", + "metadataTypeId": 0 + }, + { + "type": "(_, _)", + "metadataTypeId": 1, + "components": [ + { + "name": "__tuple_element", + "typeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + }, + { + "name": "__tuple_element", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + ] + }, + { + "type": "(_, _, _, _)", + "metadataTypeId": 2, + "components": [ + { + "name": "__tuple_element", + "typeId": 3 + }, + { + "name": "__tuple_element", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "__tuple_element", + "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + }, + { + "name": "__tuple_element", + "typeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + ] + }, + { + "type": "(_, _, _, _, _, _, _, _, _, _, _, _)", + "metadataTypeId": 3, + "components": [ + { + "name": "__tuple_element", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "__tuple_element", + "typeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + }, + { + "name": "__tuple_element", + "typeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + }, + { + "name": "__tuple_element", + "typeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc" + }, + { + "name": "__tuple_element", + "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + }, + { + "name": "__tuple_element", + "typeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e" + }, + { + "name": "__tuple_element", + "typeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b" + }, + { + "name": "__tuple_element", + "typeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a" + }, + { + "name": "__tuple_element", + "typeId": 1 + }, + { + "name": "__tuple_element", + "typeId": 4 + }, + { + "name": "__tuple_element", + "typeId": 7, + "typeArguments": [ + { + "name": "", + "typeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + ] + }, + { + "name": "__tuple_element", + "typeId": 5, + "typeArguments": [ + { + "name": "", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + ] + } + ] + }, + { + "type": "[_; 3]", + "metadataTypeId": 4, + "components": [ + { + "name": "__array_element", + "typeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc" + } + ] + }, + { + "type": "enum EnumWithGeneric", + "metadataTypeId": 5, + "components": [ + { + "name": "VariantOne", + "typeId": 6 + }, + { + "name": "VariantTwo", + "typeId": 0 + } + ], + "typeParameters": [ + 6 + ] + }, + { + "type": "generic D", + "metadataTypeId": 6 + }, + { + "type": "struct StructWithGeneric", + "metadataTypeId": 7, + "components": [ + { + "name": "field_1", + "typeId": 6 + }, + { + "name": "field_2", + "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + } + ], + "typeParameters": [ + 6 + ] + } + ], + "functions": [ + { + "inputs": [ + { + "name": "a", + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc" + }, + { + "name": "contract_addr", + "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b" + } + ], + "name": "main", + "output": "25fbba860b8a1983ebcfa3f135136266a7edb7ca3a7e1f8ec988135c12a9f873", + "attributes": null + } + ], + "loggedTypes": [ + { + "logId": "14454674236531057292", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + ], + "messagesTypes": [], + "configurables": [ + { + "name": "BOOL", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "offset": 136 + }, + { + "name": "U8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "offset": 248 + }, + { + "name": "U16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "offset": 192 + }, + { + "name": "U32", + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", + "offset": 232 + }, + { + "name": "U64", + "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0", + "offset": 240 + }, + { + "name": "U256", + "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", + "offset": 200 + }, + { + "name": "B256", + "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b", + "offset": 104 + }, + { + "name": "STR_4", + "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a", + "offset": 176 + }, + { + "name": "TUPLE", + "concreteTypeId": "e0128f7be9902d1fe16326cafe703b52038064a7997b03ebfc1c9dd607e1536c", + "offset": 184 + }, + { + "name": "ARRAY", + "concreteTypeId": "d9fac01ab38fe10950758ae9604da330d6406a71fda3ef1ea818121261132d56", + "offset": 88 + }, + { + "name": "STRUCT", + "concreteTypeId": "563310524b4f4447a10d0e50556310253dfb3b5eb4b29c3773222b737c8b7075", + "offset": 160 + }, + { + "name": "ENUM", + "concreteTypeId": "37cd1cba311039a851ac8bfa614cc41359b4ad95c8656fcef2e8f504fe7a1272", + "offset": 144 + } + ] +} \ No newline at end of file diff --git a/forc-plugins/forc-client/test/data/deployed_script/deployed_script.bin b/forc-plugins/forc-client/test/data/deployed_script/deployed_script.bin new file mode 100644 index 0000000000000000000000000000000000000000..0daa7de8f3e5707f28d74b70d11d1f5b4395c5af GIT binary patch literal 8784 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 literal 0 HcmV?d00001 diff --git a/forc-plugins/forc-client/tests/deploy.rs b/forc-plugins/forc-client/tests/deploy.rs index d751afbf662..adb83853318 100644 --- a/forc-plugins/forc-client/tests/deploy.rs +++ b/forc-plugins/forc-client/tests/deploy.rs @@ -1090,3 +1090,208 @@ async fn deployed_predicate_call() { node.kill().unwrap(); } + +/// Generates a script instance using SDK, and returns the result as a string. +async fn call_with_sdk_generated_overrides(node_url: &str, contract_id: ContractId) -> String { + let project_dir = test_data_path().join("deployed_script"); + abigen!(Script( + name = "MyScript", + abi = "forc-plugins/forc-client/test/data/deployed_script/deployed_script-abi.json" + )); + let provider = Provider::connect(&node_url).await.unwrap(); + let secret_key = SecretKey::from_str(forc_client::constants::DEFAULT_PRIVATE_KEY).unwrap(); + let wallet_unlocked = WalletUnlocked::new_from_private_key(secret_key, Some(provider.clone())); + let bin_dir = project_dir.join("deployed_script.bin"); + let script_instance = MyScript::new(wallet_unlocked, bin_dir.display().to_string().as_str()); + + let strc = StructWithGeneric { + field_1: 1u8, + field_2: 2, + }; + let encoded = MyScriptConfigurables::default() + .with_BOOL(false) + .unwrap() + .with_U8(1) + .unwrap() + .with_U16(2) + .unwrap() + .with_U32(3) + .unwrap() + .with_U64(4) + .unwrap() + .with_U256(5.into()) + .unwrap() + .with_B256(Bits256::zeroed()) + .unwrap() + .with_ARRAY([1, 2, 3]) + .unwrap() + .with_STRUCT(strc) + .unwrap() + .with_ENUM(EnumWithGeneric::VariantTwo) + .unwrap(); + + let mut script_instance_with_configs = script_instance.with_configurables(encoded); + + let loader_from_sdk = script_instance_with_configs + .convert_into_loader() + .await + .unwrap(); + + let contract_ids_bits256 = Bits256(contract_id.into()); + format!( + "{:?}", + loader_from_sdk + .main(10, contract_ids_bits256) + .with_contract_ids(&[contract_id.into()]) + .call() + .await + .unwrap() + .value + ) +} + +/// Generates a script instance using the shifted abi, and returns the result as a string. +async fn call_with_forc_generated_overrides(node_url: &str, contract_id: ContractId) -> String { + let provider = Provider::connect(&node_url).await.unwrap(); + let secret_key = SecretKey::from_str(forc_client::constants::DEFAULT_PRIVATE_KEY).unwrap(); + let wallet_unlocked = WalletUnlocked::new_from_private_key(secret_key, Some(provider.clone())); + let tmp_dir = tempdir().unwrap(); + let project_dir = test_data_path().join("deployed_script"); + copy_dir(&project_dir, tmp_dir.path()).unwrap(); + patch_manifest_file_with_path_std(tmp_dir.path()).unwrap(); + + let target = NodeTarget { + node_url: Some(node_url.to_string()), + target: None, + testnet: false, + }; + let pkg = Pkg { + path: Some(tmp_dir.path().display().to_string()), + ..Default::default() + }; + let cmd = cmd::Deploy { + pkg, + salt: Some(vec![format!("{}", Salt::default())]), + node: target, + default_signer: true, + ..Default::default() + }; + + expect_deployed_script(deploy(cmd).await.unwrap().remove(0)); + + // Since `abigen!` macro does not allow for dynamic paths, we need to + // pre-generate the loader bin and abi and read them from project dir. Here + // we are ensuring forc-deploy indeed generated the files we are basing our + // tests below. + let generated_loader_abi_path = tmp_dir.path().join("out/deployed_script-loader-abi.json"); + let generated_loader_abi = fs::read_to_string(generated_loader_abi_path).unwrap(); + + // this path is basically, `forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json`. + let used_loader_abi_path = project_dir.join("deployed_script-loader-abi.json"); + let used_loader_abi = fs::read_to_string(used_loader_abi_path).unwrap(); + + assert_eq!(generated_loader_abi, used_loader_abi); + + let generated_loader_bin = tmp_dir.path().join("out/deployed_script-loader.bin"); + abigen!(Script( + name = "MyScript", + abi = "forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json" + )); + let forc_generated_script_instance = MyScript::new( + wallet_unlocked, + generated_loader_bin.display().to_string().as_str(), + ); + let strc = StructWithGeneric { + field_1: 1u8, + field_2: 2, + }; + let encoded = MyScriptConfigurables::default() + .with_BOOL(false) + .unwrap() + .with_U8(1) + .unwrap() + .with_U16(2) + .unwrap() + .with_U32(3) + .unwrap() + .with_U64(4) + .unwrap() + .with_U256(5.into()) + .unwrap() + .with_B256(Bits256::zeroed()) + .unwrap() + .with_ARRAY([1, 2, 3]) + .unwrap() + .with_STRUCT(strc) + .unwrap() + .with_ENUM(EnumWithGeneric::VariantTwo) + .unwrap(); + + let forc_generated_script_with_configs = + forc_generated_script_instance.with_configurables(encoded); + let contract_ids_bits256 = Bits256(contract_id.into()); + format!( + "{:?}", + forc_generated_script_with_configs + .main(10, contract_ids_bits256) + .with_contract_ids(&[contract_id.into()]) + .call() + .await + .unwrap() + .value + ) +} + +#[tokio::test] +async fn offset_shifted_abi_works() { + // To test if offset shifted abi works or not, we generate a loader + // contract using sdk and give a configurable override, and call the + // main function. + + // We also create the shited abi using forc-deploy and create a script + // instance using this new shifted abi, and generate a normal script out of + // the loader binary generated again by forc-deploy. + + // We then override the configurables with the same values as sdk flow on + // this script, generated with loader abi and bin coming from forc-deploy. + + // If returned value is equal, than the configurables work correctly. + let (mut node, port) = run_node(); + // Deploy the contract the script is going to be calling. + let contract_tmp_dir = tempdir().unwrap(); + let project_dir = test_data_path().join("standalone_contract"); + copy_dir(&project_dir, contract_tmp_dir.path()).unwrap(); + patch_manifest_file_with_path_std(contract_tmp_dir.path()).unwrap(); + + let pkg = Pkg { + path: Some(contract_tmp_dir.path().display().to_string()), + ..Default::default() + }; + + let node_url = format!("http://127.0.0.1:{}/v1/graphql", port); + let target = NodeTarget { + node_url: Some(node_url.clone()), + target: None, + testnet: false, + }; + let cmd = cmd::Deploy { + pkg, + salt: Some(vec![format!("{}", Salt::default())]), + node: target, + default_signer: true, + ..Default::default() + }; + let deployed_packages = deploy(cmd).await.unwrap().remove(0); + let contract = expect_deployed_contract(deployed_packages); + let contract_id = contract.id; + // Generating the sdk loader bytecode with configurables. + let loader_with_configs_from_sdk = + call_with_sdk_generated_overrides(&node_url, contract_id).await; + + // Genearating the forc-deploy loader bytecode and loader abi. + let loader_with_configs_from_forc = + call_with_forc_generated_overrides(&node_url, contract_id).await; + pretty_assertions::assert_eq!(loader_with_configs_from_forc, loader_with_configs_from_sdk); + + node.kill().unwrap() +}