Skip to content

Commit 0aef022

Browse files
committed
make integer overflow/underflow handling explicit
1 parent 8e4798f commit 0aef022

7 files changed

Lines changed: 74 additions & 21 deletions

File tree

src/database/enums.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,19 @@ impl Assignment {
354354
#[cfg(any(feature = "electrum", feature = "esplora"))]
355355
pub(crate) fn add_to_assignments(&self, assignments: &mut AssignmentsCollection) {
356356
match self {
357-
Self::Fungible(amt) => assignments.fungible += amt,
357+
Self::Fungible(amt) => {
358+
assignments.fungible = assignments
359+
.fungible
360+
.checked_add(*amt)
361+
.expect("total fungible amount cannot exceed u64::MAX")
362+
}
358363
Self::NonFungible => assignments.non_fungible = true,
359-
Self::InflationRight(amt) => assignments.inflation += amt,
364+
Self::InflationRight(amt) => {
365+
assignments.inflation = assignments
366+
.inflation
367+
.checked_add(*amt)
368+
.expect("total inflation amount cannot exceed u64::MAX")
369+
}
360370
_ => unreachable!("when using this method we should know the assignment type"),
361371
}
362372
}

src/database/mod.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,7 @@ impl DbTxn {
739739
.map(|a| a.assignment.main_amount())
740740
.sum();
741741

742-
let mut ass_pending_incoming: u64 = ass_allocations
742+
let allocations_pending_incoming: u64 = ass_allocations
743743
.iter()
744744
.filter(|a| !a.txo_spent && a.incoming && a.status.pending())
745745
.map(|a| a.assignment.main_amount())
@@ -767,7 +767,9 @@ impl DbTxn {
767767
}
768768
})
769769
.sum();
770-
ass_pending_incoming += witness_pending;
770+
let ass_pending_incoming = allocations_pending_incoming
771+
.checked_add(witness_pending)
772+
.expect("total pending incoming amount cannot exceed u64::MAX");
771773
let ass_pending_outgoing: u64 = ass_allocations
772774
.iter()
773775
.filter(|a| !a.incoming && a.status.pending())
@@ -807,11 +809,14 @@ impl DbTxn {
807809
})
808810
.sum();
809811

810-
let spendable = settled - unspendable;
812+
let spendable = settled
813+
.checked_sub(unspendable)
814+
.expect("unspendable allocations are a subset of settled ones");
811815

812816
Ok(Balance {
813817
settled,
814-
future: future as u64,
818+
future: u64::try_from(future)
819+
.expect("pending outgoing cannot exceed available balance"),
815820
spendable,
816821
})
817822
}

src/wallet/multisig.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,9 @@ impl WalletOffline for MultisigWallet {
259259
let is_internal = keychain == KeychainKind::Internal;
260260
let start_index = self.hub_client().bump_address_indices(count, is_internal)?;
261261
let local_index = self.bdk_wallet().derivation_index(keychain).unwrap_or(0);
262-
let target_index = start_index + count;
262+
let target_index = start_index
263+
.checked_add(count)
264+
.expect("address derivation index cannot exceed u32::MAX");
263265
let (bdk_wallet, bdk_database) = self.bdk_wallet_db_mut();
264266
for _ in local_index..target_index {
265267
bdk_wallet.reveal_next_address(keychain);
@@ -1826,7 +1828,10 @@ impl MultisigWallet {
18261828

18271829
let op_idx = self.get_local_last_processed_operation_idx_impl(&txn)?;
18281830
txn.commit()?;
1829-
let Some(op) = self.hub_client().get_operation_by_idx(op_idx + 1)? else {
1831+
let next_op_idx = op_idx
1832+
.checked_add(1)
1833+
.expect("operation index cannot exceed i32::MAX");
1834+
let Some(op) = self.hub_client().get_operation_by_idx(next_op_idx)? else {
18301835
return Ok(None);
18311836
};
18321837

src/wallet/objects.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -808,15 +808,21 @@ pub struct AssignmentsCollection {
808808
#[cfg(any(feature = "electrum", feature = "esplora"))]
809809
impl AssignmentsCollection {
810810
fn add_fungible(&mut self, amt: u64) {
811-
self.fungible += amt;
811+
self.fungible = self
812+
.fungible
813+
.checked_add(amt)
814+
.expect("total fungible amount cannot exceed u64::MAX");
812815
}
813816

814817
fn add_non_fungible(&mut self) {
815818
self.non_fungible = true;
816819
}
817820

818821
fn add_inflation(&mut self, amt: u64) {
819-
self.inflation += amt;
822+
self.inflation = self
823+
.inflation
824+
.checked_add(amt)
825+
.expect("total inflation amount cannot exceed u64::MAX");
820826
}
821827

822828
pub(crate) fn add_opout_state(&mut self, opout: &Opout, state: &AllocatedState) {

src/wallet/offline.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,12 @@ pub trait WalletOffline: WalletBackup {
124124
mut_unspents
125125
.iter_mut()
126126
.for_each(|u| u.rgb_allocations.retain(|a| !a.status.failed()));
127-
let max_allocs = max_allocations.unwrap_or(self.max_allocations_per_utxo() - 1);
127+
let max_allocs = max_allocations.unwrap_or_else(|| {
128+
// guaranteed > 0; guard here too to avoid future regression
129+
self.max_allocations_per_utxo()
130+
.checked_sub(1)
131+
.expect("max allocations per UTXO must be greater than 0")
132+
});
128133
Ok(mut_unspents
129134
.iter()
130135
.filter(|u| u.utxo.exists)
@@ -1453,7 +1458,10 @@ pub trait WalletOffline: WalletBackup {
14531458
Ok(Balance {
14541459
settled: balance.confirmed.to_sat(),
14551460
future: future.to_sat(),
1456-
spendable: future.to_sat() - balance.immature.to_sat(),
1461+
spendable: future
1462+
.to_sat()
1463+
.checked_sub(balance.immature.to_sat())
1464+
.expect("immature balance cannot exceed the total balance"),
14571465
})
14581466
}
14591467

src/wallet/online.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,10 @@ pub trait WalletOnline: WalletOffline {
255255
(inputs, usable_btc_amount)
256256
} else {
257257
inputs.push(outpoint);
258-
(inputs, usable_btc_amount + value)
258+
let usable_btc_amount = usable_btc_amount
259+
.checked_add(value)
260+
.expect("total UTXO value cannot exceed u64::MAX");
261+
(inputs, usable_btc_amount)
259262
}
260263
},
261264
);
@@ -1809,7 +1812,7 @@ pub trait WalletOnline: WalletOffline {
18091812
}
18101813

18111814
let mut assignments_collected = AssignmentsCollection::default();
1812-
let mut input_btc_amt = 0;
1815+
let mut input_btc_amt: u64 = 0;
18131816
for unspent in mut_unspents {
18141817
// get spendable allocations for the required asset
18151818
let asset_allocations: Vec<LocalRgbAllocation> = unspent
@@ -1857,7 +1860,14 @@ pub trait WalletOnline: WalletOffline {
18571860
.for_each(|a| a.assignment.add_to_assignments(&mut assignments_collected));
18581861
input_outpoints.push(unspent.utxo.outpoint());
18591862

1860-
input_btc_amt += unspent.utxo.btc_amount.parse::<u64>().unwrap();
1863+
let utxo_btc_amt = unspent
1864+
.utxo
1865+
.btc_amount
1866+
.parse::<u64>()
1867+
.expect("DB should contain a valid BTC amount");
1868+
input_btc_amt = input_btc_amt
1869+
.checked_add(utxo_btc_amt)
1870+
.expect("total input BTC value cannot exceed u64::MAX");
18611871

18621872
// stop as soon as we have the needed assignments
18631873
if assignments_collected.enough(assignments_needed) {
@@ -1961,7 +1971,12 @@ pub trait WalletOnline: WalletOffline {
19611971
details: e.to_string(),
19621972
})?;
19631973
}
1964-
free_utxos.sort_by_key(|u| u.utxo.btc_amount.parse::<u64>().unwrap());
1974+
free_utxos.sort_by_key(|u| {
1975+
u.utxo
1976+
.btc_amount
1977+
.parse::<u64>()
1978+
.expect("DB should contain a valid BTC amount")
1979+
});
19651980
}
19661981
if let Some(a) = free_utxos.pop() {
19671982
all_inputs.insert(a.utxo.into());
@@ -3493,13 +3508,15 @@ pub trait WalletOnline: WalletOffline {
34933508
let (asset_id, transfer_info) = info_contents.transfers.into_iter().next().unwrap();
34943509
let inflation = transfer_info.original_assignments_needed.inflation;
34953510
let db_asset = txn.get_asset(asset_id).unwrap().unwrap();
3496-
let updated_known_circulating_supply = db_asset
3511+
let known_circulating_supply = db_asset
34973512
.known_circulating_supply
34983513
.as_ref()
34993514
.unwrap()
35003515
.parse::<u64>()
3501-
.unwrap()
3502-
+ inflation;
3516+
.expect("DB should contain a valid known circulating supply");
3517+
let updated_known_circulating_supply = known_circulating_supply
3518+
.checked_add(inflation)
3519+
.expect("known circulating supply plus inflation cannot exceed u64::MAX");
35033520
let mut updated_asset: DbAssetActMod = db_asset.into();
35043521
updated_asset.known_circulating_supply =
35053522
ActiveValue::Set(Some(updated_known_circulating_supply.to_string()));

src/wallet/rust_only.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,16 @@ impl Wallet {
135135
let mut asset_transition_builder =
136136
runtime.transition_builder(contract_id, "transfer")?;
137137

138-
let mut asset_available_amt = 0;
138+
let mut asset_available_amt: u64 = 0;
139139
let mut uda_state = None;
140140
for (_, opout_state_map) in
141141
runtime.contract_assignments_for(contract_id, prev_outputs.iter().copied())?
142142
{
143143
for (opout, state) in opout_state_map {
144144
if let AllocatedState::Amount(amt) = &state {
145-
asset_available_amt += amt.as_u64();
145+
asset_available_amt = asset_available_amt
146+
.checked_add(amt.as_u64())
147+
.expect("total available asset amount cannot exceed u64::MAX");
146148
} else if let AllocatedState::Data(_) = &state {
147149
asset_available_amt = 1;
148150
// there can be only a single state when contract is UDA

0 commit comments

Comments
 (0)