From 00016a7b6dc3032c2e21275737a5f54137ff2e95 Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:28:58 +0200 Subject: [PATCH 1/3] Clean up interfaces --- contracts/scripts/DeployLocal.sol | 6 +- contracts/scripts/FundAgent.sol | 7 +- contracts/scripts/UpgradeShell.sol | 1 - contracts/scripts/westend/UpgradeShell.sol | 1 - contracts/src/Functions.sol | 20 +- contracts/src/Gateway.sol | 45 ++-- contracts/src/Initializer.sol | 1 - contracts/src/Shell.sol | 10 +- contracts/src/Types.sol | 2 + contracts/src/interfaces/IGateway.sol | 226 --------------------- contracts/src/interfaces/IGatewayBase.sol | 46 +++++ contracts/src/interfaces/IUpgradable.sol | 2 + contracts/src/v1/Calls.sol | 12 +- contracts/src/v1/Handlers.sol | 30 +-- contracts/src/v1/IGateway.sol | 117 +++++++++++ contracts/src/v2/Calls.sol | 34 +--- contracts/src/v2/Handlers.sol | 5 +- contracts/src/v2/IGateway.sol | 84 ++++++++ contracts/test/ForkUpgrade.t.sol | 7 +- contracts/test/GatewayV1.t.sol | 197 +++++++++--------- contracts/test/GatewayV2.t.sol | 20 +- contracts/test/Shell.t.sol | 1 - 22 files changed, 448 insertions(+), 426 deletions(-) delete mode 100644 contracts/src/interfaces/IGateway.sol create mode 100644 contracts/src/interfaces/IGatewayBase.sol create mode 100644 contracts/src/v1/IGateway.sol create mode 100644 contracts/src/v2/IGateway.sol diff --git a/contracts/scripts/DeployLocal.sol b/contracts/scripts/DeployLocal.sol index a5bce42442..fef2e9f43e 100644 --- a/contracts/scripts/DeployLocal.sol +++ b/contracts/scripts/DeployLocal.sol @@ -6,7 +6,7 @@ import {WETH9} from "canonical-weth/WETH9.sol"; import {Script} from "forge-std/Script.sol"; import {BeefyClient} from "../src/BeefyClient.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; +import {IGatewayV2} from "../src/v2/IGateway.sol"; import {GatewayProxy} from "../src/GatewayProxy.sol"; import {Gateway} from "../src/Gateway.sol"; import {MockGatewayV2} from "../test/mocks/MockGatewayV2.sol"; @@ -99,9 +99,9 @@ contract DeployLocal is Script { uint256 initialDeposit = vm.envUint("BRIDGE_HUB_INITIAL_DEPOSIT"); address bridgeHubAgent = - IGateway(address(gateway)).agentOf(Constants.BRIDGE_HUB_AGENT_ID); + IGatewayV2(address(gateway)).agentOf(Constants.BRIDGE_HUB_AGENT_ID); address assetHubAgent = - IGateway(address(gateway)).agentOf(Constants.ASSET_HUB_AGENT_ID); + IGatewayV2(address(gateway)).agentOf(Constants.ASSET_HUB_AGENT_ID); payable(bridgeHubAgent).safeNativeTransfer(initialDeposit); payable(assetHubAgent).safeNativeTransfer(initialDeposit); diff --git a/contracts/scripts/FundAgent.sol b/contracts/scripts/FundAgent.sol index 37eedceb6b..23779a9edc 100644 --- a/contracts/scripts/FundAgent.sol +++ b/contracts/scripts/FundAgent.sol @@ -5,8 +5,7 @@ pragma solidity 0.8.25; import {WETH9} from "canonical-weth/WETH9.sol"; import {Script} from "forge-std/Script.sol"; import {BeefyClient} from "../src/BeefyClient.sol"; - -import {IGateway} from "../src/interfaces/IGateway.sol"; +import {IGatewayV2} from "../src/v2/IGateway.sol"; import {GatewayProxy} from "../src/GatewayProxy.sol"; import {Gateway} from "../src/Gateway.sol"; import {Agent} from "../src/Agent.sol"; @@ -32,8 +31,8 @@ contract FundAgent is Script { bytes32 bridgeHubAgentID = vm.envBytes32("BRIDGE_HUB_AGENT_ID"); bytes32 assetHubAgentID = vm.envBytes32("ASSET_HUB_AGENT_ID"); - address bridgeHubAgent = IGateway(gatewayAddress).agentOf(bridgeHubAgentID); - address assetHubAgent = IGateway(gatewayAddress).agentOf(assetHubAgentID); + address bridgeHubAgent = IGatewayV2(gatewayAddress).agentOf(bridgeHubAgentID); + address assetHubAgent = IGatewayV2(gatewayAddress).agentOf(assetHubAgentID); payable(bridgeHubAgent).safeNativeTransfer(initialDeposit); payable(assetHubAgent).safeNativeTransfer(initialDeposit); diff --git a/contracts/scripts/UpgradeShell.sol b/contracts/scripts/UpgradeShell.sol index 9ee7a9a4c3..fbf152c475 100644 --- a/contracts/scripts/UpgradeShell.sol +++ b/contracts/scripts/UpgradeShell.sol @@ -6,7 +6,6 @@ import {WETH9} from "canonical-weth/WETH9.sol"; import {Script} from "forge-std/Script.sol"; import {BeefyClient} from "../src/BeefyClient.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; import {IShell} from "../src/interfaces/IShell.sol"; import {GatewayProxy} from "../src/GatewayProxy.sol"; import {Gateway} from "../src/Gateway.sol"; diff --git a/contracts/scripts/westend/UpgradeShell.sol b/contracts/scripts/westend/UpgradeShell.sol index 5918e8b0ff..44cc40b184 100644 --- a/contracts/scripts/westend/UpgradeShell.sol +++ b/contracts/scripts/westend/UpgradeShell.sol @@ -8,7 +8,6 @@ import {stdJson} from "forge-std/StdJson.sol"; import {UD60x18, ud60x18} from "prb/math/src/UD60x18.sol"; import {BeefyClient} from "../../src/BeefyClient.sol"; -import {IGateway} from "../../src/interfaces/IGateway.sol"; import {IShell} from "../../src/interfaces/IShell.sol"; import {GatewayProxy} from "../../src/GatewayProxy.sol"; import {Gateway} from "../../src/Gateway.sol"; diff --git a/contracts/src/Functions.sol b/contracts/src/Functions.sol index 991bf4896d..2c4814762a 100644 --- a/contracts/src/Functions.sol +++ b/contracts/src/Functions.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.25; import {IERC20} from "./interfaces/IERC20.sol"; -import {IGateway} from "./interfaces/IGateway.sol"; import {SafeTokenTransferFrom} from "./utils/SafeTransfer.sol"; import {Agent} from "./Agent.sol"; import {Call} from "./utils/Call.sol"; @@ -14,6 +13,9 @@ import {AssetsStorage} from "./storage/AssetsStorage.sol"; import {Token} from "./Token.sol"; import {TokenInfo} from "./types/Common.sol"; import {ChannelID, Channel} from "./v1/Types.sol"; +import {IGatewayBase} from "./interfaces/IGatewayBase.sol"; +import {IGatewayV1} from "./v1/IGateway.sol"; +import {IGatewayV2} from "./v2/IGateway.sol"; library Functions { using Address for address; @@ -27,7 +29,7 @@ library Functions { function ensureAgent(bytes32 agentID) internal view returns (address agent) { agent = CoreStorage.layout().agents[agentID]; if (agent == address(0)) { - revert AgentDoesNotExist(); + revert IGatewayBase.AgentDoesNotExist(); } } @@ -40,7 +42,7 @@ library Functions { ch = CoreStorage.layout().channels[channelID]; // A channel always has an agent specified. if (ch.agent == address(0)) { - revert ChannelDoesNotExist(); + revert IGatewayV1.ChannelDoesNotExist(); } } @@ -78,9 +80,9 @@ library Functions { if (agent == address(0)) { agent = address(new Agent(origin)); core.agents[origin] = agent; - emit IGateway.AgentCreated(origin, agent); + emit IGatewayBase.AgentCreated(origin, agent); } else { - revert IGateway.AgentAlreadyCreated(); + revert IGatewayBase.AgentAlreadyCreated(); } } @@ -108,7 +110,7 @@ library Functions { abi.encodeCall(AgentExecutor.transferToken, (token, recipient, amount)); (bool success,) = Agent(payable(agent)).invoke(executor, call); if (!success) { - revert IGateway.TokenTransferFailed(); + revert IGatewayBase.TokenTransferFailed(); } } @@ -120,7 +122,7 @@ library Functions { ) external { AssetsStorage.Layout storage $ = AssetsStorage.layout(); if ($.tokenAddressOf[foreignTokenID] != address(0)) { - revert IGateway.TokenAlreadyRegistered(); + revert IGatewayBase.TokenAlreadyRegistered(); } Token token = new Token(name, symbol, decimals); TokenInfo memory info = @@ -129,7 +131,7 @@ library Functions { $.tokenAddressOf[foreignTokenID] = address(token); $.tokenRegistry[address(token)] = info; - emit IGateway.ForeignTokenRegistered(foreignTokenID, address(token)); + emit IGatewayBase.ForeignTokenRegistered(foreignTokenID, address(token)); } function mintForeignToken(bytes32 foreignTokenID, address recipient, uint128 amount) @@ -143,7 +145,7 @@ library Functions { function _ensureTokenAddressOf(bytes32 tokenID) internal view returns (address) { AssetsStorage.Layout storage $ = AssetsStorage.layout(); if ($.tokenAddressOf[tokenID] == address(0)) { - revert IGateway.TokenNotRegistered(); + revert IGatewayBase.TokenNotRegistered(); } return $.tokenAddressOf[tokenID]; } diff --git a/contracts/src/Gateway.sol b/contracts/src/Gateway.sol index 794d7c3b4d..7d18be0e96 100644 --- a/contracts/src/Gateway.sol +++ b/contracts/src/Gateway.sol @@ -8,6 +8,7 @@ import {Initializer} from "./Initializer.sol"; import {AgentExecutor} from "./AgentExecutor.sol"; import {Agent} from "./Agent.sol"; import {MultiAddress} from "./MultiAddress.sol"; +import {IGatewayBase} from "./interfaces/IGatewayBase.sol"; import { OperatingMode, ParaID, @@ -22,10 +23,11 @@ import { CallsV1, HandlersV1, CallsV2, - HandlersV2 + HandlersV2, + IGatewayV1, + IGatewayV2 } from "./Types.sol"; import {Upgrade} from "./Upgrade.sol"; -import {IGateway} from "./interfaces/IGateway.sol"; import {IInitializable} from "./interfaces/IInitializable.sol"; import {IUpgradable} from "./interfaces/IUpgradable.sol"; import {ERC1967} from "./utils/ERC1967.sol"; @@ -44,7 +46,7 @@ import {OperatorStorage} from "./storage/OperatorStorage.sol"; import {UD60x18, ud60x18, convert} from "prb/math/src/UD60x18.sol"; -contract Gateway is IGateway, IInitializable, IUpgradable { +contract Gateway is IGatewayBase, IGatewayV1, IGatewayV2, IInitializable, IUpgradable { using Address for address; using SafeNativeTransfer for address payable; @@ -61,7 +63,7 @@ contract Gateway is IGateway, IInitializable, IUpgradable { // Message handlers can only be dispatched by the gateway itself modifier onlySelf() { if (msg.sender != address(this)) { - revert IGateway.Unauthorized(); + revert IGatewayBase.Unauthorized(); } _; } @@ -132,7 +134,7 @@ contract Gateway is IGateway, IInitializable, IUpgradable { // Ensure this message is not being replayed if (message.nonce != channel.inboundNonce + 1) { - revert IGateway.InvalidNonce(); + revert IGatewayBase.InvalidNonce(); } // Increment nonce for origin. @@ -146,14 +148,14 @@ contract Gateway is IGateway, IInitializable, IUpgradable { // Verify that the commitment is included in a parachain header finalized by BEEFY. if (!_verifyCommitment(commitment, headerProof)) { - revert IGateway.InvalidProof(); + revert IGatewayBase.InvalidProof(); } // Make sure relayers provide enough gas so that inner message dispatch // does not run out of gas. uint256 maxDispatchGas = message.maxDispatchGas; if (gasleft() < maxDispatchGas + DISPATCH_OVERHEAD_GAS_V1) { - revert IGateway.NotEnoughGas(); + revert IGatewayBase.NotEnoughGas(); } bool success = true; @@ -239,12 +241,17 @@ contract Gateway is IGateway, IInitializable, IUpgradable { ); } - emit IGateway.InboundMessageDispatched( + emit IGatewayV1.InboundMessageDispatched( message.channelID, message.nonce, message.id, success ); } - function operatingMode() external view returns (OperatingMode) { + function operatingMode() + external + view + override(IGatewayV1, IGatewayV2) + returns (OperatingMode) + { return CoreStorage.layout().mode; } @@ -264,7 +271,12 @@ contract Gateway is IGateway, IInitializable, IUpgradable { return CallsV1.channelNoncesOf(channelID); } - function agentOf(bytes32 agentID) external view returns (address) { + function agentOf(bytes32 agentID) + external + view + override(IGatewayV1, IGatewayV2) + returns (address) + { return Functions.ensureAgent(agentID); } @@ -276,7 +288,12 @@ contract Gateway is IGateway, IInitializable, IUpgradable { return ERC1967.load(); } - function isTokenRegistered(address token) external view returns (bool) { + function isTokenRegistered(address token) + external + view + override(IGatewayV1, IGatewayV2) + returns (bool) + { return CallsV1.isTokenRegistered(token); } @@ -427,7 +444,7 @@ contract Gateway is IGateway, IInitializable, IUpgradable { bytes32 leafHash = keccak256(abi.encode(message)); if ($.inboundNonce.get(message.nonce)) { - revert IGateway.InvalidNonce(); + revert IGatewayBase.InvalidNonce(); } $.inboundNonce.set(message.nonce); @@ -437,12 +454,12 @@ contract Gateway is IGateway, IInitializable, IUpgradable { // Verify that the commitment is included in a parachain header finalized by BEEFY. if (!_verifyCommitment(commitment, headerProof)) { - revert IGateway.InvalidProof(); + revert IGatewayBase.InvalidProof(); } bool success = v2_dispatch(message); - emit IGateway.InboundMessageDispatched(message.nonce, success, rewardAddress); + emit IGatewayV2.InboundMessageDispatched(message.nonce, success, rewardAddress); } function v2_dispatch(InboundMessageV2 calldata message) internal returns (bool) { diff --git a/contracts/src/Initializer.sol b/contracts/src/Initializer.sol index 0ce8a11731..7be8bfa023 100644 --- a/contracts/src/Initializer.sol +++ b/contracts/src/Initializer.sol @@ -16,7 +16,6 @@ import { ChannelID } from "./Types.sol"; import {Upgrade} from "./Upgrade.sol"; -import {IGateway} from "./interfaces/IGateway.sol"; import {IInitializable} from "./interfaces/IInitializable.sol"; import {IUpgradable} from "./interfaces/IUpgradable.sol"; import {ERC1967} from "./utils/ERC1967.sol"; diff --git a/contracts/src/Shell.sol b/contracts/src/Shell.sol index 146632afec..1b8fbfc99a 100644 --- a/contracts/src/Shell.sol +++ b/contracts/src/Shell.sol @@ -15,7 +15,11 @@ contract Shell is IShell, IUpgradable, IInitializable { OPERATOR = _operator; } - function upgrade(address impl, bytes32 implCodeHash, bytes calldata initializerParams) external { + function upgrade( + address impl, + bytes32 implCodeHash, + bytes calldata initializerParams + ) external { if (msg.sender != OPERATOR) { revert Unauthorized(); } @@ -32,4 +36,8 @@ contract Shell is IShell, IUpgradable, IInitializable { function operator() external view returns (address) { return OPERATOR; } + + function implementation() public view returns (address) { + return ERC1967.load(); + } } diff --git a/contracts/src/Types.sol b/contracts/src/Types.sol index 3a51172696..a0a31f07f8 100644 --- a/contracts/src/Types.sol +++ b/contracts/src/Types.sol @@ -13,6 +13,7 @@ import { } from "./v1/Types.sol"; import {CallsV1} from "./v1/Calls.sol"; import {HandlersV1} from "./v1/Handlers.sol"; +import {IGatewayV1} from "./v1/IGateway.sol"; import { InboundMessage as InboundMessageV2, @@ -21,3 +22,4 @@ import { } from "./v2/Types.sol"; import {CallsV2} from "./v2/Calls.sol"; import {HandlersV2} from "./v2/Handlers.sol"; +import {IGatewayV2} from "./v2/IGateway.sol"; diff --git a/contracts/src/interfaces/IGateway.sol b/contracts/src/interfaces/IGateway.sol deleted file mode 100644 index 9264f4959e..0000000000 --- a/contracts/src/interfaces/IGateway.sol +++ /dev/null @@ -1,226 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -pragma solidity 0.8.25; - -import {MultiAddress} from "../MultiAddress.sol"; -import { - OperatingMode, - InboundMessage as InboundMessageV1, - ParaID, - ChannelID -} from "../v1/Types.sol"; -import {InboundMessage as InboundMessageV2} from "../v2/Types.sol"; -import {Verification} from "../Verification.sol"; -import {UD60x18} from "prb/math/src/UD60x18.sol"; - -interface IGateway { - error InvalidToken(); - error InvalidAmount(); - error InvalidDestination(); - error TokenNotRegistered(); - error Unsupported(); - error InvalidDestinationFee(); - error AgentDoesNotExist(); - error TokenAlreadyRegistered(); - error TokenMintFailed(); - error TokenTransferFailed(); - error InvalidProof(); - error InvalidNonce(); - error NotEnoughGas(); - error FeePaymentToLow(); - error InvalidFee(); - error Unauthorized(); - error Disabled(); - error AgentAlreadyCreated(); - error ChannelAlreadyCreated(); - error ChannelDoesNotExist(); - error InvalidChannelUpdate(); - error AgentExecutionFailed(bytes returndata); - error InvalidAgentExecutionPayload(); - error InvalidConstructorParams(); - error AlreadyInitialized(); - error TooManyAssets(); - - /** - * Events - */ - - // V1: Emitted when inbound message has been dispatched - event InboundMessageDispatched( - ChannelID indexed channelID, - uint64 nonce, - bytes32 indexed messageID, - bool success - ); - - // V2: Emitted when inbound message has been dispatched - event InboundMessageDispatched( - uint64 indexed nonce, bool success, bytes32 indexed rewardAddress - ); - - // Emitted when an outbound message has been accepted for delivery to a Polkadot parachain - event OutboundMessageAccepted( - ChannelID indexed channelID, - uint64 nonce, - bytes32 indexed messageID, - bytes payload - ); - - // v2 Emitted when an outbound message has been accepted for delivery to a Polkadot parachain - event OutboundMessageAccepted(uint64 nonce, uint256 reward, bytes payload); - - // Emitted when an agent has been created for a consensus system on Polkadot - event AgentCreated(bytes32 agentID, address agent); - - // Emitted when a channel has been created - event ChannelCreated(ChannelID indexed channelID); - - // Emitted when a channel has been updated - event ChannelUpdated(ChannelID indexed channelID); - - // Emitted when the operating mode is changed - event OperatingModeChanged(OperatingMode mode); - - // Emitted when pricing params updated - event PricingParametersChanged(); - - // Emitted when funds are withdrawn from an agent - event AgentFundsWithdrawn( - bytes32 indexed agentID, address indexed recipient, uint256 amount - ); - - // Emitted when foreign token from polkadot registed - event ForeignTokenRegistered(bytes32 indexed tokenID, address token); - - /** - * Getters - */ - function operatingMode() external view returns (OperatingMode); - - function channelOperatingModeOf(ChannelID channelID) - external - view - returns (OperatingMode); - - function channelNoncesOf(ChannelID channelID) - external - view - returns (uint64, uint64); - - function agentOf(bytes32 agentID) external view returns (address); - - function pricingParameters() external view returns (UD60x18, uint128); - - function implementation() external view returns (address); - - /** - * Messaging - */ - - // Submit a message from a Polkadot network - function submitV1( - InboundMessageV1 calldata message, - bytes32[] calldata leafProof, - Verification.Proof calldata headerProof - ) external; - - /** - * Token Transfers - */ - - // @dev Emitted when the fees updated - event TokenTransferFeesChanged(); - - /// @dev Emitted once the funds are locked and an outbound message is successfully queued. - event TokenSent( - address indexed token, - address indexed sender, - ParaID indexed destinationChain, - MultiAddress destinationAddress, - uint128 amount - ); - - /// @dev Emitted when a command is sent to register a new wrapped token on AssetHub - event TokenRegistrationSent(address token); - - /// @dev Check whether a token is registered - function isTokenRegistered(address token) external view returns (bool); - - /// @dev Quote a fee in Ether for registering a token, covering - /// 1. Delivery costs to BridgeHub - /// 2. XCM Execution costs on AssetHub - function quoteRegisterTokenFee() external view returns (uint256); - - /// @dev Register an ERC20 token and create a wrapped derivative on AssetHub in the `ForeignAssets` pallet. - function registerToken(address token) external payable; - - /// @dev Quote a fee in Ether for sending a token - /// 1. Delivery costs to BridgeHub - /// 2. XCM execution costs on destinationChain - function quoteSendTokenFee( - address token, - ParaID destinationChain, - uint128 destinationFee - ) external view returns (uint256); - - /// @dev Send ERC20 tokens to parachain `destinationChain` and deposit into account `destinationAddress` - function sendToken( - address token, - ParaID destinationChain, - MultiAddress calldata destinationAddress, - uint128 destinationFee, - uint128 amount - ) external payable; - - // V2 - - // Submit a message for verification and dispatch - function v2_submit( - InboundMessageV2 calldata message, - bytes32[] calldata leafProof, - Verification.Proof calldata headerProof, - bytes32 rewardAddress - ) external; - - // Send an XCM with arbitrary assets to Polkadot Asset Hub - // - // Params: - // * `xcm` (bytes): SCALE-encoded VersionedXcm message - // * `assets` (bytes[]): Array of asset specs, constrained to maximum of eight. - // - // Supported asset specs: - // * ERC20: abi.encode(0, tokenAddress, value) - // - // On Asset Hub, the assets will be received into the assets holding register. - // - // The `xcm` should contain the necessary instructions to: - // 1. Pay XCM execution fees for `xcm`, either from assets in holding, - // or from the sovereign account of `msg.sender`. - // 2. Handle the assets in holding, either depositing them into - // some account, or forwarding them to another destination. - // - // To incentivize message delivery, some amount of ether must be passed and should - // at least cover the total cost of delivery to Polkadot. This ether be sent across - // the bridge as WETH, and given to the relayer as compensation and incentivization. - // - function v2_sendMessage( - bytes calldata xcm, - bytes[] calldata assets, - bytes calldata claimer - ) external payable; - - // Register Ethereum-native token on AHP, using `xcmFeeAHP` of `msg.value` - // to pay for execution on AHP. - function v2_registerToken(address token, uint128 xcmFeeAHP) external payable; - - // Register Ethereum-native token on AHK, using `xcmFeeAHP` and `xcmFeeAHK` - // of `msg.value` to pay for execution on AHP and AHK respectively. - function v2_registerTokenOnKusama( - address token, - uint128 xcmFeeAHP, - uint128 xcmFeeAHK - ) external payable; - - // Check if an inbound message was previously accepted and dispatched - function v2_isDispatched(uint64 nonce) external returns (bool); -} diff --git a/contracts/src/interfaces/IGatewayBase.sol b/contracts/src/interfaces/IGatewayBase.sol new file mode 100644 index 0000000000..14e0fad6e0 --- /dev/null +++ b/contracts/src/interfaces/IGatewayBase.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +pragma solidity 0.8.25; + +import {OperatingMode} from "../types/Common.sol"; + +interface IGatewayBase { + error InvalidToken(); + error InvalidAmount(); + error InvalidDestination(); + error TokenNotRegistered(); + error Unsupported(); + error InvalidDestinationFee(); + error AgentDoesNotExist(); + error TokenAlreadyRegistered(); + error TokenMintFailed(); + error TokenTransferFailed(); + error InvalidProof(); + error InvalidNonce(); + error NotEnoughGas(); + error FeePaymentToLow(); + error Unauthorized(); + error Disabled(); + error AgentAlreadyCreated(); + error AgentExecutionFailed(bytes returndata); + error InvalidAgentExecutionPayload(); + error InvalidConstructorParams(); + error AlreadyInitialized(); + error TooManyAssets(); + + /** + * Getters + */ + + // Emitted when an agent has been created for a consensus system on Polkadot + event AgentCreated(bytes32 agentID, address agent); + + // Emitted when the operating mode is changed + event OperatingModeChanged(OperatingMode mode); + + // Emitted when foreign token from polkadot registed + event ForeignTokenRegistered(bytes32 indexed tokenID, address token); + + /// @dev Emitted when a command is sent to register a new wrapped token on AssetHub + event TokenRegistrationSent(address token); +} diff --git a/contracts/src/interfaces/IUpgradable.sol b/contracts/src/interfaces/IUpgradable.sol index beaf62c1d2..9a2deb05c9 100644 --- a/contracts/src/interfaces/IUpgradable.sol +++ b/contracts/src/interfaces/IUpgradable.sol @@ -10,4 +10,6 @@ interface IUpgradable { // The implementation contract was upgraded event Upgraded(address indexed implementation); + + function implementation() external view returns (address); } diff --git a/contracts/src/v1/Calls.sol b/contracts/src/v1/Calls.sol index 2c37be4d80..c8ffd8cddc 100644 --- a/contracts/src/v1/Calls.sol +++ b/contracts/src/v1/Calls.sol @@ -3,8 +3,6 @@ pragma solidity 0.8.25; import {IERC20} from "../interfaces/IERC20.sol"; -import {IGateway} from "../interfaces/IGateway.sol"; - import {SafeNativeTransfer, SafeTokenTransferFrom} from "../utils/SafeTransfer.sol"; import {AssetsStorage, TokenInfo} from "../storage/AssetsStorage.sol"; @@ -28,6 +26,8 @@ import { Ticket, Costs } from "./Types.sol"; +import {IGatewayBase} from "../interfaces/IGatewayBase.sol"; +import {IGatewayV1} from "./IGateway.sol"; import {UD60x18, ud60x18, convert} from "prb/math/src/UD60x18.sol"; @@ -88,7 +88,7 @@ library CallsV1 { payload: SubstrateTypes.RegisterToken(token, $.assetHubCreateAssetFee) }); - emit IGateway.TokenRegistrationSent(token); + emit IGatewayBase.TokenRegistrationSent(token); _submitOutbound(ticket); } @@ -247,7 +247,7 @@ library CallsV1 { // Generate a unique ID for this message bytes32 messageID = keccak256(abi.encodePacked(channelID, channel.outboundNonce)); - emit IGateway.OutboundMessageAccepted( + emit IGatewayV1.OutboundMessageAccepted( channelID, channel.outboundNonce, messageID, ticket.payload ); } @@ -347,7 +347,7 @@ library CallsV1 { revert Unsupported(); } } - emit IGateway.TokenSent( + emit IGatewayV1.TokenSent( token, sender, destinationChain, destinationAddress, amount ); } @@ -383,7 +383,7 @@ library CallsV1 { revert Unsupported(); } - emit IGateway.TokenSent( + emit IGatewayV1.TokenSent( token, sender, destinationChain, destinationAddress, amount ); } diff --git a/contracts/src/v1/Handlers.sol b/contracts/src/v1/Handlers.sol index 2c976da6c6..95a68662d6 100644 --- a/contracts/src/v1/Handlers.sol +++ b/contracts/src/v1/Handlers.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.25; import {IERC20} from "../interfaces/IERC20.sol"; -import {IGateway} from "../interfaces/IGateway.sol"; import {SafeTokenTransferFrom} from "../utils/SafeTransfer.sol"; @@ -19,6 +18,8 @@ import {Call} from "../utils/Call.sol"; import {Token} from "../Token.sol"; import {Upgrade} from "../Upgrade.sol"; import {Functions} from "../Functions.sol"; +import {IGatewayBase} from "../interfaces/IGatewayBase.sol"; +import {IGatewayV1} from "./IGateway.sol"; import { ParaID, @@ -43,30 +44,13 @@ library HandlersV1 { using Address for address; using SafeTokenTransferFrom for IERC20; - error InvalidProof(); - error InvalidNonce(); - error NotEnoughGas(); - error FeePaymentToLow(); - error Unauthorized(); - error Disabled(); - error AgentAlreadyCreated(); - error AgentDoesNotExist(); - error ChannelAlreadyCreated(); - error ChannelDoesNotExist(); - error InvalidChannelUpdate(); - error AgentExecutionFailed(bytes returndata); - error InvalidAgentExecutionPayload(); - error InvalidConstructorParams(); - error AlreadyInitialized(); - error TokenNotRegistered(); - function agentExecute(address executor, bytes calldata data) external { AgentExecuteParams memory params = abi.decode(data, (AgentExecuteParams)); address agent = Functions.ensureAgent(params.agentID); if (params.payload.length == 0) { - revert InvalidAgentExecutionPayload(); + revert IGatewayBase.InvalidAgentExecutionPayload(); } (AgentExecuteCommand command, bytes memory commandParams) = @@ -95,7 +79,7 @@ library HandlersV1 { CoreStorage.Layout storage $ = CoreStorage.layout(); SetOperatingModeParams memory params = abi.decode(data, (SetOperatingModeParams)); $.mode = params.mode; - emit IGateway.OperatingModeChanged(params.mode); + emit IGatewayBase.OperatingModeChanged(params.mode); } // @dev Transfer funds from an agent to a recipient account @@ -108,7 +92,7 @@ library HandlersV1 { Functions.withdrawEther( executor, agent, payable(params.recipient), params.amount ); - emit IGateway.AgentFundsWithdrawn( + emit IGatewayV1.AgentFundsWithdrawn( params.agentID, params.recipient, params.amount ); } @@ -121,7 +105,7 @@ library HandlersV1 { $.assetHubCreateAssetFee = params.assetHubCreateAssetFee; $.assetHubReserveTransferFee = params.assetHubReserveTransferFee; $.registerTokenFee = params.registerTokenFee; - emit IGateway.TokenTransferFeesChanged(); + emit IGatewayV1.TokenTransferFeesChanged(); } // @dev Set pricing params of the gateway @@ -132,7 +116,7 @@ library HandlersV1 { pricing.exchangeRate = params.exchangeRate; pricing.deliveryCost = params.deliveryCost; pricing.multiplier = params.multiplier; - emit IGateway.PricingParametersChanged(); + emit IGatewayV1.PricingParametersChanged(); } // @dev Register a new fungible Polkadot token for an agent diff --git a/contracts/src/v1/IGateway.sol b/contracts/src/v1/IGateway.sol new file mode 100644 index 0000000000..1f5c45944c --- /dev/null +++ b/contracts/src/v1/IGateway.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +pragma solidity 0.8.25; + +import {MultiAddress} from "../MultiAddress.sol"; +import {OperatingMode, InboundMessage, ParaID, ChannelID} from "./Types.sol"; +import {Verification} from "../Verification.sol"; +import {UD60x18} from "prb/math/src/UD60x18.sol"; + +interface IGatewayV1 { + error ChannelAlreadyCreated(); + error ChannelDoesNotExist(); + error InvalidChannelUpdate(); + + /** + * Events + */ + + // V1: Emitted when inbound message has been dispatched + event InboundMessageDispatched( + ChannelID indexed channelID, + uint64 nonce, + bytes32 indexed messageID, + bool success + ); + + // Emitted when an outbound message has been accepted for delivery to a Polkadot parachain + event OutboundMessageAccepted( + ChannelID indexed channelID, + uint64 nonce, + bytes32 indexed messageID, + bytes payload + ); + + // Emitted when pricing params updated + event PricingParametersChanged(); + + // Emitted when funds are withdrawn from an agent + event AgentFundsWithdrawn( + bytes32 indexed agentID, address indexed recipient, uint256 amount + ); + + /** + * Getters + */ + function operatingMode() external view returns (OperatingMode); + + function agentOf(bytes32 agentID) external view returns (address); + + function channelOperatingModeOf(ChannelID channelID) + external + view + returns (OperatingMode); + + function channelNoncesOf(ChannelID channelID) + external + view + returns (uint64, uint64); + + function pricingParameters() external view returns (UD60x18, uint128); + + /** + * Messaging + */ + + // Submit a message from a Polkadot network + function submitV1( + InboundMessage calldata message, + bytes32[] calldata leafProof, + Verification.Proof calldata headerProof + ) external; + + /** + * Token Transfers + */ + + // @dev Emitted when the fees updated + event TokenTransferFeesChanged(); + + /// @dev Emitted once the funds are locked and an outbound message is successfully queued. + event TokenSent( + address indexed token, + address indexed sender, + ParaID indexed destinationChain, + MultiAddress destinationAddress, + uint128 amount + ); + + /// @dev Check whether a token is registered + function isTokenRegistered(address token) external view returns (bool); + + /// @dev Quote a fee in Ether for registering a token, covering + /// 1. Delivery costs to BridgeHub + /// 2. XCM Execution costs on AssetHub + function quoteRegisterTokenFee() external view returns (uint256); + + /// @dev Register an ERC20 token and create a wrapped derivative on AssetHub in the `ForeignAssets` pallet. + function registerToken(address token) external payable; + + /// @dev Quote a fee in Ether for sending a token + /// 1. Delivery costs to BridgeHub + /// 2. XCM execution costs on destinationChain + function quoteSendTokenFee( + address token, + ParaID destinationChain, + uint128 destinationFee + ) external view returns (uint256); + + /// @dev Send ERC20 tokens to parachain `destinationChain` and deposit into account `destinationAddress` + function sendToken( + address token, + ParaID destinationChain, + MultiAddress calldata destinationAddress, + uint128 destinationFee, + uint128 amount + ) external payable; +} diff --git a/contracts/src/v2/Calls.sol b/contracts/src/v2/Calls.sol index 85df1dd517..a82b4907e1 100644 --- a/contracts/src/v2/Calls.sol +++ b/contracts/src/v2/Calls.sol @@ -3,9 +3,11 @@ pragma solidity 0.8.25; import {IERC20} from "../interfaces/IERC20.sol"; -import {IGateway} from "../interfaces/IGateway.sol"; import {WETH9} from "canonical-weth/WETH9.sol"; +import {IGatewayBase} from "../interfaces/IGatewayBase.sol"; +import {IGatewayV2} from "./IGateway.sol"; + import {SafeNativeTransfer, SafeTokenTransfer} from "../utils/SafeTransfer.sol"; import {AssetsStorage, TokenInfo} from "../storage/AssetsStorage.sol"; @@ -32,24 +34,6 @@ library CallsV2 { using SafeTokenTransfer for IERC20; using SafeNativeTransfer for address payable; - error InvalidProof(); - error InvalidNonce(); - error NotEnoughGas(); - error FeePaymentToLow(); - error Unauthorized(); - error Disabled(); - error AgentAlreadyCreated(); - error AgentDoesNotExist(); - error ChannelAlreadyCreated(); - error ChannelDoesNotExist(); - error InvalidChannelUpdate(); - error AgentExecutionFailed(bytes returndata); - error InvalidAgentExecutionPayload(); - error InvalidConstructorParams(); - error AlreadyInitialized(); - error TokenNotRegistered(); - error InvalidAsset(); - address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; uint8 public constant MAX_ASSETS = 8; @@ -104,7 +88,7 @@ library CallsV2 { uint256 reward ) internal { if (assets.length > MAX_ASSETS) { - revert IGateway.TooManyAssets(); + revert IGatewayBase.TooManyAssets(); } bytes[] memory encodedAssets = new bytes[](assets.length); @@ -141,7 +125,7 @@ library CallsV2 { // Lock up the total xcm fee if (xcmFee > msg.value) { - revert IGateway.InvalidFee(); + revert IGatewayV2.InvalidFee(); } _lockEther(xcmFee); @@ -167,7 +151,7 @@ library CallsV2 { ticket.origin, ticket.assets, ticket.xcm, ticket.claimer ); - emit IGateway.OutboundMessageAccepted($.outboundNonce, ticket.reward, payload); + emit IGatewayV2.OutboundMessageAccepted($.outboundNonce, ticket.reward, payload); } // Lock wrapped ether into the AssetHub Agent @@ -181,7 +165,7 @@ library CallsV2 { function _ensureOutboundMessagingEnabled() internal view { CoreStorage.Layout storage $ = CoreStorage.layout(); if ($.mode != OperatingMode.Normal) { - revert Disabled(); + revert IGatewayBase.Disabled(); } } @@ -196,7 +180,7 @@ library CallsV2 { abi.decode(asset, (uint8, address, uint128)); return _handleAssetERC20(token, amount); } else { - revert InvalidAsset(); + revert IGatewayV2.InvalidAsset(); } } @@ -208,7 +192,7 @@ library CallsV2 { TokenInfo storage info = $.tokenRegistry[token]; if (!info.isRegistered) { - revert TokenNotRegistered(); + revert IGatewayBase.TokenNotRegistered(); } if (info.foreignID == bytes32(0)) { diff --git a/contracts/src/v2/Handlers.sol b/contracts/src/v2/Handlers.sol index 20a53adab1..d86f7ff99c 100644 --- a/contracts/src/v2/Handlers.sol +++ b/contracts/src/v2/Handlers.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.25; import {IERC20} from "../interfaces/IERC20.sol"; -import {IGateway} from "../interfaces/IGateway.sol"; import {SafeTokenTransferFrom} from "../utils/SafeTransfer.sol"; import {AssetsStorage, TokenInfo} from "../storage/AssetsStorage.sol"; import {CoreStorage} from "../storage/CoreStorage.sol"; @@ -18,6 +17,8 @@ import {Token} from "../Token.sol"; import {Upgrade} from "../Upgrade.sol"; import {Functions} from "../Functions.sol"; import {Constants} from "../Constants.sol"; +import {IGatewayV2} from "./IGateway.sol"; +import {IGatewayBase} from "../interfaces/IGatewayBase.sol"; import { UpgradeParams, @@ -45,7 +46,7 @@ library HandlersV2 { SetOperatingModeParams memory params = abi.decode(data, (SetOperatingModeParams)); CoreStorage.Layout storage $ = CoreStorage.layout(); $.mode = params.mode; - emit IGateway.OperatingModeChanged(params.mode); + emit IGatewayBase.OperatingModeChanged(params.mode); } // @dev Register a new fungible Polkadot token for an agent diff --git a/contracts/src/v2/IGateway.sol b/contracts/src/v2/IGateway.sol new file mode 100644 index 0000000000..b58d3d24b8 --- /dev/null +++ b/contracts/src/v2/IGateway.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +pragma solidity 0.8.25; + +import {MultiAddress} from "../MultiAddress.sol"; +import {OperatingMode, InboundMessage} from "./Types.sol"; +import {Verification} from "../Verification.sol"; +import {UD60x18} from "prb/math/src/UD60x18.sol"; + +interface IGatewayV2 { + error InvalidAsset(); + error InvalidFee(); + + function operatingMode() external view returns (OperatingMode); + + function agentOf(bytes32 agentID) external view returns (address); + + /** + * Events + */ + + // V2: Emitted when inbound message has been dispatched + event InboundMessageDispatched( + uint64 indexed nonce, bool success, bytes32 indexed rewardAddress + ); + + // v2 Emitted when an outbound message has been accepted for delivery to a Polkadot parachain + event OutboundMessageAccepted(uint64 nonce, uint256 reward, bytes payload); + + // V2 + + // Submit a message for verification and dispatch + function v2_submit( + InboundMessage calldata message, + bytes32[] calldata leafProof, + Verification.Proof calldata headerProof, + bytes32 rewardAddress + ) external; + + // Send an XCM with arbitrary assets to Polkadot Asset Hub + // + // Params: + // * `xcm` (bytes): SCALE-encoded VersionedXcm message + // * `assets` (bytes[]): Array of asset specs, constrained to maximum of eight. + // + // Supported asset specs: + // * ERC20: abi.encode(0, tokenAddress, value) + // + // On Asset Hub, the assets will be received into the assets holding register. + // + // The `xcm` should contain the necessary instructions to: + // 1. Pay XCM execution fees for `xcm`, either from assets in holding, + // or from the sovereign account of `msg.sender`. + // 2. Handle the assets in holding, either depositing them into + // some account, or forwarding them to another destination. + // + // To incentivize message delivery, some amount of ether must be passed and should + // at least cover the total cost of delivery to Polkadot. This ether be sent across + // the bridge as WETH, and given to the relayer as compensation and incentivization. + // + function v2_sendMessage( + bytes calldata xcm, + bytes[] calldata assets, + bytes calldata claimer + ) external payable; + + // Register Ethereum-native token on AHP, using `xcmFeeAHP` of `msg.value` + // to pay for execution on AHP. + function v2_registerToken(address token, uint128 xcmFeeAHP) external payable; + + // Register Ethereum-native token on AHK, using `xcmFeeAHP` and `xcmFeeAHK` + // of `msg.value` to pay for execution on AHP and AHK respectively. + function v2_registerTokenOnKusama( + address token, + uint128 xcmFeeAHP, + uint128 xcmFeeAHK + ) external payable; + + // Check if an inbound message was previously accepted and dispatched + function v2_isDispatched(uint64 nonce) external returns (bool); + + /// @dev Check whether a token is registered + function isTokenRegistered(address token) external view returns (bool); +} diff --git a/contracts/test/ForkUpgrade.t.sol b/contracts/test/ForkUpgrade.t.sol index 73f8ddf3e2..33d04a94d9 100644 --- a/contracts/test/ForkUpgrade.t.sol +++ b/contracts/test/ForkUpgrade.t.sol @@ -5,7 +5,8 @@ import {Test} from "forge-std/Test.sol"; import {console} from "forge-std/console.sol"; import {IUpgradable} from "../src/interfaces/IUpgradable.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; +import {IGatewayBase} from "../src/interfaces/IGatewayBase.sol"; +import {IGatewayV1} from "../src/v1/IGateway.sol"; import {Gateway} from "../src/Gateway.sol"; import {Gateway202411} from "../src/upgrades/polkadot/Gateway202411.sol"; import {AgentExecutor} from "../src/AgentExecutor.sol"; @@ -79,7 +80,7 @@ contract ForkUpgradeTest is Test { }); vm.expectEmit(true, true, false, false); - emit IGateway.ForeignTokenRegistered(dotId, address(0x0)); + emit IGatewayBase.ForeignTokenRegistered(dotId, address(0x0)); Gateway202411(GATEWAY_PROXY).v1_handleRegisterForeignToken(abi.encode(params)); TokenInfo memory dot = Gateway202411(GATEWAY_PROXY).tokenInfo( @@ -91,7 +92,7 @@ contract ForkUpgradeTest is Test { function testSanityCheck() public { // Check AH channel nonces as expected - (uint64 inbound, uint64 outbound) = IGateway(GATEWAY_PROXY).channelNoncesOf( + (uint64 inbound, uint64 outbound) = IGatewayV1(GATEWAY_PROXY).channelNoncesOf( ChannelID.wrap( 0xc173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539 ) diff --git a/contracts/test/GatewayV1.t.sol b/contracts/test/GatewayV1.t.sol index 1352db8336..12cb41d2a9 100644 --- a/contracts/test/GatewayV1.t.sol +++ b/contracts/test/GatewayV1.t.sol @@ -6,8 +6,8 @@ import {Strings} from "openzeppelin/utils/Strings.sol"; import {console} from "forge-std/console.sol"; import {BeefyClient} from "../src/BeefyClient.sol"; - -import {IGateway} from "../src/interfaces/IGateway.sol"; +import {IGatewayBase} from "../src/interfaces/IGatewayBase.sol"; +import {IGatewayV1} from "../src/v1/IGateway.sol"; import {IInitializable} from "../src/interfaces/IInitializable.sol"; import {IUpgradable} from "../src/interfaces/IUpgradable.sol"; import {Gateway} from "../src/Gateway.sol"; @@ -143,8 +143,9 @@ contract GatewayV1Test is Test { ); bridgeHubAgent = - IGateway(address(gateway)).agentOf(Constants.BRIDGE_HUB_AGENT_ID); - assetHubAgent = IGateway(address(gateway)).agentOf(Constants.ASSET_HUB_AGENT_ID); + IGatewayV1(address(gateway)).agentOf(Constants.BRIDGE_HUB_AGENT_ID); + assetHubAgent = + IGatewayV1(address(gateway)).agentOf(Constants.ASSET_HUB_AGENT_ID); // fund the message relayer account relayer = makeAddr("relayer"); @@ -218,10 +219,12 @@ contract GatewayV1Test is Test { // Expect the gateway to emit `InboundMessageDispatched` vm.expectEmit(true, false, false, false); - emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); + emit IGatewayV1.InboundMessageDispatched( + assetHubParaID.into(), 1, messageID, true + ); hoax(relayer, 1 ether); - IGateway(address(gateway)).submitV1( + IGatewayV1(address(gateway)).submitV1( InboundMessage( assetHubParaID.into(), 1, @@ -243,7 +246,7 @@ contract GatewayV1Test is Test { (Command command, bytes memory params) = makeCreateAgentCommand(); hoax(relayer, 1 ether); - IGateway(address(gateway)).submitV1( + IGatewayV1(address(gateway)).submitV1( InboundMessage( assetHubParaID.into(), 1, @@ -259,9 +262,9 @@ contract GatewayV1Test is Test { ); // try to replay the message - vm.expectRevert(IGateway.InvalidNonce.selector); + vm.expectRevert(IGatewayBase.InvalidNonce.selector); hoax(relayer, 1 ether); - IGateway(address(gateway)).submitV1( + IGatewayV1(address(gateway)).submitV1( InboundMessage( assetHubParaID.into(), 1, @@ -280,9 +283,9 @@ contract GatewayV1Test is Test { function testSubmitFailInvalidChannel() public { (Command command,) = makeCreateAgentCommand(); - vm.expectRevert(IGateway.ChannelDoesNotExist.selector); + vm.expectRevert(IGatewayV1.ChannelDoesNotExist.selector); hoax(relayer); - IGateway(address(gateway)).submitV1( + IGatewayV1(address(gateway)).submitV1( InboundMessage( ParaID.wrap(42).into(), 1, @@ -304,10 +307,10 @@ contract GatewayV1Test is Test { (Command command, bytes memory params) = makeCreateAgentCommand(); MockGateway(address(gateway)).setCommitmentsAreVerified(false); - vm.expectRevert(IGateway.InvalidProof.selector); + vm.expectRevert(IGatewayBase.InvalidProof.selector); hoax(relayer, 1 ether); - IGateway(address(gateway)).submitV1( + IGatewayV1(address(gateway)).submitV1( InboundMessage( assetHubParaID.into(), 1, @@ -339,7 +342,7 @@ contract GatewayV1Test is Test { uint256 agentBalanceBefore = address(assetHubAgent).balance; uint256 startGas = gasleft(); - IGateway(address(gateway)).submitV1( + IGatewayV1(address(gateway)).submitV1( InboundMessage( assetHubParaID.into(), 1, @@ -374,7 +377,7 @@ contract GatewayV1Test is Test { (Command command, bytes memory params) = makeCreateAgentCommand(); hoax(relayer, 1 ether); - IGateway(address(gateway)).submitV1( + IGatewayV1(address(gateway)).submitV1( InboundMessage( assetHubParaID.into(), 1, @@ -400,10 +403,10 @@ contract GatewayV1Test is Test { deal(address(token), user, 1); // register token first - uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); - IGateway(address(gateway)).registerToken{value: fee}(address(token)); + uint256 fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); + IGatewayV1(address(gateway)).registerToken{value: fee}(address(token)); - fee = IGateway(address(gateway)).quoteSendTokenFee( + fee = IGatewayV1(address(gateway)).quoteSendTokenFee( address(token), ParaID.wrap(0), 1 ); @@ -412,7 +415,7 @@ contract GatewayV1Test is Test { token.approve(address(gateway), 1); hoax(user, fee); - IGateway(address(gateway)).sendToken{value: fee}( + IGatewayV1(address(gateway)).sendToken{value: fee}( address(token), ParaID.wrap(0), recipientAddress32(), 1, 1 ); @@ -422,8 +425,8 @@ contract GatewayV1Test is Test { // User doesn't have enough funds to send message function testUserDoesNotProvideEnoughFees() public { // register token first - uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); - IGateway(address(gateway)).registerToken{value: fee}(address(token)); + uint256 fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); + IGatewayV1(address(gateway)).registerToken{value: fee}(address(token)); // Create a mock user address user = makeAddr("user"); @@ -433,9 +436,9 @@ contract GatewayV1Test is Test { hoax(user); token.approve(address(gateway), 1); - vm.expectRevert(IGateway.FeePaymentToLow.selector); + vm.expectRevert(IGatewayBase.FeePaymentToLow.selector); hoax(user, 2 ether); - IGateway(address(gateway)).sendToken{value: 0.002 ether}( + IGatewayV1(address(gateway)).sendToken{value: 0.002 ether}( address(token), ParaID.wrap(0), recipientAddress32(), 1, 1 ); @@ -449,7 +452,7 @@ contract GatewayV1Test is Test { AgentExecuteParams memory params = AgentExecuteParams({agentID: assetHubAgentID, payload: ""}); - vm.expectRevert(IGateway.InvalidAgentExecutionPayload.selector); + vm.expectRevert(IGatewayBase.InvalidAgentExecutionPayload.selector); MockGateway(address(gateway)).v1_handleAgentExecute_public(abi.encode(params)); } @@ -458,7 +461,7 @@ contract GatewayV1Test is Test { CreateAgentParams memory params = CreateAgentParams({agentID: agentID}); vm.expectEmit(false, false, false, false, address(gateway)); - emit IGateway.AgentCreated(agentID, address(0)); + emit IGatewayBase.AgentCreated(agentID, address(0)); MockGateway(address(gateway)).v1_handleCreateAgent_public(abi.encode(params)); } @@ -468,7 +471,7 @@ contract GatewayV1Test is Test { MockGateway(address(gateway)).v1_handleCreateAgent_public(abi.encode(params)); - vm.expectRevert(IGateway.AgentAlreadyCreated.selector); + vm.expectRevert(IGatewayBase.AgentAlreadyCreated.selector); MockGateway(address(gateway)).v1_handleCreateAgent_public(abi.encode(params)); } @@ -522,14 +525,14 @@ contract GatewayV1Test is Test { SetOperatingModeParams memory params = SetOperatingModeParams({mode: OperatingMode.RejectingOutboundMessages}); - OperatingMode mode = IGateway(address(gateway)).operatingMode(); + OperatingMode mode = IGatewayV1(address(gateway)).operatingMode(); assertEq(uint256(mode), 0); MockGateway(address(gateway)).v1_handleSetOperatingMode_public( abi.encode(params) ); - mode = IGateway(address(gateway)).operatingMode(); + mode = IGatewayV1(address(gateway)).operatingMode(); assertEq(uint256(mode), 1); } @@ -557,29 +560,29 @@ contract GatewayV1Test is Test { */ function testRegisterToken() public { vm.expectEmit(false, false, false, true); - emit IGateway.TokenRegistrationSent(address(token)); + emit IGatewayBase.TokenRegistrationSent(address(token)); vm.expectEmit(true, false, false, false); - emit IGateway.OutboundMessageAccepted( + emit IGatewayV1.OutboundMessageAccepted( assetHubParaID.into(), 1, messageID, bytes("") ); - IGateway(address(gateway)).registerToken{value: 2 ether}(address(token)); + IGatewayV1(address(gateway)).registerToken{value: 2 ether}(address(token)); } function testRegisterTokenReimbursesExcessFees() public { vm.expectEmit(false, false, false, true); - emit IGateway.TokenRegistrationSent(address(token)); + emit IGatewayBase.TokenRegistrationSent(address(token)); vm.expectEmit(true, false, false, false); - emit IGateway.OutboundMessageAccepted( + emit IGatewayV1.OutboundMessageAccepted( assetHubParaID.into(), 1, messageID, bytes("") ); uint256 totalFee = MockGateway(address(gateway)).quoteRegisterTokenFee(); uint256 balanceBefore = address(this).balance; - IGateway(address(gateway)).registerToken{value: totalFee + 1 ether}( + IGatewayV1(address(gateway)).registerToken{value: totalFee + 1 ether}( address(token) ); uint256 balanceAfter = address(this).balance; @@ -598,23 +601,23 @@ contract GatewayV1Test is Test { ParaID destPara = ParaID.wrap(2043); // register token first - uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); - IGateway(address(gateway)).registerToken{value: fee}(address(token)); + uint256 fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); + IGatewayV1(address(gateway)).registerToken{value: fee}(address(token)); - fee = IGateway(address(gateway)).quoteSendTokenFee(address(token), destPara, 1); + fee = IGatewayV1(address(gateway)).quoteSendTokenFee(address(token), destPara, 1); vm.expectEmit(true, true, false, true); - emit IGateway.TokenSent( + emit IGatewayV1.TokenSent( address(token), address(this), destPara, recipientAddress32(), 1 ); // Expect the gateway to emit `OutboundMessageAccepted` vm.expectEmit(true, false, false, false); - emit IGateway.OutboundMessageAccepted( + emit IGatewayV1.OutboundMessageAccepted( assetHubParaID.into(), 1, messageID, bytes("") ); - IGateway(address(gateway)).sendToken{value: fee}( + IGatewayV1(address(gateway)).sendToken{value: fee}( address(token), destPara, recipientAddress32(), 1, 1 ); } @@ -627,23 +630,23 @@ contract GatewayV1Test is Test { ParaID destPara = assetHubParaID; // register token first - uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); - IGateway(address(gateway)).registerToken{value: fee}(address(token)); + uint256 fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); + IGatewayV1(address(gateway)).registerToken{value: fee}(address(token)); - fee = IGateway(address(gateway)).quoteSendTokenFee(address(token), destPara, 1); + fee = IGatewayV1(address(gateway)).quoteSendTokenFee(address(token), destPara, 1); vm.expectEmit(true, true, false, true); - emit IGateway.TokenSent( + emit IGatewayV1.TokenSent( address(token), address(this), destPara, recipientAddress32(), 1 ); // Expect the gateway to emit `OutboundMessageAccepted` vm.expectEmit(true, false, false, false); - emit IGateway.OutboundMessageAccepted( + emit IGatewayV1.OutboundMessageAccepted( assetHubParaID.into(), 1, messageID, bytes("") ); - IGateway(address(gateway)).sendToken{value: fee}( + IGatewayV1(address(gateway)).sendToken{value: fee}( address(token), destPara, recipientAddress32(), 1, 1 ); } @@ -656,23 +659,23 @@ contract GatewayV1Test is Test { ParaID destPara = ParaID.wrap(2043); // register token first - uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); - IGateway(address(gateway)).registerToken{value: fee}(address(token)); + uint256 fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); + IGatewayV1(address(gateway)).registerToken{value: fee}(address(token)); - fee = IGateway(address(gateway)).quoteSendTokenFee(address(token), destPara, 1); + fee = IGatewayV1(address(gateway)).quoteSendTokenFee(address(token), destPara, 1); vm.expectEmit(true, true, false, true); - emit IGateway.TokenSent( + emit IGatewayV1.TokenSent( address(token), address(this), destPara, recipientAddress20(), 1 ); // Expect the gateway to emit `OutboundMessageAccepted` vm.expectEmit(true, false, false, false); - emit IGateway.OutboundMessageAccepted( + emit IGatewayV1.OutboundMessageAccepted( assetHubParaID.into(), 1, messageID, bytes("") ); - IGateway(address(gateway)).sendToken{value: fee}( + IGatewayV1(address(gateway)).sendToken{value: fee}( address(token), destPara, recipientAddress20(), 1, 1 ); } @@ -684,12 +687,12 @@ contract GatewayV1Test is Test { ParaID destPara = assetHubParaID; // register token first - uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); - IGateway(address(gateway)).registerToken{value: fee}(address(token)); + uint256 fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); + IGatewayV1(address(gateway)).registerToken{value: fee}(address(token)); // Should fail to send tokens to AssetHub - vm.expectRevert(IGateway.Unsupported.selector); - IGateway(address(gateway)).sendToken{value: 2 ether}( + vm.expectRevert(IGatewayBase.Unsupported.selector); + IGatewayV1(address(gateway)).sendToken{value: 2 ether}( address(token), destPara, recipientAddress20(), 1, 1 ); } @@ -707,7 +710,7 @@ contract GatewayV1Test is Test { ) ); - OperatingMode mode = IGateway(address(gateway)).operatingMode(); + OperatingMode mode = IGatewayV1(address(gateway)).operatingMode(); assertEq(uint256(mode), 1); } @@ -717,30 +720,30 @@ contract GatewayV1Test is Test { // Initialize function should not be externally callable on either proxy or implementation contract function testInitializeNotExternallyCallable() public { - vm.expectRevert(IGateway.Unauthorized.selector); + vm.expectRevert(IGatewayBase.Unauthorized.selector); Gateway(address(gateway)).initialize(""); - vm.expectRevert(IGateway.Unauthorized.selector); + vm.expectRevert(IGatewayBase.Unauthorized.selector); MockGateway(address(gatewayLogic)).initialize(""); } // Handler functions should not be externally callable function testHandlersNotExternallyCallable() public { - vm.expectRevert(IGateway.Unauthorized.selector); + vm.expectRevert(IGatewayBase.Unauthorized.selector); Gateway(address(gateway)).v1_handleCreateAgent(""); - vm.expectRevert(IGateway.Unauthorized.selector); + vm.expectRevert(IGatewayBase.Unauthorized.selector); Gateway(address(gateway)).v1_handleSetOperatingMode(""); - vm.expectRevert(IGateway.Unauthorized.selector); + vm.expectRevert(IGatewayBase.Unauthorized.selector); Gateway(address(gateway)).v1_handleUpgrade(""); - vm.expectRevert(IGateway.Unauthorized.selector); + vm.expectRevert(IGatewayBase.Unauthorized.selector); Gateway(address(gateway)).v1_handleTransferNativeFromAgent(""); } function testGetters() public { - IGateway gw = IGateway(address(gateway)); + IGatewayV1 gw = IGatewayV1(address(gateway)); OperatingMode mode = gw.operatingMode(); assertEq(uint256(mode), 0); @@ -758,7 +761,7 @@ contract GatewayV1Test is Test { address agent = gw.agentOf(assetHubAgentID); assertEq(agent, assetHubAgent); - address implementation = gw.implementation(); + address implementation = IUpgradable(address(gw)).implementation(); assertEq(implementation, address(gatewayLogic)); } @@ -771,11 +774,11 @@ contract GatewayV1Test is Test { vm.expectEmit(true, false, false, true); // Expect dispatch result as false for `OutOfGas` - emit IGateway.InboundMessageDispatched( + emit IGatewayV1.InboundMessageDispatched( assetHubParaID.into(), 1, messageID, false ); // maxDispatchGas as 1 for `create_agent` is definitely not enough - IGateway(address(gateway)).submitV1( + IGatewayV1(address(gateway)).submitV1( InboundMessage( assetHubParaID.into(), 1, @@ -792,7 +795,7 @@ contract GatewayV1Test is Test { } function testSetTokenFees() public { - uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); + uint256 fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); assertEq(fee, 5_000_000_000_000_000); // Double the assetHubCreateAssetFee MockGateway(address(gateway)).v1_handleSetTokenTransferFees_public( @@ -804,7 +807,7 @@ contract GatewayV1Test is Test { }) ) ); - fee = IGateway(address(gateway)).quoteRegisterTokenFee(); + fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); // since deliveryCost not changed, so the total fee increased only by 50% assertEq(fee, 7_500_000_000_000_000); } @@ -819,7 +822,7 @@ contract GatewayV1Test is Test { } function testSetPricingParameters() public { - uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); + uint256 fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); assertEq(fee, 5_000_000_000_000_000); // Double both the exchangeRate and multiplier. Should lead to an 4x fee increase MockGateway(address(gateway)).v1_handleSetPricingParameters_public( @@ -832,7 +835,7 @@ contract GatewayV1Test is Test { ) ); // Should expect 4x fee increase - fee = IGateway(address(gateway)).quoteRegisterTokenFee(); + fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); assertEq(fee, 20_000_000_000_000_001); } @@ -844,12 +847,12 @@ contract GatewayV1Test is Test { ParaID destPara = ParaID.wrap(2043); // register token first - uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); - IGateway(address(gateway)).registerToken{value: fee}(address(token)); - fee = IGateway(address(gateway)).quoteSendTokenFee(address(token), destPara, 0); + uint256 fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); + IGatewayV1(address(gateway)).registerToken{value: fee}(address(token)); + fee = IGatewayV1(address(gateway)).quoteSendTokenFee(address(token), destPara, 0); - vm.expectRevert(IGateway.InvalidDestinationFee.selector); - IGateway(address(gateway)).sendToken{value: fee}( + vm.expectRevert(IGatewayBase.InvalidDestinationFee.selector); + IGatewayV1(address(gateway)).sendToken{value: fee}( address(token), destPara, recipientAddress32(), 0, 1 ); } @@ -862,16 +865,16 @@ contract GatewayV1Test is Test { ParaID destPara = ParaID.wrap(2043); // register token first - uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); - IGateway(address(gateway)).registerToken{value: fee}(address(token)); + uint256 fee = IGatewayV1(address(gateway)).quoteRegisterTokenFee(); + IGatewayV1(address(gateway)).registerToken{value: fee}(address(token)); - vm.expectRevert(IGateway.InvalidDestinationFee.selector); - IGateway(address(gateway)).quoteSendTokenFee( + vm.expectRevert(IGatewayBase.InvalidDestinationFee.selector); + IGatewayV1(address(gateway)).quoteSendTokenFee( address(token), destPara, maxDestinationFee + 1 ); - vm.expectRevert(IGateway.InvalidDestinationFee.selector); - IGateway(address(gateway)).sendToken{value: fee}( + vm.expectRevert(IGatewayBase.InvalidDestinationFee.selector); + IGatewayV1(address(gateway)).sendToken{value: fee}( address(token), destPara, recipientAddress32(), maxDestinationFee + 1, 1 ); } @@ -899,7 +902,7 @@ contract GatewayV1Test is Test { }); vm.expectEmit(true, true, false, false); - emit IGateway.ForeignTokenRegistered(bytes32(uint256(1)), address(0)); + emit IGatewayBase.ForeignTokenRegistered(bytes32(uint256(1)), address(0)); MockGateway(address(gateway)).v1_handleRegisterForeignToken_public( abi.encode(params) @@ -916,7 +919,7 @@ contract GatewayV1Test is Test { decimals: 10 }); - vm.expectRevert(IGateway.TokenAlreadyRegistered.selector); + vm.expectRevert(IGatewayBase.TokenAlreadyRegistered.selector); MockGateway(address(gateway)).v1_handleRegisterForeignToken_public( abi.encode(params) @@ -955,7 +958,7 @@ contract GatewayV1Test is Test { amount: 1000 }); - vm.expectRevert(IGateway.TokenNotRegistered.selector); + vm.expectRevert(IGatewayBase.TokenNotRegistered.selector); MockGateway(address(gateway)).v1_handleMintForeignToken_public( abi.encode(params) @@ -973,17 +976,17 @@ contract GatewayV1Test is Test { vm.prank(account1); vm.expectEmit(true, true, false, true); - emit IGateway.TokenSent( + emit IGatewayV1.TokenSent( address(dotToken), account1, destPara, recipientAddress32(), 1 ); // Expect the gateway to emit `OutboundMessageAccepted` vm.expectEmit(true, false, false, false); - emit IGateway.OutboundMessageAccepted( + emit IGatewayV1.OutboundMessageAccepted( assetHubParaID.into(), 1, messageID, bytes("") ); - IGateway(address(gateway)).sendToken{value: 0.1 ether}( + IGatewayV1(address(gateway)).sendToken{value: 0.1 ether}( address(dotToken), destPara, recipientAddress32(), 1, 1 ); } @@ -998,8 +1001,8 @@ contract GatewayV1Test is Test { vm.prank(account1); - vm.expectRevert(IGateway.Unsupported.selector); - IGateway(address(gateway)).sendToken{value: 0.1 ether}( + vm.expectRevert(IGatewayBase.Unsupported.selector); + IGatewayV1(address(gateway)).sendToken{value: 0.1 ether}( address(dotToken), destPara, recipientAddress20(), 1, 1 ); } @@ -1014,8 +1017,8 @@ contract GatewayV1Test is Test { vm.prank(account1); - vm.expectRevert(IGateway.Unsupported.selector); - IGateway(address(gateway)).sendToken{value: 0.1 ether}( + vm.expectRevert(IGatewayBase.Unsupported.selector); + IGatewayV1(address(gateway)).sendToken{value: 0.1 ether}( address(dotToken), destPara, recipientAddress32(), 1, 1 ); } @@ -1030,8 +1033,8 @@ contract GatewayV1Test is Test { vm.prank(account1); - vm.expectRevert(IGateway.Unsupported.selector); - IGateway(address(gateway)).sendToken{value: 0.1 ether}( + vm.expectRevert(IGatewayBase.Unsupported.selector); + IGatewayV1(address(gateway)).sendToken{value: 0.1 ether}( address(dotToken), destPara, recipientAddress20(), 1, 1 ); } @@ -1039,9 +1042,9 @@ contract GatewayV1Test is Test { function testSendNotRegisteredTokenWillFail() public { ParaID destPara = assetHubParaID; - vm.expectRevert(IGateway.TokenNotRegistered.selector); + vm.expectRevert(IGatewayBase.TokenNotRegistered.selector); - IGateway(address(gateway)).sendToken{value: 0.1 ether}( + IGatewayV1(address(gateway)).sendToken{value: 0.1 ether}( address(0x0), destPara, recipientAddress32(), 1, 1 ); } @@ -1059,7 +1062,7 @@ contract GatewayV1Test is Test { abi.encodeWithSelector(IERC20.InsufficientBalance.selector, account1, 0, 1) ); - IGateway(address(gateway)).sendToken{value: 0.1 ether}( + IGatewayV1(address(gateway)).sendToken{value: 0.1 ether}( address(dotToken), destPara, recipientAddress32(), 1, 1 ); } diff --git a/contracts/test/GatewayV2.t.sol b/contracts/test/GatewayV2.t.sol index 323d2fb326..02644661a5 100644 --- a/contracts/test/GatewayV2.t.sol +++ b/contracts/test/GatewayV2.t.sol @@ -7,7 +7,8 @@ import {console} from "forge-std/console.sol"; import {BeefyClient} from "../src/BeefyClient.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; +import {IGatewayBase} from "../src/interfaces/IGatewayBase.sol"; +import {IGatewayV2} from "../src/v2/IGateway.sol"; import {IInitializable} from "../src/interfaces/IInitializable.sol"; import {IUpgradable} from "../src/interfaces/IUpgradable.sol"; import {Gateway} from "../src/Gateway.sol"; @@ -109,7 +110,8 @@ contract GatewayV2Test is Test { abi.encode(params) ); - assetHubAgent = IGateway(address(gateway)).agentOf(Constants.ASSET_HUB_AGENT_ID); + assetHubAgent = + IGatewayV2(address(gateway)).agentOf(Constants.ASSET_HUB_AGENT_ID); // fund the message relayer account relayer = makeAddr("relayer"); @@ -175,10 +177,10 @@ contract GatewayV2Test is Test { function testSubmitHappyPath() public { // Expect the gateway to emit `InboundMessageDispatched` vm.expectEmit(true, false, false, true); - emit IGateway.InboundMessageDispatched(1, true, relayerRewardAddress); + emit IGatewayV2.InboundMessageDispatched(1, true, relayerRewardAddress); hoax(relayer, 1 ether); - IGateway(address(gateway)).v2_submit( + IGatewayV2(address(gateway)).v2_submit( InboundMessageV2({ origin: keccak256("666"), nonce: 1, @@ -198,13 +200,13 @@ contract GatewayV2Test is Test { }); hoax(relayer, 1 ether); - IGateway(address(gateway)).v2_submit( + IGatewayV2(address(gateway)).v2_submit( message, proof, makeMockProof(), relayerRewardAddress ); - vm.expectRevert(IGateway.InvalidNonce.selector); + vm.expectRevert(IGatewayBase.InvalidNonce.selector); hoax(relayer, 1 ether); - IGateway(address(gateway)).v2_submit( + IGatewayV2(address(gateway)).v2_submit( message, proof, makeMockProof(), relayerRewardAddress ); } @@ -217,10 +219,10 @@ contract GatewayV2Test is Test { }); MockGateway(address(gateway)).setCommitmentsAreVerified(false); - vm.expectRevert(IGateway.InvalidProof.selector); + vm.expectRevert(IGatewayBase.InvalidProof.selector); hoax(relayer, 1 ether); - IGateway(address(gateway)).v2_submit( + IGatewayV2(address(gateway)).v2_submit( message, proof, makeMockProof(), relayerRewardAddress ); } diff --git a/contracts/test/Shell.t.sol b/contracts/test/Shell.t.sol index e8cdac3e3a..8d72f67c87 100644 --- a/contracts/test/Shell.t.sol +++ b/contracts/test/Shell.t.sol @@ -5,7 +5,6 @@ import {Test} from "forge-std/Test.sol"; import {Strings} from "openzeppelin/utils/Strings.sol"; import {console} from "forge-std/console.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; import {IInitializable} from "../src/interfaces/IInitializable.sol"; import {IUpgradable} from "../src/interfaces/IUpgradable.sol"; import {IShell} from "../src/interfaces/IShell.sol"; From 5817542939872f2b9d47dd84186f72732d895ee6 Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Mon, 4 Nov 2024 06:40:06 +0200 Subject: [PATCH 2/3] Make WETH address configurable --- contracts/scripts/DeployLocal.sol | 9 +++++---- contracts/scripts/UpgradeShell.sol | 3 ++- contracts/scripts/westend/UpgradeShell.sol | 3 ++- contracts/src/Functions.sol | 5 +++++ contracts/src/Initializer.sol | 2 ++ contracts/src/storage/AssetsStorage.sol | 1 + contracts/src/v2/Calls.sol | 8 ++++---- contracts/test/GatewayV1.t.sol | 6 +++--- contracts/test/GatewayV2.t.sol | 6 +++--- 9 files changed, 27 insertions(+), 16 deletions(-) diff --git a/contracts/scripts/DeployLocal.sol b/contracts/scripts/DeployLocal.sol index fef2e9f43e..6de5b6aa63 100644 --- a/contracts/scripts/DeployLocal.sol +++ b/contracts/scripts/DeployLocal.sol @@ -75,6 +75,9 @@ contract DeployLocal is Script { defaultOperatingMode = OperatingMode.Normal; } + // Deploy WETH for testing + address weth = address(new WETH9()); + Initializer.Config memory config = Initializer.Config({ mode: defaultOperatingMode, deliveryCost: uint128(vm.envUint("DELIVERY_COST")), @@ -85,15 +88,13 @@ contract DeployLocal is Script { multiplier: ud60x18(vm.envUint("FEE_MULTIPLIER")), rescueOperator: address(0), foreignTokenDecimals: foreignTokenDecimals, - maxDestinationFee: maxDestinationFee + maxDestinationFee: maxDestinationFee, + weth: weth }); GatewayProxy gateway = new GatewayProxy(address(gatewayLogic), abi.encode(config)); - // Deploy WETH for testing - new WETH9(); - // Fund the sovereign account for the BridgeHub parachain. Used to reward relayers // of messages originating from BridgeHub uint256 initialDeposit = vm.envUint("BRIDGE_HUB_INITIAL_DEPOSIT"); diff --git a/contracts/scripts/UpgradeShell.sol b/contracts/scripts/UpgradeShell.sol index fbf152c475..25d85c3eaa 100644 --- a/contracts/scripts/UpgradeShell.sol +++ b/contracts/scripts/UpgradeShell.sol @@ -55,7 +55,8 @@ contract UpgradeShell is Script { multiplier: ud60x18(1.33e18), rescueOperator: 0x4B8a782D4F03ffcB7CE1e95C5cfe5BFCb2C8e967, foreignTokenDecimals: 10, - maxDestinationFee: dot(2) + maxDestinationFee: dot(2), + weth: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 }) }); } diff --git a/contracts/scripts/westend/UpgradeShell.sol b/contracts/scripts/westend/UpgradeShell.sol index 44cc40b184..be483438c0 100644 --- a/contracts/scripts/westend/UpgradeShell.sol +++ b/contracts/scripts/westend/UpgradeShell.sol @@ -42,7 +42,8 @@ contract UpgradeShell is Script { multiplier: ud60x18(1_330_000_000_000_000_000), rescueOperator: 0x302F0B71B8aD3CF6dD90aDb668E49b2168d652fd, foreignTokenDecimals: 12, - maxDestinationFee: 2_000_000_000_000 + maxDestinationFee: 2_000_000_000_000, + weth: 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9 }) }); } diff --git a/contracts/src/Functions.sol b/contracts/src/Functions.sol index 2c4814762a..e8caac73c5 100644 --- a/contracts/src/Functions.sol +++ b/contracts/src/Functions.sol @@ -26,6 +26,11 @@ library Functions { error InvalidAmount(); error ChannelDoesNotExist(); + function weth() internal view returns (address) { + AssetsStorage.Layout storage $ = AssetsStorage.layout(); + return $.weth; + } + function ensureAgent(bytes32 agentID) internal view returns (address agent) { agent = CoreStorage.layout().agents[agentID]; if (agent == address(0)) { diff --git a/contracts/src/Initializer.sol b/contracts/src/Initializer.sol index 7be8bfa023..0c19e7f62d 100644 --- a/contracts/src/Initializer.sol +++ b/contracts/src/Initializer.sol @@ -56,6 +56,7 @@ library Initializer { address rescueOperator; uint8 foreignTokenDecimals; uint128 maxDestinationFee; + address weth; } function initialize(bytes calldata data) external { @@ -118,6 +119,7 @@ library Initializer { assets.assetHubReserveTransferFee = config.assetHubReserveTransferFee; assets.foreignTokenDecimals = config.foreignTokenDecimals; assets.maxDestinationFee = config.maxDestinationFee; + assets.weth = config.weth; // Initialize operator storage OperatorStorage.Layout storage operatorStorage = OperatorStorage.layout(); diff --git a/contracts/src/storage/AssetsStorage.sol b/contracts/src/storage/AssetsStorage.sol index bca187f538..b57b87ad0c 100644 --- a/contracts/src/storage/AssetsStorage.sol +++ b/contracts/src/storage/AssetsStorage.sol @@ -26,6 +26,7 @@ library AssetsStorage { // * Prevents users from mistakenly providing too much fees, which would drain AssetHub's // sovereign account here on Ethereum. uint128 maxDestinationFee; + address weth; } bytes32 internal constant SLOT = keccak256("org.snowbridge.storage.assets"); diff --git a/contracts/src/v2/Calls.sol b/contracts/src/v2/Calls.sol index a82b4907e1..92fcc94484 100644 --- a/contracts/src/v2/Calls.sol +++ b/contracts/src/v2/Calls.sol @@ -34,7 +34,6 @@ library CallsV2 { using SafeTokenTransfer for IERC20; using SafeNativeTransfer for address payable; - address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; uint8 public constant MAX_ASSETS = 8; // Send an XCM with arbitrary assets to Polkadot Asset Hub @@ -130,7 +129,7 @@ library CallsV2 { _lockEther(xcmFee); bytes[] memory assets = new bytes[](1); - assets[0] = abi.encode(0, WETH_ADDRESS, xcmFee); + assets[0] = abi.encode(0, Functions.weth(), xcmFee); _sendMessage(address(this), xcm, assets, "", msg.value - xcmFee); } @@ -156,9 +155,10 @@ library CallsV2 { // Lock wrapped ether into the AssetHub Agent function _lockEther(uint256 value) internal { + address weth = Functions.weth(); address assetHubAgent = Functions.ensureAgent(Constants.ASSET_HUB_AGENT_ID); - WETH9(payable(WETH_ADDRESS)).deposit{value: value}(); - IERC20(WETH_ADDRESS).safeTransfer(assetHubAgent, value); + WETH9(payable(weth)).deposit{value: value}(); + IERC20(weth).safeTransfer(assetHubAgent, value); } /// @dev Outbound message can be disabled globally or on a per-channel basis. diff --git a/contracts/test/GatewayV1.t.sol b/contracts/test/GatewayV1.t.sol index 12cb41d2a9..c36172ffd5 100644 --- a/contracts/test/GatewayV1.t.sol +++ b/contracts/test/GatewayV1.t.sol @@ -119,6 +119,7 @@ contract GatewayV1Test is Test { bytes32 public dotTokenID; function setUp() public { + token = new WETH9(); AgentExecutor executor = new AgentExecutor(); gatewayLogic = new MockGateway(address(0), address(executor)); Initializer.Config memory config = Initializer.Config({ @@ -131,7 +132,8 @@ contract GatewayV1Test is Test { multiplier: multiplier, rescueOperator: 0x4B8a782D4F03ffcB7CE1e95C5cfe5BFCb2C8e967, foreignTokenDecimals: foreignTokenDecimals, - maxDestinationFee: maxDestinationFee + maxDestinationFee: maxDestinationFee, + weth: address(token) }); gateway = new GatewayProxy(address(gatewayLogic), abi.encode(config)); MockGateway(address(gateway)).setCommitmentsAreVerified(true); @@ -152,8 +154,6 @@ contract GatewayV1Test is Test { // Features - token = new WETH9(); - account1 = makeAddr("account1"); account2 = makeAddr("account2"); diff --git a/contracts/test/GatewayV2.t.sol b/contracts/test/GatewayV2.t.sol index 02644661a5..f41aaeb877 100644 --- a/contracts/test/GatewayV2.t.sol +++ b/contracts/test/GatewayV2.t.sol @@ -87,6 +87,7 @@ contract GatewayV2Test is Test { bytes32 public dotTokenID; function setUp() public { + token = new WETH9(); AgentExecutor executor = new AgentExecutor(); gatewayLogic = new MockGateway(address(0), address(executor)); Initializer.Config memory config = Initializer.Config({ @@ -99,7 +100,8 @@ contract GatewayV2Test is Test { multiplier: ud60x18(1e18), rescueOperator: 0x4B8a782D4F03ffcB7CE1e95C5cfe5BFCb2C8e967, foreignTokenDecimals: 10, - maxDestinationFee: 1e11 + maxDestinationFee: 1e11, + weth: address(token) }); gateway = new GatewayProxy(address(gatewayLogic), abi.encode(config)); MockGateway(address(gateway)).setCommitmentsAreVerified(true); @@ -118,8 +120,6 @@ contract GatewayV2Test is Test { // Features - token = new WETH9(); - account1 = makeAddr("account1"); account2 = makeAddr("account2"); From 4c3b2affb214e40681d782dc37ece49d0f74db1f Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:31:24 +0200 Subject: [PATCH 3/3] Autowrap ether --- contracts/src/v2/Calls.sol | 50 +++++++++++++++++++++++++--------- contracts/src/v2/IGateway.sol | 4 ++- contracts/test/GatewayV2.t.sol | 24 ++++++++++++---- 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/contracts/src/v2/Calls.sol b/contracts/src/v2/Calls.sol index 92fcc94484..5f987a2a93 100644 --- a/contracts/src/v2/Calls.sol +++ b/contracts/src/v2/Calls.sol @@ -58,7 +58,7 @@ library CallsV2 { bytes[] calldata assets, bytes calldata claimer ) external { - _sendMessage(msg.sender, xcm, assets, claimer, msg.value); + _sendMessage(msg.sender, xcm, assets, claimer); } // Register Ethereum-native token on AHP, using `xcmFeeAHP` of `msg.value` @@ -83,23 +83,31 @@ library CallsV2 { address origin, bytes memory xcm, bytes[] memory assets, - bytes memory claimer, - uint256 reward + bytes memory claimer ) internal { if (assets.length > MAX_ASSETS) { revert IGatewayBase.TooManyAssets(); } bytes[] memory encodedAssets = new bytes[](assets.length); + uint128 etherValue = 0; + uint128 totalEtherValue = 0; + for (uint256 i = 0; i < assets.length; i++) { - encodedAssets[i] = _handleAsset(assets[i]); + (encodedAssets[i], etherValue) = _handleAsset(assets[i]); + totalEtherValue += etherValue; + } + + if (totalEtherValue > msg.value) { + revert IGatewayV2.InvalidEtherValue(); } + Ticket memory ticket = Ticket({ origin: origin, assets: encodedAssets, xcm: xcm, claimer: claimer, - reward: reward + reward: msg.value - totalEtherValue }); _submitOutbound(ticket); } @@ -126,12 +134,11 @@ library CallsV2 { if (xcmFee > msg.value) { revert IGatewayV2.InvalidFee(); } - _lockEther(xcmFee); bytes[] memory assets = new bytes[](1); - assets[0] = abi.encode(0, Functions.weth(), xcmFee); + assets[0] = abi.encode(0, xcmFee); - _sendMessage(address(this), xcm, assets, "", msg.value - xcmFee); + _sendMessage(address(this), xcm, assets, ""); } // Submit an outbound message to Polkadot, after taking fees @@ -169,13 +176,17 @@ library CallsV2 { } } - function _handleAsset(bytes memory asset) internal returns (bytes memory) { + function _handleAsset(bytes memory asset) internal returns (bytes memory, uint128) { uint8 assetKind; assembly { assetKind := byte(31, mload(add(asset, 32))) } if (assetKind == 0) { - // ERC20: abi.encode(0, tokenAddress, value) + // Ether: abi.encode(0, value) + (, uint128 amount) = abi.decode(asset, (uint8, uint128)); + return _handleAssetEther(amount); + } else if (assetKind == 1) { + // ERC20: abi.encode(1, tokenAddress, value) (, address token, uint128 amount) = abi.decode(asset, (uint8, address, uint128)); return _handleAssetERC20(token, amount); @@ -184,9 +195,20 @@ library CallsV2 { } } + function _handleAssetEther(uint128 amount) + internal + returns (bytes memory, uint128) + { + _lockEther(amount); + return ( + SubstrateTypes.encodeTransferNativeTokenERC20(Functions.weth(), amount), + amount + ); + } + function _handleAssetERC20(address token, uint128 amount) internal - returns (bytes memory) + returns (bytes memory, uint128) { AssetsStorage.Layout storage $ = AssetsStorage.layout(); TokenInfo storage info = $.tokenRegistry[token]; @@ -197,10 +219,12 @@ library CallsV2 { if (info.foreignID == bytes32(0)) { Functions.transferToAgent($.assetHubAgent, token, msg.sender, amount); - return SubstrateTypes.encodeTransferNativeTokenERC20(token, amount); + return (SubstrateTypes.encodeTransferNativeTokenERC20(token, amount), 0); } else { Token(token).burn(msg.sender, amount); - return SubstrateTypes.encodeTransferForeignTokenERC20(info.foreignID, amount); + return ( + SubstrateTypes.encodeTransferForeignTokenERC20(info.foreignID, amount), 0 + ); } } } diff --git a/contracts/src/v2/IGateway.sol b/contracts/src/v2/IGateway.sol index b58d3d24b8..2668bfb8a9 100644 --- a/contracts/src/v2/IGateway.sol +++ b/contracts/src/v2/IGateway.sol @@ -10,6 +10,7 @@ import {UD60x18} from "prb/math/src/UD60x18.sol"; interface IGatewayV2 { error InvalidAsset(); error InvalidFee(); + error InvalidEtherValue(); function operatingMode() external view returns (OperatingMode); @@ -44,7 +45,8 @@ interface IGatewayV2 { // * `assets` (bytes[]): Array of asset specs, constrained to maximum of eight. // // Supported asset specs: - // * ERC20: abi.encode(0, tokenAddress, value) + // * Ether: abi.encode(0, value) + // * ERC20: abi.encode(1, tokenAddress, value) // // On Asset Hub, the assets will be received into the assets holding register. // diff --git a/contracts/test/GatewayV2.t.sol b/contracts/test/GatewayV2.t.sol index f41aaeb877..ead95d440c 100644 --- a/contracts/test/GatewayV2.t.sol +++ b/contracts/test/GatewayV2.t.sol @@ -80,8 +80,8 @@ contract GatewayV2Test is Test { WETH9 public token; - address public account1; - address public account2; + address public user1; + address public user2; // tokenID for DOT bytes32 public dotTokenID; @@ -120,14 +120,15 @@ contract GatewayV2Test is Test { // Features - account1 = makeAddr("account1"); - account2 = makeAddr("account2"); + user1 = makeAddr("user1"); + user2 = makeAddr("user2"); // create tokens for account 1 - hoax(account1); + hoax(user1); token.deposit{value: 500}(); // create tokens for account 2 + hoax(user2); token.deposit{value: 500}(); dotTokenID = bytes32(uint256(1)); @@ -226,4 +227,17 @@ contract GatewayV2Test is Test { message, proof, makeMockProof(), relayerRewardAddress ); } + + function testSendEther() public { + bytes[] memory assets = new bytes[](1); + assets[0] = abi.encode(0, 0.5 ether); + + hoax(user1, 1 ether); + IGatewayV2(payable(address(gateway))).v2_sendMessage{value: 1 ether}( + "", assets, "" + ); + + // Agent balance should be 0.5 + 0.5 + assertEq(token.balanceOf(assetHubAgent), 1 ether); + } }