Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
263 changes: 119 additions & 144 deletions Cargo.lock

Large diffs are not rendered by default.

50 changes: 48 additions & 2 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
use crate::commands::l2;
use crate::common::AuthorizeArgs;
use crate::utils::{parse_contract_creation, parse_func_call, parse_hex, parse_hex_string};
use crate::{
commands::autocomplete,
common::{CallArgs, DeployArgs, SendArgs, TransferArgs},
utils::parse_private_key,
};
use clap::{ArgAction, Parser, Subcommand};
use ethrex_common::types::TxType;
use ethrex_common::types::{AuthorizationTupleEntry, TxType};
use ethrex_common::{Address, Bytes, H256, H520};
use ethrex_l2_common::calldata::Value;
use ethrex_l2_common::utils::get_address_from_secret_key;
use ethrex_l2_rpc::signer::{LocalSigner, Signer};
use ethrex_rlp::encode::RLPEncode;
use ethrex_rpc::EthClient;
use ethrex_rpc::clients::Overrides;
use ethrex_rpc::types::block_identifier::{BlockIdentifier, BlockTag};
use ethrex_sdk::calldata::decode_calldata;
use ethrex_sdk::{build_generic_tx, create2_deploy_from_bytecode, send_generic_transaction};
use ethrex_sdk::{compile_contract, git_clone};
use keccak_hash::keccak;
use rex_sdk::authorize::build_authorization_tuple;
use rex_sdk::client::eth::get_token_balance;
use rex_sdk::create::{
DETERMINISTIC_DEPLOYER, brute_force_create2, compute_create_address, compute_create2_address,
};
use rex_sdk::l2::authorize::parse_authorization_list;
use rex_sdk::sign::{get_address_from_message_and_signature, sign_hash};
use rex_sdk::utils::to_checksum_address;
use rex_sdk::{balance_in_eth, deploy, transfer, wait_for_transaction_receipt};
Expand Down Expand Up @@ -276,6 +280,13 @@ pub(crate) enum Command {
#[arg(value_parser = parse_hex)]
data: Bytes,
},
#[clap(about = "Authorize a delegated account")]
Authorize {
#[clap(flatten)]
args: AuthorizeArgs,
#[arg(long, default_value = "http://localhost:8545", env = "RPC_URL")]
rpc_url: Url,
},
}

impl Command {
Expand Down Expand Up @@ -456,6 +467,7 @@ impl Command {
args.amount,
from,
args.to,
TxType::EIP1559,
&args.private_key,
&client,
Overrides::default(),
Expand Down Expand Up @@ -484,9 +496,27 @@ impl Command {
parse_func_call(args._args)?
};

let tx_type = if args.auth_tuple.is_empty() {
TxType::EIP1559
} else {
TxType::EIP7702
};

let auth_list = parse_authorization_list(&args.auth_tuple)?;
let auth_list = if auth_list.is_empty() {
None
} else {
Some(
auth_list
.iter()
.map(AuthorizationTupleEntry::from)
.collect(),
)
};

let tx = build_generic_tx(
&client,
TxType::EIP1559,
tx_type,
args.to,
from,
calldata,
Expand All @@ -498,6 +528,7 @@ impl Command {
max_fee_per_gas: args.max_fee_per_gas,
max_priority_fee_per_gas: args.max_priority_fee_per_gas,
from: Some(from),
authorization_list: auth_list,
..Default::default()
},
)
Expand Down Expand Up @@ -662,6 +693,21 @@ impl Command {
print_calldata(0, elem);
}
}
Command::Authorize { args, rpc_url } => {
let client = EthClient::new(rpc_url)?;

let auth_tuple = build_authorization_tuple(
&client,
args.delegated_address,
&args.private_key,
args.chain_id,
args.nonce,
)
.await?;
let mut buf = Vec::new();
auth_tuple.encode(&mut buf);
println!("0x{:x}", Bytes::from(buf));
}
};
Ok(())
}
Expand Down
239 changes: 235 additions & 4 deletions cli/src/commands/l2.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
use crate::{
cli::Command as EthCommand,
common::{BalanceArgs, CallArgs, DeployArgs, SendArgs, TransferArgs},
utils::{parse_private_key, parse_u256},
common::{AuthorizeArgs, BalanceArgs, CallArgs, DeployArgs, SendArgs, TransferArgs},
utils::{parse_hex, parse_private_key, parse_u256},
};
use clap::Subcommand;
use ethrex_common::{Address, H256, U256};
use ethrex_common::{Bytes, types::TxType};
use ethrex_l2_common::utils::get_address_from_secret_key;
use ethrex_l2_rpc::clients::{get_batch_by_number, get_batch_number};
use ethrex_rpc::EthClient;
use ethrex_rpc::clients::Overrides;
use ethrex_sdk::wait_for_l1_message_proof;
use rex_sdk::transfer;
use rex_sdk::{
l2::authorize::send_authorized_transaction,
l2::fees::fetch_fee_info,
l2::{
deposit::{deposit_erc20, deposit_through_contract_call},
withdraw::{claim_erc20withdraw, claim_withdraw, withdraw, withdraw_erc20},
Expand Down Expand Up @@ -210,6 +216,12 @@ pub(crate) enum Command {
Transfer {
#[clap(flatten)]
args: TransferArgs,
#[arg(
long,
required = false,
help = "The L2 address of a Fee Token to pay the gas fees"
)]
fee_token: Option<Address>,
#[arg(
default_value = "http://localhost:1729",
env = "RPC_URL",
Expand Down Expand Up @@ -276,6 +288,86 @@ pub(crate) enum Command {
)]
rpc_url: Url,
},
#[clap(about = "Get L2 fees info for a block")]
GetFeeInfo {
#[arg(
long,
required = false,
help = "Block number to query the fees info for"
)]
block: Option<u64>,
#[arg(
long,
default_value = "http://localhost:1729",
env = "RPC_URL",
help = "L2 RPC URL"
)]
rpc_url: Url,
},
#[clap(about = "Get the latest batch number")]
BatchNumber {
#[arg(
long,
default_value = "http://localhost:1729",
env = "RPC_URL",
help = "L2 RPC URL"
)]
rpc_url: Url,
},
#[clap(about = "Get the latest batch or a batch by its number")]
BatchByNumber {
#[arg(
long,
short = 'b',
required = false,
help = "Batch number to retrieve information"
)]
batch_number: Option<u64>,
#[arg(
long,
default_value = "http://localhost:1729",
env = "RPC_URL",
help = "L2 RPC URL"
)]
rpc_url: Url,
},
#[clap(about = "Send an ethrex sponsored transaction")]
SponsorTx {
#[arg(help = "Destination address of the transaction")]
to: Address,
#[arg(long, value_parser = parse_hex, help = "Calldata of the transaction")]
calldata: Option<Bytes>,
#[arg(long, help = "Hex-encoded RLP authorization tuples (EIP-7702)")]
auth_list: Vec<String>,
#[clap(
long,
short = 'c',
required = false,
help = "Send the request asynchronously."
)]
cast: bool,
#[clap(
long,
short = 's',
required = false,
help = "Display only the tx hash."
)]
silent: bool,
#[arg(
long,
default_value = "http://localhost:1729",
env = "RPC_URL",
help = "L2 RPC URL"
)]
rpc_url: Url,
},
#[clap(about = "Authorize a delegated account")]
Authorize {
#[clap(flatten)]
args: AuthorizeArgs,
#[arg(long, default_value = "http://localhost:1729", env = "RPC_URL")]
rpc_url: Url,
},
}

impl Command {
Expand Down Expand Up @@ -475,8 +567,48 @@ impl Command {
Command::Nonce { account, rpc_url } => {
Box::pin(async { EthCommand::Nonce { account, rpc_url }.run().await }).await?
}
Command::Transfer { args, rpc_url } => {
Box::pin(async { EthCommand::Transfer { args, rpc_url }.run().await }).await?
Command::Transfer {
args,
fee_token,
rpc_url,
} => {
if args.token_address.is_some() {
todo!("Handle ERC20 transfers")
}

if args.explorer_url {
todo!("Display transaction URL in the explorer")
}

let from = get_address_from_secret_key(&args.private_key.secret_bytes())
.map_err(|e| eyre::eyre!(e))?;

let client = EthClient::new(rpc_url)?;
let tx_type = if fee_token.is_some() {
TxType::FeeToken
} else {
TxType::EIP1559
};

let tx_hash = transfer(
args.amount,
from,
args.to,
tx_type,
&args.private_key,
&client,
Overrides {
fee_token,
..Default::default()
},
)
.await?;

println!("{tx_hash:#x}");

if !args.cast {
wait_for_transaction_receipt(tx_hash, &client, 100, args.silent).await?;
}
}
Command::Send { args, rpc_url } => {
Box::pin(async { EthCommand::Send { args, rpc_url }.run().await }).await?
Expand All @@ -490,6 +622,105 @@ impl Command {
Command::ChainId { hex, rpc_url } => {
Box::pin(async { EthCommand::ChainId { hex, rpc_url }.run().await }).await?
}
Command::GetFeeInfo { block, rpc_url } => {
let client: EthClient = EthClient::new(rpc_url)?;
let fee_info = fetch_fee_info(&client, block).await?;

let base_fee_vault_address = fee_info
.base_fee_vault_address
.map(|addr| format!("{addr:#x}"))
.unwrap_or_else(String::new);
let operator_fee_vault_address = fee_info
.operator_fee_vault_address
.map(|addr| format!("{addr:#x}"))
.unwrap_or_else(String::new);
let l1_fee_vault_address = fee_info
.l1_fee_vault_address
.map(|addr| format!("{addr:#x}"))
.unwrap_or_else(String::new);

println!("L2 fee info for block {}:", fee_info.block_number);
println!(" Base fee vault: {base_fee_vault_address}");
println!(" Operator fee vault: {operator_fee_vault_address}");
println!(" L1 fee vault: {l1_fee_vault_address}");
println!(
" Operator fee (wei/gas): {:#x}",
fee_info.operator_fee
);
println!(
" L1 blob base fee (wei/blob-gas): {:#x}",
fee_info.blob_base_fee
);
}
Command::BatchNumber { rpc_url } => {
let client = EthClient::new(rpc_url)?;

let batch_number = get_batch_number(&client).await?;
println!("{batch_number}");
}
Command::BatchByNumber {
batch_number,
rpc_url,
} => {
let client = EthClient::new(rpc_url)?;
let batch_number = match batch_number {
Some(number) => number,
None => get_batch_number(&client).await?,
};

let batch = match get_batch_by_number(&client, batch_number).await {
Ok(batch) => batch.batch,
Err(err) => {
println!("Batch {batch_number} not available yet: {err}");
return Ok(());
}
};

let commit_tx = batch
.commit_tx
.map(|tx| format!("{tx:#x}"))
.unwrap_or_else(String::new);
let verify_tx = batch
.verify_tx
.map(|tx| format!("{tx:#x}"))
.unwrap_or_else(String::new);

// TODO: update with new fields
println!("Batch info for batch {}", batch.number);
println!(" Number: {}", batch.number);
println!(" First block: {}", batch.first_block);
println!(" Last block: {}", batch.last_block);
println!(" State root: {:#x}", batch.state_root);
println!(
" Privileged transactions hash: {:#x}",
batch.l1_in_messages_rolling_hash
);
println!(" Commit tx: {commit_tx}");
println!(" Verify tx: {verify_tx}");
}
Command::SponsorTx {
rpc_url,
to,
calldata,
auth_list,
cast,
silent,
} => {
let client = EthClient::new(rpc_url)?;
let calldata = calldata.unwrap_or_else(Bytes::new);

let tx_hash =
send_authorized_transaction(&client, to, calldata, &auth_list).await?;

println!("{tx_hash:#x}");

if !cast {
wait_for_transaction_receipt(tx_hash, &client, 100, silent).await?;
}
}
Command::Authorize { args, rpc_url } => {
Box::pin(async { EthCommand::Authorize { args, rpc_url }.run().await }).await?
}
};
Ok(())
}
Expand Down
Loading