Platform: Code4rena
Start Date: 03/03/2023
Pot Size: $90,500 USDC
Total HM: 4
Participants: 42
Period: 7 days
Judge: 0xean
Total Solo HM: 2
Id: 219
League: ETH
Rank: 16/42
Findings: 2
Award: $647.62
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: rbserver
Also found by: 0x6980, 0xAgro, 0xSmartContract, 0xmichalis, 0xnev, BRONZEDISC, DevABDee, IceBear, RaymondFam, Rolezn, SaeedAlipoor01988, Sathish9098, arialblack14, brgltd, chrisdior4, codeislight, descharre, imare, lukris02, luxartvinsec, matrix_0wl, tnevler, yongskiws
72.4344 USDC - $72.43
Context: All Contracts.
Recommendation:
import {contract1 , contract2} from "filename.sol";
Function writing
that does not comply with the Solidity Style Guide
Order of Functions; ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier. But there are contracts in the project that do not comply with this. soliditylang-style-guide Functions should be grouped according to their visibility and ordered:
constructor
receive function (if exists)
fallback function (if exists)
external
public
internal
private
within a grouping, place the view and pure functions last
Empty blocks
should be removed or Emit somethingCode contains empty block
136: function _authorizeUpgrade(address) internal virtual override auth(UPGRADE_DAO_PERMISSION_ID) {}
320: } catch {}
17: constructor(IDAO _dao) DaoAuthorizable(_dao) {}
61: function _authorizeUpgrade( 62: address 63: ) internal virtual override auth(UPGRADE_PLUGIN_PERMISSION_ID) {}
257: function _authorizeUpgrade( 258: address 259: ) internal virtual override auth(UPGRADE_REPO_PERMISSION_ID) {}
external virtual override returns (bytes memory initData, PreparedSetupData memory preparedSetupData) 27: {}
56: ) internal virtual override auth(UPGRADE_REGISTRY_PERMISSION_ID) {}
76: ) internal virtual override auth(UPGRADE_REGISTRAR_PERMISSION_ID) {}
31: function setTrustedForwarder(address /* _trustedForwarder */) external override {} 33: function setMetadata(bytes calldata /* _metadata */) external override {} 48: ) external payable override {} 50: function setSignatureValidator(address /* _signatureValidator */) external override {} 63: ) external override {}
33 ) internal pure override {}
8: constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {}
8: constructor(string memory URI_) ERC1155(URI_) {}
The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting.
This codebase will be difficult to navigate, as there are no descriptive naming conventions that specify which files should contain meaningful logic.
Prefixes should be added like this by filing:
We recommend that you implement this or a similar agreement.
15: IDAO private immutable dao_;
19: address public immutable daoBase; /// @notice The DAO registry listing the `DAO` contracts created via this contract. 22: DAORegistry public immutable daoRegistry; /// @notice The plugin setup processor for installing plugins on the newly created `DAO`s. 25: PluginSetupProcessor public immutable pluginSetupProcessor;
20: address private immutable implementation_;
17: AddresslistVoting private immutable addresslistVotingBase;
/// @notice The address of the `TokenVoting` base contract. 30: TokenVoting private immutable tokenVotingBase; /// @notice The address of the `GovernanceERC20` base contract. 33: address public immutable governanceERC20Base; /// @notice The address of the `GovernanceWrappedERC20` base contract. 36: address public immutable governanceWrappedERC20Base;
16: Multisig private immutable multisigBase;
12: address private immutable implementation;
192: if (!hasBit(_allowFailureMap, uint8(i))) { 198: failureMap = flipBit(failureMap, uint8(i));
We recommend the project handle downcasting and use safe casting library to make sure the downcast does not provide an unexpected truncate value. openzeppelin
uin256
instead of uint
contract ActionExecute { uint num = 10; function setTest(uint newNum) public returns (uint) { num = newNum; return num; } function fail() public pure { revert("ActionExecute:Revert"); } }
Description: I recommend using header for Solidity code layout and readability
/*////////////////////////////////////////////////////////////// TESTING 123 //////////////////////////////////////////////////////////////*/
111: uint256 claimedWord_index = _index / 256; 121: uint256 claimedWord_index = _index / 256;
24: result = _value / RATIO_BASE;
#0 - c4-judge
2023-03-12T16:29:06Z
0xean marked the issue as grade-b
#1 - novaknole20
2023-03-22T10:49:20Z
We keep them to guide devs which functions they need to implement
uint
is uint256
#2 - c4-sponsor
2023-03-22T10:49:27Z
novaknole20 marked the issue as sponsor acknowledged
🌟 Selected for report: JCN
Also found by: 0x6980, 0xSmartContract, 0xnev, Madalad, Phantasmagoria, Rageur, RaymondFam, ReyAdmirado, Rolezn, Sathish9098, atharvasama, descharre, hunter_w3b, matrix_0wl, saneryee, volodya, yongskiws
575.1851 USDC - $575.19
// @notice The ID of the permission required to call the `execute` function. 40: bytes32 public constant EXECUTE_PERMISSION_ID = keccak256("EXECUTE_PERMISSION"); /// @notice The ID of the permission required to call the `_authorizeUpgrade` function. 43: bytes32 public constant UPGRADE_DAO_PERMISSION_ID = keccak256("UPGRADE_DAO_PERMISSION"); /// @notice The ID of the permission required to call the `setMetadata` function. 46: bytes32 public constant SET_METADATA_PERMISSION_ID = keccak256("SET_METADATA_PERMISSION"); /// @notice The ID of the permission required to call the `setTrustedForwarder` function. 49: bytes32 public constant SET_TRUSTED_FORWARDER_PERMISSION_ID = keccak256("SET_TRUSTED_FORWARDER_PERMISSION"); /// @notice The ID of the permission required to call the `setSignatureValidator` function. 53: bytes32 public constant SET_SIGNATURE_VALIDATOR_PERMISSION_ID = keccak256("SET_SIGNATURE_VALIDATOR_PERMISSION"); /// @notice The ID of the permission required to call the `registerStandardCallback` function. 57: bytes32 public constant REGISTER_STANDARD_CALLBACK_PERMISSION_ID = keccak256("REGISTER_STANDARD_CALLBACK_PERMISSION");
/// @notice The ID of the permission required to call the `grant`, `grantWithCondition`, `revoke`, and `bulk` function. 15: bytes32 public constant ROOT_PERMISSION_ID = keccak256("ROOT_PERMISSION");
/// @notice The ID of the permission required to call the `_authorizeUpgrade` function. 35: bytes32 public constant UPGRADE_PLUGIN_PERMISSION_ID = keccak256("UPGRADE_PLUGIN_PERMISSION");
/// @notice The ID of the permission required to call the `register` function. 15: bytes32 public constant REGISTER_DAO_PERMISSION_ID = keccak256("REGISTER_DAO_PERMISSION");
/// @notice The ID of the permission required to call the `createVersion` function. 49: bytes32 public constant MAINTAINER_PERMISSION_ID = keccak256("MAINTAINER_PERMISSION"); /// @notice The ID of the permission required to call the `createVersion` function. 52: bytes32 public constant UPGRADE_REPO_PERMISSION_ID = keccak256("UPGRADE_REPO_PERMISSION");
/// @notice The ID of the permission required to call the `register` function. 16: bytes32 public constant REGISTER_PLUGIN_REPO_PERMISSION_ID = 17: keccak256("REGISTER_PLUGIN_REPO_PERMISSION");
/// @notice The ID of the permission required to call the `applyInstallation` function. 27: bytes32 public constant APPLY_INSTALLATION_PERMISSION_ID = 28: keccak256("APPLY_INSTALLATION_PERMISSION"); /// @notice The ID of the permission required to call the `applyUpdate` function. 31: bytes32 public constant APPLY_UPDATE_PERMISSION_ID = keccak256("APPLY_UPDATE_PERMISSION"); /// @notice The ID of the permission required to call the `applyUninstallation` function. 34: bytes32 public constant APPLY_UNINSTALLATION_PERMISSION_ID = 35: keccak256("APPLY_UNINSTALLATION_PERMISSION");
17: /// @notice The ID of the permission required to call the `_authorizeUpgrade` function. 18: bytes32 public constant UPGRADE_REGISTRY_PERMISSION_ID = 19: keccak256("UPGRADE_REGISTRY_PERMISSION");
/// @notice The ID of the permission required to call the `_authorizeUpgrade` function. 18: bytes32 public constant UPGRADE_REGISTRAR_PERMISSION_ID = 19: keccak256("UPGRADE_REGISTRAR_PERMISSION"); /// @notice The ID of the permission required to call the `registerSubnode` and `setDefaultResolver` function. 22: bytes32 public constant REGISTER_ENS_SUBDOMAIN_PERMISSION_ID = 23: keccak256("REGISTER_ENS_SUBDOMAIN_PERMISSION");
11: /// @notice The ID of the permission required to call the `multiply` function. 12: bytes32 public constant MULTIPLY_PERMISSION_ID = keccak256("MULTIPLY_PERMISSION");
13: /// @notice The ID of the permission required to call the `multiply` function. 14: bytes32 public constant MULTIPLY_PERMISSION_ID = keccak256("MULTIPLY_PERMISSION");
47: bytes memory initData = abi.encodeWithSelector( 48: bytes4(keccak256("initialize(address,address,uint256)")), 49: _dao, 50: multiplyHelper, 51: _num 52: ); 64: permissions[0] = PermissionLib.MultiTargetPermission( 65: PermissionLib.Operation.Grant, 66: _dao, 67: plugin, 68: PermissionLib.NO_CONDITION, 69: keccak256("EXECUTE_PERMISSION") 70: ); 109: permissions[0] = PermissionLib.MultiTargetPermission( 110: PermissionLib.Operation.Revoke, 111: _dao, 112: _payload.plugin, 113: PermissionLib.NO_CONDITION, 114: keccak256("EXECUTE_PERMISSION") 115: );
13: /// @notice The ID of the permission required to call the `multiply` function. 14: bytes32 public constant MULTIPLY_PERMISSION_ID = keccak256("MULTIPLY_PERMISSION");
48: bytes memory initData = abi.encodeWithSelector( 49: bytes4(keccak256("initialize(address,address,uint256)")), 50: _dao, 51: multiplyHelper, 52: _num 53: ); 65: permissions[0] = PermissionLib.MultiTargetPermission( 66: PermissionLib.Operation.Grant, 67: _dao, 68: plugin, 69: PermissionLib.NO_CONDITION, 70: keccak256("EXECUTE_PERMISSION") 71: ); 113: if (_currentBuild == 1) { 114: (_newVariable) = abi.decode(_payload.data, (uint256)); 115: initData = abi.encodeWithSelector( 116: bytes4(keccak256("setNewVariable(uint256)")), 117: _newVariable 118: ); 119: }
22: /// @notice The ID of the permission required to call the `executeProposal` function. 23: bytes32 public constant EXECUTE_PROPOSAL_PERMISSION_ID = 24: keccak256("EXECUTE_PROPOSAL_PERMISSION");
182: /// @notice The ID of the permission required to call the `updateVotingSettings` function. 183: bytes32 public constant UPDATE_VOTING_SETTINGS_PERMISSION_ID = 184: keccak256("UPDATE_VOTING_SETTINGS_PERMISSION");
26: /// @notice The ID of the permission required to call the `addAddresses` and `removeAddresses` functions. 27: bytes32 public constant UPDATE_ADDRESSES_PERMISSION_ID = 28: keccak256("UPDATE_ADDRESSES_PERMISSION");
70: /// @notice The ID of the permission required to call the `addAddresses` and `removeAddresses` functions. 71: bytes32 public constant UPDATE_MULTISIG_SETTINGS_PERMISSION_ID = 72: keccak256("UPDATE_MULTISIG_SETTINGS_PERMISSION");
23: /// @notice The ID of the permission required to call the `merkleMint` function. 24: bytes32 public constant MERKLE_MINT_PERMISSION_ID = keccak256("MERKLE_MINT_PERMISSION"); 25: 26: /// @notice The ID of the permission required to call the `changeDistributor` function. 27: bytes32 public constant CHANGE_DISTRIBUTOR_PERMISSION_ID = 28: keccak256("CHANGE_DISTRIBUTOR_PERMISSION");
10: // Restricted permissionIds that shouldn't be allowed to grant for who = ANY_ADDR or where = ANY_ADDR 11: bytes32 public constant TEST_PERMISSION_1_ID = keccak256("TEST_PERMISSION_1"); 12: bytes32 public constant TEST_PERMISSION_2_ID = keccak256("TEST_PERMISSION_2");
18: for (uint160 i = start; i < end; i++) { 19: permissions[i - start] = PermissionLib.MultiTargetPermission( 20: op, 21: address(i), 22: address(i), 23: PermissionLib.NO_CONDITION, 24: keccak256("MOCK_PERMISSION") 25: ); 26: } 37: function mockPluginProxy(address _pluginBase, address _dao) returns (address) { 38: return 39: createERC1967Proxy( 40: _pluginBase, 41: abi.encodeWithSelector(bytes4(keccak256("initialize(address)")), _dao) 42: ); 43: }
9: bytes32 public constant DO_SOMETHING_PERMISSION_ID = keccak256("DO_SOMETHING_PERMISSION");
15: bytes32 public constant ID_GATED_ACTION_PERMISSION_ID = keccak256("ID_GATED_ACTION_PERMISSION");
8: bytes32 public constant REGISTER_PERMISSION_ID = keccak256("REGISTER_PERMISSION");
27: /// @notice The permission identifier to mint new tokens 28: bytes32 public constant MINT_PERMISSION_ID = keccak256("MINT_PERMISSION");
type(uint160).max or type(uint16).max, etc. it uses more gas in the distribution process and also for each transaction than constant usage.
17: /// @notice A special address encoding permissions that are valid for any address `who` or `where`. 18: address internal constant ANY_ADDR = address(type(uint160).max);
-Multisig.sol#L159-L163, 2 instances
158: // Check if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. 159: if (newAddresslistLength > type(uint16).max) { 160: revert AddresslistLengthOutOfBounds({ 161: limit: type(uint16).max, 162: actual: newAddresslistLength 163: }); 164: }
15: require(end > start); 16: permissions = new PermissionLib.MultiTargetPermission[](end - start); 17: 18: for (uint160 i = start; i < end; i++) { 19: permissions[i - start] = PermissionLib.MultiTargetPermission( 20: op, 21: address(i), 22: address(i), 23: PermissionLib.NO_CONDITION, 24: keccak256("MOCK_PERMISSION") 25: ); 26: }
25: function setBalance(address to, uint256 amount) public { 26: uint256 old = balanceOf(to); 27 if (old < amount) { 28 _mint(to, amount - old); 29 } else if (old > amount) { 30 _burn(to, old - amount); 31 } 32 }
assembly
to write address storage values239: function setSignatureValidator( 240: address _signatureValidator 241: ) external override auth(SET_SIGNATURE_VALIDATOR_PERMISSION_ID) { 242: signatureValidator = IERC1271(_signatureValidator); 243: 244: emit SignatureValidatorSet({signatureValidator: _signatureValidator}); 245: } 281: /// @notice Sets the trusted forwarder on the DAO and emits the associated event. 282: /// @param _trustedForwarder The trusted forwarder address. 283: function _setTrustedForwarder(address _trustedForwarder) internal { 284: trustedForwarder = _trustedForwarder; 285: 286: emit TrustedForwarderSet(_trustedForwarder); 287: }
53: constructor(DAORegistry _registry, PluginSetupProcessor _pluginSetupProcessor) { 54: daoRegistry = _registry; 55: pluginSetupProcessor = _pluginSetupProcessor; 56: 57: daoBase = address(new DAO()); 58: }
22: constructor(PluginRepoRegistry _pluginRepoRegistry) { 23: pluginRepoRegistry = _pluginRepoRegistry; 24: 25: pluginRepoBase = address(new PluginRepo()); 26: }
146: governanceERC20Base = address( 147: new GovernanceERC20( 148: IDAO(address(0)), 149: "baseName", 150: "baseSymbol", 151: GovernanceERC20.MintSettings(new address[](0), new uint256[](0)) 152: ) 153: ); 154: governanceWrappedERC20Base = address( 155: new GovernanceWrappedERC20(IERC20Upgradeable(address(0)), "baseName", "baseSymbol") 156: );
69: resolver = nodeResolver; 107: resolver = _resolver;
27: constructor() { 28: implementation_ = address(new Admin()); 29: }
43: votingToken = _token;
constructor() { 61: governanceERC20Base = address( 62: new GovernanceERC20( 63: IDAO(address(0)), 64: "", 65: "", 66: GovernanceERC20.MintSettings(new address[](0), new uint256[](0)) 67: ) 68: ); 69: governanceWrappedERC20Base = address( 70: new GovernanceWrappedERC20(IERC20Upgradeable(address(0)), "", "") 71: ); 72: tokenVotingBase = new TokenVoting(); 73 }
46: function initialize( 47: IDAO _dao, 48: IERC20Upgradeable _token, 49: bytes32 _merkleRoot 50: ) external initializer { 51: __PluginUUPSUpgradeable_init(_dao); 52: token = _token; 53: merkleRoot = _merkleRoot; 54: }
41: function initialize( 42: IDAO _dao, 43: IERC20MintableUpgradeable _token, 44: IMerkleDistributor _distributorBase 45: ) external initializer { 46: __PluginUUPSUpgradeable_init(_dao); 47: 48: token = _token; 49: distributorBase = _distributorBase; 50: } 53: function changeDistributorBase( 54: IMerkleDistributor _distributorBase 55: ) external override auth(CHANGE_DISTRIBUTOR_PERMISSION_ID) { 56: distributorBase = _distributorBase; 57: }
14: constructor() { 15: implementation = address(new Admin()); 16: }
15: constructor() { 16: pluginBase = address(new PluginUUPSUpgradeableV1Mock()); 17: }
Recommendation Code
function changeDistributorBase( IMerkleDistributor _distributorBase ) external override auth(CHANGE_DISTRIBUTOR_PERMISSION_ID) { assembly { sstore(distributorBase.slot, _distributorBase) } }
Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions.
All Contracts
Find a lower method ID name for the most called functions for example Call() vs. Call1() is cheaper by 22 gas.
For example, the function IDs in the DAO.sol
contract will be the most used; A lower method ID may be given.
Sighash | Function Signature eafb8b06 => initialize(bytes,address,address,string) 552b1863 => isPermissionRestrictedForAnyAddr(bytes32) 5ec29272 => _authorizeUpgrade(address) da742228 => setTrustedForwarder(address) ce1b815f => getTrustedForwarder() fdef9106 => hasPermission(address,address,bytes32,bytes) ee57e36f => setMetadata(bytes) f01de670 => execute(bytes32,Action[],uint256) bfe07da6 => deposit(address,uint256,string) 3e2ab0d9 => setSignatureValidator(address) 1626ba7e => isValidSignature(bytes32,bytes) 4146c61e => _setMetadata(bytes) d292f98a => _setTrustedForwarder(address) f2e5d775 => _registerTokenInterfaces() c4a50145 => registerStandardCallback(bytes4,bytes4,bytes4) 7034731b => daoURI() 1080f99b => setDaoURI(string) f392bdc0 => _setDaoURI(string)
The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping’s value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key’s keccak256 hash (Gkeccak256 - 30 gas) and that calculation’s associated stack operations. Caching an array’s struct avoids recalculating the array offsets into memory/calldata.
460: _executeProposal( 461: dao(), 462: _proposalId, 463: proposals[_proposalId].actions, 464: proposals[_proposalId].allowFailureMap 465: );
_executeProposal( dao(), _proposalId, 356: proposals[_proposalId].actions, 357: proposals[_proposalId].allowFailureMap );
private
rather than public
for constants, saves gas.If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it’s used, and not adding another entry to the method ID table.
/// notice The address of the GovernanceERC20 base contract. 33: address public immutable governanceERC20Base; /// notice The address of the GovernanceWrappedERC20 base contract. 36: address public immutable governanceWrappedERC20Base;
/// @notice The DAO base contract, to be used for creating new DAOs via createERC1967Proxy function. 19: address public immutable daoBase; /// @notice The DAO registry listing the DAO contracts created via this contract. 22: DAORegistry public immutable daoRegistry; /// @notice The plugin setup processor for installing plugins on the newly created DAOs. 25: PluginSetupProcessor public immutable pluginSetupProcessor;
/// @notice The DAO base contract, to be used for creating new DAOs via createERC1967Proxy function. 19: address public immutable daoBase; /// @notice The DAO registry listing the DAO contracts created via this contract. 22: DAORegistry public immutable daoRegistry; /// @notice The plugin setup processor for installing plugins on the newly created DAOs. 25: PluginSetupProcessor public immutable pluginSetupProcessor;
<array>.length
should not be looked up in every loop of a for
-loopThe overheads outlined below are PER LOOP, excluding the first loop
storage arrays incur a Gwarmaccess (100 gas)
memory arrays use MLOAD
(3 gas)
calldata arrays use CALLDATALOAD
(3 gas)
Caching the length changes each of these to a DUP<N>
(3 gas), and gets rid of the extra DUP<N>
needed to store the stack offset
184: for (uint256 i = 0; i < _actions.length; ) {
150: for (uint256 i; i < items.length; ) { 170: for (uint256 i; i < _items.length; ) {
95: for (uint256 i; i < _pluginSettings.length; ++i) {
130: for (uint256 i; i < _actions.length; ) {
124: for (uint256 i; i < _actions.length; ) {
252: for (uint256 i; i < _actions.length; ) {
60: for (uint256 i; i < _newAddresses.length; ) { 78: for (uint256 i; i < _exitingAddresses.length; ) {
81: for (uint256 i; i < _mintSettings.receivers.length; ) {
bytes(subdomain).length == 0
instead of55: if (!(bytes(subdomain).length > 0)) { 56: revert EmptyPluginRepoSubdomain(); 57: }
105: _payload.currentHelpers.length != 0 ? 3 : 2
105: _payload.currentHelpers.length != 0 ? 3 : 2
Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case.
308: bytes32 pluginInstallationId = _getPluginInstallationId(_dao, plugin); 310: bytes32 preparedSetupId = _getPreparedSetupId( _params.pluginSetupRef, hashPermissions(preparedSetupData.permissions), hashHelpers(preparedSetupData.helpers), bytes(""), PreparationType.Installation ); 318: PluginState storage pluginState = states[pluginInstallationId]; // Check if this plugin is already installed. 321: if (pluginState.currentAppliedSetupId != bytes32(0)) { 322: revert PluginAlreadyInstalled(); 323: }
216: if (multisigSettings.onlyListed && !isListedAtBlock(_msgSender(), snapshotBlock)) { 217: revert ProposalCreationForbidden(_msgSender()); 218: } 220: if (_startDate == 0) { 221: _startDate = block.timestamp.toUint64(); 222: } else if (_startDate < block.timestamp.toUint64()) { 223: revert DateOutOfBounds({limit: block.timestamp.toUint64(), actual: _startDate}); 224: } 226: if (_endDate < _startDate) { 227: revert DateOutOfBounds({limit: _startDate, actual: _endDate}); 228: }
assembly
to check for address(0)
Saves 6 gas per instance:
225: if (_token == address(0)) { 252: if (address(signatureValidator) == address(0)) {
86: if (token != address(0)) {
65: if (nodeResolver == address(0)) { 89: if (currentOwner != address(0)) { 103: if (_resolver == address(0)) {
43: if (_multiplyHelper == address(0)) { 80: if (_multiplyHelper == address(0)) {
43: if (_multiplyHelper == address(0)) { 80: if (_multiplyHelper == address(0)) {
39: if (admin == address(0)) {
98: if (token != address(0)) { 181: if (tokenSettings.addr == address(0)) {
24: if (address(ownedIds[_id]) == address(0)) {
116: if (to != address(0) && numCheckpoints(to) == 0 && delegates(to) == address(0)) {
106: if (to != address(0) && numCheckpoints(to) == 0 && delegates(to) == address(0)) {
#0 - c4-judge
2023-03-12T18:16:58Z
0xean marked the issue as grade-a
#1 - novaknole20
2023-03-13T17:58:59Z
good
gain with it and in the end, it's not worth it. Also, 95% Of these, they are upgradeable which means only one time deployment cost happens for implementation for these keccak256 and even if not, we prefer it this way.#2 - c4-sponsor
2023-03-22T10:48:04Z
novaknole20 marked the issue as sponsor acknowledged