Reference: Layout of State Variables in Storage.
- In
struct AppStorage
,bool zkPorterIsAvailable
can be placed beforeaddress governor
, 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.
// ethereum/contracts/zksync/Storage.sol
struct AppStorage {
address governor;
// ...
bool zkPorterIsAvailable;
}
- In
struct DiamondStorage
,bool isFrozen
can be placed beforemapping(bytes4 => SelectorToFacet) selectorToFacet
, 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.
// ethereum/contracts/zksync/libraries/Diamond.sol
struct DiamondStorage {
mapping(bytes4 => SelectorToFacet) selectorToFacet;
mapping(address => FacetToSelectors) facetToSelectors;
address[] facets;
bool isFrozen;
}
- In
struct FacetCut
,bool isFreezable
can be placed beforeaddress facet
, 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.
// ethereum/contracts/zksync/libraries/Diamond.sol
struct FacetCut {
address facet;
Action action;
bool isFreezable;
bytes4[] selectors;
}
For example, to update the num
variable with newVal
, the current way is as following:
uint oldVal = num;
num = newVal;
emit update(oldVal, newVal);
If the execution order is adjusted, some operations can be saved (memory space allocation, variable assignment), reducing both the deployment and run time gas cost.
emit update(num, newVal);
num = newVal;
The operation saved: 1 sload, 1 memory allocation, 1 mstore.
The demo of the gas comparison can be seen here.
Instances number of this issue:
// ethereum/contracts/zksync/facets/Governance.sol
function setVerifierParams(VerifierParams calldata _newVerifierParams) external onlyGovernor {
VerifierParams memory oldVerifierParams = s.verifierParams;
s.verifierParams = _newVerifierParams;
emit NewVerifierParams(oldVerifierParams, _newVerifierParams);
}
The above can be changed to:
function setVerifierParams(VerifierParams calldata _newVerifierParams) external onlyGovernor {
emit NewVerifierParams(s.verifierParams, _newVerifierParams);
s.verifierParams = _newVerifierParams;
}
require()
statements with multiple conditions can be split.
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.
Instances number of this issue: 2
// ethereum/contracts/common/AllowList.sol
96-101
require(
callersLength == _targets.length &&
callersLength == _functionSigs.length &&
callersLength == _enables.length,
"yw"
);
// ethereum/contracts/common/L2ContractHelper.sol
65 require(version == 1 && _bytecodeHash[1] == bytes1(0), "zf"); // Incorrectly formatted bytecodeHash
Some will not underflow because of a previous code or require()
or if-statement
require(a <= b);
x = b - a
->
require(a <= b);
unchecked { x = b - a }
Instances number of this issue: 1
// ethereum/contracts/bridge/L1ERC20Bridge.sol
145 return balanceAfter - balanceBefore;