Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/evm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
version: stable

- name: Run Forge build
run: |
Expand Down Expand Up @@ -103,7 +103,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
version: stable

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
Expand Down
98 changes: 29 additions & 69 deletions evm/script/DeployIntentGateway.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,92 +4,52 @@ pragma solidity ^0.8.17;
import "forge-std/Script.sol";
import "stringutils/strings.sol";

import {IntentGatewayV2, Params, Deployment} from "../src/apps/IntentGatewayV2.sol";
import {IntentGatewayV2, Params} from "../src/apps/IntentGatewayV2.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {BaseScript} from "./BaseScript.sol";
import {CallDispatcher} from "../src/utils/CallDispatcher.sol";
import {SolverAccount} from "../src/apps/intentsv2/SolverAccount.sol";
import {VWAPOracle} from "../src/utils/VWAPOracle.sol";
import {StateMachine} from "@hyperbridge/core/libraries/StateMachine.sol";

contract DeployScript is BaseScript {
using strings for *;

/// @notice Main deployment logic - called by BaseScript's run() functions
/// @dev This function is called within a broadcast context
function deploy() internal override {
IntentGatewayV2 intentGateway = new IntentGatewayV2{salt: salt}(admin);
console.log("IntentGateway deployed at:", address(intentGateway));

SolverAccount solverAccount = new SolverAccount{salt: salt}(address(intentGateway));
console.log("SolverAccount deployed at:", address(solverAccount));

// Deploy implementation and proxy via CREATE2 with the same salt. The proxy is initialized
// atomically through its init data, so its address depends on (impl address, salt, params).
// Params are identical across chains, keeping the proxy address identical everywhere — which
// lets cross-chain peers resolve to `address(this)` rather than a stored registry.
address priceOracle = address(0);
// address priceOracle = deployPriceOracle(address(intentGateway));

Deployment[] memory deployments;
if (config.get("is_mainnet").toBool()) {
deployments = new Deployment[](9);
deployments[0] = Deployment({
chain: StateMachine.evm(1), // ethereum
gateway: address(intentGateway)
});
deployments[1] = Deployment({
chain: StateMachine.evm(10), // optimism
gateway: address(intentGateway)
});
deployments[2] = Deployment({
chain: StateMachine.evm(42161), // arbitrum
gateway: address(intentGateway)
});
deployments[3] = Deployment({
chain: StateMachine.evm(8453), // base
gateway: address(intentGateway)
});
deployments[4] = Deployment({
chain: StateMachine.evm(56), // bsc
gateway: address(intentGateway)
});
deployments[5] = Deployment({
chain: StateMachine.evm(100), // gnosis
gateway: address(intentGateway)
});
deployments[6] = Deployment({
chain: StateMachine.evm(137), // polygon
gateway: address(intentGateway)
});
deployments[7] = Deployment({
chain: StateMachine.evm(420420419), // polkadot
gateway: address(intentGateway)
});
deployments[8] = Deployment({
chain: StateMachine.evm(1868), // soneium
gateway: address(intentGateway)
});
} else {
deployments = new Deployment[](2);
deployments[0] = Deployment({
chain: StateMachine.evm(97), // bsc testnet (chapel)
gateway: address(intentGateway)
});
deployments[1] = Deployment({
chain: StateMachine.evm(80002), // polygon amoy
gateway: address(intentGateway)
});
}

intentGateway.init(
Params({
host: HOST_ADDRESS,
dispatcher: config.get("CALL_DISPATCHER").toAddress(),
solverSelection: config.get("7702").toBool(),
surplusShareBps: 5_000, // 50%
protocolFeeBps: 30, // 0.3%
priceOracle: address(priceOracle)
}),
deployments
IntentGatewayV2 implementation = new IntentGatewayV2{salt: salt}(admin);

bytes memory initData = abi.encodeCall(
IntentGatewayV2.initialize,
Comment thread
seunlanlege marked this conversation as resolved.
(
Params({
host: HOST_ADDRESS,
dispatcher: config.get("CALL_DISPATCHER").toAddress(),
solverSelection: config.get("7702").toBool(),
surplusShareBps: 5_000, // 50%
protocolFeeBps: 30, // 0.3%
priceOracle: priceOracle
})
)
);
ERC1967Proxy proxy = new ERC1967Proxy{salt: salt}(address(implementation), initData);
IntentGatewayV2 intentGateway = IntentGatewayV2(payable(address(proxy)));

SolverAccount solverAccount = new SolverAccount{salt: salt}(address(intentGateway));

vm.stopBroadcast();

console.log("IntentGateway implementation deployed at:", address(implementation));
console.log("IntentGateway proxy deployed at:", address(intentGateway));
console.log("SolverAccount deployed at:", address(solverAccount));

config.set("INTENT_GATEWAY_V2", address(intentGateway));
config.set("SOLVER_ACCOUNT", address(solverAccount));
config.set("PRICE_ORACLE", address(priceOracle));
Expand Down
41 changes: 21 additions & 20 deletions evm/src/apps/IntentGatewayV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {IDispatcher} from "@hyperbridge/core/interfaces/IDispatcher.sol";
import {IIntentPriceOracle} from "@hyperbridge/core/apps/IntentPriceOracle.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IUniswapV2Router02} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
Expand Down Expand Up @@ -57,16 +58,24 @@ import {
* \ /
* IntentGatewayV2
*/
contract IntentGatewayV2 is IntrinsicIntents, ExtrinsicIntents, ReentrancyGuardTransient {
contract IntentGatewayV2 is IntrinsicIntents, ExtrinsicIntents, ReentrancyGuardTransient, Initializable {
using SafeERC20 for IERC20;

/**
* @dev Initializes the EIP-712 domain with name "IntentGateway" and version "2".
* Sets the initial admin who has one-time authority to call `setParams`.
* @param admin The address that will have permission to set initial parameters.
* @dev Owner authorized to call `initialize` once on the proxy. Immutable (zero storage
* slots), so it must be byte-identical across chains or the deterministic proxy address diverges.
*/
constructor(address admin) EIP712("IntentGateway", "2") {
_admin = admin;
address private immutable _owner;

/**
* @dev Initializes the EIP-712 domain with name "IntentGateway" and version "2",
* records the owner authorized to initialize the proxy, and locks this raw
* implementation so it can never be initialized directly.
* @param owner The address permitted to call `initialize` on the proxy.
*/
constructor(address owner) EIP712("IntentGateway", "2") {
_owner = owner;
_disableInitializers();
}

/**
Expand All @@ -87,25 +96,17 @@ contract IntentGatewayV2 is IntrinsicIntents, ExtrinsicIntents, ReentrancyGuardT
}

/**
* @dev One-time parameter initialization. Can only be called by the admin set in
* the constructor. After successful execution, the admin is burned (set to address(0)),
* preventing any further calls.
*
* Subsequent parameter updates must come through Hyperbridge governance via the `onAccept` callback.
* @dev One-time initialization run against the proxy's storage, typically atomically via the
* proxy's init data. The `initializer` modifier caps it to a single call and the owner gate
* restricts who may call it. Cross-chain peers resolve to `address(this)` (the shared gateway
* address), and later config changes go through `onAccept` governance.
*
* @param p The initial gateway configuration parameters.
* @param deployments The initial gateway cross-chain peers
*/
function init(Params memory p, Deployment[] memory deployments) public {
if (msg.sender != _admin) revert Unauthorized();

uint256 deploymentsLength = deployments.length;
for (uint256 i = 0; i < deploymentsLength; i++) {
_addDeployment(deployments[i]);
}
function initialize(Params memory p) public initializer {
if (msg.sender != _owner) revert Unauthorized();
_validateParams(p);
_params = p;
_admin = address(0);
}

/**
Expand Down
6 changes: 6 additions & 0 deletions evm/src/apps/intentsv2/ExtrinsicIntents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from "@hyperbridge/core/apps/IntentGatewayV2.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";


/**
Expand Down Expand Up @@ -280,6 +281,8 @@ abstract contract ExtrinsicIntents is IntentsBase, HyperApp {
* protocol fees. Only Hyperbridge may dispatch this request.
* - SweepDust: Transfers accumulated protocol dust to a specified beneficiary.
* Only Hyperbridge may dispatch this request.
* - UpgradeContract: Points the ERC-1967 proxy at a new implementation, optionally
* running migration calldata atomically. Only Hyperbridge may dispatch this request.
*
* @param incoming The incoming post request from Hyperbridge.
*/
Expand All @@ -299,6 +302,9 @@ abstract contract ExtrinsicIntents is IntentsBase, HyperApp {
_updateParams(abi.decode(incoming.request.body[1:], (ParamsUpdate)));
} else if (kind == RequestKind.SweepDust) {
_sweepDust(abi.decode(incoming.request.body[1:], (SweepDust)));
} else if (kind == RequestKind.UpgradeContract) {
(address newImpl, bytes memory initData) = abi.decode(incoming.request.body[1:], (address, bytes));
ERC1967Utils.upgradeToAndCall(newImpl, initData);
}
}

Expand Down
23 changes: 9 additions & 14 deletions evm/src/apps/intentsv2/IntentsBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ abstract contract IntentsBase is EIP712 {
/**
* @dev Refund escrowed tokens to the user after a cross-chain cancellation.
*/
RefundEscrow
RefundEscrow,
/**
* @dev Upgrade the gateway implementation behind its ERC-1967 proxy.
*/
UpgradeContract
}

/**
Expand All @@ -108,12 +112,6 @@ abstract contract IntentsBase is EIP712 {
*/
Params internal _params;

/**
* @dev One-time admin address set in the constructor. Has permission to call
* `setParams` exactly once, after which it is burned to address(0).
*/
address internal _admin;

/**
* @dev Maps (commitment, token address) to the escrowed amount for that token.
* Decremented as tokens are released via fills or refunds.
Expand All @@ -138,6 +136,9 @@ abstract contract IntentsBase is EIP712 {
*/
mapping(bytes32 => uint256) public _destinationProtocolFees;

/// @dev Appended last to preserve existing storage slots.
bool public _paused;

/**
* @dev Thrown when the caller is not authorized to perform the action.
*/
Expand Down Expand Up @@ -183,11 +184,6 @@ abstract contract IntentsBase is EIP712 {
*/
error UnknownOrder();

/*
* @dev Thrown when the cross-chain peer is unknown.
*/
error UnknownInstance();

/*
* @dev Thrown when a solver attempts to partially fill an order that carries
* output calldata. Such orders must be filled completely in a single fill.
Expand Down Expand Up @@ -320,8 +316,7 @@ abstract contract IntentsBase is EIP712 {
*/
function _instance(bytes calldata stateMachineId) internal view returns (address) {
address gateway = _instances[keccak256(stateMachineId)];
if (gateway == address(0)) revert UnknownInstance();
return gateway;
return gateway == address(0) ? address(this) : gateway;
Comment thread
seunlanlege marked this conversation as resolved.
Outdated
}

/**
Expand Down
Loading
Loading