diff --git a/src/cli/burn.rs b/src/cli/burn.rs index 9a06c99..230dfb0 100644 --- a/src/cli/burn.rs +++ b/src/cli/burn.rs @@ -1,5 +1,5 @@ use super::CommonOpt; -use crate::cli::utils::check_required_files; +use crate::cli::{get_swap_calldata, utils::check_required_files}; use alloy::primitives::utils::parse_ether; use anyhow::Result; use structopt::StructOpt; @@ -24,6 +24,12 @@ impl BurnOpt { let fee = parse_ether(&self.fee)?; let spend = parse_ether(&self.spend)?; + // let receiver_hook = get_swap_calldata( + // parse_ether("0.0001").unwrap(), + // self.common_opt.private_key.address(), + // ); + let receiver_hook = Vec::new(); + let ( burn_key, burn_addr, @@ -32,7 +38,10 @@ impl BurnOpt { remaining_coin_val, remaining_coin_u256, burn_extra_commit, - ) = self.common_opt.prepare_inputs(amount, fee, spend).await?; + ) = self + .common_opt + .prepare_inputs(amount, fee, spend, receiver_hook.clone().into()) + .await?; let (_tx_hash, _ok) = self.common_opt.send_burn_tx(burn_addr, amount).await?; self.common_opt.persist_burn_data( @@ -65,6 +74,7 @@ impl BurnOpt { remaining_coin_u256, fee, spend, + receiver_hook.into(), ) .await?; diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 95966a1..1beee39 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -5,7 +5,6 @@ mod info; mod ls; mod mine; mod participate; -mod recover; mod spend; mod utils; use crate::cli::utils::{append_new_entry, burn_file, coins_file, init_coins_file, next_id}; @@ -16,8 +15,10 @@ use crate::utils::{ compute_nullifier, compute_previous_coin, compute_remaining_coin, fetch_block_and_header_bytes, find_burn_key, generate_burn_address, get_account_proof, }; -use alloy::primitives::Bytes; +use alloy::consensus::Receipt; +use alloy::primitives::{Bytes, U160, address}; use alloy::signers::local::PrivateKeySigner; +use alloy::sol_types::{SolCall, SolValue}; use alloy::{ hex::ToHexExt, network::TransactionBuilder, @@ -56,6 +57,51 @@ pub struct RuntimeContext { pub provider: P, } +use alloy::dyn_abi::DynSolValue; +use alloy::primitives::I256; +use alloy::{hex, sol}; + +sol! { + interface IUniswapV3Pool { + /// swap function + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + } +} + +/// Generate calldata for Uniswap V3 pool swap (BETH -> ETH) +fn get_swap_calldata(amount_in: U256, recipient: Address) -> Vec { + let zero_for_one = false; + let amount_specified = I256::from_raw(amount_in); + + let sqrt_price_limit_x96 = if zero_for_one { + U160::from_str_radix("4295128741", 10).unwrap() + } else { + U160::from_str_radix("1461446703485210103287273052203988822378723970340", 10).unwrap() + }; + let data = Vec::new(); + let swap_call = IUniswapV3Pool::swapCall { + recipient, + zeroForOne: zero_for_one, + amountSpecified: amount_specified, + sqrtPriceLimitX96: sqrt_price_limit_x96, + data: data.into(), + } + .abi_encode(); + + ( + address!("0x646b5eB499411390448b5e21838aCB8B2FF548dA"), + amount_in, + swap_call, + ) + .abi_encode_params() +} + impl CommonOpt { pub fn overridden_network(&self) -> Result { let mut net = NETWORKS @@ -97,6 +143,7 @@ impl CommonOpt { remaining_coin: U256, fee: U256, spend: U256, + swap_calldata: Bytes, ) -> Result<()> { let rt = self.setup().await?; // get provider, wallet, network from self println!("Broadcasting mint transaction..."); @@ -105,7 +152,7 @@ impl CommonOpt { let beth = BETH::new(net.beth, rt.provider); // call the zk-proof mintCoin(...) method - let receipt = beth + let pending_tx = beth .mintCoin( // pi_a [proof.proof.pi_a[0], proof.proof.pi_a[1]], @@ -128,19 +175,39 @@ impl CommonOpt { rt.wallet_address, U256::ZERO, rt.wallet_address, - Bytes::new(), + swap_calldata, Bytes::new(), ) .send() - .await? - .get_receipt() - .await?; - - if receipt.status() { - println!("Success!"); - } else { - println!("Transaction failed!"); + .await; + match pending_tx { + Ok(pending) => { + // transaction mined successfully + let receipt = pending.get_receipt().await?; + if receipt.status() { + println!("Success!"); + } else { + println!("Transaction failed!"); + } + } + Err(err) => { + // transaction reverted, err may contain revert data + if let Some(revert_bytes) = err.as_revert_data() { + // revert_bytes: Vec — ABI encoded + println!("Revert data (raw): 0x{}", hex::encode(revert_bytes.clone())); + + // decode revert reason as string + if let Ok(reason) = + ethers::abi::decode(&[ethers::abi::ParamType::String], &revert_bytes) + { + println!("Revert reason: {}", reason[0].to_string()); + } + } else { + println!("Transaction failed without revert data: {:?}", err); + } + } } + Ok(()) } pub async fn broadcast_spend( @@ -211,6 +278,7 @@ impl CommonOpt { amount: U256, fee: U256, spend: U256, + receiver_hook: Bytes, ) -> Result<(Fp, Address, Fp, U256, Fp, U256, U256)> { let rt = self.setup().await?; @@ -225,7 +293,8 @@ impl CommonOpt { // 1) burn_key println!("Generating a burn-key..."); - let extra_commit = generate_burn_extra_commit(rt.wallet_address, U256::ZERO, fee); + let extra_commit = + generate_burn_extra_commit(rt.wallet_address, U256::ZERO, fee, receiver_hook.clone()); let burn_key = find_burn_key(2, extra_commit, spend); println!("Your burn_key: {:?}", burn_key); println!( @@ -242,6 +311,7 @@ impl CommonOpt { U256::ZERO, fee, spend, + receiver_hook.clone(), ); // 3) nullifier (Fp only needed by caller) @@ -267,6 +337,7 @@ impl CommonOpt { burn_key: Fp, fee: U256, reveal: U256, + receiver_hook: Bytes, ) -> anyhow::Result<(alloy::primitives::Address, Fp)> { let rt = self.setup().await?; // wallet addr + provider + network let burn_addr_prefix = crate::constants::poseidon_burn_address_prefix(); @@ -278,6 +349,7 @@ impl CommonOpt { U256::ZERO, fee, reveal, + receiver_hook, ); let (nullifier_fp, _nullifier_u256) = compute_nullifier(burn_key); Ok((burn_addr, nullifier_fp)) @@ -521,5 +593,4 @@ pub use ls::LsCommand; pub use ls::LsOpt; pub use mine::MineOpt; pub use participate::ParticipateOpt; -pub use recover::RecoverOpt; pub use spend::SpendOpt; diff --git a/src/cli/recover.rs b/src/cli/recover.rs deleted file mode 100644 index e391012..0000000 --- a/src/cli/recover.rs +++ /dev/null @@ -1,164 +0,0 @@ -use structopt::StructOpt; - -use anyhow::{Context, bail}; -use serde_json::Value; - -use super::CommonOpt; -use crate::cli::utils::check_required_files; - -use crate::fp::Fp; -use alloy::{ - hex, - primitives::{U256, utils::parse_ether}, -}; -use anyhow::anyhow; -use ff::PrimeField; -use std::fs; - -#[derive(StructOpt)] -pub enum RecoverOpt { - ById { - #[structopt(flatten)] - common_opt: CommonOpt, - #[structopt(long)] - id: String, - #[structopt(long)] - spend: Option, - }, - - Manual { - #[structopt(flatten)] - common_opt: CommonOpt, - #[structopt(long)] - burn_key: String, - #[structopt(long)] - spend: String, - #[structopt(long)] - fee: String, - }, -} -impl RecoverOpt { - pub async fn run(self, params_dir: &std::path::Path) -> Result<(), anyhow::Error> { - let (raw_burn_key, spend, fee, common_opt) = match self { - RecoverOpt::Manual { - burn_key, - spend, - fee, - common_opt, - } => { - let fee = parse_ether(&fee)?; - let spend = parse_ether(&spend)?; - (burn_key, spend, fee, common_opt) - } - - RecoverOpt::ById { - id, - common_opt, - spend, - } => { - let burn_json_path = "burn.json"; - - let burn_path = params_dir.join(burn_json_path); - if !burn_path.exists() { - println!("No coins.json found at {}", burn_path.display()); - return Ok(()); - } - let data = fs::read_to_string(&burn_path) - .with_context(|| format!("failed to read {}", burn_path.display()))?; - - let json: Value = serde_json::from_str(&data) - .with_context(|| format!("failed to parse {} as JSON", burn_path.display()))?; - - let arr = json.as_array().with_context(|| { - format!("expected {} to be a JSON array", burn_path.display()) - })?; - - let coin = arr - .iter() - .find(|obj| { - obj.get("id").map_or(false, |v| match v { - Value::String(s) => s == &id, - Value::Number(n) => n.to_string() == id, - _ => false, - }) - }) - .ok_or_else(|| { - anyhow!("no coin with id {} found in {}", id, burn_path.display()) - })?; - println!("{}", serde_json::to_string_pretty(coin)?); - let burn_key = match coin.get("burnKey") { - Some(Value::String(key)) => key.clone(), - _ => bail!("burn_key not found in the burn object"), - }; - let fee_str = match coin.get("fee") { - Some(Value::String(key)) => key.clone(), - _ => bail!("fee not found in the burn object"), - }; - let fee = fee_str.parse::()?; - let stored_spend = match coin.get("spend") { - Some(Value::String(key)) => key.clone(), - _ => bail!("spend not found in the burn object"), - }; - - let spend = match spend { - Some(s) => parse_ether(&s)?, - None => stored_spend.parse::()?, - }; - - (burn_key, spend, fee, common_opt) - } - }; - - let burn_key = if raw_burn_key.starts_with("0x") { - let hex = raw_burn_key.strip_prefix("0x").unwrap(); - let bytes = hex::decode(hex)?; - Fp::from_be_bytes(&bytes) - } else { - Fp::from_str_vartime(&raw_burn_key.to_string()).unwrap() - }; - - check_required_files(params_dir)?; - - let (burn_addr, nullifier_fp) = common_opt - .recover_prepare_from_key(burn_key, fee, spend) - .await?; - - println!( - "Your burn-key as string: {}", - U256::from_le_bytes(burn_key.to_repr().0).to_string() - ); - println!("Your burn-address is: {}", burn_addr); - - let (remaining_coin_val, remaining_coin_u256) = common_opt - .recover_check_balance_and_compute_remaining(burn_addr, burn_key, fee, spend) - .await?; - - let (json_output, block_number, _out_path) = common_opt - .build_and_prove_burn( - params_dir, - burn_addr, - burn_key, - fee, - spend, - "input.json", - "witness.wtns", - ) - .await?; - - common_opt.persist_burn_data(params_dir, burn_key, remaining_coin_val, None, None, true)?; - - let nullifier_u256 = U256::from_le_bytes(nullifier_fp.to_repr().0); - common_opt - .broadcast_mint( - &json_output, - block_number, - nullifier_u256, - remaining_coin_u256, - fee, - spend, - ) - .await?; - - Ok(()) - } -} diff --git a/src/main.rs b/src/main.rs index 698c554..c5cc8fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,6 @@ mod fp; mod poseidon; -use cli::RecoverOpt; - use std::path::PathBuf; use structopt::StructOpt; pub mod cli; @@ -34,7 +32,6 @@ enum MinerOpt { GenerateWitness(GenerateWitnessOpt), Burn(BurnOpt), Mine(MineOpt), - Recover(RecoverOpt), Server, } @@ -67,7 +64,6 @@ impl MinerOpt { Ok(()) } - MinerOpt::Recover(cmd) => cmd.run(params_dir).await, MinerOpt::Server => { println!("🚀 Starting server..."); run_server().await diff --git a/src/networks.rs b/src/networks.rs index de4ae93..c1e4946 100644 --- a/src/networks.rs +++ b/src/networks.rs @@ -24,8 +24,8 @@ lazy_static! { "sepolia".into(), Network { rpc: "https://sepolia.drpc.org".parse().unwrap(), - beth: address!("0xEc71903c94fe79634164c0ad1ba67be41f37e804"), - worm: address!("0xF25453f75ff520f9cE922E689B1cCE65DE3dC646"), + beth: address!("0x716bC7e331c9Da551e5Eb6A099c300db4c08E994"), + worm: address!("0xcBdF9890B5935F01B2f21583d1885CdC8389eb5F"), }, ), ] diff --git a/src/server/proof_logic.rs b/src/server/proof_logic.rs index 4fed538..b1a05b3 100644 --- a/src/server/proof_logic.rs +++ b/src/server/proof_logic.rs @@ -7,6 +7,8 @@ use crate::utils::{ build_and_prove_burn_logic, compute_nullifier, compute_remaining_coin, fetch_block_and_header_bytes, get_account_proof, }; +use alloy::hex::FromHex; +use alloy::primitives::Bytes; use alloy::{ primitives::{Address, U256, utils::parse_ether}, providers::{Provider, ProviderBuilder}, @@ -38,6 +40,7 @@ fn derive_burn_and_nullifier_from_input( prover_fee, broadcaster_fee, spend, + Bytes::from_hex(input.receiver_hook.clone())?, ); println!("Extra commit: {}", extra_commit.to_string()); diff --git a/src/server/types.rs b/src/server/types.rs index 4105f09..0bdca4a 100644 --- a/src/server/types.rs +++ b/src/server/types.rs @@ -23,6 +23,7 @@ pub struct ProofInput { pub wallet_address: String, pub proof: Option, pub block_number: Option, + pub receiver_hook: String, } #[derive(Serialize, Debug, Clone)] diff --git a/src/utils.rs b/src/utils.rs index 3c6216c..477306d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,5 @@ use crate::fp::Fp; +use alloy::primitives::Bytes; use alloy::rpc::types::BlockNumberOrTag; use alloy::sol_types::SolValue; use alloy::{ @@ -71,9 +72,10 @@ pub fn generate_burn_extra_commit( receiver: Address, prover_fee: U256, broadcaster_fee: U256, + receiver_hook: Bytes, ) -> U256 { - let extra_commit_preimage = (broadcaster_fee, prover_fee, receiver).abi_encode_packed(); - println!("{:?}", extra_commit_preimage); + let extra_commit_preimage = + (broadcaster_fee, prover_fee, receiver, receiver_hook).abi_encode_packed(); U256::from_be_slice(keccak256(extra_commit_preimage.as_slice()).as_slice()) >> U256::from(8) } @@ -84,10 +86,12 @@ pub fn generate_burn_address( prover_fee: U256, broadcaster_fee: U256, reveal: U256, + receiver_hook: Bytes, ) -> (Address, U256) { let reveal_be: [u8; 32] = reveal.to_be_bytes(); let reveal_fp = Fp::from_be_bytes(&reveal_be); - let extra_commitment = generate_burn_extra_commit(receiver, prover_fee, broadcaster_fee); + let extra_commitment = + generate_burn_extra_commit(receiver, prover_fee, broadcaster_fee, receiver_hook); let extra_commitment_be: [u8; 32] = extra_commitment.to_be_bytes(); let extra_commitment_fp = Fp::from_be_bytes(&extra_commitment_be); let hash = poseidon::poseidon4(burn_addr_constant, burn_key, reveal_fp, extra_commitment_fp);