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

Fee for Gateway.sendToken should cover all costs #1015

Merged
merged 43 commits into from
Nov 30, 2023
Merged

Conversation

vgeddes
Copy link
Collaborator

@vgeddes vgeddes commented Nov 22, 2023

For token transfers from Ethereum, if the final destination of the transfer is not AssetHub, we need to charge double the sendToken fee to cover cost of the extra XCM message to the final destination.

I've also improved the API that allows users to query fees for registering and sending tokens. The APIs give more detailed fee info, and in the case of SendToken, accounts for special case above.

requires: Snowfork/polkadot-sdk#49

Fixes: SNO-761

Copy link

codecov bot commented Nov 22, 2023

Codecov Report

Attention: 33 lines in your changes are missing coverage. Please review.

Comparison is base (0120c60) 80.97% compared to head (770c965) 81.50%.
Report is 2 commits behind head on main.

Files Patch % Lines
parachain/primitives/router/src/inbound/mod.rs 25.00% 18 Missing ⚠️
parachain/pallets/control/src/weights.rs 0.00% 4 Missing ⚠️
parachain/pallets/inbound-queue/src/lib.rs 72.72% 3 Missing ⚠️
parachain/primitives/core/src/pricing.rs 80.00% 3 Missing ⚠️
contracts/src/Gateway.sol 96.07% 2 Missing ⚠️
parachain/primitives/core/src/outbound.rs 88.88% 2 Missing ⚠️
parachain/pallets/control/src/lib.rs 95.83% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1015      +/-   ##
==========================================
+ Coverage   80.97%   81.50%   +0.52%     
==========================================
  Files          52       54       +2     
  Lines        2150     2233      +83     
  Branches       67       70       +3     
==========================================
+ Hits         1741     1820      +79     
- Misses        394      398       +4     
  Partials       15       15              
Flag Coverage Δ
rust 81.45% <75.39%> (+0.40%) ⬆️
solidity 81.72% <97.10%> (+1.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@vgeddes vgeddes changed the title Improve fee related code in Gateway Fee for Gateway.sendToken should cover all costs Nov 22, 2023
@yrong
Copy link
Contributor

yrong commented Nov 22, 2023

For token transfers from Ethereum, if the final destination of the transfer is not AssetHub, we need to charge double the sendToken fee to cover cost of the extra XCM message to the final destination.

The estimation to double the cost may not be accurate, the xcm fee totally depends on respective xcm config/impl in a specific parachain and may not be same as(or even differs a lot from) assetHub.

E.g. Like for acala the trader they use to calculate the fee are totally different from trader in assetHub and they don't even use the asset pallet for MultiCurrency support.

Copy link
Contributor

@alistair-singh alistair-singh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor suggestion. But looks good!

@@ -75,12 +87,19 @@ library Assets {
} 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Why not call sendTokenFee(ParaID assetHubParaID, ParaID destinationChain) here? Is it because we are worried about gas costs because its external? I think we should have an internal implementation and use it for both here and in sendTokenFee(ParaID assetHubParaID, ParaID destinationChain) so that the fee can be tuned in one place.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah mostly worried about gas costs.

Gonna have to redo this code though, see my latest comment further below.

@vgeddes
Copy link
Collaborator Author

vgeddes commented Nov 22, 2023

That's a good point Ron. And we will have the same issue for Xcm::Transact. I understand now that we need to implement something like this:

  1. The sendToken function takes an destinationChainFee parameter, which is the amount of DOT it will cost to execute the XCM.
  2. When called, the destinationChainFee is converted to ETH using a DOT/ETH exchange rate
  3. This converted amount is transferred from the user to the agent contract for AssetHub
  4. We need to include destinationChainFee amount in the message sent to BridgeHub, so that the XCM messages can have the correct BuyExecution instructions.

I think we can reasonably expect that parachains should accept DOT as the fee currency. This simplifies things. No need for custom fee tokens.

There are other changes to make if we go with this approach. For example, Channel.outboundFee should be in DOT, not ETH. Then we convert to ETH using the exchange rate when sendToken or registerToken is called.

Potential issues: The currency conversion with a lagging & mostly static exchange rate would lead to either of the following scenarios:

Scenario 1:

  1. Suppose actual XR is 1ETH/100DOT, however Gateway is configured with an XR of 1ETH/50DOT.
  2. Then user calls sendToken, paying a fee of 1 ETH for a XCM fee of 50 DOT.
  3. User ends up overpaying ETH to AssetHub's agent, as DOT is worth less in reality.
  4. Bridge security & operations are maintained, however users are potentially unhappy.

Scenario 2:

  1. Suppose actual XR is 1ETH/100DOT, however Gateway is configured with an XR of 1ETH/200DOT.
  2. Then user calls sendToken, paying a fee of 1 ETH for a XCM fee of 200 DOT.
  3. User ends up underpaying ETH to AssetHub's agent, as DOT is worth more in reality.
  4. Bridge security & operations are maintained. however, in the longer run, AssetHub may end up short of funds to pay for rewards on BridgeHub.

@vgeddes vgeddes marked this pull request as draft November 22, 2023 18:27
@yrong
Copy link
Contributor

yrong commented Nov 23, 2023

The sendToken function takes a destinationChainFee parameter, which is the amount of DOT it will cost to execute the XCM.

Or could be loaded from some map storage something like
(Multilocation(Acala)->0.01DOT),(MultiLocation(Moonbeam)->0.02DOT)
), That storage can be configured from the control pallet.

When called, the destinationChainFee is converted to ETH using a DOT/ETH exchange rate

Since we've already introduced the exchange rate configurable in

exchange_rate: FixedU128::saturating_from_rational(1, 400),
, so maybe we can reuse it on Ethereum side with another control command set bothboth at the same time.

As you mentioned the main issue is the outdated price. So we may need to introduce some price oracle like Chainlink Data Feeds or remember as @musnit mentioned before with an on-chain swap.

@vgeddes
Copy link
Collaborator Author

vgeddes commented Nov 23, 2023

The sendToken function takes a destinationChainFee parameter, which is the amount of DOT it will cost to execute the XCM.

Or could be loaded from some map storage something like (Multilocation(Acala)->0.01DOT),(MultiLocation(Moonbeam)->0.02DOT), That storage can be configured from the control pallet.

Well the idea is that indeed there need to be some mapping of parachain to fees. But its much simpler for clients to maintain that mapping offchain, and pass the required destinationChainFee to our API.

When called, the destinationChainFee is converted to ETH using a DOT/ETH exchange rate

Since we've already introduced the exchange rate configurable in

exchange_rate: FixedU128::saturating_from_rational(1, 400),
, so maybe we can reuse it on Ethereum side with another control command set both at the same time.

Agree 👍

I'm implementing that in this PR.

As you mentioned the main issue is the outdated price. So we may need to introduce some price oracle like Chainlink Data Feeds or remember as @musnit mentioned before with an on-chain swap.

I think a outdated exchange rate is OK for our bridge, for various reasons:

  1. The exchange rate is only used to calculate fees, which are generally much less than the actual value of the tokens being transferred.
  2. At least for the past few months the ETH/DOT rate has been quite stable around 1/400.
  3. We can always upgrade the bridge to implement a better mechanism (such as on-chain swaps)

The issue with all Chainlink services is that they are all centralized. They are not actually decentralised and trustless, since they are governed and maintained by a single company.

@yrong
Copy link
Contributor

yrong commented Nov 24, 2023

Actually this param can be removed now, should be exactly the same as(cover) the cost in

refund.saturating_add(T::Reward::get()),

@alistair-singh
Copy link
Contributor

Scenario 1:

  1. Suppose actual XR is 1ETH/100DOT, however Gateway is configured with an XR of 1ETH/50DOT.
  2. Then user calls sendToken, paying a fee of 1 ETH for a XCM fee of 50 DOT.
  3. User ends up overpaying ETH to AssetHub's agent, as DOT is worth less in reality.
  4. Bridge security & operations are maintained, however users are potentially unhappy.

Scenario 2:

  1. Suppose actual XR is 1ETH/100DOT, however Gateway is configured with an XR of 1ETH/200DOT.
  2. Then user calls sendToken, paying a fee of 1 ETH for a XCM fee of 200 DOT.
  3. User ends up underpaying ETH to AssetHub's agent, as DOT is worth more in reality.
  4. Bridge security & operations are maintained. however, in the longer run, AssetHub may end up short of funds to pay for rewards on BridgeHub.

Both these scenarios apply to our conversion rate on the substrate side as well. So we may want one extrinsic so that governance can upgrade the conversion rates on both sides of the bridge, maybe from the control pallet?

@vgeddes vgeddes marked this pull request as ready for review November 28, 2023 11:02
contracts/src/Assets.sol Outdated Show resolved Hide resolved
@yrong yrong mentioned this pull request Nov 30, 2023
@vgeddes vgeddes merged commit e4c9135 into main Nov 30, 2023
8 checks passed
@vgeddes vgeddes deleted the remove-redundant-address branch November 30, 2023 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants