Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement ldc based automatic contract chunking #6250

Merged
merged 24 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
67b020d
feat: loader contract generation
kayagokalp Aug 8, 2024
9fed5ab
feat: implemented chunking and chunk deployment
kayagokalp Aug 8, 2024
11e3097
test: contract chunking
kayagokalp Aug 8, 2024
45a0358
minor ux improvements
kayagokalp Aug 8, 2024
b849312
test: add re route call
kayagokalp Aug 8, 2024
426d2da
test: chunked deploy with proxy
kayagokalp Aug 8, 2024
44168e4
run tests sequantially
kayagokalp Aug 8, 2024
11333a3
Merge branch 'master' into kayagokalp/proxy-with-chunks
sdankel Aug 9, 2024
8ede08d
Merge branch 'master' into kayagokalp/proxy-with-chunks
kayagokalp Aug 15, 2024
e0cfba5
feat: use sdk for chunked deployment
kayagokalp Aug 16, 2024
9c0b860
fix: invalid contract id reporting fixed for proxy and chunked
kayagokalp Aug 16, 2024
23a7bcc
add blobs keyword to details
kayagokalp Aug 16, 2024
2da7ee1
chore fix nits
kayagokalp Aug 16, 2024
1754854
Merge branch 'master' into kayagokalp/proxy-with-chunks
kayagokalp Aug 16, 2024
14bb5d5
fix comment
kayagokalp Aug 16, 2024
021330b
chore: clippy nits
kayagokalp Aug 16, 2024
495e80a
fmt
kayagokalp Aug 16, 2024
3688741
Update deploy.rs
kayagokalp Aug 16, 2024
d2562a5
Merge branch 'master' into kayagokalp/proxy-with-chunks
sdankel Aug 16, 2024
2fde7ea
Additional tests for contract chunking in forc (#6428)
sdankel Aug 16, 2024
7f8a6ee
Merge branch 'master' into kayagokalp/proxy-with-chunks
sdankel Aug 16, 2024
235aaf4
Merge branch 'master' into kayagokalp/proxy-with-chunks
sdankel Aug 16, 2024
e49fc52
Merge branch 'master' into kayagokalp/proxy-with-chunks
Voxelot Aug 16, 2024
2f861ae
Update forc-client docs
sdankel Aug 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
157 changes: 134 additions & 23 deletions forc-plugins/forc-client/src/op/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
kayagokalp marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord)]
pub struct DeployedContract {
pub id: fuel_tx::ContractId,
pub proxy: Option<fuel_tx::ContractId>,
pub chunked: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -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(
kayagokalp marked this conversation as resolved.
Show resolved Hide resolved
command: &cmd::Deploy,
compiled: &BuiltPackage,
) -> Result<Vec<fuel_tx::StorageSlot>> {
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<StorageSlot> = serde_json::from_str(&storage_slots_file)?;
storage_slots
} else {
compiled.storage_slots.clone()
};
storage_slots.sort();
kayagokalp marked this conversation as resolved.
Show resolved Hide resolved
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) make call into.
kayagokalp marked this conversation as resolved.
Show resolved Hide resolved
async fn deploy_chunked(
kayagokalp marked this conversation as resolved.
Show resolved Hide resolved
command: &cmd::Deploy,
compiled: &BuiltPackage,
salt: Salt,
signing_key: &SecretKey,
provider: &Provider,
pkg_name: &str,
) -> anyhow::Result<ContractId> {
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());
kayagokalp marked this conversation as resolved.
Show resolved Hide resolved
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)
}

kayagokalp marked this conversation as resolved.
Show resolved Hide resolved
/// 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<fuel_tx::ContractId> {
) -> Result<ContractId> {
fuels::macros::abigen!(Contract(
name = "ProxyContract",
abi = "forc-plugins/forc-client/proxy_abi/proxy_contract-abi.json"
Expand All @@ -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());
Expand All @@ -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
Expand Down Expand Up @@ -277,7 +354,24 @@ pub async fn deploy(command: cmd::Deploy) -> Result<Vec<DeployedContract>> {
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 {
Expand Down Expand Up @@ -306,9 +400,14 @@ pub async fn deploy(command: cmd::Deploy) -> Result<Vec<DeployedContract>> {
}) => {
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(
Expand All @@ -324,6 +423,7 @@ pub async fn deploy(command: cmd::Deploy) -> Result<Vec<DeployedContract>> {
let deployed_contract = DeployedContract {
id: deployed_contract_id,
proxy: proxy_id,
chunked: bytecode_size > MAX_CONTRACT_SIZE,
};
deployed_contracts.push(deployed_contract);
}
Expand Down Expand Up @@ -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()
)
})
Expand Down Expand Up @@ -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<StorageSlot> = 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(),
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions forc-plugins/forc-client/src/util/pkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -42,8 +42,8 @@ pub(crate) fn update_proxy_address_in_manifest(
pub(crate) fn create_proxy_contract(pkg_name: &str) -> Result<PathBuf> {
// 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),
Expand Down
2 changes: 2 additions & 0 deletions forc-plugins/forc-client/test/data/big_contract/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
8 changes: 8 additions & 0 deletions forc-plugins/forc-client/test/data/big_contract/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "big_contract"

[dependencies]
std = { path = "../../../../../sway-lib-std/" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"programType": "contract",
"specVersion": "1",
"encodingVersion": "1",
"concreteTypes": [
{
"type": "bool",
"concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903"
}
],
"metadataTypes": [],
"functions": [
{
"inputs": [],
"name": "test_function",
"output": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903",
"attributes": null
}
],
"loggedTypes": [],
"messagesTypes": [],
"configurables": []
}
14 changes: 14 additions & 0 deletions forc-plugins/forc-client/test/data/big_contract/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
contract;

abi MyContract {
fn test_function() -> bool;
}

impl MyContract for Contract {
fn test_function() -> bool {
Voxelot marked this conversation as resolved.
Show resolved Hide resolved
asm() {
blob i91000;
}
true
}
}
Loading
Loading