Skip to content

Commit

Permalink
Support Native ETH in v1 (#1354)
Browse files Browse the repository at this point in the history
Co-authored-by: Vincent Geddes <[email protected]>
  • Loading branch information
alistair-singh and vgeddes authored Jan 27, 2025
1 parent 4a0e8fd commit cb05e1f
Show file tree
Hide file tree
Showing 28 changed files with 1,327 additions and 159 deletions.
10 changes: 3 additions & 7 deletions contracts/scripts/DeployLocal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,11 @@ contract DeployLocal is Script {
// Deploy WETH for testing
new WETH9();

// Fund the sovereign account for the BridgeHub parachain. Used to reward relayers
// Fund the gateway proxy contract. Used to reward relayers
// of messages originating from BridgeHub
uint256 initialDeposit = vm.envUint("BRIDGE_HUB_INITIAL_DEPOSIT");
uint256 initialDeposit = vm.envUint("GATEWAY_PROXY_INITIAL_DEPOSIT");

address bridgeHubAgent = IGateway(address(gateway)).agentOf(bridgeHubAgentID);
address assetHubAgent = IGateway(address(gateway)).agentOf(assetHubAgentID);

payable(bridgeHubAgent).safeNativeTransfer(initialDeposit);
payable(assetHubAgent).safeNativeTransfer(initialDeposit);
IGateway(address(gateway)).depositEther{value: initialDeposit}();

// Deploy MockGatewayV2 for testing
new MockGatewayV2();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {ParaID} from "../src/Types.sol";
import {SafeNativeTransfer} from "../src/utils/SafeTransfer.sol";
import {stdJson} from "forge-std/StdJson.sol";

contract FundAgent is Script {
contract FundGateway is Script {
using SafeNativeTransfer for address payable;
using stdJson for string;

Expand All @@ -26,17 +26,10 @@ contract FundAgent is Script {
address deployer = vm.rememberKey(privateKey);
vm.startBroadcast(deployer);

uint256 initialDeposit = vm.envUint("BRIDGE_HUB_INITIAL_DEPOSIT");
uint256 initialDeposit = vm.envUint("GATEWAY_PROXY_INITIAL_DEPOSIT");
address gatewayAddress = vm.envAddress("GATEWAY_PROXY_CONTRACT");

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);

payable(bridgeHubAgent).safeNativeTransfer(initialDeposit);
payable(assetHubAgent).safeNativeTransfer(initialDeposit);
IGateway(address(gatewayAddress)).depositEther{value: initialDeposit}();

vm.stopBroadcast();
}
Expand Down
33 changes: 33 additions & 0 deletions contracts/scripts/upgrades/polkadot/DeployGateway202502.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]>
pragma solidity 0.8.28;

import {WETH9} from "canonical-weth/WETH9.sol";
import {Script} from "forge-std/Script.sol";
import {BeefyClient} from "../../../src/BeefyClient.sol";
import {AgentExecutor} from "../../../src/AgentExecutor.sol";
import {ParaID} from "../../../src/Types.sol";
import {Gateway202502} from "../../../src/upgrades/Gateway202502.sol";
import {SafeNativeTransfer} from "../../../src/utils/SafeTransfer.sol";
import {stdJson} from "forge-std/StdJson.sol";
import {UD60x18, ud60x18} from "prb/math/src/UD60x18.sol";

contract DeployGateway202502 is Script {
address public constant BEEFY_CLIENT_ADDRESS = 0x6eD05bAa904df3DE117EcFa638d4CB84e1B8A00C;

function run() public {
vm.startBroadcast();

AgentExecutor executor = new AgentExecutor();
Gateway202502 gatewayLogic = new Gateway202502(
BEEFY_CLIENT_ADDRESS,
address(executor),
ParaID.wrap(1002),
0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314,
10,
20_000_000_000 // 2 DOT
);

vm.stopBroadcast();
}
}
33 changes: 33 additions & 0 deletions contracts/scripts/upgrades/westend/DeployGateway202502.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]>
pragma solidity 0.8.28;

import {WETH9} from "canonical-weth/WETH9.sol";
import {Script} from "forge-std/Script.sol";
import {BeefyClient} from "../../../src/BeefyClient.sol";
import {AgentExecutor} from "../../../src/AgentExecutor.sol";
import {ParaID} from "../../../src/Types.sol";
import {Gateway202502} from "../../../src/upgrades/Gateway202502.sol";
import {SafeNativeTransfer} from "../../../src/utils/SafeTransfer.sol";
import {stdJson} from "forge-std/StdJson.sol";
import {UD60x18, ud60x18} from "prb/math/src/UD60x18.sol";

contract DeployGateway202502 is Script {
address public constant BEEFY_CLIENT_ADDRESS = 0x6DFaD3D73A28c48E4F4c616ECda80885b415283a;

function run() public {
vm.startBroadcast();

AgentExecutor executor = new AgentExecutor();
Gateway202502 gatewayLogic = new Gateway202502(
BEEFY_CLIENT_ADDRESS,
address(executor),
ParaID.wrap(1002),
0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314,
12,
2_000_000_000_000 // 2 DOT
);

vm.stopBroadcast();
}
}
3 changes: 1 addition & 2 deletions contracts/src/Agent.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ contract Agent {
}

/// @dev Agents can receive ether permissionlessly.
/// This is important, as agents for top-level parachains also act as sovereign accounts from which message relayers
/// are rewarded.
/// This is important, as agents are used to lock ether.
receive() external payable {}

/// @dev Allow the gateway to invoke some code within the context of this agent
Expand Down
14 changes: 4 additions & 10 deletions contracts/src/AgentExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {AgentExecuteCommand, ParaID} from "./Types.sol";
import {SubstrateTypes} from "./SubstrateTypes.sol";

import {IERC20} from "./interfaces/IERC20.sol";
import {IGateway} from "./interfaces/IGateway.sol";
import {SafeTokenTransfer, SafeNativeTransfer} from "./utils/SafeTransfer.sol";

/// @title Code which will run within an `Agent` using `delegatecall`.
Expand All @@ -14,20 +15,13 @@ contract AgentExecutor {
using SafeTokenTransfer for IERC20;
using SafeNativeTransfer for address payable;

/// @dev Transfer ether to `recipient`. Unlike `_transferToken` This logic is not nested within `execute`,
/// as the gateway needs to control an agent's ether balance directly.
///
function transferNative(address payable recipient, uint256 amount) external {
/// @dev Transfer ether to `recipient`.
function transferEther(address payable recipient, uint256 amount) external {
recipient.safeNativeTransfer(amount);
}

/// @dev Transfer ERC20 to `recipient`. Only callable via `execute`.
/// @dev Transfer ERC20 to `recipient`.
function transferToken(address token, address recipient, uint128 amount) external {
_transferToken(token, recipient, amount);
}

/// @dev Transfer ERC20 to `recipient`. Only callable via `execute`.
function _transferToken(address token, address recipient, uint128 amount) internal {
IERC20(token).safeTransfer(recipient, amount);
}
}
36 changes: 30 additions & 6 deletions contracts/src/Assets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {CoreStorage} from "./storage/CoreStorage.sol";
import {SubstrateTypes} from "./SubstrateTypes.sol";
import {ChannelID, ParaID, MultiAddress, Ticket, Costs} from "./Types.sol";
import {Address} from "./utils/Address.sol";
import {SafeNativeTransfer} from "./utils/SafeTransfer.sol";
import {AgentExecutor} from "./AgentExecutor.sol";
import {Agent} from "./Agent.sol";
import {Call} from "./utils/Call.sol";
Expand All @@ -21,6 +22,7 @@ import {Token} from "./Token.sol";
/// @title Library for implementing Ethereum->Polkadot ERC20 transfers.
library Assets {
using Address for address;
using SafeNativeTransfer for address payable;
using SafeTokenTransferFrom for IERC20;

/* Errors */
Expand Down Expand Up @@ -108,14 +110,18 @@ library Assets {
) external returns (Ticket memory ticket) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();

if (amount == 0) {
revert InvalidAmount();
}

TokenInfo storage info = $.tokenRegistry[token];

if (!info.isRegistered) {
revert TokenNotRegistered();
}

if (info.foreignID == bytes32(0)) {
return _sendNativeToken(
if (info.isNativeToken()) {
return _sendNativeTokenOrEther(
token, sender, destinationChain, destinationAddress, destinationChainFee, maxDestinationChainFee, amount
);
} else {
Expand All @@ -132,7 +138,8 @@ library Assets {
}
}

function _sendNativeToken(
// @dev Transfer ERC20(Ethereum-native) tokens to Polkadot
function _sendNativeTokenOrEther(
address token,
address sender,
ParaID destinationChain,
Expand All @@ -143,8 +150,15 @@ library Assets {
) internal returns (Ticket memory ticket) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();

// Lock the funds into AssetHub's agent contract
_transferToAgent($.assetHubAgent, token, sender, amount);
if (token != address(0)) {
// Lock ERC20
_transferToAgent($.assetHubAgent, token, sender, amount);
ticket.value = 0;
} else {
// Track the ether to bridge to Polkadot. This will be handled
// in `Gateway._submitOutbound`.
ticket.value = amount;
}

ticket.dest = $.assetHubParaID;
ticket.costs = _sendTokenCosts(destinationChain, destinationChainFee, maxDestinationChainFee);
Expand Down Expand Up @@ -191,6 +205,7 @@ library Assets {
revert Unsupported();
}
}

emit IGateway.TokenSent(token, sender, destinationChain, destinationAddress, amount);
}

Expand All @@ -211,6 +226,7 @@ library Assets {

ticket.dest = $.assetHubParaID;
ticket.costs = _sendTokenCosts(destinationChain, destinationChainFee, maxDestinationChainFee);
ticket.value = 0;

// Construct a message payload
if (destinationChain == $.assetHubParaID && destinationAddress.isAddress32()) {
Expand Down Expand Up @@ -262,6 +278,7 @@ library Assets {
ticket.dest = $.assetHubParaID;
ticket.costs = _registerTokenCosts();
ticket.payload = SubstrateTypes.RegisterToken(token, $.assetHubCreateAssetFee);
ticket.value = 0;

emit IGateway.TokenRegistrationSent(token);
}
Expand Down Expand Up @@ -299,7 +316,14 @@ library Assets {
function transferNativeToken(address executor, address agent, address token, address recipient, uint128 amount)
external
{
bytes memory call = abi.encodeCall(AgentExecutor.transferToken, (token, recipient, amount));
bytes memory call;
if (token != address(0)) {
// ERC20
call = abi.encodeCall(AgentExecutor.transferToken, (token, recipient, amount));
} else {
// Native ETH
call = abi.encodeCall(AgentExecutor.transferEther, (payable(recipient), amount));
}
(bool success,) = Agent(payable(agent)).invoke(executor, call);
if (!success) {
revert TokenTransferFailed();
Expand Down
Loading

0 comments on commit cb05e1f

Please sign in to comment.