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↗.