Skip to content

Commit 685dbc6

Browse files
committed
Merge branch 'main' of github.com:polytope-labs/hyperbridge into dami/phantom-order
2 parents a9ea624 + e5d1de2 commit 685dbc6

24 files changed

Lines changed: 1096 additions & 469 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ env:
4444
SEPOLIA_URL: ${{ secrets.SEPOLIA_URL }}
4545
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
4646
POLYGON_RPC: ${{ secrets.POLYGON_RPC }}
47+
# Live endpoints for the BEEFY consensus-proof simtest; override via repo secrets.
48+
RELAY_WS_URL: ${{ secrets.RELAY_WS_URL || 'wss://paseo.dotters.network' }}
49+
PARA_WS_URL: ${{ secrets.PARA_WS_URL || 'wss://gargantua.rpc.polytope.technology' }}
4750
WASM_BINDGEN_TEST_TIMEOUT: 5400
4851
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
4952
MAINNET_FORK_URL: ${{ secrets.MAINNET_FORK_URL }}

.github/workflows/evm.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
- name: Install Foundry
6262
uses: foundry-rs/foundry-toolchain@v1
6363
with:
64-
version: nightly
64+
version: stable
6565

6666
- name: Run Forge build
6767
run: |
@@ -103,7 +103,7 @@ jobs:
103103
- name: Install Foundry
104104
uses: foundry-rs/foundry-toolchain@v1
105105
with:
106-
version: nightly
106+
version: stable
107107

108108
- name: Install Rust toolchain
109109
uses: dtolnay/rust-toolchain@stable

evm/script/DeployIntentGateway.s.sol

Lines changed: 43 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ pragma solidity ^0.8.17;
44
import "forge-std/Script.sol";
55
import "stringutils/strings.sol";
66

7-
import {IntentGatewayV2, Params, Deployment} from "../src/apps/IntentGatewayV2.sol";
7+
import {IntentGatewayV2, Params} from "../src/apps/IntentGatewayV2.sol";
8+
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
89
import {BaseScript} from "./BaseScript.sol";
910
import {CallDispatcher} from "../src/utils/CallDispatcher.sol";
1011
import {SolverAccount} from "../src/apps/intentsv2/SolverAccount.sol";
@@ -17,200 +18,57 @@ contract DeployScript is BaseScript {
1718
/// @notice Main deployment logic - called by BaseScript's run() functions
1819
/// @dev This function is called within a broadcast context
1920
function deploy() internal override {
20-
IntentGatewayV2 intentGateway = new IntentGatewayV2{salt: salt}(admin);
21-
console.log("IntentGateway deployed at:", address(intentGateway));
22-
23-
SolverAccount solverAccount = new SolverAccount{salt: salt}(address(intentGateway));
24-
console.log("SolverAccount deployed at:", address(solverAccount));
25-
21+
// Deploy implementation and proxy via CREATE2 with the same salt. The proxy is initialized
22+
// atomically through its init data. The cross-chain peer registry is passed in by chain id
23+
// only — `initialize` binds each to `address(this)` — so no peer address is embedded in the
24+
// init data. The address depends on (impl address, salt, params, peer chain ids), all of
25+
// which are identical across chains, keeping the proxy address identical everywhere.
2626
address priceOracle = address(0);
27-
// address priceOracle = deployPriceOracle(address(intentGateway));
28-
29-
Deployment[] memory deployments;
27+
IntentGatewayV2 implementation = new IntentGatewayV2{salt: salt}(admin);
28+
bytes[] memory peerChains;
3029
if (config.get("is_mainnet").toBool()) {
31-
deployments = new Deployment[](9);
32-
deployments[0] = Deployment({
33-
chain: StateMachine.evm(1), // ethereum
34-
gateway: address(intentGateway)
35-
});
36-
deployments[1] = Deployment({
37-
chain: StateMachine.evm(10), // optimism
38-
gateway: address(intentGateway)
39-
});
40-
deployments[2] = Deployment({
41-
chain: StateMachine.evm(42161), // arbitrum
42-
gateway: address(intentGateway)
43-
});
44-
deployments[3] = Deployment({
45-
chain: StateMachine.evm(8453), // base
46-
gateway: address(intentGateway)
47-
});
48-
deployments[4] = Deployment({
49-
chain: StateMachine.evm(56), // bsc
50-
gateway: address(intentGateway)
51-
});
52-
deployments[5] = Deployment({
53-
chain: StateMachine.evm(100), // gnosis
54-
gateway: address(intentGateway)
55-
});
56-
deployments[6] = Deployment({
57-
chain: StateMachine.evm(137), // polygon
58-
gateway: address(intentGateway)
59-
});
60-
deployments[7] = Deployment({
61-
chain: StateMachine.evm(420420419), // polkadot
62-
gateway: address(intentGateway)
63-
});
64-
deployments[8] = Deployment({
65-
chain: StateMachine.evm(1868), // soneium
66-
gateway: address(intentGateway)
67-
});
30+
peerChains = new bytes[](9);
31+
peerChains[0] = StateMachine.evm(1); // ethereum
32+
peerChains[1] = StateMachine.evm(10); // optimism
33+
peerChains[2] = StateMachine.evm(42161); // arbitrum
34+
peerChains[3] = StateMachine.evm(8453); // base
35+
peerChains[4] = StateMachine.evm(56); // bsc
36+
peerChains[5] = StateMachine.evm(100); // gnosis
37+
peerChains[6] = StateMachine.evm(137); // polygon
38+
peerChains[7] = StateMachine.evm(420420419); // polkadot
39+
peerChains[8] = StateMachine.evm(1868); // soneium
6840
} else {
69-
deployments = new Deployment[](2);
70-
deployments[0] = Deployment({
71-
chain: StateMachine.evm(97), // bsc testnet (chapel)
72-
gateway: address(intentGateway)
73-
});
74-
deployments[1] = Deployment({
75-
chain: StateMachine.evm(80002), // polygon amoy
76-
gateway: address(intentGateway)
77-
});
41+
peerChains = new bytes[](2);
42+
peerChains[0] = StateMachine.evm(97); // bsc testnet (chapel)
43+
peerChains[1] = StateMachine.evm(80002); // polygon amoy
7844
}
7945

80-
intentGateway.init(
81-
Params({
82-
host: HOST_ADDRESS,
83-
dispatcher: config.get("CALL_DISPATCHER").toAddress(),
84-
solverSelection: config.get("7702").toBool(),
85-
surplusShareBps: 5_000, // 50%
86-
protocolFeeBps: 30, // 0.3%
87-
priceOracle: address(priceOracle)
88-
}),
89-
deployments
46+
bytes memory initData = abi.encodeCall(
47+
IntentGatewayV2.initialize,
48+
(
49+
Params({
50+
host: HOST_ADDRESS,
51+
dispatcher: config.get("CALL_DISPATCHER").toAddress(),
52+
solverSelection: config.get("7702").toBool(),
53+
surplusShareBps: 5_000, // 50%
54+
protocolFeeBps: 30, // 0.3%
55+
priceOracle: priceOracle
56+
}),
57+
peerChains
58+
)
9059
);
60+
ERC1967Proxy proxy = new ERC1967Proxy{salt: salt}(address(implementation), initData);
61+
IntentGatewayV2 intentGateway = IntentGatewayV2(payable(address(proxy)));
62+
SolverAccount solverAccount = new SolverAccount{salt: salt}(address(intentGateway));
9163

9264
vm.stopBroadcast();
65+
66+
console.log("IntentGateway implementation deployed at:", address(implementation));
67+
console.log("IntentGateway proxy deployed at:", address(intentGateway));
68+
console.log("SolverAccount deployed at:", address(solverAccount));
69+
9370
config.set("INTENT_GATEWAY_V2", address(intentGateway));
9471
config.set("SOLVER_ACCOUNT", address(solverAccount));
9572
config.set("PRICE_ORACLE", address(priceOracle));
9673
}
97-
98-
function deployPriceOracle(address intentGateway) internal returns (address) {
99-
VWAPOracle priceOracle = new VWAPOracle{salt: salt}(admin, intentGateway);
100-
console.log("VWAPOracle deployed at:", address(priceOracle));
101-
102-
// Initialize price oracle with token decimals
103-
VWAPOracle.TokenDecimalsUpdate[] memory decimalsUpdates = new VWAPOracle.TokenDecimalsUpdate[](9);
104-
105-
// Ethereum
106-
decimalsUpdates[0].sourceChain = bytes("EVM-1");
107-
decimalsUpdates[0].tokens = new VWAPOracle.TokenDecimal[](2);
108-
decimalsUpdates[0].tokens[0] = VWAPOracle.TokenDecimal({
109-
token: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC
110-
decimals: 6
111-
});
112-
decimalsUpdates[0].tokens[1] = VWAPOracle.TokenDecimal({
113-
token: 0xdAC17F958D2ee523a2206206994597C13D831ec7, // USDT
114-
decimals: 6
115-
});
116-
117-
// Arbitrum
118-
decimalsUpdates[1].sourceChain = bytes("EVM-42161");
119-
decimalsUpdates[1].tokens = new VWAPOracle.TokenDecimal[](2);
120-
decimalsUpdates[1].tokens[0] = VWAPOracle.TokenDecimal({
121-
token: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831, // USDC
122-
decimals: 6
123-
});
124-
decimalsUpdates[1].tokens[1] = VWAPOracle.TokenDecimal({
125-
token: 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9, // USDT
126-
decimals: 6
127-
});
128-
129-
// Optimism
130-
decimalsUpdates[2].sourceChain = bytes("EVM-10");
131-
decimalsUpdates[2].tokens = new VWAPOracle.TokenDecimal[](2);
132-
decimalsUpdates[2].tokens[0] = VWAPOracle.TokenDecimal({
133-
token: 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, // USDC
134-
decimals: 6
135-
});
136-
decimalsUpdates[2].tokens[1] = VWAPOracle.TokenDecimal({
137-
token: 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58, // USDT
138-
decimals: 6
139-
});
140-
141-
// Base
142-
decimalsUpdates[3].sourceChain = bytes("EVM-8453");
143-
decimalsUpdates[3].tokens = new VWAPOracle.TokenDecimal[](2);
144-
decimalsUpdates[3].tokens[0] = VWAPOracle.TokenDecimal({
145-
token: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, // USDC
146-
decimals: 6
147-
});
148-
decimalsUpdates[3].tokens[1] = VWAPOracle.TokenDecimal({
149-
token: 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2, // USDT
150-
decimals: 6
151-
});
152-
153-
// BSC
154-
decimalsUpdates[4].sourceChain = bytes("EVM-56");
155-
decimalsUpdates[4].tokens = new VWAPOracle.TokenDecimal[](2);
156-
decimalsUpdates[4].tokens[0] = VWAPOracle.TokenDecimal({
157-
token: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, // USDC
158-
decimals: 18
159-
});
160-
decimalsUpdates[4].tokens[1] = VWAPOracle.TokenDecimal({
161-
token: 0x55d398326f99059fF775485246999027B3197955, // USDT
162-
decimals: 18
163-
});
164-
165-
// Gnosis
166-
decimalsUpdates[5].sourceChain = bytes("EVM-100");
167-
decimalsUpdates[5].tokens = new VWAPOracle.TokenDecimal[](2);
168-
decimalsUpdates[5].tokens[0] = VWAPOracle.TokenDecimal({
169-
token: 0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83, // USDC (WXDAI)
170-
decimals: 6
171-
});
172-
decimalsUpdates[5].tokens[1] = VWAPOracle.TokenDecimal({
173-
token: 0x4ECaBa5870353805a9F068101A40E0f32ed605C6, // USDT
174-
decimals: 6
175-
});
176-
177-
// Polygon
178-
decimalsUpdates[6].sourceChain = bytes("EVM-137");
179-
decimalsUpdates[6].tokens = new VWAPOracle.TokenDecimal[](2);
180-
decimalsUpdates[6].tokens[0] = VWAPOracle.TokenDecimal({
181-
token: 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, // USDC
182-
decimals: 6
183-
});
184-
decimalsUpdates[6].tokens[1] = VWAPOracle.TokenDecimal({
185-
token: 0xc2132D05D31c914a87C6611C10748AEb04B58e8F, // USDT
186-
decimals: 6
187-
});
188-
189-
// Unichain
190-
decimalsUpdates[7].sourceChain = bytes("EVM-130");
191-
decimalsUpdates[7].tokens = new VWAPOracle.TokenDecimal[](2);
192-
decimalsUpdates[7].tokens[0] = VWAPOracle.TokenDecimal({
193-
token: 0x078D782b760474a361dDA0AF3839290b0EF57AD6, // USDC
194-
decimals: 6
195-
});
196-
decimalsUpdates[7].tokens[1] = VWAPOracle.TokenDecimal({
197-
token: 0x9151434b16b9763660705744891fA906F660EcC5, // USDT
198-
decimals: 6
199-
});
200-
201-
// Tron
202-
decimalsUpdates[8].sourceChain = bytes("EVM-728126428");
203-
decimalsUpdates[8].tokens = new VWAPOracle.TokenDecimal[](2);
204-
decimalsUpdates[8].tokens[0] = VWAPOracle.TokenDecimal({
205-
token: 0x742b023b58488B4be587bBA63ed11134b02217B3, // USDC
206-
decimals: 6
207-
});
208-
decimalsUpdates[8].tokens[1] = VWAPOracle.TokenDecimal({
209-
token: 0xa614f803B6FD780986A42c78Ec9c7f77e6DeD13C, // USDT
210-
decimals: 6
211-
});
212-
priceOracle.init(HOST_ADDRESS, decimalsUpdates);
213-
214-
return address(priceOracle);
215-
}
21674
}

evm/src/apps/IntentGatewayV2.sol

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {IDispatcher} from "@hyperbridge/core/interfaces/IDispatcher.sol";
2323
import {IIntentPriceOracle} from "@hyperbridge/core/apps/IntentPriceOracle.sol";
2424
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
2525
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
26+
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
2627
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
2728
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
2829
import {IUniswapV2Router02} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
@@ -57,16 +58,20 @@ import {
5758
* \ /
5859
* IntentGatewayV2
5960
*/
60-
contract IntentGatewayV2 is IntrinsicIntents, ExtrinsicIntents, ReentrancyGuardTransient {
61+
contract IntentGatewayV2 is IntrinsicIntents, ExtrinsicIntents, ReentrancyGuardTransient, Initializable {
6162
using SafeERC20 for IERC20;
6263

63-
/**
64-
* @dev Initializes the EIP-712 domain with name "IntentGateway" and version "2".
65-
* Sets the initial admin who has one-time authority to call `setParams`.
66-
* @param admin The address that will have permission to set initial parameters.
67-
*/
68-
constructor(address admin) EIP712("IntentGateway", "2") {
69-
_admin = admin;
64+
/// @dev Privileged admin for future upgrade-gated actions (e.g. pausing). Immutable, so it must
65+
/// be identical across chains or the deterministic proxy address diverges. Does not gate
66+
/// `initialize`; atomic CREATE2 deployment already binds the init data to the canonical address.
67+
address public immutable _owner;
68+
69+
/// @dev Sets the EIP-712 domain ("IntentGateway", "2"), records the admin, and locks this raw
70+
/// implementation against direct initialization.
71+
/// @param owner The privileged admin address.
72+
constructor(address owner) EIP712("IntentGateway", "2") {
73+
_owner = owner;
74+
_disableInitializers();
7075
}
7176

7277
/**
@@ -87,25 +92,26 @@ contract IntentGatewayV2 is IntrinsicIntents, ExtrinsicIntents, ReentrancyGuardT
8792
}
8893

8994
/**
90-
* @dev One-time parameter initialization. Can only be called by the admin set in
91-
* the constructor. After successful execution, the admin is burned (set to address(0)),
92-
* preventing any further calls.
93-
*
94-
* Subsequent parameter updates must come through Hyperbridge governance via the `onAccept` callback.
95+
* @dev One-time init (the `initializer` modifier caps it to a single call). Registers the
96+
* initial cross-chain peers, each bound to `address(this)`; `_instance` reverts with
97+
* `UnknownInstance` for any chain not registered here or later via `onAccept` governance.
9598
*
9699
* @param p The initial gateway configuration parameters.
97-
* @param deployments The initial gateway cross-chain peers
100+
* @param peerChains State-machine ids of the cross-chain peers to register. Each is bound to
101+
* this gateway's own address, identical across chains under deterministic CREATE2, so no peer
102+
* address is carried in the proxy's init data.
98103
*/
99-
function init(Params memory p, Deployment[] memory deployments) public {
100-
if (msg.sender != _admin) revert Unauthorized();
101-
102-
uint256 deploymentsLength = deployments.length;
103-
for (uint256 i = 0; i < deploymentsLength; i++) {
104-
_addDeployment(deployments[i]);
104+
function initialize(Params memory p, bytes[] memory peerChains) public initializer {
105+
uint256 peersLength = peerChains.length;
106+
for (uint256 i = 0; i < peersLength; i++) {
107+
Deployment memory deployment = Deployment({
108+
chain: peerChains[i],
109+
gateway: address(this)
110+
});
111+
_addDeployment(deployment);
105112
}
106113
_validateParams(p);
107114
_params = p;
108-
_admin = address(0);
109115
}
110116

111117
/**
@@ -118,7 +124,7 @@ contract IntentGatewayV2 is IntrinsicIntents, ExtrinsicIntents, ReentrancyGuardT
118124

119125
/**
120126
* @dev Returns the registered gateway address for a given state machine.
121-
* Falls back to this contract's address if no remote deployment is registered.
127+
* Reverts with `UnknownInstance` if no remote deployment is registered.
122128
* @param stateMachineId The raw state machine identifier bytes.
123129
* @return The gateway address for the given state machine.
124130
*/

evm/src/apps/intentsv2/ExtrinsicIntents.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
} from "@hyperbridge/core/apps/IntentGatewayV2.sol";
3232
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
3333
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
34+
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
3435

3536

3637
/**
@@ -280,6 +281,8 @@ abstract contract ExtrinsicIntents is IntentsBase, HyperApp {
280281
* protocol fees. Only Hyperbridge may dispatch this request.
281282
* - SweepDust: Transfers accumulated protocol dust to a specified beneficiary.
282283
* Only Hyperbridge may dispatch this request.
284+
* - UpgradeContract: Points the ERC-1967 proxy at a new implementation, optionally
285+
* running migration calldata atomically. Only Hyperbridge may dispatch this request.
283286
*
284287
* @param incoming The incoming post request from Hyperbridge.
285288
*/
@@ -299,6 +302,9 @@ abstract contract ExtrinsicIntents is IntentsBase, HyperApp {
299302
_updateParams(abi.decode(incoming.request.body[1:], (ParamsUpdate)));
300303
} else if (kind == RequestKind.SweepDust) {
301304
_sweepDust(abi.decode(incoming.request.body[1:], (SweepDust)));
305+
} else if (kind == RequestKind.UpgradeContract) {
306+
(address newImpl, bytes memory initData) = abi.decode(incoming.request.body[1:], (address, bytes));
307+
ERC1967Utils.upgradeToAndCall(newImpl, initData);
302308
}
303309
}
304310

0 commit comments

Comments
 (0)