Impacts: Contract fails to deliver promised returns, but doesn't lose value
Description
Brief / Intro
The _buildPath function constructs a Uniswap V3 swap path dynamically, choosing either a direct pool or routing through a configured wrapped native token. However, in the two-hop scenario, the function only checks if the first pool exists (tokenIn → W_NATIVE) and assumes the second pool (W_NATIVE → tokenOut) exists. If the second pool does not exist, the swap will revert at execution, causing the transaction to fail.
Vulnerability Details
In the current implementation, the function only validates the existence of the first hop in a two-hop swap path and assumes that the second hop (from the wrapped native token to the final output token) exists. As a result, the function can produce a path that is invalid, leading to failed swaps at runtime when executed through the Uniswap V3 router. If the second pool does not exist, a swap call using this path will revert, blocking users from completing the transaction.
Code excerpt:
function_buildPath(PaymentsInfomemory_paymentsInfo,addresstokenIn,addresstokenOut)internalviewreturns(bytesmemorypath){// Direct poolif(IV3Factory(_paymentsInfo.swapV3Factory).getPool(tokenIn, tokenOut, _paymentsInfo.swapPoolFees)!=address(0)){ path =abi.encodePacked(tokenIn, _paymentsInfo.swapPoolFees, tokenOut);}// tokenIn -> W_NATIVE_CURRENCY -> tokenOut// @audit - W_NATIVE_CURRENCY to tokenOut pool validation is missingelseif(IV3Factory(_paymentsInfo.swapV3Factory).getPool(tokenIn, _paymentsInfo.wNativeCurrency, _paymentsInfo.swapPoolFees)!=address(0)){ path =abi.encodePacked( tokenIn, _paymentsInfo.swapPoolFees, _paymentsInfo.wNativeCurrency, _paymentsInfo.swapPoolFees, tokenOut);}else{revertNoValidSwapPath();}}
Impact Details
Failed swaps stop the protocol from sending the expected output tokens to promoters, escrows, or venues, resulting in the protocol not delivering the promised returns. The issue is classified as LOW severity because it prevents the expected transfer but does not result in direct loss of funds from the protocol.
The following steps reproduce the issue by mocking Uniswap V3 components and showing a missing second hop causes a swap to fail at execution time.
1
1) Add mock Uniswap V3 contracts
Create contracts/mocks/MockUniswapV3.sol with:
2
2) Add helper to set up mocks in tests
Add the following helper to test/v2/platform/belong-check-in.test.ts:
3
3) Update the test fixture to inject mock Uniswap addresses
Update fixture in test/v2/platform/belong-check-in.test.ts to call setUpMockUniswap() and set mockPaymentsInfo using returned addresses:
4
4) Add the test case that demonstrates the failing swap
Add this test inside the Venue Flow describe block in test/v2/platform/belong-check-in.test.ts:
5
5) Run tests
Run:
Expected behavior demonstrated by the test:
The path builder does not revert with NoValidSwapPath because it validated only the first hop.
The Uniswap router simulation reverts during swap execution due to the missing second hop, causing SwapFailed.
Notes:
The root cause is missing validation of the second hop (W_NATIVE → tokenOut) when building a two-hop path.
Fixing the issue requires adding an extra pool existence check for the second hop before returning the two-hop encoded path. The report does not include a suggested patch; it only documents the detection and reproduction.