Skip to content

Commit

Permalink
extract contract auto-extraction from input params
Browse files Browse the repository at this point in the history
  • Loading branch information
zees-dev committed Jan 6, 2025
1 parent c6f7d67 commit 945e38e
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 5 deletions.
7 changes: 7 additions & 0 deletions forc-plugins/forc-client/src/cmd/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ forc_util::cli_examples! {
[ Call a contract with function parameters => "forc call <CONTRACT_ID> <FUNCTION_SIGNATURE> <ARGS>" ]
[ Call a contract without function parameters => "forc call <CONTRACT_ID> <FUNCTION_SIGNATURE>" ]
[ Call a contract given an ABI file with function parameters => "forc call <CONTRACT_ID> --abi <ABI_FILE> <FUNCTION_SELECTOR> <ARGS>" ]
[ Call a contract that makes external contract calls => "forc call <CONTRACT_ID> --abi <ABI_FILE> <FUNCTION_SELECTOR> <ARGS> --contracts <CONTRACT_ADDRESS_1> <CONTRACT_ADDRESS_2>..." ]
[ Call a contract in simulation mode => "forc call <CONTRACT_ID> <FUNCTION_SIGNATURE> --simulate" ]
[ Call a contract in live mode which performs state changes => "forc call <CONTRACT_ID> <FUNCTION_SIGNATURE> --live" ]
}
Expand Down Expand Up @@ -120,6 +121,12 @@ pub struct Command {
/// The gas price to use for the call; defaults to 0
#[clap(flatten)]
pub gas: Option<Gas>,

/// The external contract addresses to use for the call
/// If none are provided, the call will automatically extract contract addresses from the function arguments
/// and use them for the call as external contracts
#[clap(long, alias = "contracts")]
pub external_contracts: Option<Vec<ContractId>>,
}

fn parse_abi_path(s: &str) -> Result<Either<PathBuf, Url>, String> {
Expand Down
56 changes: 51 additions & 5 deletions forc-plugins/forc-client/src/op/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ use fuels_accounts::{provider::Provider, wallet::WalletUnlocked};
use fuels_core::{
codec::{encode_fn_selector, ABIDecoder, ABIEncoder, DecoderConfig, EncoderConfig},
types::{
bech32::Bech32ContractId,
param_types::ParamType,
transaction::TxPolicies,
transaction_builders::{BuildableTransaction, ScriptBuildStrategy, VariableOutputPolicy},
EnumSelector, StaticStringToken, Token, U256,
ContractId, EnumSelector, StaticStringToken, Token, U256,
},
};
use std::{collections::HashMap, fs::File, io::Write, str::FromStr};
Expand All @@ -38,6 +39,7 @@ pub async fn call(cmd: cmd::Call) -> anyhow::Result<String> {
caller,
mode,
gas,
external_contracts,
} = cmd;
let node_url = get_node_url(&node, &None)?;
let provider: Provider = Provider::connect(node_url.clone()).await?;
Expand Down Expand Up @@ -108,13 +110,33 @@ pub async fn call(cmd: cmd::Call) -> anyhow::Result<String> {
let abi_encoder = ABIEncoder::new(EncoderConfig::default());
let encoded_data = abi_encoder.encode(&tokens)?;

let external_contracts = match external_contracts {
Some(external_contracts) => external_contracts,
None => {
// Automatically extract contract addresses from args; provide these as inputs to the call
// This makes the CLI more ergonomic
let extracted_external_contracts = extract_contract_addresses(&args);
if !extracted_external_contracts.is_empty() {
forc_tracing::println_warning(
"Automatically provided external contract addresses with call:",
);
extracted_external_contracts.iter().for_each(|addr| {
forc_tracing::println_warning(&format!("- 0x{}", addr.to_string()));
});
}
extracted_external_contracts
}
};

// Create and execute call
let call = ContractCall {
contract_id: contract_id.into(),
encoded_selector: encode_fn_selector(&selector),
encoded_args: Ok(encoded_data),
call_parameters: Default::default(),
external_contracts: vec![],
external_contracts: external_contracts
.iter()
.map(|addr| Bech32ContractId::from(*addr))
.collect(),
output_param: output_param.clone(),
is_payable: false,
custom_assets: Default::default(),
Expand Down Expand Up @@ -698,6 +720,29 @@ fn parse_delimited_string(param_type: &ParamType, input: &str) -> Result<Vec<Str
Ok(parts)
}

/// Best-effort to extract contract addresses from args
/// These must start with 0x and be 42 characters long
/// Returns a vector of unique contract addresses
fn extract_contract_addresses(args: &[String]) -> Vec<ContractId> {
let contract_regex =
regex::Regex::new(r"(?:^|[^a-fA-F0-9])(0x[a-fA-F0-9]{64})(?:$|[^a-fA-F0-9])").unwrap();
let mut addresses: Vec<ContractId> = args
.iter()
.filter_map(|arg| {
contract_regex.captures_iter(arg).next().and_then(|cap| {
let addr = cap[1].to_string();
ContractId::from_str(&addr).ok()
})
})
.collect();

// Sort and remove duplicates
addresses.sort_unstable();
addresses.dedup();

addresses
}

// pub (crate) fn ty_to_token(ty: &Ty) -> Result<Token> {
// match ty {
// Ty::Path(path) => {
Expand All @@ -716,7 +761,7 @@ mod tests {
use fuel_crypto::SecretKey;
use fuels::prelude::*;
use fuels_accounts::wallet::{Wallet, WalletUnlocked};
use fuels_core::types::{param_types::EnumVariants, ContractId};
use fuels_core::types::param_types::EnumVariants;

abigen!(Contract(
name = "TestContract",
Expand Down Expand Up @@ -774,7 +819,6 @@ mod tests {
))),
function: FuncType::Selector(selector.into()),
args: vec_args,
gas: None,
node: crate::NodeTarget {
node_url: Some(wallet.provider().unwrap().url().to_owned()),
..Default::default()
Expand All @@ -784,6 +828,8 @@ mod tests {
wallet: false,
},
mode: cmd::call::ExecutionMode::DryRun,
gas: None,
external_contracts: None,
}
}

Expand Down

0 comments on commit 945e38e

Please sign in to comment.