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 함수는 previewDeposit와 processDeposit에서도 사용된다. 이 때는 버림을 하는 게 맞다.
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.
따라서 deposit과 withdraw에서 서로 처리를 다르게 할 수 있도록 함수를 수정하고, 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