Incorrect state-variable initialization in the constructor
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↗.