code4rena-2023-09-centrifuge-m07

[M-07] trancheTokenAmount should be rounded UP when proceeding to a withdrawal or previewing a withdrawal

보고서

Summary

eip-4626 문서에서 제안하는 올림/버림 규칙을 따르지 않았다.

Keyword

arithmetic error, rounding error, erc4626

Vulnerability

processWithdraw를 호출해 트랜치 토큰을 반환하고 asset 토큰을 돌려받을 수 있다. 반환할 트랜치 토큰 수를 _calculateTrancheTokenAmount 함수에서 계산한다. 이 때 나눗셈에서 버림을 한다. 즉, 반환할 트랜치 토큰 수는 실제보다 적게 잡힌다.

previewWithdraw는 asset 토큰을 출금하기 위해 얼마나 많은 트랜치 토큰이 필요한지 조회하는 함수이다. 이 함수도 마찬가지로 _calculateTrancheTokenAmount를 이용한다.

    function processWithdraw(uint256 currencyAmount, address receiver, address user)
        public
        auth
        returns (uint256 trancheTokenAmount)
    {
        address liquidityPool = msg.sender;
        uint128 _currencyAmount = _toUint128(currencyAmount);
        require(
            (_currencyAmount <= orderbook[user][liquidityPool].maxWithdraw && _currencyAmount != 0),
            "InvestmentManager/amount-exceeds-withdraw-limits"
        );
 
        uint256 redeemPrice = calculateRedeemPrice(user, liquidityPool);
        require(redeemPrice != 0, "LiquidityPool/redeem-token-price-0");
 
@>      uint128 _trancheTokenAmount = _calculateTrancheTokenAmount(_currencyAmount, liquidityPool, redeemPrice);
        _redeem(_trancheTokenAmount, _currencyAmount, liquidityPool, receiver, user);
        trancheTokenAmount = uint256(_trancheTokenAmount);
    }
 
    function previewWithdraw(address user, address liquidityPool, uint256 _currencyAmount)
        public
        view
        returns (uint256 trancheTokenAmount)
    {
        uint128 currencyAmount = _toUint128(_currencyAmount);
        uint256 redeemPrice = calculateRedeemPrice(user, liquidityPool);
        if (redeemPrice == 0) return 0;
 
@>      trancheTokenAmount = uint256(_calculateTrancheTokenAmount(currencyAmount, liquidityPool, redeemPrice));
    }
 
    function _calculateTrancheTokenAmount(uint128 currencyAmount, address liquidityPool, uint256 price)
        internal
        view
        returns (uint128 trancheTokenAmount)
    {
        (uint8 currencyDecimals, uint8 trancheTokenDecimals) = _getPoolDecimals(liquidityPool);
 
        uint256 currencyAmountInPriceDecimals = _toPriceDecimals(currencyAmount, currencyDecimals, liquidityPool).mulDiv(
@>          10 ** PRICE_DECIMALS, price, MathLib.Rounding.Down
        );
 
        trancheTokenAmount = _fromPriceDecimals(currencyAmountInPriceDecimals, trancheTokenDecimals, liquidityPool);
    }

asset 토큰을 특정량 돌려받기 위해 필요한 트랜치 토큰 수를 구할 때는 올림으로 계산하는 것이 모범적이라고 eip-4626 문서에서도 언급한다.

If (1) it’s calculating the amount of shares a user has to supply to receive a given amount of the underlying tokens or (2) it’s calculating the amount of underlying tokens a user has to provide to receive a certain amount of shares, it should round up.

Impact

ERC4626 best practice가 아니다. eip-4626 문서를 따라야 한다.

Mitigation

_calculateTrancheTokenAmount 함수는 previewDepositprocessDeposit에서도 사용된다. 이 때는 버림을 하는 게 맞다.

If (1) it’s calculating how many shares to issue to a user for a certain amount of the underlying tokens they provide or (2) it’s determining the amount of the underlying tokens to transfer to them for returning a certain amount of shares, it should round down.

따라서 depositwithdraw에서 서로 처리를 다르게 할 수 있도록 함수를 수정하고, withdraw 시에는 올림을 한다.

function _calculateTrancheTokenAmount(uint128 currencyAmount, address liquidityPool, uint256 price, Math.Rounding rounding)
    internal
    view
    returns (uint128 trancheTokenAmount)
{
    (uint8 currencyDecimals, uint8 trancheTokenDecimals) = _getPoolDecimals(liquidityPool);

    uint256 currencyAmountInPriceDecimals = _toPriceDecimals(currencyAmount, currencyDecimals, liquidityPool).mulDiv(
    10 ** PRICE_DECIMALS, price, MathLib.Rounding.Down
    );

    trancheTokenAmount = _fromPriceDecimals(currencyAmountInPriceDecimals, trancheTokenDecimals, liquidityPool);
}

tags: bughunting, centrifuge, smart contract, solidity, arithmetic error, rounding error, erc4626, severity medium