@@ -108,7 +108,7 @@ struct WithdrawParams {
108108 * It is only responsible for dispatching incoming & outgoing requests/responses. As well as managing
109109 * the state of the ISMP protocol.
110110 */
111- abstract contract EvmHost is IHost , IHostManager , Context {
111+ contract EvmHost is IHost , IHostManager , Context {
112112 using Message for PostRequest;
113113 using Message for GetRequest;
114114 using Message for GetResponse;
@@ -134,15 +134,15 @@ abstract contract EvmHost is IHost, IHostManager, Context {
134134
135135 // mapping of state machine identifier to latest known height
136136 // (stateMachineId => blockHeight)
137- mapping (uint256 => uint256 ) private _latestStateMachineHeight;
137+ mapping (uint256 => uint256 ) internal _latestStateMachineHeight;
138138
139139 // mapping of state machine identifier to height vetoed to fisherman
140140 // useful for rewarding fishermen on hyperbridge
141141 // (stateMachineId => (blockHeight => fisherman))
142142 mapping (uint256 => mapping (uint256 => address )) private _vetoes;
143143
144144 // Parameters for the host
145- HostParams private _hostParams;
145+ HostParams internal _hostParams;
146146
147147 // Monotonically increasing nonce for outgoing requests
148148 uint256 private _nonce;
@@ -163,6 +163,10 @@ abstract contract EvmHost is IHost, IHostManager, Context {
163163 // The most recent authority set ID for which a consensus proof has been submitted.
164164 uint256 private _currentEpoch;
165165
166+ // One-shot guard for `initialize`. Appended after all existing storage so
167+ // this change does not shift any pre-existing storage slots.
168+ bool private _initialized;
169+
166170 // Emitted when an incoming POST request is handled
167171 event PostRequestHandled (
168172 // Commitment of the incoming request
@@ -352,9 +356,28 @@ abstract contract EvmHost is IHost, IHostManager, Context {
352356 _;
353357 }
354358
355- constructor (HostParams memory params ) {
356- updateHostParamsInternal (params);
359+ /**
360+ * @dev Constructor only sets the initial admin and the consensus update
361+ * timestamp. All other configuration is deferred to `initialize` so that
362+ * the constructor's init code is identical on every chain (assuming the
363+ * same `admin` is used everywhere), which is required for CREATE2 address
364+ * parity across chains.
365+ */
366+ constructor (address _admin ) {
357367 _consensusUpdateTimestamp = block .timestamp ;
368+ _hostParams.admin = _admin;
369+ }
370+
371+ /**
372+ * @dev One-shot initializer. Can only be called once, and only by the
373+ * admin set in the constructor. Applies the initial `HostParams` via
374+ * the internal updater.
375+ */
376+ function initialize (HostParams memory params ) external {
377+ if (_msgSender () != _hostParams.admin) revert UnauthorizedAction ();
378+ if (_initialized) revert UnauthorizedAction ();
379+ _initialized = true ;
380+ updateHostParamsInternal (params);
358381 }
359382
360383 /*
@@ -369,11 +392,6 @@ abstract contract EvmHost is IHost, IHostManager, Context {
369392 return StateMachine.evm (block .chainid );
370393 }
371394
372- /**
373- * @return the mainnet evm chainId for this host
374- */
375- function chainId () public virtual returns (uint256 );
376-
377395 /**
378396 * @return the host timestamp
379397 */
@@ -544,26 +562,15 @@ abstract contract EvmHost is IHost, IHostManager, Context {
544562 }
545563
546564 /**
547- * @dev Updates the HostParams. On mainnet it can only be called by cross-chain governance.
565+ * @dev Updates the HostParams. Only callable by cross-chain governance
566+ * via the configured `hostManager`. The admin has no privileges here —
567+ * environments that need a privileged admin override (testnets, forks)
568+ * should use `TestnetHost`, which extends this contract.
569+ *
570+ * Marked `virtual` so subclasses can broaden the authorization
548571 * @param params, the new host params.
549572 */
550- function updateHostParams (HostParams memory params ) external {
551- address caller = _msgSender ();
552- if (caller != _hostParams.hostManager && caller != _hostParams.admin) {
553- revert UnauthorizedAction ();
554- }
555-
556- if (caller == _hostParams.admin) {
557- // admin cannot change host params on mainnet
558- if (chainId () == block .chainid ) revert UnauthorizedAction ();
559-
560- // reset all state machines, on testnet
561- uint256 whitelistLength = params.stateMachines.length ;
562- for (uint256 i = 0 ; i < whitelistLength; ++ i) {
563- delete _latestStateMachineHeight[params.stateMachines[i]];
564- }
565- }
566-
573+ function updateHostParams (HostParams memory params ) external virtual restrict (_hostParams.hostManager) {
567574 updateHostParamsInternal (params);
568575 }
569576
@@ -746,17 +753,31 @@ abstract contract EvmHost is IHost, IHostManager, Context {
746753 }
747754
748755 /**
749- * @dev sets the initial consensus state
756+ * @dev Whether the admin is permitted to (re)initialize the consensus
757+ * state. On mainnet hosts the consensus state may only be set once via
758+ * `setConsensusState` and is thereafter only updated through consensus
759+ * proofs. `TestnetHost` overrides this to permit repeated admin
760+ * re-initialization.
761+ */
762+ function _canReinitConsensus () internal view virtual returns (bool ) {
763+ return keccak256 (_consensusState) == keccak256 (bytes ("" ));
764+ }
765+
766+ /**
767+ * @dev sets the initial consensus state. By default this is a one-shot
768+ * operation: once `_consensusState` is non-empty the admin can no longer
769+ * call this and consensus state moves only through `storeConsensusState`
770+ * (handler-only, driven by consensus proofs). `TestnetHost` overrides
771+ * `_canReinitConsensus` to lift this restriction.
750772 * @param state initial consensus state
773+ * @param height initial state-machine height
774+ * @param commitment initial state commitment at `height`
751775 */
752776 function setConsensusState (bytes memory state , StateMachineHeight memory height , StateCommitment memory commitment )
753777 public
754778 restrict (_hostParams.admin)
755779 {
756- // if we're on mainnet, then consensus state can only be initialized once
757- // and updated subsequently through consensus proofs
758- bool canUpdate = chainId () == block .chainid ? keccak256 (_consensusState) == keccak256 (bytes ("" )) : true ;
759- if (! canUpdate) revert UnauthorizedAction ();
780+ if (! _canReinitConsensus ()) revert UnauthorizedAction ();
760781
761782 _consensusState = state;
762783 _consensusUpdateTimestamp = block .timestamp ;
0 commit comments