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

plugin: forc-call #6791

Open
wants to merge 48 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
be0a1bb
initialized forc-call CLI in forc-plugins - forc-client
zees-dev Dec 17, 2024
a655a0c
initial implementation using local or remote abi
zees-dev Dec 17, 2024
cfd9490
sway contract testing all primitives for forc-call
zees-dev Dec 17, 2024
9555932
cargo.lock upd
zees-dev Dec 17, 2024
1d939f3
added proper support for wallet, default and explicit signer, added s…
zees-dev Dec 18, 2024
91a9b57
param_type_val_to_token_conversion unit tests
zees-dev Dec 29, 2024
d9e47ed
token_to_string_conversion unit tests
zees-dev Dec 29, 2024
17325f9
updated code and tests to handle more complex examples
zees-dev Dec 30, 2024
93516b1
FuncType FromStr function impl
zees-dev Jan 3, 2025
4093b48
added execution mode strategies, supporting dry-run, simulate and liv…
zees-dev Jan 3, 2025
ef08ec2
added forc-tx cargo.lock due to prev change
zees-dev Jan 3, 2025
4dc6ec9
cargo fmt --all
zees-dev Jan 3, 2025
fe56e95
cargo fmt --all
zees-dev Jan 3, 2025
b5308d4
typo fixes
zees-dev Jan 3, 2025
c405516
extract contract auto-extraction from input params
zees-dev Jan 3, 2025
e53eefe
integrated call parameters
zees-dev Jan 13, 2025
cac170c
added get_explorer_url function to getcanonical explorer urls
zees-dev Jan 13, 2025
5b00190
printing out tx details and link for viewing
zees-dev Jan 13, 2025
7458f36
external contract simulation and address retrieval for ergonomic CLI …
zees-dev Jan 13, 2025
cc45a2e
address cargo clippy warnings
zees-dev Jan 13, 2025
9551a57
cargo fmt --all
zees-dev Jan 13, 2025
f2854c0
added clone to core cmd structs
zees-dev Jan 14, 2025
7447143
fixed clippy warning, missing contract retrieval refactor; tx url onl…
zees-dev Jan 14, 2025
e1c4ab4
tests refactor; explicit fn args input
zees-dev Jan 14, 2025
bac84b5
added another test to forward value
zees-dev Jan 14, 2025
b285433
updated test contract
zees-dev Jan 14, 2025
b01ea16
rename test function
zees-dev Jan 14, 2025
98a95a3
cargo fmt --all
zees-dev Jan 14, 2025
bc30b7e
updated external contracts description
zees-dev Jan 14, 2025
4f2fa8f
fix build (#6831)
sdankel Jan 15, 2025
219d920
contract upd
zees-dev Jan 15, 2025
4288f66
node target get explorer url function migration
zees-dev Jan 15, 2025
17c1d30
updated tests for contract value forwarding
zees-dev Jan 15, 2025
f903876
cargo clippy fixes
zees-dev Jan 15, 2025
ffd47c9
consolidated select_account and select_local_wallet_account functions
zees-dev Jan 15, 2025
ece1107
removed redundant comment
zees-dev Jan 15, 2025
b21334f
split up forc call lib.rs file to mod.rs and parser.rs
zees-dev Jan 16, 2025
38144cf
split up forc call mod.rs into missing_contracts.rs
zees-dev Jan 16, 2025
90495f5
refactored imports
zees-dev Jan 16, 2025
460b17f
hint on function should be selector
zees-dev Jan 16, 2025
569e457
helper function to format check and build sway file for tests
zees-dev Jan 16, 2025
1c78736
removed temp writing to file
zees-dev Jan 30, 2025
a27a005
raw output format support; small test refactor
zees-dev Jan 31, 2025
19b3e99
forc-client devnet node url and faucet url support
zees-dev Jan 31, 2025
cc9e032
Merge branch 'master' into feat/forc-call
zees-dev Feb 1, 2025
486c8d7
cargo.lock file upd
zees-dev Feb 1, 2025
027e443
removed redundant gas cost import
zees-dev Feb 1, 2025
9b0bbc8
updated base_asset_id retrieval code
zees-dev Feb 1, 2025
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
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion forc-plugins/forc-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ chrono = { workspace = true, features = ["std"] }
clap = { workspace = true, features = ["derive", "env"] }
devault.workspace = true
dialoguer.workspace = true
either.workspace = true
forc.workspace = true
forc-pkg.workspace = true
forc-tracing.workspace = true
Expand All @@ -36,26 +37,32 @@ futures.workspace = true
hex.workspace = true
k256.workspace = true
rand.workspace = true
regex.workspace = true
reqwest = { workspace = true }
rpassword.workspace = true
serde.workspace = true
serde_json.workspace = true
sway-ast.workspace = true
sway-core.workspace = true
sway-features.workspace = true
sway-parse.workspace = true
sway-types.workspace = true
sway-utils.workspace = true
swayfmt.workspace = true
tempfile.workspace = true
tokio = { workspace = true, features = [
"macros",
"process",
"rt-multi-thread",
] }
toml_edit.workspace = true
tracing.workspace = true
url.workspace = true

[dev-dependencies]
portpicker = "0.1.1"
pretty_assertions = "1.4.1"
rexpect = "0.5"
tempfile = "3"

[build-dependencies]
regex = "1.5.4"
zees-dev marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -72,5 +79,9 @@ path = "src/bin/run.rs"
name = "forc-submit"
path = "src/bin/submit.rs"

[[bin]]
name = "forc-call"
path = "src/bin/call.rs"

[lib]
path = "src/lib.rs"
12 changes: 12 additions & 0 deletions forc-plugins/forc-client/src/bin/call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use clap::Parser;
use forc_tracing::{init_tracing_subscriber, println_error};

#[tokio::main]
async fn main() {
init_tracing_subscriber(Default::default());
let command = forc_client::cmd::Call::parse();
if let Err(err) = forc_client::op::call(command).await {
println_error(&format!("{}", err));
std::process::exit(1);
}
}
198 changes: 198 additions & 0 deletions forc-plugins/forc-client/src/cmd/call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
use crate::NodeTarget;
use clap::Parser;
use either::Either;
use fuel_crypto::SecretKey;
use fuels::programs::calls::CallParameters;
use fuels_core::types::{AssetId, ContractId};
use std::{path::PathBuf, str::FromStr};
use url::Url;

pub use forc::cli::shared::{BuildOutput, BuildProfile, Minify, Pkg, Print};
pub use forc_tx::{Gas, Maturity};

forc_util::cli_examples! {
zees-dev marked this conversation as resolved.
Show resolved Hide resolved
super::Command {
[ Call a contract with function parameters => "forc call <CONTRACT_ID> <FUNCTION_SIGNATURE> <ARGS>" ]
Copy link
Member

@JoshuaBatty JoshuaBatty Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if being a bit more explicit on what these calls look like might be easier for people to reference? maybe the above could be

[ Call a simple contract function => "forc call 0x123..abc add 1 2" ]

thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed; makes sense 👍

[ 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" ]
[ Call a contract payable function which transfers value of native asset => "forc call <CONTRACT_ID> <FUNCTION_SIGNATURE> --live --amount <VALUE>" ]
[ Call a contract payable function which transfers value of custom asset => "forc call <CONTRACT_ID> <FUNCTION_SIGNATURE> --live --amount <VALUE> --asset-id <ASSET_ID>" ]
}
}

#[derive(Debug, Clone)]
pub enum FuncType {
Signature(String),
Selector(String),
}

impl Default for FuncType {
fn default() -> Self {
FuncType::Signature(String::new())
}
}

impl FromStr for FuncType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim().replace(' ', "");
if s.is_empty() {
return Err("Function signature cannot be empty".to_string());
}
// Check if function signature is a valid selector (alphanumeric and underscore support)
let selector_pattern = regex::Regex::new(r"^[a-zA-Z][a-zA-Z0-9_]*$").unwrap();
if !selector_pattern.is_match(&s) {
return Ok(FuncType::Signature(s.to_string()));
}
Ok(FuncType::Selector(s.to_string()))
}
}

/// Flags for specifying the caller.
#[derive(Debug, Default, Clone, Parser, serde::Deserialize, serde::Serialize)]
pub struct Caller {
/// Derive an account from a secret key to make the call
#[clap(long, env = "SIGNING_KEY")]
pub signing_key: Option<SecretKey>,

/// Use forc-wallet to make the call
#[clap(long, default_value = "false")]
pub wallet: bool,
}

#[derive(Debug, Default, Clone, Parser)]
pub struct CallParametersOpts {
/// Amount of native assets to forward with the call
#[clap(long, default_value = "0", alias = "value")]
pub amount: u64,

/// Asset ID to forward with the call
#[clap(long)]
pub asset_id: Option<AssetId>,

/// Amount of gas to forward with the call
#[clap(long)]
pub gas_forwarded: Option<u64>,
}

impl From<CallParametersOpts> for CallParameters {
fn from(opts: CallParametersOpts) -> Self {
let mut params = CallParameters::default();
if opts.amount != 0 {
params = params.with_amount(opts.amount);
}
if let Some(asset_id) = opts.asset_id {
params = params.with_asset_id(asset_id);
}
if let Some(gas) = opts.gas_forwarded {
params = params.with_gas_forwarded(gas);
}
params
}
}

#[derive(Debug, Clone, PartialEq, Default)]
pub enum ExecutionMode {
/// Execute a dry run - no state changes, no gas fees, wallet is not used or validated
#[default]
DryRun,
/// Execute in simulation mode - no state changes, estimates gas, wallet is used but not validated
/// State changes are not applied
Simulate,
/// Execute live on chain - state changes, gas fees apply, wallet is used and validated
/// State changes are applied
Live,
}

impl FromStr for ExecutionMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"dry-run" => Ok(ExecutionMode::DryRun),
"simulate" => Ok(ExecutionMode::Simulate),
"live" => Ok(ExecutionMode::Live),
_ => Err(format!("Invalid execution mode: {}", s)),
}
}
}

#[derive(Debug, Clone, PartialEq, Default)]
pub enum OutputFormat {
#[default]
Default,
Raw,
}

impl FromStr for OutputFormat {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"default" => Ok(OutputFormat::Default),
"raw" => Ok(OutputFormat::Raw),
_ => Err(format!("Invalid output format: {}", s)),
}
}
}

/// Call a contract function.
#[derive(Debug, Default, Parser, Clone)]
#[clap(bin_name = "forc call", version)]
pub struct Command {
/// The contract ID to call.
pub contract_id: ContractId,

/// Optional path or URI to a JSON ABI file.
#[clap(long, value_parser = parse_abi_path)]
pub abi: Option<Either<PathBuf, Url>>,
Comment on lines +147 to +149
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Optional path or URI to a JSON ABI file.
#[clap(long, value_parser = parse_abi_path)]
pub abi: Option<Either<PathBuf, Url>>,
/// Path or URI to a JSON ABI file.
#[clap(long, value_parser = parse_abi_path)]
pub abi: Either<PathBuf, Url>,


/// The function signature to call.
/// When ABI is provided, this should be a selector (e.g. "transfer")
/// When no ABI is provided, this should be the full function signature (e.g. "transfer(address,u64)")
Comment on lines +151 to +153
Copy link
Member

@sdankel sdankel Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since ABI is required:

Suggested change
/// The function signature to call.
/// When ABI is provided, this should be a selector (e.g. "transfer")
/// When no ABI is provided, this should be the full function signature (e.g. "transfer(address,u64)")
/// The function signature to call. This should be a selector (e.g. "transfer")

pub function: FuncType,

/// Arguments to pass into main function with forc run.
pub args: Vec<String>,

#[clap(flatten)]
pub node: NodeTarget,

/// Select the caller to use for the call
#[clap(flatten)]
pub caller: Caller,

/// Call parameters to use for the call
#[clap(flatten)]
pub call_parameters: CallParametersOpts,

/// The execution mode to use for the call; defaults to dry-run; possible values: dry-run, simulate, live
#[clap(long, default_value = "dry-run")]
pub mode: ExecutionMode,

/// 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 populate external contracts by making a dry-run calls
/// to the node, and extract the contract addresses based on the revert reason
#[clap(long, alias = "contracts")]
pub external_contracts: Option<Vec<ContractId>>,

/// The output format to use; possible values: default, raw
#[clap(long, default_value = "default")]
pub output: OutputFormat,
}

fn parse_abi_path(s: &str) -> Result<Either<PathBuf, Url>, String> {
if let Ok(url) = Url::parse(s) {
match url.scheme() {
"http" | "https" | "ipfs" => Ok(Either::Right(url)),
_ => Err(format!("Unsupported URL scheme: {}", url.scheme())),
}
} else {
Ok(Either::Left(PathBuf::from(s)))
}
}
2 changes: 2 additions & 0 deletions forc-plugins/forc-client/src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pub mod call;
pub mod deploy;
pub mod run;
pub mod submit;

pub use call::Command as Call;
pub use deploy::Command as Deploy;
pub use run::Command as Run;
pub use submit::Command as Submit;
7 changes: 5 additions & 2 deletions forc-plugins/forc-client/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
/// Default to localhost to favour the common case of testing.
pub const NODE_URL: &str = sway_utils::constants::DEFAULT_NODE_URL;
pub const TESTNET_ENDPOINT_URL: &str = "https://testnet.fuel.network";

pub const MAINNET_ENDPOINT_URL: &str = "https://mainnet.fuel.network";
pub const TESTNET_ENDPOINT_URL: &str = "https://testnet.fuel.network";
pub const DEVNET_ENDPOINT_URL: &str = "https://devnet.fuel.network";

pub const TESTNET_FAUCET_URL: &str = "https://faucet-testnet.fuel.network";
pub const DEVNET_FAUCET_URL: &str = "https://faucet-devnet.fuel.network";

pub const TESTNET_EXPLORER_URL: &str = "https://app-testnet.fuel.network";
pub const MAINNET_EXPLORER_URL: &str = "https://app.fuel.network";
pub const TESTNET_EXPLORER_URL: &str = "https://app-testnet.fuel.network";

/// Default PrivateKey to sign transactions submitted to local node.
pub const DEFAULT_PRIVATE_KEY: &str =
Expand Down
Loading
Loading