You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
1. Avoid compound assignment operator in state variables
Using compound assignment operators for state variables (like State += X or State -= X ...) it's more expensive than using operator assignment (like State = State + X or State = State - X ...).
Shifting one to the right will calculate a division by two.
he SHR opcode only requires 3 gas, compared to the DIV opcode's consumption of 5. Additionally, shifting is used to get around Solidity's division operation's division-by-0 prohibition.
An empty block or an empty method generally indicates a lack of logic that it’s unnecessary and should be eliminated, call a method that literally does nothing only consumes gas unnecessarily, if it is a virtual method which is expects it to be filled by the class that implements it, it is better to use abstract contracts with just the definition.
Because each write operation requires an additional SLOAD to read the slot's contents, replace the bits occupied by the boolean, and then write back, booleans are more expensive than uint256 or any other type that uses a complete word. This cannot be turned off because it is the compiler's defense against pointer aliasing and contract upgrades.
6. constants expressions are expressions, not constants
Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.
If the variable was immutable instead: the calculation would only be done once at deploy time (in the constructor), and then the result would be saved and read directly at runtime rather than being recalculated.
Consequences: each usage of a "constant" costs ~100gas more on each access (it is still a little better than storing the result in storage, but not much..). since these are not real constants, they can't be referenced from a real constant environment (e.g. from assembly, or from another library).
9. There's no need to set default values for variables
If a variable is not set/initialized, the default value is assumed (0, false, 0x0 ... depending on the data type). You are simply wasting gas if you directly initialize it with its default value.
It's possible to optimize the method MetadataRenderer.onMinted as follows:
Recommended changes:
function onMinted(uint256 _tokenId) external returns (bool) {
...
unchecked {
// For each property:
- for (uint256 i = 0; i < numProperties; ++i) {+ for (uint256 i = 0; i < numProperties;) {
// Get the number of items to choose from
uint256 numItems = properties[i].items.length;
// Use the token's seed to select an item
+ ++i;+ tokenAttributes[i] = uint16(seed % numItems);- tokenAttributes[i + 1] = uint16(seed % numItems);
// Adjust the randomness
seed >>= 16;
}
}
return true;
}
Having a method that always returns the same value is not correct in terms of consumption, if you want to modify a value, and the method will perform a revert in case of failure, it is not necessary to return a true, since it will never be false. It is less expensive not to return anything, and it also eliminates the need to double-check the returned value by the caller.
It's possible to optimize some methods in MetadataRenderer contract changing the argument type as follows:
Recommended changes:
- function initialize(address _governor, uint256 _delay) external initializer {+ function initialize(address _governor, uint128 _delay) external initializer {
// Ensure the caller is the contract manager
if (msg.sender != address(manager)) revert ONLY_MANAGER();
// Ensure a governor address was provided
if (_governor == address(0)) revert ADDRESS_ZERO();
// Grant ownership to the governor
__Ownable_init(_governor);
// Store the time delay
- settings.delay = SafeCast.toUint128(_delay);+ settings.delay = _delay;
// Set the default grace period
settings.gracePeriod = 2 weeks;
emit DelayUpdated(0, _delay);
}
...
- function updateDelay(uint256 _newDelay) external {+ function updateDelay(uint128 _newDelay) external {
// Ensure the caller is the treasury itself
if (msg.sender != address(this)) revert ONLY_TREASURY();
emit DelayUpdated(settings.delay, _newDelay);
// Update the delay
- settings.delay = SafeCast.toUint128(_newDelay);+ settings.delay = _newDelay;
}
...
- function updateGracePeriod(uint256 _newGracePeriod) external {+ function updateGracePeriod(uint128 _newGracePeriod) external {
// Ensure the caller is the treasury itself
if (msg.sender != address(this)) revert ONLY_TREASURY();
emit GracePeriodUpdated(settings.gracePeriod, _newGracePeriod);
// Update the grace period
- settings.gracePeriod = SafeCast.toUint128(_newGracePeriod);+ settings.gracePeriod = _newGracePeriod;
}
It is not necessary to send more information than necessary to the initialize method. It's possible to optimize the method Token.initialize as follows:
Recommended changes:
function initialize(
IManager.FounderParams[] calldata _founders,
bytes calldata _initStrings,
address _metadataRenderer,
address _auction
) external initializer {
// Ensure the caller is the contract manager
if (msg.sender != address(manager)) revert ONLY_MANAGER();
// Initialize the reentrancy guard
__ReentrancyGuard_init();
// Store the founders and compute their allocations
_addFounders(_founders);
// Decode the token name and symbol
- (string memory _name, string memory _symbol, , , ) = abi.decode(_initStrings, (string, string, string, string, string));+ (string memory _name, string memory _symbol) = abi.decode(_initStrings, (string, string));
// Initialize the ERC-721 token
__ERC721_init(_name, _symbol);
// Store the metadata renderer and auction house
settings.metadataRenderer = IBaseMetadata(_metadataRenderer);
settings.auction = _auction;
}
There are mathematical operations that can be eliminated and not affect the logic of the contract.
Recommended changes:
function _addFounders(IManager.FounderParams[] calldata _founders) internal {
...
unchecked {
// For each founder:
for (uint256 i; i < numFounders; ++i) {
...
// For each token to vest:
for (uint256 j; j < founderPct; ++j) {
...
// Update the base token id
- (baseTokenId += schedule) % 100;+ baseTokenId += schedule;
}
}
...
}
}
The Token contract uses a map to store the founders, but the key is the index, it is better to use an array instead of a mapping because there is no need to store the length and it will also be cheaper to return the array without iterating through all the entries.
functiongetFounders()externalviewreturns(Founder[]memory){// Cache the number of foundersuint256numFounders=settings.numFounders;// Get a temporary array to hold all foundersFounder[]memoryfounders=newFounder[](numFounders);// Cannot realistically overflowunchecked{// Add each founder to the arrayfor(uint256i;i<numFounders;++i)founders[i]=founder[i];}returnfounders;}
Gas
1. Avoid compound assignment operator in state variables
Using compound assignment operators for state variables (like
State += X
orState -= X
...) it's more expensive than using operator assignment (likeState = State + X
orState = State - X
...).Proof of concept (without optimizations):
Gas saving executing: 13 per entry
Affected source code:
Total gas saved: 13 * 3 = 39
2. Shift right instead of dividing by 2
Shifting one to the right will calculate a division by two.
he
SHR
opcode only requires 3 gas, compared to theDIV
opcode's consumption of 5. Additionally, shifting is used to get around Solidity's division operation's division-by-0 prohibition.Proof of concept (without optimizations):
Gas saving executing: 172 per entry
Affected source code:
Total gas saved: 172 * 1 = 172
3. Remove empty blocks
An empty block or an empty method generally indicates a lack of logic that it’s unnecessary and should be eliminated, call a method that literally does nothing only consumes gas unnecessarily, if it is a
virtual
method which is expects it to be filled by the class that implements it, it is better to useabstract
contracts with just the definition.Sample code:
Reference:
Affected source code:
4. Use
calldata
instead ofmemory
Some methods are declared as
external
but the arguments are defined asmemory
instead of ascalldata
.By marking the function as
external
it is possible to usecalldata
in the arguments shown below and save significant gas.Recommended change:
Affected source code:
5. Change
bool
touint256
can save gasBecause each write operation requires an additional
SLOAD
to read the slot's contents, replace the bits occupied by the boolean, and then write back,booleans
are more expensive thanuint256
or any other type that uses a complete word. This cannot be turned off because it is the compiler's defense against pointer aliasing and contract upgrades.Reference:
Also, this is applicable to integer types different from
uint256
orint256
.Affected source code for
booleans
:6.
constants
expressions are expressions, notconstants
Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.
If the variable was immutable instead: the calculation would only be done once at deploy time (in the constructor), and then the result would be saved and read directly at runtime rather than being recalculated.
Reference:
Affected source code:
7. Optimize
ERC721Votes.delegateBySig
logicIt's possible to optimize the method
ERC721Votes.delegateBySig
as follows:Recommended changes:
Affected source code:
8. Optimize
MetadataRenderer.initialize
logicIt's possible to optimize the method
MetadataRenderer.initialize
as follows:Recommended changes:
Affected source code:
9. There's no need to set default values for variables
If a variable is not set/initialized, the default value is assumed (0,
false
, 0x0 ... depending on the data type). You are simply wasting gas if you directly initialize it with its default value.Proof of concept (without optimizations):
Gas saving executing: 8 per entry
Affected source code:
Total gas saved: 8 * 5 = 40
10. Optimize
MetadataRenderer.onMinted
logicIt's possible to optimize the method
MetadataRenderer.onMinted
as follows:Recommended changes:
Affected source code:
11. Avoid unused returns
Having a method that always returns the same value is not correct in terms of consumption, if you want to modify a value, and the method will perform a
revert
in case of failure, it is not necessary to return atrue
, since it will never befalse
. It is less expensive not to return anything, and it also eliminates the need to double-check the returned value by the caller.Affected source code:
12. Optimize
MetadataRenderer.getAttributes
It's possible to optimize the method
MetadataRenderer.getAttributes
as follows:Recommended changes:
Affected source code:
13. Change arguments type in
MetadataRenderer
It's possible to optimize some methods in
MetadataRenderer
contract changing the argument type as follows:Recommended changes:
Affected source code:
14. Optimize
Token.initialize
It is not necessary to send more information than necessary to the
initialize
method. It's possible to optimize the methodToken.initialize
as follows:Recommended changes:
Affected source code:
15. Remove unused math
There are mathematical operations that can be eliminated and not affect the logic of the contract.
Recommended changes:
Affected source code:
16. Use array instead of mapping
The
Token
contract uses a map to store the founders, but the key is the index, it is better to use an array instead of a mapping because there is no need to store the length and it will also be cheaper to return the array without iterating through all the entries.Affected source code:
17. Change arguments type in
Auction
It's possible to optimize some methods in
Auction
contract changing the argument type as follows:Recommended changes:
Affected source code:
18. Change arguments type in
Governor
It's possible to optimize some methods in
Governor
contract changing the argument type as follows:Recommended changes:
Affected source code:
The text was updated successfully, but these errors were encountered: