Skip to content

Commit

Permalink
migrate ether on upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
alistair-singh committed Dec 18, 2024
1 parent 0e40439 commit a9768e1
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 8 deletions.
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
7 changes: 6 additions & 1 deletion 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 @@ -16,11 +17,15 @@ contract AgentExecutor {

/// @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 {
recipient.safeNativeTransfer(amount);
}

/// @dev Transfer ether to Gateway. Used once off for migration purposes. Can be removed after version 1.
function transferNativeToGateway(address payable gateway, uint256 amount) external {
IGateway(gateway).depositEther{value: amount}();
}

/// @dev Transfer ERC20 to `recipient`. Only callable via `execute`.
function transferToken(address token, address recipient, uint128 amount) external {
_transferToken(token, recipient, amount);
Expand Down
11 changes: 11 additions & 0 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,17 @@ 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);
}

/**
* Handlers
*/
Expand Down
7 changes: 5 additions & 2 deletions contracts/src/GatewayProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ contract GatewayProxy is IInitializable {
}
}

// Allow the Gateway proxy to receive ether in order to pay out rewards and refunds
receive() external payable {}
// Prevent users from unwittingly sending ether to the gateway, as these funds
// would otherwise be lost forever.
receive() external payable {
revert NativeCurrencyNotAccepted();
}
}
10 changes: 10 additions & 0 deletions contracts/src/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ interface IGateway {
// Emitted when foreign token from polkadot registed
event ForeignTokenRegistered(bytes32 indexed tokenID, address token);

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

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

function implementation() external view returns (address);

function version() external view returns (uint64);

/**
* Fee management
*/
function depositEther() external payable;

/**
* Messaging
*/
Expand Down
2 changes: 2 additions & 0 deletions contracts/src/storage/CoreStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ 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
15 changes: 14 additions & 1 deletion contracts/src/upgrades/Gateway202410.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,23 @@ contract Gateway202410 is Gateway {
{}

// Override parent initializer to prevent re-initialization of storage.
function initialize(bytes memory) external view override {
function initialize(bytes memory) external override {
// Ensure that arbitrary users cannot initialize storage in this logic contract.
if (ERC1967.load() == address(0)) {
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.transferNativeToGateway, (payable(address(this)), agent.balance));
_invokeOnAgent(agent, call);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ contract ForkUpgradeTest is Test {
address private constant GatewayProxy = 0x27ca963C279c93801941e1eB8799c23f407d68e7;
address private constant BeefyClient = 0x6eD05bAa904df3DE117EcFa638d4CB84e1B8A00C;
bytes32 private constant BridgeHubAgent = 0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314;
bytes32 private constant AssetHubAgent = 0x81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79;

function setUp() public {
vm.createSelectFork("https://rpc.tenderly.co/fork/b77e07b8-ad6d-4e83-b5be-30a2001964aa", 20645700);
Expand All @@ -33,10 +34,20 @@ contract ForkUpgradeTest is Test {
UpgradeParams memory params =
UpgradeParams({impl: address(newLogic), implCodeHash: address(newLogic).codehash, initParams: bytes("")});

vm.expectEmit(true, false, false, false);
Gateway gateway = Gateway(GatewayProxy);

// Check pre-migration of ETH from Asset Hub agent
assertGt(IGateway(GatewayProxy).agentOf(AssetHubAgent).balance, 0);
// Check pre-migration of ETH to Gateway
assertEq(address(GatewayProxy).balance, 0);

vm.expectEmit();
emit IGateway.EtherDeposited(gateway.agentOf(AssetHubAgent), 587928061927368450);

vm.expectEmit();
emit IUpgradable.Upgraded(address(newLogic));

Gateway(GatewayProxy).upgrade(abi.encode(params));
gateway.upgrade(abi.encode(params));
}

function checkLegacyToken() public {
Expand All @@ -60,6 +71,12 @@ contract ForkUpgradeTest is Test {
}

function testSanityCheck() public {
// Check that the version is correctly set.
assertEq(IGateway(GatewayProxy).version(), 1);
// Check migration of ETH from Asset Hub agent
assertEq(IGateway(GatewayProxy).agentOf(AssetHubAgent).balance, 0);
// Check migration of ETH to Gateway
assertGt(address(GatewayProxy).balance, 0);
// Check AH channel nonces as expected
(uint64 inbound, uint64 outbound) = IGateway(GatewayProxy).channelNoncesOf(
ChannelID.wrap(0xc173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539)
Expand Down
3 changes: 3 additions & 0 deletions contracts/test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,11 @@ contract GatewayTest is Test {

assertEq(address(gateway).balance, 0);

vm.expectRevert(GatewayProxy.NativeCurrencyNotAccepted.selector);
SafeNativeTransfer.safeNativeTransfer(payable(gateway), amount);

IGateway(address(gateway)).depositEther{value: amount}();

assertEq(address(gateway).balance, amount);
}

Expand Down

0 comments on commit a9768e1

Please sign in to comment.