zkSync v2 contest - HardlyCodeMan's results

Rely on math, not validators.

General Information

Platform: Code4rena

Start Date: 28/10/2022

Pot Size: $165,500 USDC

Total HM: 2

Participants: 24

Period: 12 days

Judge: GalloDaSballo

Total Solo HM: 1

Id: 177

League: ETH

zkSync

Findings Distribution

Researcher Performance

Rank: 23/24

Findings: 1

Award: $229.76

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 0x1f8b

Also found by: Aymen0909, HardlyCodeMan, IllIllI, ReyAdmirado, Rolezn, TomJ, c3phas, gogo, mcwildy

Labels

bug
G (Gas Optimization)
grade-b
G-02

Awards

229.7602 USDC - $229.76

External Links

Gas Report 2022-10-zksync

Three gas optimisations in two areas were found other than the C4udit public findings.

Use require over assert

Assert should not be used by other mean than testing purposes. (because when the assertion fails, gas is NOT refunded contrary to require)

DiamondCut.sol

    assert(SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE > 0);

Storage Optimisation

Changing order of storage slots 20 000 gas saved at deployment per slot saved. Ethereum storage is composed of slots of 32 bytes, the problem is that writing is expensive. (Up to 20000gas using “cold” writing) https://docs.soliditylang.org/en/v0.8.13/internals/layout_in_storage.html

Affected contracts

  • ./ethereum/contracts/zksync/Storage.sol
  • ./ethereum/contracts/zksync/interfaces/IExecutor.sol

Storage.sol

Potential for contract storage optimisation in Storage.sol reducing storage slots required by 1. Variable zkPorterIsAvailable is of type boolean taking up 1 byte of storage and 1 slot of storage at the end of the struct as the previous entity of the struct is bytes32 which fill an entire storage slot. Variable zkPorterIsAvailable can be put together with an address entity of size bytes 20 to share a single slot utilising 21 of 32 bytes rather than currently having a slot occupying 1 of 32 bytes.

Original

(@dev comments removed to save space)

struct AppStorage {
    DiamondCutStorage diamondCutStorage;
    address governor;
    address pendingGovernor;
    mapping(address => bool) validators;
    Verifier verifier;
    uint256 totalBlocksExecuted;
    uint256 totalBlocksVerified;
    uint256 totalBlocksCommitted;
    mapping(uint256 => bytes32) storedBlockHashes;
    mapping(uint256 => bytes32) l2LogsRootHashes;
    PriorityQueue.Queue priorityQueue;
    IAllowList allowList;
    VerifierParams verifierParams;
    bytes32 l2BootloaderBytecodeHash;
    bytes32 l2DefaultAccountBytecodeHash;
    bool zkPorterIsAvailable;
}
sizeslottype
320struct
201address
202address
323mapping
204address
325uint256
326uint256
327uint256
328mapping
329mapping
3210struct
3211address
3212struct
3213bytes32
3214bytes32
115bool
Modified

(@dev comments removed to save space)

struct AppStorage {
    DiamondCutStorage diamondCutStorage;
    bool zkPorterIsAvailable;
    address governor;
    address pendingGovernor;
    mapping(address => bool) validators;
    Verifier verifier;
    uint256 totalBlocksExecuted;
    uint256 totalBlocksVerified;
    uint256 totalBlocksCommitted;
    mapping(uint256 => bytes32) storedBlockHashes;
    mapping(uint256 => bytes32) l2LogsRootHashes;
    PriorityQueue.Queue priorityQueue;
    IAllowList allowList;
    VerifierParams verifierParams;
    bytes32 l2BootloaderBytecodeHash;
    bytes32 l2DefaultAccountBytecodeHash;
}
sizeslottype
320struct
11bool
201address
202address
323mapping
204address
325uint256
326uint256
327uint256
328mapping
329mapping
3210struct
3211address
3212struct
3213bytes32
3214bytes32

IExecutor.sol

There may be potential for contract storage optimisation in IExecutor.sol reducing storage slots required by 1. As the struct contains rollup block stored data, this may be a fixed struct data format reliant on how the struct data is populated invalidating this optimisation, however seems to be able to be modified for optimisation with data coming from DiamondInit.sol and Executor.sol. Variables blockNumber and indexRepeatedStorageChanges are both of type uint64 taking up 8 bytes each of storage and one slot of storage each. Having variable blockHash of type bytes32 in between the two uint64 variables taking up an entire storage slot. Should it be possible to position both blockHash and indexRepeatedStorageChanges together, they will have a combined size of 16 bytes enabling them to share a single storage slot utilising 16 of 32 bytes rather than currently having one slot each.

Original
    struct StoredBlockInfo {
        uint64 blockNumber;
        bytes32 blockHash;
        uint64 indexRepeatedStorageChanges;
        uint256 numberOfLayer1Txs;
        bytes32 priorityOperationsHash;
        bytes32 l2LogsTreeRoot;
        uint256 timestamp;
        bytes32 commitment;
    }
sizeslottype
80uint64
321bytes32
82uint64
323uint256
324bytes32
325bytes32
326uint256
327bytes32
Modified
    struct StoredBlockInfo {
        uint64 blockNumber;
        uint64 indexRepeatedStorageChanges;
        bytes32 blockHash;
        uint256 numberOfLayer1Txs;
        bytes32 priorityOperationsHash;
        bytes32 l2LogsTreeRoot;
        uint256 timestamp;
        bytes32 commitment;
    }
sizeslottype
80uint64
80uint64
321bytes32
322uint256
323bytes32
324bytes32
325uint256
326bytes32

#0 - GalloDaSballo

2022-11-25T00:51:06Z

Storage Packing: 4k

Good report but as I told you we need more heavy hitters, great progress, just need more findings

#1 - GalloDaSballo

2022-12-03T20:57:51Z

One order of magnitude better than the other 50% so will award a B with 60/100, report quality is good but you need to find a few more (e.g. Immutables) else you still risk not saving enough

#2 - c4-judge

2022-12-03T20:57:56Z

GalloDaSballo marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter