Skip to content

Commit

Permalink
Fee for Gateway.sendToken should cover all costs (#1015)
Browse files Browse the repository at this point in the history
Co-authored-by: ron <[email protected]>
  • Loading branch information
vgeddes and yrong authored Nov 30, 2023
1 parent 3964ee5 commit e4c9135
Show file tree
Hide file tree
Showing 50 changed files with 2,536 additions and 1,168 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
[submodule "contracts/lib/canonical-weth"]
path = contracts/lib/canonical-weth
url = https://github.com/Snowfork/canonical-weth
[submodule "contracts/lib/prb-math"]
path = contracts/lib/prb-math
url = https://github.com/PaulRBerg/prb-math
1 change: 1 addition & 0 deletions contracts/lib/prb-math
Submodule prb-math added at 77fa88
1 change: 1 addition & 0 deletions contracts/remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ ds-test/=lib/ds-test/src/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/
openzeppelin/=lib/openzeppelin-contracts/contracts/
prb/math/=lib/prb-math/
4 changes: 1 addition & 3 deletions contracts/src/AgentExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ contract AgentExecutor {
/// @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.
///
/// NOTE: There are no authorization checks here. Since this contract is only used for its code.
function execute(address, bytes memory data) external {
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));
Expand All @@ -29,7 +28,6 @@ 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.
///
/// NOTE: There are no authorization checks here. Since this contract is only used for its code.
function transferNative(address payable recipient, uint256 amount) external {
recipient.safeNativeTransfer(amount);
}
Expand Down
82 changes: 60 additions & 22 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, MultiAddress} from "./Types.sol";
import {ParaID, MultiAddress, Ticket, Costs} from "./Types.sol";
import {Address} from "./utils/Address.sol";

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

// This library requires state which must be initialized in the gateway's storage.
function initialize(uint256 registerTokenFee, uint256 sendTokenFee) external {
AssetsStorage.Layout storage $ = AssetsStorage.layout();

$.registerTokenFee = registerTokenFee;
$.sendTokenFee = sendTokenFee;
}

/// @dev transfer tokens from the sender to the specified
function _transferToAgent(address assetHubAgent, address token, address sender, uint128 amount) internal {
if (!token.isContract()) {
Expand All @@ -44,54 +36,100 @@ library Assets {
IERC20(token).safeTransferFrom(sender, assetHubAgent, amount);
}

function sendTokenCosts(ParaID assetHubParaID, ParaID destinationChain, uint128 destinationChainFee)
external
view
returns (Costs memory costs)
{
return _sendTokenCosts(assetHubParaID, destinationChain, destinationChainFee);
}

function _sendTokenCosts(ParaID assetHubParaID, ParaID destinationChain, uint128 destinationChainFee)
internal
view
returns (Costs memory costs)
{
AssetsStorage.Layout storage $ = AssetsStorage.layout();
if (assetHubParaID == destinationChain) {
costs.foreign = $.assetHubReserveTransferFee;
} else {
// 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;
}
costs.native = 0;
}

function sendToken(
ParaID assetHubParaID,
address assetHubAgent,
address token,
address sender,
ParaID destinationChain,
MultiAddress calldata destinationAddress,
uint128 destinationChainFee,
uint128 amount
) external returns (bytes memory payload, uint256 extraFee) {
) external returns (Ticket memory ticket) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();

_transferToAgent(assetHubAgent, token, sender, amount);
ticket.costs = _sendTokenCosts(assetHubParaID, destinationChain, destinationChainFee);

if (destinationChain == assetHubParaID) {
if (destinationAddress.isAddress32()) {
payload = SubstrateTypes.SendTokenToAssetHubAddress32(token, destinationAddress.asAddress32(), amount);
ticket.payload = SubstrateTypes.SendTokenToAssetHubAddress32(
token, destinationAddress.asAddress32(), $.assetHubReserveTransferFee, amount
);
} else {
// AssetHub does not support 20-byte account IDs
revert Unsupported();
}
} else {
if (destinationAddress.isAddress32()) {
payload = SubstrateTypes.SendTokenToAddress32(
token, destinationChain, destinationAddress.asAddress32(), amount
ticket.payload = SubstrateTypes.SendTokenToAddress32(
token,
destinationChain,
destinationAddress.asAddress32(),
$.assetHubReserveTransferFee,
destinationChainFee,
amount
);
} else if (destinationAddress.isAddress20()) {
payload = SubstrateTypes.SendTokenToAddress20(
token, destinationChain, destinationAddress.asAddress20(), amount
ticket.payload = SubstrateTypes.SendTokenToAddress20(
token,
destinationChain,
destinationAddress.asAddress20(),
$.assetHubReserveTransferFee,
destinationChainFee,
amount
);
} else {
revert Unsupported();
}
}
extraFee = $.sendTokenFee;

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

/// @dev Enqueues a create native token message to substrate.
/// @param token The ERC20 token address.
function registerToken(address token) external returns (bytes memory payload, uint256 extraFee) {
function registerTokenCosts() external view returns (Costs memory costs) {
return _registerTokenCosts();
}

function _registerTokenCosts() internal view returns (Costs memory costs) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
costs.foreign = $.assetHubCreateAssetFee;
costs.native = $.registerTokenFee;
}

/// @dev Enqueues a create native token message to substrate.
/// @param token The ERC20 token address.
function registerToken(address token) external returns (Ticket memory ticket) {
if (!token.isContract()) {
revert InvalidToken();
}

payload = SubstrateTypes.RegisterToken(token);
extraFee = $.registerTokenFee;
AssetsStorage.Layout storage $ = AssetsStorage.layout();
ticket.costs = _registerTokenCosts();
ticket.payload = SubstrateTypes.RegisterToken(token, $.assetHubCreateAssetFee);

emit IGateway.TokenRegistrationSent(token);
}
Expand Down
14 changes: 10 additions & 4 deletions contracts/src/DeployScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {AgentExecutor} from "./AgentExecutor.sol";
import {ChannelID, ParaID, OperatingMode} from "./Types.sol";
import {SafeNativeTransfer} from "./utils/SafeTransfer.sol";
import {stdJson} from "forge-std/StdJson.sol";
import {UD60x18, ud60x18} from "prb/math/src/UD60x18.sol";

contract DeployScript is Script {
using SafeNativeTransfer for address payable;
Expand Down Expand Up @@ -56,14 +57,17 @@ contract DeployScript is Script {
ParaID assetHubParaID = ParaID.wrap(uint32(vm.envUint("ASSET_HUB_PARAID")));
bytes32 assetHubAgentID = vm.envBytes32("ASSET_HUB_AGENT_ID");

uint8 foreignTokenDecimals = uint8(vm.envUint("FOREIGN_TOKEN_DECIMALS"));

AgentExecutor executor = new AgentExecutor();
Gateway gatewayLogic = new Gateway(
address(beefyClient),
address(executor),
bridgeHubParaID,
bridgeHubAgentID,
assetHubParaID,
assetHubAgentID
assetHubAgentID,
foreignTokenDecimals
);

bool rejectOutboundMessages = vm.envBool("REJECT_OUTBOUND_MESSAGES");
Expand All @@ -76,9 +80,11 @@ contract DeployScript is Script {

Gateway.Config memory config = Gateway.Config({
mode: defaultOperatingMode,
outboundFee: vm.envUint("DEFAULT_FEE"),
registerTokenFee: vm.envUint("REGISTER_NATIVE_TOKEN_FEE"),
sendTokenFee: vm.envUint("SEND_NATIVE_TOKEN_FEE")
deliveryCost: uint128(vm.envUint("DELIVERY_COST")),
registerTokenFee: uint128(vm.envUint("REGISTER_TOKEN_FEE")),
assetHubCreateAssetFee: uint128(vm.envUint("CREATE_ASSET_FEE")),
assetHubReserveTransferFee: uint128(vm.envUint("RESERVE_TRANSFER_FEE")),
exchangeRate: ud60x18(vm.envUint("EXCHANGE_RATE"))
});

GatewayProxy gateway = new GatewayProxy(address(gatewayLogic), abi.encode(config));
Expand Down
Loading

0 comments on commit e4c9135

Please sign in to comment.