57586 sc high calculating slippage for swap onchain does not prevent slippage loss
Submitted on Oct 27th 2025 at 11:06:06 UTC by @kaysoft for Audit Comp | Belong
Report ID: #57586
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/platform/BelongCheckIn.sol
Impacts:
Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Description
Brief / Intro
Calculating slippage onchain does not prevent slippage loss because at the time the victim's transaction is calling the quoteExactInput(...) function to calculate amountMinOut, the pool has already been manipulated and price has shifted.
Vulnerability Details
The _swapExact(...) function is used for swaps between USDC and LONG token in the venueDeposit(...), payToVenue(...), and distributePromoterPayments(...) functions of BelongCheckIn.sol.
The issue is that the amountOutMinimum (used for slippage protection) is calculated inside the same transaction. If a large swap that buys the output token (Long) has already been executed before venueDeposit(...) is executed, amountOutMinimum calculation will only use the already-adjusted price since blockchain execution is atomic.
The _swapExact(...) function calls quoteExactInput(...) to derive a fraction as amountOutMinimum. Because execution is atomic, even after the price has been impacted by a prior on-chain swap in the same block, quoteExactInput(...) reads the impacted price and returns a fraction of that impacted price. Thus calculating slippage onchain in this way provides no effective protection against sandwich/frontrun-style price moves.
Vulnerable snippet:
Impact Details
Swaps executed in venueDeposit(...), payToVenue(...), and distributePromoterPayments(...) can be executed at a loss if an attacker performs a large swap that moves the price of the output token before these functions execute. Because amountOutMinimum is computed onchain after the attacker has manipulated the pool, the computed minimum will reflect the manipulated (worse) price and provide no effective protection for the user or protocol.
Recommendation
Use an off-chain/signed or externally submitted amountOutMinimum
Consider passing amountOutMinimum as an input parameter to the contract (provided and reviewed by the caller before broadcasting the transaction). DEX frontends typically call quoteExactInput(...) (or use a TWAP) off-chain and present the user with an amountOutMinimum they can tweak before sending the transaction. This ensures the amountOutMinimum used by the swap is not read on-chain after a prior manipulation.
Allow deadline to be provided by caller (not block.timestamp)
Allow the deadline to be passed as an input parameter instead of using block.timestamp. Using block.timestamp at call time makes the deadline effectively dynamic (and will not cause reverts in front-running scenarios). The frontend should calculate a concrete deadline (e.g., current unix timestamp + 20 minutes) and pass it in to help prevent replay/failing-attempts beyond a reasonable window.
Proof of Concept
The following demonstrates how a sandwich/frontrun can buy a large amount of the output token (CAKE used as stand-in for LONG), moving price before venueDeposit(...) executes. Because venueDeposit(...) recalculates amountOutMinimum on-chain after the price has moved, the escrow receives significantly less output token.
Test steps to reproduce (add to the existing test file as described):
Copy and paste the test below into the
belong-check-in-bsc-fork.test.tsfile in thedescribe('Customer flow usdc payment', () => {test suite.Run
yarn test.The test demonstrates a large CAKE buy executed before
venueDeposit(...)which causes the escrow to receive less CAKE.
Test code:
To run the test without the frontrun (control case), comment out the Step 2 exactInput call:
Logs observed:
When
venueDeposit(...)is frontrun with a large CAKE buy swap:Escrow balance after: BigNumber { value: "2314060485415" } == 0.000002314060485415 CAKE
When
venueDeposit(...)executed without being frontrun:Escrow balance after: BigNumber { value: "1873394195571341465" } == 1.873394195571341465 CAKE
These logs show the severe reduction in received output token when the on-chain slippage quote is obtained after the pool has already been manipulated.
Notes / Additional Observations
The snippet also uses
block.timestampasdeadline. Passing a precomputed deadline from the frontend is preferable so transactions can be rejected if they’re executed unexpectedly late.Preventing this class of attack generally requires moving price checks off-chain (or using protected primitives like TWAP or Keeper-supplied prices) or allowing the caller to specify and sign the acceptable
amountOutMinimum.
Was this helpful?