Skip to content

Commit 7269d51

Browse files
authored
[evm]: EvmHost CREATE2 refactor (#866)
1 parent 9cb22fa commit 7269d51

23 files changed

Lines changed: 192 additions & 616 deletions

evm/script/DeployHFT.s.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ contract DeployHFT is BaseScript {
1919
dispatcher: address(dispatcher)
2020
}));
2121

22+
vm.stopBroadcast();
2223
console.log("=== HFT Deployment ===");
2324
console.log("HyperFungibleToken:", address(hft));
2425
console.log("CallDispatcher:", address(dispatcher));

evm/script/DeployIsmp.s.sol

Lines changed: 11 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,14 @@ import "../src/core/HandlerV2.sol";
88
import "../src/core/EvmHost.sol";
99
import "../src/core/HostManager.sol";
1010

11+
import {TestnetHost} from "../src/core/TestnetHost.sol";
12+
1113
import "../src/consensus/EcdsaBeefy.sol";
1214
import "../src/consensus/ConsensusRouter.sol";
13-
import "../src/hosts/Ethereum.sol";
14-
import "../src/hosts/Arbitrum.sol";
15-
import "../src/hosts/Optimism.sol";
16-
import "../src/hosts/Base.sol";
17-
import "../src/hosts/Gnosis.sol";
18-
import "../src/hosts/Soneium.sol";
19-
import "../src/hosts/Unichain.sol";
20-
import "../src/hosts/Sei.sol";
2115

2216
import {HyperFungibleTokenImpl} from "../src/utils/HyperFungibleTokenImpl.sol";
2317
import {TokenFaucet} from "../src/utils/TokenFaucet.sol";
2418

25-
import {BscHost} from "../src/hosts/Bsc.sol";
26-
import {PolygonHost} from "../src/hosts/Polygon.sol";
27-
import {PolkadotHost} from "../src/hosts/Polkadot.sol";
28-
import {PharosHost} from "../src/hosts/Pharos.sol";
29-
3019
import {SP1Verifier} from "@sp1-contracts/v5.0.0/SP1VerifierGroth16.sol";
3120
import {SP1Beefy} from "../src/consensus/SP1Beefy.sol";
3221
import {EcdsaBeefy} from "../src/consensus/EcdsaBeefy.sol";
@@ -63,10 +52,8 @@ contract DeployScript is BaseScript {
6352
// Deploy SP1 ZK consensus client
6453
SP1Verifier verifier = new SP1Verifier{salt: salt}();
6554
SP1Beefy sp1Beefy = new SP1Beefy{salt: salt}(verifier, sp1VerificationKey);
66-
6755
// Deploy EcdsaBeefy naive consensus client
6856
EcdsaBeefy ecdsaBeefy = new EcdsaBeefy{salt: salt}();
69-
7057
// Deploy ConsensusRouter wrapping both consensus clients
7158
ConsensusRouter consensusRouter = new ConsensusRouter{salt: salt}(
7259
IConsensus(address(sp1Beefy)), IConsensus(address(ecdsaBeefy))
@@ -108,7 +95,6 @@ contract DeployScript is BaseScript {
10895

10996
// handler
11097
HandlerV2 handler = new HandlerV2{salt: salt}();
111-
11298
// Host manager
11399
HostManager manager = new HostManager{salt: salt}(HostManagerParams({admin: admin, host: address(0)}));
114100
uint256[] memory stateMachines = new uint256[](1);
@@ -128,12 +114,15 @@ contract DeployScript is BaseScript {
128114
stateMachines: stateMachines
129115
});
130116

131-
address hostAddress = initHost(params);
117+
EvmHost host = isMainnet
118+
? new EvmHost{salt: salt}(admin)
119+
: EvmHost(payable(address(new TestnetHost{salt: salt}(admin))));
120+
host.initialize(params);
132121
// set the host address on the host manager
133-
manager.setIsmpHost(hostAddress);
122+
manager.setIsmpHost(address(host));
134123

135124
// Set the consensus state
136-
EvmHost(payable(hostAddress))
125+
EvmHost(payable(address(host)))
137126
.setConsensusState(
138127
consensusState,
139128
StateMachineHeight({stateMachineId: paraId, height: 1}),
@@ -143,81 +132,12 @@ contract DeployScript is BaseScript {
143132
// ============= Deploy applications =============
144133
CallDispatcher callDispatcher = new CallDispatcher{salt: salt}();
145134

146-
if (!isMainnet) {
147-
config.set("TOKEN_FAUCET", address(faucet));
148-
}
149-
135+
vm.stopBroadcast();
150136
// ============= Write addresses to config =============
151-
config.set("HOST", hostAddress);
137+
if (!isMainnet) config.set("TOKEN_FAUCET", address(faucet));
138+
config.set("HOST", address(host));
152139
config.set("HANDLER", address(handler));
153140
config.set("FEE_TOKEN", feeToken);
154141
config.set("CALL_DISPATCHER", address(callDispatcher));
155142
}
156-
157-
function initHost(HostParams memory params) public returns (address) {
158-
uint256 chainId = block.chainid;
159-
160-
// Ethereum (mainnet: 1, sepolia: 11155111)
161-
if (chainId == 1 || chainId == 11155111) {
162-
EthereumHost h = new EthereumHost{salt: salt}(params);
163-
return address(h);
164-
}
165-
// Arbitrum (mainnet: 42161, sepolia: 421614)
166-
else if (chainId == 42161 || chainId == 421614) {
167-
ArbitrumHost h = new ArbitrumHost{salt: salt}(params);
168-
return address(h);
169-
}
170-
// Optimism (mainnet: 10, sepolia: 11155420)
171-
else if (chainId == 10 || chainId == 11155420) {
172-
OptimismHost h = new OptimismHost{salt: salt}(params);
173-
return address(h);
174-
}
175-
// Base (mainnet: 8453, sepolia: 84532)
176-
else if (chainId == 8453 || chainId == 84532) {
177-
BaseHost h = new BaseHost{salt: salt}(params);
178-
return address(h);
179-
}
180-
// BSC (mainnet: 56, testnet: 97)
181-
else if (chainId == 56 || chainId == 97) {
182-
BscHost h = new BscHost{salt: salt}(params);
183-
return address(h);
184-
}
185-
// Polygon (mainnet: 137, amoy: 80002)
186-
else if (chainId == 137 || chainId == 80002) {
187-
PolygonHost h = new PolygonHost{salt: salt}(params);
188-
return address(h);
189-
}
190-
// Gnosis (mainnet: 100, chiado: 10200)
191-
else if (chainId == 100 || chainId == 10200) {
192-
GnosisHost h = new GnosisHost{salt: salt}(params);
193-
return address(h);
194-
}
195-
// Soneium (mainnet: 1868)
196-
else if (chainId == 1868) {
197-
SoneiumHost h = new SoneiumHost{salt: salt}(params);
198-
return address(h);
199-
}
200-
// Unichain (mainnet: 1301)
201-
else if (chainId == 1301) {
202-
UnichainHost h = new UnichainHost{salt: salt}(params);
203-
return address(h);
204-
}
205-
// Sei (mainnet: 1329, arctic testnet: 1328)
206-
else if (chainId == 1329 || chainId == 1328) {
207-
SeiHost h = new SeiHost{salt: salt}(params);
208-
return address(h);
209-
}
210-
// Polkadot Asset Hub (mainnet: 420420419, testnet: 420420417)
211-
else if (chainId == 420420419 || chainId == 420420417) {
212-
PolkadotHost h = new PolkadotHost{salt: salt}(params);
213-
return address(h);
214-
}
215-
// Pharos (mainnet: 688600, testnet: 688689)
216-
else if (chainId == 688600 || chainId == 688689) {
217-
PharosHost h = new PharosHost{salt: salt}(params);
218-
return address(h);
219-
}
220-
221-
revert("Unknown chain ID");
222-
}
223143
}

evm/script/DeployUniV3Wrapper.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ contract DeployScript is BaseScript {
2121
address uniswapV2 = IDispatcher(HOST_ADDRESS).uniswapV2Router();
2222

2323
UniV3UniswapV2Wrapper wrapper = new UniV3UniswapV2Wrapper{salt: salt}(admin);
24-
console.log("UniV3UniswapV2Wrapper deployed at:", address(wrapper));
25-
2624
wrapper.init(
2725
UniV3UniswapV2Wrapper.Params({
2826
WETH: IUniswapV2Router02(uniswapV2).WETH(), swapRouter: swapRouter, quoter: quoter, maxFee: maxFee
2927
})
3028
);
29+
vm.stopBroadcast();
30+
console.log("UniV3UniswapV2Wrapper deployed at:", address(wrapper));
3131
console.log("UniV3UniswapV2Wrapper initialized");
3232
}
3333
}

evm/script/DeployUniV4Wrapper.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ contract DeployScript is BaseScript {
2020
address weth = config.get("WETH").toAddress();
2121

2222
UniV4UniswapV2Wrapper wrapper = new UniV4UniswapV2Wrapper{salt: salt}(admin);
23-
console.log("UniV4UniswapV2Wrapper deployed at:", address(wrapper));
2423

2524
wrapper.init(
2625
UniV4UniswapV2Wrapper.Params({
@@ -31,6 +30,8 @@ contract DeployScript is BaseScript {
3130
defaultTickSpacing: defaultTickSpacing
3231
})
3332
);
33+
vm.stopBroadcast();
34+
console.log("UniV4UniswapV2Wrapper deployed at:", address(wrapper));
3435
console.log("UniV4UniswapV2Wrapper initialized");
3536
}
3637
}

evm/script/DeployWrappedHFT.s.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ contract DeployWrappedHFT is BaseScript {
2121
isWeth: isWeth
2222
}));
2323

24+
vm.stopBroadcast();
2425
console.log("=== WrappedHFT Deployment ===");
2526
console.log("WrappedHyperFungibleToken:", address(whft));
2627
console.log("CallDispatcher:", address(dispatcher));

evm/src/core/EvmHost.sol

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)