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

Add max destination fee #1168

Merged
merged 18 commits into from
Apr 14, 2024
22 changes: 18 additions & 4 deletions contracts/src/Assets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ library Assets {
IERC20(token).safeTransferFrom(sender, agent, amount);
}

function sendTokenCosts(address token, ParaID destinationChain, uint128 destinationChainFee)
function sendTokenCosts(address token, ParaID destinationChain, uint128 destinationChainFee, uint128 maxDestinationChainFee)
external
view
returns (Costs memory costs)
Expand All @@ -53,10 +53,10 @@ library Assets {
revert TokenNotRegistered();
}

return _sendTokenCosts(destinationChain, destinationChainFee);
return _sendTokenCosts(destinationChain, destinationChainFee, maxDestinationChainFee);
}

function _sendTokenCosts(ParaID destinationChain, uint128 destinationChainFee)
function _sendTokenCosts(ParaID destinationChain, uint128 destinationChainFee, uint128 maxDestinationChainFee)
internal
view
returns (Costs memory costs)
Expand All @@ -65,6 +65,19 @@ library Assets {
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;
Expand All @@ -79,6 +92,7 @@ library Assets {
ParaID destinationChain,
MultiAddress calldata destinationAddress,
uint128 destinationChainFee,
uint128 maxDestinationChainFee,
uint128 amount
) external returns (Ticket memory ticket) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
Expand All @@ -92,7 +106,7 @@ library Assets {
_transferToAgent($.assetHubAgent, token, sender, amount);

ticket.dest = $.assetHubParaID;
ticket.costs = _sendTokenCosts(destinationChain, destinationChainFee);
ticket.costs = _sendTokenCosts(destinationChain, destinationChainFee, maxDestinationChainFee);

// Construct a message payload
if (destinationChain == $.assetHubParaID) {
Expand Down
11 changes: 10 additions & 1 deletion contracts/src/DeployGatewayLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@ contract DeployGatewayLogic is Script {
bytes32 bridgeHubAgentID = vm.envBytes32("BRIDGE_HUB_AGENT_ID");

uint8 foreignTokenDecimals = uint8(vm.envUint("FOREIGN_TOKEN_DECIMALS"));
uint128 maxDestinationFee = uint128(vm.envUint("RESERVE_TRANSFER_MAX_DESTINATION_FEE"));

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

new Gateway(
address(beefyClient),
address(executor),
bridgeHubParaID,
bridgeHubAgentID,
foreignTokenDecimals,
maxDestinationFee
);

vm.stopBroadcast();
}
Expand Down
8 changes: 7 additions & 1 deletion contracts/src/DeployScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,16 @@ contract DeployScript is Script {
bytes32 assetHubAgentID = vm.envBytes32("ASSET_HUB_AGENT_ID");

uint8 foreignTokenDecimals = uint8(vm.envUint("FOREIGN_TOKEN_DECIMALS"));
uint128 maxDestinationFee = uint128(vm.envUint("RESERVE_TRANSFER_MAX_DESTINATION_FEE"));

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

bool rejectOutboundMessages = vm.envBool("REJECT_OUTBOUND_MESSAGES");
Expand Down
15 changes: 12 additions & 3 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
// 2. Calling implementation function
uint256 DISPATCH_OVERHEAD_GAS = 10_000;

// The maximum fee that can be sent to a destination parachain to pay for execution (DOT).
// Has two functions:
// * Reduces the ability of users to perform arbitrage using a favourable exchange rate
// * Prevents users from mistakenly providing too much fees, which would drain AssetHub's
// sovereign account here on Ethereum.
uint128 internal immutable MAX_DESTINATION_FEE;

uint8 internal immutable FOREIGN_TOKEN_DECIMALS;

error InvalidProof();
Expand Down Expand Up @@ -101,7 +108,8 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
address agentExecutor,
ParaID bridgeHubParaID,
bytes32 bridgeHubAgentID,
uint8 foreignTokenDecimals
uint8 foreignTokenDecimals,
uint128 maxDestinationFee
) {
if (bridgeHubParaID == ParaID.wrap(0) || bridgeHubAgentID == 0) {
revert InvalidConstructorParams();
Expand All @@ -113,6 +121,7 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
BRIDGE_HUB_PARA_ID = bridgeHubParaID;
BRIDGE_HUB_AGENT_ID = bridgeHubAgentID;
FOREIGN_TOKEN_DECIMALS = foreignTokenDecimals;
MAX_DESTINATION_FEE = maxDestinationFee;
}

/// @dev Submit a message from Polkadot for verification and dispatch
Expand Down Expand Up @@ -395,7 +404,7 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
view
returns (uint256)
{
return _calculateFee(Assets.sendTokenCosts(token, destinationChain, destinationFee));
return _calculateFee(Assets.sendTokenCosts(token, destinationChain, destinationFee, MAX_DESTINATION_FEE));
}

// Transfer ERC20 tokens to a Polkadot parachain
Expand All @@ -407,7 +416,7 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
uint128 amount
) external payable {
_submitOutbound(
Assets.sendToken(token, msg.sender, destinationChain, destinationAddress, destinationFee, amount)
Assets.sendToken(token, msg.sender, destinationChain, destinationAddress, destinationFee, MAX_DESTINATION_FEE, amount)
);
}

Expand Down
14 changes: 12 additions & 2 deletions contracts/src/upgrades/rococo/GatewayV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,18 @@ contract GatewayV2 is Gateway {
address agentExecutor,
ParaID bridgeHubParaID,
bytes32 bridgeHubAgentID,
uint8 foreignTokenDecimals
) Gateway(beefyClient, agentExecutor, bridgeHubParaID, bridgeHubAgentID, foreignTokenDecimals) {}
uint8 foreignTokenDecimals,
uint128 destinationMaxTransferFee
)
Gateway(
beefyClient,
agentExecutor,
bridgeHubParaID,
bridgeHubAgentID,
foreignTokenDecimals,
destinationMaxTransferFee
)
{}

function initialize(bytes memory data) external override {
// Prevent initialization of storage in implementation contract
Expand Down
34 changes: 29 additions & 5 deletions contracts/test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ contract GatewayTest is Test {
uint128 public registerTokenFee = 0;
uint128 public sendTokenFee = 1e10;
uint128 public createTokenFee = 1e10;
uint128 public maxDestinationFee = 1e11;
claravanstaden marked this conversation as resolved.
Show resolved Hide resolved

MultiAddress public recipientAddress32;
MultiAddress public recipientAddress20;
Expand All @@ -97,8 +98,14 @@ contract GatewayTest is Test {

function setUp() public {
AgentExecutor executor = new AgentExecutor();
gatewayLogic =
new GatewayMock(address(0), address(executor), bridgeHubParaID, bridgeHubAgentID, foreignTokenDecimals);
gatewayLogic = new GatewayMock(
address(0),
address(executor),
bridgeHubParaID,
bridgeHubAgentID,
foreignTokenDecimals,
maxDestinationFee
);
Gateway.Config memory config = Gateway.Config({
mode: OperatingMode.Normal,
deliveryCost: outboundFee,
Expand Down Expand Up @@ -784,7 +791,7 @@ contract GatewayTest is Test {
SetTokenTransferFeesParams({
assetHubCreateAssetFee: createTokenFee * 2,
registerTokenFee: registerTokenFee,
assetHubReserveTransferFee: sendTokenFee
assetHubReserveTransferFee: sendTokenFee * 3
})
)
);
Expand Down Expand Up @@ -819,7 +826,7 @@ contract GatewayTest is Test {
assertEq(fee, 20000000000000001);
}

function testSendTokenToForeignDestWithInvalidFee() public {
function testSendTokenWithZeroDestinationFee() public {
// Let gateway lock up to 1 tokens
token.approve(address(gateway), 1);

Expand All @@ -829,10 +836,27 @@ contract GatewayTest is Test {
// register token first
uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee();
IGateway(address(gateway)).registerToken{value: fee}(address(token));

fee = IGateway(address(gateway)).quoteSendTokenFee(address(token), destPara, 0);

vm.expectRevert(Assets.InvalidDestinationFee.selector);
IGateway(address(gateway)).sendToken{value: fee}(address(token), destPara, recipientAddress32, 0, 1);
}

function testSendTokenWithLargeDestinationFee() public {
// Let gateway lock up to 1 tokens
token.approve(address(gateway), 1);

// Multilocation for recipient
ParaID destPara = ParaID.wrap(2043);

// register token first
uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee();
IGateway(address(gateway)).registerToken{value: fee}(address(token));

vm.expectRevert(Assets.InvalidDestinationFee.selector);
IGateway(address(gateway)).quoteSendTokenFee(address(token), destPara, maxDestinationFee + 1);

vm.expectRevert(Assets.InvalidDestinationFee.selector);
IGateway(address(gateway)).sendToken{value: fee}(address(token), destPara, recipientAddress32, maxDestinationFee + 1, 1);
}
}
14 changes: 12 additions & 2 deletions contracts/test/mocks/GatewayMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,18 @@ contract GatewayMock is Gateway {
address agentExecutor,
ParaID bridgeHubParaID,
bytes32 bridgeHubHubAgentID,
uint8 foreignTokenDecimals
) Gateway(beefyClient, agentExecutor, bridgeHubParaID, bridgeHubHubAgentID, foreignTokenDecimals) {}
uint8 foreignTokenDecimals,
uint128 maxDestinationFee
)
Gateway(
beefyClient,
agentExecutor,
bridgeHubParaID,
bridgeHubHubAgentID,
foreignTokenDecimals,
maxDestinationFee
)
{}

function agentExecutePublic(bytes calldata params) external {
this.agentExecute(params);
Expand Down
4 changes: 4 additions & 0 deletions smoketest/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ lazy_static! {
.unwrap_or("2500000000000000".to_string())
.parse()
.unwrap();
pub static ref FEE_MULTIPLIER: u128 = env::var("FEE_MULTIPLIER")
.unwrap_or("1000000000000000000".to_string())
.parse()
.unwrap();
pub static ref FEE_PER_GAS: u64 =
env::var("FEE_PER_GAS").unwrap_or("20000000000".to_string()).parse().unwrap();
pub static ref LOCAL_REWARD: u128 =
Expand Down
1 change: 1 addition & 0 deletions smoketest/tests/set_pricing_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async fn set_pricing_params() {
exchange_rate: FixedU128(*EXCHANGE_RATE),
rewards: Rewards { local: *LOCAL_REWARD, remote: U256([*REMOTE_REWARD, 0, 0, 0]) },
fee_per_gas: U256([*FEE_PER_GAS, 0, 0, 0]),
multiplier: FixedU128(*FEE_MULTIPLIER),
})
.encode_call_data(&test_clients.bridge_hub_client.metadata())
.expect("encoded call");
Expand Down
1 change: 1 addition & 0 deletions web/packages/test/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ rococo-local.json
.pnp.*
ethereum-goerli
testdata
beacon-state
claravanstaden marked this conversation as resolved.
Show resolved Hide resolved
.env
1 change: 1 addition & 0 deletions web/packages/test/scripts/set-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export REJECT_OUTBOUND_MESSAGES="${REJECT_OUTBOUND_MESSAGES:-false}"
export REGISTER_TOKEN_FEE="${REGISTER_TOKEN_FEE:-200000000000000000}"
export CREATE_ASSET_FEE="${CREATE_ASSET_FEE:-10000000000}"
export RESERVE_TRANSFER_FEE="${RESERVE_TRANSFER_FEE:-10000000000}"
export RESERVE_TRANSFER_MAX_DESTINATION_FEE="${RESERVE_TRANSFER_MAX_DESTINATION_FEE:-10000000000000}"

## Pricing Parameters
export EXCHANGE_RATE="${EXCHANGE_RATE:-2500000000000000}"
Expand Down
Loading