From d003a5f5a82930b1585e0fdecf36cc131bfa26d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaya=20G=C3=B6kalp?= Date: Fri, 16 Aug 2024 23:47:54 -0700 Subject: [PATCH] feat: Implement ldc based automatic contract chunking with auto split and loader generation (#6250) ## Description This PR adds chunk deployment based on LDC. Contracts larger than 100 kb size, is split into chunks and chunks are deployed as `blobs`. Out of these blobs we create a loader contract, which loads all the blobs using `LDC` opcode. One important thing is that this feature works nicely with the proxy feature introduced in #6069, so a large contract, with proxy can be deployed directly. Large contract will be split into chunks, chunks will get deployed, loader will get get generated and deployed, after all these a proxy contract is deployed and pointed to the loader contract deployed. Simple chunked deploy, chunked deployment re routing the call, chunked deployment behind a proxy re routes the call is tested. --------- Co-authored-by: Sophie Dankel <47993817+sdankel@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .../src/forc/plugins/forc_client/index.md | 4 + forc-plugins/forc-client/src/op/deploy.rs | 157 +++++++-- forc-plugins/forc-client/src/util/pkg.rs | 6 +- .../test/data/big_contract/.gitignore | 2 + .../test/data/big_contract/Forc.toml | 8 + .../data/big_contract/big_contract-abi.json | 327 ++++++++++++++++++ .../test/data/big_contract/src/main.sw | 129 +++++++ forc-plugins/forc-client/tests/deploy.rs | 177 +++++++++- 9 files changed, 784 insertions(+), 28 deletions(-) create mode 100644 forc-plugins/forc-client/test/data/big_contract/.gitignore create mode 100644 forc-plugins/forc-client/test/data/big_contract/Forc.toml create mode 100644 forc-plugins/forc-client/test/data/big_contract/big_contract-abi.json create mode 100644 forc-plugins/forc-client/test/data/big_contract/src/main.sw diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b054468d04a..8225f599c58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -535,7 +535,7 @@ jobs: chmod +x fuel-core-${{ needs.get-fuel-core-version.outputs.fuel_core_version }}-x86_64-unknown-linux-gnu/fuel-core mv fuel-core-${{ needs.get-fuel-core-version.outputs.fuel_core_version }}-x86_64-unknown-linux-gnu/fuel-core /usr/local/bin/fuel-core - name: Run tests - run: cargo test --locked --release -p forc-client + run: cargo test --locked --release -p forc-client -- --test-threads 1 cargo-test-sway-lsp: runs-on: ubuntu-latest steps: diff --git a/docs/book/src/forc/plugins/forc_client/index.md b/docs/book/src/forc/plugins/forc_client/index.md index 986be423dac..4127c9956de 100644 --- a/docs/book/src/forc/plugins/forc_client/index.md +++ b/docs/book/src/forc/plugins/forc_client/index.md @@ -176,3 +176,7 @@ address = "0xd8c4b07a0d1be57b228f4c18ba7bca0c8655eb6e9d695f14080f2cf4fc7cd946" # ``` If an `address` is present, `forc` calls into that contract to update its `target` instead of deploying a new contract. Since a new proxy deployment adds its own `address` into the `Forc.toml` automatically, you can simply enable the proxy once and after the initial deployment, `forc` will keep updating the target accordingly for each new deployment of the same contract. + +## Large Contracts + +For contracts over 100KB, `forc-deploy` will split the contract into chunks and deploy the contract with multiple transactions using the Rust SDK's [loader contract](https://github.com/FuelLabs/fuels-rs/blob/master/docs/src/deploying/large_contracts.md) functionality. Chunks that have already been deployed will be reused on subsequent deployments. diff --git a/forc-plugins/forc-client/src/op/deploy.rs b/forc-plugins/forc-client/src/op/deploy.rs index 1188a88b353..8d5c78d13a5 100644 --- a/forc-plugins/forc-client/src/op/deploy.rs +++ b/forc-plugins/forc-client/src/op/deploy.rs @@ -22,7 +22,10 @@ use fuel_core_client::client::FuelClient; use fuel_crypto::fuel_types::ChainId; use fuel_tx::{Salt, Transaction}; use fuel_vm::prelude::*; -use fuels::programs::contract::{LoadConfiguration, StorageConfiguration}; +use fuels::{ + programs::contract::{LoadConfiguration, StorageConfiguration}, + types::{bech32::Bech32ContractId, transaction_builders::Blob}, +}; use fuels_accounts::{provider::Provider, wallet::WalletUnlocked, Account}; use fuels_core::types::{transaction::TxPolicies, transaction_builders::CreateTransactionBuilder}; use futures::FutureExt; @@ -38,10 +41,17 @@ use std::{ use sway_core::language::parsed::TreeType; use sway_core::BuildTarget; +/// Maximum contract size allowed for a single contract. If the target +/// contract size is bigger than this amount, forc-deploy will automatically +/// starts dividing the contract and deploy them in chunks automatically. +/// The value is in bytes. +const MAX_CONTRACT_SIZE: usize = 100_000; + #[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord)] pub struct DeployedContract { pub id: fuel_tx::ContractId, pub proxy: Option, + pub chunked: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -122,13 +132,77 @@ fn validate_and_parse_salts<'a>( Ok(contract_salt_map) } +/// Depending on the cli options user passed, either returns storage slots from +/// compiled package, or the ones user provided as overrides. +fn resolve_storage_slots( + command: &cmd::Deploy, + compiled: &BuiltPackage, +) -> Result> { + let mut storage_slots = + if let Some(storage_slot_override_file) = &command.override_storage_slots { + let storage_slots_file = std::fs::read_to_string(storage_slot_override_file)?; + let storage_slots: Vec = serde_json::from_str(&storage_slots_file)?; + storage_slots + } else { + compiled.storage_slots.clone() + }; + storage_slots.sort(); + Ok(storage_slots) +} + +/// Creates blobs from the contract to deploy contracts that are larger than +/// `MAX_CONTRACT_SIZE`. Created blobs are deployed, and a loader contract is +/// generated such that it loads all the deployed blobs, and provides the user +/// a single contract (loader contract that loads the blobs) to call into. +async fn deploy_chunked( + command: &cmd::Deploy, + compiled: &BuiltPackage, + salt: Salt, + signing_key: &SecretKey, + provider: &Provider, + pkg_name: &str, +) -> anyhow::Result { + println_action_green("Deploying", &format!("contract {pkg_name} chunks")); + + let storage_slots = resolve_storage_slots(command, compiled)?; + let chain_info = provider.chain_info().await?; + let target = Target::from_str(&chain_info.name).unwrap_or(Target::testnet()); + let contract_url = match target.explorer_url() { + Some(explorer_url) => format!("{explorer_url}/contract/0x"), + None => "".to_string(), + }; + + let wallet = WalletUnlocked::new_from_private_key(*signing_key, Some(provider.clone())); + let blobs = compiled + .bytecode + .bytes + .chunks(MAX_CONTRACT_SIZE) + .map(|chunk| Blob::new(chunk.to_vec())) + .collect(); + + let tx_policies = tx_policies_from_cmd(command); + let contract_id = + fuels::programs::contract::Contract::loader_from_blobs(blobs, salt, storage_slots)? + .deploy(&wallet, tx_policies) + .await? + .into(); + + println_action_green( + "Finished", + &format!("deploying loader contract for {pkg_name} {contract_url}{contract_id}"), + ); + + Ok(contract_id) +} + /// Deploys a new proxy contract for the given package. async fn deploy_new_proxy( + command: &cmd::Deploy, pkg_name: &str, impl_contract: &fuel_tx::ContractId, provider: &Provider, signing_key: &SecretKey, -) -> Result { +) -> Result { fuels::macros::abigen!(Contract( name = "ProxyContract", abi = "forc-plugins/forc-client/proxy_abi/proxy_contract-abi.json" @@ -149,12 +223,14 @@ async fn deploy_new_proxy( .with_storage_configuration(storage_configuration) .with_configurables(configurables); - let proxy_contract_id = fuels::programs::contract::Contract::load_from( + let tx_policies = tx_policies_from_cmd(command); + let proxy_contract_id: ContractId = fuels::programs::contract::Contract::load_from( proxy_dir_output.join("proxy.bin"), configuration, )? - .deploy(&wallet, TxPolicies::default()) - .await?; + .deploy(&wallet, tx_policies) + .await? + .into(); let chain_info = provider.chain_info().await?; let target = Target::from_str(&chain_info.name).unwrap_or(Target::testnet()); @@ -168,10 +244,11 @@ async fn deploy_new_proxy( &format!("deploying proxy contract for {pkg_name} {contract_url}{proxy_contract_id}"), ); - let instance = ProxyContract::new(&proxy_contract_id, wallet); + let proxy_contract_bech_id: Bech32ContractId = proxy_contract_id.into(); + let instance = ProxyContract::new(&proxy_contract_bech_id, wallet); instance.methods().initialize_proxy().call().await?; println_action_green("Initialized", &format!("proxy contract for {pkg_name}")); - Ok(proxy_contract_id.into()) + Ok(proxy_contract_id) } /// Builds and deploys contract(s). If the given path corresponds to a workspace, all deployable members @@ -277,7 +354,24 @@ pub async fn deploy(command: cmd::Deploy) -> Result> { bail!("Both `--salt` and `--default-salt` were specified: must choose one") } }; - let deployed_contract_id = deploy_pkg(&command, pkg, salt, &provider, &signing_key).await?; + let bytecode_size = pkg.bytecode.bytes.len(); + let deployed_contract_id = if bytecode_size > MAX_CONTRACT_SIZE { + // Deploy chunked + let node_url = get_node_url(&command.node, &pkg.descriptor.manifest_file.network)?; + let provider = Provider::connect(node_url).await?; + + deploy_chunked( + &command, + pkg, + salt, + &signing_key, + &provider, + &pkg.descriptor.name, + ) + .await? + } else { + deploy_pkg(&command, pkg, salt, &provider, &signing_key).await? + }; let proxy_id = match &pkg.descriptor.manifest_file.proxy { Some(forc_pkg::manifest::Proxy { @@ -306,9 +400,14 @@ pub async fn deploy(command: cmd::Deploy) -> Result> { }) => { let pkg_name = &pkg.descriptor.name; // Deploy a new proxy contract. - let deployed_proxy_contract = - deploy_new_proxy(pkg_name, &deployed_contract_id, &provider, &signing_key) - .await?; + let deployed_proxy_contract = deploy_new_proxy( + &command, + pkg_name, + &deployed_contract_id, + &provider, + &signing_key, + ) + .await?; // Update manifest file such that the proxy address field points to the new proxy contract. update_proxy_address_in_manifest( @@ -324,6 +423,7 @@ pub async fn deploy(command: cmd::Deploy) -> Result> { let deployed_contract = DeployedContract { id: deployed_contract_id, proxy: proxy_id, + chunked: bytecode_size > MAX_CONTRACT_SIZE, }; deployed_contracts.push(deployed_contract); } @@ -357,8 +457,17 @@ async fn confirm_transaction_details( _ => "", }; + let pkg_bytecode_len = pkg.bytecode.bytes.len(); + let blob_text = if pkg_bytecode_len > MAX_CONTRACT_SIZE { + let number_of_blobs = pkg_bytecode_len.div_ceil(MAX_CONTRACT_SIZE); + tx_count += number_of_blobs; + &format!(" + {number_of_blobs} blobs") + } else { + "" + }; + format!( - "deploy {}{proxy_text}", + "deploy {}{blob_text}{proxy_text}", pkg.descriptor.manifest_file.project_name() ) }) @@ -408,21 +517,12 @@ pub async fn deploy_pkg( let bytecode = &compiled.bytecode.bytes; - let mut storage_slots = - if let Some(storage_slot_override_file) = &command.override_storage_slots { - let storage_slots_file = std::fs::read_to_string(storage_slot_override_file)?; - let storage_slots: Vec = serde_json::from_str(&storage_slots_file)?; - storage_slots - } else { - compiled.storage_slots.clone() - }; - storage_slots.sort(); - + let storage_slots = resolve_storage_slots(command, compiled)?; let contract = Contract::from(bytecode.clone()); let root = contract.root(); let state_root = Contract::initial_state_root(storage_slots.iter()); let contract_id = contract.id(&salt, &root, &state_root); - let tx_policies = TxPolicies::default(); + let tx_policies = tx_policies_from_cmd(command); let mut tb = CreateTransactionBuilder::prepare_contract_deployment( bytecode.clone(), @@ -523,6 +623,17 @@ pub async fn deploy_pkg( Ok(contract_id) } +fn tx_policies_from_cmd(cmd: &cmd::Deploy) -> TxPolicies { + let mut tx_policies = TxPolicies::default(); + if let Some(max_fee) = cmd.gas.max_fee { + tx_policies = tx_policies.with_max_fee(max_fee); + } + if let Some(script_gas_limit) = cmd.gas.script_gas_limit { + tx_policies = tx_policies.with_script_gas_limit(script_gas_limit); + } + tx_policies +} + fn build_opts_from_cmd(cmd: &cmd::Deploy) -> pkg::BuildOpts { pkg::BuildOpts { pkg: pkg::PkgOpts { diff --git a/forc-plugins/forc-client/src/util/pkg.rs b/forc-plugins/forc-client/src/util/pkg.rs index 94a068a5bea..3aa8f6b2236 100644 --- a/forc-plugins/forc-client/src/util/pkg.rs +++ b/forc-plugins/forc-client/src/util/pkg.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; use std::{collections::HashMap, path::Path, sync::Arc}; /// The name of the folder that forc generated proxy contract project will reside at. -pub const PROXY_CONTRACT_FOLDER_NAME: &str = ".generated_proxy_contracts"; +pub const GENERATED_CONTRACT_FOLDER_NAME: &str = ".generated_contracts"; pub const PROXY_CONTRACT_BIN: &[u8] = include_bytes!("../../proxy_abi/proxy_contract.bin"); pub const PROXY_CONTRACT_STORAGE_SLOTS: &str = include_str!("../../proxy_abi/proxy_contract-storage_slots.json"); @@ -42,8 +42,8 @@ pub(crate) fn update_proxy_address_in_manifest( pub(crate) fn create_proxy_contract(pkg_name: &str) -> Result { // Create the proxy contract folder. let proxy_contract_dir = user_forc_directory() - .join(PROXY_CONTRACT_FOLDER_NAME) - .join(pkg_name); + .join(GENERATED_CONTRACT_FOLDER_NAME) + .join(format!("{}-proxy", pkg_name)); std::fs::create_dir_all(&proxy_contract_dir)?; std::fs::write( proxy_contract_dir.join(PROXY_BIN_FILE_NAME), diff --git a/forc-plugins/forc-client/test/data/big_contract/.gitignore b/forc-plugins/forc-client/test/data/big_contract/.gitignore new file mode 100644 index 00000000000..77d3844f58c --- /dev/null +++ b/forc-plugins/forc-client/test/data/big_contract/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/forc-plugins/forc-client/test/data/big_contract/Forc.toml b/forc-plugins/forc-client/test/data/big_contract/Forc.toml new file mode 100644 index 00000000000..539c466315e --- /dev/null +++ b/forc-plugins/forc-client/test/data/big_contract/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "big_contract" + +[dependencies] +std = { path = "../../../../../sway-lib-std/" } diff --git a/forc-plugins/forc-client/test/data/big_contract/big_contract-abi.json b/forc-plugins/forc-client/test/data/big_contract/big_contract-abi.json new file mode 100644 index 00000000000..c3c892e05b5 --- /dev/null +++ b/forc-plugins/forc-client/test/data/big_contract/big_contract-abi.json @@ -0,0 +1,327 @@ +{ + "programType": "contract", + "specVersion": "1", + "encodingVersion": "1", + "concreteTypes": [ + { + "type": "()", + "concreteTypeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" + }, + { + "type": "(bool, u64)", + "concreteTypeId": "c998ca9a5f221fe7b5c66ae70c8a9562b86d964408b00d17f883c906bc1fe4be", + "metadataTypeId": 0 + }, + { + "type": "[bool; 3]", + "concreteTypeId": "4926d35d1a5157936b0a29bc126b8aace6d911209a5c130e9b716b0c73643ea6", + "metadataTypeId": 2 + }, + { + "type": "[u64; 3]", + "concreteTypeId": "776fb5a3824169d6736138565fdc20aad684d9111266a5ff6d5c675280b7e199", + "metadataTypeId": 3 + }, + { + "type": "b256", + "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b" + }, + { + "type": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "type": "enum Location", + "concreteTypeId": "6966f221767e2951c5d68ba5e81e897449375a9f51f5e4c9e3b65f1dd7defabb", + "metadataTypeId": 4 + }, + { + "type": "str[4]", + "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a" + }, + { + "type": "struct Person", + "concreteTypeId": "a6b272bcf0f572840e2cc1241bdcf8d42db51eeb1aead750c8e608160f83f926", + "metadataTypeId": 6 + }, + { + "type": "struct SimpleStruct", + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "metadataTypeId": 7 + }, + { + "type": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + }, + { + "type": "u256", + "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e" + }, + { + "type": "u32", + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc" + }, + { + "type": "u64", + "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + }, + { + "type": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + ], + "metadataTypes": [ + { + "type": "(_, _)", + "metadataTypeId": 0, + "components": [ + { + "name": "__tuple_element", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "__tuple_element", + "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + } + ] + }, + { + "type": "[_; 2]", + "metadataTypeId": 1, + "components": [ + { + "name": "__array_element", + "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + } + ] + }, + { + "type": "[_; 3]", + "metadataTypeId": 2, + "components": [ + { + "name": "__array_element", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + ] + }, + { + "type": "[_; 3]", + "metadataTypeId": 3, + "components": [ + { + "name": "__array_element", + "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + } + ] + }, + { + "type": "enum Location", + "metadataTypeId": 4, + "components": [ + { + "name": "Earth", + "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + }, + { + "name": "Mars", + "typeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" + } + ] + }, + { + "type": "str", + "metadataTypeId": 5 + }, + { + "type": "struct Person", + "metadataTypeId": 6, + "components": [ + { + "name": "name", + "typeId": 5 + }, + { + "name": "age", + "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + }, + { + "name": "alive", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "location", + "typeId": 4 + }, + { + "name": "some_tuple", + "typeId": 0 + }, + { + "name": "some_array", + "typeId": 1 + }, + { + "name": "some_b256", + "typeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b" + } + ] + }, + { + "type": "struct SimpleStruct", + "metadataTypeId": 7, + "components": [ + { + "name": "a", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "b", + "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + } + ] + } + ], + "functions": [ + { + "inputs": [], + "name": "assert_configurables", + "output": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "attributes": null + }, + { + "inputs": [ + { + "name": "loc", + "concreteTypeId": "6966f221767e2951c5d68ba5e81e897449375a9f51f5e4c9e3b65f1dd7defabb" + } + ], + "name": "enum_input_output", + "output": "6966f221767e2951c5d68ba5e81e897449375a9f51f5e4c9e3b65f1dd7defabb", + "attributes": null + }, + { + "inputs": [ + { + "name": "index", + "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + } + ], + "name": "get_storage", + "output": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "attributes": [ + { + "name": "storage", + "arguments": [ + "read" + ] + } + ] + }, + { + "inputs": [], + "name": "large_blob", + "output": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "attributes": null + }, + { + "inputs": [ + { + "name": "value", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + ], + "name": "push_storage", + "output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d", + "attributes": [ + { + "name": "storage", + "arguments": [ + "read", + "write" + ] + } + ] + }, + { + "inputs": [ + { + "name": "person", + "concreteTypeId": "a6b272bcf0f572840e2cc1241bdcf8d42db51eeb1aead750c8e608160f83f926" + } + ], + "name": "struct_input_output", + "output": "a6b272bcf0f572840e2cc1241bdcf8d42db51eeb1aead750c8e608160f83f926", + "attributes": null + } + ], + "loggedTypes": [], + "messagesTypes": [], + "configurables": [ + { + "name": "BOOL", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "offset": 380112 + }, + { + "name": "U8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "offset": 380232 + }, + { + "name": "U16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "offset": 380176 + }, + { + "name": "U32", + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", + "offset": 380216 + }, + { + "name": "U64", + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", + "offset": 380224 + }, + { + "name": "U256", + "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", + "offset": 380184 + }, + { + "name": "B256", + "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b", + "offset": 380080 + }, + { + "name": "CONFIGURABLE_STRUCT", + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "offset": 380136 + }, + { + "name": "CONFIGURABLE_ENUM", + "concreteTypeId": "6966f221767e2951c5d68ba5e81e897449375a9f51f5e4c9e3b65f1dd7defabb", + "offset": 380120 + }, + { + "name": "ARRAY_BOOL", + "concreteTypeId": "4926d35d1a5157936b0a29bc126b8aace6d911209a5c130e9b716b0c73643ea6", + "offset": 380048 + }, + { + "name": "ARRAY_U64", + "concreteTypeId": "776fb5a3824169d6736138565fdc20aad684d9111266a5ff6d5c675280b7e199", + "offset": 380056 + }, + { + "name": "TUPLE_BOOL_U64", + "concreteTypeId": "c998ca9a5f221fe7b5c66ae70c8a9562b86d964408b00d17f883c906bc1fe4be", + "offset": 380160 + }, + { + "name": "STR_4", + "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a", + "offset": 380152 + } + ] +} \ No newline at end of file diff --git a/forc-plugins/forc-client/test/data/big_contract/src/main.sw b/forc-plugins/forc-client/test/data/big_contract/src/main.sw new file mode 100644 index 00000000000..b62ee995940 --- /dev/null +++ b/forc-plugins/forc-client/test/data/big_contract/src/main.sw @@ -0,0 +1,129 @@ +contract; + +use std::storage::storage_vec::*; +use std::hash::*; + +abi MyContract { + fn large_blob() -> bool; + + fn enum_input_output(loc: Location) -> Location; + + fn struct_input_output(person: Person) -> Person; + + #[storage(read, write)] + fn push_storage(value: u16); + + #[storage(read)] + fn get_storage(index: u64) -> u16; + + fn assert_configurables() -> bool; +} + +enum Location { + Earth: u64, + Mars: (), +} + +struct Person { + name: str, + age: u64, + alive: bool, + location: Location, + some_tuple: (bool, u64), + some_array: [u64; 2], + some_b256: b256, +} + +struct SimpleStruct { + a: bool, + b: u64, +} + +storage { + my_vec: StorageVec = StorageVec {}, +} + +configurable { + BOOL: bool = true, + U8: u8 = 1, + U16: u16 = 2, + U32: u32 = 3, + U64: u32 = 4, + U256: u256 = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu256, + B256: b256 = 0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, + CONFIGURABLE_STRUCT: SimpleStruct = SimpleStruct { a: true, b: 5 }, + CONFIGURABLE_ENUM: Location = Location::Earth(1), + ARRAY_BOOL: [bool; 3] = [true, false, true], + ARRAY_U64: [u64; 3] = [9, 8, 7], + TUPLE_BOOL_U64: (bool, u64) = (true, 11), + STR_4: str[4] = __to_str_array("abcd"), +} + +impl core::ops::Eq for Location { + fn eq(self, other: Location) -> bool { + match (self, other) { + (Location::Earth(inner1), Location::Earth(inner2)) => inner1 == inner2, + (Location::Mars, Location::Mars) => true, + _ => false, + } + } +} + +impl MyContract for Contract { + fn large_blob() -> bool { + asm() { + blob i91000; + } + true + } + + fn enum_input_output(loc: Location) -> Location { + loc + } + + fn struct_input_output(person: Person) -> Person { + person + } + + #[storage(read, write)] + fn push_storage(value: u16) { + storage.my_vec.push(value); + } + + #[storage(read)] + fn get_storage(index: u64) -> u16 { + storage.my_vec.get(index).unwrap().read() + } + + fn assert_configurables() -> bool { + assert(BOOL == true); + assert(U8 == 1); + assert(U16 == 2); + assert(U32 == 3); + assert(U64 == 4); + assert(U256 == 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu256); + assert(B256 == 0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB); + assert(CONFIGURABLE_STRUCT.a == true); + assert(CONFIGURABLE_STRUCT.b == 5); + assert(CONFIGURABLE_ENUM == Location::Earth(1)); + assert(ARRAY_BOOL[0] == true); + assert(ARRAY_BOOL[1] == false); + assert(ARRAY_BOOL[2] == true); + assert(ARRAY_U64[0] == 9); + assert(ARRAY_U64[1] == 8); + assert(ARRAY_U64[2] == 7); + assert(TUPLE_BOOL_U64.0 == true); + assert(TUPLE_BOOL_U64.1 == 11); + assert(sha256_str_array(STR_4) == sha256("abcd")); + + // Assert address do not change + let addr_1 = asm(addr: __addr_of(&BOOL)) { + addr: u64 + }; + let addr_2 = asm(addr: __addr_of(&BOOL)) { + addr: u64 + }; + assert(addr_1 == addr_2); + true + } +} diff --git a/forc-plugins/forc-client/tests/deploy.rs b/forc-plugins/forc-client/tests/deploy.rs index 26c15c700fd..1f40025596d 100644 --- a/forc-plugins/forc-client/tests/deploy.rs +++ b/forc-plugins/forc-client/tests/deploy.rs @@ -8,7 +8,10 @@ use forc_client::{ use forc_pkg::manifest::Proxy; use fuel_crypto::SecretKey; use fuel_tx::{ContractId, Salt}; -use fuels::{macros::abigen, types::transaction::TxPolicies}; +use fuels::{ + macros::abigen, + types::{transaction::TxPolicies, AsciiString, Bits256}, +}; use fuels_accounts::{provider::Provider, wallet::WalletUnlocked, Account}; use portpicker::Port; use rand::thread_rng; @@ -111,6 +114,64 @@ fn update_main_sw(tmp_dir: &Path) -> anyhow::Result<()> { Ok(()) } +async fn assert_big_contract_calls(wallet: WalletUnlocked, contract_id: ContractId) { + abigen!(Contract( + name = "BigContract", + abi = "forc-plugins/forc-client/test/data/big_contract/big_contract-abi.json" + )); + + let instance = BigContract::new(contract_id, wallet); + + let result = instance.methods().large_blob().call().await.unwrap().value; + assert!(result); + + let result = instance + .methods() + .enum_input_output(Location::Mars) + .call() + .await + .unwrap() + .value; + assert_eq!(result, Location::Mars); + + let input = Person { + name: AsciiString::new("Alice".into()).unwrap(), + age: 42, + alive: true, + location: Location::Earth(1), + some_tuple: (false, 42), + some_array: [4, 2], + some_b_256: Bits256::zeroed(), + }; + let result = instance + .methods() + .struct_input_output(input.clone()) + .call() + .await + .unwrap() + .value; + assert_eq!(result, input); + + let _ = instance.methods().push_storage(42).call().await.unwrap(); + let result = instance + .methods() + .get_storage(0) + .call() + .await + .unwrap() + .value; + assert_eq!(result, 42); + + let result = instance + .methods() + .assert_configurables() + .call() + .await + .unwrap() + .value; + assert!(result); +} + #[tokio::test] async fn test_simple_deploy() { let (mut node, port) = run_node(); @@ -145,6 +206,7 @@ async fn test_simple_deploy() { ) .unwrap(), proxy: None, + chunked: false, }]; assert_eq!(contract_ids, expected) @@ -186,6 +248,7 @@ async fn test_deploy_submit_only() { ) .unwrap(), proxy: None, + chunked: false, }]; assert_eq!(contract_ids, expected) @@ -235,6 +298,7 @@ async fn test_deploy_fresh_proxy() { ) .unwrap(), ), + chunked: false, }; let expected = vec![impl_contract]; @@ -446,3 +510,114 @@ fn test_deploy_interactive_wrong_password() -> Result<(), rexpect::error::Error> node.kill().unwrap(); Ok(()) } + +#[tokio::test] +async fn chunked_deploy() { + let (mut node, port) = run_node(); + let tmp_dir = tempdir().unwrap(); + let project_dir = test_data_path().join("big_contract"); + copy_dir(&project_dir, tmp_dir.path()).unwrap(); + patch_manifest_file_with_path_std(tmp_dir.path()).unwrap(); + + let pkg = Pkg { + path: Some(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), + target: None, + testnet: false, + }; + let cmd = cmd::Deploy { + pkg, + salt: Some(vec![format!("{}", Salt::default())]), + node: target, + default_signer: true, + ..Default::default() + }; + let deployed_contract = deploy(cmd).await.unwrap().remove(0); + node.kill().unwrap(); + + assert!(deployed_contract.chunked); +} + +#[tokio::test] +async fn chunked_deploy_re_routes_calls() { + let (mut node, port) = run_node(); + let tmp_dir = tempdir().unwrap(); + let project_dir = test_data_path().join("big_contract"); + copy_dir(&project_dir, tmp_dir.path()).unwrap(); + patch_manifest_file_with_path_std(tmp_dir.path()).unwrap(); + + let pkg = Pkg { + path: Some(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_contract = deploy(cmd).await.unwrap().remove(0); + + 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)); + + assert_big_contract_calls(wallet_unlocked, deployed_contract.id).await; + + node.kill().unwrap(); +} + +#[tokio::test] +async fn chunked_deploy_with_proxy_re_routes_call() { + let (mut node, port) = run_node(); + let tmp_dir = tempdir().unwrap(); + let project_dir = test_data_path().join("big_contract"); + copy_dir(&project_dir, tmp_dir.path()).unwrap(); + patch_manifest_file_with_path_std(tmp_dir.path()).unwrap(); + let proxy = Proxy { + enabled: true, + address: None, + }; + patch_manifest_file_with_proxy_table(tmp_dir.path(), proxy).unwrap(); + + let pkg = Pkg { + path: Some(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_contract = deploy(cmd).await.unwrap().remove(0); + + 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)); + + assert_big_contract_calls(wallet_unlocked, deployed_contract.id).await; + + node.kill().unwrap(); +}