Skip to content

fix(rust-client): reject zero-amount assets in swap and pswap notes - #2459

Open
Dusk1e wants to merge 1 commit into
0xMiden:nextfrom
Dusk1e:reject-zero-swap-assets
Open

fix(rust-client): reject zero-amount assets in swap and pswap notes#2459
Dusk1e wants to merge 1 commit into
0xMiden:nextfrom
Dusk1e:reject-zero-swap-assets

Conversation

@Dusk1e

@Dusk1e Dusk1e commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Problem

Follow-up to #2457, which closed the same hole on build_mint_fungible_asset. The remaining note builders still take a zero-amount asset:

build_swap(offered=0,   requested=100) -> Ok
build_swap(offered=100, requested=0)   -> Ok
build_pswap_create(offered=0,   requested=100) -> Ok
build_pswap_create(offered=100, requested=0)   -> Ok

Both sides of a swap are supposed to carry value. SwapNote's own docs describe it: the consumer takes the offered asset and "the script creates a P2ID payback note carrying the requested asset" back to the creator.

So requested = 0 builds a swap whose payback is a P2ID note carrying nothing - the exact case build_pay_to_id rejects with P2IDNoteWithoutAsset, and the one #2457 just closed for minting. And offered = 0 builds a note whose consumer hands over the requested asset and receives nothing.

I should have covered these in #2457; I only looked at the mint path at the time.

Change

Reject a zero-amount asset on either side of build_swap and build_pswap_create.

P2IDNoteWithoutAsset does not fit here - the offered side is not a P2ID note, and its message and hint are about a P2ID transfer - so this adds SwapNoteWithZeroAsset, carrying which side was zero. It follows ZeroExpirationDelta in spirit, and gets an ErrorHint like the neighbouring variants.

TransactionRequestError is not #[non_exhaustive], so the new variant is a breaking change for downstream exhaustive matches; the CHANGELOG records it as such.

Swap assets are Asset, so the zero test goes through a small is_zero_fungible helper - non-fungible assets always carry value. PSWAP is fungible on both sides and compares directly.

Test plan

cargo test -p miden-client-unit-tests -- note_without_asset swap_note_with_zero_asset

swap_note_with_zero_asset covers all four cases above. It fails on next: with the guards removed the first unwrap_err panics on an Ok request.

It is a separate test rather than more cases inside note_without_asset because appending there pushed that function past clippy::too_many_lines.

122 miden-client lib tests still pass. The full unit-test suite runs past 15 minutes locally, so I ran the affected tests and am relying on CI for the rest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant