Ethernaut题库闯关连载的第21篇
今天这篇是Ethernaut 题库闯关连载的第21篇,难度等级: 一般。
欢迎大家订阅专栏:Ethernaut 题库闯关,坚持挑战下去,你的 Solidity代码能力肯定大有提高。
本关的目标是让你破解下面的基本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
方法是如何工作的?本挑战的目标是能够窃取Dex内部的所有代币。本关开始时,Dex拥有(作为流动资金)100个token1
和token2
的代币,而我们只拥有其中的10个。
这个挑战由两个不同的合约组成,让我们来回顾一下它们。
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(去中心化交易所)的基本功能。它允许Dex的 "所有者 "提供一对代币 "token1 "和 "token2 "的流动性,且这些代币被用户兑换时,不收取任何费用。用户将使用Dex来 "兑换"(卖出)特定数量的一个代币,以获得另一个代币的 "swapAmount"(取决于Dex的代币价格)。
让我们回顾一下所有的函数:
function setTokens(address _token1, address _token2) p...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!