Platform: Code4rena
Start Date: 08/09/2023
Pot Size: $70,000 USDC
Total HM: 8
Participants: 84
Period: 6 days
Judge: gzeon
Total Solo HM: 2
Id: 285
League: ETH
Rank: 56/84
Findings: 1
Award: $50.43
🌟 Selected for report: 0
🚀 Solo Findings: 0
50.4324 USDC - $50.43
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L598-L600 https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/InvestmentManager.sol#L612-L614
LiquidityPool.sol
is tokenized vault expected to be ERC-4626 compliant. Certain view methods from the vault call methods on InvestmentManager
to read values but the methods on this contract does not handle rounding according to the standard.
Other protocols acting as investors that integrate with Centrifuge might wrongly assume that functions in the protocol handle rounding as per ERC-4626 specifications. For this reason I evaluate this vulnerability to MEDIUM.
The ERC-4626 standard describe certain security considerations for the view
functions regarding rounding:
Finally, EIP-4626 Vault implementers should be aware of the need for specific, opposing rounding directions across the different mutable and view methods, as it is considered most secure to favor the Vault itself during calculations over its users:
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.
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.
It is the second point that is not follow in InvestmentManager::_calculateTrancheTokenAmount
according to the standard.
LiquidityPool::previewWithdraw
can be used by investors to know how much shares they need to redeem in order to receive an amount of assets from the pool.
This function calls InvestmentManager::previewWithdraw
, and here the rounding issues appear.
/// @return trancheTokenAmount is type of uin256 to support the EIP4626 Liquidity Pool interface 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));
The function then calls _calculateTrancheTokenAmount
to find how much tokens the investor will receive.
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); }
In this function currencyAmountInPriceDecimals
is calculated and rounded down when it should be rounded up.
Manual review.
Round up instead of rounding down to follow the Security Considerations of the standard.
diff --git a/InvestmentManager.sol.orig b/src/InvestmentManager.sol index fe94965..500ff61 100644 --- a/InvestmentManager.sol.orig +++ b/src/InvestmentManager.sol @@ -596,7 +596,7 @@ contract InvestmentManager is Auth { (uint8 currencyDecimals, uint8 trancheTokenDecimals) = _getPoolDecimals(liquidityPool); uint256 currencyAmountInPriceDecimals = _toPriceDecimals(currencyAmount, currencyDecimals, liquidityPool).mulDiv( - 10 ** PRICE_DECIMALS, price, MathLib.Rounding.Down + 10 ** PRICE_DECIMALS, price, MathLib.Rounding.Up ); trancheTokenAmount = _fromPriceDecimals(currencyAmountInPriceDecimals, trancheTokenDecimals, liquidityPool);
This issue can also be found in previewMint
function that makes a call to _calculateCurrencyAmount
which also round down instead of rounding up.
ERC4626
#0 - c4-pre-sort
2023-09-14T23:13:51Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-09-14T23:14:10Z
raymondfam marked the issue as duplicate of #34
#2 - c4-judge
2023-09-26T18:11:40Z
gzeon-c4 marked the issue as satisfactory