Platform: Code4rena
Start Date: 16/12/2022
Pot Size: $60,500 USDC
Total HM: 12
Participants: 58
Period: 5 days
Judge: Trust
Total Solo HM: 4
Id: 196
League: ETH
Rank: 47/58
Findings: 1
Award: $40.94
π Selected for report: 0
π Solo Findings: 0
π Selected for report: IllIllI
Also found by: 0xSmartContract, 0xhacksmithh, Awesome, Aymen0909, Mukund, RaymondFam, Rolezn, TomJ, c3phas, eyexploit, noot, rbitbytes, rjs, saneryee
40.9392 USDC - $40.94
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 | 2 |
[G-002] | Functions guaranteed to revert when called by normal users can be marked payable | 7 |
[G-003] | Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate | 1 |
[G-004] | Multiple accesses of a mapping/array should use a local variable cache | 1 |
[G-005] | internal functions only called once can be inlined to save gas | 6 |
[G-006] | Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead | 1 |
[G-007] | Replace modifier with function | 1 |
[G-008] | Use named returns for local variables where it is possible. | 1 |
[G-009] | revert operator should be in the code as early as reasonably possible | 1 |
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:2
src/libraries/OracleLibrary.sol#L57
57: uint32[] memory secondAgos = new uint32[](1);
src/libraries/OracleLibrary.sol#L59
59: (int56[] memory tickCumulatives,) = IUniswapV3Pool(pool).observe(secondAgos);
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:7
28: function burn(address account, uint256 amount) external onlyController {
24: function mint(address to, uint256 amount) external onlyController {
382: function sendPaprFromAuctionFees(address to, uint256 amount) external override onlyOwner {
386: function burnPaprFromAuctionFees(uint256 amount) external override onlyOwner {
355: function setFundingPeriod(uint256 _fundingPeriod) external override onlyOwner {
360: function setLiquidationsLocked(bool locked) external override onlyOwner {
350: function setPool(address _pool) external override onlyOwner {
Mark the function as payable.
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
57: mapping(ERC721 => mapping(uint256 => address)) public override collateralOwner; 58: 59: /// @inheritdoc IPaprController 60: mapping(address => bool) public override isAllowed; 61οΌ 62: /// @dev account => asset => vaultInfo 63: mapping(address => mapping(ERC721 => IPaprController.VaultInfo)) private _vaultInfo;
Same as description
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
Total:1
src/NFTEDA/extensions/NFTEDAStarterIncentive.sol#L44
44: auctionState[id] = AuctionState({startTime: uint96(block.timestamp), starter: msg.sender});
Same as description
Not inlining costs 20 to 40 gas because of two extra JUMP
instructions and additional stack operations needed for function calls.
Total:6
src/UniswapOracleFundingRateController.sol#L156
156: function _multiplier(uint256 _mark_, uint256 cachedTarget) internal view returns (uint256) {
src/UniswapOracleFundingRateController.sol#L122
122: function _setFundingPeriod(uint256 _fundingPeriod) internal {
src/UniswapOracleFundingRateController.sol#L111
111: function _setPool(address _pool) internal {
106: function _clearAuctionState(uint256 id) internal virtual;
101: function _setAuctionStartTime(uint256 id) internal virtual;
src/NFTEDA/extensions/NFTEDAStarterIncentive.sol#L72
72: function _setCreatorDiscount(uint256 _auctionCreatorDiscountPercentWad) internal virtual {
Same as description
uints/ints
smaller than 32 bytes (256 bits) incurs overheadWhen using elements that are smaller than 32 bytes, your contractβs gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed
Total:1
src/libraries/OracleLibrary.sol#L42
42: int56 delta = endTick - startTick;
Same as description
Modifiers make code more elegant, but cost more than normal functions.
Total:1
11: modifier onlyController() {
Same as description
It is not necessary to have both a named return and a return statement.
Total:1
src/NFTEDA/extensions/NFTEDAStarterIncentive.sol#L60
60: uint256 price = super._auctionCurrentPrice(id, startTime, auction);
revert
operator should be in the code as early as reasonably possible
Total:1
src/PaprController.sol#L321-322
321: if (block.timestamp - info.latestAuctionStartTime < liquidationAuctionMinSpacing) { 322: revert IPaprController.MinAuctionSpacing();
#0 - c4-judge
2022-12-25T09:52:00Z
trust1995 marked the issue as grade-b