Platform: Code4rena
Start Date: 20/09/2022
Pot Size: $100,000 USDC
Total HM: 4
Participants: 109
Period: 7 days
Judge: GalloDaSballo
Id: 163
League: ETH
Rank: 4/109
Findings: 3
Award: $6,785.46
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: minhtrng
Also found by: 0xSmartContract, IllIllI, rbserver
https://github.com/code-423n4/2022-09-artgobblers/blob/main/src/utils/GobblerReserve.sol#L34 https://github.com/code-423n4/2022-09-artgobblers/blob/main/src/ArtGobblers.sol#L560
GobblerReserve.sol#L34
The withdraw
function on GobblerReserve.sol
has a single point of failure and onlyOwner
can withdraw all gobblers from the reserve
Admin is not behind a multisig and changes are not behind a timelock.Even if protocol admins/developers are not malicious there is still a chance for admin keys to be stolen. In such case, an attacker could steal all the reserve. In such a case, users who have invested in NFTs will suffer high financial losses.
function withdraw(address to, uint256[] calldata ids) external onlyOwner { unchecked { for (uint256 i = 0; i < ids.length; i++) { artGobblers.transferFrom(address(this), to, ids[i]); } } }
ArtGobblers.sol#L560
The upgradeRandProvider
function on ArtGobblers.sol
has a single point of failure and onlyOwner
can change the rand provider contract
This authorization given for the upgrade of rand provider may cause a malicious contract to be added in case the password is stolen.
function upgradeRandProvider(RandProvider newRandProvider) external onlyOwner { // Revert if waiting for seed, so we don't interrupt requests in flight. if (gobblerRevealsData.waitingForSeed) revert SeedPending(); randProvider = newRandProvider; // Update the randomness provider. emit RandProviderUpgraded(msg.sender, newRandProvider); }
onlyowner
is defined as teamColdWallet
in the project and is also the only recipient address of NFTs printed with the mintCommunityPages
function in Pages.sol
This increases the risk of A single point of failure
See this example where a similar finding has been flagged as a high-severity issue: https://github.com/code-423n4/2021-08-realitycards-findings/issues/73
Manuel Code Review
Split the role into two : Get upgradeRandProvider and withdraw different roles Add a time lock to these 2 critical functions. Admin-only functions that change critical parameters should emit events and have timelocks. Events allow capturing the changed parameters so that off-chain tools/interfaces can register such changes with timelocks that allow users to evaluate them and consider if they would like to engage/exit based on how they perceive the changes as affecting the trustworthiness of the protocol or profitability of the implemented financial services. Allow only multi-signature wallets to call the function to reduce the likelihood of an attack.
https://twitter.com/danielvf/status/1572963475101556738?s=20&t=V1kvzfJlsx-D2hfnG0OmuQ
#0 - Shungy
2022-09-28T18:13:40Z
#1 - GalloDaSballo
2022-09-29T20:51:27Z
Will bulk as dup for Randomness Provider
#2 - GalloDaSballo
2022-09-29T20:51:48Z
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0x52, 0x5rings, 0xNazgul, 0xRobocop, 0xSmartContract, 0xdeadbeef, 0xsanson, 8olidity, Amithuddar, Aymen0909, B2, B353N, CertoraInc, Ch_301, Chom, CodingNameKiki, Deivitto, ElKu, Funen, JC, JohnnyTime, Kresh, Lambda, Noah3o6, RaymondFam, ReyAdmirado, RockingMiles, Rolezn, Sm4rty, SuldaanBeegsi, Tadashi, TomJ, Tomio, V_B, Waze, __141345__, a12jmx, ak1, arcoun, asutorufos, aviggiano, berndartmueller, bharg4v, bin2chen, brgltd, bulej93, c3phas, catchup, cccz, ch0bu, cryptonue, cryptphi, csanuragjain, delfin454000, devtooligan, djxploit, durianSausage, eighty, erictee, exd0tpy, fatherOfBlocks, giovannidisiena, hansfriese, ignacio, joestakey, ladboy233, lukris02, m9800, malinariy, martin, minhtrng, obront, oyc_109, pedr02b2, pedroais, pfapostol, philogy, prasantgupta52, rbserver, ronnyx2017, rotcivegaf, rvierdiiev, sach1r0, shung, simon135, throttle, tnevler, tonisives, wagmi, yixxas, zkhorse, zzykxx, zzzitron
55.1985 USDC - $55.20
Number | Issues Details | Context |
---|---|---|
[N-01] | Include return parameters in NatSpec comments | 14 |
[N-02] | 0 address check | 9 |
[N-03] | Long lines are not suitable for the Solidity Style Guide | 1 |
[N-04] | Inconsistent NatSpec desciption |
Total 4 issues
Number | Issues Details | Context |
---|---|---|
[L-01] | Add to blacklist function | |
[L-02] | Missing supportsInterface | 3 |
[L-03] | Lock pragmas to specific compiler version | |
[L-04] | Avoid shadowing inherited state variables | 1 |
Total 4 issues
return parameters
in NatSpec commentsContext: Pages.sol#L265 Pages.sol#L239 Pages.sol#L219 ArtGobblers.sol#L872 ArtGobblers.sol#L866 ArtGobblers.sol#L839 ArtGobblers.sol#L757 ArtGobblers.sol#L693 ArtGobblers.sol#L509 Functions with all return in GobblersERC1155B.sol Functions with all return in GobblersERC721.sol Functions with all return in PagesERC721.sol ChainlinkV1RandProvider.sol#L62 Functions with all return in SignedWadMath.sol
Description: It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.
https://docs.soliditylang.org/en/v0.8.15/natspec-format.html
If Return parameters are declared, you must prefix them with "/// @return".
Some code analysis programs do analysis by reading NatSpec details, if they can't see the "@return" tag, they do incomplete analysis.
Recommendation: Include return parameters in NatSpec comments
Current Code:
src/Pages.sol#L265 /// @notice Returns a page's URI if it has been minted. /// @param pageId The id of the page to get the URI for. function tokenURI(uint256 pageId) public view virtual override returns (string memory) { if (pageId == 0 || pageId > currentId) revert("NOT_MINTED"); return string.concat(BASE_URI, pageId.toString()); }
Recommendation Code:
/// @notice information about what a function does /// @param pageId The id of the page to get the URI for. /// @return Returns a page's URI if it has been minted function tokenURI(uint256 pageId) public view virtual override returns (string memory) { if (pageId == 0 || pageId > currentId) revert("NOT_MINTED"); return string.concat(BASE_URI, pageId.toString()); }
0
address checkContext: ArtGobblers.sol#L294 ArtGobblers.sol#L295 ArtGobblers.sol#L292 ArtGobblers.sol#L293 ArtGobblers.sol#L296 Goo.sol#L82 Pages.sol#L160 Pages.sol#L161 Pages.sol#L162
Description: Also check of the address to protect the code from 0x0 address problem just in case. This is best practice or instead of suggesting that they verify address != 0x0, you could add some good natspec comments explaining what is valid and what is invalid and what are the implications of accidentally using an invalid address.
Recommendation:
like this ;
if (_team == address(0)) revert ADDRESS_ZERO();
Solidity Style Guide
Context: ArtGobblers.sol#L499
Description: It is generally recommended that lines in the source code should not exceed 80-120 characters. Today's screens are much larger, so in some cases it makes sense to expand that. The lines above should be split when they reach that length, as the files will most likely be on GitHub and GitHub always uses a scrollbar when the length is more than 164 characters.
(why-is-80-characters-the-standard-limit-for-code-width)[https://softwareengineering.stackexchange.com/questions/148677/why-is-80-characters-the-standard-limit-for-code-width]
Recommendation: Multiline output parameters and return statements should follow the same style recommended for wrapping long lines found in the Maximum Line Length section.
https://docs.soliditylang.org/en/v0.8.17/style-guide.html#introduction
thisFunctionCallIsReallyLong( longArgument1, longArgument2, longArgument3 );
Description:
One of the Pages.sol
contract constructor arguments is address _community
and NatSpec explanation :
"/// @param _community Address of the community reserve"
One of the ArtGobblers.sol
contract constructor arguments is address _community
and NatSpec explanation :
"/// @param _community Address of the community reserve"
However, the addresses defined during these two constructor a deploy are different.
Recommendation: Update comments or addresses.
Description:
Cryptocurrency mixing service, Tornado Cash, has been blacklisted in the OFAC.
A lot of blockchain companies, token projects, NFT Projects have blacklisted
all Ethereum addresses owned by Tornado Cash listed in the US Treasury Department's sanction against the protocol.
https://home.treasury.gov/policy-issues/financial-sanctions/recent-actions/20220808
In addition, these platforms even ban accounts that have received ETH on their account with Tornadocash.
Some of these Projects; USDC (https://www.circle.com/en/usdc) Flashbots (https://www.paradigm.xyz/portfolio/flashbots ) Aave (https://aave.com/) Uniswap Balancer Infura Alchemy Opensea dYdX
Details on the subject; https://twitter.com/bantg/status/1556712790894706688?s=20&t=HUTDTeLikUr6Dv9JdMF7AA
For this reason, every project in the Ethereum network must have a blacklist function, this is a good method to avoid legal problems in the future, apart from the current need.
Transactions from the project by an account funded by Tonadocash or banned by OFAC can lead to legal problems.Especially American citizens may want to get addresses to the blacklist legally, this is not an obligation
If you think that such legal prohibitions should be made directly by validators, this may not be possible: https://www.paradigm.xyz/2022/09/base-layer-neutrality
The ban on Tornado Cash makes little sense, because in the end, no one can prevent people from using other mixer smart contracts, or forking the existing ones. It neither hinders cybercrime, nor privacy.
Here is the most beautiful and close to the project example; Manifold
Manifold Contract https://etherscan.io/address/0xe4e4003afe3765aca8149a82fc064c0b125b9e5a#code
modifier nonBlacklistRequired(address extension) { require(!_blacklistedExtensions.contains(extension), "Extension blacklisted"); _; }
Recommended Mitigation Steps add to Blacklist function and modifier.
Context: GobblersERC1155B.sol#L124 GobblersERC721.sol#L149 PagesERC721.sol#L162
Description: ERC721TokenReceiver interface ID is missing in supportInterface
function supportsInterface(bytes4 _interfaceId) external pure returns (bool) { return _interfaceId == 0x01ffc9a7 || // ERC165 Interface ID _interfaceId == 0x80ac58cd || // ERC721 Interface ID _interfaceId == 0x5b5e139f; // ERC721Metadata Interface ID }
Recommendation:
function supportsInterface(bytes4 _interfaceId) external pure returns (bool) { return _interfaceId == 0x01ffc9a7 || // ERC165 Interface ID _interfaceId == 0x80ac58cd || // ERC721 Interface ID _interfaceId == 0x150b7a02 || // ERC721TokenReceiver Interface ID _interfaceId == 0x5b5e139f; // ERC721Metadata Interface ID }
Description: Pragma statements can be allowed to float when a contract is intended for consumption by other developers, as in the case with contracts in a library or EthPM package. Otherwise, the developer would need to manually update the pragma in order to compile locally. https://swcregistry.io/docs/SWC-103
Recommendation: Ethereum Smart Contract Best Practices - Lock pragmas to specific compiler version. solidity-specific/locking-pragmas
inherited state variables
Context: ArtGobblers.sol#L730
Description:
In ArtGobblers.sol#L730
, there is a local variable named owner
, but there is a state varible named owner
in the inherited Owned.sol
with the same name. This use causes compilers to issue warnings, negatively affecting checking and code readability.
function gobble( uint256 gobblerId, address nft, uint256 id, bool isERC1155 ) external { address owner = getGobblerData[gobblerId].owner; // Codes...
Recommendation: Avoid using variables with the same name, including inherited in the same contract, if used, it must be specified in the NatSpec comments.
[S-01] Gobblers NFTs are "Art Gobblers themselves are fully animated ERC1155 NFTs." it is mentioned as ERC721 using GobblersERC721.sol file in the project, documents should be corrected
[S-02] The getTargetSaleTime function in the VRGDA.sol file does not have a body, the reason for this preference is not clear, at least it should broadcast br emit
function getTargetSaleTime(int256 sold) public view virtual returns (int256);
[S-03] For code readability, add real numbers as comments with "//" next to scientific notation. Recommendation;
GobblersERC721("Art Gobblers", "GOBBLER") Owned(msg.sender) LogisticVRGDA( 69.42e18, // Target price. // 69420000000000000000 0.31e18, // Price decay percent. // 310000000000000000 // Max gobblers mintable via VRGDA. toWadUnsafe(MAX_MINTABLE), 0.0023e18 // Time scale. // 2300000000000000
[S-04] Natspec comments are missing the return tag, add it as in all other functions. ArtGobblers.sol#L509
[S-05] For code readability, make sure what the numbers look like below, at least specify "//" in comments Recommendation;
MAX_SUPPLY = 10000; MINTLIST_SUPPLY = 2000; LEGENDARY_SUPPLY = 10; RESERVED_SUPPLY = 1598 //(MAX_SUPPLY - MINTLIST_SUPPLY - LEGENDARY_SUPPLY) / 5; MAX_MINTABLE = 6392 // MAX_SUPPLY- MINTLIST_SUPPLY- LEGENDARY_SUPPLY- RESERVED_SUPPLY;
[S-06] ChainlinkV1RandProvider.sol file tests could not be seen, because it is an external call, rinkeby tests must be done and added to the project. The Virtual Test environment is insufficient for such tests.
#0 - GalloDaSballo
2022-10-04T22:00:07Z
Really not a fan, but valid NC
L
NC
Not convinced
Disagree, the Sponsor will release the project and let it live on it's own
Per discussion about using safeTransfer, this is Refactoring R
NC
Valid Low
NC
The finding is nice, but invalid, basically you're supposed to inherit LogisticVRGDA
not the base Contract, these child contract do implement the function meaning ArtGobblers also does
NC
NC
Disagree as I think having constants build as derivate of other constants is fine
Disagree as Sponsor can put out of scope at their discretion
Overall an interesting report
#1 - GalloDaSballo
2022-10-04T22:00:35Z
2L 1R 6NC
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xNazgul, 0xSmartContract, Atarpara, CertoraInc, Deathstore, Deivitto, ElKu, MiloTruck, ReyAdmirado, SnowMan, Tadashi, V_B, __141345__, aviggiano, catchup, djxploit, gogo, pfapostol, philogy, shung
68.6605 USDC - $68.66
Number | Optimization Details | Per gas saved | Context |
---|---|---|---|
[G-01] | No need return keyword for gas efficient (for external functions) | 3 | 2 |
[G-02] | Shifting cheaper than division | 6 | 1 |
[G-03] | Using uint256 instead of bool in mappings is more gas efficient | 146 | 2 |
[G-04] | Use assembly to write address storage values | 33-399 | 13 |
[G-05] | Function ordering via methodID | 22 | All contracts |
[G-06] | Use assembly to check for address(0) | 6 | 2 |
[G-07] | Setting the constructor to payable | 13 | 6 |
[G-08] | Catching the array length prior to loop | 13 | 1 |
[G-09] | Use ++index instead of index++ to increment a loop counter | 5 | 1 |
[G-10] | Use Custom Errors rather than revert() / require() strings to save deployment gas | 68 | 14 |
[G-11] | Using private rather than public for constants, saves gas | 10 | 7 |
[G-12] | Functions guaranteed to revert when callled by normal users can be marked payable | 24 | 2 |
[G-13] | Save gas by just catching to msg.sender (even if you never use it) | 2 | 1 |
Total 13 issues
Number | Suggestion Details | Context |
---|---|---|
[S-01] | Missing zero-address check in `constructor | 9 |
Total 1 suggestion
return
keyword for gas efficient (for external functions) [ 3 gas saved]Context: ArtGobblers.sol#L541 ChainlinkV1RandProvider.sol#L69
Recommendation:
You must remove the return
keyword from the specified contexts.
Proof Of Concept: The optimizer was turned on and set to 10000 runs.
Gas Report: src/ArtGobblers.sol/requestRandomSeed [ 3 gas saved] src/utils/rand/ChainlinkV1RandProvider.sol/ requestRandomBytes [ 3 gas saved]
First gas test with return parameter (Current Code) Second gas test by removing return parameter (Recommended code)
Current Code: ╭─────────────────────────────────────────────┬─────────────────┬────────┬────────┬─────────┬─────────╮ │ src/ArtGobblers.sol:ArtGobblers contract ┆ ┆ ┆ ┆ ┆ │ ╞═════════════════════════════════════════════╪═════════════════╪════════╪════════╪═════════╪═════════╡ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ requestRandomSeed ┆ 419 ┆ 47286 ┆ 58718 ┆ 58718 ┆ 45 │ ╰─────────────────────────────────────────────┴─────────────────┴────────┴────────┴─────────┴─────────╯ Recommendation Code: ╭─────────────────────────────────────────────┬─────────────────┬────────┬────────┬─────────┬─────────╮ │ src/ArtGobblers.sol:ArtGobblers contract ┆ ┆ ┆ ┆ ┆ │ ╞═════════════════════════════════════════════╪═════════════════╪════════╪════════╪═════════╪═════════╡ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ requestRandomSeed ┆ 419 ┆ 47283 ┆ 58715 ┆ 58715 ┆ 45 │ ╰─────────────────────────────────────────────┴─────────────────┴────────┴────────┴─────────┴─────────╯
Current Code: ╭─────────────────────────────────────────────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ │ src/utils/rand/ChainlinkV1RandProvider.sol:ChainlinkV1RandProvider contract ┆ ┆ ┆ ┆ ┆ │ ╞═════════════════════════════════════════════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ requestRandomBytes ┆ 232 ┆ 41869 ┆ 46248 ┆ 46248 ┆ 42 │ ╰─────────────────────────────────────────────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ Recommendation Code: ╭─────────────────────────────────────────────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ │ src/utils/rand/ChainlinkV1RandProvider.sol:ChainlinkV1RandProvider contract ┆ ┆ ┆ ┆ ┆ │ ╞═════════════════════════════════════════════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ requestRandomBytes ┆ 232 ┆ 41866 ┆ 46245 ┆ 46245 ┆ 42 │ ╰─────────────────────────────────────────────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯
Shifting
cheaper than division
[6 gas saved]Context: ArtGobblers.sol#L462
Description:
A division by 2 can be calculated by shifting one to the right. While the DIV
opcode uses 5 gas
, the SHR
opcode only uses 3 gas
Furthermore, Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting.
Recommendation:
Instead of dividing by 2
use >> 1
.
Proof Of Concept: The optimizer was turned on and set to 10000 runs
Current: ArtGobblers.sol#L462
legendaryGobblerAuctionData.startPrice = uint120( cost <= LEGENDARY_GOBBLER_INITIAL_START_PRICE / 2 ? LEGENDARY_GOBBLER_INITIAL_START_PRICE : cost * 2 );
Recommendation:
legendaryGobblerAuctionData.startPrice = uint120( cost <= LEGENDARY_GOBBLER_INITIAL_START_PRICE >>1 ? LEGENDARY_GOBBLER_INITIAL_START_PRICE : cost * 2 );
Gas Report:
Current Code: ╭─────────────────────────────────────────────┬─────────────────┬────────┬────────┬─────────┬─────────╮ │ src/ArtGobblers.sol:ArtGobblers contract ┆ ┆ ┆ ┆ ┆ │ ╞═════════════════════════════════════════════╪═════════════════╪════════╪════════╪═════════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ mintLegendaryGobbler ┆ 911 ┆ 72774 ┆ 31201 ┆ 456707 ┆ 23 │ ╰─────────────────────────────────────────────┴─────────────────┴────────┴────────┴─────────┴─────────╯ Recommendation Code: ╭─────────────────────────────────────────────┬─────────────────┬────────┬────────┬─────────┬─────────╮ │ src/ArtGobblers.sol:ArtGobblers contract ┆ ┆ ┆ ┆ ┆ │ ╞═════════════════════════════════════════════╪═════════════════╪════════╪════════╪═════════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ mintLegendaryGobbler ┆ 911 ┆ 72768 ┆ 31193 ┆ 456700 ┆ 23 │ ╰─────────────────────────────────────────────┴─────────────────┴────────┴────────┴─────────┴─────────╯
uint256
instead of bool
in mappings is more gas efficient [146 gas per instance]Context: ArtGobblers.sol#L149 ArtGobblers.sol#L212
Description: OpenZeppelin uint256 and bool comparison: Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled. The values being non-zero value makes deployment a bit more expensive.
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past.
Recommendation: Use uint256(1) and uint256(2) instead of bool.
Proof Of Concept: The optimizer was turned on and set to 10000 runs
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0._setPolicyPermissions(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 79, true); c1._setPolicyPermissions(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 79, 2); } } contract Contract0 { mapping(address => mapping(uint256 => bool)) public modulePermissions; error boolcheck(); function _setPolicyPermissions(address addr_, uint256 id, bool log) external { modulePermissions[addr_][id] = log; if(modulePermissions[addr_][id] = false) revert boolcheck(); } } contract Contract1 { mapping(address => mapping(uint256 => uint256)) public modulePermissions; error boolcheck(); // Use uint256 instead of bool (1 = false , 2 = true) function _setPolicyPermissions(address addr_, uint256 id, uint256 log) external { modulePermissions[addr_][id] == log; if(modulePermissions[addr_][id] == 1) revert boolcheck(); } }
Gas Report:
╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 88335 ┆ 473 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ _setPolicyPermissions ┆ 2750 ┆ 2750 ┆ 2750 ┆ 2750 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ ╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 87135 ┆ 467 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ _setPolicyPermissions ┆ 2604 ┆ 2604 ┆ 2604 ┆ 2604 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯
assembly
to write address storage values [39 - 399 gas per instance]Context: ArtGobblers.sol#L320 ArtGobblers.sol#L321 ArtGobblers.sol#L314 ArtGobblers.sol#L315 ArtGobblers.sol#L316 ArtGobblers.sol#L317 Goo.sol#L83 Goo.sol#L84 Pages.sol#L179 Pages.sol#L181 Pages.sol#L183 GobblerReserve.sol#L24 ChainlinkV1RandProvider.sol#L55
Description:
Use assembly to write address storage values.
399 gas saves per instance for string
39 gas saves per instance for address
uint256 also saves no gas
Proof Of Concept: (for string) The optimizer was turned on and set to 10000 runs.
contract GasTestFoundry is DSTest { Contract1 c1; Contract2 c2; function setUp() public { c1 = new Contract1(); c2 = new Contract2(); } function testGas() public { c1.setRewardTokenAndAmount("Test"); c2.setRewardTokenAndAmount("Test"); } } contract Contract1 { string reward; function setRewardTokenAndAmount(string memory reward_) external { reward = reward_; } } contract Contract2 { string reward; function setRewardTokenAndAmount(string memory reward_) external { assembly { sstore(reward.slot, reward_) } } }
Gas Report:
╭──────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ │ src/test/test.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞══════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 167614 ┆ 869 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ setRewardTokenAndAmount ┆ 23012 ┆ 23012 ┆ 23012 ┆ 23012 ┆ 1 │ ╰──────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ ╭──────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ │ src/test/test.sol:Contract2 contract ┆ ┆ ┆ ┆ ┆ │ ╞══════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 75523 ┆ 409 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ setRewardTokenAndAmount ┆ 22613 ┆ 22613 ┆ 22613 ┆ 22613 ┆ 1 │ ╰──────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯
Proof Of Concept: (for address) The optimizer was turned on and set to 10000 runs.
contract GasTestFoundry is DSTest { Contract1 c1; Contract2 c2; function setUp() public { c1 = new Contract1(); c2 = new Contract2(); } function testGas() public { c1.setRewardTokenAndAmount(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4); c2.setRewardTokenAndAmount(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4); } } contract Contract1 { address reward; function setRewardTokenAndAmount(address reward_) external { reward = reward_; } } contract Contract2 { address reward; function setRewardTokenAndAmount(address reward_) external { assembly { sstore(reward.slot, reward_) } } }
Gas Report:
╭──────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ │ src/test/test.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞══════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 48499 ┆ 273 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ setRewardTokenAndAmount ┆ 22363 ┆ 22363 ┆ 22363 ┆ 22363 ┆ 1 │ ╰──────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ ╭──────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ │ src/test/test.sol:Contract2 contract ┆ ┆ ┆ ┆ ┆ │ ╞══════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 35287 ┆ 207 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ setRewardTokenAndAmount ┆ 22324 ┆ 22324 ┆ 22324 ┆ 22324 ┆ 1 │ ╰──────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯
methodID
[22 gas per instance]Context: All Contracts
Description:
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.
Recommendation:
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 ArtGobblers.sol
contract will be the most used; A lower method ID may be given.
Proof of Consept: https://coinsbench.com/advanced-gas-optimizations-tips-for-solidity-85c47f413dc5
ArtGobblers.sol function names can be named and sorted according to METHOD ID
Sighash | Function Signature ======================== acede5fd => claimGobbler(bytes32[]) c9bddac6 => mintFromGoo(uint256,bool) 10f255f5 => gobblerPrice() 201db8dd => mintLegendaryGobbler(uint256[]) d71d672f => legendaryGobblerPrice() e1da26c6 => requestRandomSeed() 13b45a47 => acceptRandomSeed(bytes32,uint256) beb31911 => upgradeRandProvider(RandProvider) 743ee994 => revealGobblers(uint256) c87b56dd => tokenURI(uint256) 6cfdbcae => gobble(uint256,address,uint256,bool) d075fbba => gooBalance(address) 0b38049f => addGoo(uint256) 9cc397fb => removeGoo(uint256) 2f689b85 => burnGooForPages(address,uint256) 33349cde => updateUserGooBalance(address,uint256,GooBalanceUpdateType) 65ca58b8 => mintReservedGobblers(uint256) fa522a15 => getGobblerEmissionMultiple(uint256) 11f93e78 => getUserEmissionMultiple(address) 23b872dd => transferFrom(address,address,uint256)
address(0)
[6 gas per instance]Context: ArtGobblers.sol#L887 PagesERC721.sol#L105
Proof Of Concept: The optimizer was turned on and set to 10000 runs.
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public view { c0.setOperator(address(this)); c1.setOperator(address(this)); } } contract Contract0 { function setOperator(address operator_) public pure { require(operator_) != address(0), "INVALID_RECIPIENT"); } } contract Contract1 { function setOperator(address operator_) public pure { assembly { if iszero(operator_) { mstore(0x00, "Callback_InvalidParams") revert(0x00, 0x20) } } } }
Gas Report:
╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 50899 ┆ 285 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ setOperator ┆ 258 ┆ 258 ┆ 258 ┆ 258 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 44893 ┆ 255 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ setOperator ┆ 252 ┆ 252 ┆ 252 ┆ 252 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯
constructor
to payable
[13 gas per instance]Context: ArtGobblers.sol#L287 Goo.sol#L82 Pages.sol#L156 GobblerReserve.sol#L23 ChainlinkV1RandProvider.sol#L48 PagesERC721.sol#L36
Description:
You can cut out 10 opcodes in the creation-time EVM bytecode if you declare a constructor payable. Making the constructor payable eliminates the need for an initial check of msg.value == 0
and saves 13 gas
on deployment with no security risks.
Recommendation:
Set the constructor to payable
.
Proof Of Concept: https://forum.openzeppelin.com/t/a-collection-of-gas-optimisation-tricks/19966/5?u=pcaversaccio
The optimizer was turned on and set to 10000 runs.
contract GasTestFoundry is DSTest { Contract1 c1; Contract2 c2; function setUp() public { c1 = new Contract1(); c2 = new Contract2(); } function testGas() public { c1.x(); c2.x(); } } contract Contract1 { uint256 public dummy; constructor() payable { dummy = 1; } function x() public { } } contract Contract2 { uint256 public dummy; constructor() { dummy = 1; } function x() public { } }
Gas Report:
╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 49563 ┆ 159 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ │ src/test/GasTest.t.sol:Contract2 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 49587 ┆ 172 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
Context: GobblerReserve.sol#L37
Description: One can save gas by caching the array length (in stack) and using that set variable in the loop. Replace state variable reads and writes within loops with local variable reads and writes. This is done by assigning state variable values to new local variables, reading and/or writing the local variables in a loop, then after the loop assigning any changed local variables to their equivalent state variables.
Recommendation:
Simply do something like so before the for loop: uint length = variable.length
Then add length in place of variable.length
in the for loop.
Proof Of Concept: The optimizer was turned on and set to 10000 runs.
contract GasTest is DSTest { Contract1 c1; Contract2 c2; function setUp() public { c1 = new Contract1(); c2 = new Contract2(); } function testGas() public { address[] memory Inputs = new address[](6); Inputs[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; Inputs[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; Inputs[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; Inputs[3] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; Inputs[4] = 0x617F2E2fD72FD9D5503197092aC168c91465E7f2; Inputs[5] = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372; c1.executeProposal(Inputs); c2.executeProposal(Inputs); } } contract Contract1 { function executeProposal(address[] memory test) public pure { uint256 a = test.length; for (uint256 step; step < a; ) { unchecked { ++step; } } } } contract Contract2 { function executeProposal(address[] memory test) public pure { for (uint256 step; step < test.length; ) { unchecked { ++step; } } } }
Gas Report:
╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 92941 ┆ 496 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ executeProposal ┆ 1653 ┆ 1653 ┆ 1653 ┆ 1653 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ ╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ │ src/test/GasTest.t.sol:Contract2 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 92541 ┆ 494 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ executeProposal ┆ 1666 ┆ 1666 ┆ 1666 ┆ 1666 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯
++index
instead of index++
to increment a loop counter [5 gas per instance]Context: Pages.sol#L251
Description: Due to reduced stack operations, using ++index saves 5 gas per iteration.
Recommendation: Use ++index to increment a loop counter.
Proof Of Concept: The optimizer was turned on and set to 10000 runs.
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.foo(); c1.foo(); } } contract Contract0 { function foo() external { uint256 versionNFTDropCollection; ++versionNFTDropCollection; } } contract Contract1 { function foo() external { uint256 versionNFTDropCollection; versionNFTDropCollection++; } }
Gas Report:
╭──────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ │ src/test/test.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ ╞══════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 42893 ┆ 245 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ foo ┆ 193 ┆ 193 ┆ 193 ┆ 193 ┆ 1 │ ╰──────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ ╭──────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ │ src/test/test.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞══════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 43293 ┆ 247 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ foo ┆ 198 ┆ 198 ┆ 198 ┆ 198 ┆ 1 │ ╰──────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯
Custom Errors
rather than revert() / require() strings to save deployment gas [68 gas per instance]Context: ArtGobblers.sol#L437 ArtGobblers.sol#L885 ArtGobblers.sol#L887 ArtGobblers.sol#L889 PagesERC721.sol#L85 PagesERC721.sol#L103 PagesERC721.sol#L105 PagesERC721.sol#L107 PagesERC721.sol#L135 PagesERC721.sol#L151 GobblersERC721.sol#L95 GobblersERC721.sol#L121 GobblersERC1155B.sol#L149 GobblersERC1155B.sol#L185
Description: Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they’re hitby avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas. https://blog.soliditylang.org/2021/04/21/custom-errors/
Proof Of Concept: The optimizer was turned on and set to 10000 runs.
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.foo(); c1.foo(); } } contract Contract0 { uint256 version; error Error_Message(); function foo() external { if (version != 0) { revert Error_Message(); } } } contract Contract1 { uint256 version; function foo() external { require(version == 0, "error"); } }
Gas Report:
╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 33287 ┆ 196 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ foo ┆ 2242 ┆ 2242 ┆ 2242 ┆ 2242 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ ╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 41693 ┆ 239 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ foo ┆ 2310 ┆ 2310 ┆ 2310 ┆ 2310 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯
private
rather than public
for constants, saves gas [ 10 gas per instance]Context: ArtGobblers.sol#L112 ArtGobblers.sol#L115 ArtGobblers.sol#L118 ArtGobblers.sol#L122 ArtGobblers.sol#L177 ArtGobblers.sol#L180 ArtGobblers.sol#L184
Proof Of Concept The optimizer was turned on and set to 10000 runs.
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.gas(); c1.gas(); } } contract Contract0 { uint256 public constant BASE_GAS = 36000; function gas() external returns(uint256 gasPrice) { gasPrice = gasleft() + BASE_GAS; } } contract Contract1 { uint256 private constant BASE_GAS = 36000; function gas() external returns(uint256 gasPrice) { gasPrice = gasleft() + BASE_GAS; } }
Gas Report:
╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 43693 ┆ 249 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ gas ┆ 263 ┆ 263 ┆ 263 ┆ 263 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 40893 ┆ 235 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ gas ┆ 253 ┆ 253 ┆ 253 ┆ 253 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯
payable
[24 gas per instance]Context: ArtGobblers.sol#L560 GobblerReserve.sol#L34
Description: If a function modifier or require such as onlyOwner-admin 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.
Recommendation:
Functions guaranteed to revert when called by normal users can be marked payable (for only onlyowner or admin
functions)
Proof Of Concept: The optimizer was turned on and set to 10000 runs.
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.foo(); c1.foo(); } } contract Contract0 { uint256 versionNFTDropCollection; function foo() external { versionNFTDropCollection++; } } contract Contract1 { uint256 versionNFTDropCollection; function foo() external payable { versionNFTDropCollection++; } }
Gas Report:
╭───────────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 44293 ┆ 252 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ foo ┆ 22308 ┆ 22308 ┆ 22308 ┆ 22308 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ ╭───────────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞═══════════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 41893 ┆ 240 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ foo ┆ 22284 ┆ 22284 ┆ 22284 ┆ 22284 ┆ 1 │ ╰───────────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯
msg.sender
(even if you never use it) [ 2 gas per function run]Context: ArtGobblers.sol#L339
Proof Of Concept The optimizer was turned on and set to 1000000 runs.
ArtGobblers.sol#L339 Current Code : function claimGobbler(bytes32[] calldata proof) external returns (uint256 gobblerId) { // If minting has not yet begun, revert. if (mintStart > block.timestamp) revert MintStartPending(); // If the user has already claimed, revert. if (hasClaimedMintlistGobbler[msg.sender]) revert AlreadyClaimed(); // If the user's proof is invalid, revert. if (!MerkleProofLib.verify(proof, merkleRoot, keccak256(abi.encodePacked(msg.sender)))) revert InvalidProof(); hasClaimedMintlistGobbler[msg.sender] = true; unchecked { // Overflow should be impossible due to supply cap of 10,000. emit GobblerClaimed(msg.sender, gobblerId = ++currentNonLegendaryId); } _mint(msg.sender, gobblerId); } Recommendation Code: function claimGobbler(bytes32[] calldata proof) external returns (uint256 gobblerId) { // If minting has not yet begun, revert. if (mintStart > block.timestamp) revert MintStartPending(); address _msgSender = msg.sender; // This is fantastic optimization // If the user has already claimed, revert. if (hasClaimedMintlistGobbler[msg.sender]) revert AlreadyClaimed(); // If the user's proof is invalid, revert. if (!MerkleProofLib.verify(proof, merkleRoot, keccak256(abi.encodePacked(msg.sender)))) revert InvalidProof(); hasClaimedMintlistGobbler[msg.sender] = true; unchecked { // Overflow should be impossible due to supply cap of 10,000. emit GobblerClaimed(msg.sender, gobblerId = ++currentNonLegendaryId); } _mint(msg.sender, gobblerId); }
Gas Report:
Current Code : ╭─────────────────────────────────────────────┬─────────────────┬────────┬────────┬─────────┬─────────╮ │ src/ArtGobblers.sol:ArtGobblers contract ┆ ┆ ┆ ┆ ┆ │ ╞═════════════════════════════════════════════╪═════════════════╪════════╪════════╪═════════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 4034227 ┆ 22345 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ claimGobbler ┆ 574 ┆ 47364 ┆ 48139 ┆ 93282 ┆ 6 │ ╰─────────────────────────────────────────────┴─────────────────┴────────┴────────┴─────────┴─────────╯ Recommendation Code: ╭─────────────────────────────────────────────┬─────────────────┬────────┬────────┬─────────┬─────────╮ │ src/ArtGobblers.sol:ArtGobblers contract ┆ ┆ ┆ ┆ ┆ │ ╞═════════════════════════════════════════════╪═════════════════╪════════╪════════╪═════════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 4033827 ┆ 22343 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ claimGobbler ┆ 574 ┆ 47362 ┆ 48137 ┆ 93278 ┆ 6 │ ╰─────────────────────────────────────────────┴─────────────────┴────────┴────────┴─────────┴─────────╯
zero-address
check in constructor
Context: ArtGobblers.sol#L294 ArtGobblers.sol#L295 ArtGobblers.sol#L292 ArtGobblers.sol#L293 ArtGobblers.sol#L296 Goo.sol#L82 Pages.sol#L160 Pages.sol#L161 Pages.sol#L162
Description: Missing checks for zero-addresses may lead to infunctional protocol, if the variable addresses are updated incorrectly. It also wast gas as it requires the redeployment of the contract.
#0 - GalloDaSballo
2022-10-06T01:20:15Z
Will give you 400 gas saved because it's better than the average report, however I think benchmarks should be offered on the real code, not via dummy examples.
The dummy examples obfuscate the optimization from the real contest, meaning some of these will not actually work in the in-production code
#1 - GalloDaSballo
2022-10-06T01:20:27Z
A good report, but you should customize per contest if you want to win