Platform: Code4rena
Start Date: 28/06/2022
Pot Size: $25,000 USDC
Total HM: 14
Participants: 50
Period: 4 days
Judge: GalloDaSballo
Total Solo HM: 7
Id: 141
League: ETH
Rank: 42/50
Findings: 1
Award: $23.95
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0x1f8b
Also found by: 0x29A, 0xArshia, 0xKitsune, Bnke0x0, Chom, Fitraldys, Funen, JC, Lambda, Meera, Noah3o6, Picodes, RedOneN, Rohan16, Sm4rty, TerrierLover, TomJ, Tomio, Waze, ajtra, c3phas, cRat1st0s, defsec, durianSausage, fatherOfBlocks, grGred, hake, ladboy233, m_Rassska, mrpathfindr, oyc_109, rfa, ynnad
23.9548 USDC - $23.95
- setAccountantContract(address) should be declared external contracts/CNote.sol#L16-L21 - RetAccountant() should be declared external contracts/Note.sol#L17-L19 - delegateToViewImplementation(bytes) should be declared external contracts/Accountant/AccountantDelegator.sol#L107-L115 - initialize(address) should be declared external contracts/Treasury/TreasuryDelegate.sol#L19-L27 - delegate(address) should be declared external contracts/Governance/Comp.sol#L148-L150 - delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32) should be declared external contracts/Governance/Comp.sol#L161-L170 - getPriorVotes(address,uint256) should be declared external contracts/Governance/Comp.sol#L189-L221 - getActions(uint256) should be declared external: contracts/Governance/GovernorAlpha.sol#L218-L221GovernorAlpha.getActions(uint256) - castVote(uint256,bool) should be declared external: contracts/Governance/GovernorAlpha.sol#L249-L251 - castVoteBySig(uint256,bool,uint8,bytes32,bytes32) should be declared external: contracts/Governance/GovernorAlpha.sol#L253-L260 - __queueSetTimelockPendingAdmin(address,uint256) should be declared external: contracts/Governance/GovernorAlpha.sol#L292-L295 - cancel(uint256) should be declared external: contracts/Governance/GovernorAlpha.sol#L203-L216 - queue(uint256) should be declared external: contracts/Governance/GovernorAlpha.sol#L177-L186 - __abdicate() should be declared external: contracts/Governance/GovernorAlpha.sol#L287-L290 - execute(uint256) should be declared external: contracts/Governance/GovernorAlpha.sol#L193-L201 - propose(address[],uint256[],string[],bytes[],string) should be declared external: contracts/Governance/GovernorAlpha.sol#L136-L175 - __executeSetTimelockPendingAdmin(address,uint256) should be declared external: contracts/Governance/GovernorAlpha.sol#L297-L300 - getReceipt(uint256,address) should be declared external: contracts/Governance/GovernorAlpha.sol#L223-L225 - __acceptAdmin() should be declared external contracts/Governance/GovernorAlpha.sol#L282-L285 - delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32) should be declared external: contracts/Governance/Comp.sol#L161-L170 - setAccountantContract(address) should be declared external: contracts/CNote.sol#L16-L21 - initialize(address) should be declared external: contracts/Treasury/TreasuryDelegate.sol#L19-L27 - initialize(address) should be declared external: contracts/Governance/GovernorBravoDelegate.sol#L24-L31 - drip() should be declared external: contracts/Reservoir.sol#L46-L67 - RetAccountant() should be declared external: contracts/Note.sol#L17-L19 - delegateToViewImplementation(bytes) should be declared external: contracts/Accountant/AccountantDelegator.sol#L107-L115
require
statements.contracts/CNote.sol
- require(msg.sender == admin, "CNote::_setAccountantContract:Only admin may call this function"); - require(success, "TOKEN_TRANSFER_IN_FAILED"); - require(balanceCur == 0, "Accountant has not been correctly supplied"); - require(success, "TOKEN_TRANSFER_OUT_FAILED"); - require(token.balanceOf(address(this)) == 0, "cNote::doTransferOut: TransferOut Failed"); - require(_notEntered, "re-entered"); - require(_notEntered, "re-entered");
contracts/CToken.sol
- require(msg.sender == admin, "only admin may initialize the market"); - require(accrualBlockNumber == 0 && borrowIndex == 0, "market may only be initialized once"); - require(initialExchangeRateMantissa > 0, "initial exchange rate must be greater than zero."); - require(err == NO_ERROR, "setting comptroller failed"); - require(err == NO_ERROR, "setting interest rate model failed"); - require(borrowRateMantissa <= borrowRateMaxMantissa, "borrow rate is absurdly high"); - require(redeemTokensIn == 0 || redeemAmountIn == 0, "one of redeemTokensIn or redeemAmountIn must be zero"); - require(amountSeizeError == NO_ERROR, "LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED"); - require(cTokenCollateral.balanceOf(borrower) >= seizeTokens, "LIQUIDATE_SEIZE_TOO_MUCH"); - require(cTokenCollateral.seize(liquidator, borrower, seizeTokens) == NO_ERROR, "token seizure failed"); - require(newComptroller.isComptroller(), "marker method returned false");
contracts/NoteInterest.sol
- require(msg.sender == admin, "only the admin may set the base rate"); - require(msg.sender == admin, "only the admin may set the adjuster coefficient"); - require(msg.sender == admin, "only the admin may set the update frequency");
contracts/Accountant/AccountantDelegate.sol
- require(cNoteAmt >= noteDiff, "AccountantDelegate::sweepInterest:Error calculating interest to sweep"); - require(cnote.balanceOf(treasury) == 0, "AccountantDelegate::sweepInterestError");
contracts/Accountant/AccountantDelegator.sol
- require(msg.sender == admin, "AccountantDelegator::_setImplementation: admin only"); - require(implementation_ != address(0), "AccountantDelegator::_setImplementation: invalid implementation address"); - require(msg.value == 0,"AccountantDelegator:fallback: cannot send value to fallback");
contracts/Comp.sol
- require(signatory != address(0), "Comp::delegateBySig: invalid signature"); - require(nonce == nonces[signatory]++, "Comp::delegateBySig: invalid nonce"); - require(block.timestamp <= expiry, "Comp::delegateBySig: signature expired"); - require(blockNumber < block.number, "Comp::getPriorVotes: not yet determined"); - require(src != address(0), "Comp::_transferTokens: cannot transfer from the zero address"); - require(dst != address(0), "Comp::_transferTokens: cannot transfer to the zero address");
Following contracts also contains optimization possibilities.
Define your own custom errors and revert them instead of directly provided revert-message. I would probably create my own library for errors, but it's up to you.
It's also great to use error-codes, like: "L1" or "P1, P2", alowing fetching this codes with actual messages off-chain.
<br/>contracts/GovernorAlpha.sol
Maybe, it's better to push this proposal into the memory rather than operating with references? I undestood the motivation behind the reference to the state var, simply you want to be able to change some attributes, but if you want to read those attributes, it's better to load directly from the memory, because SLOAD >> MLOAD. It's sagnificant gas saving, especially in the loops.
- // Lines: [179-183] Proposal storage proposal = proposals[proposalId]; uint eta = add256(block.timestamp, timelock.delay()); for (uint i = 0; i < proposal.targets.length; i++) { _queueOrRevert(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta); } - // Lines: [195-199] Proposal storage proposal = proposals[proposalId]; proposal.executed = true; for (uint i = 0; i < proposal.targets.length; i++) { timelock.executeTransaction{value: proposal.values[i]}(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta); } - // Lines: [207-213] Proposal storage proposal = proposals[proposalId]; proposal.executed = true; for (uint i = 0; i < proposal.targets.length; i++) { timelock.executeTransaction{value: proposal.values[i]}(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta); } - // Lines: [218-221] function getActions(uint proposalId) public view returns (address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas) { Proposal storage p = proposals[proposalId]; return (p.targets, p.values, p.signatures, p.calldatas); } - // Lines: [227-247] function state(uint proposalId) public view returns (ProposalState) { require(proposalCount >= proposalId && proposalId > 0, "GovernorAlpha::state: invalid proposal id"); Proposal storage proposal = proposals[proposalId]; if (proposal.canceled) { return ProposalState.Canceled; } else if (block.number <= proposal.startBlock) { return ProposalState.Pending; } else if (block.number <= proposal.endBlock) { return ProposalState.Active; } else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes()) { return ProposalState.Defeated; } else if (proposal.eta == 0) { return ProposalState.Succeeded; } else if (proposal.executed) { return ProposalState.Executed; } else if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) { return ProposalState.Expired; } else { return ProposalState.Queued; } } - // Lines: [262-280] function _castVote(address voter, uint proposalId, bool support) internal { require(state(proposalId) == ProposalState.Active, "GovernorAlpha::_castVote: voting is closed"); Proposal storage proposal = proposals[proposalId]; Receipt storage receipt = proposal.receipts[voter]; require(receipt.hasVoted == false, "GovernorAlpha::_castVote: voter already voted"); uint96 votes = comp.getPriorVotes(voter, proposal.startBlock); if (support) { proposal.forVotes = add256(proposal.forVotes, votes); } else { proposal.againstVotes = add256(proposal.againstVotes, votes); } receipt.hasVoted = true; receipt.support = support; receipt.votes = votes; emit VoteCast(voter, proposalId, support, votes); } - // Lines: [287-290] function __abdicate() public { require(msg.sender == guardian, "GovernorAlpha::__abdicate: sender must be gov guardian"); guardian = address(0); } - // Lines: [292-295] function __queueSetTimelockPendingAdmin(address newPendingAdmin, uint eta) public { require(msg.sender == guardian, "GovernorAlpha::__queueSetTimelockPendingAdmin: sender must be gov guardian"); timelock.queueTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta); } - // Lines: [297-300] function __executeSetTimelockPendingAdmin(address newPendingAdmin, uint eta) public { require(msg.sender == guardian, "GovernorAlpha::__executeSetTimelockPendingAdmin: sender must be gov guardian"); timelock.executeTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta); }
- // Lines: [57-60] uint dripTotal_ = mul(dripRate_, blockNumber_ - dripStart_, "dripTotal overflow"); uint deltaDrip_ = sub(dripTotal_, dripped_, "deltaDrip underflow"); uint toDrip_ = min(reservoirBalance_, deltaDrip_); uint drippedNext_ = add(dripped_, toDrip_, "tautological");
- // Lines: [71-101] function add(uint a, uint b, string memory errorMessage) internal pure returns (uint) { uint c; unchecked { c = a + b; } require(c >= a, errorMessage); return c; } function sub(uint a, uint b, string memory errorMessage) internal pure returns (uint) { require(b <= a, errorMessage); uint c = a - b; return c; } function mul(uint a, uint b, string memory errorMessage) internal pure returns (uint) { if (a == 0) { return 0; } uint c; unchecked { c = a * b; } require(c / a == b, errorMessage); return c; } function min(uint a, uint b) internal pure returns (uint) { if (a <= b) { return a; } else { return b; } }
- // Lines: [302-311] function add256(uint256 a, uint256 b) internal pure returns (uint) { uint c = a + b; require(c >= a, "addition overflow"); return c; } function sub256(uint256 a, uint256 b) internal pure returns (uint) { require(b <= a, "subtraction underflow"); return a - b; }
- // Lines: [174-183] function add256(uint256 a, uint256 b) internal pure returns (uint) { uint c = a + b; require(c >= a, "addition overflow"); return c; } function sub256(uint256 a, uint256 b) internal pure returns (uint) { require(b <= a, "subtraction underflow"); return a - b; }
dripped = 0
or uint i = 0
, it's == 0 by default. It consumes gas for MLOAD/SLOAD opcodesrequire(a == b && c == d)
=> require(a == b); require(c == d)
, extra gas usage for AND
opcode.if (msg.sender != admin){revert ...}
with modifiers, reduces deployment cost.uint256
=> uint96/uint32
, if it's not properly tide up in a storage. It costs extra gas for casting..balance
see EIP-2929.#0 - GalloDaSballo
2022-08-14T20:55:33Z
This is ignoring the fact that in all the linked instances, each storage value is read only once, a pointer to storage is actually cheaper for all those cases.
The one exception is proposal.targets.length;
however I will not count this as valid as 99% of the submissions is incorrect
Missing immutables, will save less than 300 gas
#1 - GalloDaSballo
2022-08-14T20:55:59Z
I really appreciate the manual report, next time try to find clear Storage savings, less can be more