Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Native ETH in v1 #1354

Merged
merged 35 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2c5e273
initial support
alistair-singh Dec 12, 2024
3f178ef
add error
alistair-singh Dec 17, 2024
5887da7
fixed tests
alistair-singh Dec 17, 2024
4677fe7
test legacy and new unlock messages
alistair-singh Dec 17, 2024
13b59fb
fix spelling
alistair-singh Dec 17, 2024
6d6f3fc
test registration is blocked
alistair-singh Dec 17, 2024
299c197
fix inbound messages
alistair-singh Dec 17, 2024
5aad78d
allow gateway proxy to receive funds
alistair-singh Dec 17, 2024
dfcfb6c
final tests and fixes
alistair-singh Dec 17, 2024
3c0d826
warnings
alistair-singh Dec 17, 2024
1212459
PR feedback
alistair-singh Dec 18, 2024
e75906a
migrate ether on upgrade
alistair-singh Dec 18, 2024
182d6ed
fix scripts
alistair-singh Dec 18, 2024
61c52b0
update bindings
alistair-singh Dec 18, 2024
db45375
e2e tests
alistair-singh Dec 19, 2024
a44d093
fix assert
alistair-singh Dec 19, 2024
9b67398
duplicated error removal
alistair-singh Dec 19, 2024
2b9173e
fix test fixture generator
alistair-singh Dec 20, 2024
e209869
template fixes
alistair-singh Dec 20, 2024
bb55b2a
contract address changes
alistair-singh Dec 21, 2024
7359dba
contract address change
alistair-singh Dec 24, 2024
366c457
Ignore AgentTransferFromNative bridge command (#1359)
alistair-singh Jan 7, 2025
3ac9646
fmt
alistair-singh Jan 10, 2025
aaa8139
merge conflicts
alistair-singh Jan 10, 2025
a12492b
Rename and remove parameter
alistair-singh Jan 16, 2025
a149663
added sendEth sanity check
alistair-singh Jan 16, 2025
566f2fb
register ether on contruction and migration
alistair-singh Jan 16, 2025
6ed7140
remove extra branches
alistair-singh Jan 16, 2025
f86a6d8
simplify math
alistair-singh Jan 16, 2025
bd1548e
separate branches for ether and native tokens
alistair-singh Jan 16, 2025
e45c9ee
fix comments
alistair-singh Jan 16, 2025
2699ebe
factor out emitting of sendToken
alistair-singh Jan 17, 2025
ddad89f
comment
alistair-singh Jan 22, 2025
b972591
Refactor
vgeddes Jan 23, 2025
eed9eb1
Improve validation of passed ether
vgeddes Jan 27, 2025
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
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 <hello@snowfork.com>
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 <hello@snowfork.com>
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();
}
}
17 changes: 3 additions & 14 deletions contracts/src/AgentExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +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 ether to Gateway. Used once off for migration purposes. Can be removed after version 1.
function transferEtherToGateway(uint256 amount) external {
IGateway(msg.sender).depositEther{value: 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);
}
}
93 changes: 63 additions & 30 deletions contracts/src/Assets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,8 @@ library Assets {
revert TokenNotRegistered();
}

if (token == address(0)) {
return _sendEther(
sender, destinationChain, destinationAddress, destinationChainFee, maxDestinationChainFee, amount
);
} else if (info.foreignID == bytes32(0)) {
return _sendNativeToken(
if (info.foreignID == bytes32(0)) {
return _sendNativeTokenOrEther(
token, sender, destinationChain, destinationAddress, destinationChainFee, maxDestinationChainFee, amount
);
} else {
Expand Down Expand Up @@ -208,10 +204,6 @@ library Assets {
) internal returns (Ticket memory ticket) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();

if (msg.value < amount) {
revert InvalidAmount();
}

ticket = _emitSendNativeTokenTicket(
address(0),
sender,
Expand All @@ -223,14 +215,10 @@ library Assets {
maxDestinationChainFee,
amount
);
ticket.value = amount;

// Lock Native Ether into AssetHub's agent contract
payable($.assetHubAgent).safeNativeTransfer(amount);
}

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

ticket = _emitSendNativeTokenTicket(
token,
sender,
$.assetHubParaID,
destinationChain,
destinationAddress,
$.assetHubReserveTransferFee,
destinationChainFee,
maxDestinationChainFee,
amount
);
ticket.value = 0;
if (token != address(0)) {
vgeddes marked this conversation as resolved.
Show resolved Hide resolved
// Lock ERC20
_transferToAgent($.assetHubAgent, token, sender, amount);
} else {
// Lock Ether
if (msg.value < amount) {
revert InvalidAmount();
}
// Lock Ether
payable($.assetHubAgent).safeNativeTransfer(amount);
ticket.value = amount;
}

// Lock the ERC20 into AssetHub's agent contract
_transferToAgent($.assetHubAgent, token, sender, amount);
ticket.dest = $.assetHubParaID;
ticket.costs = _sendTokenCosts(destinationChain, destinationChainFee, maxDestinationChainFee);

// Construct a message payload
if (destinationChain == $.assetHubParaID) {
// The funds will be minted into the receiver's account on AssetHub
if (destinationAddress.isAddress32()) {
// The receiver has a 32-byte account ID
ticket.payload = SubstrateTypes.SendTokenToAssetHubAddress32(
token, destinationAddress.asAddress32(), $.assetHubReserveTransferFee, amount
);
} else {
// AssetHub does not support 20-byte account IDs
revert Unsupported();
}
} else {
if (destinationChainFee == 0) {
revert InvalidDestinationFee();
}
// The funds will be minted into sovereign account of the destination parachain on AssetHub,
// and then reserve-transferred to the receiver's account on the destination parachain.
if (destinationAddress.isAddress32()) {
// The receiver has a 32-byte account ID
ticket.payload = SubstrateTypes.SendTokenToAddress32(
token,
destinationChain,
destinationAddress.asAddress32(),
$.assetHubReserveTransferFee,
destinationChainFee,
amount
);
} else if (destinationAddress.isAddress20()) {
// The receiver has a 20-byte account ID
ticket.payload = SubstrateTypes.SendTokenToAddress20(
token,
destinationChain,
destinationAddress.asAddress20(),
$.assetHubReserveTransferFee,
destinationChainFee,
amount
);
} else {
revert Unsupported();
}
}

emit IGateway.TokenSent(token, sender, destinationChain, destinationAddress, amount);
vgeddes marked this conversation as resolved.
Show resolved Hide resolved
}

// @dev Transfer Polkadot-native tokens back to Polkadot
Expand Down Expand Up @@ -371,7 +404,7 @@ library Assets {
call = abi.encodeCall(AgentExecutor.transferToken, (token, recipient, amount));
} else {
// Native ETH
call = abi.encodeCall(AgentExecutor.transferNative, (payable(recipient), amount));
call = abi.encodeCall(AgentExecutor.transferEther, (payable(recipient), amount));
}
(bool success,) = Agent(payable(agent)).invoke(executor, call);
if (!success) {
Expand Down
13 changes: 5 additions & 8 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -296,15 +296,11 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
return ERC1967.load();
}

function version() public view returns (uint64) {
return CoreStorage.layout().version;
}

/**
* Fee management
*/
function depositEther() external payable {
emit EtherDeposited(msg.sender, msg.value);
emit Deposited(msg.sender, msg.value);
}

/**
Expand Down Expand Up @@ -556,7 +552,7 @@ contract Gateway is IGateway, IInitializable, IUpgradable {

// The fee is already collected into the gateway contract
// Reimburse excess fee payment
uint256 excessFee = (msg.value - totalEther);
uint256 excessFee = msg.value - totalEther;
if (excessFee > _dustThreshold()) {
payable(msg.sender).safeNativeTransfer(excessFee);
}
Expand Down Expand Up @@ -701,9 +697,10 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
assets.registerTokenFee = config.registerTokenFee;
assets.assetHubCreateAssetFee = config.assetHubCreateAssetFee;
assets.assetHubReserveTransferFee = config.assetHubReserveTransferFee;

// Register native Ether
TokenInfo storage nativeEther = assets.tokenRegistry[address(0)];
nativeEther.isRegistered = true;
TokenInfo storage etherTokenInfo = assets.tokenRegistry[address(0)];
etherTokenInfo.isRegistered = true;

// Initialize operator storage
OperatorStorage.Layout storage operatorStorage = OperatorStorage.layout();
Expand Down
1 change: 1 addition & 0 deletions contracts/src/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ struct Ticket {
ParaID dest;
Costs costs;
bytes payload;
// amount of native ether to be sent
uint128 value;
}

Expand Down
4 changes: 1 addition & 3 deletions contracts/src/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ interface IGateway {
event ForeignTokenRegistered(bytes32 indexed tokenID, address token);

// Emitted when ether is deposited
event EtherDeposited(address who, uint256 amount);
event Deposited(address sender, uint256 amount);

/**
* Getters
Expand All @@ -53,8 +53,6 @@ interface IGateway {

function implementation() external view returns (address);

function version() external view returns (uint64);

/**
* Fee management
*/
Expand Down
2 changes: 0 additions & 2 deletions contracts/src/storage/CoreStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ library CoreStorage {
mapping(bytes32 agentID => address) agents;
// Agent addresses
mapping(address agent => bytes32 agentID) agentAddresses;
// Version of the Gateway Implementation
uint64 version;
}

bytes32 internal constant SLOT = keccak256("org.snowbridge.storage.core");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity 0.8.28;
import "../Gateway.sol";

// New `Gateway` logic contract for the `GatewayProxy` deployed on mainnet
contract Gateway202410 is Gateway {
contract Gateway202502 is Gateway {
constructor(
address beefyClient,
address agentExecutor,
Expand All @@ -31,21 +31,9 @@ contract Gateway202410 is Gateway {
revert Unauthorized();
}

// We expect version 0, deploying version 1.
CoreStorage.Layout storage $ = CoreStorage.layout();
if ($.version != 0) {
revert Unauthorized();
}
$.version = 1;

// migrate asset hub agent
address agent = _ensureAgent(hex"81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79");
bytes memory call = abi.encodeCall(AgentExecutor.transferEtherToGateway, (agent.balance));
_invokeOnAgent(agent, call);

// Register native Ether
AssetsStorage.Layout storage assets = AssetsStorage.layout();
TokenInfo storage nativeEther = assets.tokenRegistry[address(0)];
nativeEther.isRegistered = true;
TokenInfo storage tokenInfo = assets.tokenRegistry[address(0)];
tokenInfo.isRegistered = true;
}
}
Loading
Loading