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
contractorConfirmed and contractorDelegated can be placed next to each other, as a result, 1 slot storage can be saved. According to the currently layout, they both occupy 1 slot, but after re-arrangement, they can be packed into 1 slot.
And in Disputes.sol, the require() statement is in modifier resolvable(uint256 _disputeID) which might be called frequently, a gas saving can benefit.
USE CUSTOM ERRORS INSTEAD OF REVERT STRINGS
Custom errors from Solidity 0.8.4 are more gas efficient than revert strings (lower deployment cost and runtime cost when the revert condition is met) reference
Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.
Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).
require() statements are heavily used across the rigor contracts:
contracts/Project.sol
contracts/Community.sol
These 2 sol files use around 50 require, and there are lots in other files.
optimization on the condition checks is worth consideration.
Since inside these functions, the function is the same as the parent contract function, there is no need to rewrite it.
contracts/HomeFi.sol
/// @dev This is same as ERC2771ContextUpgradeable._msgSender()
function _msgSender()
internal
view
override(ContextUpgradeable, ERC2771ContextUpgradeable)
returns (address sender)
{
// We want to use the _msgSender() implementation of ERC2771ContextUpgradeable
return super._msgSender();
}
/// @dev This is same as ERC2771ContextUpgradeable._msgData()
function _msgData()
internal
view
override(ContextUpgradeable, ERC2771ContextUpgradeable)
returns (bytes calldata)
{
// We want to use the _msgData() implementation of ERC2771ContextUpgradeable
return super._msgData();
}
FOR-LOOPS LENGTH COULD BE CACHED
The for loop length can be cached to memory before the loop, even for memory variables, the comparison can been seen in the reference link at the end (the length() function for test).
contracts/Community.sol
624: for (uint256 i =0; i < _communities[_communityID].memberCount; i++) {
contracts/Project.sol
603: for (; i < _changeOrderedTask.length; i++) {
650: for (++j; j <= taskCount; j++) {
taskCount is a state variable, which uses sload opcode consumes 100 gas (warm access). While read from memory uses mload only use 3 gas.
Suggestion:
uint length = names.length;
The demo of the loop gas comparison can be seen here.
FOR-LOOPS unchecked{ ++i } COSTS LESS GAS COMPARED TO i++ OR i += 1
Use ++i instead of i++ to increment the value of an uint variable, and for guaranteed non-overflow/underflow, it can be unchecked.
The demo of the loop gas comparison can be seen here.
contracts/Community.sol
624: for (uint256 i =0; i < _communities[_communityID].memberCount; i++) {
contracts/HomeFiProxy.sol
87: for (uint256 i =0; i < _length; i++) {
136: for (uint256 i =0; i < _length; i++) {
contracts/Project.sol
248: for (uint256 i =0; i < _length; i++) {
311: for (uint256 i =0; i < _length; i++) {
322: for (uint256 i =0; i < _length; i++) {
368: for (uint256 _taskID =1; _taskID <= _length; _taskID++) {
603: for (; i < _changeOrderedTask.length; i++) {
650: for (++j; j <= taskCount; j++) {
710: for (uint256 _taskID =1; _taskID <= _length; _taskID++) {
contracts/libraries/Tasks.sol
181: for (uint256 i =0; i < _length; i++) _alerts[i] = _self.alerts[i];
Suggestion:
For readability, uncheck the whole for loop is the same.
unchecked{
for (uint256 i; i < length; ++i) {
}
}
FOR-LOOPS NO NEED TO EXPLICITLY INITIALIZE VARIABLES WITH DEFAULT VALUES
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
contracts/Community.sol
624: for (uint256 i =0; i < _communities[_communityID].memberCount; i++) {
contracts/HomeFiProxy.sol
87: for (uint256 i =0; i < _length; i++) {
136: for (uint256 i =0; i < _length; i++) {
contracts/Project.sol
248: for (uint256 i =0; i < _length; i++) {
311: for (uint256 i =0; i < _length; i++) {
322: for (uint256 i =0; i < _length; i++) {
contracts/libraries/Tasks.sol
181: for (uint256 i =0; i < _length; i++) _alerts[i] = _self.alerts[i];
The demo of the loop gas comparison can be seen here.
FUNCTIONS GUARANTEED TO REVERT WHEN CALLED BY NORMAL USERS CAN BE MARKED PAYABLE
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.
NFT is only created when createProject(), and not used elsewhere. If it does not have special usage, for transfer, claim ownership of a project, etc, it can be removed for gas saving and reduce code complexity.
contracts/HomeFi.sol
mintNFT(_sender, string(_hash));
Multiple nonZero() modifier can be combined
There are many nonZero() modifiers, the only difference is the error message.
They can be combined and provide the error message as parameter. In this way, some duplicate compilation cost could be saved.
Suggestion:
Use a library.
Use a base contract, and inherit from it.
contracts/Community.sol
modifier nonZero(address_address) {
// Revert if _address zero address (0x00) (invalid)require(_address !=address(0), "Community::0 address");
_;
}
contracts/Disputes.sol
modifier nonZero(address_address) {
// Revert if _address zero address (0x00)require(_address !=address(0), "Disputes::0 address");
_;
}
Variable re-arrangement for storage packing
contractorConfirmed
andcontractorDelegated
can be placed next to each other, as a result, 1 slot storage can be saved. According to the currently layout, they both occupy 1 slot, but after re-arrangement, they can be packed into 1 slot.contracts/Project.sol
Reference: Layout of State Variables in Storage.
SPLITTING REQUIRE() STATEMENTS THAT USE &&
See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper.
The demo of the gas comparison can be seen here.
And in Disputes.sol, the
require()
statement is in modifierresolvable(uint256 _disputeID)
which might be called frequently, a gas saving can benefit.USE CUSTOM ERRORS INSTEAD OF REVERT STRINGS
Custom errors from Solidity 0.8.4 are more gas efficient than revert strings (lower deployment cost and runtime cost when the revert condition is met) reference
require()
statements are heavily used across the rigor contracts:These 2 sol files use around 50
require
, and there are lots in other files.optimization on the condition checks is worth consideration.
The demo of the gas comparison can be seen here.
Duplicate function of parent contract
Since inside these functions, the function is the same as the parent contract function, there is no need to rewrite it.
contracts/HomeFi.sol
/// @dev This is same as ERC2771ContextUpgradeable._msgSender()
function _msgSender()
internal
view
override(ContextUpgradeable, ERC2771ContextUpgradeable)
returns (address sender)
{
// We want to use the _msgSender() implementation of ERC2771ContextUpgradeable
return super._msgSender();
}
FOR-LOOPS LENGTH COULD BE CACHED
The for loop length can be cached to memory before the loop, even for memory variables, the comparison can been seen in the reference link at the end (the
length()
function for test).taskCount
is a state variable, which usessload
opcode consumes 100 gas (warm access). While read from memory usesmload
only use 3 gas.Suggestion:
The demo of the loop gas comparison can be seen here.
FOR-LOOPS
unchecked{ ++i }
COSTS LESS GAS COMPARED TOi++
ORi += 1
Use
++i
instead ofi++
to increment the value of an uint variable, and for guaranteed non-overflow/underflow, it can be unchecked.The demo of the loop gas comparison can be seen here.
Suggestion:
For readability, uncheck the whole for loop is the same.
FOR-LOOPS NO NEED TO EXPLICITLY INITIALIZE VARIABLES WITH DEFAULT VALUES
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
The demo of the loop gas comparison can be seen here.
X = X + Y
/X = X - Y
IS CHEAPER THANX += Y
/X -= Y
Suggestion:
Consider using
X = X + Y
/X = X - Y
.The demo of the gas comparison can be seen here.
USE CALLDATA INSTEAD OF MEMORY
When arguments are read-only on external functions, the data location can be calldata.
The demo of the gas comparison can be seen here.
FUNCTIONS GUARANTEED TO REVERT WHEN CALLED BY NORMAL USERS CAN BE MARKED PAYABLE
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
which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
contracts/Community.sol
contracts/DebtToken.sol
contracts/Disputes.sol
contracts/HomeFi.sol
Modifier ONLY CALLED ONCE CAN BE INLINED
Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.
onlyAdmin()
andonlyProject()
is only called once.contracts/Disputes.sol
approvedHashes
check can be performed firstcontracts/Project.sol
If
approvedHashes[_address][_hash]
is evaluated to be true, there is no need forrecoverKey
and signature check, the extra operations can be saved.Suggestion:
NFT usage
NFT is only created when
createProject()
, and not used elsewhere. If it does not have special usage, for transfer, claim ownership of a project, etc, it can be removed for gas saving and reduce code complexity.contracts/HomeFi.sol
Multiple
nonZero()
modifier can be combinedThere are many
nonZero()
modifiers, the only difference is the error message.They can be combined and provide the error message as parameter. In this way, some duplicate compilation cost could be saved.
Suggestion:
contracts/Community.sol
contracts/Disputes.sol
contracts/HomeFi.sol
contracts/HomeFiProxy.sol
contracts/ProjectFactory.sol
The text was updated successfully, but these errors were encountered: