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: 10/84
Findings: 2
Award: $1,155.88
🌟 Selected for report: 0
🚀 Solo Findings: 0
1143.0858 USDC - $1,143.09
Incorrect calculations in _fromPriceDecimals(...)
in the InvestmentManager contract may cause problems with other functions, because the return value is 0 due to the specifics of calculations in solidity.
uint8 public constant PRICE_DECIMALS = 18;
value = _toUint128(_value / 10 ** (PRICE_DECIMALS - decimals));
If _value = 10**9
and decimals = 8
then
value = 10**9 / 10 ** (18 - 8) = 10**9 / 10 ** 10 = 0
We can try with other values as well:
_value = 10**11
, decimals = 6
value = 10**11 / 10 ** (18 - 6) = 10**11 / 10 ** 12 = 0
This happens because we divide a smaller number by a larger one
I wrote an automated test using Foundry for all values of decimals <= 18
and _value <= 10**18
.
3 values will be output to the log
VALUE - DECIMALS - CALCULATED_VALUE
create file /test/POC_fromPriceDecimals.t.sol
paste code:
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity 0.8.21; import "forge-std/Test.sol"; import "forge-std/console2.sol"; contract POC_fromPriceDecimals is Test { uint8 public constant PRICE_DECIMALS = 18; function _toUint128(uint256 _value) internal pure returns (uint128 value) { if (_value > type(uint128).max) { revert("InvestmentManager/uint128-overflow"); } else { value = uint128(_value); } } function _fromPriceDecimals(uint256 _value, uint8 decimals) internal view returns (uint128 value) { if (PRICE_DECIMALS == decimals) return _toUint128(_value); value = _toUint128(_value / 10 ** (PRICE_DECIMALS - decimals)); } function test_fromPriceDecimals() public view { uint8 max_decimals = 18; uint8 max_value_n = 18; // decimals for (uint8 _decimals = 0; _decimals <= max_decimals; _decimals++) { // value for (uint8 value_n = 0; value_n <= max_value_n; value_n++) { uint256 value = 10 ** value_n; uint128 calculated_value = _fromPriceDecimals(value, _decimals); // log format: // VALUE - DECIMALS - CALCULATED_VALUE console2.log(_decimals, value, calculated_value); } } } }
run test:
$ forge test --mt test_fromPriceDecimals -vvv
Manual analysis, Foudry, Chisel
Check the return value to be greater than 0, or make sure that the numerator is greater than the denominator using require()
Math
#0 - c4-pre-sort
2023-09-15T21:45:08Z
raymondfam marked the issue as low quality report
#1 - c4-pre-sort
2023-09-15T21:45:23Z
raymondfam marked the issue as duplicate of #123
#2 - c4-pre-sort
2023-09-17T08:32:52Z
raymondfam marked the issue as not a duplicate
#3 - c4-pre-sort
2023-09-17T08:33:05Z
raymondfam marked the issue as duplicate of #321
#4 - c4-judge
2023-09-25T16:48:35Z
gzeon-c4 marked the issue as duplicate of #118
#5 - c4-judge
2023-09-26T18:15:40Z
gzeon-c4 marked the issue as satisfactory
🌟 Selected for report: castle_chain
Also found by: 0xAadi, 0xHelium, 0xLook, 0xblackskull, 0xfuje, 0xmystery, 0xnev, 0xpiken, 7ashraf, BARW, Bauchibred, Bughunter101, Ch_301, JP_Courses, Kaysoft, Krace, MohammedRizwan, SanketKogekar, Sathish9098, alexzoid, ast3ros, btk, catellatech, degensec, fatherOfBlocks, grearlake, imtybik, jkoppel, jolah1, klau5, lsaudit, m_Rassska, merlin, mrudenko, nobody2018, rokinot, rvierdiiev, sandy
12.7917 USDC - $12.79
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L124, https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L154
The interface used in src\InvestmentManager.sol
interface PoolManagerLike { ... function isAllowedAsPoolCurrency(uint64 poolId, address currencyAddress) external view returns (bool); }
function returns bool
, but the check is not executed.
Token (currency
) that is not supported in Centrifuge pool will be used in InvestmentManager::requestRedeem() and InvestmentManager::requestDeposit().
In other contracts, the check is performed. In this case, it was probably forgotten to be performed.
Line 124: poolManager.isAllowedAsPoolCurrency(lPool.poolId(), currency);
Line 154: poolManager.isAllowedAsPoolCurrency(lPool.poolId(), lPool.asset());
At the moment PoolManager::isAllowedAsPoolCurrency() will always return true
or revert()
will occur. But if the PoolManager logic is changed so that true
/false
is returned and the address poolManager
is updated with InvestmentManager::file(), the Token (currency
) that is not supported in Centrifuge pool will be used in InvestmentManager::requestRedeem() and InvestmentManager::requestDeposit().
Manual analysis
Change from
Line 124: poolManager.isAllowedAsPoolCurrency(lPool.poolId(), currency);
Line 154: poolManager.isAllowedAsPoolCurrency(lPool.poolId(), lPool.asset());
to
Line 124: requre(poolManager.isAllowedAsPoolCurrency(lPool.poolId(), currency), "PoolManager/currency-not-supported")
Line 154: requre(poolManager.isAllowedAsPoolCurrency(lPool.poolId(), lPool.asset()), "PoolManager/currency-not-supported")
Invalid Validation
#0 - c4-pre-sort
2023-09-15T04:51:18Z
raymondfam marked the issue as low quality report
#1 - c4-pre-sort
2023-09-15T04:51:36Z
raymondfam marked the issue as duplicate of #68
#2 - c4-judge
2023-09-25T14:13:05Z
gzeon-c4 marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2023-09-25T14:14:30Z
gzeon-c4 marked the issue as not a duplicate
#4 - c4-judge
2023-09-25T14:14:35Z
gzeon-c4 marked the issue as unsatisfactory: Invalid
#5 - c4-judge
2023-09-25T14:14:44Z
gzeon-c4 changed the severity to QA (Quality Assurance)
#6 - gzeon-c4
2023-09-25T14:15:18Z
isAllowedAsPoolCurrency revert if false, but as QA the bool should be checked
#7 - c4-sponsor
2023-09-25T14:46:13Z
hieronx (sponsor) confirmed
#8 - c4-judge
2023-09-25T15:02:32Z
gzeon-c4 marked the issue as grade-b