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: 36/84
Findings: 1
Award: $132.86
🌟 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
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/token/ERC20.sol#L67-L77 https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/token/ERC20.sol#L67-L77 https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/token/ERC20.sol#L79-L81
Assigned DOMAIN_SEPARATOR in the constructor is wrong as by that time, the name for the ERC20 token is not assigned which is part of the keccak256 input.
The usage of DOMAIN_SEPARATOR is not evident from the scoped code base, but it could be far reaching if the bytes32 returned is used as a key which will not match the keccak256 generated after assignment.
Refer to the code below.
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) ) ); }
In the constructor, the name is not assigned, but immutable _DOMAIN_SEPARATOR is assigned.
constructor(uint8 decimals_) { decimals = decimals_; wards[_msgSender()] = 1; emit Rely(_msgSender()); deploymentChainId = block.chainid; _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid); }
Manaul Review
Pass the name of the token as parameter of constructor.
ERC20
#0 - raymondfam
2023-09-14T22:10:06Z
It will be initialized here:
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/token/ERC20.sol#L83-L88
function file(bytes32 what, string memory data) external auth { if (what == "name") name = data; else if (what == "symbol") symbol = data; else revert("ERC20/file-unrecognized-param"); emit File(what, data); }
#1 - c4-pre-sort
2023-09-14T22:10:19Z
raymondfam marked the issue as primary issue
#2 - c4-pre-sort
2023-09-14T22:10:23Z
raymondfam marked the issue as low quality report
#3 - c4-pre-sort
2023-09-15T01:41:45Z
raymondfam marked the issue as duplicate of #146
#4 - c4-pre-sort
2023-09-15T02:32:23Z
raymondfam marked the issue as sufficient quality report
#5 - c4-judge
2023-09-26T18:08:50Z
gzeon-c4 marked the issue as satisfactory
🌟 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
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/token/ERC20.sol#L67-L77 https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/LiquidityPool.sol#L220-L226 https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/LiquidityPool.sol#L237-L243 https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/token/ERC20.sol#L216-L237
Depositing or redeeming with permit may stop working, if the token name is updated. The digest generation in the permit function of ERC20 takes into account the name of the ERC20 token at the time of generation.
Updating the name of such token with new name using file function could break the signature digests and will not work.
Refer to the digest generation in the below function in LiquidityPool comtract.
function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) public { require(block.timestamp <= deadline, "ERC20/permit-expired"); require(owner != address(0), "ERC20/invalid-owner"); uint256 nonce; unchecked { nonce = nonces[owner]++; } bytes32 digest = keccak256( abi.encodePacked( "\x19\x01", block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid), keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline)) ) ); require(_isValidSignature(owner, digest, signature), "ERC20/invalid-permit"); allowance[owner][spender] = value; emit Approval(owner, spender, value); }
note how value returned by _calculateDomainSeparator() function above is part of the digest. Note how name is part of the keccack256 which is part of the digest. As such update the name of the ERC20 token will invalidate the digests.
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) ) ); }
Manual Review
Assign the name of the token during token in the construction and dont prove a way to change the name later.
ERC20
#0 - c4-pre-sort
2023-09-14T22:12:18Z
raymondfam marked the issue as low quality report
#1 - c4-pre-sort
2023-09-14T22:12:31Z
raymondfam marked the issue as duplicate of #51
#2 - c4-pre-sort
2023-09-15T01:41:07Z
raymondfam marked the issue as duplicate of #146
#3 - c4-judge
2023-09-26T18:08:42Z
gzeon-c4 changed the severity to 2 (Med Risk)
#4 - c4-judge
2023-09-26T18:08:48Z
gzeon-c4 marked the issue as satisfactory
🌟 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
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/PoolManager.sol#L214-L225 https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/token/ERC20.sol#L67-L77 https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/token/ERC20.sol#L216-L237
Name of the ERC20 token is part of the digest generated in the permit function of the contract. Any change in the name of the ERC20 will result in a different digest and hence create conflict for permit functions.
updateTrancheTokenMetadata() function in Poolmanager contract allows for updating the name of ERC20 token in the attached Tranche Token contract.
The permit function relies on the digest which is generated using _calculateDomainSeparator as one of the parameters. The calculateDomainSeperator uses name of the token as one of the inputs, hence changing the name will impact the digest and hence the permit functionality.
The PoolManager offering the function to update the name of underlying trancheToken will effect the permit based functions.
function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) public { require(block.timestamp <= deadline, "ERC20/permit-expired"); require(owner != address(0), "ERC20/invalid-owner"); uint256 nonce; unchecked { nonce = nonces[owner]++; } bytes32 digest = keccak256( abi.encodePacked( "\x19\x01", block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid), keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline)) ) ); require(_isValidSignature(owner, digest, signature), "ERC20/invalid-permit"); allowance[owner][spender] = value; emit Approval(owner, spender, value); }
en/de-code
#0 - c4-pre-sort
2023-09-14T22:46:42Z
raymondfam marked the issue as low quality report
#1 - c4-pre-sort
2023-09-14T22:47:14Z
raymondfam marked the issue as duplicate of #51
#2 - c4-pre-sort
2023-09-15T01:41:43Z
raymondfam marked the issue as duplicate of #146
#3 - c4-judge
2023-09-26T18:08:38Z
gzeon-c4 marked the issue as satisfactory