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: 19/84
Findings: 3
Award: $704.11
π Selected for report: 0
π Solo Findings: 0
π Selected for report: Kow
Also found by: 0xRobsol, 0xfuje, 0xkazim, 0xpiken, Aymen0909, T1MOH, bin2chen, codegpt, gumgumzum, josephdara, lsaudit, nmirchev8, ravikiranweb3, rvierdiiev
132.8565 USDC - $132.86
The domain separator is computed at deployment time. However at the point it is computed the values needed have not been set.
//In the constructor _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid); .. .. function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256(bytes(version)), chainId, address(this) ) ); }
As seen above the function used to set the domain separator uses the name, version, and chainID, However the value of the name is not set in the constructor. They are set after deployment.
Hence this causes the DOMAIN_SEPARATOR()
function to return a different value from what should have been originally computed if the name was available.
The function DOMAIN_SEPARATOR()
returns the wrong _DOMAIN_SEPARATOR
every time it is called with the same chainID.
function DOMAIN_SEPARATOR() external view returns (bytes32) { return block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid); }
Manual Review
Do not compute the Domain Separator in the constructor, compute it after the name has been set
ERC20
#0 - c4-pre-sort
2023-09-16T00:54:17Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-09-16T00:54:29Z
raymondfam marked the issue as duplicate of #146
#2 - c4-judge
2023-09-26T18:07:19Z
gzeon-c4 marked the issue as satisfactory
50.4324 USDC - $50.43
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/LiquidityPool.sol#L125-L132 https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L323-L347
Rounding of shares to assets do not conform to the ERC4626 standard: EIP 4626's Security Considerations (https://eips.ethereum.org/EIPS/eip-4626)
Finally, 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 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.
However in the LiquidityPool
and the InvestmentManager
, the convertToShares
and convertToAssets
are both rounded down. The resulting effect of this is that users get less tokens when they are redeeming their shares.
/// @dev Calculates the amount of shares / tranche tokens that any user would get for the amount of currency / assets provided. /// The calculation is based on the tranche token price from the most recent epoch retrieved from Centrifuge. 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 ); } /// @dev Calculates the asset value for an amount of shares / tranche tokens provided. /// The calculation is based on the tranche token price from the most recent epoch retrieved from Centrifuge. 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), MathLib.Rounding.Down //@audit-issue SH=hould round up ); }
Manual Review
I recommend that the convertToAssets
should round up
ERC4626
#0 - c4-pre-sort
2023-09-16T00:27:34Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-09-16T00:27:46Z
raymondfam marked the issue as duplicate of #34
#2 - c4-judge
2023-09-26T18:11:12Z
gzeon-c4 marked the issue as satisfactory
π Selected for report: Bauchibred
Also found by: 0xnev, BARW, Ch_301, J4X, ast3ros, degensec, josephdara
520.8185 USDC - $520.82
The detectTransferRestriction
takes in all the necessary parameters:
function detectTransferRestriction(address from, address to, uint256 value) public view returns (uint8)
However it only performs a check on the address to
The restriction used here is timebased, however a user can continuing using, swapping, transferring and even redeeming their tranche tokens long after their restriction time has passed.
This is caused by the function below
function hasMember(address user) public view returns (bool) { if (members[user] >= block.timestamp) { return true; } return false; } function detectTransferRestriction(address from, address to, uint256 value) public view returns (uint8) { if (!hasMember(to)) { return DESTINATION_NOT_A_MEMBER_RESTRICTION_CODE; } return SUCCESS_CODE; }
This function detectTransferRestriction
checks only the destination, not the origin address
Manual review
Implement checks for the from
address
ERC20
#0 - c4-pre-sort
2023-09-16T02:09:50Z
raymondfam marked the issue as low quality report
#1 - c4-pre-sort
2023-09-16T02:09:57Z
raymondfam marked the issue as duplicate of #29
#2 - c4-judge
2023-09-25T14:39:59Z
gzeon-c4 marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2023-09-26T16:07:07Z
gzeon-c4 marked the issue as not a duplicate
#4 - c4-judge
2023-09-26T16:07:21Z
gzeon-c4 marked the issue as primary issue
#5 - c4-judge
2023-09-26T16:07:42Z
gzeon-c4 marked the issue as duplicate of #779
#6 - c4-judge
2023-09-26T16:08:02Z
gzeon-c4 marked the issue as satisfactory