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

Improve Gateway API for sending tokens to Polkadot #1009

Merged
merged 1 commit into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
69 changes: 31 additions & 38 deletions contracts/src/Assets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {SafeTokenTransferFrom} from "./utils/SafeTransfer.sol";

import {AssetsStorage} from "./storage/AssetsStorage.sol";
import {SubstrateTypes} from "./SubstrateTypes.sol";
import {ParaID} from "./Types.sol";
import {ParaID, MultiAddress} from "./Types.sol";
import {Address} from "./utils/Address.sol";

/// @title Library for implementing Ethereum->Polkadot ERC20 transfers.
Expand All @@ -21,6 +21,7 @@ library Assets {
error InvalidToken();
error InvalidAmount();
error InvalidDestination();
error Unsupported();

// This library requires state which must be initialized in the gateway's storage.
function initialize(uint256 registerTokenFee, uint256 sendTokenFee) external {
Expand All @@ -30,26 +31,17 @@ library Assets {
$.sendTokenFee = sendTokenFee;
}

function sendToken(
ParaID assetHubParaID,
address assetHubAgent,
address token,
address sender,
ParaID destinationChain,
bytes32 destinationAddress,
uint128 amount
) external returns (bytes memory payload, uint256 extraFee) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
/// @dev transfer tokens from the sender to the specified
function _transferToAgent(address assetHubAgent, address token, address sender, uint128 amount) internal {
if (!token.isContract()) {
revert InvalidToken();
}

_transferToAgent(assetHubAgent, token, sender, amount);
if (destinationChain == assetHubParaID) {
payload = SubstrateTypes.SendToken(token, destinationAddress, amount);
} else {
payload = SubstrateTypes.SendToken(token, destinationChain, destinationAddress, amount);
if (amount == 0) {
revert InvalidAmount();
}
extraFee = $.sendTokenFee;

emit IGateway.TokenSent(sender, token, destinationChain, abi.encodePacked(destinationAddress), amount);
IERC20(token).safeTransferFrom(sender, assetHubAgent, amount);
}

function sendToken(
Expand All @@ -58,34 +50,35 @@ library Assets {
address token,
address sender,
ParaID destinationChain,
address destinationAddress,
MultiAddress calldata destinationAddress,
uint128 amount
) external returns (bytes memory payload, uint256 extraFee) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
if (destinationChain == assetHubParaID) {
// AssetHub parachain doesn't support Ethereum-style addresses
revert InvalidDestination();
}

_transferToAgent(assetHubAgent, token, sender, amount);

payload = SubstrateTypes.SendToken(address(this), token, destinationChain, destinationAddress, amount);
extraFee = $.sendTokenFee;

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

/// @dev transfer tokens from the sender to the specified
function _transferToAgent(address assetHubAgent, address token, address sender, uint128 amount) internal {
if (!token.isContract()) {
revert InvalidToken();
}

if (amount == 0) {
revert InvalidAmount();
if (destinationChain == assetHubParaID) {
if (destinationAddress.isAddress32()) {
payload = SubstrateTypes.SendTokenToAssetHubAddress32(token, destinationAddress.asAddress32(), amount);
} else {
revert Unsupported();
}
} else {
if (destinationAddress.isAddress32()) {
payload = SubstrateTypes.SendTokenToAddress32(
token, destinationChain, destinationAddress.asAddress32(), amount
);
} else if (destinationAddress.isAddress20()) {
payload = SubstrateTypes.SendTokenToAddress20(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh will this make it easier for native Moonbeam support?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i also see we had it already, so just a refactor

token, destinationChain, destinationAddress.asAddress20(), amount
);
} else {
revert Unsupported();
}
Comment on lines +61 to +77
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the destinationAddress is Index?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not supported right now, requires more plumbing on the BridgeHub side, to convert the message into the right XCM.

Not many parachains use pallet_indices anyway.

}
extraFee = $.sendTokenFee;

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

/// @dev Enqueues a create native token message to substrate.
Expand Down
19 changes: 2 additions & 17 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Verification} from "./Verification.sol";
import {Assets} from "./Assets.sol";
import {AgentExecutor} from "./AgentExecutor.sol";
import {Agent} from "./Agent.sol";
import {Channel, ChannelID, InboundMessage, OperatingMode, ParaID, Command} from "./Types.sol";
import {Channel, ChannelID, InboundMessage, OperatingMode, ParaID, Command, MultiAddress} from "./Types.sol";
import {IGateway} from "./interfaces/IGateway.sol";
import {IInitializable} from "./interfaces/IInitializable.sol";
import {ERC1967} from "./utils/ERC1967.sol";
Expand Down Expand Up @@ -454,22 +454,7 @@ contract Gateway is IGateway, IInitializable {
}

// Transfer ERC20 tokens to a Polkadot parachain
function sendToken(address token, ParaID destinationChain, bytes32 destinationAddress, uint128 amount)
external
payable
{
CoreStorage.Layout storage $ = CoreStorage.layout();
address assetHubAgent = $.agents[ASSET_HUB_AGENT_ID];

(bytes memory payload, uint256 extraFee) = Assets.sendToken(
ASSET_HUB_PARA_ID, assetHubAgent, token, msg.sender, destinationChain, destinationAddress, amount
);

_submitOutbound(ASSET_HUB_PARA_ID, payload, extraFee);
}

// Transfer ERC20 tokens to a Polkadot parachain
function sendToken(address token, ParaID destinationChain, address destinationAddress, uint128 amount)
function sendToken(address token, ParaID destinationChain, MultiAddress calldata destinationAddress, uint128 amount)
external
payable
{
Expand Down
53 changes: 53 additions & 0 deletions contracts/src/MultiAddress.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]>
pragma solidity 0.8.22;

using {isIndex, asIndex, isAddress32, asAddress32, isAddress20, asAddress20} for MultiAddress global;

/// @dev An address for an on-chain account
struct MultiAddress {
Kind kind;
bytes data;
}

enum Kind {
Index,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this Index kind? A parachain ID?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you install pallet_indices on a substrate chain, then its possible for the chain to map a u32 to a AccountId under the hood.

So its basically a "short" id for accountid.

In fact Substrate allows all sorts of mappings:
https://paritytech.github.io/polkadot-sdk/master/sp_runtime/enum.MultiAddress.html

Address32,
Address20
}

function isIndex(MultiAddress calldata multiAddress) pure returns (bool) {
return multiAddress.kind == Kind.Index;
}

function asIndex(MultiAddress calldata multiAddress) pure returns (uint32) {
return abi.decode(multiAddress.data, (uint32));
}

function isAddress32(MultiAddress calldata multiAddress) pure returns (bool) {
return multiAddress.kind == Kind.Address32;
}

function asAddress32(MultiAddress calldata multiAddress) pure returns (bytes32) {
return bytes32(multiAddress.data);
}

function isAddress20(MultiAddress calldata multiAddress) pure returns (bool) {
return multiAddress.kind == Kind.Address20;
}

function asAddress20(MultiAddress calldata multiAddress) pure returns (bytes20) {
return bytes20(multiAddress.data);
}

function multiAddressFromUint32(uint32 id) pure returns (MultiAddress memory) {
return MultiAddress({kind: Kind.Index, data: abi.encode(id)});
}

function multiAddressFromBytes32(bytes32 id) pure returns (MultiAddress memory) {
return MultiAddress({kind: Kind.Address32, data: bytes.concat(id)});
}

function multiAddressFromBytes20(bytes20 id) pure returns (MultiAddress memory) {
return MultiAddress({kind: Kind.Address20, data: bytes.concat(id)});
}
17 changes: 11 additions & 6 deletions contracts/src/SubstrateTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ library SubstrateTypes {
* @dev SCALE-encodes `router_primitives::inbound::VersionedMessage` containing payload
* `NativeTokensMessage::Mint`
*/
// solhint-disable-next-line func-name-mixedcase
function SendToken(address token, bytes32 recipient, uint128 amount) internal view returns (bytes memory) {
// destination is AccountID32 address on AssetHub
function SendTokenToAssetHubAddress32(address token, bytes32 recipient, uint128 amount)
internal
view
returns (bytes memory)
{
return bytes.concat(
bytes1(0x00),
ScaleCodec.encodeU64(uint64(block.chainid)),
Expand All @@ -79,7 +83,8 @@ library SubstrateTypes {
);
}

function SendToken(address token, ParaID paraID, bytes32 recipient, uint128 amount)
// destination is AccountID32 address
function SendTokenToAddress32(address token, ParaID paraID, bytes32 recipient, uint128 amount)
internal
view
returns (bytes memory)
Expand All @@ -96,7 +101,8 @@ library SubstrateTypes {
);
}

function SendToken(address gateway, address token, ParaID paraID, address recipient, uint128 amount)
// destination is AccountID20 address
function SendTokenToAddress20(address token, ParaID paraID, bytes20 recipient, uint128 amount)
internal
view
returns (bytes memory)
Expand All @@ -105,11 +111,10 @@ library SubstrateTypes {
bytes1(0x00),
ScaleCodec.encodeU64(uint64(block.chainid)),
bytes1(0x01),
SubstrateTypes.H160(gateway),
SubstrateTypes.H160(token),
bytes1(0x02),
ScaleCodec.encodeU32(uint32(ParaID.unwrap(paraID))),
abi.encodePacked(recipient),
recipient,
ScaleCodec.encodeU128(amount)
);
}
Expand Down
4 changes: 4 additions & 0 deletions contracts/src/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]>
pragma solidity 0.8.22;

import {
MultiAddress, multiAddressFromUint32, multiAddressFromBytes32, multiAddressFromBytes20
} from "./MultiAddress.sol";

type ParaID is uint32;

using {ParaIDEq as ==, ParaIDNe as !=, into} for ParaID global;
Expand Down
15 changes: 7 additions & 8 deletions contracts/src/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]>
pragma solidity 0.8.22;

import {OperatingMode, InboundMessage, ParaID, ChannelID} from "../Types.sol";
import {OperatingMode, InboundMessage, ParaID, ChannelID, MultiAddress} from "../Types.sol";
import {Verification} from "../Verification.sol";

interface IGateway {
Expand Down Expand Up @@ -65,7 +65,11 @@ interface IGateway {

/// @dev Emitted once the funds are locked and an outbound message is successfully queued.
event TokenSent(
address indexed token, address indexed sender, ParaID destinationChain, bytes destinationAddress, uint128 amount
address indexed token,
address indexed sender,
ParaID destinationChain,
MultiAddress destinationAddress,
uint128 amount
);

/// @dev Emitted when a command is sent to register a new wrapped token on AssetHub
Expand All @@ -79,12 +83,7 @@ interface IGateway {
function registerToken(address token) external payable;

/// @dev Send ERC20 tokens to parachain `destinationChain` and deposit into account `destinationAddress`
function sendToken(address token, ParaID destinationChain, bytes32 destinationAddress, uint128 amount)
external
payable;

/// @dev Send ERC20 tokens to parachain `destinationChain` and deposit into account `destinationAddress`
function sendToken(address token, ParaID destinationChain, address destinationAddress, uint128 amount)
function sendToken(address token, ParaID destinationChain, MultiAddress calldata destinationAddress, uint128 amount)
external
payable;
}
Loading