Skip to content

Commit

Permalink
Add exchage rate to gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
vgeddes committed Nov 23, 2023
1 parent 26c9098 commit 2fcc415
Show file tree
Hide file tree
Showing 13 changed files with 274 additions and 102 deletions.
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/
31 changes: 24 additions & 7 deletions contracts/src/Assets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ library Assets {
error Unsupported();

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

$.registerTokenFee = _registerTokenFee;
Expand All @@ -44,14 +44,18 @@ library Assets {
IERC20(token).safeTransferFrom(sender, assetHubAgent, amount);
}

function sendTokenFee(ParaID assetHubParaID, ParaID destinationChain) external view returns (uint256) {
function sendTokenFee(ParaID assetHubParaID, ParaID destinationChain, uint128 destinationChainFee)
external
view
returns (uint256)
{
AssetsStorage.Layout storage $ = AssetsStorage.layout();
if (assetHubParaID == destinationChain) {
return $.sendTokenFee;
}
// 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.
return 2 * $.sendTokenFee;
return $.sendTokenFee + destinationChainFee;
}

function sendToken(
Expand All @@ -61,6 +65,7 @@ library Assets {
address sender,
ParaID destinationChain,
MultiAddress calldata destinationAddress,
uint128 destinationChainFee,
uint128 amount
) external returns (bytes memory payload, uint256 extraFee) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
Expand All @@ -69,7 +74,9 @@ library Assets {

if (destinationChain == assetHubParaID) {
if (destinationAddress.isAddress32()) {
payload = SubstrateTypes.SendTokenToAssetHubAddress32(token, destinationAddress.asAddress32(), amount);
payload = SubstrateTypes.SendTokenToAssetHubAddress32(
token, destinationAddress.asAddress32(), $.sendTokenFee, amount
);
} else {
// AssetHub does not support 20-byte account IDs
revert Unsupported();
Expand All @@ -78,18 +85,28 @@ library Assets {
} else {
if (destinationAddress.isAddress32()) {
payload = SubstrateTypes.SendTokenToAddress32(
token, destinationChain, destinationAddress.asAddress32(), amount
token,
destinationChain,
destinationAddress.asAddress32(),
$.sendTokenFee,
destinationChainFee,
amount
);
} else if (destinationAddress.isAddress20()) {
payload = SubstrateTypes.SendTokenToAddress20(
token, destinationChain, destinationAddress.asAddress20(), amount
token,
destinationChain,
destinationAddress.asAddress20(),
$.sendTokenFee,
destinationChainFee,
amount
);
} else {
revert Unsupported();
}
// 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.
extraFee = 2 * $.sendTokenFee;
extraFee = $.sendTokenFee + destinationChainFee;
}

emit IGateway.TokenSent(sender, token, destinationChain, destinationAddress, amount);
Expand Down
15 changes: 11 additions & 4 deletions contracts/src/DeployScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ 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, convert} from "prb/math/src/UD60x18.sol";

contract DeployScript is Script {
using SafeNativeTransfer for address payable;
using stdJson for string;

UD60x18 internal dotToEthDecimals;

function setUp() public {}

function run() public {
Expand Down Expand Up @@ -56,14 +59,17 @@ contract DeployScript is Script {
ParaID assetHubParaID = ParaID.wrap(uint32(vm.envUint("ASSET_HUB_PARAID")));
bytes32 assetHubAgentID = vm.envBytes32("ASSET_HUB_AGENT_ID");

dotToEthDecimals = convert(vm.envUint("DOT_TO_ETH_DECIMALS"));

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

bool rejectOutboundMessages = vm.envBool("REJECT_OUTBOUND_MESSAGES");
Expand All @@ -76,9 +82,10 @@ 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")
fee: uint128(vm.envUint("DEFAULT_FEE")),
registerTokenFee: uint128(vm.envUint("REGISTER_NATIVE_TOKEN_FEE")),
sendTokenFee: uint128(vm.envUint("SEND_NATIVE_TOKEN_FEE")),
exchangeRate: ud60x18(0.0025e18)
});

GatewayProxy gateway = new GatewayProxy(address(gatewayLogic), abi.encode(config));
Expand Down
104 changes: 74 additions & 30 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {ScaleCodec} from "./utils/ScaleCodec.sol";
import {CoreStorage} from "./storage/CoreStorage.sol";
import {AssetsStorage} from "./storage/AssetsStorage.sol";

import {UD60x18, ud60x18, convert} from "prb/math/src/UD60x18.sol";

contract Gateway is IGateway, IInitializable {
using Address for address;
using SafeNativeTransfer for address payable;
Expand Down Expand Up @@ -52,6 +54,8 @@ contract Gateway is IGateway, IInitializable {
// 2. Calling implementation function
uint256 DISPATCH_OVERHEAD_GAS = 10_000;

UD60x18 internal immutable DOT_TO_ETH_DECIMALS;

error InvalidProof();
error InvalidNonce();
error NotEnoughGas();
Expand Down Expand Up @@ -82,7 +86,8 @@ contract Gateway is IGateway, IInitializable {
ParaID bridgeHubParaID,
bytes32 bridgeHubAgentID,
ParaID assetHubParaID,
bytes32 assetHubAgentID
bytes32 assetHubAgentID,
UD60x18 dotToEthDecimals
) {
if (
bridgeHubParaID == ParaID.wrap(0) || bridgeHubAgentID == 0 || assetHubParaID == ParaID.wrap(0)
Expand All @@ -98,12 +103,13 @@ contract Gateway is IGateway, IInitializable {
BRIDGE_HUB_AGENT_ID = bridgeHubAgentID;
ASSET_HUB_PARA_ID = assetHubParaID;
ASSET_HUB_AGENT_ID = assetHubAgentID;
DOT_TO_ETH_DECIMALS = dotToEthDecimals;
}

/// @dev Submit a message from Polkadot for verification and dispatch
/// @param message A message produced by the OutboundQueue pallet on BridgeHub
/// @param leafProof A message proof used to verify that the message is in the merkle tree committed by the OutboundQueue pallet
/// @param headerProof A proof used to verify that the commitment was included in a BridgeHub header that was finalized by BEEFY.
/// @param headerProof A proof that the commitment is included in parachain header that was finalized by BEEFY.
function submitInbound(
InboundMessage calldata message,
bytes32[] calldata leafProof,
Expand Down Expand Up @@ -225,9 +231,9 @@ contract Gateway is IGateway, IInitializable {
return (ch.inboundNonce, ch.outboundNonce);
}

function channelOutboundFeeOf(ChannelID channelID) external view returns (uint256) {
function channelFeeOf(ChannelID channelID) external view returns (uint256) {
Channel storage ch = _ensureChannel(channelID);
return ch.outboundFee;
return calculateLocalFee(ch.exchangeRate, ch.fee);
}

function agentOf(bytes32 agentID) external view returns (address) {
Expand Down Expand Up @@ -317,7 +323,7 @@ contract Gateway is IGateway, IInitializable {
ch.agent = agent;
ch.inboundNonce = 0;
ch.outboundNonce = 0;
ch.outboundFee = params.outboundFee;
ch.fee = params.outboundFee;

emit ChannelCreated(params.channelID);
}
Expand All @@ -327,8 +333,11 @@ contract Gateway is IGateway, IInitializable {
ChannelID channelID;
/// @dev The new operating mode
OperatingMode mode;
/// @dev The new fee for accepting outbound messages
uint256 outboundFee;
/// @dev The new fee in DOT for accepting outbound messages
uint128 fee;
/// @dev The new ETH/DOT exchange rate
uint128 exchangeRateNumerator;
uint128 exchangeRateDenominator;
}

/// @dev Update the configuration for a channel
Expand All @@ -343,7 +352,8 @@ contract Gateway is IGateway, IInitializable {
}

ch.mode = params.mode;
ch.outboundFee = params.outboundFee;
ch.fee = params.fee;
ch.exchangeRate = convert(params.exchangeRateNumerator).div(convert(params.exchangeRateDenominator));

emit ChannelUpdated(params.channelID);
}
Expand Down Expand Up @@ -420,10 +430,10 @@ contract Gateway is IGateway, IInitializable {
}

struct SetTokenTransferFeesParams {
/// @dev The fee for register token
uint256 register;
/// @dev The fee for send token from ethereum to polkadot
uint256 send;
/// @dev The remote fee (DOT) for registering a token on AssetHub
uint128 register;
/// @dev The remote fee (DOT) for send tokens to AssetHub
uint128 send;
}

// @dev Set the operating mode of the gateway
Expand All @@ -442,7 +452,10 @@ contract Gateway is IGateway, IInitializable {
// Total fee for registering a token
function registerTokenFee() external view returns (Fee memory) {
Channel storage channel = _ensureChannel(ASSET_HUB_PARA_ID.into());
return Fee({bridge: channel.outboundFee, xcm: Assets.registerTokenFee()});
return Fee({
bridge: calculateLocalFee(channel.exchangeRate, channel.fee),
xcm: calculateLocalFee(channel.exchangeRate, Assets.registerTokenFee())
});
}

// Register a token on AssetHub
Expand All @@ -453,21 +466,40 @@ contract Gateway is IGateway, IInitializable {
}

// Total fee for sending a token
function sendTokenFee(address, ParaID destinationChain) external view returns (Fee memory) {
function sendTokenFee(address, ParaID destinationChain, uint128 destinationFee)
external
view
returns (Fee memory)
{
Channel storage channel = _ensureChannel(ASSET_HUB_PARA_ID.into());
return Fee({bridge: channel.outboundFee, xcm: Assets.sendTokenFee(ASSET_HUB_PARA_ID, destinationChain)});
return Fee({
bridge: calculateLocalFee(channel.exchangeRate, channel.fee),
xcm: calculateLocalFee(
channel.exchangeRate, Assets.sendTokenFee(ASSET_HUB_PARA_ID, destinationChain, destinationFee)
)
});
}

// Transfer ERC20 tokens to a Polkadot parachain
function sendToken(address token, ParaID destinationChain, MultiAddress calldata destinationAddress, uint128 amount)
external
payable
{
function sendToken(
address token,
ParaID destinationChain,
MultiAddress calldata destinationAddress,
uint128 destinationFee,
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
ASSET_HUB_PARA_ID,
assetHubAgent,
token,
msg.sender,
destinationChain,
destinationAddress,
destinationFee,
amount
);

_submitOutbound(ASSET_HUB_PARA_ID, payload, extraFee);
Expand All @@ -485,6 +517,13 @@ contract Gateway is IGateway, IInitializable {
return Verification.verifyCommitment(BEEFY_CLIENT, BRIDGE_HUB_PARA_ID_ENCODED, commitment, proof);
}

function calculateLocalFee(UD60x18 exchangeRate, uint256 remoteFee) internal pure returns (uint256) {
UD60x18 remoteFeeFP = convert(remoteFee);
UD60x18 localFeeFP = remoteFeeFP.mul(exchangeRate).mul(convert(1e8));
uint256 localFee = convert(localFeeFP);
return localFee;
}

// Submit an outbound message to Polkadot
function _submitOutbound(ParaID dest, bytes memory payload, uint256 extraFee) internal {
ChannelID channelID = dest.into();
Expand All @@ -493,7 +532,7 @@ contract Gateway is IGateway, IInitializable {
// Ensure outbound messaging is allowed
_ensureOutboundMessagingEnabled(channel);

uint256 totalFee = channel.outboundFee + extraFee;
uint256 totalFee = calculateLocalFee(channel.exchangeRate, channel.fee + extraFee);

// Ensure the user has enough funds for this message to be accepted
if (msg.value < totalFee) {
Expand Down Expand Up @@ -565,12 +604,14 @@ contract Gateway is IGateway, IInitializable {
// Initial configuration for bridge
struct Config {
OperatingMode mode;
/// @dev The fee charged to users for submitting outbound messages.
uint256 outboundFee;
/// @dev The extra fee charged for registering tokens.
uint256 registerTokenFee;
/// @dev The extra fee charged for sending tokens.
uint256 sendTokenFee;
/// @dev The fee charged to users for submitting outbound messages (DOT)
uint128 fee;
/// @dev The extra fee charged for registering tokens (DOT)
uint128 registerTokenFee;
/// @dev The extra fee charged for sending tokens (DOT)
uint128 sendTokenFee;
/// @dev The ETH/DOT exchange rate
UD60x18 exchangeRate;
}

/// @dev Initialize storage in the gateway
Expand All @@ -597,7 +638,8 @@ contract Gateway is IGateway, IInitializable {
agent: bridgeHubAgent,
inboundNonce: 0,
outboundNonce: 0,
outboundFee: config.outboundFee
fee: config.fee,
exchangeRate: config.exchangeRate
});

// Initialize channel for secondary governance track
Expand All @@ -606,7 +648,8 @@ contract Gateway is IGateway, IInitializable {
agent: bridgeHubAgent,
inboundNonce: 0,
outboundNonce: 0,
outboundFee: config.outboundFee
fee: config.fee,
exchangeRate: config.exchangeRate
});

// Initialize agent for for AssetHub
Expand All @@ -619,7 +662,8 @@ contract Gateway is IGateway, IInitializable {
agent: assetHubAgent,
inboundNonce: 0,
outboundNonce: 0,
outboundFee: config.outboundFee
fee: config.fee,
exchangeRate: config.exchangeRate
});

Assets.initialize(config.registerTokenFee, config.sendTokenFee);
Expand Down
Loading

0 comments on commit 2fcc415

Please sign in to comment.