diff --git a/contracts/src/Assets.sol b/contracts/src/Assets.sol index eece801d14..cdf54a8b18 100644 --- a/contracts/src/Assets.sol +++ b/contracts/src/Assets.sol @@ -42,21 +42,22 @@ library Assets { IERC20(token).safeTransferFrom(sender, agent, amount); } - function sendTokenCosts(address token, ParaID destinationChain, uint128 destinationChainFee) - external - view - returns (Costs memory costs) - { + function sendTokenCosts( + address token, + ParaID destinationChain, + uint128 destinationChainFee, + uint128 maxDestinationChainFee + ) external view returns (Costs memory costs) { AssetsStorage.Layout storage $ = AssetsStorage.layout(); TokenInfo storage info = $.tokenRegistry[token]; if (!info.isRegistered) { 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) @@ -66,7 +67,7 @@ library Assets { costs.foreign = $.assetHubReserveTransferFee; } else { // Destination fee cannot be zero. MultiAssets are not allowed to be zero in xcm v4. - if (destinationChainFee == 0 || destinationChainFee > $.destinationMaxTransferFee) { + if (destinationChainFee == 0 || destinationChainFee > maxDestinationChainFee) { revert InvalidDestinationFee(); } @@ -84,7 +85,8 @@ library Assets { ParaID destinationChain, MultiAddress calldata destinationAddress, uint128 destinationChainFee, - uint128 amount + uint128 amount, + uint128 maxDestinationChainFee ) external returns (Ticket memory ticket) { AssetsStorage.Layout storage $ = AssetsStorage.layout(); @@ -97,7 +99,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) { diff --git a/contracts/src/DeployGatewayLogic.sol b/contracts/src/DeployGatewayLogic.sol index 8c5cc70282..acc95852fb 100644 --- a/contracts/src/DeployGatewayLogic.sol +++ b/contracts/src/DeployGatewayLogic.sol @@ -24,9 +24,17 @@ contract DeployGatewayLogic is Script { bytes32 bridgeHubAgentID = vm.envBytes32("BRIDGE_HUB_AGENT_ID"); uint8 foreignTokenDecimals = uint8(vm.envUint("FOREIGN_TOKEN_DECIMALS")); + uint128 destinationMaxTransferFee = uint128(vm.envUint("RESERVE_TRANSFER_MAX_DESTINATION_FEE")); AgentExecutor executor = new AgentExecutor(); - new Gateway(address(beefyClient), address(executor), bridgeHubParaID, bridgeHubAgentID, foreignTokenDecimals); + new Gateway( + address(beefyClient), + address(executor), + bridgeHubParaID, + bridgeHubAgentID, + foreignTokenDecimals, + destinationMaxTransferFee + ); vm.stopBroadcast(); } diff --git a/contracts/src/DeployScript.sol b/contracts/src/DeployScript.sol index 9d6254e6e8..2a43d8dd2d 100644 --- a/contracts/src/DeployScript.sol +++ b/contracts/src/DeployScript.sol @@ -58,10 +58,16 @@ contract DeployScript is Script { bytes32 assetHubAgentID = vm.envBytes32("ASSET_HUB_AGENT_ID"); uint8 foreignTokenDecimals = uint8(vm.envUint("FOREIGN_TOKEN_DECIMALS")); + uint128 destinationMaxTransferFee = 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, + destinationMaxTransferFee ); bool rejectOutboundMessages = vm.envBool("REJECT_OUTBOUND_MESSAGES"); @@ -80,7 +86,6 @@ contract DeployScript is Script { assetHubAgentID: assetHubAgentID, assetHubCreateAssetFee: uint128(vm.envUint("CREATE_ASSET_FEE")), assetHubReserveTransferFee: uint128(vm.envUint("RESERVE_TRANSFER_FEE")), - destinationMaxTransferFee: uint128(vm.envUint("RESERVE_TRANSFER_MAX_DESTINATION_FEE")), exchangeRate: ud60x18(vm.envUint("EXCHANGE_RATE")), multiplier: ud60x18(vm.envUint("FEE_MULTIPLIER")) }); diff --git a/contracts/src/Gateway.sol b/contracts/src/Gateway.sol index 3e5d3e37f1..a5ce6346e7 100644 --- a/contracts/src/Gateway.sol +++ b/contracts/src/Gateway.sol @@ -69,6 +69,9 @@ contract Gateway is IGateway, IInitializable { // 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) + uint128 internal immutable MAX_DESTINATION_TRANSFER_FEE; + uint8 internal immutable FOREIGN_TOKEN_DECIMALS; error InvalidProof(); @@ -101,7 +104,8 @@ contract Gateway is IGateway, IInitializable { address agentExecutor, ParaID bridgeHubParaID, bytes32 bridgeHubAgentID, - uint8 foreignTokenDecimals + uint8 foreignTokenDecimals, + uint128 destinationMaxTransferFee ) { if (bridgeHubParaID == ParaID.wrap(0) || bridgeHubAgentID == 0) { revert InvalidConstructorParams(); @@ -113,6 +117,7 @@ contract Gateway is IGateway, IInitializable { BRIDGE_HUB_PARA_ID = bridgeHubParaID; BRIDGE_HUB_AGENT_ID = bridgeHubAgentID; FOREIGN_TOKEN_DECIMALS = foreignTokenDecimals; + MAX_DESTINATION_TRANSFER_FEE = destinationMaxTransferFee; } /// @dev Submit a message from Polkadot for verification and dispatch @@ -417,7 +422,8 @@ contract Gateway is IGateway, IInitializable { view returns (uint256) { - return _calculateFee(Assets.sendTokenCosts(token, destinationChain, destinationFee)); + return + _calculateFee(Assets.sendTokenCosts(token, destinationChain, destinationFee, MAX_DESTINATION_TRANSFER_FEE)); } // Transfer ERC20 tokens to a Polkadot parachain @@ -429,7 +435,15 @@ contract Gateway is IGateway, IInitializable { uint128 amount ) external payable { _submitOutbound( - Assets.sendToken(token, msg.sender, destinationChain, destinationAddress, destinationFee, amount) + Assets.sendToken( + token, + msg.sender, + destinationChain, + destinationAddress, + destinationFee, + amount, + MAX_DESTINATION_TRANSFER_FEE + ) ); } @@ -571,8 +585,6 @@ contract Gateway is IGateway, IInitializable { uint128 assetHubCreateAssetFee; /// @dev The extra fee charged for sending tokens (DOT) uint128 assetHubReserveTransferFee; - /// @dev The maximum fee that can be sent to a destination parachain to pay for execution (DOT) - uint128 destinationMaxTransferFee; /// @dev extra fee to discourage spamming uint256 registerTokenFee; /// @dev Fee multiplier diff --git a/contracts/src/storage/AssetsStorage.sol b/contracts/src/storage/AssetsStorage.sol index 25e8c54734..b4a34ed4dc 100644 --- a/contracts/src/storage/AssetsStorage.sol +++ b/contracts/src/storage/AssetsStorage.sol @@ -15,8 +15,6 @@ library AssetsStorage { uint128 assetHubReserveTransferFee; // Extra fee for registering a token, to discourage spamming (Ether) uint256 registerTokenFee; - /// The maximum fee that can be sent to a destination chain during a reserve transfer (DOT) - uint128 destinationMaxTransferFee; } bytes32 internal constant SLOT = keccak256("org.snowbridge.storage.assets"); diff --git a/contracts/src/upgrades/rococo/GatewayV2.sol b/contracts/src/upgrades/rococo/GatewayV2.sol index 3918e3d790..d92f64e2b4 100644 --- a/contracts/src/upgrades/rococo/GatewayV2.sol +++ b/contracts/src/upgrades/rococo/GatewayV2.sol @@ -13,8 +13,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 diff --git a/contracts/test/Gateway.t.sol b/contracts/test/Gateway.t.sol index a1c76977a6..67d1b5da24 100644 --- a/contracts/test/Gateway.t.sol +++ b/contracts/test/Gateway.t.sol @@ -81,7 +81,7 @@ contract GatewayTest is Test { uint128 public registerTokenFee = 0; uint128 public sendTokenFee = 1e10; uint128 public createTokenFee = 1e10; - uint128 public maxDestinationFee = 1e11; + uint128 public destinationMaxTransferFee = 1e11; MultiAddress public recipientAddress32; MultiAddress public recipientAddress20; @@ -95,8 +95,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, + destinationMaxTransferFee + ); Gateway.Config memory config = Gateway.Config({ mode: OperatingMode.Normal, deliveryCost: outboundFee, @@ -105,7 +111,6 @@ contract GatewayTest is Test { assetHubAgentID: assetHubAgentID, assetHubCreateAssetFee: createTokenFee, assetHubReserveTransferFee: sendTokenFee, - destinationMaxTransferFee: maxDestinationFee, exchangeRate: exchangeRate, multiplier: multiplier }); @@ -490,8 +495,14 @@ contract GatewayTest is Test { function testUpgradeInitializerRunsOnlyOnce() public { // Upgrade to this current logic contract AgentExecutor executor = new AgentExecutor(); - GatewayMock currentLogic = - new GatewayMock(address(0), address(executor), bridgeHubParaID, bridgeHubAgentID, foreignTokenDecimals); + GatewayMock currentLogic = new GatewayMock( + address(0), + address(executor), + bridgeHubParaID, + bridgeHubAgentID, + foreignTokenDecimals, + destinationMaxTransferFee + ); Gateway.Config memory config = Gateway.Config({ mode: OperatingMode.Normal, @@ -501,7 +512,6 @@ contract GatewayTest is Test { assetHubAgentID: assetHubAgentID, assetHubCreateAssetFee: createTokenFee, assetHubReserveTransferFee: sendTokenFee, - destinationMaxTransferFee: maxDestinationFee, exchangeRate: exchangeRate, multiplier: multiplier }); @@ -529,8 +539,14 @@ contract GatewayTest is Test { // Upgrade to this current logic contract AgentExecutor executor = new AgentExecutor(); - GatewayMock currentLogic = - new GatewayMock(address(0), address(executor), bridgeHubParaID, bridgeHubAgentID, foreignTokenDecimals); + GatewayMock currentLogic = new GatewayMock( + address(0), + address(executor), + bridgeHubParaID, + bridgeHubAgentID, + foreignTokenDecimals, + destinationMaxTransferFee + ); bytes memory initParams; // empty UpgradeParams memory params = UpgradeParams({ @@ -925,7 +941,7 @@ contract GatewayTest is Test { uint256 fee = IGateway(address(gateway)).quoteRegisterTokenFee(); IGateway(address(gateway)).registerToken{value: fee}(address(token)); - uint128 largeFee = maxDestinationFee + 1; // greater than 10 DOT, 10 decimal places + uint128 largeFee = destinationMaxTransferFee + 1; // greater than 10 DOT, 10 decimal places vm.expectRevert(Assets.InvalidDestinationFee.selector); fee = IGateway(address(gateway)).quoteSendTokenFee(address(token), destPara, largeFee); diff --git a/contracts/test/mocks/GatewayMock.sol b/contracts/test/mocks/GatewayMock.sol index b1014811be..23dfaddeb2 100644 --- a/contracts/test/mocks/GatewayMock.sol +++ b/contracts/test/mocks/GatewayMock.sol @@ -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 destinationMaxTransferFee + ) + Gateway( + beefyClient, + agentExecutor, + bridgeHubParaID, + bridgeHubHubAgentID, + foreignTokenDecimals, + destinationMaxTransferFee + ) + {} function agentExecutePublic(bytes calldata params) external { this.agentExecute(params);