[Ethernaut CTF] Dex

0xDave·2022년 10월 11일
0

Ethereum

목록 보기
44/112

소스코드


// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';

contract Dex is Ownable {
  using SafeMath for uint;
  address public token1;
  address public token2;
  constructor() public {}

  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) 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 goal of this level is for you to hack the basic DEX 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?

컨트랙트에 있는 token1 or token2 탈취하기

해결과정


SwappableToken 컨트랙트를 통해서 Dex 컨트랙트가 Attack 컨트랙트에 approve 하도록 하면 transferFrom으로 토큰을 가져올 수 있지 않을까..?

답은 생각보다 간단했다. 내가 너무 복잡하게 생각했던 것 같다. 핵심은 getSwapPrice의 공식이다. 단순히 나눗셈을 하면 solidity에서는 소숫점을 내림으로 만든다. 따라서 token1을 전부 tokean2로 바꾸고 나서 다시 token2를 전부 token1으로 바꿀 때 비율이 달라진다. 20 * 110 / 90 = 24.4444로 다시 바꾸기만 했는데 얻을 수 있는 수량이 많아졌다.

이런 식으로 반복하다보면 결국 컨트랙트에 있는 token1이 바닥난다.

직접 해볼 수 있으면 정말 좋을텐데,, 네트워크 자체가 사라져서 너무 아쉽다.

출처 및 참고자료


  1. Ethernaut Hacks Level 22: Dex
profile
Just BUIDL :)

0개의 댓글