Precompile advanced order#2685
Conversation
|
🔄 AI review updated — Skeptic: VULNERABLE |
| /// Thin orchestrator for `execute_batched_orders`. | ||
| fn do_execute_batched_orders( |
There was a problem hiding this comment.
[HIGH] Batched execution is not atomic after collecting user assets
do_execute_batched_orders performs multiple fallible mutations in sequence: it first pulls buyer TAO and seller stake into the pallet account, then swaps, distributes outputs, writes order status, and finally forwards fees. Because the function has no transaction boundary, any failure after an earlier transfer leaves those earlier asset movements committed while the extrinsic returns an error. A malformed batch can therefore strand funds/stake in the pallet intermediary or partially settle only some orders. Wrap the whole batch execution in a FRAME transaction, or otherwise prove every later fallible operation before moving assets.
| /// Thin orchestrator for `execute_batched_orders`. | |
| fn do_execute_batched_orders( | |
| /// Thin orchestrator for `execute_batched_orders`. | |
| #[frame_support::transactional] | |
| fn do_execute_batched_orders( |
| )?; | ||
|
|
||
| // Forward the fee TAO to the order's fee recipient. | ||
| Self::forward_fee(&order.signer, &order.fee_recipient, fee_tao); |
There was a problem hiding this comment.
[MEDIUM] Direct buy execution can fill an order without collecting its fee
The buy path subtracts fee_tao from the signed amount and calls buy_alpha with only tao_after_fee. buy_alpha(..., validate: true) therefore only checks and removes the net amount from the signer. The fee transfer is attempted afterward through forward_fee, which swallows transfer failures, so a signer with enough balance for the net swap but not the fee can still receive alpha and have the order marked fulfilled without paying the signed fee. Collect/escrow the gross TAO up front, or make fee collection a mandatory part of a transaction-wrapped single-order execution.
|
🔄 AI review updated — Skeptic: VULNERABLE |
| } | ||
|
|
||
| /// Thin orchestrator for `execute_batched_orders`. | ||
| fn do_execute_batched_orders( |
There was a problem hiding this comment.
[HIGH] Batched execution is not atomic after collecting user assets
execute_batched_orders still enters do_execute_batched_orders without a transaction boundary, then debits user TAO/alpha in collect_assets before later fallible ? calls in the pool swap and distribution steps. If any later step fails, prior balance/stake transfers and any pool mutation can persist while the extrinsic returns an error, leaving user assets stranded or partially settled. Wrap the whole orchestrator in a transactional boundary, or move all fallible validation ahead of collection and make the post-collection path infallible.
| fn do_execute_batched_orders( | |
| #[frame_support::transactional] | |
| fn do_execute_batched_orders( |
| )?; | ||
|
|
||
| // Forward the fee TAO to the order's fee recipient. | ||
| Self::forward_fee(&order.signer, &order.fee_recipient, fee_tao); |
There was a problem hiding this comment.
[MEDIUM] Direct buy execution can fill an order without collecting its fee
For buy orders, buy_alpha only debits tao_after_fee; the fee is then attempted with best-effort forward_fee, which emits FeeTransferFailed but does not revert. A signer funded with exactly tao_after_fee can receive the alpha, have the order marked executed, and avoid paying the signed fee. Collect the gross amount atomically, or make fee transfer failure revert before marking the order filled.
|
🔄 AI review updated — Skeptic: VULNERABLE |
…e-advanced-orders
| } | ||
|
|
||
| /// Thin orchestrator for `execute_batched_orders`. | ||
| fn do_execute_batched_orders( |
There was a problem hiding this comment.
[HIGH] Batched execution is not atomic after collecting user assets
do_execute_batched_orders performs a multi-step settlement without a #[transactional] boundary. After collect_assets pulls gross TAO/alpha into the pallet account, later fallible calls (net_pool_swap, pro-rata distributions, order-status writes, and collect_fees) can still return Err. FRAME does not automatically roll back prior balance/storage mutations on DispatchError, so a failed batch can leave users debited, the pallet credited, or orders partially settled. Wrap the whole batched settlement in a transaction, or pre-validate every later fallible condition before moving assets.
| let fee_tao = TaoBalance::from(order.fee_rate.mul_floor(tao_in.to_u64())); | ||
| let tao_after_fee = tao_in.saturating_sub(fee_tao); | ||
|
|
||
| let alpha_out = T::SwapInterface::buy_alpha( |
There was a problem hiding this comment.
[MEDIUM] Direct buy execution can fill an order without collecting its fee
The buy path spends only tao_after_fee in buy_alpha, then tries to transfer the fee afterward. A signer with enough balance for tao_after_fee but not the fee can get the alpha purchase applied, then make forward_fee fail. In best-effort execute_orders (should_fail = false), that error is swallowed as OrderSkipped, and there is no transactional wrapper around try_execute_order to roll back the already-applied buy. Collect/reserve the gross input before the swap, or make each per-order attempt transactional so skipped failures cannot retain prior side effects.
|
🔄 AI review updated — Skeptic: VULNERABLE |
| } | ||
|
|
||
| /// Thin orchestrator for `execute_batched_orders`. | ||
| fn do_execute_batched_orders( |
There was a problem hiding this comment.
[HIGH] Batched execution is not atomic after collecting user assets
do_execute_batched_orders collects buyer TAO and seller alpha into the pallet account, then performs several later fallible steps: residual pool swap, pro-rata distributions, order status writes, and fee forwarding. Because this orchestrator is not transactional, any error after collect_assets can return Err while already-moving assets or partially-written settlement state remains committed. This can strand user funds in the pallet intermediary or leave a partially settled batch. Wrap the whole batch orchestrator in a storage transaction so any later failure rolls back the prior transfers and status changes.
| fn do_execute_batched_orders( | |
| #[frame_support::transactional] | |
| fn do_execute_batched_orders( |
|
|
||
| /// Attempt to execute one signed order. Returns an error on any | ||
| /// validation or execution failure without panicking. | ||
| fn try_execute_order( |
There was a problem hiding this comment.
[HIGH] Single-order execution is not atomic after swap side effects
try_execute_order performs the buy/sell swap before later fallible operations such as fee forwarding and status insertion. For a buy order, buy_alpha only validates/removes tao_after_fee; if the signer has enough balance for the net swap but not the fee, the swap succeeds, forward_fee fails, execute_orders treats the order as skipped, and the order remains executable without the fee being paid. The same missing transaction also makes should_fail = true misleading: returning Err does not by itself roll back prior successful order executions. Make this helper transactional so every failed order attempt reverts its own swap/fee/status side effects.
| fn try_execute_order( | |
| #[frame_support::transactional] | |
| fn try_execute_order( |
|
🔄 AI review updated — Skeptic: VULNERABLE |
Description
Related Issue(s)
Type of Change
Breaking Change
If this PR introduces a breaking change, please provide a detailed description of the impact and the migration path for existing applications.
Checklist
./scripts/fix_rust.shto ensure my code is formatted and linted correctlyScreenshots (if applicable)
Please include any relevant screenshots or GIFs that demonstrate the changes made.
Additional Notes
Please provide any additional information or context that may be helpful for reviewers.