From 9cf4cb5ecb4b7091b3d3b262e0e278a139b2b500 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Wed, 1 Apr 2026 19:37:11 +0200 Subject: [PATCH 1/8] feat: add payment amount verification tests and migrate to unified payment vault Add E2E security tests verifying that nodes reject underpayment for both single-node and merkle payment modes: - test_attack_underpayment_single_node: pays 1 atto instead of 3x median - test_attack_underpayment_half_price: pays ~1.5x instead of 3x median - test_attack_merkle_proof_for_wrong_chunk: uses proof for wrong chunk - test_attack_merkle_proof_swap_within_batch: swaps proofs between chunks Also migrates client payment code to the unified payment vault contract, replacing separate data_payments/merkle_payments approvals with a single payment_vault approval, and using node-reported prices directly instead of fetching from the on-chain contract. Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 6 +- ant-cli/src/main.rs | 18 +--- ant-core/Cargo.toml | 4 +- ant-core/src/data/client/batch.rs | 48 +-------- ant-core/src/data/client/merkle.rs | 67 +------------ ant-core/src/data/client/mod.rs | 6 +- ant-core/src/data/client/payment.rs | 60 +++-------- ant-core/src/data/client/quote.rs | 3 +- ant-core/src/node/devnet.rs | 19 ++-- ant-core/tests/e2e_merkle.rs | 138 +++++++++++++++++++++++++- ant-core/tests/e2e_security.rs | 149 +++++++++++++++++++++++++++- ant-core/tests/support/mod.rs | 16 +-- 12 files changed, 328 insertions(+), 206 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3460d52..26a318f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -900,8 +900,7 @@ dependencies = [ [[package]] name = "ant-node" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bc2ac7c262accf6b4ab73448816662c921303c4a81932379b90edfdee3e4a5d" +source = "git+https://github.com/WithAutonomi/ant-node.git?branch=feat%2Fadapt-evmlib-payment-vault#08bb16f5e240d0c963c36afebd1e1b6c43368d81" dependencies = [ "aes-gcm-siv", "blake3", @@ -2416,8 +2415,7 @@ dependencies = [ [[package]] name = "evmlib" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d608fcd0976beee509fef7fa391735571cb2fffd715ddca174322180300b6615" +source = "git+https://github.com/WithAutonomi/evmlib.git?branch=refactor%2Funify-payment-vault-v2#f4cdd45ea76a98ced0a416794c92b9b4bc2da224" dependencies = [ "alloy", "ant-merkle", diff --git a/ant-cli/src/main.rs b/ant-cli/src/main.rs index 4094c7f..9c58512 100644 --- a/ant-cli/src/main.rs +++ b/ant-cli/src/main.rs @@ -215,24 +215,14 @@ fn resolve_evm_network( .payment_token_address .parse() .map_err(|e| anyhow::anyhow!("Invalid token address: {e}"))?; - let payments_addr: EvmAddress = evm - .data_payments_address + let vault_addr: EvmAddress = evm + .payment_vault_address .parse() - .map_err(|e| anyhow::anyhow!("Invalid payments address: {e}"))?; - let merkle_addr: Option = evm - .merkle_payments_address - .as_ref() - .map(|s| { - s.parse().map_err(|e| { - anyhow::anyhow!("Invalid merkle payments address: {e}") - }) - }) - .transpose()?; + .map_err(|e| anyhow::anyhow!("Invalid payment vault address: {e}"))?; return Ok(EvmNetwork::Custom(CustomNetwork { rpc_url_http: rpc_url, payment_token_address: token_addr, - data_payments_address: payments_addr, - merkle_payments_address: merkle_addr, + payment_vault_address: vault_addr, })); } } diff --git a/ant-core/Cargo.toml b/ant-core/Cargo.toml index 512dfd9..cafbdd2 100644 --- a/ant-core/Cargo.toml +++ b/ant-core/Cargo.toml @@ -24,7 +24,7 @@ zip = "2" tower-http = { version = "0.6.8", features = ["cors"] } # Data operations -evmlib = "0.5.0" +evmlib = { git = "https://github.com/WithAutonomi/evmlib.git", branch = "refactor/unify-payment-vault-v2" } xor_name = "5" self_encryption = "0.35.0" futures = "0.3" @@ -35,7 +35,7 @@ tracing = "0.1" bytes = "1" lru = "0.16" rand = "0.8" -ant-node = "0.9.0" +ant-node = { git = "https://github.com/WithAutonomi/ant-node.git", branch = "feat/adapt-evmlib-payment-vault" } saorsa-pqc = "0.5" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/ant-core/src/data/client/batch.rs b/ant-core/src/data/client/batch.rs index 7c53361..92930bd 100644 --- a/ant-core/src/data/client/batch.rs +++ b/ant-core/src/data/client/batch.rs @@ -13,7 +13,6 @@ use ant_node::core::{MultiAddr, PeerId}; use ant_node::payment::{serialize_single_node_proof, PaymentProof, SingleNodePayment}; use bytes::Bytes; use evmlib::common::{Amount, QuoteHash, TxHash}; -use evmlib::contract::payment_vault::get_market_price; use evmlib::wallet::PayForQuotesError; use evmlib::{EncodedPeerId, PaymentQuote, ProofOfPayment, RewardsAddress}; use futures::stream::{self, StreamExt}; @@ -144,7 +143,7 @@ pub fn finalize_batch_payment( impl Client { /// Prepare a single chunk for batch payment. /// - /// Collects quotes and fetches contract prices without making any + /// Collects quotes and uses node-reported prices without making any /// on-chain transaction. Returns `Ok(None)` if the chunk is already /// stored on the network. /// @@ -168,44 +167,21 @@ impl Client { Err(e) => return Err(e), }; - let evm_network = self.require_evm_network()?; - // Capture all quoted peers for close-group replication. let quoted_peers: Vec<(PeerId, Vec)> = quotes_with_peers .iter() .map(|(peer_id, addrs, _, _)| (*peer_id, addrs.clone())) .collect(); - // Fetch authoritative prices from the on-chain contract. - let metrics_batch: Vec<_> = quotes_with_peers - .iter() - .map(|(_, _, quote, _)| quote.quoting_metrics.clone()) - .collect(); - - let contract_prices = get_market_price(evm_network, metrics_batch) - .await - .map_err(|e| { - Error::Payment(format!("Failed to get market prices from contract: {e}")) - })?; - - if contract_prices.len() != quotes_with_peers.len() { - return Err(Error::Payment(format!( - "Contract returned {} prices for {} quotes", - contract_prices.len(), - quotes_with_peers.len() - ))); - } - // Build peer_quotes for ProofOfPayment + quotes for SingleNodePayment. + // Use node-reported prices directly — no contract price fetch needed. let mut peer_quotes = Vec::with_capacity(quotes_with_peers.len()); let mut quotes_for_payment = Vec::with_capacity(quotes_with_peers.len()); - for ((peer_id, _addrs, quote, _local_price), contract_price) in - quotes_with_peers.into_iter().zip(contract_prices) - { + for (peer_id, _addrs, quote, price) in quotes_with_peers { let encoded = peer_id_to_encoded(&peer_id)?; peer_quotes.push((encoded, quote.clone())); - quotes_for_payment.push((quote, contract_price)); + quotes_for_payment.push((quote, price)); } let payment = SingleNodePayment::from_quotes(quotes_for_payment) @@ -437,21 +413,6 @@ mod tests { use super::*; use ant_node::payment::single_node::QuotePaymentInfo; use ant_node::CLOSE_GROUP_SIZE; - use evmlib::quoting_metrics::QuotingMetrics; - - fn test_metrics() -> QuotingMetrics { - QuotingMetrics { - data_size: 0, - data_type: 0, - close_records_stored: 0, - records_per_type: vec![], - max_records: 0, - received_payment_count: 0, - live_time: 0, - network_density: None, - network_size: None, - } - } /// Helper: build a PreparedChunk with specified payment amounts. fn make_prepared_chunk(amounts: [u64; CLOSE_GROUP_SIZE]) -> PreparedChunk { @@ -460,7 +421,6 @@ mod tests { quote_hash: QuoteHash::from([i as u8 + 1; 32]), rewards_address: RewardsAddress::new([i as u8 + 10; 20]), amount: Amount::from(amounts[i]), - quoting_metrics: test_metrics(), }); PreparedChunk { diff --git a/ant-core/src/data/client/merkle.rs b/ant-core/src/data/client/merkle.rs index 0b28841..95c912a 100644 --- a/ant-core/src/data/client/merkle.rs +++ b/ant-core/src/data/client/merkle.rs @@ -376,12 +376,8 @@ impl Client { candidate_futures.push(fut); } - self.collect_validated_candidates( - &mut candidate_futures, - merkle_payment_timestamp, - data_type, - ) - .await + self.collect_validated_candidates(&mut candidate_futures, merkle_payment_timestamp) + .await } /// Collect and validate merkle candidate responses until we have enough. @@ -396,7 +392,6 @@ impl Client { >, >, merkle_payment_timestamp: u64, - expected_data_type: u32, ) -> Result<[MerklePaymentCandidateNode; CANDIDATES_PER_POOL]> { let mut candidates = Vec::with_capacity(CANDIDATES_PER_POOL); let mut failures: Vec = Vec::new(); @@ -414,14 +409,6 @@ impl Client { failures.push(format!("{peer_id}: timestamp mismatch")); continue; } - if candidate.quoting_metrics.data_type != expected_data_type { - warn!( - "Data type mismatch from {peer_id}: expected {expected_data_type}, got {}", - candidate.quoting_metrics.data_type - ); - failures.push(format!("{peer_id}: wrong data_type")); - continue; - } candidates.push(candidate); if candidates.len() >= CANDIDATES_PER_POOL { break; @@ -633,8 +620,8 @@ mod tests { #[test] fn test_merkle_proof_serialize_deserialize_roundtrip() { use ant_node::payment::{deserialize_merkle_proof, serialize_merkle_proof}; + use evmlib::common::Amount; use evmlib::merkle_payments::MerklePaymentCandidateNode; - use evmlib::quoting_metrics::QuotingMetrics; use evmlib::RewardsAddress; let addrs = make_test_addresses(4); @@ -654,17 +641,7 @@ mod tests { let candidate_nodes: [MerklePaymentCandidateNode; CANDIDATES_PER_POOL] = std::array::from_fn(|i| MerklePaymentCandidateNode { pub_key: vec![i as u8; 32], - quoting_metrics: QuotingMetrics { - data_size: 1024, - data_type: 0, - close_records_stored: 0, - records_per_type: vec![], - max_records: 100, - received_payment_count: 0, - live_time: 0, - network_density: None, - network_size: None, - }, + price: Amount::from(1024u64), reward_address: RewardsAddress::new([i as u8; 20]), merkle_payment_timestamp: timestamp, signature: vec![i as u8; 64], @@ -702,17 +679,7 @@ mod tests { // Simulates what collect_validated_candidates checks let candidate = MerklePaymentCandidateNode { pub_key: vec![0u8; 32], - quoting_metrics: evmlib::quoting_metrics::QuotingMetrics { - data_size: 0, - data_type: 0, - close_records_stored: 0, - records_per_type: vec![], - max_records: 0, - received_payment_count: 0, - live_time: 0, - network_density: None, - network_size: None, - }, + price: evmlib::common::Amount::ZERO, reward_address: evmlib::RewardsAddress::new([0u8; 20]), merkle_payment_timestamp: 1000, signature: vec![0u8; 64], @@ -722,30 +689,6 @@ mod tests { assert_ne!(candidate.merkle_payment_timestamp, 2000); } - #[test] - fn test_candidate_wrong_data_type_rejected() { - let candidate = MerklePaymentCandidateNode { - pub_key: vec![0u8; 32], - quoting_metrics: evmlib::quoting_metrics::QuotingMetrics { - data_size: 0, - data_type: 1, // scratchpad - close_records_stored: 0, - records_per_type: vec![], - max_records: 0, - received_payment_count: 0, - live_time: 0, - network_density: None, - network_size: None, - }, - reward_address: evmlib::RewardsAddress::new([0u8; 20]), - merkle_payment_timestamp: 1000, - signature: vec![0u8; 64], - }; - - // data_type check: 1 (scratchpad) != 0 (chunk) - assert_ne!(candidate.quoting_metrics.data_type, 0); - } - // ========================================================================= // Batch splitting edge cases // ========================================================================= diff --git a/ant-core/src/data/client/mod.rs b/ant-core/src/data/client/mod.rs index eaa9fb1..bbf9e52 100644 --- a/ant-core/src/data/client/mod.rs +++ b/ant-core/src/data/client/mod.rs @@ -118,7 +118,7 @@ impl Client { /// Set the wallet for payment operations. /// /// Also populates the EVM network from the wallet so that - /// price queries work without a separate `with_evm_network` call. + /// token approvals work without a separate `with_evm_network` call. #[must_use] pub fn with_wallet(mut self, wallet: Wallet) -> Self { self.evm_network = Some(wallet.network().clone()); @@ -126,9 +126,9 @@ impl Client { self } - /// Set the EVM network for price queries without requiring a wallet. + /// Set the EVM network without requiring a wallet. /// - /// This enables operations like quote collection and cost estimation + /// This enables token approval and contract interactions /// for external-signer flows where the private key lives outside Rust. #[must_use] pub fn with_evm_network(mut self, network: evmlib::Network) -> Self { diff --git a/ant-core/src/data/client/payment.rs b/ant-core/src/data/client/payment.rs index 39ba048..651bdb6 100644 --- a/ant-core/src/data/client/payment.rs +++ b/ant-core/src/data/client/payment.rs @@ -24,7 +24,7 @@ impl Client { /// /// This orchestrates the full payment flow: /// 1. Collect `CLOSE_GROUP_SIZE` quotes from closest peers - /// 2. Build `SingleNodePayment` (median 3x, others 0) + /// 2. Build `SingleNodePayment` using node-reported prices (median 3x, others 0) /// 3. Pay on-chain via the wallet /// 4. Serialize `PaymentProof` with transaction hashes /// @@ -41,7 +41,7 @@ impl Client { data_size: u64, data_type: u32, ) -> Result<(Vec, Vec<(PeerId, Vec)>)> { - // Wallet is required for the on-chain payment step (step 5 below). + // Wallet is required for the on-chain payment step (step 4 below). // Check early so we don't waste time collecting quotes for a misconfigured client. let wallet = self.require_wallet()?; @@ -56,50 +56,24 @@ impl Client { .map(|(peer_id, addrs, _, _)| (*peer_id, addrs.clone())) .collect(); - // 2. Fetch prices from the on-chain contract rather than using the - // locally-computed estimates. The contract's getQuote() is the authoritative - // price source — the verifyPayment() call recomputes prices from QuotingMetrics - // using the same formula, so we must use matching prices. - let metrics_batch: Vec<_> = quotes_with_peers - .iter() - .map(|(_, _, quote, _)| quote.quoting_metrics.clone()) - .collect(); - - let evm_network = self.require_evm_network()?; - let contract_prices = - evmlib::contract::payment_vault::get_market_price(evm_network, metrics_batch) - .await - .map_err(|e| { - Error::Payment(format!("Failed to get market prices from contract: {e}")) - })?; - - if contract_prices.len() != quotes_with_peers.len() { - return Err(Error::Payment(format!( - "Contract returned {} prices for {} quotes", - contract_prices.len(), - quotes_with_peers.len() - ))); - } - - // 3. Build peer_quotes for ProofOfPayment + quotes for SingleNodePayment + // 2. Build peer_quotes for ProofOfPayment + quotes for SingleNodePayment. + // Use node-reported prices directly — no contract price fetch needed. let mut peer_quotes = Vec::with_capacity(quotes_with_peers.len()); let mut quotes_for_payment = Vec::with_capacity(quotes_with_peers.len()); - for ((peer_id, _addrs, quote, _local_price), contract_price) in - quotes_with_peers.into_iter().zip(contract_prices) - { + for (peer_id, _addrs, quote, price) in quotes_with_peers { let encoded = peer_id_to_encoded(&peer_id)?; peer_quotes.push((encoded, quote.clone())); - quotes_for_payment.push((quote, contract_price)); + quotes_for_payment.push((quote, price)); } - // 4. Create SingleNodePayment (sorts by price, selects median) + // 3. Create SingleNodePayment (sorts by price, selects median) let payment = SingleNodePayment::from_quotes(quotes_for_payment) .map_err(|e| Error::Payment(format!("Failed to create payment: {e}")))?; info!("Payment total: {} atto", payment.total_amount()); - // 5. Pay on-chain + // 4. Pay on-chain let tx_hashes = payment .pay(wallet) .await @@ -110,7 +84,7 @@ impl Client { tx_hashes.len() ); - // 6. Build and serialize proof with version tag + // 5. Build and serialize proof with version tag let proof = PaymentProof { proof_of_payment: ProofOfPayment { peer_quotes }, tx_hashes, @@ -134,22 +108,12 @@ impl Client { let wallet = self.require_wallet()?; let evm_network = self.require_evm_network()?; - // Approve data payments contract - let data_payments_address = evm_network.data_payments_address(); + let vault_address = evm_network.payment_vault_address(); wallet - .approve_to_spend_tokens(*data_payments_address, evmlib::common::U256::MAX) + .approve_to_spend_tokens(*vault_address, evmlib::common::U256::MAX) .await .map_err(|e| Error::Payment(format!("Token approval failed: {e}")))?; - info!("Token spend approved for data payments contract"); - - // Approve merkle payments contract (if deployed) - if let Some(merkle_address) = evm_network.merkle_payments_address() { - wallet - .approve_to_spend_tokens(*merkle_address, evmlib::common::U256::MAX) - .await - .map_err(|e| Error::Payment(format!("Merkle token approval failed: {e}")))?; - info!("Token spend approved for merkle payments contract"); - } + info!("Token spend approved for payment vault contract"); Ok(()) } diff --git a/ant-core/src/data/client/quote.rs b/ant-core/src/data/client/quote.rs index 680c247..52b695b 100644 --- a/ant-core/src/data/client/quote.rs +++ b/ant-core/src/data/client/quote.rs @@ -10,7 +10,6 @@ use ant_node::ant_protocol::{ }; use ant_node::client::send_and_await_chunk_response; use ant_node::core::{MultiAddr, PeerId}; -use ant_node::payment::calculate_price; use ant_node::{CLOSE_GROUP_MAJORITY, CLOSE_GROUP_SIZE}; use evmlib::common::Amount; use evmlib::PaymentQuote; @@ -103,7 +102,7 @@ impl Client { } match rmp_serde::from_slice::("e) { Ok(payment_quote) => { - let price = calculate_price(&payment_quote.quoting_metrics); + let price = payment_quote.price; debug!("Received quote from {peer_id_clone}: price = {price}"); Some(Ok((payment_quote, price))) } diff --git a/ant-core/src/node/devnet.rs b/ant-core/src/node/devnet.rs index 04755a0..9d1f012 100644 --- a/ant-core/src/node/devnet.rs +++ b/ant-core/src/node/devnet.rs @@ -48,8 +48,7 @@ impl LocalDevnet { .default_wallet_private_key() .map_err(|e| Error::Config(format!("failed to get wallet key: {e}")))?; - let (rpc_url, token_addr, payments_addr, merkle_addr) = - extract_custom_network_info(&network)?; + let (rpc_url, token_addr, vault_addr) = extract_custom_network_info(&network)?; config.evm_network = Some(network.clone()); @@ -70,8 +69,7 @@ impl LocalDevnet { rpc_url, wallet_private_key: wallet_key.clone(), payment_token_address: token_addr, - data_payments_address: payments_addr, - merkle_payments_address: merkle_addr, + payment_vault_address: vault_addr, }; let manifest = DevnetManifest { @@ -192,21 +190,16 @@ impl LocalDevnet { } } -/// Extract RPC URL, token address, payments address, and merkle address from a Custom network. -fn extract_custom_network_info( - network: &EvmNetwork, -) -> Result<(String, String, String, Option)> { +/// Extract RPC URL, token address, and payment vault address from a Custom network. +fn extract_custom_network_info(network: &EvmNetwork) -> Result<(String, String, String)> { match network { EvmNetwork::Custom(custom) => { let token = custom.payment_token_address; - let payments = custom.data_payments_address; + let vault = custom.payment_vault_address; Ok(( custom.rpc_url_http.to_string(), format!("{token:?}"), - format!("{payments:?}"), - custom - .merkle_payments_address - .map(|addr| format!("{addr:?}")), + format!("{vault:?}"), )) } _ => Err(Error::Config( diff --git a/ant-core/tests/e2e_merkle.rs b/ant-core/tests/e2e_merkle.rs index dc848f1..85fe34b 100644 --- a/ant-core/tests/e2e_merkle.rs +++ b/ant-core/tests/e2e_merkle.rs @@ -13,7 +13,7 @@ mod support; use ant_core::data::client::merkle::PaymentMode; -use ant_core::data::{Client, ClientConfig}; +use ant_core::data::{compute_address, Client, ClientConfig}; use serial_test::serial; use std::io::Write; use std::sync::Arc; @@ -22,6 +22,9 @@ use tempfile::{NamedTempFile, TempDir}; const CLIENT_TIMEOUT_SECS: u64 = 120; +/// Chunk size for merkle security tests (small, fast to hash). +const TEST_CHUNK_SIZE: usize = 1024; + /// Create a 35-node testnet suitable for merkle payments. async fn setup_merkle_testnet() -> (Client, MiniTestnet) { eprintln!("Starting 35-node testnet..."); @@ -143,6 +146,139 @@ async fn test_merkle_data_upload_download() { testnet.teardown().await; } +// ─── Merkle Payment Security Tests ───────────────────────────────────────── +// +// Verify that nodes reject tampered merkle proofs. Unlike single-node payments +// where the client controls the amount, merkle payment amounts are determined +// by the smart contract. The cheating vectors for merkle are: +// - Using a proof from one chunk to store a different chunk (address mismatch) +// - Using a proof from a payment that didn't happen on-chain + +/// Use a valid merkle proof from chunk A to try storing chunk B. +/// +/// The merkle proof contains an address-binding commitment: the proof's +/// `address` field and sibling hashes bind it to a specific leaf in the tree. +/// Nodes must verify this binding rejects mismatched chunks. +#[tokio::test(flavor = "multi_thread")] +#[serial] +async fn test_attack_merkle_proof_for_wrong_chunk() { + let (client, testnet) = setup_merkle_testnet().await; + + // Create 4 small chunks for a minimal merkle tree + let chunks: Vec = (0..4u8) + .map(|i| bytes::Bytes::from(vec![i; TEST_CHUNK_SIZE])) + .collect(); + let addresses: Vec<[u8; 32]> = chunks.iter().map(|c| compute_address(c)).collect(); + + eprintln!("Paying for 4 chunks via merkle batch..."); + + // Pay for these chunks via merkle batch payment + let batch_result = client + .pay_for_merkle_batch(&addresses, 0, TEST_CHUNK_SIZE as u64) + .await + .expect("merkle batch payment should succeed"); + + assert_eq!( + batch_result.proofs.len(), + 4, + "should have proofs for all 4 chunks" + ); + + // Get the proof for chunk 0 + let proof_for_chunk_0 = batch_result + .proofs + .get(&addresses[0]) + .expect("should have proof for chunk 0") + .clone(); + + // Create a completely different chunk NOT in the merkle tree + let evil_content = bytes::Bytes::from("this content was NOT in the merkle tree"); + let evil_address = compute_address(&evil_content); + assert_ne!( + evil_address, addresses[0], + "evil chunk must have a different address" + ); + + // Find a peer close to the evil chunk's address to PUT to + let peers = client + .network() + .find_closest_peers(&evil_address, 1) + .await + .expect("should find peers"); + let (target_peer, target_addrs) = &peers[0]; + + eprintln!("Attempting PUT of wrong chunk with merkle proof for chunk 0..."); + + // Try to store the evil chunk using chunk 0's merkle proof + let result = client + .chunk_put_with_proof(evil_content, proof_for_chunk_0, target_peer, target_addrs) + .await; + + assert!( + result.is_err(), + "PUT with merkle proof for a different chunk should be rejected (address mismatch)" + ); + + drop(client); + testnet.teardown().await; +} + +/// Use a proof from chunk A to try storing chunk B where both are in the tree. +/// +/// Even when both chunks have valid merkle proofs from the same batch, the +/// proofs are NOT interchangeable — each proof binds to its specific leaf +/// via the address and sibling hash path. +#[tokio::test(flavor = "multi_thread")] +#[serial] +async fn test_attack_merkle_proof_swap_within_batch() { + let (client, testnet) = setup_merkle_testnet().await; + + let chunks: Vec = (0..4u8) + .map(|i| bytes::Bytes::from(vec![i; TEST_CHUNK_SIZE])) + .collect(); + let addresses: Vec<[u8; 32]> = chunks.iter().map(|c| compute_address(c)).collect(); + + eprintln!("Paying for 4 chunks via merkle batch..."); + + let batch_result = client + .pay_for_merkle_batch(&addresses, 0, TEST_CHUNK_SIZE as u64) + .await + .expect("merkle batch payment should succeed"); + + // Take chunk 0's proof and try to store chunk 1 with it + let proof_for_chunk_0 = batch_result + .proofs + .get(&addresses[0]) + .expect("should have proof for chunk 0") + .clone(); + + let peers = client + .network() + .find_closest_peers(&addresses[1], 1) + .await + .expect("should find peers"); + let (target_peer, target_addrs) = &peers[0]; + + eprintln!("Attempting to store chunk 1 using chunk 0's merkle proof..."); + + let result = client + .chunk_put_with_proof( + chunks[1].clone(), + proof_for_chunk_0, + target_peer, + target_addrs, + ) + .await; + + assert!( + result.is_err(), + "Swapping merkle proofs between chunks in the same batch should be rejected" + ); + + drop(client); + testnet.teardown().await; +} + // Single-node coexistence is tested in e2e_file.rs (6-node testnet). // The 35-node testnet's DHT can have sparse XOR regions where single-node // quotes can't find 5 peers for a random chunk address, making that test diff --git a/ant-core/tests/e2e_security.rs b/ant-core/tests/e2e_security.rs index cbb0ca3..42f0256 100644 --- a/ant-core/tests/e2e_security.rs +++ b/ant-core/tests/e2e_security.rs @@ -11,7 +11,7 @@ use ant_core::data::{compute_address, Client, ClientConfig}; use ant_node::core::PeerId; use ant_node::payment::{serialize_single_node_proof, PaymentProof, SingleNodePayment}; use bytes::Bytes; -use evmlib::common::TxHash; +use evmlib::common::{Amount, TxHash}; use evmlib::{EncodedPeerId, ProofOfPayment}; use serial_test::serial; use std::sync::Arc; @@ -359,3 +359,150 @@ async fn test_attack_client_without_wallet() { drop(client); testnet.teardown().await; } + +// ─── Test 9: Underpayment — Single Node ───────────────────────────────────── +// +// Collects real quotes, builds a valid SingleNodePayment, then tampers with +// the median quote's amount (reducing it to 1 atto). Pays on-chain with the +// reduced amount. The node's on-chain verifyPayment check should detect that +// the paid amount is far below the expected 3× median price and reject the PUT. + +#[tokio::test(flavor = "multi_thread")] +#[serial] +async fn test_attack_underpayment_single_node() { + let (client, testnet) = setup().await; + + let content = Bytes::from("underpayment attack: client pays too little"); + let address = compute_address(&content); + let data_size = content.len() as u64; + + // 1. Collect real quotes from close group + let quotes = client + .get_store_quotes(&address, data_size, 0) + .await + .expect("quote collection should succeed"); + + let target_peer = quotes.first().expect("should have quotes").0; + + // 2. Build SingleNodePayment normally (sorts by price, median gets 3×) + let mut peer_quotes = Vec::with_capacity(quotes.len()); + let mut quotes_for_payment = Vec::with_capacity(quotes.len()); + for (peer_id, _addrs, quote, price) in quotes { + let encoded = EncodedPeerId::new(*peer_id.as_bytes()); + peer_quotes.push((encoded, quote.clone())); + quotes_for_payment.push((quote, price)); + } + + let mut payment = SingleNodePayment::from_quotes(quotes_for_payment) + .expect("payment creation should succeed"); + + // Median is at index 2 (CLOSE_GROUP_SIZE=5, sorted by price) + let original_amount = payment.quotes[2].amount; + assert!( + !original_amount.is_zero(), + "Median quote (index 2) should have non-zero payment amount" + ); + + // 3. Tamper: reduce median payment to 1 atto (should be 3× median price) + payment.quotes[2].amount = Amount::from(1u64); + + // 4. Pay on-chain with the reduced amount — the contract records whatever + // amount is sent, it only validates amounts in verifyPayment() + let wallet = client.wallet().expect("wallet should be set"); + let tx_hashes = payment + .pay(wallet) + .await + .expect("on-chain payment should succeed (contract accepts any amount in payForQuotes)"); + + assert!( + !tx_hashes.is_empty(), + "Should have at least one tx hash for the 1-atto payment" + ); + + // 5. Build proof with real ML-DSA-65 signed quotes but underpaid tx + let proof = PaymentProof { + proof_of_payment: ProofOfPayment { peer_quotes }, + tx_hashes, + }; + let proof_bytes = serialize_single_node_proof(&proof).expect("serialize proof"); + + // 6. PUT should be REJECTED — node's verifyPayment detects 1 atto < 3× expected + let result = client + .chunk_put_with_proof(content, proof_bytes, &target_peer, &[]) + .await; + + assert!( + result.is_err(), + "PUT with underpayment (1 atto instead of {original_amount}) should be rejected" + ); + + drop(client); + testnet.teardown().await; +} + +// ─── Test 10: Underpayment — Pay Half the Required Amount ─────────────────── +// +// Verifies the boundary more closely: pays roughly half the expected 3× median +// price. The contract should still reject this because the amount is below the +// 3× threshold, even though it's not trivially small like 1 atto. + +#[tokio::test(flavor = "multi_thread")] +#[serial] +async fn test_attack_underpayment_half_price() { + let (client, testnet) = setup().await; + + let content = Bytes::from("half-price underpayment attack test data"); + let address = compute_address(&content); + let data_size = content.len() as u64; + + let quotes = client + .get_store_quotes(&address, data_size, 0) + .await + .expect("quote collection should succeed"); + + let target_peer = quotes.first().expect("should have quotes").0; + + let mut peer_quotes = Vec::with_capacity(quotes.len()); + let mut quotes_for_payment = Vec::with_capacity(quotes.len()); + for (peer_id, _addrs, quote, price) in quotes { + let encoded = EncodedPeerId::new(*peer_id.as_bytes()); + peer_quotes.push((encoded, quote.clone())); + quotes_for_payment.push((quote, price)); + } + + let mut payment = SingleNodePayment::from_quotes(quotes_for_payment) + .expect("payment creation should succeed"); + + // Halve the median payment (3× → ~1.5×) + let original_amount = payment.quotes[2].amount; + let half_amount = original_amount / Amount::from(2u64); + assert!( + !half_amount.is_zero(), + "Half of original amount should still be non-zero" + ); + payment.quotes[2].amount = half_amount; + + let wallet = client.wallet().expect("wallet should be set"); + let tx_hashes = payment + .pay(wallet) + .await + .expect("on-chain payment should succeed"); + + let proof = PaymentProof { + proof_of_payment: ProofOfPayment { peer_quotes }, + tx_hashes, + }; + let proof_bytes = serialize_single_node_proof(&proof).expect("serialize proof"); + + let result = client + .chunk_put_with_proof(content, proof_bytes, &target_peer, &[]) + .await; + + assert!( + result.is_err(), + "PUT with half-price payment ({half_amount} instead of {original_amount}) should be rejected" + ); + + drop(client); + testnet.teardown().await; +} diff --git a/ant-core/tests/support/mod.rs b/ant-core/tests/support/mod.rs index 5748a61..a808c27 100644 --- a/ant-core/tests/support/mod.rs +++ b/ant-core/tests/support/mod.rs @@ -153,20 +153,12 @@ impl MiniTestnet { sleep(Duration::from_millis(500)).await; } - // Approve token spend for data payments contract - let data_payments_address = evm_network.data_payments_address(); + // Approve token spend for the unified payment vault contract + let vault_address = evm_network.payment_vault_address(); wallet - .approve_to_spend_tokens(*data_payments_address, evmlib::common::U256::MAX) + .approve_to_spend_tokens(*vault_address, evmlib::common::U256::MAX) .await - .expect("approve data payment token spend"); - - // Approve token spend for merkle payments contract (if deployed) - if let Some(merkle_address) = evm_network.merkle_payments_address() { - wallet - .approve_to_spend_tokens(*merkle_address, evmlib::common::U256::MAX) - .await - .expect("approve merkle payment token spend"); - } + .expect("approve payment vault token spend"); Self { nodes, From d22458aa3ea6633e4d43f54cb708b51fabacf7d8 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 2 Apr 2026 09:43:07 +0200 Subject: [PATCH 2/8] fix: update ant-node and evmlib to fix payment verification failure The verifier was calling verifyPayment with original quote prices from ProofOfPayment::digest(), but the SingleNode model pays 3x to the median and 0 to others. The contract does exact amount matching, so all 5 quotes failed verification. The updated ant-node reconstructs actual paid amounts via SingleNodePayment::from_quotes() before calling verifyPayment. - ant-node: 08bb16f5 -> f283b9ac - evmlib: f4cdd45e -> 56148ce8 Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26a318f..9c69a5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -900,7 +900,7 @@ dependencies = [ [[package]] name = "ant-node" version = "0.9.0" -source = "git+https://github.com/WithAutonomi/ant-node.git?branch=feat%2Fadapt-evmlib-payment-vault#08bb16f5e240d0c963c36afebd1e1b6c43368d81" +source = "git+https://github.com/WithAutonomi/ant-node.git?branch=feat%2Fadapt-evmlib-payment-vault#f283b9ac333b1a4a9251a3ece57c9302b24078f9" dependencies = [ "aes-gcm-siv", "blake3", @@ -2415,7 +2415,7 @@ dependencies = [ [[package]] name = "evmlib" version = "0.5.0" -source = "git+https://github.com/WithAutonomi/evmlib.git?branch=refactor%2Funify-payment-vault-v2#f4cdd45ea76a98ced0a416794c92b9b4bc2da224" +source = "git+https://github.com/WithAutonomi/evmlib.git?branch=refactor%2Funify-payment-vault-v2#56148ce8751bf0a1f33817573d6501ab6c4e91d3" dependencies = [ "alloy", "ant-merkle", From b7ac3b3b023feef2dc724c41c90d72ab115d119c Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 2 Apr 2026 12:55:06 +0200 Subject: [PATCH 3/8] fix: target median peer in underpayment security tests Both underpayment tests were sending the PUT to quotes.first() (arbitrary peer) instead of the median peer. Since verify() only checks the target node's own payment, non-median nodes see expected=0, on-chain=0 and pass verification regardless of median tampering. Now we resolve the median peer via rewards_address after from_quotes() sorts internally. Co-Authored-By: Claude Opus 4.6 (1M context) --- ant-core/tests/e2e_security.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/ant-core/tests/e2e_security.rs b/ant-core/tests/e2e_security.rs index 42f0256..9bf23c1 100644 --- a/ant-core/tests/e2e_security.rs +++ b/ant-core/tests/e2e_security.rs @@ -12,7 +12,7 @@ use ant_node::core::PeerId; use ant_node::payment::{serialize_single_node_proof, PaymentProof, SingleNodePayment}; use bytes::Bytes; use evmlib::common::{Amount, TxHash}; -use evmlib::{EncodedPeerId, ProofOfPayment}; +use evmlib::{EncodedPeerId, ProofOfPayment, RewardsAddress}; use serial_test::serial; use std::sync::Arc; use support::MiniTestnet; @@ -382,7 +382,12 @@ async fn test_attack_underpayment_single_node() { .await .expect("quote collection should succeed"); - let target_peer = quotes.first().expect("should have quotes").0; + // Save (PeerId, RewardsAddress) mapping before consuming quotes — needed + // to target the median peer after from_quotes() sorts internally. + let peer_by_rewards: Vec<(PeerId, RewardsAddress)> = quotes + .iter() + .map(|(pid, _, q, _)| (*pid, q.rewards_address)) + .collect(); // 2. Build SingleNodePayment normally (sorts by price, median gets 3×) let mut peer_quotes = Vec::with_capacity(quotes.len()); @@ -396,8 +401,16 @@ async fn test_attack_underpayment_single_node() { let mut payment = SingleNodePayment::from_quotes(quotes_for_payment) .expect("payment creation should succeed"); - // Median is at index 2 (CLOSE_GROUP_SIZE=5, sorted by price) + // Median is at index 2 (CLOSE_GROUP_SIZE=5, sorted by price). + // Target the median peer specifically — it's the only one that receives + // payment, so only it will detect the amount mismatch in verifyPayment(). let original_amount = payment.quotes[2].amount; + let median_rewards = payment.quotes[2].rewards_address; + let target_peer = peer_by_rewards + .iter() + .find(|(_, addr)| *addr == median_rewards) + .expect("median rewards address must match a quoted peer") + .0; assert!( !original_amount.is_zero(), "Median quote (index 2) should have non-zero payment amount" @@ -460,7 +473,10 @@ async fn test_attack_underpayment_half_price() { .await .expect("quote collection should succeed"); - let target_peer = quotes.first().expect("should have quotes").0; + let peer_by_rewards: Vec<(PeerId, RewardsAddress)> = quotes + .iter() + .map(|(pid, _, q, _)| (*pid, q.rewards_address)) + .collect(); let mut peer_quotes = Vec::with_capacity(quotes.len()); let mut quotes_for_payment = Vec::with_capacity(quotes.len()); @@ -473,8 +489,15 @@ async fn test_attack_underpayment_half_price() { let mut payment = SingleNodePayment::from_quotes(quotes_for_payment) .expect("payment creation should succeed"); - // Halve the median payment (3× → ~1.5×) + // Halve the median payment (3× → ~1.5×). + // Target the median peer — only it verifies the amount it received. let original_amount = payment.quotes[2].amount; + let median_rewards = payment.quotes[2].rewards_address; + let target_peer = peer_by_rewards + .iter() + .find(|(_, addr)| *addr == median_rewards) + .expect("median rewards address must match a quoted peer") + .0; let half_amount = original_amount / Amount::from(2u64); assert!( !half_amount.is_zero(), From 0ee36ac1b568c607ff8f46247157d1d7d1713f6a Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 2 Apr 2026 12:56:16 +0200 Subject: [PATCH 4/8] chore: update Cargo.lock dependencies Updated `ant-node` and `evmlib` to newer commits and adjusted dependency versions in `Cargo.lock` for compatibility. Removed redundant `windows` package versions for cleanup. --- Cargo.lock | 62 ++++++++++++------------------------------------------ 1 file changed, 14 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c69a5f..1b21bb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -900,7 +900,7 @@ dependencies = [ [[package]] name = "ant-node" version = "0.9.0" -source = "git+https://github.com/WithAutonomi/ant-node.git?branch=feat%2Fadapt-evmlib-payment-vault#f283b9ac333b1a4a9251a3ece57c9302b24078f9" +source = "git+https://github.com/WithAutonomi/ant-node.git?branch=feat%2Fadapt-evmlib-payment-vault#26d1fe6d3ccb1e917c4259d4f46ec51f02b11df9" dependencies = [ "aes-gcm-siv", "blake3", @@ -919,6 +919,7 @@ dependencies = [ "lru", "objc2", "objc2-foundation", + "page_size", "parking_lot", "postcard", "rand 0.8.5", @@ -1765,7 +1766,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -2414,8 +2415,8 @@ dependencies = [ [[package]] name = "evmlib" -version = "0.5.0" -source = "git+https://github.com/WithAutonomi/evmlib.git?branch=refactor%2Funify-payment-vault-v2#56148ce8751bf0a1f33817573d6501ab6c4e91d3" +version = "0.6.0" +source = "git+https://github.com/WithAutonomi/evmlib.git?branch=refactor%2Funify-payment-vault-v2#dacee4d9688d38709417847776c45852641a20b0" dependencies = [ "alloy", "ant-merkle", @@ -3108,7 +3109,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "system-configuration 0.7.0", "tokio", "tower-service", @@ -3128,7 +3129,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -4329,7 +4330,7 @@ dependencies = [ "quinn-udp 0.5.14", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -4367,7 +4368,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] @@ -4380,7 +4381,7 @@ checksum = "76150b617afc75e6e21ac5f39bc196e80b65415ae48d62dbef8e2519d040ce42" dependencies = [ "cfg_aliases", "libc", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", "windows-sys 0.61.2", ] @@ -6612,7 +6613,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -6627,7 +6628,7 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core 0.58.0", + "windows-core", "windows-targets 0.52.6", ] @@ -6637,26 +6638,13 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", + "windows-implement", + "windows-interface", "windows-result 0.2.0", "windows-strings 0.1.0", "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - [[package]] name = "windows-implement" version = "0.58.0" @@ -6668,17 +6656,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "windows-interface" version = "0.58.0" @@ -6690,17 +6667,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "windows-link" version = "0.2.1" From 8255db70410c98920c59f482ad74534469b3663f Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 2 Apr 2026 13:35:28 +0200 Subject: [PATCH 5/8] chore: remove unused constants and update dependency versions in Cargo.lock Removed `TEST_INITIAL_RECORDS` constant due to redundancy. Updated `windows-sys` and `socket2` dependencies in `Cargo.lock` for compatibility. --- Cargo.lock | 12 ++++++------ ant-core/tests/support/mod.rs | 6 ++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b21bb8..d28a6b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1766,7 +1766,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -3109,7 +3109,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "system-configuration 0.7.0", "tokio", "tower-service", @@ -4330,7 +4330,7 @@ dependencies = [ "quinn-udp 0.5.14", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -4368,7 +4368,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] @@ -4381,7 +4381,7 @@ checksum = "76150b617afc75e6e21ac5f39bc196e80b65415ae48d62dbef8e2519d040ce42" dependencies = [ "cfg_aliases", "libc", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.61.2", ] @@ -6613,7 +6613,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/ant-core/tests/support/mod.rs b/ant-core/tests/support/mod.rs index a808c27..c12137c 100644 --- a/ant-core/tests/support/mod.rs +++ b/ant-core/tests/support/mod.rs @@ -44,8 +44,6 @@ const STABILIZATION_TIMEOUT_SECS: u64 = 180; const TEST_REWARDS_ADDRESS: [u8; 20] = [0x01; 20]; /// Max records for quoting metrics. const TEST_MAX_RECORDS: usize = 1280; -/// Initial records for quoting metrics. -const TEST_INITIAL_RECORDS: usize = 1000; pub struct TestNode { pub p2p_node: Option>, @@ -217,8 +215,8 @@ impl MiniTestnet { let storage_config = LmdbStorageConfig { root_dir: data_dir.to_path_buf(), verify_on_read: true, - max_chunks: 0, max_map_size: 0, + disk_reserve: 0, }; let storage = Arc::new( LmdbStorage::new(storage_config) @@ -242,7 +240,7 @@ impl MiniTestnet { local_rewards_address: rewards_address, }; let payment_verifier = Arc::new(PaymentVerifier::new(payment_config)); - let metrics_tracker = QuotingMetricsTracker::new(TEST_MAX_RECORDS, TEST_INITIAL_RECORDS); + let metrics_tracker = QuotingMetricsTracker::new(TEST_MAX_RECORDS); let mut quote_generator = QuoteGenerator::new(rewards_address, metrics_tracker); // Wire ML-DSA-65 signing so quotes are properly signed and verifiable From d01c3ea0b45cec908667a0163aad99258f7744e7 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 2 Apr 2026 15:32:59 +0200 Subject: [PATCH 6/8] chore: update ant-node dependency in Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index d28a6b8..0867995 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -900,7 +900,7 @@ dependencies = [ [[package]] name = "ant-node" version = "0.9.0" -source = "git+https://github.com/WithAutonomi/ant-node.git?branch=feat%2Fadapt-evmlib-payment-vault#26d1fe6d3ccb1e917c4259d4f46ec51f02b11df9" +source = "git+https://github.com/WithAutonomi/ant-node.git?branch=feat%2Fadapt-evmlib-payment-vault#1ca840ced9f97c9f1a54474319a9995d8b0d26c9" dependencies = [ "aes-gcm-siv", "blake3", From 6a38018da23d3ad3c70870f6b1ee83a945346c85 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 2 Apr 2026 16:05:56 +0200 Subject: [PATCH 7/8] fix: add missing price field to prepared batch chunks Include `price` in `PreparedChunk` to ensure complete amount details are preserved during batch processing. --- ant-core/src/data/client/batch.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ant-core/src/data/client/batch.rs b/ant-core/src/data/client/batch.rs index 92930bd..0528d9a 100644 --- a/ant-core/src/data/client/batch.rs +++ b/ant-core/src/data/client/batch.rs @@ -421,6 +421,7 @@ mod tests { quote_hash: QuoteHash::from([i as u8 + 1; 32]), rewards_address: RewardsAddress::new([i as u8 + 10; 20]), amount: Amount::from(amounts[i]), + price: Amount::from(amounts[i]), }); PreparedChunk { From 8287da6e464f254a12ae7119494ba605b1f10b21 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 2 Apr 2026 17:36:37 +0200 Subject: [PATCH 8/8] chore: update evmlib to 0.7 and adjust Cargo.lock dependencies --- Cargo.lock | 7 ++++--- ant-core/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0867995..ae98151 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -900,7 +900,7 @@ dependencies = [ [[package]] name = "ant-node" version = "0.9.0" -source = "git+https://github.com/WithAutonomi/ant-node.git?branch=feat%2Fadapt-evmlib-payment-vault#1ca840ced9f97c9f1a54474319a9995d8b0d26c9" +source = "git+https://github.com/WithAutonomi/ant-node.git?branch=feat%2Fadapt-evmlib-payment-vault#41511ab2339a0e2bd3a1904c50e5cc63de9283bc" dependencies = [ "aes-gcm-siv", "blake3", @@ -2415,8 +2415,9 @@ dependencies = [ [[package]] name = "evmlib" -version = "0.6.0" -source = "git+https://github.com/WithAutonomi/evmlib.git?branch=refactor%2Funify-payment-vault-v2#dacee4d9688d38709417847776c45852641a20b0" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af67fe5494790b75d91fed4c5dd3215098e5adf071f73f40a238d199116c75ac" dependencies = [ "alloy", "ant-merkle", diff --git a/ant-core/Cargo.toml b/ant-core/Cargo.toml index cafbdd2..53eb188 100644 --- a/ant-core/Cargo.toml +++ b/ant-core/Cargo.toml @@ -24,7 +24,7 @@ zip = "2" tower-http = { version = "0.6.8", features = ["cors"] } # Data operations -evmlib = { git = "https://github.com/WithAutonomi/evmlib.git", branch = "refactor/unify-payment-vault-v2" } +evmlib = "0.7" xor_name = "5" self_encryption = "0.35.0" futures = "0.3"