Lucene search

K
code423n4Code4renaCODE423N4:2022-08-FRAX-FINDINGS-ISSUES-292
HistoryAug 17, 2022 - 12:00 a.m.

Possible frontrunning attack in Vault.

2022-08-1700:00:00
Code4rena
github.com
4

Lines of code
<https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L583-L595&gt;
<https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L676-L685&gt;

Vulnerability details

Proof of Concept

The project uses VaultAccount.sol library for math implementations. To determine the number of shares to mint to a depositor, shares = (amount * total.shares) / total.amount is used in toShares function

VaultAccount.sol’s toShares calculation;

    function toShares(
        VaultAccount memory total,
        uint256 amount,
        bool roundUp
    ) internal pure returns (uint256 shares) {
        if (total.amount == 0) {
            shares = amount;
        } else {
            shares = (amount * total.shares) / total.amount;
            if (roundUp && (shares * total.amount) / total.shares &lt; amount) {
                shares = shares + 1;
            }
        }
    }

<https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/libraries/VaultAccount.sol#L16-L29&gt;

Potential attackers can spot a call to FraxlenPairCore.deposit() and front-run it with a transaction that sends tokens to the contract, causing the victim to receive fewer shares than what he expected.
In case total.amount is greater than (amount * total.shares), then the number of shares the depositor receives will be 0, although _amount of tokens will be still pulled from the depositor’s balance.

FraxlendPairCore.sol’s deposit function;

    function deposit(uint256 _amount, address _receiver)
        external
        nonReentrant
        isNotPastMaturity
        whenNotPaused
        approvedLender(_receiver)
        returns (uint256 _sharesReceived)
    {
        _addInterest();
        VaultAccount memory _totalAsset = totalAsset;
        _sharesReceived = _totalAsset.toShares(_amount, false);
        _deposit(_totalAsset, _amount.toUint128(), _sharesReceived.toUint128(), _receiver);
    }

<https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L583-L595&gt;

An attacker with access to enough liquidity and to the mem-pool data can spot a call to FraxlenPairCore.deposit(uint256 _amount, address _receiver)) and front-run it by sending at least totalSupplyBefore * (amountIn - 1) + 1 tokens to the contract . This way, the victim will get 0 shares, but _amount will still be pulled from its account balance. Now the price for a share is inflated, and all shareholders can redeem this profit using FraxlenPairCore.withdraw().

FraxlendPairCore.sol’s withdraw function;

    function withdraw(
        uint256 _amount,
        address _receiver,
        address _owner
    ) external nonReentrant returns (uint256 _shares) {
        _addInterest();
        VaultAccount memory _totalAsset = totalAsset;
        _shares = _totalAsset.toShares(_amount, true);
        _redeem(_totalAsset, _amount.toUint128(), _shares.toUint128(), _receiver, _owner);
    }

<https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L676-L685&gt;

The attack vector mentioned above is the general front runner case, the most profitable attack vector will be the case when the attacker is able to determine the share price (for instance if the attacker mints the first share). In this scenario, the attacker will need to send at least attackerShares * (amountIn -1) + 1 to the contract,(attackerShares is completely controlled by the attacker), and this amount can be then entirely redeemed by the attacker himself (alongside the victim’s deposit) by calling FraxlenPairCore.withdraw(). The attacker can lower the risk of losing the funds he sent to the contract to some other front-runner by using the flashbots api.

Tools Used

Manual Review

Recommended Mitigation Steps

Ensure the number of shares to be minted is non-zero: require(_shares != 0, “zero shares minted”);


The text was updated successfully, but these errors were encountered:

All reactions