Ethernaut: 22. Dex (modified version) - Writeups

thispost
3 min readOct 12, 2021

--

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:

  1. 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
  2. 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.

Listing 1 - Swap 24 token1 to token2.

After the prior step, we now have 30 token2.

Listing 2 - 30 token2 received after swapping from token1.

In the last swapping, we’ll have 65 token2

Listing 3 - 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

Listing 4 - 110 token1 and 45 token2.

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.

Listing 5 - Swap 45 token2 to 110 token1.

token1 is drained and has 0 amount, we’ve successfully solved the challenge.

Listing 6 - token1 is drained.

--

--