Platform: Code4rena
Start Date: 31/01/2023
Pot Size: $90,500 USDC
Total HM: 47
Participants: 169
Period: 7 days
Judge: LSDan
Total Solo HM: 9
Id: 211
League: ETH
Rank: 123/169
Findings: 1
Award: $35.48
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x3b, 0xAgro, 0xBeirao, 0xMirce, 0xNineDec, 0xRobocop, 0xSmartContract, 0xTraub, 0xWeiss, 2997ms, 41i3xn, Awesome, Aymen0909, Bauer, Bnke0x0, Breeje, Cryptor, DadeKuma, Deathstore, Deekshith99, DevABDee, DevTimSch, Dewaxindo, Diana, Ermaniwe, Guild_3, H0, IceBear, Inspectah, JDeryl, Kaiziron, Kaysoft, Kenshin, Mukund, Praise, RaymondFam, Rickard, Rolezn, Ruhum, Sathish9098, SkyWalkerMan, SleepingBugs, UdarTeam, Udsen, Walter, aashar, adeolu, apvlki, arialblack14, ast3ros, btk, chaduke, chandkommanaboyina, chrisdior4, climber2002, codetilda, cryptonue, cryptostellar5, csanuragjain, ddimitrov22, descharre, dharma09, doublesharp, eccentricexit, ethernomad, fs0c, georgits, halden, hansfriese, hashminer0725, immeas, lukris02, luxartvinsec, matrix_0wl, merlin, mookimgo, mrpathfindr, nadin, olegthegoat, pavankv, rbserver, rebase, savi0ur, sayan, scokaf, seeu, shark, simon135, tnevler, tsvetanovv, ulqiorra, ustas, waldenyan20, y1cunhui, yongskiws, yosuke
35.4779 USDC - $35.48
It is possible to add address(0) and address(this) to the cloneRegistry contract's allClones mapping. There should be checks to prevent this.
Link to affected part of code --> https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneRegistry.sol#L41
Proof of concept code
// SPDX-License-Identifier: GPL-3.0 // Docgen-SOLC: 0.8.15 pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; import { CloneRegistry } from "../../src/vault/CloneRegistry.sol"; contract CloneRegistryTest is Test { CloneRegistry registry; bytes32 templateCategory = "templateCategory"; bytes32 templateId = "templateId"; event CloneAdded(address clone); function setUp() public { registry = new CloneRegistry(address(this)); } /*////////////////////////////////////////////////////////////// ADD CLONE //////////////////////////////////////////////////////////////*/ function test__addCloneAsRegistry() public { assertEq(registry.cloneExists(address(registry)), false); vm.expectEmit(false, false, false, true); emit CloneAdded(address(registry)); registry.addClone(templateCategory, templateId, address(registry)); assertEq(registry.cloneExists(address(registry)), true); assertEq(registry.clones(templateCategory, templateId, 0), address(registry)); assertEq(registry.allClones(0), address(registry)); } function test__addCloneAsAddr0() public { assertEq(registry.cloneExists(address(0)), false); vm.expectEmit(false, false, false, true); emit CloneAdded(address(0)); registry.addClone(templateCategory, templateId, address(0)); assertEq(registry.cloneExists(address(0)), true); assertEq(registry.clones(templateCategory, templateId, 0), address(0)); assertEq(registry.allClones(0), address(0)); } }
Recommended mitigation ---> add a check in the addClone function that reverts when address(0) is supplied as clone address.
Link to code in repo --> https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/PermissionRegistry.sol#L45
function setPermissions(address[] calldata targets, Permission[] calldata newPermissions) external onlyOwner { uint256 len = targets.length; if (len != newPermissions.length) revert Mismatch(); for (uint256 i = 0; i < len; i++) { if (newPermissions[i].endorsed && newPermissions[i].rejected) revert Mismatch(); emit PermissionSet(targets[i], newPermissions[i].endorsed, newPermissions[i].rejected); permissions[targets[i]] = newPermissions[i]; } }
Recommended mitigation --> The code above emits the PermissionSet event before the permission is actually set in logic. The event PermissionSet should be emitted after the permission is actually set so in this function the event emit should be the last line of code logic here, like this;
function setPermissions(address[] calldata targets, Permission[] calldata newPermissions) external onlyOwner { uint256 len = targets.length; if (len != newPermissions.length) revert Mismatch(); for (uint256 i = 0; i < len; i++) { if (newPermissions[i].endorsed && newPermissions[i].rejected) revert Mismatch(); permissions[targets[i]] = newPermissions[i]; emit PermissionSet(targets[i], newPermissions[i].endorsed, newPermissions[i].rejected); } }
link to affected code --> https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L544
/// @notice Change fees to the previously proposed fees after the quit period has passed. function changeFees() external { if (block.timestamp < proposedFeeTime + quitPeriod) revert NotPassedQuitPeriod(quitPeriod); emit ChangedFees(fees, proposedFees); fees = proposedFees; }
In the above function event is emitted before the fees value is actually changed in storage.
Recommended Mitigation --> emit event after the fees value is set to the proposedFees.
link to affected code --> https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L553
function setFeeRecipient(address _feeRecipient) external onlyOwner { if (_feeRecipient == address(0)) revert InvalidFeeRecipient(); emit FeeRecipientUpdated(feeRecipient, _feeRecipient); feeRecipient = _feeRecipient; }
In the above function event is emitted before the fees value is actually changed in storage.
Recommended Mitigation --> emit event after the fees value is set to the proposedFees.
Links to affected code in repo --> https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultRegistry.sol#L59, https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultRegistry.sol#L75
function getVault(address vault) external view returns (VaultMetadata memory) { return metadata[vault]; } function getSubmitter(address vault) external view returns (VaultMetadata memory) { return metadata[vault]; }
Recommended Mitigation ---> The above functions are both in the VaultRegistry contract and they return the exact same thing, even though they are named differently. Perhaps getSubmitter is to return the creator
value in VaultMetadata
struct? If this is the case the code in getSubmitter() should be updated to return such (metadata[vault].creator) or one of the functions should be removed to reduce bytecode size as they are both doing the same thing.
function setEscrowTokenFees(IERC20[] calldata tokens, uint256[] calldata fees) external onlyOwner { _verifyEqualArrayLength(tokens.length, fees.length); (bool success, bytes memory returnData) = adminProxy.execute( address(escrow), abi.encodeWithSelector(IMultiRewardEscrow.setFees.selector, tokens, fees) ); if (!success) revert UnderlyingError(returnData); }
link to setEscrowTokenFees in repo --> https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultController.sol#L543
In setEscrowTokenFees
in VaultController
contract, the lengths of token and fees arrays are checked to be equal and it is also checked for equality again in the setFees
function in MultiRewardEscrow
contract. See setFees function in MultiRewardEscrow below;
function setFees(IERC20[] memory tokens, uint256[] memory tokenFees) external onlyOwner { if (tokens.length != tokenFees.length) revert ArraysNotMatching(tokens.length, tokenFees.length); for (uint256 i = 0; i < tokens.length; i++) { if (tokenFees[i] >= 1e17) revert DontGetGreedy(tokenFees[i]); fees[tokens[i]].feePerc = tokenFees[i]; emit FeeSet(tokens[i], tokenFees[i]); } }
link to setFees in repo ---> https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L207
Recommended mitigation ---> Remove the check in one of the two functions, I suggest removing the equal length check in setEscrowTokenFees
as setFees
is the underlying function being called in setEscrowTokenFees
VS Code, Forge
#0 - c4-judge
2023-02-28T14:58:52Z
dmvt marked the issue as grade-b