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

[Do not merge] Simulated upgrade test for mainnet Gateway contract #1279

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
7501630
Asset from polkadot
yrong Mar 13, 2024
e0c3581
Remove params unnecessary
yrong Mar 19, 2024
f070e16
Merge branch 'main' into ron/polkadot-assets-on-ethereum
yrong Mar 20, 2024
780c7bd
Experiment: return calldata from Agent instead of call Gateway directly
yrong Mar 20, 2024
27353f0
To store registries also on agent
yrong Mar 20, 2024
a543f0b
Merge branch 'main' into ron/polkadot-assets-on-ethereum
yrong Mar 21, 2024
6212597
Send polkadot tokens back
yrong Mar 22, 2024
d28d9df
Cleanup for not exceed the size limit
yrong Mar 25, 2024
c23f8bb
Add smoke test for register polkadot token
yrong Mar 26, 2024
8c8afac
Transfer relay token to Ethereum
yrong Mar 26, 2024
0b3344c
Reuse IGateway.sendToken for polkadot native asset
yrong Mar 26, 2024
2d17fca
More cleanup
yrong Mar 26, 2024
047becf
Fix encode substrate types
yrong Mar 26, 2024
3fc6cab
Add smoke test send relay token back
yrong Mar 26, 2024
e53afd0
Rename to sendForeignToken
yrong Mar 27, 2024
5e9820b
Improve ERC20.sol
yrong Mar 27, 2024
fddb9cf
Revert the change double register the token
yrong Mar 28, 2024
e5d9dd8
Migration for AssetsStorage
yrong Mar 29, 2024
9f7e99b
Rename to _sendForeignTokenCosts
yrong Mar 29, 2024
724c649
Use storage for less gas
yrong Mar 29, 2024
6eb8344
Rename to tokenAddressOf
yrong Mar 29, 2024
7c29684
Rename as _burnToken
yrong Mar 29, 2024
edccccd
_ensureAgent in Gateway
yrong Mar 29, 2024
c170eea
Remove unused
yrong Mar 29, 2024
953245b
Clean up smoke test
yrong Mar 29, 2024
adc73a3
Terminate register foreign token in Gateway
yrong Apr 4, 2024
28f513d
Mint foreign token without the callback
yrong Apr 4, 2024
9b7811d
Burn token without the callback
yrong Apr 4, 2024
b00538f
constructor ERC20 with the agent as owner
yrong Apr 4, 2024
3da6be4
Move handler to Assets.sol reduce contract size
yrong Apr 4, 2024
e051f4c
Refactoring to move transferToken to Assets
yrong Apr 4, 2024
79c1ba0
Remove TokenMinted&TokenBurned event
yrong Apr 4, 2024
fdf15aa
Move Command.RegisterToken to top level
yrong Apr 4, 2024
1e6dac0
Merge branch 'main' into ron/polkadot-assets-on-ethereum
yrong Apr 5, 2024
3439213
Fix smoke tests
yrong Apr 6, 2024
c2adf8f
Move the check before interactions
yrong Apr 8, 2024
b0b582d
Polish
yrong Apr 8, 2024
985115f
Introduce ERC20Lib.sol
yrong Apr 9, 2024
ec33548
Remove unused
yrong Apr 9, 2024
32f8d86
More test
yrong Apr 9, 2024
b2a666a
Remove AgentExecuteCommand
yrong Apr 9, 2024
17f328b
Merge branch 'bridge-next-gen' into ron/polkadot-assets-on-ethereum
yrong Apr 9, 2024
6148c03
More contract tests
yrong Apr 9, 2024
4075c11
Update contract address
yrong Apr 10, 2024
fcd7539
Add AgentExecuteCommand back for compatibility
yrong Apr 11, 2024
dc78276
Cleanup
yrong Apr 11, 2024
07545cf
Merge branch 'main' into ron/polkadot-assets-on-ethereum
yrong Apr 16, 2024
03cf01c
Add rfc
yrong Apr 16, 2024
e9013cd
Update polkadot-native-assets.md
yrong Apr 16, 2024
a028d5c
Merge branch 'bridge-next-gen' into ron/polkadot-assets-on-ethereum
yrong Apr 19, 2024
3b7e178
Update rfc
yrong Apr 22, 2024
63e8322
Update rfc with fee section
yrong Apr 22, 2024
c887e7e
Fix _calculateFee for polkadot native token
yrong May 3, 2024
f892da4
Merge branch 'main' into ron/polkadot-assets-on-ethereum
yrong Aug 22, 2024
84bb52c
Improve solidity code for PNA (#1275)
vgeddes Aug 28, 2024
64b9ef4
Remove storage migration
yrong Aug 29, 2024
de3863c
Merge branch 'main' into ron/polkadot-assets-on-ethereum
yrong Aug 29, 2024
25092dd
Merge branch 'main' into ron/polkadot-assets-on-ethereum
yrong Aug 29, 2024
001c990
Smoke test for register relay token
yrong Aug 30, 2024
77f7e78
Add forked upgrade test
vgeddes Aug 30, 2024
f39b7c0
Add secret to GA env vars
vgeddes Aug 30, 2024
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
2 changes: 2 additions & 0 deletions .github/workflows/contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ jobs:
build:
runs-on: snowbridge-runner
timeout-minutes: 15
env:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
steps:
- uses: actions/checkout@v1
with:
Expand Down
17 changes: 6 additions & 11 deletions contracts/src/AgentExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,26 @@ import {SubstrateTypes} from "./SubstrateTypes.sol";

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

/// @title Code which will run within an `Agent` using `delegatecall`.
/// @dev This is a singleton contract, meaning that all agents will execute the same code.
contract AgentExecutor {
using SafeTokenTransfer for IERC20;
using SafeNativeTransfer for address payable;

/// @dev Execute a message which originated from the Polkadot side of the bridge. In other terms,
/// the `data` parameter is constructed by the BridgeHub parachain.
///
function execute(bytes memory data) external {
(AgentExecuteCommand command, bytes memory params) = abi.decode(data, (AgentExecuteCommand, bytes));
if (command == AgentExecuteCommand.TransferToken) {
(address token, address recipient, uint128 amount) = abi.decode(params, (address, address, uint128));
_transferToken(token, recipient, amount);
}
}

/// @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 ERC20 to `recipient`. Only callable via `execute`.
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);
Expand Down
197 changes: 192 additions & 5 deletions contracts/src/Assets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ 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";

import {SubstrateTypes} from "./SubstrateTypes.sol";
import {ParaID, MultiAddress, Ticket, Costs} from "./Types.sol";
import {Address} from "./utils/Address.sol";
import {AgentExecutor} from "./AgentExecutor.sol";
import {Agent} from "./Agent.sol";
import {Call} from "./utils/Call.sol";
import {Token} from "./Token.sol";

/// @title Library for implementing Ethereum->Polkadot ERC20 transfers.
library Assets {
Expand All @@ -24,6 +30,10 @@ library Assets {
error TokenNotRegistered();
error Unsupported();
error InvalidDestinationFee();
error AgentDoesNotExist();
error TokenAlreadyRegistered();
error TokenMintFailed();
error TokenTransferFailed();

function isTokenRegistered(address token) external view returns (bool) {
return AssetsStorage.layout().tokenRegistry[token].isRegistered;
Expand All @@ -42,11 +52,12 @@ library Assets {
IERC20(token).safeTransferFrom(sender, agent, amount);
}

function sendTokenCosts(address token, ParaID destinationChain, uint128 destinationChainFee, uint128 maxDestinationChainFee)
external
view
returns (Costs memory costs)
{
function sendTokenCosts(
address token,
ParaID destinationChain,
uint128 destinationChainFee,
uint128 maxDestinationChainFee
) external view returns (Costs memory costs) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
TokenInfo storage info = $.tokenRegistry[token];
if (!info.isRegistered) {
Expand Down Expand Up @@ -98,10 +109,40 @@ library Assets {
AssetsStorage.Layout storage $ = AssetsStorage.layout();

TokenInfo storage info = $.tokenRegistry[token];

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

if (info.foreignID == bytes32(0)) {
return _sendNativeToken(
token, sender, destinationChain, destinationAddress, destinationChainFee, maxDestinationChainFee, amount
);
} else {
return _sendForeignToken(
info.foreignID,
token,
sender,
destinationChain,
destinationAddress,
destinationChainFee,
maxDestinationChainFee,
amount
);
}
}

function _sendNativeToken(
address token,
address sender,
ParaID destinationChain,
MultiAddress calldata destinationAddress,
uint128 destinationChainFee,
uint128 maxDestinationChainFee,
uint128 amount
) internal returns (Ticket memory ticket) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();

// Lock the funds into AssetHub's agent contract
_transferToAgent($.assetHubAgent, token, sender, amount);

Expand Down Expand Up @@ -153,6 +194,98 @@ library Assets {
emit IGateway.TokenSent(token, sender, destinationChain, destinationAddress, amount);
}

function _sendForeignTokenCosts(
ParaID destinationChain,
uint128 destinationChainFee,
uint128 maxDestinationChainFee
) internal view returns (Costs memory costs) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
if ($.assetHubParaID == destinationChain) {
costs.foreign = $.assetHubReserveTransferFee;
} else {
// Reduce the ability for users to perform arbitrage by exploiting a
// favourable exchange rate. For example supplying Ether
// and gaining a more valuable amount of DOT on the destination chain.
//
// Also prevents users from mistakenly sending more fees than would be required
// which has negative effects like draining AssetHub's sovereign account.
//
// For safety, `maxDestinationChainFee` should be less valuable
// than the gas cost to send tokens.
if (destinationChainFee > maxDestinationChainFee) {
revert InvalidDestinationFee();
}

// If the final destination chain is not AssetHub, then the fee needs to additionally
// include the cost of executing an XCM on the final destination parachain.
costs.foreign = $.assetHubReserveTransferFee + destinationChainFee;
}
// We don't charge any extra fees beyond delivery costs
costs.native = 0;
}

// @dev Transfer Polkadot-native tokens back to Polkadot
function _sendForeignToken(
bytes32 foreignID,
address token,
address sender,
ParaID destinationChain,
MultiAddress calldata destinationAddress,
uint128 destinationChainFee,
uint128 maxDestinationChainFee,
uint128 amount
) internal returns (Ticket memory ticket) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();

Token(token).burn(sender, amount);

ticket.dest = $.assetHubParaID;
ticket.costs = _sendForeignTokenCosts(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.SendForeignTokenToAssetHubAddress32(
foreignID, destinationAddress.asAddress32(), $.assetHubReserveTransferFee, amount
);
} else {
// AssetHub does not support 20-byte account IDs
revert Unsupported();
}
} else {
if (destinationChainFee == 0) {
revert InvalidDestinationFee();
}
if (destinationAddress.isAddress32()) {
// The receiver has a 32-byte account ID
ticket.payload = SubstrateTypes.SendForeignTokenToAddress32(
foreignID,
destinationChain,
destinationAddress.asAddress32(),
$.assetHubReserveTransferFee,
destinationChainFee,
amount
);
} else if (destinationAddress.isAddress20()) {
// The receiver has a 20-byte account ID
ticket.payload = SubstrateTypes.SendForeignTokenToAddress20(
foreignID,
destinationChain,
destinationAddress.asAddress20(),
$.assetHubReserveTransferFee,
destinationChainFee,
amount
);
} else {
revert Unsupported();
}
}

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

function registerTokenCosts() external view returns (Costs memory costs) {
return _registerTokenCosts();
}
Expand Down Expand Up @@ -188,4 +321,58 @@ library Assets {

emit IGateway.TokenRegistrationSent(token);
}

// @dev Register a new fungible Polkadot token for an agent
function registerForeignToken(bytes32 foreignTokenID, string memory name, string memory symbol, uint8 decimals)
external
{
AssetsStorage.Layout storage $ = AssetsStorage.layout();
if ($.tokenAddressOf[foreignTokenID] != address(0)) {
revert TokenAlreadyRegistered();
}
Token token = new Token(name, symbol, decimals);
TokenInfo memory info = TokenInfo({isRegistered: true, foreignID: foreignTokenID});

$.tokenAddressOf[foreignTokenID] = address(token);
$.tokenRegistry[address(token)] = info;

emit IGateway.ForeignTokenRegistered(foreignTokenID, address(token));
}

// @dev Mint foreign token from Polkadot
function mintForeignToken(bytes32 foreignTokenID, address recipient, uint256 amount) external {
address token = _ensureTokenAddressOf(foreignTokenID);
Token(token).mint(recipient, amount);
}

// @dev Transfer ERC20 to `recipient`
function transferNativeToken(address executor, address agent, address token, address recipient, uint128 amount)
external
{
bytes memory call = abi.encodeCall(AgentExecutor.transferToken, (token, recipient, amount));
(bool success,) = Agent(payable(agent)).invoke(executor, call);
if (!success) {
revert TokenTransferFailed();
}
}

// @dev Get token address by tokenID
function tokenAddressOf(bytes32 tokenID) external view returns (address) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
return $.tokenAddressOf[tokenID];
}

// @dev Get token address by tokenID
function _ensureTokenAddressOf(bytes32 tokenID) internal view returns (address) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
if ($.tokenAddressOf[tokenID] == address(0)) {
revert TokenNotRegistered();
}
return $.tokenAddressOf[tokenID];
}

function _isTokenRegistered(address token) internal view returns (bool) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
return $.tokenRegistry[token].isRegistered;
}
}
Loading
Loading