sherlock-audit / 2023-12-ubiquity-judging

2 stars 2 forks source link

cducrest-brainbot - LibTWAPOracle price can be manipualted through falsh loans #197

Closed sherlock-admin closed 6 months ago

sherlock-admin commented 6 months ago

cducrest-brainbot

high

LibTWAPOracle price can be manipualted through falsh loans

Summary

The LibTWAPOracle bases the TWAP price for Ubiquity dollar on a curve meta pool for 3CRV / Ubiquity dollar. The currently deployed version of that pool uses current pool balance to compute the TWAP price which can easily be manipulated though flash loans.

Anyone can manipulate the price of Ubiquity dollar reflected by that pool. This will allow for minting/redeeming Ubiquity dollar on LibUbiquityPool.sol when normally not possible.

Vulnerability Detail

The function to update the price of Ubiquity dollar and 3CRV calls pool.get_dy() with the twap balances.

        uint256[2] memory twapBalances = IMetaPool(ts.pool)
            .get_twap_balances(
                ts.priceCumulativeLast,
                priceCumulative,
                blockTimestamp - ts.pricesBlockTimestampLast
            );

        // price to exchange amountIn Ubiquity Dollar to 3CRV based on TWAP
        ts.price0Average = IMetaPool(ts.pool).get_dy(
            0,
            1,
            1 ether,
            twapBalances
        );

get_dy() gets the amount of CRV out for 1 ether of Ubiquity dollar in. get_twap_balances() gets the average balance across two cumulative prices:

def get_twap_balances(_first_balances: uint256[N_COINS], _last_balances: uint256[N_COINS], _time_elapsed: uint256) -> uint256[N_COINS]:
    balances: uint256[N_COINS] = empty(uint256[N_COINS])
    for x in range(N_COINS):
        balances[x] = (_last_balances[x] - _first_balances[x]) / _time_elapsed
    return balances

The cumulative prices are fetched by LibTWAPOracle via currentCumulativePrices():

    function currentCumulativePrices()
        internal
        view
        returns (uint256[2] memory priceCumulative, uint256 blockTimestamp)
    {
        address metapool = twapOracleStorage().pool;
        priceCumulative = IMetaPool(metapool).get_price_cumulative_last();
        blockTimestamp = IMetaPool(metapool).block_timestamp_last();
    }

This calls on the metapool:

def get_price_cumulative_last() -> uint256[N_COINS]:
    return self.price_cumulative_last

The value of price_cumulative_last is updated in:

@internal
def _update():
    """
    Commits pre-change balances for the previous block
    Can be used to compare against current values for flash loan checks
    """
    elapsed_time: uint256 = block.timestamp - self.block_timestamp_last
    if elapsed_time > 0:
        for i in range(N_COINS):
            _balance: uint256 = self.balances[i]
            self.price_cumulative_last[i] += _balance * elapsed_time
            self.previous_balances[i] = _balance
        self.block_timestamp_last = block.timestamp

This purely takes into account the current balance in token of the contract multiplied by the elapsed time. This balance can easily be manipulated by depositing / withdrawing from the metapool.

The _update() function is called at the beginning of any pool function that impacts its balance.

Impact

An attacker can flash loans and exchange to get 3CRV tokens, deposit a large amount of 3CRV tokens into the metapool, and trigger an update of the cumulative prices via any pool action. This will increase the perceived price of Ubiquity tokens.

This will allow minting Ubiquity tokens in LibUbiquityPool.sol when the minting should have been impossible with fair Ubiquity dollar price.

The opposite behaviour where the perceived price of Ubiquity tokens is decreased is also possible if Ubiquity dollars can somehow be flash loaned / exchanged in flash loans or if the attacker holds LP tokens in the meta pool.

This will bring more instability to the Ubiquity dollar as the mechanism supposed to stabilize the coin can be abused.

Code Snippet

https://github.com/sherlock-audit/2023-12-ubiquity/blob/main/ubiquity-dollar/packages/contracts/src/dollar/libraries/LibTWAPOracle.sol#L68-L102

https://github.com/sherlock-audit/2023-12-ubiquity/blob/main/ubiquity-dollar/packages/contracts/src/dollar/libraries/LibTWAPOracle.sol#L129-L137

https://etherscan.io/address/0x20955CB69Ae1515962177D164dfC9522feef567E#code

Tool used

Manual Review

Recommendation

In LibTWAPOracle only use cumulative prices of previous blocks instead of also relying on the current block.

Duplicate of #56

sherlock-admin2 commented 6 months ago

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

auditsea commented:

Flashloan does not affect TWAP because in Curve, token balances are recorded before any liquidity changes. Refer to Curve's _update function

sherlock-admin2 commented 6 months ago

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

auditsea commented:

Flashloan does not affect TWAP because in Curve, token balances are recorded before any liquidity changes. Refer to Curve's _update function