GithubHelp home page GithubHelp logo

osmosis-labs / fee-abstraction Goto Github PK

View Code? Open in Web Editor NEW
32.0 7.0 13.0 14.94 MB

Enable users on any Cosmos chain with IBC connections to pay fee using IBC tokens.

Dockerfile 0.76% Makefile 1.83% Go 88.21% Shell 9.20%
cosmos osmosis cosmos-sdk ibc

fee-abstraction's Introduction

Fee Abstraction

Context

The concrete use cases which motivated this module include:

  • The desire to use IBC token as transaction fees on any chain instead of having to use native token as fee.
  • To fully take advantage of the newly represented Osmosis swap router with the ibc-hooks module and the async-icq module.

Description

Fee abstraction modules enable users on any Cosmos chain with IBC connections to pay fee using ibc token.

The implememtation also uses Osmosis swap router and async-icq module which are already deployed on Osmosis testnet.

Prototype

Fee-abs mechanism in a nutshell:

  1. Pulling twap data and update exchange rate:
  • Periodically pulling twap data from osmosis by ibc-ing to async-icq module on Osmosis, this twap data will update the exchange rate of osmosis to customer chain's native token.
  1. Handling txs with ibc-token fee:
  • The exchange rate is used to calculate the amount of ibc-token needed for tx fee allowing users to pay ibc-token for tx fee instead of chain's native token.
  1. Swap accumulated ibc-token fee:
  • The collected ibc-token users use for tx fee is periodically swaped back to customer chain's native token using osmosis.

We'll goes into all the details now:

Pulling twap data and update exchange rate

For this to work, we first has to set up an ibc channel from feeabs to async-icq. This channel set-up process can be done by anyone, just like setting up an ibc transfer channel. Once that ibc channel is there, we'll use that channel to ibc-query Twap data. Let's call this the querying channel.

The process of pulling Twap data and update exchange rate :

Diagram of the process of pulling Twap data and updating exchange rate

Description : For every update exchange rate period, at fee-abs BeginBlocker() we submit a InterchainQueryPacketData which wrapped QueryArithmeticTwapToNowRequest to the querying channel on the customer chain's end. Then relayers will submit MsgReceivePacket so that our QueryTwapPacket which will be routed to async-icq module to be processed. async-icq module then unpack InterchainQueryPacketData and send query to TWAP module. The correspone response will be wrapped in the ibc acknowledgement. Relayers then submit MsgAcknowledgement to the customer chain so that the ibc acknowledgement is routed to fee-abs to be processed. Fee-abs then update exchange rate according to the Twap wrapped in the ibc acknowledgement.

Handling txs with ibc-token fee

We modified MempoolFeeDecorator so that it can handle ibc-token as fee. If the tx has ibc-token fee, the AnteHandler will first check if that token is allowed (which is setup by Gov) we basically replace the amount of ibc-token with the equivalent native-token amount which is calculated by exchange rate * ibc-token amount.

We have an account to manage the ibc-token user used to pay for tx fee. The collected ibc-token fee is sent to that account instead of community pool account.

Swap accumulated ibc-tokens fee

Fee-abstraction will use osmosis's Cross chain Swap (XCS) feature to do this. We basically ibc transfer to the osmosis crosschain swap contract with custom memo to swap the osmosis fee back to customer chain's native-token and ibc transfer back to the customer chain.

How XCS work

Reverse With Path-unwinding to get Ibc-token on Osmosis
  • Create a ibc transfer message with a specific MEMO to work with ibc packet-forward-middleware which is path-unwinding (an ibc feature that allow to automatic define the path and ibc transfer multiple hop follow the defined path)
  • Ibc transfer the created packet to get the fee Ibc-token on Osmosis

Example: When you sent STARS on Hub to Osmosis, you will get Osmosis(Hub(STARS)) which is different with STARS on Osmosis Osmosis(STARS). It will reverse back Osmosis(Hub(STARS)) to Osmosis(STARS):

Diagram of the process of swapping accumulated ibc-tokens fee

Swap IBC token

After reverse the ibc-token, XCS will :

  • Swap with the specific pool (which is defined in the transfer packet from Feeabs-chain) to get Feeabs-chain native-token
  • Transfer back Feeabs-chain native-token to Feeabs module account (will use to pay fee for other transaction)

Diagram of the process of swapping accumulated ibc-tokens fee

Current version of fee-abstraction working with XCSv2

Repository Structure

This repository is branched by the cosmos-sdk versions and ibc-go versions used. Currently fee abstraction supports:

  • SDK v0.50.x & IBC-go v8.*
    • branch: release/v8.0.x
    • path: github.com/osmosis-labs/fee-abstraction/v8
  • SDK v0.47.x & IBC-go v7.*
    • branch: release/v7.0.x
    • path: github.com/osmosis-labs/fee-abstraction/v7
  • SDK v0.46.x (Obsolete)
    • branch: release/v6.0.x
    • path: github.com/osmosis-labs/fee-abstraction/v6
  • SDK v0.45.x (Obsolete)
    • branch: release/v4.0.x
    • path: github.com/osmosis-labs/fee-abstraction/v4

Acknowledgements

  • cosmos-sdk:
    • many developers :)
  • async-icq
    • strangelove ventures
  • ideation
    • osmosis grants program
  • mergify configuration and branching strategy
    • ibc-go

fee-abstraction's People

Contributors

anhductn2001 avatar catshaark avatar dependabot[bot] avatar expertdicer avatar faddat avatar gnad13 avatar hieuvubk avatar hoank101 avatar minhngoc274 avatar nghuyenthevinh2000 avatar phamminh0811 avatar tnv1 avatar tuantran1702 avatar vuong177 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

fee-abstraction's Issues

cosmossdk.io vs github.com/cosmos/cosmos-sdk

Trying to use the notional fork of the repo for sdk47, and getting this:

go: github.com/burnt-labs/xion/app imports
        github.com/notional-labs/fee-abstraction/v2/x/feeabs/keeper tested by
        github.com/notional-labs/fee-abstraction/v2/x/feeabs/keeper.test imports
        github.com/notional-labs/fee-abstraction/v2/app imports
        github.com/cosmos/cosmos-sdk/simapp: github.com/cosmos/cosmos-sdk/[email protected]: parsing go.mod:
        module declares its path as: cosmossdk.io/simapp
                but was required as: github.com/cosmos/cosmos-sdk/simapp

Any ideas? Not getting this issue on the osmosis version (last I checked)

test nomic btc on feeabs

Tasks

Investigate speeding up interchaintest

It might be possible to get interchaintest to run more quickly by pre-building some of the contents of the docker image. Let's see if we can make that happen.

review

  • bump sdk to v0.45.16
  • bump wasmd to v0.32.1
  • add ibc fee support
  • lint the repo some
  • use mvdan for build and test

Not important:

  • Tests fail on windows (I think I'll just remove the windows builds)

Optimize Fee Calculation Process

Description

In the process of calculating native coin fees, the coins first get split between the ones with zero and non-zero amounts, as it is pretty clear according to the variable naming in the code and the code semantics:

 // split feeRequired into zero and non-zero coins(nonZeroCoinFeesReq,
  zeroCoinFeesDenomReq), split feeCoins according to
          // nonZeroCoinFeesReq, zeroCoinFeesDenomReq,
          // so that feeCoins can be checked separately against them.
          nonZeroCoinFeesReq, zeroCoinFeesDenomReq := getNonZeroFees(feeRequired)
          // feeCoinsNonZeroDenom contains non-zero denominations from the feeRequired
          // feeCoinsNonZeroDenom is used to check if the fees meets the requirement imposed by nonZeroCoinFeesReq
          // when feeCoins does not contain zero coins' denoms in feeRequired
          feeCoinsNonZeroDenom, feeCoinsZeroDenom := splitCoinsByDenoms(feeCoins,
  zeroCoinFeesDenomReq)
          feeCoinsLen := feeCoins.Len()

However, few lines after, the calculation is performed over the complete set of coins by calling the CalculateNativeFromIBCCoins with feeCoins as a parameter:

if feeCoinsNonZeroDenom.Len() == 1 {
feeDenom := feeCoinsNonZeroDenom.GetDenomByIndex(0)
hasHostChainConfig := famfd.feeabsKeeper.HasHostZoneConfig(ctx, feeDenom) if hasHostChainConfig {
    hostChainConfig, _ := famfd.feeabsKeeper.GetHostZoneConfig(ctx,
                  nativeCoinsFees, err :=
  famfd.feeabsKeeper.CalculateNativeFromIBCCoins(ctx, feeCoins, hostChainConfig)
if err != nil {
return ctx, sdkerrors.Wrapf(errorstypes.ErrInsufficientFee,
  "insufficient fees")
                  }
                  feeCoinsNonZeroDenom = nativeCoinsFees
              }
}

The code could be optimized by calling the function over only non zero coins, thus saving time and memory, as the zero amount coins won’t affect the end result.

Minor Code Improvements

Involved artifacts
• x/feeabs/ante/decorate.go
• x/feeabs/ibc.go

Description
During the code review, several minor code improvements were identified in the Fee Abstraction module. These improvements aim to enhance code readability, maintainability, and adherence to best practices.

Lack of Frozen Status Check in Cross-chain Swap Execution

Description

The system exhibits inconsistent behavior in handling frozen host zones when executing cross-chain swaps. In the executeAllHostChainSwap method, a check is performed to skip the operation for frozen host zones, as
shown below:

if hostZoneConfig.Frozen { 
    return false
}

However, in the SwapCrossChain method triggered via the message server, there is no similar check for the frozen status, and the method proceeds to attempt the cross-chain swap regardless:

err = k.transferOsmosisCrosschainSwap(ctx, hostChainConfig)

This lack of a frozen status check can potentially lead to unintended transactions on frozen host zones, which could have adverse impacts.

Problem Scenarios

Suppose a host zone is frozen due to an error or an abnormal condition detected in the system. Despite this, a user
or another system component triggers a cross-chain swap via the message server using the SwapCrossChain method.

Since there's no check for the frozen status, the method would proceed to execute the cross-chain swap on the frozen host zone, possibly leading to unintended transactions, resource wastage, or even financial loss.

A test could be constructed to simulate a frozen host zone, then trigger a cross-chain swap via the SwapCrossChain method to observe and validate the behavior, demonstrating the lack of frozen status check
and the potential adverse impact.

Non-Unique KVStore Key

The non-uniqueness of keys

  • GetKeyHostZoneConfigByFeeabsIBCDenom
  • GetKeyHostZoneConfigByOsmosisIBCDenom
  • GetKeyTwapExchangeRate
    Solution:
    Add check that block add new hostzone which is duplicate ibcdenom
    We can't add poolID to store key because in some case we still get the hostzone but don't know the poolID (in antehandler, we only know ibc-denom)

sdk 50

a new crop of chains will soon be released using sdk 50.

Tasks

Unnecessary Host Zone Freezing Due to Lack of Granular Error Handling

Involved artifacts
• x/feeabs/ibc.go

Description
The system, as per the provided code snippets, exhibits a behavior where all host zones are attempted to be frozen in the event of an error acknowledgment received from inter-chain queries. The relevant logic is encapsulated in
the OnAcknowledgementPacket method and the helper method FrozenHostZoneByIBCDenom . Below is the code snippet from OnAcknowledgementPacket method where the system iterates over all host zones to
freeze them on encountering an error acknowledgment:

case *channeltypes.Acknowledgement_Error:
k.IterateHostZone(ctx, func(hostZoneConfig types.HostChainFeeAbsConfig) (stop
bool) {
err := k.FrozenHostZoneByIBCDenom(ctx, hostZoneConfig.IbcDenom) if err != nil {
              k.Logger(ctx).Error(fmt.Sprintf("Failed to frozen host zone %s",
  err.Error()))
}
return false
})
      k.Logger(ctx).Error(fmt.Sprintf("failed to send packet ICQ request %v",
  resp.Error))

Problem Scenarios
The current approach of freezing all host zones upon receiving an error acknowledgment can lead to a complete halt in system operations even if the error occurs to a single host zone. This approach lacks granularity and could significantly disrupt the user experience and system functionality.

fee abstraction channel freeze easily after either a single failed ibc query or failed cross - chain swap

  • should never freeze a fee abstraction channel
  • should set OUTDATED state for a fee abstraction channel if unable to ibq query, or fail to cross - chain swap after 5 retries. If OUTDATED, chain should retry in the next 30 mins.

With current logic, once a connection is frozen, the chain will not perform IBC query to break that.

  • ibc query
  • cross - chain swap

states for a fee abstraction channel:

  1. UPDATED
  2. OUTDATED
  3. FROZEN

Outdated exchange rate when IBC Timeout

In the event of a delay or failure in IBC relayer functionality, the system may be unable to obtain the latest
TWAP from the Osmosis chain, leading to the utilization of outdated exchange rate information for fee
calculations.
Solution

  • Frozen the hostzone when icq get timeout.

Organize branches

I mentioned this some in #31 but I think it makes sense to lay out:

  • main should be sdk50
  • release/v4.0.x should be sdk47
  • release/v3.0.x should be sdk46
  • release/v2.0.x should be sdk45

In the future let's add features to main, and then if we make breaking changes when we do that, we will pass those along and then we can cut branches like:

  • release/v4.1.x
  • release/v3.1.x
  • release/v2.1.x

That way it will be clearer as to which branch to use.

Tasks

Optimization Opportunities in KVStore Calls

Involved artifacts
• x/feeabs/ante/decorate.go
• x/feeabs/ibc.go
• x/feeabs/keeper/config.go
• x/feeabs/keeper/proposal.go

Description
During the code review, it was identified that there are several places within the codebase where KVStore calls could be optimized. These optimizations can help reduce redundant queries and improve the efficiency of the code.

Problem Scenarios
The issues regarding KVStore calls and their potential optimizations are as follows:

  1. Redundant Calls to HasHostZoneConfig and GetHostZoneConfig: In some parts of the code, there are redundant calls to HasHostZoneConfig followed by GetHostZoneConfig (decorate.go#L45-L50, decorate.go#L254-L256).
hasHostChainConfig := fadfd.feeabsKeeper.HasHostZoneConfig(ctx, feeDenom) if !hasHostChainConfig {
return fadfd.normalDeductFeeAnteHandle(ctx, tx, simulate, next, feeTx) }
        hostChainConfig, _ := fadfd.feeabsKeeper.GetHostZoneConfig(ctx, feeDenom)

This is unnecessary, as it's sufficient to call GetHostZoneConfig and check the 'found' field to determine if the key exists.
The same can be found for EpochInfo at ibc.go#L256-L263.
2. Unnecessary Use of GetHostZoneConfig: In certain instances, GetHostZoneConfig is called to check
the existence of a key. It's more efficient to direc tly000 call HasHostZoneConfig (proposal.go#L10-L13,
proposal.go#L24-L27, proposal.go#L38-L41).
3. Double Call to GetHostZoneConfig: In the DeleteHostZoneProposal and
DeleteHostZoneConfig functions, GetHostZoneConfig is called twice, leading to redundant queries. This can be optimized by calling GetHostZoneConfig once, potentially in the
DeleteHostZoneConfig function (proposal.go#L24-L27, config.go#L62).

Documentation

We should create docs for:

  • sdk 45
  • sdk 46
  • sdk 47
  • sdk 50

that contain the best practices for using fee abstraction, including the infrastructure setup necessary for both test and prod.

Critical Risk of Non-Uniqueness in KVStore Keys

Description

The existing system contains a significant risk of non-uniqueness for keys:
GetKeyHostZoneConfigByFeeabsIBCDenom , GetKeyHostZoneConfigByOsmosisIBCDenom , and GetKeyTwapExchangeRate

keys.go#L38-L48:

func GetKeyHostZoneConfigByFeeabsIBCDenom(feeabsIbcDenom string) []byte { 
    return append(KeyHostChainChainConfigByFeeAbs, []byte(feeabsIbcDenom)...)
}
func GetKeyHostZoneConfigByOsmosisIBCDenom(osmosisIbcDenom string) []byte { 
    return append(KeyHostChainChainConfigByOsmosis, []byte(osmosisIbcDenom)...)
}
func GetKeyTwapExchangeRate(ibcDenom string) []byte {
    return append(OsmosisTwapExchangeRate, []byte(ibcDenom)...)
}

The absence of uniqueness in these keys poses a severe risk to the system's integrity and proper functionality. These non-unique keys may lead to various critical issues within the system, such as sending incorrect Interchain Query (ICQ) requests, miscalculations of native coins with incorrect TWAP rate values, and erroneous transfers of native coins to the fee-collector account.

For instance, if Osmosis's pools 3 (AKT/OSMO) and 4 (AKT/ATOM) share identical KeyHostChainChainConfigByFeeAbs and OsmosisTwapExchangeRate keys, the system could
inadvertently send requests for pool 4 instead of pool 3. This misdirection of system actions can result in financial discrepancies and disrupt the normal operations of the system.

Dependency Reduction

Currently we're describing fee abstraction in readme.md as using async-icq, but it doesn't seem that the final app uses that.

Additionally, there's good reason to try to reduce the dependencies present in fee abstraction.

Specifically, I'm interested to see if we can get packet-forward-middleware out of the stack here, so that using fee abstraction doesn't force anyone to use packet-forward-middleware.

Cannot use 'app.TransferKeeper'

We have an integrator getting the following error message:

Cannot use 'app.TransferKeeper' (type "[github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper](http://github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper)".Keeper) as the type "[github.com/cosmos/ibc-go/v4/modules/apps/transfer/keeper](http://github.com/cosmos/ibc-go/v4/modules/apps/transfer/keeper)".Keeper

app.FeeAbsKeeper = feeabskeeper.NewKeeper(
    appCodec,
    keys[feeabstypes.StoreKey],
    keys[feeabstypes.MemStoreKey],
    app.GetSubspace(feeabstypes.ModuleName),
    app.StakingKeeper,
    app.AccountKeeper,
    app.BankKeeper,
    app.TransferKeeper,
    app.IBCKeeper.ChannelKeeper,
    &app.IBCKeeper.PortKeeper,
    scopedFeeabsKeeper,
)...

is the feeabskeeper module compatible with IBC v7?

Add support for getting price with multihop route

Current Limitation

Price data is only available for token pairs with existing liquidity pools on Osmosis. The Multihop route is calculated off-chain.
https://github.com/osmosis-labs/osmosis/blob/e4290c54c0f77b91b6a08bdad1ec62f9739dc963/x/poolmanager/README.md#multi-hop

Desired Functionality

Enable price fetching for any supported token against the native token, regardless of whether a liquidity pool exists. This capability is needed to accurately calculate fees for a wider range of token pairs.

Missing Frozen Host Zone Checks in AnteHandles

Involved artifacts

• x/feeabs/ante/decorate.go
• x/feeabs/keeper/keeper.go

Description

The FeeAbstractionDeductFeeDecorator and FeeAbstractionMempoolFeeDecorator
AnteHandles lack the necessary checks to verify whether host zones are frozen. These AnteHandles calculate native coins from IBC coins (DeductFee, Mempool), and they potentially supply outdated Twap Rates for these calculations. The absence of frozen host zone checks in these AnteHandles creates the risk of incorrect
calculations, which could result in the wrong amount of native coins being sent from the feeabs module to the fee_collector .

Problem Scenarios

The issue becomes evident when transactions involving FeeAbstractionDeductFeeDecorator and
FeeAbstractionMempoolFeeDecorator AnteHandles are processed. In the absence of frozen host zone checks, the following problems may arise:

  1. Incorrect Coin Calculations: If a host zone is frozen, the rate information might be outdated or unavailable.
    Without checks, the AnteHandles could proceed with calculations based on erroneous or outdated data,
    leading to incorrect coin conversions.
  2. Wrong Amounts Sent: The lack of frozen host zone checks can result in
    FeeAbstractionDeductFeeDecorator AnteHandle sending the wrong amount of native coins from the feeabs module to the fee_collector account, which could have financial implications for the system.

Stuck juggling different forks

We have an open PR attempting to integrate Fee Abstraction: burnt-labs/xion#91

Currently, the situation is this. We need to point to the notional fork, since it is the only one supporting ibc-go v7. For some reason, that fork points at a Notional specific fork of Packet Forward Middleware. We don't intend to point at a non-standard fork of a common IBC module, so we have a replace directive in our go.mod pointing it back at ibc-apps.

This causes an issues like so:
go: github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/[email protected] used for two different module paths (github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 and github.com/strangelove-ventures/packet-forward-middleware/v7)

Improve crosschain swap call

In the current implementation, cross swap is called each end of epoch. It's ineffective when amount of token maybe very small.

Need add a field MinSwapAmount on each HostZoneConfig, cross swap should be called when bypass MinSwapAmount value.

[ci] mergify

We should configure mergify on this repository, so that:

  • main is sdk's next release (currently v50)
    we have branches for 47, 46, and 45

We'll drop support for 45 only when it isn't the most widely used version of the cosmos-sdk :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.