Repository navigation
fix: two wallet shapes that the transaction builder gets wrong - #117
Merged
Merged
Conversation
`whisky-pallas` mishandles native tokens twice, and both surfaced on spo4 on 2026-09-24. It converts each input's asset list on its own and inserts the result per policy id instead of merging, so a UTxO carrying two asset names under one policy loses all but the last, and the ledger refuses the transaction as ValueNotConserved, naming a value the node never left out. Separately it prices a transaction from a mock body assembled before the change output carries its tokens: the fee it sets does not move with the token load at all, so an ada-only change leaves 1,496 lovelace of surplus, one token turns that into a 396 lovelace shortfall, and two into 3,300, answered by FeeTooSmallUTxO. Between them these stopped the key handoff on bridge epochs 1556 and 1557 and left `ensure-collateral` unable to dig the wallet out. Input selection now passes over a UTxO whose assets would be stripped, in `select_fee`, in the collateral split and in `register_pool`, and names the policy when nothing else can fund the transaction. Fee inputs prefer ada-only UTxOs so the change carries nothing to underprice, and the collateral split spends ada-only dust before one fat token-bearing UTxO for the same reason. Where the change must carry tokens, the split measures the signed transaction and rebuilds once with an explicit fee rather than trusting the estimate. Two asset names on separate UTxOs remain fine, since adding two already-converted values merges correctly; a test pins that the limit is per UTxO. The stripping half is fixed upstream as sidan-lab/whisky#40, but both stand regardless, since the wallet is ours to choose inputs from.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both bugs are in
whisky-pallas1.0.25 (and1.0.28-beta.1, unchanged — there is no newer stable). Both hit spo4 on preprod on 2026-09-24 and between them stopped it posting anything to Cardano for a day.Bug 1: assets stripped off an input
convert_valueemits one(policy, [one asset])entry per asset, andMultiassetPositiveCoin::newdoespolicy_map.insert(policy_id, asset_map)— replace, not merge. A UTxO carrying two asset names under one policy loses all but the last before the builder ever sees it, so the change it computes is short that asset while the lovelace balances exactly:The ledger names an asset the node never left out, which makes it hard to trace back.
Bug 2: the fee ignores the change output's tokens
whisky prices a mock body assembled before the change carries its tokens. Measured on
build_collateral_top_up, same amounts, only the token load varying:fee setnever moves. This is why heimdall posted fine historically and then stopped: nothing regressed in our code, the wallet's largest UTxO simply started carrying tokens, andselect_feetakes the largest.Changes
dropped_policy— names the policy a UTxO would lose assets under. Wired intoselect_fee,build_collateral_top_upandregister_pool::select_inputs; each refuses by name when nothing else can fund the transaction.select_feeprefers ada-only UTxOs, falling back to token-bearing ones when no ada-only one can cover the outputs. A preference, not a rule —ensure-collateralhas to spend what the wallet holds.Scope
treasury_bootstrapalready refuses token-bearing inputs. Every other builder routes its fee input throughselect_fee. Mints are unaffected in practice — everymints:call site is a singleScriptMint— thoughMultiassetNonZeroInt::newhas the same replace-not-merge hole upstream.Tests
10 new tests, each watched fail first, including a characterisation test pinning that the stripping limit is per UTxO (
Value::addmerges correctly, so two names on separate UTxOs are fine). 943 pass, clippy clean.Upstream
Bug 1 is fixed in sidan-lab/whisky#40 with a regression test for both affected functions. Both changes here stand regardless of whether that lands, since the wallet is ours to choose inputs from.