Skip to content

Commit 380645a

Browse files
bfoss765claude
andcommitted
fix(platform-wallet): map NoUtxosAvailable to the typed asset-lock insufficient-funds error
Addresses carried review finding prior-no-utxos-846 on dashpay#4074. The union-funding coin selector returns SelectionError::NoUtxosAvailable when filtering leaves zero spendable candidates — the most extreme shortfall — but map_builder_error only promoted the two InsufficientFunds variants to the typed PlatformWalletError::AssetLockInsufficientFunds, so the empty candidate set fell through to the generic AssetLockTransaction(String) while partial shortfalls stayed typed. Hosts then had to string-match to recognize the zero-funds case. NoUtxosAvailable carries no amounts, so map_builder_error now takes the caller's requested target and maps this case to AssetLockInsufficientFunds { available: 0, required: requested }, keeping the empty set on the same structured shortfall contract dashpay#4073 introduced. Carried InsufficientFunds amounts still win over the requested arg. No third_party changes. Unit test covers both mappings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ce482fd commit 380645a

1 file changed

Lines changed: 64 additions & 8 deletions

File tree

  • packages/rs-platform-wallet/src/wallet/asset_lock

packages/rs-platform-wallet/src/wallet/asset_lock/build.rs

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ impl<B: TransactionBroadcaster + ?Sized> AssetLockManager<B> {
358358
let (transaction, fee) = builder
359359
.build_signed(signer, move |addr| path_map.get(&addr).cloned())
360360
.await
361-
.map_err(map_builder_error)?;
361+
.map_err(|e| map_builder_error(e, target_duffs))?;
362362
tracing::debug!(
363363
selected_inputs = transaction.input.len(),
364364
fee,
@@ -1000,13 +1000,20 @@ impl<B: TransactionBroadcaster + ?Sized> AssetLockManager<B> {
10001000
}
10011001

10021002
/// Map a key-wallet [`BuilderError`] to a [`PlatformWalletError`], promoting
1003-
/// the two shortfall shapes (`BuilderError::InsufficientFunds` and a
1004-
/// coin-selection `SelectionError::InsufficientFunds`) to the typed
1005-
/// [`PlatformWalletError::AssetLockInsufficientFunds`] so the exact
1006-
/// `available`/`required` duff amounts survive instead of being flattened into
1007-
/// a string (dashpay/platform#4073's typed-error ask). Every other builder
1008-
/// error keeps the generic `AssetLockTransaction` string form.
1009-
fn map_builder_error(e: BuilderError) -> PlatformWalletError {
1003+
/// every shortfall shape to the typed
1004+
/// [`PlatformWalletError::AssetLockInsufficientFunds`] so callers get one
1005+
/// structured shortfall contract (dashpay/platform#4073's typed-error ask)
1006+
/// instead of a string they must pattern-match:
1007+
/// - `BuilderError::InsufficientFunds` / `SelectionError::InsufficientFunds`
1008+
/// carry their own exact `available`/`required` duff amounts — preserved.
1009+
/// - `SelectionError::NoUtxosAvailable` — the zero-spendable-candidate case,
1010+
/// the MOST extreme shortfall — carries no amounts, so it previously fell
1011+
/// through to the generic string form while partial shortfalls stayed typed
1012+
/// (dashpay/platform#4074 prior-no-utxos-846). It now maps to `available: 0`
1013+
/// with the caller's `requested` target as `required`, keeping the empty
1014+
/// candidate set on the same structured path.
1015+
/// Every other builder error keeps the generic `AssetLockTransaction` string.
1016+
fn map_builder_error(e: BuilderError, requested: u64) -> PlatformWalletError {
10101017
match e {
10111018
BuilderError::InsufficientFunds {
10121019
available,
@@ -1019,6 +1026,12 @@ fn map_builder_error(e: BuilderError) -> PlatformWalletError {
10191026
available,
10201027
required,
10211028
},
1029+
BuilderError::CoinSelection(SelectionError::NoUtxosAvailable) => {
1030+
PlatformWalletError::AssetLockInsufficientFunds {
1031+
available: 0,
1032+
required: requested,
1033+
}
1034+
}
10221035
other => {
10231036
PlatformWalletError::AssetLockTransaction(format!("Asset lock builder failed: {other}"))
10241037
}
@@ -1052,6 +1065,49 @@ mod tests {
10521065
use crate::wallet::platform_wallet::WalletId;
10531066
use crate::{AssetLockFundingType, PlatformWalletError};
10541067

1068+
/// prior-no-utxos-846 (dashpay/platform#4074): the zero-spendable-candidate
1069+
/// selection error must surface the SAME typed shortfall as a partial
1070+
/// shortfall (not the generic string), so hosts stay on one structured path;
1071+
/// and a partial shortfall must still carry its own exact amounts.
1072+
#[test]
1073+
fn no_utxos_available_maps_to_typed_insufficient_funds() {
1074+
use super::{map_builder_error, BuilderError, SelectionError};
1075+
1076+
// Zero spendable candidates -> typed, available: 0, required = requested.
1077+
match map_builder_error(
1078+
BuilderError::CoinSelection(SelectionError::NoUtxosAvailable),
1079+
12_345,
1080+
) {
1081+
PlatformWalletError::AssetLockInsufficientFunds {
1082+
available,
1083+
required,
1084+
} => {
1085+
assert_eq!(available, 0, "empty candidate set means nothing available");
1086+
assert_eq!(required, 12_345, "requested target threaded through as required");
1087+
}
1088+
other => panic!("expected typed AssetLockInsufficientFunds, got {other:?}"),
1089+
}
1090+
1091+
// A partial shortfall keeps its own exact amounts; the requested arg is
1092+
// NOT substituted for the builder's carried values.
1093+
match map_builder_error(
1094+
BuilderError::CoinSelection(SelectionError::InsufficientFunds {
1095+
available: 100,
1096+
required: 500,
1097+
}),
1098+
999,
1099+
) {
1100+
PlatformWalletError::AssetLockInsufficientFunds {
1101+
available,
1102+
required,
1103+
} => {
1104+
assert_eq!(available, 100);
1105+
assert_eq!(required, 500, "carried amounts win over the requested arg");
1106+
}
1107+
other => panic!("expected typed AssetLockInsufficientFunds, got {other:?}"),
1108+
}
1109+
}
1110+
10551111
/// Persistence stub that records every stored changeset so tests can
10561112
/// assert what the asset-lock flow queued.
10571113
#[derive(Default)]

0 commit comments

Comments
 (0)