Preface
Please refer to the old writeups.
The challenge’s goal
Please refer to the old writeups.
Code analysis
The modified version of 22. Dex is only added an additional require()
statement to prevent the malicious token swapping as we did in the old writeups when performing swap()
.
function swap(address from, address to, uint amount) public {
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
...(SNIP)...
However, the swapping price is still calculated based on the token amount of the contract.
function get_swap_amount(address from, address to, uint amount) public view returns(uint){
return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
}
Suppose that we swap 10 token1 to token2, we’ll receive 10 token2 since the initial balance of token1 and token2 is 100 (price ration is 1:1). The price will update according to the current balance of each token i.e., 90 token2 per 110 token1.
Vulnerability Analysis
Since the price relies on the token amount of the contract, we could swap reversibly until either token1 or token2 has 0 amount. In addition, the mathematic precision issue exists due to the fact that Solidity is no floating-point currently, so, the number is round down e.g., 24.444 to 24. We can illustrate the swapping as follows:
- Initial balance.
Player’s balance:
10 token1
10 token2
Contract’s balance:
100 token1
100 token2
The received amount if swap 10 token1 to token2:
(10 * 100) / 100 = 10 token2 - Player’s balance (swap 10 token1 to token2) :
0 token1
20 token2
Contract’s balance:
110 token1
90 token2
Received amount if swap 20 token2 to token1:
(20 * 110) / 90 ~ 24.444 = 24 token1 (round down)
If we notice the received amount on step 2, more token is obtained.
Exploitation
There is nothing special, just swap it using the maximum balance of the token we currently hold until the one side of the token has 0 amount.
Now, we have 24 token1 and 0 token2, next, we just swap 24 token1 to token2.
After the prior step, we now have 30 token2.
In the last swapping, we’ll have 65 token2
We cannot swap 65 token2 to 158** token1 since token1 is not enough as of the current price. The contract currently has 110 token1 and 45 token2.** 65 * (110/45) = 158
If we need to drain all 110 token1, the amount of token2 to be swapped is:
(65 * 110) / 158 = 45
Swap 45 token2 to 110 token1 as shown in Listing 5.
token1 is drained and has 0 amount, we’ve successfully solved the challenge.