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: 73/84
Findings: 1
Award: $12.79
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
updateTrancheTokenMetadata
pools[poolId].tranches[trancheId].tokenName
and pools[poolId].tranches[trancheId].tokenSymbol
are not match with real token name/symbol if updated later.
At updateTrancheTokenMetadata
, you can change tranche token name and symbol. But it doesn’t change storage variable at PoolManager contract.
function updateTrancheTokenMetadata( uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol ) public onlyGateway { TrancheTokenLike trancheToken = TrancheTokenLike(getTrancheToken(poolId, trancheId)); require(address(trancheToken) != address(0), "PoolManager/unknown-token"); trancheToken.file("name", tokenName); trancheToken.file("symbol", tokenSymbol); }
Manual Review
Change state variable.
function updateTrancheTokenMetadata( uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol ) public onlyGateway { TrancheTokenLike trancheToken = TrancheTokenLike(getTrancheToken(poolId, trancheId)); require(address(trancheToken) != address(0), "PoolManager/unknown-token"); trancheToken.file("name", tokenName); trancheToken.file("symbol", tokenSymbol); + pools[poolId].tranches[trancheId].tokenName = tokenName; + pools[poolId].tranches[trancheId].tokenSymbol = tokenSymbol; }
handleTransferTrancheTokens
Check same condition twice at handleTransferTrancheTokens
At handleTransferTrancheTokens
, it calls MemberlistLike(address(trancheToken.restrictionManager())).hasMember(destinationAddress)
to check if destinationAddress
is allowed member or not.
function handleTransferTrancheTokens(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) public onlyGateway { TrancheTokenLike trancheToken = TrancheTokenLike(getTrancheToken(poolId, trancheId)); require(address(trancheToken) != address(0), "PoolManager/unknown-token"); require( MemberlistLike(address(trancheToken.restrictionManager())).hasMember(destinationAddress), "PoolManager/not-a-member" ); trancheToken.mint(destinationAddress, amount); }
At trancheToken.mint
, it check same thing at restricted
modifier.
function mint(address to, uint256 value) public override restricted(_msgSender(), to, value) { return super.mint(to, value); }
Manual Review
function handleTransferTrancheTokens(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) public onlyGateway { TrancheTokenLike trancheToken = TrancheTokenLike(getTrancheToken(poolId, trancheId)); require(address(trancheToken) != address(0), "PoolManager/unknown-token"); - require( - MemberlistLike(address(trancheToken.restrictionManager())).hasMember(destinationAddress), - "PoolManager/not-a-member" - ); trancheToken.mint(destinationAddress, amount); }
MAX_DELAY
at Root constructorCan set delay more than MAX_DELAY
at root constructor
constructor(address _escrow, uint256 _delay) { escrow = _escrow; delay = _delay; wards[msg.sender] = 1; emit Rely(msg.sender); }
Manual Review
constructor(address _escrow, uint256 _delay) { escrow = _escrow; + require(data <= MAX_DELAY, "Root/delay-too-long"); delay = _delay; wards[msg.sender] = 1; emit Rely(msg.sender); }
Some contract using Auth cannot be managed by Root contract
Root contract have functions to manage wards of all the other contracts. To use these functions, all the other contracts should set Root contract as ward.
function relyContract(address target, address user) public auth { AuthLike(target).rely(user); emit RelyContract(target, user); } function denyContract(address target, address user) public auth { AuthLike(target).deny(user); emit DenyContract(target, user); }
At Deploy script, the LiquidityPoolFactory and TrancheTokenFactory, PauseAdmin, DelayedAdmin contracts don’t call rely
to set root contract ward. And also, these contracts don’t set Root contract as ward at constructor.
function deployInvestmentManager() public { ... LiquidityPoolFactory(liquidityPoolFactory).rely(address(poolManager)); TrancheTokenFactory(trancheTokenFactory).rely(address(poolManager)); } function wire(address router) public { // Deploy gateway and admins pauseAdmin = new PauseAdmin(address(root)); delayedAdmin = new DelayedAdmin(address(root)); pauseAdmin.rely(address(delayedAdmin)); ... }
You can't manage them with management functions unless you add Root as a ward.
Manual Review
To avoid these situation, make Root contract the default ward at contructor. This is an example.(DelayedAdmin constructor)
constructor(address root_) { root = Root(root_); + wards[root_] = 1; + emit Rely(root_); wards[msg.sender] = 1; emit Rely(msg.sender); }
detectTransferRestriction
do not check from
parameter so that restricted
modifier from
parameter doesn’t need to set perfectlyUseless functino call for useless parameter
At detectTransferRestriction
, it does not use from
parameter at all. But it cannot be removed to comply with ERC1404.
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; }
But at Tranche contract, you don’t need to use _msgSender()
to set restricted
modifier from
parameter, because it will not be used.
modifier restricted(address from, address to, uint256 value) { uint8 restrictionCode = detectTransferRestriction(from, to, value); require(restrictionCode == restrictionManager.SUCCESS_CODE(), messageForTransferRestriction(restrictionCode)); _; } function transfer(address to, uint256 value) public override restricted(_msgSender(), to, value) returns (bool) { return super.transfer(to, value); } function mint(address to, uint256 value) public override restricted(_msgSender(), to, value) { return super.mint(to, value); }
#0 - c4-pre-sort
2023-09-17T01:41:02Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-09-26T17:38:46Z
gzeon-c4 marked the issue as grade-b