diff --git a/signer/src/bitcoin/utxo.rs b/signer/src/bitcoin/utxo.rs index d71db6739..d159838c6 100644 --- a/signer/src/bitcoin/utxo.rs +++ b/signer/src/bitcoin/utxo.rs @@ -109,6 +109,70 @@ pub trait GetFees { fn get_fees(&self) -> Result, Error>; } +/// Filter out the deposit requests that do not meet the amount or fee requirements. +pub struct DepositFilter<'a> { + requests: &'a Vec, + minimum_fee: u64, + per_deposit_minimum: u64, + per_deposit_cap: u64, + max_mintable_cap: u64, + amount_to_mint: u64, +} + +impl<'a> DepositFilter<'a> { + /// Create a new [`DepositFilter`] instance. + pub fn new( + requests: &'a Vec, + minimum_fee: u64, + per_deposit_minimum: u64, + per_deposit_cap: u64, + max_mintable_cap: u64, + ) -> Self { + Self { + requests, + minimum_fee, + per_deposit_minimum, + per_deposit_cap, + max_mintable_cap, + amount_to_mint: 0, + } + } + + /// Validate deposit requests based on four constraints: + /// 1. The user's max fee must be >= our minimum required fee for deposits + /// (based on fixed deposit tx size) + /// 2. The deposit amount must be greater than or equal to the per-deposit minimum + /// 3. The deposit amount must be less than or equal to the per-deposit cap + /// 4. The total amount being minted must stay under the maximum allowed mintable amount + fn validate(&mut self, req: &'a DepositRequest) -> Option> { + let is_fee_valid = req.max_fee.min(req.amount) >= self.minimum_fee; + let is_above_per_deposit_minimum = req.amount >= self.per_deposit_minimum; + let is_within_per_deposit_cap = req.amount <= self.per_deposit_cap; + let is_within_max_mintable_cap = + if let Some(new_amount) = self.amount_to_mint.checked_add(req.amount) { + new_amount <= self.max_mintable_cap + } else { + false + }; + + if is_fee_valid + && is_above_per_deposit_minimum + && is_within_per_deposit_cap + && is_within_max_mintable_cap + { + self.amount_to_mint += req.amount; + Some(RequestRef::Deposit(req)) + } else { + None + } + } + + /// Filter out the deposit requests that do not meet the validation requirements. + pub fn filter_deposits(&mut self) -> impl Iterator> + '_ { + self.requests.iter().filter_map(|req| self.validate(req)) + } +} + /// Summary of the Signers' UTXO and information necessary for /// constructing their next UTXO. #[derive(Debug, Clone, Copy)] @@ -172,32 +236,15 @@ impl SbtcRequests { }) .map(RequestRef::Withdrawal); - // Filter deposit requests based on two constraints: - // 1. The user's max fee must be >= our minimum required fee for deposits - // (based on fixed deposit tx size) - // 2. The deposit amount must be less than the per-deposit limit - // 3. The total amount being minted must stay under the maximum allowed mintable amount - let minimum_deposit_fee = self.compute_minimum_fee(SOLO_DEPOSIT_TX_VSIZE); - let max_mintable_cap = self.sbtc_limits.max_mintable_cap().to_sat(); - let per_deposit_cap = self.sbtc_limits.per_deposit_cap().to_sat(); - - let mut amount_to_mint: u64 = 0; - let deposits = self.deposits.iter().filter_map(|req| { - let is_fee_valid = req.max_fee.min(req.amount) >= minimum_deposit_fee; - let is_within_per_deposit_cap = req.amount <= per_deposit_cap; - let is_within_max_mintable_cap = - if let Some(new_amount) = amount_to_mint.checked_add(req.amount) { - new_amount <= max_mintable_cap - } else { - false - }; - if is_fee_valid && is_within_per_deposit_cap && is_within_max_mintable_cap { - amount_to_mint += req.amount; - Some(RequestRef::Deposit(req)) - } else { - None - } - }); + let mut deposit_filter = DepositFilter::new( + &self.deposits, + self.compute_minimum_fee(SOLO_DEPOSIT_TX_VSIZE), + self.sbtc_limits.per_deposit_minimum().to_sat(), + self.sbtc_limits.per_deposit_cap().to_sat(), + self.sbtc_limits.max_mintable_cap().to_sat(), + ); + let deposits = deposit_filter.filter_deposits(); + // Create a list of requests where each request can be approved on its own. let items = deposits.chain(withdrawals); @@ -2776,76 +2823,81 @@ mod tests { assert!(combined_fee <= (fee + Amount::from_sat(3u64))); } - #[test_case(vec![ - create_deposit(10_000, 10_000, 0), - create_deposit(10_000, 10_000, 0), - create_deposit(10_000, 10_000, 0), - create_deposit(10_000, 10_000, 0), - create_deposit(10_000, 10_000, 0), - ], 3, 30_000, 10_000, 30_000; "should_accept_deposits_until_max_mintable_reached")] - #[test_case(vec![ - create_deposit(10_000, 10_000, 0), - create_deposit(10_000, 10_000, 0), - ], 1, 10_000, 10_000, 15_000; "should_accept_all_deposits_when_under_max_mintable")] - #[test_case(vec![ - create_deposit(10_000, 10_000, 0), - ], 0, 0, 0, 0; "should_handle_empty_deposit_list")] - #[test_case(vec![ - create_deposit(10_000, 0, 0), - create_deposit(11_000, 10_000, 0), - create_deposit(9_000, 10_000, 0), - ], 1, 9_000, 10_000, 10_000; "should_skip_invalid_fee_and_accept_valid_deposits")] - #[test_case(vec![ - create_deposit(10_001, 10_000, 0), - ], 0, 0, 10_001, 10_000; "should_reject_single_deposit_exceeding_max_mintable")] - #[test_case(vec![ - create_deposit(10_000, 10_000, 0), - ], 0, 0, 8_000, 10_000; "should_reject_single_deposit_exceeding_per_deposit_cap")] + #[test_case(DepositFilter::new( + &vec![ + create_deposit(10_000, 1_000, 0), + create_deposit(11_000, 100, 0), + create_deposit(12_000, 2_000, 0), + create_deposit(13_000, 0, 0), + ], 1_000, 0, 20_000, 100_000, + ), 2, 22_000; "should_accept_all_deposits_above_or_equal_min_fee")] + #[test_case(DepositFilter::new( + &vec![ + create_deposit(10_000, 10_000, 0), + create_deposit(10_000, 10_000, 0), + create_deposit(10_000, 10_000, 0), + create_deposit(10_000, 10_000, 0), + create_deposit(10_000, 10_000, 0), + ], 1_000, 0, 10_000, 30_000, + ), 3, 30_000; "should_accept_deposits_until_max_mintable_reached")] + #[test_case(DepositFilter::new( + &vec![ + create_deposit(10_000, 10_000, 0), + create_deposit(10_000, 10_000, 0), + ], 1_000, 0, 10_000, 15_000, + ), 1, 10_000; "should_accept_all_deposits_when_under_max_mintable")] + #[test_case(DepositFilter::new( + &vec![create_deposit(10_000, 10_000, 0),], 1_000, 0, 0, 0, + ), 0, 0; "should_handle_empty_deposit_list")] + #[test_case(DepositFilter::new( + &vec![ + create_deposit(10_000, 0, 0), + create_deposit(11_000, 10_000, 0), + create_deposit(9_000, 10_000, 0), + ], 1_000, 0, 10_000, 10_000, + ), 1, 9_000; "should_skip_invalid_fee_and_accept_valid_deposits")] + #[test_case(DepositFilter::new( + &vec![ + create_deposit(10_001, 10_000, 0), + ], 1_000, 0, 10_001, 10_000, + ), 0, 0; "should_reject_single_deposit_exceeding_max_mintable")] + #[test_case(DepositFilter::new( + &vec![ + create_deposit(10_000, 10_000, 0), + ], 1_000, 0, 8_000, 10_000, + ), 0, 0; "should_reject_single_deposit_exceeding_per_deposit_cap")] + #[test_case(DepositFilter::new( + &vec![ + create_deposit(5_000, 2_000, 0), + create_deposit(15_000, 2_000, 0), + ], 1_000, 10_000, 20_000, 30_000, + ), 1, 15_000; "should_reject_deposits_below_per_deposit_minimum")] + #[test_case(DepositFilter::new( + &vec![ + create_deposit(10_000, 10_000, 0), // accepted + create_deposit(9_000, 10_000, 0), // rejected (below per_deposit_minimum) + create_deposit(21_000, 10_000, 0), // rejected (above per_deposit_cap) + create_deposit(20_000, 10_000, 0), // accepted + create_deposit(20_000, 10_000, 0), // rejected (above max_mintable) + create_deposit(5_000, 500, 0), // rejected (below minimum_fee) + ], 1_000, 10_000, 20_000, 40_000, + ), 2, 30_000; "should_respect_all_limits")] #[tokio::test] - async fn test_construct_transactions_filters_deposits_over_max_mintable( - deposits: Vec, - num_accepted_requests: usize, + async fn test_deposit_filter_filters_deposits_over_limits( + mut deposit_filter: DepositFilter<'_>, + num_accepted_deposits: usize, accepted_amount: u64, - per_deposit_cap: u64, - max_mintable: u64, ) { + let deposits = deposit_filter.filter_deposits(); // Each deposit and withdrawal has a max fee greater than the current market fee rate - let public_key = XOnlyPublicKey::from_str(X_ONLY_PUBLIC_KEY1).unwrap(); - let requests = SbtcRequests { - deposits: deposits, - withdrawals: vec![], - signer_state: SignerBtcState { - utxo: SignerUtxo { - outpoint: generate_outpoint(300_000, 0), - amount: 300_000_000, - public_key, - }, - fee_rate: 25.0, - public_key, - last_fees: None, - magic_bytes: [0; 2], - }, - num_signers: 10, - accept_threshold: 8, - sbtc_limits: SbtcLimits::new( - None, - Some(Amount::from_sat(per_deposit_cap)), - None, - Some(Amount::from_sat(max_mintable)), - ), - }; - let txs = requests.construct_transactions().unwrap(); - let nr_requests = txs.iter().map(|tx| tx.requests.len()).sum::(); - let total_amount: u64 = txs + // let txs = requests.construct_transactions().unwrap(); + let filtered_deposits: Vec> = deposits.collect(); + let total_amount: u64 = filtered_deposits .iter() - .map(|tx| { - tx.requests - .iter() - .map(|req| req.as_deposit().unwrap().amount) - }) - .flatten() + .map(|req| req.as_deposit().unwrap().amount) .sum(); - assert_eq!(nr_requests, num_accepted_requests); + + assert_eq!(filtered_deposits.len(), num_accepted_deposits); assert_eq!(total_amount, accepted_amount); } diff --git a/signer/src/bitcoin/validation.rs b/signer/src/bitcoin/validation.rs index dfc7be929..3e10c0168 100644 --- a/signer/src/bitcoin/validation.rs +++ b/signer/src/bitcoin/validation.rs @@ -13,6 +13,7 @@ use bitcoin::XOnlyPublicKey; use crate::bitcoin::utxo::FeeAssessment; use crate::bitcoin::utxo::SignerBtcState; use crate::context::Context; +use crate::context::SbtcLimits; use crate::error::Error; use crate::keys::PublicKey; use crate::message::BitcoinPreSignRequest; @@ -296,7 +297,6 @@ impl BitcoinPreSignRequest { // their fees anymore in order for them to be accepted by the // network. signer_state.last_fees = None; - let sbtc_limits = ctx.state().get_current_limits(); let out = BitcoinTxValidationData { signer_sighash: sighashes.signer_sighash(), deposit_sighashes: sighashes.deposit_sighashes(), @@ -306,8 +306,7 @@ impl BitcoinPreSignRequest { reports, chain_tip_height: btc_ctx.chain_tip_height, // If the cap is None, then we assume that it is unlimited. - max_deposit_amount: sbtc_limits.per_deposit_cap(), - max_withdrawal_amount: sbtc_limits.per_withdrawal_cap(), + sbtc_limits: ctx.state().get_current_limits(), }; Ok((out, signer_state)) @@ -333,10 +332,8 @@ pub struct BitcoinTxValidationData { pub tx_fee: Amount, /// the chain tip height. pub chain_tip_height: u64, - /// Maximum amount of BTC allowed to be pegged-in per transaction. - pub max_deposit_amount: Amount, - /// Maximum amount of BTC allowed to be pegged-out per transaction. - pub max_withdrawal_amount: Amount, + /// The current sBTC limits. + pub sbtc_limits: SbtcLimits, } impl BitcoinTxValidationData { @@ -365,7 +362,7 @@ impl BitcoinTxValidationData { self.chain_tip_height, &self.tx, self.tx_fee, - self.max_deposit_amount, + &self.sbtc_limits, ) }); @@ -425,7 +422,7 @@ impl BitcoinTxValidationData { self.chain_tip_height, &self.tx, self.tx_fee, - self.max_withdrawal_amount, + &self.sbtc_limits, ), is_valid_tx, }) @@ -446,7 +443,7 @@ impl BitcoinTxValidationData { self.chain_tip_height, &self.tx, self.tx_fee, - self.max_deposit_amount + &self.sbtc_limits, ), InputValidationResult::Ok | InputValidationResult::CannotSignUtxo ) @@ -457,7 +454,7 @@ impl BitcoinTxValidationData { self.chain_tip_height, &self.tx, self.tx_fee, - self.max_withdrawal_amount, + &self.sbtc_limits, ) { WithdrawalValidationResult::Unsupported | WithdrawalValidationResult::Unknown @@ -508,6 +505,8 @@ impl SbtcReports { pub enum InputValidationResult { /// The deposit request passed validation Ok, + /// The deposit request amount is below the allowed per-deposit minimum. + AmountTooLow, /// The deposit request amount exceeds the allowed per-deposit cap. AmountTooHigh, /// The assessed fee exceeds the max-fee in the deposit request. @@ -678,7 +677,7 @@ impl DepositRequestReport { chain_tip_height: u64, tx: &F, tx_fee: Amount, - max_deposit_amount: Amount, + sbtc_limits: &SbtcLimits, ) -> InputValidationResult where F: FeeAssessment, @@ -702,7 +701,11 @@ impl DepositRequestReport { DepositConfirmationStatus::Confirmed(block_height, _) => block_height, }; - if self.amount > max_deposit_amount.to_sat() { + if self.amount < sbtc_limits.per_deposit_minimum().to_sat() { + return InputValidationResult::AmountTooLow; + } + + if self.amount > sbtc_limits.per_deposit_cap().to_sat() { return InputValidationResult::AmountTooHigh; } @@ -823,7 +826,7 @@ impl WithdrawalRequestReport { _: u64, _: &F, _: Amount, - _max_withdrawal_amount: Amount, + _sbtc_limits: &SbtcLimits, ) -> WithdrawalValidationResult where F: FeeAssessment, @@ -867,6 +870,7 @@ mod tests { report: DepositRequestReport, status: InputValidationResult, chain_tip_height: u64, + limits: SbtcLimits, } const TX_FEE: Amount = Amount::from_sat(10000); @@ -886,7 +890,8 @@ mod tests { }, status: InputValidationResult::TxNotOnBestChain, chain_tip_height: 2, - } ; "deposit-reorged")] + limits: SbtcLimits::new_per_deposit(0, u64::MAX), + }; "deposit-reorged")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { status: DepositConfirmationStatus::Spent(BitcoinTxId::from([1; 32])), @@ -902,7 +907,8 @@ mod tests { }, status: InputValidationResult::DepositUtxoSpent, chain_tip_height: 2, - } ; "deposit-spent")] + limits: SbtcLimits::new_per_deposit(0, u64::MAX), + }; "deposit-spent")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { status: DepositConfirmationStatus::Confirmed(0, BitcoinBlockHash::from([0; 32])), @@ -918,6 +924,7 @@ mod tests { }, status: InputValidationResult::NoVote, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "deposit-no-vote")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { @@ -934,6 +941,7 @@ mod tests { }, status: InputValidationResult::CannotSignUtxo, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "cannot-sign-for-deposit")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { @@ -950,6 +958,7 @@ mod tests { }, status: InputValidationResult::RejectedRequest, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "rejected-deposit")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { @@ -966,6 +975,7 @@ mod tests { }, status: InputValidationResult::LockTimeExpiry, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "lock-time-expires-soon-1")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { @@ -982,6 +992,7 @@ mod tests { }, status: InputValidationResult::LockTimeExpiry, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "lock-time-expires-soon-2")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { @@ -998,6 +1009,7 @@ mod tests { }, status: InputValidationResult::UnsupportedLockTime, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "lock-time-in-time-units-2")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { @@ -1014,6 +1026,7 @@ mod tests { }, status: InputValidationResult::Ok, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "happy-path")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { @@ -1030,6 +1043,7 @@ mod tests { }, status: InputValidationResult::Unknown, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "unknown-prevout")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { @@ -1046,6 +1060,7 @@ mod tests { }, status: InputValidationResult::Ok, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "at-the-border")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { @@ -1062,6 +1077,7 @@ mod tests { }, status: InputValidationResult::FeeTooHigh, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "one-sat-too-high-fee-amount")] #[test_case(DepositReportErrorMapping { report: DepositRequestReport { @@ -1078,7 +1094,42 @@ mod tests { }, status: InputValidationResult::FeeTooHigh, chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, u64::MAX), } ; "one-sat-too-high-fee")] + #[test_case(DepositReportErrorMapping { + report: DepositRequestReport { + status: DepositConfirmationStatus::Confirmed(0, BitcoinBlockHash::from([0; 32])), + can_sign: Some(true), + can_accept: Some(true), + amount: 100_000_000, + max_fee: u64::MAX, + lock_time: LockTime::from_height(DEPOSIT_LOCKTIME_BLOCK_BUFFER + 3), + outpoint: OutPoint::null(), + deposit_script: ScriptBuf::new(), + reclaim_script: ScriptBuf::new(), + signers_public_key: *sbtc::UNSPENDABLE_TAPROOT_KEY, + }, + status: InputValidationResult::AmountTooHigh, + chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(0, 99_999_999), + } ; "amount-too-high")] + #[test_case(DepositReportErrorMapping { + report: DepositRequestReport { + status: DepositConfirmationStatus::Confirmed(0, BitcoinBlockHash::from([0; 32])), + can_sign: Some(true), + can_accept: Some(true), + amount: 99_999_999, + max_fee: u64::MAX, + lock_time: LockTime::from_height(DEPOSIT_LOCKTIME_BLOCK_BUFFER + 3), + outpoint: OutPoint::null(), + deposit_script: ScriptBuf::new(), + reclaim_script: ScriptBuf::new(), + signers_public_key: *sbtc::UNSPENDABLE_TAPROOT_KEY, + }, + status: InputValidationResult::AmountTooLow, + chain_tip_height: 2, + limits: SbtcLimits::new_per_deposit(100_000_000, u64::MAX), + } ; "amount-too-low")] fn deposit_report_validation(mapping: DepositReportErrorMapping) { let mut tx = crate::testing::btc::base_signer_transaction(); tx.input.push(TxIn { @@ -1091,7 +1142,7 @@ mod tests { let status = mapping .report - .validate(mapping.chain_tip_height, &tx, TX_FEE, Amount::MAX_MONEY); + .validate(mapping.chain_tip_height, &tx, TX_FEE, &mapping.limits); assert_eq!(status, mapping.status); } @@ -1250,6 +1301,7 @@ mod tests { Some(total_cap), None, None, + None, Some(total_cap - sbtc_supply), )); // Create cache with test data diff --git a/signer/src/block_observer.rs b/signer/src/block_observer.rs index a0c46bee5..66553b54b 100644 --- a/signer/src/block_observer.rs +++ b/signer/src/block_observer.rs @@ -557,6 +557,7 @@ impl BlockObserver { let limits = SbtcLimits::new( Some(limits.total_cap()), + Some(limits.per_deposit_minimum()), Some(limits.per_deposit_cap()), Some(limits.per_withdrawal_cap()), Some(max_mintable), diff --git a/signer/src/context/signer_state.rs b/signer/src/context/signer_state.rs index edf3c108f..bc5284b18 100644 --- a/signer/src/context/signer_state.rs +++ b/signer/src/context/signer_state.rs @@ -82,6 +82,8 @@ impl SignerState { pub struct SbtcLimits { /// Represents the total cap for all pegged-in BTC/sBTC. total_cap: Option, + /// Represents the minimum amount of BTC allowed to be pegged-in per transaction. + per_deposit_minimum: Option, /// Represents the maximum amount of BTC allowed to be pegged-in per transaction. per_deposit_cap: Option, /// Represents the maximum amount of sBTC allowed to be pegged-out per transaction. @@ -94,8 +96,8 @@ impl std::fmt::Display for SbtcLimits { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "[total cap: {:?}, per-deposit cap: {:?}, per-withdrawal cap: {:?}, max-mintable cap: {:?}]", - self.total_cap, self.per_deposit_cap, self.per_withdrawal_cap, self.max_mintable_cap + "[total cap: {:?}, per-deposit min: {:?}, per-deposit cap: {:?}, per-withdrawal cap: {:?}, max-mintable cap: {:?}]", + self.total_cap, self.per_deposit_minimum, self.per_deposit_cap, self.per_withdrawal_cap, self.max_mintable_cap ) } } @@ -104,12 +106,14 @@ impl SbtcLimits { /// Create a new `SbtcLimits` object. pub fn new( total_cap: Option, + per_deposit_minimum: Option, per_deposit_cap: Option, per_withdrawal_cap: Option, max_mintable_cap: Option, ) -> Self { Self { total_cap, + per_deposit_minimum, per_deposit_cap, per_withdrawal_cap, max_mintable_cap, @@ -126,6 +130,11 @@ impl SbtcLimits { self.total_cap.is_some() } + /// Get the minimum amount of BTC allowed to be pegged-in per transaction. + pub fn per_deposit_minimum(&self) -> Amount { + self.per_deposit_minimum.unwrap_or(Amount::ZERO) + } + /// Get the maximum amount of BTC allowed to be pegged-in per transaction. pub fn per_deposit_cap(&self) -> Amount { self.per_deposit_cap.unwrap_or(Amount::MAX_MONEY) @@ -140,6 +149,17 @@ impl SbtcLimits { pub fn max_mintable_cap(&self) -> Amount { self.max_mintable_cap.unwrap_or(Amount::MAX_MONEY) } + + #[cfg(test)] + pub fn new_per_deposit(min: u64, max: u64) -> Self { + Self { + total_cap: None, + per_deposit_minimum: Some(Amount::from_sat(min)), + per_deposit_cap: Some(Amount::from_sat(max)), + per_withdrawal_cap: None, + max_mintable_cap: None, + } + } } /// Represents a signer in the current signer set. diff --git a/signer/src/emily_client.rs b/signer/src/emily_client.rs index c71a34fef..3530b8859 100644 --- a/signer/src/emily_client.rs +++ b/signer/src/emily_client.rs @@ -336,6 +336,9 @@ impl EmilyInteract for EmilyClient { .map_err(Error::EmilyApi)?; let total_cap = limits.peg_cap.and_then(|cap| cap.map(Amount::from_sat)); + let per_deposit_minimum: Option = limits + .per_deposit_minimum + .and_then(|min| min.map(Amount::from_sat)); let per_deposit_cap = limits .per_deposit_cap .and_then(|cap| cap.map(Amount::from_sat)); @@ -345,6 +348,7 @@ impl EmilyInteract for EmilyClient { Ok(SbtcLimits::new( total_cap, + per_deposit_minimum, per_deposit_cap, per_withdrawal_cap, None, diff --git a/signer/tests/integration/block_observer.rs b/signer/tests/integration/block_observer.rs index f2b66e08c..2bae058eb 100644 --- a/signer/tests/integration/block_observer.rs +++ b/signer/tests/integration/block_observer.rs @@ -663,9 +663,9 @@ async fn block_observer_stores_donation_and_sbtc_utxos() { #[cfg_attr(not(feature = "integration-tests"), ignore)] #[test_case::test_case(false, SbtcLimits::default(); "no contracts, default limits")] -#[test_case::test_case(false, SbtcLimits::new(Some(bitcoin::Amount::from_sat(1_000)), None, None, None); "no contracts, total cap limit")] +#[test_case::test_case(false, SbtcLimits::new(Some(bitcoin::Amount::from_sat(1_000)), None, None, None, None); "no contracts, total cap limit")] #[test_case::test_case(true, SbtcLimits::default(); "deployed contracts, default limits")] -#[test_case::test_case(true, SbtcLimits::new(Some(bitcoin::Amount::from_sat(1_000)), None, None, None); "deployed contracts, total cap limit")] +#[test_case::test_case(true, SbtcLimits::new(Some(bitcoin::Amount::from_sat(1_000)), None, None, None, None); "deployed contracts, total cap limit")] #[tokio::test] async fn block_observer_handles_update_limits(deployed: bool, sbtc_limits: SbtcLimits) { // We start with the typical setup with a fresh database and context