diff --git a/Cargo.lock b/Cargo.lock index b67935b1..3f524ccb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -781,6 +781,7 @@ dependencies = [ "ic-stable-structures 0.7.2", "icrc-cbor", "icrc-ledger-types", + "itertools 0.14.0", "minicbor", "num-traits", "phantom_newtype", diff --git a/Cargo.toml b/Cargo.toml index d0632db9..b5561c7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ ic-test-utilities-load-wasm = { git = "https://github.com/dfinity/ic", tag = "re icrc-cbor = "0.1.0" icrc-ledger-client-cdk = "0.1.4" icrc-ledger-types = "0.1.12" +itertools = "0.14.0" minicbor = "0.19.1" num-traits = "0.2.19" phantom_newtype = "0.2.0" diff --git a/integration_tests/tests/tests.rs b/integration_tests/tests/tests.rs index 1ebde00c..c0214c33 100644 --- a/integration_tests/tests/tests.rs +++ b/integration_tests/tests/tests.rs @@ -941,13 +941,11 @@ mod consolidation_tests { "Expected SubmittedTransaction event. Events: {events_after:?}" ); - // Verify the consolidated deposits match the deposit amount for event in &events_after { - if let EventType::ConsolidatedDeposits { deposits } = &event.payload { - let total: Lamport = deposits.iter().map(|(_, amount)| amount).sum(); - assert_eq!( - total, DEPOSIT_AMOUNT, - "Consolidated amount should match the deposit amount" + if let EventType::ConsolidatedDeposits { mint_indices } = &event.payload { + assert!( + !mint_indices.is_empty(), + "ConsolidatedDeposits should contain at least one mint index" ); } } diff --git a/libs/types-internal/src/event.rs b/libs/types-internal/src/event.rs index 18b08fbb..5fa31b89 100644 --- a/libs/types-internal/src/event.rs +++ b/libs/types-internal/src/event.rs @@ -87,9 +87,8 @@ pub enum EventType { /// Deposited funds from user deposit accounts have been consolidated /// into the minter's main account. ConsolidatedDeposits { - /// The deposit accounts from which funds were consolidated - /// and the amount consolidated from each account. - deposits: Vec<(Account, Lamport)>, + /// The mint indices of the deposits that were consolidated. + mint_indices: Vec, }, /// A withdrawal transaction was signed and is ready to be sent to the network. SentWithdrawalTransaction { diff --git a/minter/Cargo.toml b/minter/Cargo.toml index c3912118..b54f08ab 100644 --- a/minter/Cargo.toml +++ b/minter/Cargo.toml @@ -31,6 +31,7 @@ ic-http-types = { workspace = true } ic-stable-structures = { workspace = true } icrc-cbor = { workspace = true } icrc-ledger-types = { workspace = true } +itertools = { workspace = true } minicbor = { workspace = true, features = ["derive"] } num-traits = { workspace = true } phantom_newtype = { workspace = true } diff --git a/minter/cksol-minter.did b/minter/cksol-minter.did index d3c6c514..b0640076 100644 --- a/minter/cksol-minter.did +++ b/minter/cksol-minter.did @@ -291,9 +291,8 @@ type EventType = variant { // Deposited funds from user deposit accounts have been consolidated // into the minter's main account. ConsolidatedDeposits : record { - // The deposit accounts from which funds were consolidated - // and the amount consolidated from each account. - deposits: vec record { Account; Lamport }; + // The mint indices of the deposits that were consolidated. + mint_indices: vec nat64; }; // A withdrawal transaction was signed and is ready to be sent to the network. SentWithdrawalTransaction : record { diff --git a/minter/src/consolidate/mod.rs b/minter/src/consolidate/mod.rs index b3a9924d..8b166c92 100644 --- a/minter/src/consolidate/mod.rs +++ b/minter/src/consolidate/mod.rs @@ -1,6 +1,7 @@ use crate::{ address::{derivation_path, derive_public_key, lazy_get_schnorr_master_key}, guard::TimerGuard, + numeric::LedgerMintIndex, runtime::CanisterRuntime, sol_transfer::{CreateTransferError, MAX_SIGNATURES, create_signed_transfer_transaction}, state::{TaskType, audit::process_event, event::EventType, mutate_state, read_state}, @@ -9,10 +10,12 @@ use crate::{ use canlog::log; use cksol_types_internal::log::Priority; use icrc_ledger_types::icrc1::account::Account; +use itertools::Itertools; use sol_rpc_types::{Lamport, Slot}; use solana_address::Address; use solana_hash::Hash; use solana_signature::Signature; +use std::collections::BTreeMap; use std::time::Duration; use thiserror::Error; @@ -21,7 +24,7 @@ mod tests; pub const DEPOSIT_CONSOLIDATION_DELAY: Duration = Duration::from_mins(10); const MAX_CONCURRENT_TRANSACTIONS: usize = 10; -const MAX_TRANSFERS_PER_CONSOLIDATION: usize = MAX_SIGNATURES as usize - 1; +pub(crate) const MAX_TRANSFERS_PER_CONSOLIDATION: usize = MAX_SIGNATURES as usize - 1; pub async fn consolidate_deposits(runtime: R) { let _guard = match TimerGuard::new(TaskType::DepositConsolidation) { @@ -29,22 +32,18 @@ pub async fn consolidate_deposits(runtime: R) { Err(_) => return, }; - if read_state(|state| state.funds_to_consolidate().is_empty()) { - return; - } - - let funds_to_consolidate: Vec<_> = read_state(|state| { - state - .funds_to_consolidate() - .clone() + let consolidation_rounds: Vec> = + read_state(|s| group_deposits_by_account(s.deposits_to_consolidate())) .into_iter() - .collect::>() .chunks(MAX_TRANSFERS_PER_CONSOLIDATION) - .map(|c| c.to_vec()) - .collect() - }); + .into_iter() + .map(Iterator::collect) + .collect(); - for round in funds_to_consolidate.chunks(MAX_CONCURRENT_TRANSACTIONS) { + for round in &consolidation_rounds + .into_iter() + .chunks(MAX_CONCURRENT_TRANSACTIONS) + { let recent_blockhash = match get_recent_blockhash(&runtime).await { Ok(blockhash) => blockhash, Err(e) => { @@ -61,28 +60,27 @@ pub async fn consolidate_deposits(runtime: R) { return; } }; - let _ = futures::future::join_all(round.iter().cloned().map(|funds| { - try_submit_consolidation_transaction(runtime.clone(), funds, slot, recent_blockhash) + + futures::future::join_all(round.map(async |funds| { + match submit_consolidation_transaction(&runtime, funds, slot, recent_blockhash).await { + Ok(sig) => log!(Priority::Info, "Submitted consolidation transaction {sig}"), + Err(e) => log!(Priority::Info, "Deposit consolidation failed: {e}"), + } })) .await; } } -async fn try_submit_consolidation_transaction( - runtime: R, - funds_to_consolidate: Vec<(Account, Lamport)>, - slot: Slot, - recent_blockhash: Hash, -) -> Option { - match submit_consolidation_transaction(&runtime, funds_to_consolidate, slot, recent_blockhash) - .await - { - Ok(signature) => Some(signature), - Err(e) => { - log!(Priority::Info, "Deposit consolidation failed: {e}"); - None - } +fn group_deposits_by_account( + deposits: &BTreeMap, +) -> Vec<(Account, (Lamport, Vec))> { + let mut by_account: BTreeMap)> = BTreeMap::new(); + for (mint_index, (account, lamport)) in deposits { + let entry = by_account.entry(*account).or_default(); + entry.0 += lamport; + entry.1.push(*mint_index); } + by_account.into_iter().collect() } #[derive(Debug, Error)] @@ -95,7 +93,7 @@ enum ConsolidationError { async fn submit_consolidation_transaction( runtime: &R, - funds_to_consolidate: Vec<(Account, Lamport)>, + funds_to_consolidate: Vec<(Account, (Lamport, Vec))>, slot: Slot, recent_blockhash: Hash, ) -> Result { @@ -108,9 +106,13 @@ async fn submit_consolidation_transaction( derive_public_key(&master_key, derivation_path(&minter_account)).serialize_raw(), ); + let sources: Vec<(Account, Lamport)> = funds_to_consolidate + .iter() + .map(|(account, (lamport, _))| (*account, *lamport)) + .collect(); let (transaction, signers) = create_signed_transfer_transaction( minter_account, - &funds_to_consolidate, + &sources, minter_address, recent_blockhash, &runtime.signer(), @@ -119,15 +121,17 @@ async fn submit_consolidation_transaction( let signature = transaction.signatures[0]; let message = transaction.message.clone(); + let mint_indices: Vec = funds_to_consolidate + .into_iter() + .flat_map(|(_, (_, indices))| indices) + .collect(); // Record events before trying to submit the transaction to ensure we don't // resubmit the same transaction twice in case of a failed submission. mutate_state(|state| { process_event( state, - EventType::ConsolidatedDeposits { - deposits: funds_to_consolidate, - }, + EventType::ConsolidatedDeposits { mint_indices }, runtime, ) }); diff --git a/minter/src/consolidate/tests.rs b/minter/src/consolidate/tests.rs index 629f48c6..d5a7bd2e 100644 --- a/minter/src/consolidate/tests.rs +++ b/minter/src/consolidate/tests.rs @@ -1,5 +1,6 @@ use super::{MAX_TRANSFERS_PER_CONSOLIDATION, consolidate_deposits}; use crate::{ + numeric::LedgerMintIndex, state::{ TaskType, audit::process_event, @@ -22,7 +23,7 @@ type BlockResult = MultiRpcResult; type SendTransactionResult = MultiRpcResult; #[tokio::test] -async fn should_return_early_if_no_funds_to_consolidate() { +async fn should_return_early_if_no_deposits_to_consolidate() { setup(); consolidate_deposits(TestCanisterRuntime::new()).await; @@ -41,9 +42,10 @@ async fn should_return_early_if_task_already_active() { consolidate_deposits(TestCanisterRuntime::new()).await; - // Only AcceptedDeposit event from setup, no consolidation events + // Only events from setup EventsAssert::from_recorded() .expect_event(|e| assert_matches!(e, EventType::AcceptedDeposit { .. })) + .expect_event(|e| assert_matches!(e, EventType::Minted { .. })) .assert_no_more_events(); } @@ -61,9 +63,10 @@ async fn should_return_early_if_fetching_blockhash_fails() { consolidate_deposits(runtime).await; - // Only AcceptedDeposit event from setup, no consolidation events + // Only events from setup EventsAssert::from_recorded() .expect_event(|e| assert_matches!(e, EventType::AcceptedDeposit { .. })) + .expect_event(|e| assert_matches!(e, EventType::Minted { .. })) .assert_no_more_events(); } @@ -75,7 +78,6 @@ async fn should_submit_single_consolidation_request() { let deposit_amount = 1_000_000_000_u64; add_funds_to_consolidate(vec![(deposit_account, deposit_amount)]); - // Fee payer signature is first in the transaction and becomes the transaction ID let fee_payer_signature = Signature::from([0x11; 64]); let slot = 100; let runtime = TestCanisterRuntime::new() @@ -88,27 +90,16 @@ async fn should_submit_single_consolidation_request() { .add_stub_response(SendTransactionResult::Consistent(Ok( fee_payer_signature.into() ))) - // Two signatures needed: fee payer (minter) + source (deposit account) .add_signature(fee_payer_signature.into()) .add_signature([0x22; 64]); consolidate_deposits(runtime).await; EventsAssert::from_recorded() - .expect_event(|e| { - assert_matches!( - e, - EventType::AcceptedDeposit { - deposit_id, - deposit_amount: amount, - .. - } if deposit_id.account == deposit_account && amount == deposit_amount - ) - }) - .expect_event(|e| { - assert_matches!(e, EventType::ConsolidatedDeposits { deposits } - if deposits == vec![(deposit_account, deposit_amount)] - ) + .expect_event(|e| assert_matches!(e, EventType::AcceptedDeposit { .. })) + .expect_event(|e| assert_matches!(e, EventType::Minted { .. })) + .expect_event_eq(EventType::ConsolidatedDeposits { + mint_indices: vec![LedgerMintIndex::from(0_u64)], }) .expect_event(|e| { assert_matches!(e, EventType::SubmittedTransaction { signature, slot: event_slot, .. } @@ -135,7 +126,7 @@ async fn should_record_events_even_if_transaction_submission_fails() { .add_stub_response(BlockResult::Consistent(Ok(block()))) // get_slot call .add_stub_response(SlotResult::Consistent(Ok(slot))) - // Transaction submission call fails (e.g. due to inconsistent results) + // Transaction submission fails .add_stub_response(SendTransactionResult::Inconsistent(vec![])) .add_signature(fee_payer_signature.into()) .add_signature([0x22; 64]); @@ -143,15 +134,10 @@ async fn should_record_events_even_if_transaction_submission_fails() { consolidate_deposits(runtime).await; EventsAssert::from_recorded() - .expect_event(|e| { - assert_matches!(e, EventType::AcceptedDeposit { deposit_id, deposit_amount: amount, ..} - if deposit_id.account == deposit_account && amount == deposit_amount - ) - }) - .expect_event(|e| { - assert_matches!(e, EventType::ConsolidatedDeposits { deposits } - if deposits == vec![(deposit_account, deposit_amount)] - ) + .expect_event(|e| assert_matches!(e, EventType::AcceptedDeposit { .. })) + .expect_event(|e| assert_matches!(e, EventType::Minted { .. })) + .expect_event_eq(EventType::ConsolidatedDeposits { + mint_indices: vec![LedgerMintIndex::from(0_u64)], }) .expect_event(|e| { assert_matches!(e, EventType::SubmittedTransaction { signature, slot: event_slot, .. } @@ -171,11 +157,8 @@ async fn should_submit_multiple_consolidation_batches() { .collect(); add_funds_to_consolidate(funds.clone()); - // Calculate expected batch sizes, i.e. the number of transfers per transaction submitted const BATCH_1_SIZE: usize = MAX_TRANSFERS_PER_CONSOLIDATION; - const BATCH_2_SIZE: usize = NUM_DEPOSITS - BATCH_1_SIZE; - // Fee payer signatures (first signature in each batch) become transaction IDs let fee_payer_signature_1 = Signature::from([0; 64]); let fee_payer_signature_2 = Signature::from([(BATCH_1_SIZE + 1) as u8; 64]); let slot = 100; @@ -194,7 +177,6 @@ async fn should_submit_multiple_consolidation_batches() { fee_payer_signature_2.into() ))); - // Signatures needed: 2 x for fee payer (1 for each batch) + 1x for each source account for i in 0..(2 + NUM_DEPOSITS) { runtime = runtime.add_signature([i as u8; 64]); } @@ -202,20 +184,18 @@ async fn should_submit_multiple_consolidation_batches() { consolidate_deposits(runtime).await; let mut events_assert = EventsAssert::from_recorded(); - // AcceptedDeposit events from setup - for (account, amount) in funds.iter().cloned() { - events_assert = events_assert.expect_event(move |e| { - assert_matches!(e, EventType::AcceptedDeposit { deposit_id, deposit_amount, .. } - if deposit_id.account == account && deposit_amount == amount - ) - }); + // Events from setup + for _ in 0..NUM_DEPOSITS { + events_assert = events_assert + .expect_event(|e| assert_matches!(e, EventType::AcceptedDeposit { .. })) + .expect_event(|e| assert_matches!(e, EventType::Minted { .. })); } // Batch 1: events_assert = events_assert - .expect_event(|e| { - assert_matches!(e, EventType::ConsolidatedDeposits { deposits } - if deposits.len() == BATCH_1_SIZE - ) + .expect_event_eq(EventType::ConsolidatedDeposits { + mint_indices: (0..BATCH_1_SIZE as u64) + .map(LedgerMintIndex::from) + .collect(), }) .expect_event(|e| { assert_matches!(e, EventType::SubmittedTransaction { signature, slot: event_slot, .. } @@ -224,10 +204,10 @@ async fn should_submit_multiple_consolidation_batches() { }); // Batch 2: events_assert = events_assert - .expect_event(|e| { - assert_matches!(e, EventType::ConsolidatedDeposits { deposits } - if deposits.len() == BATCH_2_SIZE - ) + .expect_event_eq(EventType::ConsolidatedDeposits { + mint_indices: (BATCH_1_SIZE as u64..NUM_DEPOSITS as u64) + .map(LedgerMintIndex::from) + .collect(), }) .expect_event(|e| { assert_matches!(e, EventType::SubmittedTransaction { signature, slot: event_slot, .. } @@ -237,6 +217,49 @@ async fn should_submit_multiple_consolidation_batches() { events_assert.assert_no_more_events(); } +#[tokio::test] +async fn should_consolidate_multiple_deposits_to_same_account_in_single_transfer() { + setup(); + + let deposit_account = account(0); + // Two deposits to the same account + add_funds_to_consolidate(vec![ + (deposit_account, 500_000_000), + (deposit_account, 300_000_000), + ]); + + let fee_payer_signature = Signature::from([0x11; 64]); + let slot = 100; + let runtime = TestCanisterRuntime::new() + .with_increasing_time() + .add_stub_response(SlotResult::Consistent(Ok(slot))) + .add_stub_response(BlockResult::Consistent(Ok(block()))) + .add_stub_response(SlotResult::Consistent(Ok(slot))) + .add_stub_response(SendTransactionResult::Consistent(Ok( + fee_payer_signature.into() + ))) + // Only TWO signatures: fee payer + one source account (not two) + .add_signature(fee_payer_signature.into()) + .add_signature([0x22; 64]); + + consolidate_deposits(runtime).await; + + EventsAssert::from_recorded() + .expect_event(|e| assert_matches!(e, EventType::AcceptedDeposit { .. })) + .expect_event(|e| assert_matches!(e, EventType::Minted { .. })) + .expect_event(|e| assert_matches!(e, EventType::AcceptedDeposit { .. })) + .expect_event(|e| assert_matches!(e, EventType::Minted { .. })) + .expect_event_eq(EventType::ConsolidatedDeposits { + mint_indices: vec![LedgerMintIndex::from(0_u64), LedgerMintIndex::from(1_u64)], + }) + .expect_event(|e| { + assert_matches!(e, EventType::SubmittedTransaction { signature, .. } + if signature == fee_payer_signature + ) + }) + .assert_no_more_events(); +} + fn setup() { init_state(); init_schnorr_master_key(); @@ -255,6 +278,8 @@ fn add_funds_to_consolidate(funds: Vec<(Account, u64)>) { account, signature: Signature::from([i as u8; 64]), }; + let mint_block_index = LedgerMintIndex::from(i as u64); + let runtime = TestCanisterRuntime::new().with_increasing_time(); mutate_state(|state| { process_event( state, @@ -263,7 +288,17 @@ fn add_funds_to_consolidate(funds: Vec<(Account, u64)>) { deposit_amount: amount, amount_to_mint: amount - DEPOSIT_FEE, }, - &TestCanisterRuntime::new().with_increasing_time(), + &runtime, + ) + }); + mutate_state(|state| { + process_event( + state, + EventType::Minted { + deposit_id, + mint_block_index, + }, + &runtime, ) }); } diff --git a/minter/src/main.rs b/minter/src/main.rs index c57e5a27..352b172f 100644 --- a/minter/src/main.rs +++ b/minter/src/main.rs @@ -151,8 +151,10 @@ fn get_events( signers, slot, }, - EventType::ConsolidatedDeposits { deposits } => { - event::EventType::ConsolidatedDeposits { deposits } + EventType::ConsolidatedDeposits { mint_indices } => { + event::EventType::ConsolidatedDeposits { + mint_indices: mint_indices.iter().map(|idx| *idx.get()).collect(), + } } EventType::SentWithdrawalTransaction { transactions } => { event::EventType::SentWithdrawalTransaction { diff --git a/minter/src/state/audit.rs b/minter/src/state/audit.rs index 362ecac5..42b65ae7 100644 --- a/minter/src/state/audit.rs +++ b/minter/src/state/audit.rs @@ -49,8 +49,8 @@ fn apply_state_transition(state: &mut State, payload: &EventType) { } => { state.process_transaction_submitted(signature, transaction, signers, *slot); } - EventType::ConsolidatedDeposits { deposits } => { - state.process_consolidated_deposits(deposits); + EventType::ConsolidatedDeposits { mint_indices } => { + state.process_consolidated_deposits(mint_indices); } EventType::SentWithdrawalTransaction { transactions } => { for (burn_block_index, signature) in transactions { diff --git a/minter/src/state/event.rs b/minter/src/state/event.rs index f78c1ed4..f0f62fec 100644 --- a/minter/src/state/event.rs +++ b/minter/src/state/event.rs @@ -80,10 +80,9 @@ pub enum EventType { /// into the minter's main account. #[n(7)] ConsolidatedDeposits { - /// The deposit accounts from which funds were consolidated - /// and the amount consolidated from each account. - #[n(0)] - deposits: Vec<(Account, Lamport)>, + /// The mint indices of the deposits that were consolidated. + #[cbor(n(0), with = "cbor::mint_indices")] + mint_indices: Vec, }, /// A previously submitted transaction was resubmitted with a new signature. /// The transaction message and signers remain the same. diff --git a/minter/src/state/event/cbor/mod.rs b/minter/src/state/event/cbor/mod.rs index 220883c7..b19fb21d 100644 --- a/minter/src/state/event/cbor/mod.rs +++ b/minter/src/state/event/cbor/mod.rs @@ -95,6 +95,38 @@ pub mod burn_index_signature_vec { } } +pub mod mint_indices { + use crate::numeric::LedgerMintIndex; + use minicbor::{ + decode::{Decoder, Error}, + encode::{Encoder, Write}, + }; + + pub fn decode(d: &mut Decoder<'_>, _ctx: &mut Ctx) -> Result, Error> { + let len = d + .array()? + .ok_or_else(|| Error::message("expected definite array"))?; + let mut indices = Vec::with_capacity(len as usize); + for _ in 0..len { + let val: u64 = d.u64()?; + indices.push(LedgerMintIndex::from(val)); + } + Ok(indices) + } + + pub fn encode( + v: &Vec, + e: &mut Encoder, + _ctx: &mut Ctx, + ) -> Result<(), minicbor::encode::Error> { + e.array(v.len() as u64)?; + for idx in v { + e.u64(*idx.get())?; + } + Ok(()) + } +} + pub mod message { use minicbor::{ decode::{Decoder, Error}, diff --git a/minter/src/state/mod.rs b/minter/src/state/mod.rs index f21b3c97..4caa680d 100644 --- a/minter/src/state/mod.rs +++ b/minter/src/state/mod.rs @@ -9,7 +9,6 @@ use cksol_types_internal::{Ed25519KeyName, InitArgs, UpgradeArgs}; use ic_canister_runtime::Runtime; use ic_ed25519::PublicKey; use icrc_ledger_types::icrc1::account::Account; -use num_traits::Zero; use sol_rpc_client::SolRpcClient; use sol_rpc_types::{ConsensusStrategy, Lamport, RpcSources, Slot, SolanaCluster}; use solana_message::Message; @@ -89,7 +88,7 @@ pub struct State { minted_deposits: BTreeMap, pending_withdrawal_requests: BTreeMap, sent_withdrawal_requests: BTreeMap, - funds_to_consolidate: BTreeMap, + deposits_to_consolidate: BTreeMap, submitted_transactions: BTreeMap, active_tasks: BTreeSet, } @@ -144,8 +143,8 @@ impl State { self.update_balance_required_cycles } - pub fn funds_to_consolidate(&self) -> &BTreeMap { - &self.funds_to_consolidate + pub fn deposits_to_consolidate(&self) -> &BTreeMap { + &self.deposits_to_consolidate } pub fn submitted_transactions(&self) -> &BTreeMap { @@ -299,10 +298,6 @@ impl State { None, "Attempted to accept an already accepted deposit: {deposit_id:?}" ); - *self - .funds_to_consolidate - .entry(deposit_id.account) - .or_default() += deposit_amount; } fn process_quarantined_deposit(&mut self, deposit_id: &DepositId) { @@ -362,6 +357,14 @@ impl State { .unwrap_or_else(|| { panic!("Attempted to mint ckSOL for an unknown deposit: {deposit_id:?}") }); + assert_eq!( + self.deposits_to_consolidate.insert( + *mint_block_index, + (deposit_id.account, deposit.deposit_amount) + ), + None, + "Attempted to consolidate funds for an already consolidated mint index: {mint_block_index:?}", + ); assert_eq!( self.minted_deposits.insert( *deposit_id, @@ -417,23 +420,13 @@ impl State { ); } - fn process_consolidated_deposits(&mut self, deposits: &[(Account, Lamport)]) { - for (account, amount) in deposits { - let remaining = self - .funds_to_consolidate - .get_mut(account) + fn process_consolidated_deposits(&mut self, mint_indices: &[LedgerMintIndex]) { + for mint_index in mint_indices { + self.deposits_to_consolidate + .remove(mint_index) .unwrap_or_else(|| { - panic!("Attempted to consolidate funds for unknown account: {account:?}") + panic!("Attempted to consolidate funds for unknown mint index: {mint_index:?}") }); - *remaining = remaining.checked_sub(*amount).unwrap_or_else(|| { - panic!( - "Attempted to consolidate more funds than available for account {account:?}: \ - available {remaining}, requested {amount}" - ) - }); - if remaining.is_zero() { - self.funds_to_consolidate.remove(account); - } } } @@ -516,7 +509,7 @@ impl TryFrom for State { minted_deposits: BTreeMap::new(), pending_withdrawal_requests: BTreeMap::new(), sent_withdrawal_requests: BTreeMap::new(), - funds_to_consolidate: BTreeMap::new(), + deposits_to_consolidate: BTreeMap::new(), submitted_transactions: BTreeMap::new(), active_tasks: BTreeSet::new(), }; diff --git a/minter/src/state/tests.rs b/minter/src/state/tests.rs index 1d2e129b..a864159c 100644 --- a/minter/src/state/tests.rs +++ b/minter/src/state/tests.rs @@ -48,7 +48,7 @@ mod state_from_init_args { minted_deposits: BTreeMap::new(), pending_withdrawal_requests: BTreeMap::new(), sent_withdrawal_requests: BTreeMap::new(), - funds_to_consolidate: BTreeMap::new(), + deposits_to_consolidate: BTreeMap::new(), submitted_transactions: BTreeMap::new(), active_tasks: BTreeSet::new(), } diff --git a/minter/src/test_fixtures/mod.rs b/minter/src/test_fixtures/mod.rs index ec05b55a..4fcfe972 100644 --- a/minter/src/test_fixtures/mod.rs +++ b/minter/src/test_fixtures/mod.rs @@ -83,7 +83,7 @@ pub mod arb { use cksol_types_internal::{Ed25519KeyName, InitArgs, UpgradeArgs}; use icrc_ledger_types::icrc1::account::Account; use proptest::prelude::{Just, Strategy, any, prop, prop_oneof}; - use sol_rpc_types::{Lamport, Slot}; + use sol_rpc_types::Slot; use solana_address::Address; use solana_message::{Hash, Instruction, Message}; use solana_signature::Signature; @@ -283,8 +283,8 @@ pub mod arb { slot, } }), - prop::collection::vec((arb_account(), any::()), 1..10) - .prop_map(|deposits| EventType::ConsolidatedDeposits { deposits }), + prop::collection::vec(arb_ledger_mint_index(), 1..10) + .prop_map(|mint_indices| EventType::ConsolidatedDeposits { mint_indices }), prop::collection::vec((arb_ledger_burn_index(), arb_signature()), 1..10) .prop_map(|transactions| EventType::SentWithdrawalTransaction { transactions },), (arb_signature(), arb_signature(), any::()).prop_map(