Skip to content

Latest commit

 

History

History
128 lines (94 loc) · 4.14 KB

__141345__-G.md

File metadata and controls

128 lines (94 loc) · 4.14 KB

Variable re-arrangement by storage packing

Reference: Layout of State Variables in Storage.

  1. In struct AppStorage, bool zkPorterIsAvailable can be placed before address 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;
}
  1. In struct DiamondStorage, bool isFrozen can be placed before mapping(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;
    }
  1. In struct FacetCut, bool isFreezable can be placed before address 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;
    }

Update value order can be adjusted to simplify the code and save gas

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;
    }

Splitting require() statements that use &&

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

Add unchecked {} for subtractions where the operands cannot underflow

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;