Skip to content

Commit

Permalink
feat: transaction simulation
Browse files Browse the repository at this point in the history
Adds a `--simulate` flag to commands that send out transactions. There's
no support for skipping validation or fees yet.
  • Loading branch information
xJonathanLEI committed Oct 26, 2023
1 parent e853927 commit 2384b57
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 58 deletions.
22 changes: 11 additions & 11 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ serde = { version = "1.0.164", features = ["derive"] }
serde_json = { version = "1.0.99", features = ["preserve_order"] }
serde_with = "2.3.3"
shellexpand = "3.1.0"
starknet = { git = "https://github.com/xJonathanLEI/starknet-rs", rev = "3505fae8f97492416253cbda4f170e6b9a9cdb58" }
starknet = { git = "https://github.com/xJonathanLEI/starknet-rs", rev = "33e200d3d53a685586ee178b4bd0be4b48250c08" }
tempfile = "3.8.0"
thiserror = "1.0.40"
tokio = { version = "1.28.2", default-features = false, features = ["macros", "rt-multi-thread"] }
Expand Down
40 changes: 40 additions & 0 deletions src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,4 +370,44 @@ impl Provider for ExtendedProvider {
)
.await
}

async fn trace_transaction<H>(
&self,
transaction_hash: H,
) -> Result<TransactionTrace, ProviderError<Self::Error>>
where
H: AsRef<FieldElement> + Send + Sync,
{
<AnyProvider as Provider>::trace_transaction(&self.provider, transaction_hash).await
}

async fn simulate_transactions<B, T, S>(
&self,
block_id: B,
transactions: T,
simulation_flags: S,
) -> Result<Vec<SimulatedTransaction>, ProviderError<Self::Error>>
where
B: AsRef<BlockId> + Send + Sync,
T: AsRef<[BroadcastedTransaction]> + Send + Sync,
S: AsRef<[SimulationFlag]> + Send + Sync,
{
<AnyProvider as Provider>::simulate_transactions(
&self.provider,
block_id,
transactions,
simulation_flags,
)
.await
}

async fn trace_block_transactions<H>(
&self,
block_hash: H,
) -> Result<Vec<TransactionTraceWithHash>, ProviderError<Self::Error>>
where
H: AsRef<FieldElement> + Send + Sync,
{
<AnyProvider as Provider>::trace_block_transactions(&self.provider, block_hash).await
}
}
79 changes: 47 additions & 32 deletions src/subcommands/account/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{io::Write, path::PathBuf, sync::Arc, time::Duration};
use anyhow::Result;
use clap::Parser;
use colored::Colorize;
use colored_json::{ColorMode, Output};
use starknet::{
accounts::{AccountFactory, ArgentAccountFactory, OpenZeppelinAccountFactory},
core::types::{BlockId, BlockTag, FieldElement},
Expand Down Expand Up @@ -32,6 +33,8 @@ pub struct Deploy {
signer: SignerArgs,
#[clap(flatten)]
fee: FeeArgs,
#[clap(long, help = "Simulate the transaction only")]
simulate: bool,
#[clap(long, help = "Provide transaction nonce manually")]
nonce: Option<FieldElement>,
#[clap(
Expand Down Expand Up @@ -65,6 +68,9 @@ impl Deploy {
self.verbosity.setup_logging();

let fee_setting = self.fee.into_setting()?;
if self.simulate && fee_setting.is_estimate_only() {
anyhow::bail!("--simulate cannot be used with --estimate-only");
}

let provider = Arc::new(self.provider.into_provider());
let signer = Arc::new(self.signer.into_signer()?);
Expand Down Expand Up @@ -216,48 +222,57 @@ impl Deploy {
}
};

match max_fee {
MaxFeeType::Manual { max_fee } => {
eprintln!(
"You've manually specified the account deployment fee to be {}. \
Therefore, fund at least:\n {}",
format!("{} ETH", max_fee.to_big_decimal(18)).bright_yellow(),
format!("{} ETH", max_fee.to_big_decimal(18)).bright_yellow(),
);
}
MaxFeeType::Estimated {
estimate,
estimate_with_buffer,
} => {
eprintln!(
"The estimated account deployment fee is {}. \
However, to avoid failure, fund at least:\n {}",
format!("{} ETH", estimate.to_big_decimal(18)).bright_yellow(),
format!("{} ETH", estimate_with_buffer.to_big_decimal(18)).bright_yellow()
);
if !self.simulate {
match max_fee {
MaxFeeType::Manual { max_fee } => {
eprintln!(
"You've manually specified the account deployment fee to be {}. \
Therefore, fund at least:\n {}",
format!("{} ETH", max_fee.to_big_decimal(18)).bright_yellow(),
format!("{} ETH", max_fee.to_big_decimal(18)).bright_yellow(),
);
}
MaxFeeType::Estimated {
estimate,
estimate_with_buffer,
} => {
eprintln!(
"The estimated account deployment fee is {}. \
However, to avoid failure, fund at least:\n {}",
format!("{} ETH", estimate.to_big_decimal(18)).bright_yellow(),
format!("{} ETH", estimate_with_buffer.to_big_decimal(18)).bright_yellow()
);
}
}
}

eprintln!(
"to the following address:\n {}",
format!("{:#064x}", target_deployment_address).bright_yellow()
);
eprintln!(
"to the following address:\n {}",
format!("{:#064x}", target_deployment_address).bright_yellow()
);

// TODO: add flag for skipping this manual confirmation step
eprint!("Press [ENTER] once you've funded the address.");
std::io::stdin().read_line(&mut String::new())?;
// TODO: add flag for skipping this manual confirmation step
eprint!("Press [ENTER] once you've funded the address.");
std::io::stdin().read_line(&mut String::new())?;
}

let account_deployment = match self.nonce {
Some(nonce) => account_deployment.nonce(nonce),
None => account_deployment,
};
let account_deployment = account_deployment.max_fee(max_fee.max_fee());

if self.simulate {
let simulation = account_deployment.simulate(false, false).await?;
let simulation_json = serde_json::to_value(simulation)?;

let simulation_json =
colored_json::to_colored_json(&simulation_json, ColorMode::Auto(Output::StdOut))?;
println!("{simulation_json}");
return Ok(());
}

// TODO: add option to check ETH balance before sending out tx
let account_deployment_tx = account_deployment
.max_fee(max_fee.max_fee())
.send()
.await?
.transaction_hash;
let account_deployment_tx = account_deployment.send().await?.transaction_hash;
eprintln!(
"Account deployment transaction: {}",
format!("{:#064x}", account_deployment_tx).bright_yellow()
Expand Down
42 changes: 34 additions & 8 deletions src/subcommands/declare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{path::PathBuf, sync::Arc, time::Duration};
use anyhow::Result;
use clap::Parser;
use colored::Colorize;
use colored_json::{ColorMode, Output};
use starknet::{
accounts::Account,
core::types::{
Expand Down Expand Up @@ -32,6 +33,8 @@ pub struct Declare {
casm: CasmArgs,
#[clap(flatten)]
fee: FeeArgs,
#[clap(long, help = "Simulate the transaction only")]
simulate: bool,
#[clap(long, help = "Provide transaction nonce manually")]
nonce: Option<FieldElement>,
#[clap(long, help = "Wait for the transaction to confirm")]
Expand All @@ -57,6 +60,9 @@ impl Declare {
self.verbosity.setup_logging();

let fee_setting = self.fee.into_setting()?;
if self.simulate && fee_setting.is_estimate_only() {
anyhow::bail!("--simulate cannot be used with --estimate-only");
}

let provider = Arc::new(self.provider.into_provider());

Expand Down Expand Up @@ -159,11 +165,21 @@ impl Declare {
Some(nonce) => declaration.nonce(nonce),
None => declaration,
};
let declaration = declaration.max_fee(max_fee);

(
class_hash,
declaration.max_fee(max_fee).send().await?.transaction_hash,
)
if self.simulate {
let simulation = declaration.simulate(false, false).await?;
let simulation_json = serde_json::to_value(simulation)?;

let simulation_json = colored_json::to_colored_json(
&simulation_json,
ColorMode::Auto(Output::StdOut),
)?;
println!("{simulation_json}");
return Ok(());
}

(class_hash, declaration.send().await?.transaction_hash)
} else if let Ok(_) =
serde_json::from_reader::<_, CompiledClass>(std::fs::File::open(&self.file)?)
{
Expand Down Expand Up @@ -219,11 +235,21 @@ impl Declare {
Some(nonce) => declaration.nonce(nonce),
None => declaration,
};
let declaration = declaration.max_fee(max_fee);

(
class_hash,
declaration.max_fee(max_fee).send().await?.transaction_hash,
)
if self.simulate {
let simulation = declaration.simulate(false, false).await?;
let simulation_json = serde_json::to_value(simulation)?;

let simulation_json = colored_json::to_colored_json(
&simulation_json,
ColorMode::Auto(Output::StdOut),
)?;
println!("{simulation_json}");
return Ok(());
}

(class_hash, declaration.send().await?.transaction_hash)
} else {
anyhow::bail!("failed to parse contract artifact");
};
Expand Down
Loading

0 comments on commit 2384b57

Please sign in to comment.