Assessment reports>SP1 Helios>Medium findings>Incorrect state-variable initialization in the ,constructor
Category: Coding Mistakes

Incorrect state-variable initialization in the constructor

Medium Impact
Medium Severity
Medium Likelihood

Description

The constructor of the SP1Helios contract initializes the contract using the fields of the passed InitParams struct.

constructor(InitParams memory params) {
    GENESIS_VALIDATORS_ROOT = params.genesisValidatorsRoot;
    GENESIS_TIME = params.genesisTime;
    SECONDS_PER_SLOT = params.secondsPerSlot;
    SLOTS_PER_PERIOD = params.slotsPerPeriod;
    SLOTS_PER_EPOCH = params.slotsPerEpoch;
    SOURCE_CHAIN_ID = params.sourceChainId;
    syncCommittees[getSyncCommitteePeriod(params.head)] = params.syncCommitteeHash;
    lightClientVkey = params.lightClientVkey;
    storageSlotVkey = params.storageSlotVkey;
    headers[params.head] = params.header;
    executionStateRoots[params.head] = params.executionStateRoot;
    head = params.head;
    verifier = params.verifier;
    guardian = params.guardian;
}

While the contract initialization is generally performed correctly, it is insufficient in setting the following two state variables.

Variable: executionStateRoots

The executionStateRoots state variable is a mapping from the execution layer's block number to its state root. The update function stores the block number and the new state root from a verified ZK proof into this mapping. Subsequently, the updateStorageSlot function relies on the information stored in executionStateRoots to verify a storage-slot value at a specific block number.

executionStateRoots[params.head] = params.executionStateRoot;

Therefore, in the constructor, executionStateRoots should store params.executionStateRoot using params.executionBlockNumber as the key. However, the current implementation incorrectly uses params.head as the key.

Variable: executionBlockNumber

The executionBlockNumber state variable is used by the latestExecutionStateRoot and latestExecutionBlockNumber functions to retrieve the most recently updated state root of the execution layer and its corresponding block number.

function latestExecutionStateRoot() public view returns (bytes32) {
    return executionStateRoots[executionBlockNumber];
}

function latestExecutionBlockNumber() public view returns (uint256) {
    return executionBlockNumber;
}

As of the time of writing, executionBlockNumber is not being initialized in the constructor.

Impact

Since executionStateRoots is initialized with an incorrect key, the verifyStorageSlotsProof function cannot fetch the state root for the user-provided block number. This remains the case until the executionStateRoots mapping is first updated by a call to the update function. Therefore, any call to updateStorageSlot before this initial update will always fail.

Furthermore, it is also impossible to retrieve the most recently updated state root of the execution layer and its corresponding block number via the latestExecutionStateRoot and latestExecutionBlockNumber functions.

Recommendations

Modify the constructor function as follows.

-executionStateRoots[params.head] = params.executionStateRoot;
+executionStateRoots[params.executionBlockNumber] = params.executionStateRoot;
+executionBlockNumber = params.executionBlockNumber;

Remediation

This issue has been acknowledged by Succinct Labs, and a fix was implemented in commit 477ed4af.

Zellic © 2025Back to top ↑