From 48b6b88c0bb8564329127fd5bc30c72d99eb12bd Mon Sep 17 00:00:00 2001 From: rustdev Date: Tue, 23 Apr 2024 22:51:50 +0100 Subject: [PATCH] introduce refund logic for ibc transfer if tx failed by timeout. --- app/ibctesting/simapp/app.go | 13 +++++++- app/keepers/keepers.go | 1 + scripts/testnode.sh | 18 +++++------ x/ibctransfermiddleware/keeper/keeper.go | 18 +++++++++++ x/transfermiddleware/ibc_middleware.go | 35 +++++++++++++++++++++ x/transfermiddleware/keeper/keeper.go | 39 +++++++++++++++--------- 6 files changed, 100 insertions(+), 24 deletions(-) diff --git a/app/ibctesting/simapp/app.go b/app/ibctesting/simapp/app.go index 53a0e7165..3a53b9e56 100644 --- a/app/ibctesting/simapp/app.go +++ b/app/ibctesting/simapp/app.go @@ -128,6 +128,8 @@ import ( simappupgrades "github.com/notional-labs/composable/v6/app/ibctesting/simapp/upgrades" v6 "github.com/notional-labs/composable/v6/app/ibctesting/simapp/upgrades/v6" v7 "github.com/notional-labs/composable/v6/app/ibctesting/simapp/upgrades/v7" + ibctransfermiddleware "github.com/notional-labs/composable/v6/x/ibctransfermiddleware/keeper" + ibctransfermiddlewaretypes "github.com/notional-labs/composable/v6/x/ibctransfermiddleware/types" transfermiddleware "github.com/notional-labs/composable/v6/x/transfermiddleware" transfermiddlewarekeeper "github.com/notional-labs/composable/v6/x/transfermiddleware/keeper" transfermiddlewaretypes "github.com/notional-labs/composable/v6/x/transfermiddleware/types" @@ -258,7 +260,8 @@ type SimApp struct { ICAAuthModule ibcmock.IBCModule FeeMockModule ibcmock.IBCModule - TransferMiddlewarekeeper transfermiddlewarekeeper.Keeper + TransferMiddlewarekeeper transfermiddlewarekeeper.Keeper + IbcTransferMiddlewareKeeper ibctransfermiddleware.Keeper // the module manager mm *module.Manager @@ -378,6 +381,13 @@ func NewSimApp( app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, keys[feegrant.StoreKey], app.AccountKeeper) app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, keys[upgradetypes.StoreKey], appCodec, homePath, app.BaseApp, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + app.IbcTransferMiddlewareKeeper = ibctransfermiddleware.NewKeeper(appCodec, keys[ibctransfermiddlewaretypes.StoreKey], authtypes.NewModuleAddress(govtypes.ModuleName).String(), + []string{ + "pica1ay9y5uns9khw2kzaqr3r33v2pkuptfnnunlt5x", + "pica14lz7gaw92valqjearnye4shex7zg2p05yfguqm", + "pica1r2zlh2xn85v8ljmwymnfrnsmdzjl7k6w9f2ja8", + "pica10556m38z4x6pqalr9rl5ytf3cff8q46nf36090", + }) app.TransferMiddlewarekeeper = transfermiddlewarekeeper.NewKeeper( keys[transfermiddlewaretypes.StoreKey], app.GetSubspace(transfermiddlewaretypes.ModuleName), @@ -385,6 +395,7 @@ func NewSimApp( app.IBCKeeper.ChannelKeeper, app.TransferKeeper, app.BankKeeper, + &app.IbcTransferMiddlewareKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.MsgServiceRouter(), app.AccountKeeper) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 4dc786df7..588fea85f 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -303,6 +303,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( &appKeepers.RatelimitKeeper, &appKeepers.TransferKeeper, appKeepers.BankKeeper, + &appKeepers.IbcTransferMiddlewareKeeper, authorityAddress, ) diff --git a/scripts/testnode.sh b/scripts/testnode.sh index 9b2e76e04..4e5b67216 100755 --- a/scripts/testnode.sh +++ b/scripts/testnode.sh @@ -17,13 +17,13 @@ DENOM=${2:-ppica} # remove existing daemon rm -rf ~/.banksy* -centaurid config keyring-backend $KEYRING -centaurid config chain-id $CHAINID +picad config keyring-backend $KEYRING +picad config chain-id $CHAINID # if $KEY exists it should be deleted -echo "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" | centaurid keys add $KEY --keyring-backend $KEYRING --algo $KEYALGO --recover +echo "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" | picad keys add $KEY --keyring-backend $KEYRING --algo $KEYALGO --recover -centaurid init $MONIKER --chain-id $CHAINID +picad init $MONIKER --chain-id $CHAINID update_test_genesis () { # update_test_genesis '.consensus_params["block"]["max_gas"]="100000000"' @@ -31,10 +31,10 @@ update_test_genesis () { } # Allocate genesis accounts (cosmos formatted addresses) -centaurid add-genesis-account $KEY 100000000000000000000000000ppica --keyring-backend $KEYRING +picad add-genesis-account $KEY 100000000000000000000000000ppica --keyring-backend $KEYRING # Sign genesis transaction -centaurid gentx $KEY 10030009994127689ppica --keyring-backend $KEYRING --chain-id $CHAINID +picad gentx $KEY 10030009994127689ppica --keyring-backend $KEYRING --chain-id $CHAINID update_test_genesis '.app_state["gov"]["params"]["voting_period"]="20s"' update_test_genesis '.app_state["mint"]["params"]["mint_denom"]="'$DENOM'"' @@ -43,10 +43,10 @@ update_test_genesis '.app_state["crisis"]["constant_fee"]={"denom":"'$DENOM'","a update_test_genesis '.app_state["staking"]["params"]["bond_denom"]="'$DENOM'"' # Collect genesis tx -centaurid collect-gentxs +picad collect-gentxs # Run this to ensure everything worked and that the genesis file is setup correctly -centaurid validate-genesis +picad validate-genesis if [[ $1 == "pending" ]]; then echo "pending mode is on, please wait for the first block committed." @@ -57,4 +57,4 @@ fi sed -i'' -e 's/max_body_bytes = /max_body_bytes = 1/g' ~/.banksy/config/config.toml # Start the node (remove the --pruning=nothing flag if historical queries are not needed) -# centaurid start --pruning=nothing --minimum-gas-prices=0.0001ppica --rpc.laddr tcp://0.0.0.0:26657 +picad start --pruning=nothing --minimum-gas-prices=0.0001ppica --rpc.laddr tcp://0.0.0.0:26657 diff --git a/x/ibctransfermiddleware/keeper/keeper.go b/x/ibctransfermiddleware/keeper/keeper.go index a43bdb4b9..f66ad3947 100644 --- a/x/ibctransfermiddleware/keeper/keeper.go +++ b/x/ibctransfermiddleware/keeper/keeper.go @@ -64,3 +64,21 @@ func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { k.cdc.MustUnmarshal(bz, &p) return p } + +func (k Keeper) GetCoin(ctx sdk.Context, targetChannelID string, denom string) *types.CoinItem { + params := k.GetParams(ctx) + channelFee := findChannelParams(params.ChannelFees, targetChannelID) + if channelFee == nil { + return nil + } + return findCoinByDenom(channelFee.AllowedTokens, denom) +} + +func (k Keeper) GetChannelFeeAddress(ctx sdk.Context, targetChannelID string) string { + params := k.GetParams(ctx) + channelFee := findChannelParams(params.ChannelFees, targetChannelID) + if channelFee == nil { + return "" + } + return channelFee.FeeAddress +} diff --git a/x/transfermiddleware/ibc_middleware.go b/x/transfermiddleware/ibc_middleware.go index 4be08ca44..0ea26e722 100644 --- a/x/transfermiddleware/ibc_middleware.go +++ b/x/transfermiddleware/ibc_middleware.go @@ -127,6 +127,41 @@ func (im IBCMiddleware) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Pac return err } + denom := data.Denom + coin := im.keeper.IbcTransfermiddleware.GetCoin(ctx, packet.SourceChannel, denom) + сhannelFeeAddress := im.keeper.IbcTransfermiddleware.GetChannelFeeAddress(ctx, packet.SourceChannel) + if coin != nil { + amount := data.Amount + transferAmount, ok := sdk.NewIntFromString(amount) + if !ok { + return errors.Wrapf(transfertypes.ErrInvalidAmount, "unable to parse transfer amount: %s", amount) + } + + // calculate the percentage charge + /* + coin.Percentage is 100 that means that we charge 1/100 of the transfer amount + coin.MinFee.Amount is the minimum fee that we charge + the new amount is the transfer amount minus the percentage charge and the minimum fee + transfer amount is a 99/100 of the original amount + so to get the fee we charge transferAmount.QuoRaw(coin.Percentage - 1) + coin.MinFee.Amount + */ + + percentageCharge := sdk.NewInt(0) + if coin.Percentage > 1 { + percentageCharge = transferAmount.QuoRaw(coin.Percentage - 1) + } + percentageCharge = percentageCharge.Add(coin.MinFee.Amount) + + fee_address, err := sdk.AccAddressFromBech32(сhannelFeeAddress) + if err != nil { + return errors.Wrapf(err, "failed to decode receiver address: %s", сhannelFeeAddress) + } + + refund_fee := sdk.NewCoin(denom, percentageCharge) + im.keeper.RefundChannelCosmosFee(ctx, fee_address, sdk.AccAddress(data.Sender), []sdk.Coin{refund_fee}) + + } + return nil } diff --git a/x/transfermiddleware/keeper/keeper.go b/x/transfermiddleware/keeper/keeper.go index 4988afd09..ec5178645 100644 --- a/x/transfermiddleware/keeper/keeper.go +++ b/x/transfermiddleware/keeper/keeper.go @@ -15,17 +15,18 @@ import ( porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" "github.com/cosmos/ibc-go/v7/modules/core/exported" + ibctransfermiddleware "github.com/notional-labs/composable/v6/x/ibctransfermiddleware/keeper" "github.com/notional-labs/composable/v6/x/transfermiddleware/types" ) type Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - paramSpace paramtypes.Subspace - ICS4Wrapper porttypes.ICS4Wrapper - bankKeeper types.BankKeeper - transferKeeper types.TransferKeeper - + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + paramSpace paramtypes.Subspace + ICS4Wrapper porttypes.ICS4Wrapper + bankKeeper types.BankKeeper + transferKeeper types.TransferKeeper + IbcTransfermiddleware *ibctransfermiddleware.Keeper // the address capable of executing a AddParachainIBCTokenInfo and RemoveParachainIBCTokenInfo message. Typically, this // should be the x/gov module account. authority string @@ -39,6 +40,7 @@ func NewKeeper( ics4Wrapper porttypes.ICS4Wrapper, transferKeeper types.TransferKeeper, bankKeeper types.BankKeeper, + ibcTransfermiddleware *ibctransfermiddleware.Keeper, authority string, ) Keeper { // set KeyTable if it has not already been set @@ -47,13 +49,14 @@ func NewKeeper( } return Keeper{ - storeKey: storeKey, - paramSpace: paramSpace, - transferKeeper: transferKeeper, - bankKeeper: bankKeeper, - cdc: codec, - ICS4Wrapper: ics4Wrapper, - authority: authority, + storeKey: storeKey, + paramSpace: paramSpace, + transferKeeper: transferKeeper, + bankKeeper: bankKeeper, + cdc: codec, + ICS4Wrapper: ics4Wrapper, + IbcTransfermiddleware: ibcTransfermiddleware, + authority: authority, } } @@ -233,6 +236,14 @@ func (keeper Keeper) GetTotalEscrowedToken(ctx sdk.Context) (coins sdk.Coins) { return coins } +func (keeper Keeper) RefundChannelCosmosFee(ctx sdk.Context, sender sdk.AccAddress, receiver sdk.AccAddress, amount sdk.Coins) error { + if err := keeper.bankKeeper.SendCoins(ctx, sender, receiver, amount); err != nil { + return errorsmod.Wrap(err, "failed to refund channel cosmos fee") + } + + return nil +} + func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", "x/"+exported.ModuleName+"-"+types.ModuleName) }