Skip to content

Commit

Permalink
Improve API for sending tokens (#1009)
Browse files Browse the repository at this point in the history
  • Loading branch information
vgeddes authored Nov 16, 2023
1 parent 68c6a66 commit ae1358f
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 128 deletions.
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(
token, destinationChain, destinationAddress.asAddress20(), amount
);
} else {
revert Unsupported();
}
}
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 @@ -452,22 +452,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,
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

0 comments on commit ae1358f

Please sign in to comment.