Description:Description\
When a user attempts to swap ROSE tokens using the exactInputStableSwap function in the StableSwapRouter contract, there is a issue i.e where ROSE tokens will be trapped in the contract(and may be swapped by another user). This occurs if the pool contract does not accept ROSE or stops accepting ROSE
Attack Scenario\
lets understand with example
Attachments
Proof of Concept (PoC) File
Here, let's say a user calls exactInputStableSwap with srcToken set to ROSE.
function exactInputStableSwap(
address[] calldata path,
uint256[] calldata flag,
uint256 amountIn,
uint256 amountOutMin,
address to
) external payable nonReentrant returns (uint256 amountOut) {
require(!isKill, "Contract is killed");
address srcToken= path[0];
address dstToken = path[path.length - 1];
// use amountIn == Constants.CONTRACT_BALANCE as a flag to swap the entire balance of the contract
bool hasAlreadyPaid;
if (amountIn == Constants.CONTRACT_BALANCE) {
hasAlreadyPaid = true;
if(srcToken==ROSE){
amountIn = address(this).balance;
}else{
amountIn = IERC20(srcToken).balanceOf(address(this));
}
}
if (!hasAlreadyPaid) {
if(srcToken==ROSE){
require(msg.value>=amountIn, "Invalid msg.value");
}else{
pay(srcToken, msg.sender, address(this), amountIn);
}
}
_swap(path, flag);
Now, this function calls the _swap() function internally
,and in the swap function, as we can see, it calls the exchange function at i.e., IStableSwap(swapContract).exchange{value: amountIn}(k, j, amountIn_, 0);.
function exchange(
uint256 i,
uint256 j,
uint256 dx,
uint256 min_dy
) external payable nonReentrant {
require(!is_killed, "Killed");
if (!support_ROSE) {
require(msg.value == 0, "Inconsistent quantity"); // Avoid sending ROSE by mistake.
}
Now, the exchange() function will be invoked here. This function checks i.e., if the pool contract does not accept ROSE or it stops accepting ROSE, then the function will revert. The user's ROSE tokens will be trapped in the StableSwapRouter contract as the call to exchange function is reverted.
steps to reproduce
The user sends a certain amount of ROSE to the StableSwapRouter contract(as contract has logic for this). This increases the contract's balance of ROSE.
The user calls the exactInputStableSwap function with srcToken set to ROSE and amountIn set to Constants.CONTRACT_BALANCE.
This indicates that the user wants to swap the entire balance of ROSE held by the contract.
The function proceeds to call the _swap() function, passing the path and flags for the swap.
Within _swap(), the exchange function is called on the swapContract with the current balance of ROSE as msg.value.
The exchange function checks if the pool supports ROSE using the support_ROSE flag
If support_ROSE is false, the function reverts with "Inconsistent quantity".
If the exchange function reverts, the entire transaction is rolled back. However, since the ROSE was already in the contract, it remains there.
The ROSE tokens remain in the StableSwapRouter contract because the transaction was reverted after the function call and anyone call swap them.
Github username: -- Twitter username: -- Submission hash (on-chain): 0x7c479257717652a3d2d199486505fede4caca754c6f8189f35e689f897585adb Severity: medium
Description: Description\ When a user attempts to swap
ROSE
tokens using theexactInputStableSwap
function in theStableSwapRouter
contract, there is a issue i.e whereROSE
tokens will be trapped in the contract(and may be swapped by another user). This occurs if thepool contract
does not acceptROSE
or stops acceptingROSE
Attack Scenario\ lets understand with example
Attachments
Proof of Concept (PoC) File
Here, let's say a user calls
exactInputStableSwap
withsrcToken
set toROSE
.Now, this function calls the
_swap()
function internally,and in the swap function, as we can see, it calls the exchange function at i.e., IStableSwap(swapContract).exchange{value: amountIn}(k, j, amountIn_, 0);.
Now, the
exchange()
function will be invoked here. This function checks i.e., if the pool contract does not acceptROSE
or it stops acceptingROSE
, then the function will revert. The user's ROSE tokens will be trapped in theStableSwapRouter
contract as the call to exchange function is reverted.steps to reproduce
The user sends a certain amount of
ROSE
to theStableSwapRouter
contract(as contract has logic for this). This increases the contract's balance of ROSE.The user calls the
exactInputStableSwap
function withsrcToken
set toROSE
andamountIn
set toConstants.CONTRACT_BALANCE
.This indicates that the user wants to swap the entire balance of ROSE held by the contract.
The function proceeds to call the _swap() function, passing the path and flags for the swap.
Within
_swap()
, the exchange function is called on the swapContract with the current balance of ROSE as msg.value.The exchange function checks if the pool supports
ROSE
using thesupport_ROSE
flagIf support_ROSE is false, the function reverts with "Inconsistent quantity".
If the exchange function reverts, the entire transaction is rolled back. However, since the ROSE was already in the contract, it remains there.
The ROSE tokens remain in the StableSwapRouter contract because the transaction was reverted after the function call and anyone call swap them.