Payments bugs v12#609
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 151abd0. Configure here.
| delegate: delegate.clone(), | ||
| destination: destination.clone(), | ||
| amount, | ||
| }); |
There was a problem hiding this comment.
Zero transfer emits approval event
Low Severity
do_transfer_approved always deposits TransferredApproved after success, but transfer_and_die returns immediately when amount is zero without moving tokens or emitting Transferred. Indexers can record a delegated transfer that never happened, inconsistent with the new zero-amount guards on do_mint and do_burn.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 151abd0. Configure here.
n13
left a comment
There was a problem hiding this comment.
Review verdict: Request changes
Reviewed at 151abd0. I checked out the branch and ran it locally: cargo test -p pallet-assets passes all 92 tests (including the two new regression tests), and pallet-balances + pallet-transaction-payment compile clean.
Most of this is solid, audit-grade work. There's one confirmed (low) bug and one fix that doesn't do what the description claims — details below.
Correct and good as-is
extra_mutator.rsDrop — movingcommit()out ofdebug_assert!so release builds actually persist sidecar mutations. Correct.do_refundsupply /Burned— verifieddead_account()does not touchdetails.supply(onlyaccounts/sufficients), so decrementingsupplybyaccount.balancehere is correct with no double-count. Good catch on the phantom-issuance bug.do_mint/do_burnzero guards — return values stay consistent (Ok(())/Ok(0), matchingdecrease_balance). Fixes the event-emission-without-permission-check.Created.owner— now the create-origin owner instead ofadmin. Correct.set_min_balancerejects zero — consistent with the existingcreate/force_createinvariant (pallets/assets/src/lib.rs:775).balances::burnweight — the original mapping was inverted;if keep_alive { burn_keep_alive } else { burn_allow_death }is the correct mapping.weights.rs::set_reserves—SubstrateWeight<T>now usesT::DbWeightwhileimpl WeightInfo for ()correctly keepsRocksDbWeight. Correct.
1. Transaction-payment fix is on dead code — no runtime effect (please address)
The keep-alive mirror was added to CurrencyAdapter::can_withdraw_fee, but:
- The runtime wires
OnChargeTransaction = FungibleAdapter<...>(runtime/src/configs/mod.rs:448-449). CurrencyAdapteris#[deprecated]and is referenced nowhere in this repo except its own definition — not in the runtime, tests, mocks, or benchmarks.FungibleAdapter::can_withdraw_feealready rejects the kill case: it only acceptsWithdrawConsequence::Success(pallets/transaction-payment/src/payment.rs:163-166), which already excludes accounts that would drop below ED — consistent withwithdraw_fee'sPreservation::Preserve(payment.rs:140).
So this hunk has zero effect on the chain, and the description's "pool checks do not pass when prepare would kill the account" doesn't apply to the live path. Recommendation: drop the change (and note that FungibleAdapter is already correct), or if you believe there is a real gap, demonstrate it against FungibleAdapter and fix there. Minor aside: even within CurrencyAdapter the mirror compares only free_balance, whereas pallet-balances' withdraw keep-alive uses total balance (free + reserved), so it could over-reject.
2. Zero-amount do_transfer_approved emits TransferredApproved (confirms Bugbot — Low)
pallets/assets/src/functions.rs:1045-1051 deposits TransferredApproved unconditionally, but transfer_and_die returns early for amount == 0 (functions.rs:646-648) without moving funds or emitting Transferred. This is exactly the inconsistency you guarded against in do_mint/do_burn. Add the same guard — e.g. early-return at the top of do_transfer_approved when amount.is_zero() (a zero approved-spend is a no-op), or gate the event on !amount.is_zero().
Minor / notes
create/force_createweights are hand-edited (NextAssetIdw:0→w:1, +1 write). Direction is right —AutoIncAssetId::createdwritesNextAssetId(pallets/assets/src/lib.rs:245) — but that write only happens when auto-increment is enabled (NextAssetIdpresent), so this is the conservative worst-case value. Prefer regenerating via the benchmark if practical.- The new zero-amount guards in
do_mint/do_burnshort-circuit before the asset existence/status checks, so a zero-amount op on an unknown/non-live asset now returnsOkinstead ofUnknown/IncorrectStatus. Benign (no-op), just flagging the behavior change.
The CurrencyAdapter is deprecated and not used by the runtime (which uses FungibleAdapter). FungibleAdapter already correctly handles the keep-alive case via WithdrawConsequence::Success check. This reverts commit 7e5c6e0.
n13
left a comment
There was a problem hiding this comment.
Review verdict: Approve
Re-reviewed 151abd0 → 6a1d2035. Both requested changes are resolved, and cargo test -p pallet-assets still passes all 92 tests.
- Dead-code transaction-payment fix — resolved.
CurrencyAdapter::can_withdraw_feeis fully reverted;pallets/transaction-payment/src/payment.rsnow has zero net diff vsmain. The liveFungibleAdapterpath was already correct, so there's no behavior change on the runtime — which is the right outcome. - Zero-amount
do_transfer_approved— resolved. Cleanif amount.is_zero() { return Ok(()) }guard at the top ofdo_transfer_approved(pallets/assets/src/functions.rs:1005-1011), matching thedo_mint/do_burn/transfer_and_diepattern. A zero-value delegated transfer is now a complete no-op (no approval consumed, noTransferredApprovedevent) — consistent with the rest of the PR and benign.
The remaining changes (refund supply/Burned accounting, mint/burn zero guards, Created.owner, set_min_balance, extra_mutator Drop, balances::burn weight, set_reserves/NextAssetId weights) were verified correct in the prior pass and are unchanged here.
LGTM. One non-blocking note: the branch is behind main (#607/#608 not present), so a merge/rebase will be needed before merging.


Fix V12 Audit Items: Assets Pallet Security Fixes
Summary
This PR addresses several security and correctness issues in the assets pallet identified during the V12 audit review.
Changes
1. Fix
ExtraMutatorDrop Not Committing in Release BuildsFile:
pallets/assets/src/extra_mutator.rsThe
Dropimplementation forExtraMutatorplaced theself.commit()call insidedebug_assert!, which meant release builds would silently discard pending sidecar mutations. The fix separates the commit from the assertion:2. Prevent Zero-Amount Mint/Burn Event Emission Bypass
File:
pallets/assets/src/functions.rsZero-amount mint and burn requests bypassed role checks (issuer/admin) because the low-level
increase_balance/decrease_balancefunctions return early for zero amounts, butdo_mint/do_burnstill emitted privileged events afterward.Added early returns in
do_mintanddo_burnfor zero amounts to prevent:Issued/Burnedevents without permission checks3. Emit Missing
TransferredApprovedEventFile:
pallets/assets/src/functions.rsThe
transfer_approveddispatchable documented emission ofTransferredApproved, but only the genericTransferredevent was emitted (which lacks thedelegatefield). Added emission ofTransferredApprovedindo_transfer_approvedto allow off-chain systems to identify delegated transfers.4. Fix Incorrect
ownerField inCreatedEventFile:
pallets/assets/src/lib.rsThe
Createdevent incorrectly used theadminparameter as theownerfield instead of the actual owner fromCreateOrigin. This allowed attackers to cause event consumers to observe arbitrary accounts as owners.5. Fix Hard-Coded
RocksDbWeightinset_reservesWeightFile:
pallets/assets/src/weights.rsSubstrateWeight<T>::set_reserveswas the only weight function using hard-codedRocksDbWeightinstead ofT::DbWeight. This caused incorrect weight calculations for runtimes with different database backends.Testing
All existing tests pass:
Impact
Note
Medium Risk
Touches core asset supply/refund/mint paths and transaction fee validation; incorrect behavior could affect issuance accounting or tx acceptance, but changes are targeted fixes with tests.
Overview
Addresses V12 audit findings across assets, transaction-payment, and minor balances formatting.
Assets — correctness & security:
ExtraMutatorDrop now always runscommit()in release builds (was only insidedebug_assert!).do_refundwithallow_burndecrementssupply, emitsBurned, and keeps totals aligned.do_mint/do_burnreturn early on zero amount so issuer/admin checks are not skipped while events still fire.set_min_balancerejects zero (MinBalanceZero) like create paths.Createdeventowneris the create-origin owner, not theadminparam.do_transfer_approvedemitsTransferredApproved(delegate visible to indexers). Weight docs/fixes:NextAssetIdcounted as written on create/force_create;set_reservesusesT::DbWeightinstead of hard-coded RocksDB.Transaction payment: Fee validation mirrors keep-alive rules so pool checks do not pass when prepare would kill the account below ED.
Regression tests cover refund+burn supply and zero
min_balance.Reviewed by Cursor Bugbot for commit 151abd0. Configure here.