← All Posts | findings | December 31, 2024

YieldNest – Back-running the rewards transfer

Paweł Kuryłowicz

Paweł Kuryłowicz

Managing Partner & Smart Contract Security Auditor

The attacker acquires rewards that are generated by other users’ capital.

Vulnerability Details

An attacker can unfairly gain by back-running the transfer with rewards that have not yet been included in totalAssets. This appears due to the assumption that rewards are accumulated discretely. The processAccounting function is designed to update total assets by iterating through the asset list to retrieve and calculate their balances and rates.

function processAccounting() public virtual {
    VaultStorage storage vaultStorage = _getVaultStorage();

    uint256 totalBaseBalance = vaultStorage.countNativeAsset ? address(this).balance : 0;

    AssetStorage storage assetStorage = _getAssetStorage();
    address[] memory assetList = assetStorage.list;
    uint256 assetListLength = assetList.length;
    uint256 baseAssetUnit = 10 ** (assetStorage.assets[asset()].decimals);

    for (uint256 i = 0; i < assetListLength; i++) {
        uint256 balance = IERC20(assetList[i]).balanceOf(address(this));
        if (balance == 0) continue;
        uint256 rate = IProvider(provider()).getRate(assetList[i]);
        totalBaseBalance += balance.mulDiv(rate, baseAssetUnit, Math.Rounding.Floor);
    }

    _getVaultStorage().totalAssets = totalBaseBalance;
    emit ProcessAccounting(block.timestamp, totalBaseBalance);
}

However, the timing of the reward distribution can create an opportunity for an attacker to benefit unfairly. While the function’s purpose is to maintain correct accounting after rewards are distributed, its current design allows an arbitrage opportunity.

After the reward transfer, an attacker can make a significant deposit before the processAccounting function updates the asset rate, then withdraw immediately. Thereby unfairly sharing in the rewards generated by other users’ contributions or withdrawal fees.

In order to make this attack profitable, the increase in share price must be greater than the fee paid for such transaction. The price increase depends on the size of rewards being distributed at once, the vault’s TVL and the size of attacker’s deposit.

Vulnerable scenario

The following actions illustrate this issue:

  1. There are 100 tokens deposited, with a rate of 1 share for every 2 tokens (1:2).
  2. Then, 10 tokens are transferred as rewards.
  3. Before the processAccounting function is executed, the attacker deposits another 100 tokens, thus receiving 50 shares.
  4. Once processAccounting is called, the rate adjusts to 10:21.
  5. The attacker then withdraws 105 tokens for their 50 shares, effectively acquiring 50% of the rewards.

The similar arbitrage opportunity can appear when over 50% of Vault’s TVL is being withdrawn within a short period of time. The root cause is that the fee (assets) is help in the vault and increases the share price by more than the fee value.

Impact

MEDIUM – The attacker acquires rewards that are generated by other users’ capital.

Recommendation

  • Control the size of rewards being added to the vault and make sure it does not make the share’s price increase by more than the fee.
  • Schedule processAccounting to be called at least daily (or as often as possible) to facilitate smooth reward distribution and to reduce the potential for arbitrage.
  • Implement the transfer of rewards through protected RPC (MEV resistant). This would ensure that the attacker’s cannot see the transaction that transfers rewards and detect the opportunity. However, if the rewards distribution depend on other publicly visible events, the attacker would still be able to detect the opportunity.
  • Additionally, consider whether processAccounting should only be available to a role that is trusted, as this will prevent an attacker from using flashloans to create a leverage.

References

Join the newsletter now

Please wait...

Thank you for sign up!