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: 40/42
Findings: 1
Award: $53.96
π Selected for report: 0
π Solo Findings: 0
π 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
53.963 USDC - $53.96
Issue | Instances | |
---|---|---|
[G-001] | storage pointer to a structure is cheaper than copying each value of the structure into memory , same for array and mapping | 7 |
[G-002] | Functions guaranteed to revert when called by normal users can be marked payable | 3 |
[G-003] | Don't use _msgSender() if not supporting EIP-2771 | 6 |
[G-004] | Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate | 1 |
[G-005] | Using calldata instead of memory for read-only arguments in external functions saves gas | 7 |
[G-006] | Multiple accesses of a mapping/array should use a local variable cache | 1 |
[G-007] | internal functions only called once can be inlined to save gas | 9 |
[G-008] | Expressions that cannot be overflowed can be unchecked | 3 |
storage
pointer to a structure is cheaper than copying each value of the structure into memory
, same for array
and mapping
It may not be obvious, but every time you copy a storage struct/array/mapping
to a memory
variable, you are literally copying each member by reading it from storage
, which is expensive. And when you use the storage
keyword, you are just storing a pointer to the storage, which is much cheaper.
Total:7
packages/contracts/src/plugins/governance/majority-voting/token/TokenVotingSetup.sol#L96
96: address[] memory helpers = new address[](1);
packages/contracts/src/plugins/governance/majority-voting/token/TokenVotingSetup.sol#L266
266: bytes4[] memory interfaceIds = new bytes4[](3);
packages/contracts/src/plugins/governance/majority-voting/token/TokenVotingSetup.sol#L108
108: bool[] memory supportedIds = _getTokenInterfaceIds(token);
packages/contracts/src/plugins/governance/majority-voting/token/TokenVotingSetup.sol#L212
212: bool[] memory supportedIds = _getTokenInterfaceIds(token);
packages/contracts/src/plugins/counter-example/v2/CounterV2PluginSetup.sol#L59
59: address[] memory helpers = new address[](1);
packages/contracts/src/plugins/counter-example/v2/CounterV2PluginSetup.sol#L132
132: address[] memory activeHelpers = new address[](1);
packages/contracts/src/plugins/counter-example/v1/CounterV1PluginSetup.sol#L58
58: address[] memory helpers = new address[](1);
Using storage
instead of memory
If a function modifier such as onlyOwner
is used, the function will revert if a normal user tries to pay the function. Marking the function as payable
will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.
The extra opcodes avoided are CALLVALUE(2)
,DUP1(3)
,ISZERO(3)
,PUSH2(3)
,JUMPI(10)
,PUSH1(3)
,DUP1(3)
,REVERT(0)
,JUMPDEST(1)
,POP(2)
, which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
Total:3
packages/contracts/src/framework/utils/InterfaceBasedRegistry.sol#L46
46: ) internal virtual onlyInitializing {
packages/contracts/src/core/plugin/PluginUUPSUpgradeable.sol#L39
39: function __PluginUUPSUpgradeable_init(IDAO _dao) internal virtual onlyInitializing {
packages/contracts/src/core/plugin/PluginCloneable.sol#L22
22: function __PluginCloneable_init(IDAO _dao) internal virtual onlyInitializing {
Mark the function as payable.
_msgSender()
if not supporting EIP-2771The use of _msgSender()
when there is no implementation of a meta transaction mechanism that uses it, such as EIP-2771, very slightly increases gas consumption.
Total:6
packages/contracts/src/plugins/governance/majority-voting/addresslist/AddresslistVoting.sol#L97
97: if (minProposerVotingPower() != 0 && !isListedAtBlock(_msgSender(), snapshotBlock)) {
packages/contracts/src/plugins/governance/majority-voting/addresslist/AddresslistVoting.sol#L98
98: revert ProposalCreationForbidden(_msgSender());
packages/contracts/src/plugins/governance/majority-voting/token/TokenVoting.sol#L91
91: if (votingToken.getPastVotes(_msgSender(), snapshotBlock) < minProposerVotingPower()) {
packages/contracts/src/plugins/governance/majority-voting/token/TokenVoting.sol#L92
92: revert ProposalCreationForbidden(_msgSender());
packages/contracts/src/plugins/governance/multisig/Multisig.sol#L216
216: if (multisigSettings.onlyListed && !isListedAtBlock(_msgSender(), snapshotBlock)) {
packages/contracts/src/plugins/governance/multisig/Multisig.sol#L217
217: revert ProposalCreationForbidden(_msgSender());
Replace _msgSender()
with msg.sender
if there is no mechanism to support meta-transactions like EIP-2771 implemented.
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~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.
Total:1
packages/contracts/src/framework/plugin/repo/PluginRepo.sol#L55-L58
55: mapping(uint8 => uint16) internal buildsPerRelease; 56: 57: /// @notice The mapping between the version hash and the corresponding version information. 58: mapping(bytes32 => Version) internal versions;
calldata
instead of memory for read-only arguments in external functions saves gasWhen a function with a memory
array is called externally, the abi.decode()
step has to use a for-loop
to copy each index of the calldata
to the memory
index. Each iteration of this for-loop
costs at least 60 gas (i.e. 60 * <mem_array>.length
). Using calldata
directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having memory
arguments, it's still valid for implementation contracs to use calldata
arguments instead.<br><br> If the array is passed to an internal
function which passes the array to another internal function where the array is modified and therefore memory
is used in the external
call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one.
Total:7
packages/contracts/src/framework/plugin/setup/PluginSetupProcessorHelpers.sol#L86-L87
86: function hashPermissions( 87: PermissionLib.MultiTargetPermission[] memory _permissions
packages/contracts/src/plugins/token/MerkleDistributor.sol#L66-L70
66: function unclaimedBalance( 67: uint256 _index, 68: address _to, 69: uint256 _amount, 70: bytes32[] memory _proof
packages/contracts/src/plugins/token/MerkleDistributor.sol#L83-L87
83: function unclaimedBalance( 84: uint256 _index, 85: address _to, 86: uint256 _amount, 87: bytes32[] memory _proof
packages/contracts/src/plugins/token/MerkleDistributor.sol#L99-L103
99: function unclaimedBalance( 100: uint256 _index, 101: address _to, 102: uint256 _amount, 103: bytes32[] memory _proof
packages/contracts/src/plugins/token/IMerkleDistributor.sol#L28-L32
28: function unclaimedBalance( 29: uint256 _index, 30: address _to, 31: uint256 _amount, 32: bytes32[] memory _proof
packages/contracts/src/plugins/token/IMerkleDistributor.sol#L41-L45
41: function unclaimedBalance( 42: uint256 _index, 43: address _to, 44: uint256 _amount, 45: bytes32[] memory _proof
packages/contracts/src/core/dao/IDAO.sol#L46-L48
46: function execute( 47: bytes32 _callId, 48: Action[] memory _actions,
mapping/array
should use a local variable cacheThe 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
(δΈ[G-001]η±»δΌΌ)
Total:1
packages/contracts/src/framework/utils/InterfaceBasedRegistry.sol#L71
71: entries[_registrant] = true;
Not inlining costs 20 to 40 gas because of two extra JUMP
instructions and additional stack operations needed for function calls.
Total:9
packages/contracts/src/plugins/governance/majority-voting/MajorityVotingBase.sol#L457
457: function _execute(uint256 _proposalId) internal virtual {
packages/contracts/src/framework/dao/DAOFactory.sol#L156
156: function _setDAOPermissions(DAO _dao) internal {
packages/contracts/src/framework/dao/DAOFactory.sol#L141
141: function _createDAO(DAOSettings calldata _daoSettings) internal returns (DAO dao) {
packages/contracts/src/framework/plugin/repo/PluginRepoFactory.sol#L65
65: function _setPluginRepoPermissions(PluginRepo pluginRepo, address maintainer) internal {
packages/contracts/src/core/permission/PermissionManager.sol#L327
327: function _auth(bytes32 _permissionId) internal view virtual {
packages/contracts/src/core/permission/PermissionManager.sol#L212
212: function _initializePermissionManager(address _initialOwner) internal {
packages/contracts/src/core/plugin/proposal/ProposalUpgradeable.sol#L33
33: function _createProposalId() internal returns (uint256 proposalId) {
packages/contracts/src/core/plugin/proposal/Proposal.sol#L33
33: function _createProposalId() internal returns (uint256 proposalId) {
packages/contracts/src/token/ERC20/governance/GovernanceERC20.sol#L112
112: function _afterTokenTransfer(address from, address to, uint256 amount) internal override {
There are several cases that do not lead to overflow and can be unchecked.
Total:3
packages/contracts/src/plugins/utils/Ratio.sol#L23
23: uint256 remainder = _value % RATIO_BASE;
packages/contracts/src/plugins/token/MerkleDistributor.sol#L112
112: uint256 claimedBit_index = _index % 256;
packages/contracts/src/plugins/token/MerkleDistributor.sol#L122
122: uint256 claimedBit_index = _index % 256;
#0 - c4-judge
2023-03-12T17:50:00Z
0xean marked the issue as grade-b
#1 - c4-sponsor
2023-03-22T10:12:32Z
novaknole20 marked the issue as sponsor acknowledged