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: 50/84
Findings: 1
Award: $50.43
🌟 Selected for report: 0
🚀 Solo Findings: 0
50.4324 USDC - $50.43
ERC-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 contract itself during calculations over its users: If it’s calculating how many shares to issue to a user for a certain amount of the underlying tokens they provide or 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 it’s calculating the amount of shares a user has to supply to receive a given amount of the underlying tokens or it’s calculating the amount of underlying tokens a user has to provide to receive a certain amount of shares, it should round up. Meanwhile in our InvestmentManager contract, both convertToShares() and convertToAssets() were both rounded down whereas it shouldn't be so, convertToAssets() should be rounded up.
In our convertToShares(), it's calculating the amount of shares or tranche tokens that any user would get for the amount of currency or assets provided, therefore, we are depositing not withdrawing and it should be rounded down.
function convertToShares(uint256 _assets, address liquidityPool) public view auth returns (uint256 shares) { (uint8 currencyDecimals, uint8 trancheTokenDecimals) = _getPoolDecimals(liquidityPool); uint128 assets = _toUint128(_assets); shares = assets.mulDiv( 10 ** (PRICE_DECIMALS + trancheTokenDecimals - currencyDecimals), LiquidityPoolLike(liquidityPool).latestPrice(), MathLib.Rounding.Down ); }
But in the convertToAssets(), it's calculating the asset value for an amount of shares / tranche tokens provided. Since it's withdrawing not depositing it should be rounded up.
function convertToAssets(uint256 _shares, address liquidityPool) public view auth returns (uint256 assets) { (uint8 currencyDecimals, uint8 trancheTokenDecimals) = _getPoolDecimals(liquidityPool); uint128 shares = _toUint128(_shares); assets = shares.mulDiv( LiquidityPoolLike(liquidityPool).latestPrice(), 10 ** (PRICE_DECIMALS + trancheTokenDecimals - currencyDecimals), //@audit should round up because you are withdrawing not depositing MathLib.Rounding.Down ); }
Manual Review
Follow the due procedure recommended by the ERC4626 standard and use a rounding up(Rounding.up) for the convertToAssets()
function convertToAssets(uint256 _shares, address liquidityPool) public view auth returns (uint256 assets) { (uint8 currencyDecimals, uint8 trancheTokenDecimals) = _getPoolDecimals(liquidityPool); uint128 shares = _toUint128(_shares); assets = shares.mulDiv( LiquidityPoolLike(liquidityPool).latestPrice(), 10 ** (PRICE_DECIMALS + trancheTokenDecimals - currencyDecimals), //@audit should round up because you are withdrawing not depositing MathLib.Rounding.Up ); }
ERC4626
#0 - c4-pre-sort
2023-09-16T00:38:15Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-09-16T00:38:29Z
raymondfam marked the issue as duplicate of #34
#2 - c4-judge
2023-09-26T18:11:11Z
gzeon-c4 marked the issue as satisfactory