Ethernaut 题库闯关 #21 — Dex

Ethernaut题库闯关连载的第21篇

今天这篇是Ethernaut 题库闯关连载的第21篇,难度等级: 一般。

欢迎大家订阅专栏:Ethernaut 题库闯关,坚持挑战下去,你的 Solidity代码能力肯定大有提高。

挑战#21 Dex

本关的目标是让你破解下面的基本DEX 合约,并通过价格操纵窃取资金。

挑战前,我们手里有10个 token1和10个 token2, DEX合约开始时则每个代币有100个(流动性)。如果能从合约中至少抽空2个代币中的1个,并让合约返回"坏"的兑换价格,就能在这一关中取得成功。

合约源码如下:

// 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);
  }
}

提示:

通常情况下,当你用ERC20代币进行兑换时,必须先 approve(授权) 合约可以花费我们的代币。为了与游戏的语法保持一致,我们把 approve 方法添加到合约本身。因此,请随意使用 contract.approve(contract.address, <uint amount>),它会自动授权花费两个代币的所需金额。

完成这个挑战,我们需要了解:

  • 代币的价格是如何计算的?
  • swap方法是如何工作的?
  • 如何 "授权 "一个ERC20的交易?

本挑战的目标是能够窃取Dex内部的所有代币。本关开始时,Dex拥有(作为流动资金)100个token1token2的代币,而我们只拥有其中的10个。

研究合约

这个挑战由两个不同的合约组成,让我们来回顾一下它们。

SwappableToken.sol

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

这是一个简单的ERC20代币,在构造函数中为msg.sender发行initialSupply数量,并重写了approve函数,以防止_dex地址能够授权任何代币。

这里没有什么特别的东西可看

Dex.sol

该合约实现了Dex(去中心化交易所)的基本功能。它允许Dex的 "所有者 "提供一对代币 "token1 "和 "token2 "的流动性,且这些代币被用户兑换时,不收取任何费用。用户将使用Dex来 "兑换"(卖出)特定数量的一个代币,以获得另一个代币的 "swapAmount"(取决于Dex的代币价格)。

让我们回顾一下所有的函数:


function setTokens(address _token1, address _token2) p...

Ethernaut 题库闯关 #21 — Dex插图

剩余50%的内容订阅专栏后可查看

  • 发表于 2022-09-15 22:16
  • 阅读 ( 298 )
  • 学分 ( 0 )
  • 分类:安全
  • 专栏:Ethernaut 题库闯关

评论