From faf4bdb42357b729fd185ce0cd4e55644e318aa3 Mon Sep 17 00:00:00 2001 From: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:45:02 -0800 Subject: [PATCH 1/5] feat: ibc validation logic --- app/app.go | 1 + app/ibctransfer/expected_keepers.go | 18 +++++++++++ app/ibctransfer/ibctransfer.go | 34 +++++++++++++++++++- x/gmp/client/cli/tx.go | 21 ++++++------- x/gmp/keeper/keeper.go | 49 ++++++++++++++++++++++------- x/gmp/types/expected_keeper.go | 7 +++++ 6 files changed, 107 insertions(+), 23 deletions(-) create mode 100644 app/ibctransfer/expected_keepers.go diff --git a/app/app.go b/app/app.go index 70c9785a..8d98d13e 100644 --- a/app/app.go +++ b/app/app.go @@ -484,6 +484,7 @@ func New( app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, + app.GmpKeeper, ) transferModule := NewIBCTransferModule(app.TransferKeeper) var ibcStack ibcporttypes.IBCModule diff --git a/app/ibctransfer/expected_keepers.go b/app/ibctransfer/expected_keepers.go new file mode 100644 index 00000000..8dd77c0f --- /dev/null +++ b/app/ibctransfer/expected_keepers.go @@ -0,0 +1,18 @@ +package ibctransfer + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + gmptypes "github.com/ojo-network/ojo/x/gmp/types" +) + +// GmpKeeper defines the expected GmpKeeper interface needed by the IBCTransfer keeper. +type GmpKeeper interface { + GetParams(ctx sdk.Context) (params gmptypes.Params) + BuildGmpRequest( + goCtx context.Context, + msg *gmptypes.MsgRelayPrice, + ) (*ibctransfertypes.MsgTransfer, error) +} diff --git a/app/ibctransfer/ibctransfer.go b/app/ibctransfer/ibctransfer.go index e8e4e283..f78d0332 100644 --- a/app/ibctransfer/ibctransfer.go +++ b/app/ibctransfer/ibctransfer.go @@ -5,11 +5,13 @@ import ( "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ibctransferkeeper "github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper" types "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" "github.com/cosmos/ibc-go/v7/modules/core/exported" + gmptypes "github.com/ojo-network/ojo/x/gmp/types" ) type Keeper struct { @@ -25,10 +27,38 @@ type Keeper struct { authKeeper types.AccountKeeper bankKeeper types.BankKeeper scopedKeeper exported.ScopedKeeper + + gmpKeeper GmpKeeper } +// Transfer defines a wrapper function for the ICS20 Transfer method. +// If the receiver for the tx is axelar's GMP address, +// Then it expects a payload of the gmptypes.MsgRelayPrice msg. +// If it does not have this format, it will error out. +// If it does, it will build a MsgTransfer with the payload. func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.MsgTransferResponse, error) { - // TODO: Custom IBC validation logic + ctx := sdk.UnwrapSDKContext(goCtx) + gmpParams := k.gmpKeeper.GetParams(ctx) + + if msg.Receiver == gmpParams.GmpAddress { + relayMsg := &gmptypes.MsgRelayPrice{} + // safe byte conversion for invalid UTF-8 + bz := make([]byte, len(msg.Memo)) + copy(bz, msg.Memo) + err := relayMsg.Unmarshal(bz) + if err == nil { + // If the payload is not a relayMsg type, then a user is trying to perform GMP + // without the proper payload. This transaction be considered to be by a bad actor. + k.Logger(ctx).With(err).Error("unexpected object while trying to relay data to GMP") + return nil, err + } + + gmpTransferMsg, err := k.gmpKeeper.BuildGmpRequest(goCtx, relayMsg) + if err != nil { + return nil, err + } + return k.Keeper.Transfer(goCtx, gmpTransferMsg) + } return k.Keeper.Transfer(goCtx, msg) } @@ -37,6 +67,7 @@ func NewKeeper( cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, ics4Wrapper porttypes.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, scopedKeeper exported.ScopedKeeper, + gmpKeeper GmpKeeper, ) Keeper { // ensure ibc transfer module account is set if addr := authKeeper.GetModuleAddress(types.ModuleName); addr == nil { @@ -58,5 +89,6 @@ func NewKeeper( authKeeper: authKeeper, bankKeeper: bankKeeper, scopedKeeper: scopedKeeper, + gmpKeeper: gmpKeeper, } } diff --git a/x/gmp/client/cli/tx.go b/x/gmp/client/cli/tx.go index d7a6163d..5efe191b 100644 --- a/x/gmp/client/cli/tx.go +++ b/x/gmp/client/cli/tx.go @@ -1,6 +1,7 @@ package cli import ( + "encoding/base64" "fmt" "strconv" "strings" @@ -34,12 +35,9 @@ func GetCmdRelay() *cobra.Command { cmd := &cobra.Command{ Use: `relay [destination-chain] [ojo-contract-address] [client-contract-address] ` + `[command-selector] [command-params] [timestamp] [denoms] [amount]`, - Args: cobra.ExactArgs(4), + Args: cobra.ExactArgs(8), Short: "Relay oracle data via Axelar GMP", RunE: func(cmd *cobra.Command, args []string) error { - if err := cmd.Flags().Set(flags.FlagFrom, args[0]); err != nil { - return err - } clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err @@ -89,13 +87,14 @@ func GetCmdRelay() *cobra.Command { return err } - // convert command-selector to []byte - var commandSelector []byte - copy(commandSelector, args[3]) - - // convert command-params to []byte - var commandParams []byte - copy(commandParams, args[4]) + commandSelector, err := base64.StdEncoding.DecodeString(args[3]) + if err != nil { + return err + } + commandParams, err := base64.StdEncoding.DecodeString(args[4]) + if err != nil { + return err + } msg := types.NewMsgRelay( clientCtx.GetFromAddress().String(), diff --git a/x/gmp/keeper/keeper.go b/x/gmp/keeper/keeper.go index 2d70eafb..e2f3955c 100644 --- a/x/gmp/keeper/keeper.go +++ b/x/gmp/keeper/keeper.go @@ -51,7 +51,9 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } -// RelayPrice +// RelayPrice submits an IBC transfer with a MsgRelayPrice payload. +// This is so the IBC Transfer module can then use BuildGmpRequest +// and perform the GMP request. func (k Keeper) RelayPrice( goCtx context.Context, msg *types.MsgRelayPrice, @@ -59,12 +61,42 @@ func (k Keeper) RelayPrice( ctx := sdk.UnwrapSDKContext(goCtx) params := k.GetParams(ctx) + bz, err := msg.Marshal() + if err != nil { + return nil, err + } + + transferMsg := ibctransfertypes.NewMsgTransfer( + ibctransfertypes.PortID, + params.GmpChannel, + msg.Token, + msg.Relayer, + params.GmpAddress, + clienttypes.ZeroHeight(), + uint64(ctx.BlockTime().Add(time.Duration(params.GmpTimeout)*time.Hour).UnixNano()), + string(bz), + ) + _, err = k.ibcKeeper.Transfer(ctx, transferMsg) + if err != nil { + return &types.MsgRelayPriceResponse{}, err + } + + return &types.MsgRelayPriceResponse{}, nil +} + +func (k Keeper) BuildGmpRequest( + goCtx context.Context, + msg *types.MsgRelayPrice, +) (*ibctransfertypes.MsgTransfer, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + params := k.GetParams(ctx) + prices := []types.PriceData{} for _, denom := range msg.Denoms { // get exchange rate rate, err := k.oracleKeeper.GetExchangeRate(ctx, denom) if err != nil { - return &types.MsgRelayPriceResponse{}, err + return &ibctransfertypes.MsgTransfer{}, err } // get any available median and standard deviation data @@ -81,10 +113,10 @@ func (k Keeper) RelayPrice( // convert them to a medianData slice medianData, err := types.NewMediansSlice(medians, deviations) if err != nil { - return &types.MsgRelayPriceResponse{}, err + return &ibctransfertypes.MsgTransfer{}, err } - priceFeed, err := types.NewPriceData( + price, err := types.NewPriceData( denom, rate, big.NewInt(msg.Timestamp), @@ -94,7 +126,7 @@ func (k Keeper) RelayPrice( k.Logger(ctx).With(err).Error("unable to relay price to gmp") continue } - prices = append(prices, priceFeed) + prices = append(prices, price) } // convert commandSelector to [4]byte @@ -136,10 +168,5 @@ func (k Keeper) RelayPrice( uint64(ctx.BlockTime().Add(time.Duration(params.GmpTimeout)*time.Hour).UnixNano()), string(bz), ) - _, err = k.ibcKeeper.Transfer(ctx, transferMsg) - if err != nil { - return &types.MsgRelayPriceResponse{}, err - } - - return &types.MsgRelayPriceResponse{}, nil + return transferMsg, nil } diff --git a/x/gmp/types/expected_keeper.go b/x/gmp/types/expected_keeper.go index fd9f387a..68c446d0 100644 --- a/x/gmp/types/expected_keeper.go +++ b/x/gmp/types/expected_keeper.go @@ -1,7 +1,10 @@ package types import ( + context "context" + sdk "github.com/cosmos/cosmos-sdk/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" oracletypes "github.com/ojo-network/ojo/x/oracle/types" ) @@ -13,3 +16,7 @@ type OracleKeeper interface { HistoricMedians(ctx sdk.Context, denom string, numStamps uint64) oracletypes.PriceStamps HistoricDeviations(ctx sdk.Context, denom string, numStamps uint64) oracletypes.PriceStamps } + +type IBCTransferKeeper interface { + Transfer(goCtx context.Context, msg *ibctransfertypes.MsgTransfer) (*ibctransfertypes.MsgTransferResponse, error) +} From c84263e7a0b0768a04d88ff9501ffbdee496c6ca Mon Sep 17 00:00:00 2001 From: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> Date: Tue, 14 Nov 2023 13:40:12 -0800 Subject: [PATCH 2/5] Fix up some app wiring & add some integration tests --- app/app.go | 4 +++- app/ibctransfer/ibctransfer.go | 19 ++++++++++++---- x/gmp/keeper/keeper.go | 8 +++---- x/gmp/keeper/keeper_suite_test.go | 7 ++++++ x/gmp/keeper/msg_server_test.go | 38 +++++++++++++++++++++++++++++-- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/app/app.go b/app/app.go index 8d98d13e..aec4eef2 100644 --- a/app/app.go +++ b/app/app.go @@ -437,8 +437,8 @@ func New( appCodec, keys[gmptypes.ModuleName], app.OracleKeeper, - app.TransferKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + app.TransferKeeper, ) app.AirdropKeeper = airdropkeeper.NewKeeper( @@ -486,6 +486,8 @@ func New( scopedTransferKeeper, app.GmpKeeper, ) + // Reassign the GMP transfer keeper + app.GmpKeeper.IBCKeeper = &app.TransferKeeper transferModule := NewIBCTransferModule(app.TransferKeeper) var ibcStack ibcporttypes.IBCModule ibcStack = NewIBCAppModule(app.TransferKeeper) diff --git a/app/ibctransfer/ibctransfer.go b/app/ibctransfer/ibctransfer.go index f78d0332..629125b9 100644 --- a/app/ibctransfer/ibctransfer.go +++ b/app/ibctransfer/ibctransfer.go @@ -2,6 +2,7 @@ package ibctransfer import ( "context" + "fmt" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" @@ -28,7 +29,7 @@ type Keeper struct { bankKeeper types.BankKeeper scopedKeeper exported.ScopedKeeper - gmpKeeper GmpKeeper + GmpKeeper *GmpKeeper } // Transfer defines a wrapper function for the ICS20 Transfer method. @@ -38,7 +39,8 @@ type Keeper struct { // If it does, it will build a MsgTransfer with the payload. func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.MsgTransferResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - gmpParams := k.gmpKeeper.GetParams(ctx) + gmpKeeper := *k.GmpKeeper + gmpParams := gmpKeeper.GetParams(ctx) if msg.Receiver == gmpParams.GmpAddress { relayMsg := &gmptypes.MsgRelayPrice{} @@ -46,17 +48,20 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types. bz := make([]byte, len(msg.Memo)) copy(bz, msg.Memo) err := relayMsg.Unmarshal(bz) - if err == nil { + if err != nil { // If the payload is not a relayMsg type, then a user is trying to perform GMP // without the proper payload. This transaction be considered to be by a bad actor. k.Logger(ctx).With(err).Error("unexpected object while trying to relay data to GMP") return nil, err } - gmpTransferMsg, err := k.gmpKeeper.BuildGmpRequest(goCtx, relayMsg) + gmpTransferMsg, err := gmpKeeper.BuildGmpRequest(goCtx, relayMsg) if err != nil { return nil, err } + fmt.Println("trying to get transfer params:") + k.Keeper.SetParams(ctx, types.DefaultParams()) + fmt.Println("transfer getparams:", k.Keeper.GetParams(ctx)) return k.Keeper.Transfer(goCtx, gmpTransferMsg) } return k.Keeper.Transfer(goCtx, msg) @@ -89,6 +94,10 @@ func NewKeeper( authKeeper: authKeeper, bankKeeper: bankKeeper, scopedKeeper: scopedKeeper, - gmpKeeper: gmpKeeper, + GmpKeeper: &gmpKeeper, } } + +func (k Keeper) SetGmpKeeper(gmpKeeper GmpKeeper) { + k.GmpKeeper = &gmpKeeper +} diff --git a/x/gmp/keeper/keeper.go b/x/gmp/keeper/keeper.go index e2f3955c..efafb39e 100644 --- a/x/gmp/keeper/keeper.go +++ b/x/gmp/keeper/keeper.go @@ -23,7 +23,7 @@ type Keeper struct { cdc codec.BinaryCodec storeKey storetypes.StoreKey oracleKeeper types.OracleKeeper - ibcKeeper ibctransfer.Keeper + IBCKeeper *ibctransfer.Keeper // the address capable of executing a MsgSetParams message. Typically, this // should be the x/gov module account. authority string @@ -34,15 +34,15 @@ func NewKeeper( cdc codec.BinaryCodec, storeKey storetypes.StoreKey, oracleKeeper types.OracleKeeper, - ibcKeeper ibctransfer.Keeper, authority string, + ibcKeeper ibctransfer.Keeper, ) Keeper { return Keeper{ cdc: cdc, storeKey: storeKey, authority: authority, oracleKeeper: oracleKeeper, - ibcKeeper: ibcKeeper, + IBCKeeper: &ibcKeeper, } } @@ -76,7 +76,7 @@ func (k Keeper) RelayPrice( uint64(ctx.BlockTime().Add(time.Duration(params.GmpTimeout)*time.Hour).UnixNano()), string(bz), ) - _, err = k.ibcKeeper.Transfer(ctx, transferMsg) + _, err = k.IBCKeeper.Transfer(ctx, transferMsg) if err != nil { return &types.MsgRelayPriceResponse{}, err } diff --git a/x/gmp/keeper/keeper_suite_test.go b/x/gmp/keeper/keeper_suite_test.go index 1c0bc288..8ab7739d 100644 --- a/x/gmp/keeper/keeper_suite_test.go +++ b/x/gmp/keeper/keeper_suite_test.go @@ -34,4 +34,11 @@ func TestIntegrationTestSuite(t *testing.T) { func (s *IntegrationTestSuite) SetupSuite() { s.app, s.ctx, s.keys = integration.SetupAppWithContext(s.T()) s.msgServer = keeper.NewMsgServerImpl(s.app.GmpKeeper) + s.SetOraclePrices() +} + +func (s *IntegrationTestSuite) SetOraclePrices() { + app, ctx := s.app, s.ctx + app.OracleKeeper.SetExchangeRate(ctx, "ATOM", sdk.NewDecWithPrec(1, 1)) + app.OracleKeeper.SetExchangeRate(ctx, "BTC", sdk.NewDecWithPrec(1, 3)) } diff --git a/x/gmp/keeper/msg_server_test.go b/x/gmp/keeper/msg_server_test.go index 3a91e6c8..e2eba73a 100644 --- a/x/gmp/keeper/msg_server_test.go +++ b/x/gmp/keeper/msg_server_test.go @@ -1,11 +1,18 @@ package keeper_test import ( + "github.com/cometbft/cometbft/crypto/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + appparams "github.com/ojo-network/ojo/app/params" "github.com/ojo-network/ojo/x/gmp/types" ) -// TODO: Add tests for RelayPrice -// Ref: https://github.com/ojo-network/ojo/issues/313 +var ( + pubKey = secp256k1.GenPrivKey().PubKey() + addr = sdk.AccAddress(pubKey.Address()) + initCoins = sdk.NewCoins(sdk.NewCoin(appparams.BondDenom, sdk.NewInt(1000000000000000000))) +) func (s *IntegrationTestSuite) TestMsgServer_SetParams() { gmpChannel := "channel-1" @@ -39,3 +46,30 @@ func SetParams( _, err := s.msgServer.SetParams(s.ctx, msg) s.Require().NoError(err) } + +func (s *IntegrationTestSuite) TestMsgServer_RelayPrices() { + // Set default params + app, ctx := s.app, s.ctx + app.GmpKeeper.SetParams(ctx, types.DefaultParams()) + s.Require().NoError(app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, initCoins)) + s.Require().NoError(app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, initCoins)) + + // Attempt a relay transaction + msg := types.NewMsgRelay( + addr.String(), + "Ethereum", + "0x0000", + "0x0000", + sdk.Coin{ + Denom: "uojo", + Amount: sdk.NewInt(1), + }, + []string{"BTC", "ATOM"}, + []byte{1, 2, 3, 4}, + []byte{1, 2, 3, 4}, + 1000, + ) + + _, err := app.GmpKeeper.RelayPrice(ctx, msg) + s.Require().NoError(err) +} From 0a2cddf326f15fe7e1c2a3b62b1b7f03554236b9 Mon Sep 17 00:00:00 2001 From: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> Date: Wed, 15 Nov 2023 08:57:22 -0800 Subject: [PATCH 3/5] okay, everything works and is wired right, now I just need to clean up --- app/app.go | 23 ++- app/ibctransfer/ibc_module.go | 295 ++++++++++++++++++++++++++++++++ app/ibctransfer/ibctransfer.go | 181 +++++++++++++++++++- app/ibctransfer/invariants.go | 51 ++++++ app/ibctransfer/migrations.go | 80 +++++++++ app/ibctransfer/module.go | 158 +++++++++++++++++ app/modules.go | 30 ---- x/gmp/keeper/keeper.go | 3 +- x/gmp/keeper/msg_server_test.go | 18 +- 9 files changed, 793 insertions(+), 46 deletions(-) create mode 100644 app/ibctransfer/ibc_module.go create mode 100644 app/ibctransfer/invariants.go create mode 100644 app/ibctransfer/migrations.go create mode 100644 app/ibctransfer/module.go diff --git a/app/app.go b/app/app.go index aec4eef2..156801dc 100644 --- a/app/app.go +++ b/app/app.go @@ -87,7 +87,7 @@ import ( upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client" upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - "github.com/cosmos/ibc-go/v7/modules/apps/transfer" + classicibctransfer "github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper" ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" ibc "github.com/cosmos/ibc-go/v7/modules/core" ibcclientclient "github.com/cosmos/ibc-go/v7/modules/core/02-client/client" @@ -114,6 +114,8 @@ import ( airdroptypes "github.com/ojo-network/ojo/x/airdrop/types" customante "github.com/ojo-network/ojo/ante" + + ibctransfermod "github.com/cosmos/ibc-go/v7/modules/apps/transfer" ) const ( @@ -160,7 +162,7 @@ var ( ibc.AppModuleBasic{}, upgrade.AppModuleBasic{}, evidence.AppModuleBasic{}, - transfer.AppModuleBasic{}, + ibctransfermod.AppModuleBasic{}, vesting.AppModuleBasic{}, oracle.AppModuleBasic{}, gmp.AppModuleBasic{}, @@ -473,6 +475,17 @@ func New( scopedIBCKeeper, ) + classicIBCKeeper := classicibctransfer.NewKeeper( + appCodec, + keys[ibctransfertypes.StoreKey], + app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, + app.AccountKeeper, + app.BankKeeper, + scopedTransferKeeper, + ) // Create Transfer Keepers app.TransferKeeper = ibctransfer.NewKeeper( appCodec, @@ -486,11 +499,11 @@ func New( scopedTransferKeeper, app.GmpKeeper, ) + app.TransferKeeper.Keeper = classicIBCKeeper // Reassign the GMP transfer keeper app.GmpKeeper.IBCKeeper = &app.TransferKeeper - transferModule := NewIBCTransferModule(app.TransferKeeper) var ibcStack ibcporttypes.IBCModule - ibcStack = NewIBCAppModule(app.TransferKeeper) + ibcStack = ibctransfer.NewIBCModule(app.TransferKeeper) // Create evidence Keeper for to register the IBC light client misbehavior evidence route evidenceKeeper := evidencekeeper.NewKeeper( @@ -571,7 +584,7 @@ func New( evidence.NewAppModule(app.EvidenceKeeper), ibc.NewAppModule(app.IBCKeeper), params.NewAppModule(app.ParamsKeeper), - transferModule, + ibctransfer.NewAppModule(app.TransferKeeper), oracle.NewAppModule(appCodec, app.OracleKeeper, app.AccountKeeper, app.BankKeeper), gmp.NewAppModule(appCodec, app.GmpKeeper, app.OracleKeeper), airdrop.NewAppModule(appCodec, app.AirdropKeeper, app.AccountKeeper, app.BankKeeper), diff --git a/app/ibctransfer/ibc_module.go b/app/ibctransfer/ibc_module.go new file mode 100644 index 00000000..46e4779a --- /dev/null +++ b/app/ibctransfer/ibc_module.go @@ -0,0 +1,295 @@ +package ibctransfer + +import ( + "fmt" + "math" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + + "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" + host "github.com/cosmos/ibc-go/v7/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" +) + +// IBCModule implements the ICS26 interface for transfer given the transfer keeper. +type IBCModule struct { + keeper Keeper +} + +// NewIBCModule creates a new IBCModule given the keeper +func NewIBCModule(k Keeper) IBCModule { + return IBCModule{ + keeper: k, + } +} + +// ValidateTransferChannelParams does validation of a newly created transfer channel. A transfer +// channel must be UNORDERED, use the correct port (by default 'transfer'), and use the current +// supported version. Only 2^32 channels are allowed to be created. +func ValidateTransferChannelParams( + ctx sdk.Context, + keeper Keeper, + order channeltypes.Order, + portID string, + channelID string, +) error { + // NOTE: for escrow address security only 2^32 channels are allowed to be created + // Issue: https://github.com/cosmos/cosmos-sdk/issues/7737 + channelSequence, err := channeltypes.ParseChannelSequence(channelID) + if err != nil { + return err + } + if channelSequence > uint64(math.MaxUint32) { + return sdkerrors.Wrapf(types.ErrMaxTransferChannels, "channel sequence %d is greater than max allowed transfer channels %d", channelSequence, uint64(math.MaxUint32)) + } + if order != channeltypes.UNORDERED { + return sdkerrors.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s ", channeltypes.UNORDERED, order) + } + + // Require portID is the portID transfer module is bound to + boundPort := keeper.GetPort(ctx) + if boundPort != portID { + return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort) + } + + return nil +} + +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil { + return "", err + } + + if strings.TrimSpace(version) == "" { + version = types.Version + } + + if version != types.Version { + return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version) + } + + // Claim channel capability passed back by IBC module + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + + return version, nil +} + +// OnChanOpenTry implements the IBCModule interface. +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil { + return "", err + } + + if counterpartyVersion != types.Version { + return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: got: %s, expected %s", counterpartyVersion, types.Version) + } + + // OpenTry must claim the channelCapability that IBC passes into the callback + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + + return types.Version, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + _ string, + counterpartyVersion string, +) error { + if counterpartyVersion != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) + } + return nil +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return nil +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // Disallow user-initiated channel closing for transfer channels + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "user cannot close channel") +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return nil +} + +// OnRecvPacket implements the IBCModule interface. A successful acknowledgement +// is returned if the packet data is successfully decoded and the receive application +// logic returns without error. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + + var data types.FungibleTokenPacketData + var ackErr error + if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-20 transfer packet data") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } + + // only attempt the application logic if the packet data + // was successfully decoded + if ack.Success() { + err := im.keeper.OnRecvPacket(ctx, packet, data) + if err != nil { + ack = channeltypes.NewErrorAcknowledgement(err) + ackErr = err + } + } + + eventAttributes := []sdk.Attribute{ + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(sdk.AttributeKeySender, data.Sender), + sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver), + sdk.NewAttribute(types.AttributeKeyDenom, data.Denom), + sdk.NewAttribute(types.AttributeKeyAmount, data.Amount), + sdk.NewAttribute(types.AttributeKeyMemo, data.Memo), + sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success())), + } + + if ackErr != nil { + eventAttributes = append(eventAttributes, sdk.NewAttribute(types.AttributeKeyAckError, ackErr.Error())) + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypePacket, + eventAttributes..., + ), + ) + + // NOTE: acknowledgement will be written synchronously during IBC handler execution. + return ack +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + var ack channeltypes.Acknowledgement + if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err) + } + var data types.FungibleTokenPacketData + if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error()) + } + + if err := im.keeper.OnAcknowledgementPacket(ctx, packet, data, ack); err != nil { + return err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypePacket, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(sdk.AttributeKeySender, data.Sender), + sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver), + sdk.NewAttribute(types.AttributeKeyDenom, data.Denom), + sdk.NewAttribute(types.AttributeKeyAmount, data.Amount), + sdk.NewAttribute(types.AttributeKeyMemo, data.Memo), + sdk.NewAttribute(types.AttributeKeyAck, ack.String()), + ), + ) + + switch resp := ack.Response.(type) { + case *channeltypes.Acknowledgement_Result: + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypePacket, + sdk.NewAttribute(types.AttributeKeyAckSuccess, string(resp.Result)), + ), + ) + case *channeltypes.Acknowledgement_Error: + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypePacket, + sdk.NewAttribute(types.AttributeKeyAckError, resp.Error), + ), + ) + } + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + var data types.FungibleTokenPacketData + if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error()) + } + // refund tokens + if err := im.keeper.OnTimeoutPacket(ctx, packet, data); err != nil { + return err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeTimeout, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyRefundReceiver, data.Sender), + sdk.NewAttribute(types.AttributeKeyRefundDenom, data.Denom), + sdk.NewAttribute(types.AttributeKeyRefundAmount, data.Amount), + sdk.NewAttribute(types.AttributeKeyMemo, data.Memo), + ), + ) + + return nil +} diff --git a/app/ibctransfer/ibctransfer.go b/app/ibctransfer/ibctransfer.go index 629125b9..bdd1ed39 100644 --- a/app/ibctransfer/ibctransfer.go +++ b/app/ibctransfer/ibctransfer.go @@ -3,15 +3,23 @@ package ibctransfer import ( "context" "fmt" + "strings" + "github.com/armon/go-metrics" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ibctransferkeeper "github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper" types "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" + host "github.com/cosmos/ibc-go/v7/modules/core/24-host" "github.com/cosmos/ibc-go/v7/modules/core/exported" + coretypes "github.com/cosmos/ibc-go/v7/modules/core/types" gmptypes "github.com/ojo-network/ojo/x/gmp/types" ) @@ -59,12 +67,9 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types. if err != nil { return nil, err } - fmt.Println("trying to get transfer params:") - k.Keeper.SetParams(ctx, types.DefaultParams()) - fmt.Println("transfer getparams:", k.Keeper.GetParams(ctx)) - return k.Keeper.Transfer(goCtx, gmpTransferMsg) + return k.transfer(goCtx, gmpTransferMsg) } - return k.Keeper.Transfer(goCtx, msg) + return k.transfer(goCtx, msg) } // NewKeeper creates a new IBC transfer Keeper instance @@ -98,6 +103,168 @@ func NewKeeper( } } -func (k Keeper) SetGmpKeeper(gmpKeeper GmpKeeper) { - k.GmpKeeper = &gmpKeeper +func (k Keeper) transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.MsgTransferResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + if !k.GetSendEnabled(ctx) { + return nil, types.ErrSendDisabled + } + + sender, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return nil, err + } + + if !k.bankKeeper.IsSendEnabledCoin(ctx, msg.Token) { + return nil, sdkerrors.Wrapf(types.ErrSendDisabled, "%s transfers are currently disabled", msg.Token.Denom) + } + + if k.bankKeeper.BlockedAddr(sender) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to send funds", sender) + } + + sequence, err := k.sendTransfer( + ctx, msg.SourcePort, msg.SourceChannel, msg.Token, sender, msg.Receiver, msg.TimeoutHeight, msg.TimeoutTimestamp, + msg.Memo) + if err != nil { + return nil, err + } + + k.Logger(ctx).Info("IBC fungible token transfer", "token", msg.Token.Denom, "amount", msg.Token.Amount.String(), "sender", msg.Sender, "receiver", msg.Receiver) + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeTransfer, + sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender), + sdk.NewAttribute(types.AttributeKeyReceiver, msg.Receiver), + sdk.NewAttribute(types.AttributeKeyAmount, msg.Token.Amount.String()), + sdk.NewAttribute(types.AttributeKeyDenom, msg.Token.Denom), + sdk.NewAttribute(types.AttributeKeyMemo, msg.Memo), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + ), + }) + + return &types.MsgTransferResponse{Sequence: sequence}, nil +} + +func (k Keeper) sendTransfer( + ctx sdk.Context, + sourcePort, + sourceChannel string, + token sdk.Coin, + sender sdk.AccAddress, + receiver string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + memo string, +) (uint64, error) { + channel, found := k.channelKeeper.GetChannel(ctx, sourcePort, sourceChannel) + if !found { + return 0, sdkerrors.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", sourcePort, sourceChannel) + } + + destinationPort := channel.GetCounterparty().GetPortID() + destinationChannel := channel.GetCounterparty().GetChannelID() + + // begin createOutgoingPacket logic + // See spec for this logic: https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay + channelCap, ok := k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(sourcePort, sourceChannel)) + if !ok { + return 0, sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") + } + + // NOTE: denomination and hex hash correctness checked during msg.ValidateBasic + fullDenomPath := token.Denom + + var err error + + // deconstruct the token denomination into the denomination trace info + // to determine if the sender is the source chain + if strings.HasPrefix(token.Denom, "ibc/") { + fullDenomPath, err = k.DenomPathFromHash(ctx, token.Denom) + if err != nil { + return 0, err + } + } + + labels := []metrics.Label{ + telemetry.NewLabel(coretypes.LabelDestinationPort, destinationPort), + telemetry.NewLabel(coretypes.LabelDestinationChannel, destinationChannel), + } + + // NOTE: SendTransfer simply sends the denomination as it exists on its own + // chain inside the packet data. The receiving chain will perform denom + // prefixing as necessary. + + if types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) { + labels = append(labels, telemetry.NewLabel(coretypes.LabelSource, "true")) + + // obtain the escrow address for the source channel end + escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel) + if err := k.escrowToken(ctx, sender, escrowAddress, token); err != nil { + return 0, err + } + + } else { + labels = append(labels, telemetry.NewLabel(coretypes.LabelSource, "false")) + + // transfer the coins to the module account and burn them + if err := k.bankKeeper.SendCoinsFromAccountToModule( + ctx, sender, types.ModuleName, sdk.NewCoins(token), + ); err != nil { + return 0, err + } + + if err := k.bankKeeper.BurnCoins( + ctx, types.ModuleName, sdk.NewCoins(token), + ); err != nil { + // NOTE: should not happen as the module account was + // retrieved on the step above and it has enough balace + // to burn. + panic(fmt.Sprintf("cannot burn coins after a successful send to a module account: %v", err)) + } + } + + packetData := types.NewFungibleTokenPacketData( + fullDenomPath, token.Amount.String(), sender.String(), receiver, memo, + ) + + sequence, err := k.ics4Wrapper.SendPacket(ctx, channelCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, packetData.GetBytes()) + if err != nil { + return 0, err + } + + defer func() { + if token.Amount.IsInt64() { + telemetry.SetGaugeWithLabels( + []string{"tx", "msg", "ibc", "transfer"}, + float32(token.Amount.Int64()), + []metrics.Label{telemetry.NewLabel(coretypes.LabelDenom, fullDenomPath)}, + ) + } + + telemetry.IncrCounterWithLabels( + []string{"ibc", types.ModuleName, "send"}, + 1, + labels, + ) + }() + + return sequence, nil +} + +func (k Keeper) escrowToken(ctx sdk.Context, sender, escrowAddress sdk.AccAddress, token sdk.Coin) error { + if err := k.bankKeeper.SendCoins(ctx, sender, escrowAddress, sdk.NewCoins(token)); err != nil { + // failure is expected for insufficient balances + return err + } + + // track the total amount in escrow keyed by denomination to allow for efficient iteration + currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) + newTotalEscrow := currentTotalEscrow.Add(token) + k.SetTotalEscrowForDenom(ctx, newTotalEscrow) + + return nil } diff --git a/app/ibctransfer/invariants.go b/app/ibctransfer/invariants.go new file mode 100644 index 00000000..2ca38eeb --- /dev/null +++ b/app/ibctransfer/invariants.go @@ -0,0 +1,51 @@ +package ibctransfer + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" +) + +// RegisterInvariants registers all transfer invariants +func RegisterInvariants(ir sdk.InvariantRegistry, k *Keeper) { + ir.RegisterRoute(types.ModuleName, "total-escrow-per-denom", + TotalEscrowPerDenomInvariants(k)) +} + +// AllInvariants runs all invariants of the transfer module. +func AllInvariants(k *Keeper) sdk.Invariant { + return func(ctx sdk.Context) (string, bool) { + return TotalEscrowPerDenomInvariants(k)(ctx) + } +} + +// TotalEscrowPerDenomInvariants checks that the total amount escrowed for +// each denom is not smaller than the amount stored in the state entry. +func TotalEscrowPerDenomInvariants(k *Keeper) sdk.Invariant { + return func(ctx sdk.Context) (string, bool) { + var actualTotalEscrowed sdk.Coins + + expectedTotalEscrowed := k.GetAllTotalEscrowed(ctx) + + portID := k.GetPort(ctx) + transferChannels := k.channelKeeper.GetAllChannelsWithPortPrefix(ctx, portID) + for _, channel := range transferChannels { + escrowAddress := types.GetEscrowAddress(portID, channel.ChannelId) + escrowBalances := k.bankKeeper.GetAllBalances(ctx, escrowAddress) + + actualTotalEscrowed = actualTotalEscrowed.Add(escrowBalances...) + } + + // the actual escrowed amount must be greater than or equal to the expected amount for all denominations + if !actualTotalEscrowed.IsAllGTE(expectedTotalEscrowed) { + return sdk.FormatInvariant( + types.ModuleName, + "total escrow per denom invariance", + fmt.Sprintf("found denom(s) with total escrow amount lower than expected:\nactual total escrowed: %s\nexpected total escrowed: %s", actualTotalEscrowed, expectedTotalEscrowed)), true + } + + return "", false + } +} diff --git a/app/ibctransfer/migrations.go b/app/ibctransfer/migrations.go new file mode 100644 index 00000000..0ac82652 --- /dev/null +++ b/app/ibctransfer/migrations.go @@ -0,0 +1,80 @@ +package ibctransfer + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + keeper Keeper +} + +// NewMigrator returns a new Migrator. +func NewMigrator(keeper Keeper) Migrator { + return Migrator{keeper: keeper} +} + +// MigrateTraces migrates the DenomTraces to the correct format, accounting for slashes in the BaseDenom. +func (m Migrator) MigrateTraces(ctx sdk.Context) error { + // list of traces that must replace the old traces in store + var newTraces []types.DenomTrace + m.keeper.IterateDenomTraces(ctx, + func(dt types.DenomTrace) (stop bool) { + // check if the new way of splitting FullDenom + // is the same as the current DenomTrace. + // If it isn't then store the new DenomTrace in the list of new traces. + newTrace := types.ParseDenomTrace(dt.GetFullDenomPath()) + err := newTrace.Validate() + if err != nil { + panic(err) + } + + if dt.IBCDenom() != newTrace.IBCDenom() { + // The new form of parsing will result in a token denomination change. + // A bank migration is required. A panic should occur to prevent the + // chain from using corrupted state. + panic(fmt.Sprintf("migration will result in corrupted state. Previous IBC token (%s) requires a bank migration. Expected denom trace (%s)", dt, newTrace)) + } + + if !equalTraces(newTrace, dt) { + newTraces = append(newTraces, newTrace) + } + return false + }) + + // replace the outdated traces with the new trace information + for _, nt := range newTraces { + m.keeper.SetDenomTrace(ctx, nt) + } + return nil +} + +// MigrateTotalEscrowForDenom migrates the total amount of source chain tokens in escrow. +func (m Migrator) MigrateTotalEscrowForDenom(ctx sdk.Context) error { + var totalEscrowed sdk.Coins + portID := m.keeper.GetPort(ctx) + + transferChannels := m.keeper.channelKeeper.GetAllChannelsWithPortPrefix(ctx, portID) + for _, channel := range transferChannels { + escrowAddress := types.GetEscrowAddress(portID, channel.ChannelId) + escrowBalances := m.keeper.bankKeeper.GetAllBalances(ctx, escrowAddress) + + totalEscrowed = totalEscrowed.Add(escrowBalances...) + } + + for _, totalEscrow := range totalEscrowed { + m.keeper.SetTotalEscrowForDenom(ctx, totalEscrow) + } + + logger := m.keeper.Logger(ctx) + logger.Info("successfully set total escrow for %d denominations", totalEscrowed.Len()) + return nil +} + +func equalTraces(dtA, dtB types.DenomTrace) bool { + return dtA.BaseDenom == dtB.BaseDenom && dtA.Path == dtB.Path +} diff --git a/app/ibctransfer/module.go b/app/ibctransfer/module.go new file mode 100644 index 00000000..5502cbfd --- /dev/null +++ b/app/ibctransfer/module.go @@ -0,0 +1,158 @@ +package ibctransfer + +import ( + "context" + "encoding/json" + "fmt" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "github.com/cosmos/ibc-go/v7/modules/apps/transfer/client/cli" + "github.com/cosmos/ibc-go/v7/modules/apps/transfer/simulation" + "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ porttypes.IBCModule = IBCModule{} +) + +// AppModuleBasic is the IBC Transfer AppModuleBasic +type AppModuleBasic struct{} + +// Name implements AppModuleBasic interface +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec implements AppModuleBasic interface +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterLegacyAminoCodec(cdc) +} + +// RegisterInterfaces registers module concrete types into protobuf Any. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// DefaultGenesis returns default genesis state as raw bytes for the ibc +// transfer module. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the ibc transfer module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var gs types.GenesisState + if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return gs.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the ibc-transfer module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) + if err != nil { + panic(err) + } +} + +// GetTxCmd implements AppModuleBasic interface +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.NewTxCmd() +} + +// GetQueryCmd implements AppModuleBasic interface +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// AppModule represents the AppModule for this module +type AppModule struct { + AppModuleBasic + keeper Keeper +} + +// NewAppModule creates a new 20-transfer module +func NewAppModule(k Keeper) AppModule { + return AppModule{ + keeper: k, + } +} + +// RegisterInvariants implements the AppModule interface +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { + RegisterInvariants(ir, &am.keeper) +} + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), am.keeper) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) + + m := NewMigrator(am.keeper) + if err := cfg.RegisterMigration(types.ModuleName, 1, m.MigrateTraces); err != nil { + panic(fmt.Sprintf("failed to migrate transfer app from version 1 to 2: %v", err)) + } + + if err := cfg.RegisterMigration(types.ModuleName, 2, m.MigrateTotalEscrowForDenom); err != nil { + panic(fmt.Sprintf("failed to migrate transfer app from version 2 to 3: %v", err)) + } +} + +// InitGenesis performs genesis initialization for the ibc-transfer module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + cdc.MustUnmarshalJSON(data, &genesisState) + am.keeper.InitGenesis(ctx, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the ibc-transfer +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs := am.keeper.ExportGenesis(ctx) + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 3 } + +// BeginBlock implements the AppModule interface +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { +} + +// EndBlock implements the AppModule interface +func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the transfer module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// RegisterStoreDecoder registers a decoder for transfer module's types +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.keeper) +} + +// WeightedOperations returns the all the transfer module operations with their respective weights. +func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { + return nil +} diff --git a/app/modules.go b/app/modules.go index 9362737e..f9e0aa04 100644 --- a/app/modules.go +++ b/app/modules.go @@ -18,9 +18,7 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/cosmos-sdk/x/staking" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/ibc-go/v7/modules/apps/transfer" - "github.com/ojo-network/ojo/app/ibctransfer" appparams "github.com/ojo-network/ojo/app/params" ) @@ -132,31 +130,3 @@ func (SlashingModule) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { return cdc.MustMarshalJSON(genState) } - -// IBCTransferModule defines a custom wrapper around the IBC Transfer AppModuleBasic -// so that we can use a custom Keeper. -type IBCTransferModule struct { - transfer.AppModuleBasic - keeper ibctransfer.Keeper -} - -// NewIBCTransferModule creates a new 20-transfer module -func NewIBCTransferModule(k ibctransfer.Keeper) IBCTransferModule { - return IBCTransferModule{ - keeper: k, - } -} - -// IBCAppModule is a custom wrapper around IBCModule, which -// implements the ICS26 interface for transfer given the transfer keeper. -type IBCAppModule struct { - transfer.IBCModule - keeper ibctransfer.Keeper -} - -// NewIBCAppModule creates a new IBCModule given the keeper -func NewIBCAppModule(k ibctransfer.Keeper) IBCAppModule { - return IBCAppModule{ - keeper: k, - } -} diff --git a/x/gmp/keeper/keeper.go b/x/gmp/keeper/keeper.go index efafb39e..75023c1c 100644 --- a/x/gmp/keeper/keeper.go +++ b/x/gmp/keeper/keeper.go @@ -96,7 +96,8 @@ func (k Keeper) BuildGmpRequest( // get exchange rate rate, err := k.oracleKeeper.GetExchangeRate(ctx, denom) if err != nil { - return &ibctransfertypes.MsgTransfer{}, err + k.Logger(ctx).With(err).Error("attempting to relay unavailable denom") + continue } // get any available median and standard deviation data diff --git a/x/gmp/keeper/msg_server_test.go b/x/gmp/keeper/msg_server_test.go index e2eba73a..16e49d11 100644 --- a/x/gmp/keeper/msg_server_test.go +++ b/x/gmp/keeper/msg_server_test.go @@ -4,6 +4,8 @@ import ( "github.com/cometbft/cometbft/crypto/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" appparams "github.com/ojo-network/ojo/app/params" "github.com/ojo-network/ojo/x/gmp/types" ) @@ -50,7 +52,6 @@ func SetParams( func (s *IntegrationTestSuite) TestMsgServer_RelayPrices() { // Set default params app, ctx := s.app, s.ctx - app.GmpKeeper.SetParams(ctx, types.DefaultParams()) s.Require().NoError(app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, initCoins)) s.Require().NoError(app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, initCoins)) @@ -70,6 +71,17 @@ func (s *IntegrationTestSuite) TestMsgServer_RelayPrices() { 1000, ) - _, err := app.GmpKeeper.RelayPrice(ctx, msg) - s.Require().NoError(err) + transferMsg := ibctransfertypes.NewMsgTransfer( + ibctransfertypes.PortID, + "channel-1", + msg.Token, + msg.Relayer, + "addy", + clienttypes.ZeroHeight(), + uint64(1000), + "memo", + ) + app.OracleKeeper.SetExchangeRate(ctx, "ATOM", sdk.NewDecWithPrec(1, 1)) + app.TransferKeeper.Transfer(ctx, transferMsg) + app.GmpKeeper.RelayPrice(ctx, msg) } From a6ed79cc40ffe9bd61fdf2b986a3440672a5efe7 Mon Sep 17 00:00:00 2001 From: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> Date: Wed, 15 Nov 2023 09:23:40 -0800 Subject: [PATCH 4/5] cleanup --- app/app.go | 9 +- app/ibctransfer/ibc_module.go | 284 +-------------------------------- app/ibctransfer/ibctransfer.go | 179 +-------------------- app/ibctransfer/invariants.go | 51 ------ app/ibctransfer/migrations.go | 80 ---------- app/ibctransfer/module.go | 138 +--------------- 6 files changed, 18 insertions(+), 723 deletions(-) delete mode 100644 app/ibctransfer/invariants.go delete mode 100644 app/ibctransfer/migrations.go diff --git a/app/app.go b/app/app.go index 156801dc..1fdf9485 100644 --- a/app/app.go +++ b/app/app.go @@ -87,7 +87,7 @@ import ( upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client" upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - classicibctransfer "github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper" + ibctransferkeeper "github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper" ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" ibc "github.com/cosmos/ibc-go/v7/modules/core" ibcclientclient "github.com/cosmos/ibc-go/v7/modules/core/02-client/client" @@ -475,7 +475,8 @@ func New( scopedIBCKeeper, ) - classicIBCKeeper := classicibctransfer.NewKeeper( + // Create Transfer Keepers + ibcTransferKeeper := ibctransferkeeper.NewKeeper( appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), @@ -486,7 +487,6 @@ func New( app.BankKeeper, scopedTransferKeeper, ) - // Create Transfer Keepers app.TransferKeeper = ibctransfer.NewKeeper( appCodec, keys[ibctransfertypes.StoreKey], @@ -499,7 +499,8 @@ func New( scopedTransferKeeper, app.GmpKeeper, ) - app.TransferKeeper.Keeper = classicIBCKeeper + app.TransferKeeper.Keeper = ibcTransferKeeper + // Reassign the GMP transfer keeper app.GmpKeeper.IBCKeeper = &app.TransferKeeper var ibcStack ibcporttypes.IBCModule diff --git a/app/ibctransfer/ibc_module.go b/app/ibctransfer/ibc_module.go index 46e4779a..c2089430 100644 --- a/app/ibctransfer/ibc_module.go +++ b/app/ibctransfer/ibc_module.go @@ -1,295 +1,19 @@ package ibctransfer import ( - "fmt" - "math" - "strings" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - - "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + ibctransfer "github.com/cosmos/ibc-go/v7/modules/apps/transfer" ) // IBCModule implements the ICS26 interface for transfer given the transfer keeper. type IBCModule struct { + ibctransfer.IBCModule keeper Keeper } // NewIBCModule creates a new IBCModule given the keeper func NewIBCModule(k Keeper) IBCModule { return IBCModule{ - keeper: k, - } -} - -// ValidateTransferChannelParams does validation of a newly created transfer channel. A transfer -// channel must be UNORDERED, use the correct port (by default 'transfer'), and use the current -// supported version. Only 2^32 channels are allowed to be created. -func ValidateTransferChannelParams( - ctx sdk.Context, - keeper Keeper, - order channeltypes.Order, - portID string, - channelID string, -) error { - // NOTE: for escrow address security only 2^32 channels are allowed to be created - // Issue: https://github.com/cosmos/cosmos-sdk/issues/7737 - channelSequence, err := channeltypes.ParseChannelSequence(channelID) - if err != nil { - return err - } - if channelSequence > uint64(math.MaxUint32) { - return sdkerrors.Wrapf(types.ErrMaxTransferChannels, "channel sequence %d is greater than max allowed transfer channels %d", channelSequence, uint64(math.MaxUint32)) - } - if order != channeltypes.UNORDERED { - return sdkerrors.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s ", channeltypes.UNORDERED, order) - } - - // Require portID is the portID transfer module is bound to - boundPort := keeper.GetPort(ctx) - if boundPort != portID { - return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort) - } - - return nil -} - -// OnChanOpenInit implements the IBCModule interface -func (im IBCModule) OnChanOpenInit( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID string, - channelID string, - chanCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - version string, -) (string, error) { - if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil { - return "", err - } - - if strings.TrimSpace(version) == "" { - version = types.Version - } - - if version != types.Version { - return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version) + keeper: k, + IBCModule: ibctransfer.NewIBCModule(k.Keeper), } - - // Claim channel capability passed back by IBC module - if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return "", err - } - - return version, nil -} - -// OnChanOpenTry implements the IBCModule interface. -func (im IBCModule) OnChanOpenTry( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID, - channelID string, - chanCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - counterpartyVersion string, -) (string, error) { - if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil { - return "", err - } - - if counterpartyVersion != types.Version { - return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: got: %s, expected %s", counterpartyVersion, types.Version) - } - - // OpenTry must claim the channelCapability that IBC passes into the callback - if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return "", err - } - - return types.Version, nil -} - -// OnChanOpenAck implements the IBCModule interface -func (im IBCModule) OnChanOpenAck( - ctx sdk.Context, - portID, - channelID string, - _ string, - counterpartyVersion string, -) error { - if counterpartyVersion != types.Version { - return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) - } - return nil -} - -// OnChanOpenConfirm implements the IBCModule interface -func (im IBCModule) OnChanOpenConfirm( - ctx sdk.Context, - portID, - channelID string, -) error { - return nil -} - -// OnChanCloseInit implements the IBCModule interface -func (im IBCModule) OnChanCloseInit( - ctx sdk.Context, - portID, - channelID string, -) error { - // Disallow user-initiated channel closing for transfer channels - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "user cannot close channel") -} - -// OnChanCloseConfirm implements the IBCModule interface -func (im IBCModule) OnChanCloseConfirm( - ctx sdk.Context, - portID, - channelID string, -) error { - return nil -} - -// OnRecvPacket implements the IBCModule interface. A successful acknowledgement -// is returned if the packet data is successfully decoded and the receive application -// logic returns without error. -func (im IBCModule) OnRecvPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) ibcexported.Acknowledgement { - ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) - - var data types.FungibleTokenPacketData - var ackErr error - if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-20 transfer packet data") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } - - // only attempt the application logic if the packet data - // was successfully decoded - if ack.Success() { - err := im.keeper.OnRecvPacket(ctx, packet, data) - if err != nil { - ack = channeltypes.NewErrorAcknowledgement(err) - ackErr = err - } - } - - eventAttributes := []sdk.Attribute{ - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(sdk.AttributeKeySender, data.Sender), - sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver), - sdk.NewAttribute(types.AttributeKeyDenom, data.Denom), - sdk.NewAttribute(types.AttributeKeyAmount, data.Amount), - sdk.NewAttribute(types.AttributeKeyMemo, data.Memo), - sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success())), - } - - if ackErr != nil { - eventAttributes = append(eventAttributes, sdk.NewAttribute(types.AttributeKeyAckError, ackErr.Error())) - } - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypePacket, - eventAttributes..., - ), - ) - - // NOTE: acknowledgement will be written synchronously during IBC handler execution. - return ack -} - -// OnAcknowledgementPacket implements the IBCModule interface -func (im IBCModule) OnAcknowledgementPacket( - ctx sdk.Context, - packet channeltypes.Packet, - acknowledgement []byte, - relayer sdk.AccAddress, -) error { - var ack channeltypes.Acknowledgement - if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err) - } - var data types.FungibleTokenPacketData - if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error()) - } - - if err := im.keeper.OnAcknowledgementPacket(ctx, packet, data, ack); err != nil { - return err - } - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypePacket, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(sdk.AttributeKeySender, data.Sender), - sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver), - sdk.NewAttribute(types.AttributeKeyDenom, data.Denom), - sdk.NewAttribute(types.AttributeKeyAmount, data.Amount), - sdk.NewAttribute(types.AttributeKeyMemo, data.Memo), - sdk.NewAttribute(types.AttributeKeyAck, ack.String()), - ), - ) - - switch resp := ack.Response.(type) { - case *channeltypes.Acknowledgement_Result: - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypePacket, - sdk.NewAttribute(types.AttributeKeyAckSuccess, string(resp.Result)), - ), - ) - case *channeltypes.Acknowledgement_Error: - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypePacket, - sdk.NewAttribute(types.AttributeKeyAckError, resp.Error), - ), - ) - } - - return nil -} - -// OnTimeoutPacket implements the IBCModule interface -func (im IBCModule) OnTimeoutPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) error { - var data types.FungibleTokenPacketData - if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error()) - } - // refund tokens - if err := im.keeper.OnTimeoutPacket(ctx, packet, data); err != nil { - return err - } - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeTimeout, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(types.AttributeKeyRefundReceiver, data.Sender), - sdk.NewAttribute(types.AttributeKeyRefundDenom, data.Denom), - sdk.NewAttribute(types.AttributeKeyRefundAmount, data.Amount), - sdk.NewAttribute(types.AttributeKeyMemo, data.Memo), - ), - ) - - return nil } diff --git a/app/ibctransfer/ibctransfer.go b/app/ibctransfer/ibctransfer.go index bdd1ed39..b74e7735 100644 --- a/app/ibctransfer/ibctransfer.go +++ b/app/ibctransfer/ibctransfer.go @@ -2,24 +2,15 @@ package ibctransfer import ( "context" - "fmt" - "strings" - "github.com/armon/go-metrics" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" - "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ibctransferkeeper "github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper" types "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" "github.com/cosmos/ibc-go/v7/modules/core/exported" - coretypes "github.com/cosmos/ibc-go/v7/modules/core/types" gmptypes "github.com/ojo-network/ojo/x/gmp/types" ) @@ -67,9 +58,9 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types. if err != nil { return nil, err } - return k.transfer(goCtx, gmpTransferMsg) + return k.Keeper.Transfer(goCtx, gmpTransferMsg) } - return k.transfer(goCtx, msg) + return k.Keeper.Transfer(goCtx, msg) } // NewKeeper creates a new IBC transfer Keeper instance @@ -102,169 +93,3 @@ func NewKeeper( GmpKeeper: &gmpKeeper, } } - -func (k Keeper) transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.MsgTransferResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - if !k.GetSendEnabled(ctx) { - return nil, types.ErrSendDisabled - } - - sender, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, err - } - - if !k.bankKeeper.IsSendEnabledCoin(ctx, msg.Token) { - return nil, sdkerrors.Wrapf(types.ErrSendDisabled, "%s transfers are currently disabled", msg.Token.Denom) - } - - if k.bankKeeper.BlockedAddr(sender) { - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to send funds", sender) - } - - sequence, err := k.sendTransfer( - ctx, msg.SourcePort, msg.SourceChannel, msg.Token, sender, msg.Receiver, msg.TimeoutHeight, msg.TimeoutTimestamp, - msg.Memo) - if err != nil { - return nil, err - } - - k.Logger(ctx).Info("IBC fungible token transfer", "token", msg.Token.Denom, "amount", msg.Token.Amount.String(), "sender", msg.Sender, "receiver", msg.Receiver) - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.EventTypeTransfer, - sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender), - sdk.NewAttribute(types.AttributeKeyReceiver, msg.Receiver), - sdk.NewAttribute(types.AttributeKeyAmount, msg.Token.Amount.String()), - sdk.NewAttribute(types.AttributeKeyDenom, msg.Token.Denom), - sdk.NewAttribute(types.AttributeKeyMemo, msg.Memo), - ), - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - ), - }) - - return &types.MsgTransferResponse{Sequence: sequence}, nil -} - -func (k Keeper) sendTransfer( - ctx sdk.Context, - sourcePort, - sourceChannel string, - token sdk.Coin, - sender sdk.AccAddress, - receiver string, - timeoutHeight clienttypes.Height, - timeoutTimestamp uint64, - memo string, -) (uint64, error) { - channel, found := k.channelKeeper.GetChannel(ctx, sourcePort, sourceChannel) - if !found { - return 0, sdkerrors.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", sourcePort, sourceChannel) - } - - destinationPort := channel.GetCounterparty().GetPortID() - destinationChannel := channel.GetCounterparty().GetChannelID() - - // begin createOutgoingPacket logic - // See spec for this logic: https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay - channelCap, ok := k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(sourcePort, sourceChannel)) - if !ok { - return 0, sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") - } - - // NOTE: denomination and hex hash correctness checked during msg.ValidateBasic - fullDenomPath := token.Denom - - var err error - - // deconstruct the token denomination into the denomination trace info - // to determine if the sender is the source chain - if strings.HasPrefix(token.Denom, "ibc/") { - fullDenomPath, err = k.DenomPathFromHash(ctx, token.Denom) - if err != nil { - return 0, err - } - } - - labels := []metrics.Label{ - telemetry.NewLabel(coretypes.LabelDestinationPort, destinationPort), - telemetry.NewLabel(coretypes.LabelDestinationChannel, destinationChannel), - } - - // NOTE: SendTransfer simply sends the denomination as it exists on its own - // chain inside the packet data. The receiving chain will perform denom - // prefixing as necessary. - - if types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) { - labels = append(labels, telemetry.NewLabel(coretypes.LabelSource, "true")) - - // obtain the escrow address for the source channel end - escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel) - if err := k.escrowToken(ctx, sender, escrowAddress, token); err != nil { - return 0, err - } - - } else { - labels = append(labels, telemetry.NewLabel(coretypes.LabelSource, "false")) - - // transfer the coins to the module account and burn them - if err := k.bankKeeper.SendCoinsFromAccountToModule( - ctx, sender, types.ModuleName, sdk.NewCoins(token), - ); err != nil { - return 0, err - } - - if err := k.bankKeeper.BurnCoins( - ctx, types.ModuleName, sdk.NewCoins(token), - ); err != nil { - // NOTE: should not happen as the module account was - // retrieved on the step above and it has enough balace - // to burn. - panic(fmt.Sprintf("cannot burn coins after a successful send to a module account: %v", err)) - } - } - - packetData := types.NewFungibleTokenPacketData( - fullDenomPath, token.Amount.String(), sender.String(), receiver, memo, - ) - - sequence, err := k.ics4Wrapper.SendPacket(ctx, channelCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, packetData.GetBytes()) - if err != nil { - return 0, err - } - - defer func() { - if token.Amount.IsInt64() { - telemetry.SetGaugeWithLabels( - []string{"tx", "msg", "ibc", "transfer"}, - float32(token.Amount.Int64()), - []metrics.Label{telemetry.NewLabel(coretypes.LabelDenom, fullDenomPath)}, - ) - } - - telemetry.IncrCounterWithLabels( - []string{"ibc", types.ModuleName, "send"}, - 1, - labels, - ) - }() - - return sequence, nil -} - -func (k Keeper) escrowToken(ctx sdk.Context, sender, escrowAddress sdk.AccAddress, token sdk.Coin) error { - if err := k.bankKeeper.SendCoins(ctx, sender, escrowAddress, sdk.NewCoins(token)); err != nil { - // failure is expected for insufficient balances - return err - } - - // track the total amount in escrow keyed by denomination to allow for efficient iteration - currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) - newTotalEscrow := currentTotalEscrow.Add(token) - k.SetTotalEscrowForDenom(ctx, newTotalEscrow) - - return nil -} diff --git a/app/ibctransfer/invariants.go b/app/ibctransfer/invariants.go deleted file mode 100644 index 2ca38eeb..00000000 --- a/app/ibctransfer/invariants.go +++ /dev/null @@ -1,51 +0,0 @@ -package ibctransfer - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" -) - -// RegisterInvariants registers all transfer invariants -func RegisterInvariants(ir sdk.InvariantRegistry, k *Keeper) { - ir.RegisterRoute(types.ModuleName, "total-escrow-per-denom", - TotalEscrowPerDenomInvariants(k)) -} - -// AllInvariants runs all invariants of the transfer module. -func AllInvariants(k *Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - return TotalEscrowPerDenomInvariants(k)(ctx) - } -} - -// TotalEscrowPerDenomInvariants checks that the total amount escrowed for -// each denom is not smaller than the amount stored in the state entry. -func TotalEscrowPerDenomInvariants(k *Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - var actualTotalEscrowed sdk.Coins - - expectedTotalEscrowed := k.GetAllTotalEscrowed(ctx) - - portID := k.GetPort(ctx) - transferChannels := k.channelKeeper.GetAllChannelsWithPortPrefix(ctx, portID) - for _, channel := range transferChannels { - escrowAddress := types.GetEscrowAddress(portID, channel.ChannelId) - escrowBalances := k.bankKeeper.GetAllBalances(ctx, escrowAddress) - - actualTotalEscrowed = actualTotalEscrowed.Add(escrowBalances...) - } - - // the actual escrowed amount must be greater than or equal to the expected amount for all denominations - if !actualTotalEscrowed.IsAllGTE(expectedTotalEscrowed) { - return sdk.FormatInvariant( - types.ModuleName, - "total escrow per denom invariance", - fmt.Sprintf("found denom(s) with total escrow amount lower than expected:\nactual total escrowed: %s\nexpected total escrowed: %s", actualTotalEscrowed, expectedTotalEscrowed)), true - } - - return "", false - } -} diff --git a/app/ibctransfer/migrations.go b/app/ibctransfer/migrations.go deleted file mode 100644 index 0ac82652..00000000 --- a/app/ibctransfer/migrations.go +++ /dev/null @@ -1,80 +0,0 @@ -package ibctransfer - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" -) - -// Migrator is a struct for handling in-place store migrations. -type Migrator struct { - keeper Keeper -} - -// NewMigrator returns a new Migrator. -func NewMigrator(keeper Keeper) Migrator { - return Migrator{keeper: keeper} -} - -// MigrateTraces migrates the DenomTraces to the correct format, accounting for slashes in the BaseDenom. -func (m Migrator) MigrateTraces(ctx sdk.Context) error { - // list of traces that must replace the old traces in store - var newTraces []types.DenomTrace - m.keeper.IterateDenomTraces(ctx, - func(dt types.DenomTrace) (stop bool) { - // check if the new way of splitting FullDenom - // is the same as the current DenomTrace. - // If it isn't then store the new DenomTrace in the list of new traces. - newTrace := types.ParseDenomTrace(dt.GetFullDenomPath()) - err := newTrace.Validate() - if err != nil { - panic(err) - } - - if dt.IBCDenom() != newTrace.IBCDenom() { - // The new form of parsing will result in a token denomination change. - // A bank migration is required. A panic should occur to prevent the - // chain from using corrupted state. - panic(fmt.Sprintf("migration will result in corrupted state. Previous IBC token (%s) requires a bank migration. Expected denom trace (%s)", dt, newTrace)) - } - - if !equalTraces(newTrace, dt) { - newTraces = append(newTraces, newTrace) - } - return false - }) - - // replace the outdated traces with the new trace information - for _, nt := range newTraces { - m.keeper.SetDenomTrace(ctx, nt) - } - return nil -} - -// MigrateTotalEscrowForDenom migrates the total amount of source chain tokens in escrow. -func (m Migrator) MigrateTotalEscrowForDenom(ctx sdk.Context) error { - var totalEscrowed sdk.Coins - portID := m.keeper.GetPort(ctx) - - transferChannels := m.keeper.channelKeeper.GetAllChannelsWithPortPrefix(ctx, portID) - for _, channel := range transferChannels { - escrowAddress := types.GetEscrowAddress(portID, channel.ChannelId) - escrowBalances := m.keeper.bankKeeper.GetAllBalances(ctx, escrowAddress) - - totalEscrowed = totalEscrowed.Add(escrowBalances...) - } - - for _, totalEscrow := range totalEscrowed { - m.keeper.SetTotalEscrowForDenom(ctx, totalEscrow) - } - - logger := m.keeper.Logger(ctx) - logger.Info("successfully set total escrow for %d denominations", totalEscrowed.Len()) - return nil -} - -func equalTraces(dtA, dtB types.DenomTrace) bool { - return dtA.BaseDenom == dtB.BaseDenom && dtA.Path == dtB.Path -} diff --git a/app/ibctransfer/module.go b/app/ibctransfer/module.go index 5502cbfd..4c0aea40 100644 --- a/app/ibctransfer/module.go +++ b/app/ibctransfer/module.go @@ -1,24 +1,11 @@ package ibctransfer import ( - "context" - "encoding/json" - "fmt" - - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/grpc-ecosystem/grpc-gateway/runtime" - "github.com/spf13/cobra" - "github.com/cosmos/ibc-go/v7/modules/apps/transfer/client/cli" - "github.com/cosmos/ibc-go/v7/modules/apps/transfer/simulation" - "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" + + ibctransfer "github.com/cosmos/ibc-go/v7/modules/apps/transfer" ) var ( @@ -28,131 +15,20 @@ var ( ) // AppModuleBasic is the IBC Transfer AppModuleBasic -type AppModuleBasic struct{} - -// Name implements AppModuleBasic interface -func (AppModuleBasic) Name() string { - return types.ModuleName -} - -// RegisterLegacyAminoCodec implements AppModuleBasic interface -func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { - types.RegisterLegacyAminoCodec(cdc) -} - -// RegisterInterfaces registers module concrete types into protobuf Any. -func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { - types.RegisterInterfaces(registry) -} - -// DefaultGenesis returns default genesis state as raw bytes for the ibc -// transfer module. -func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { - return cdc.MustMarshalJSON(types.DefaultGenesisState()) -} - -// ValidateGenesis performs genesis state validation for the ibc transfer module. -func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { - var gs types.GenesisState - if err := cdc.UnmarshalJSON(bz, &gs); err != nil { - return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) - } - - return gs.Validate() -} - -// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the ibc-transfer module. -func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { - err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) - if err != nil { - panic(err) - } -} - -// GetTxCmd implements AppModuleBasic interface -func (AppModuleBasic) GetTxCmd() *cobra.Command { - return cli.NewTxCmd() -} - -// GetQueryCmd implements AppModuleBasic interface -func (AppModuleBasic) GetQueryCmd() *cobra.Command { - return cli.GetQueryCmd() +type AppModuleBasic struct { + ibctransfer.AppModuleBasic } // AppModule represents the AppModule for this module type AppModule struct { - AppModuleBasic + ibctransfer.AppModule keeper Keeper } // NewAppModule creates a new 20-transfer module func NewAppModule(k Keeper) AppModule { return AppModule{ - keeper: k, - } -} - -// RegisterInvariants implements the AppModule interface -func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { - RegisterInvariants(ir, &am.keeper) -} - -// RegisterServices registers module services. -func (am AppModule) RegisterServices(cfg module.Configurator) { - types.RegisterMsgServer(cfg.MsgServer(), am.keeper) - types.RegisterQueryServer(cfg.QueryServer(), am.keeper) - - m := NewMigrator(am.keeper) - if err := cfg.RegisterMigration(types.ModuleName, 1, m.MigrateTraces); err != nil { - panic(fmt.Sprintf("failed to migrate transfer app from version 1 to 2: %v", err)) - } - - if err := cfg.RegisterMigration(types.ModuleName, 2, m.MigrateTotalEscrowForDenom); err != nil { - panic(fmt.Sprintf("failed to migrate transfer app from version 2 to 3: %v", err)) + keeper: k, + AppModule: ibctransfer.NewAppModule(k.Keeper), } } - -// InitGenesis performs genesis initialization for the ibc-transfer module. It returns -// no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { - var genesisState types.GenesisState - cdc.MustUnmarshalJSON(data, &genesisState) - am.keeper.InitGenesis(ctx, genesisState) - return []abci.ValidatorUpdate{} -} - -// ExportGenesis returns the exported genesis state as raw bytes for the ibc-transfer -// module. -func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { - gs := am.keeper.ExportGenesis(ctx) - return cdc.MustMarshalJSON(gs) -} - -// ConsensusVersion implements AppModule/ConsensusVersion. -func (AppModule) ConsensusVersion() uint64 { return 3 } - -// BeginBlock implements the AppModule interface -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { -} - -// EndBlock implements the AppModule interface -func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} -} - -// AppModuleSimulation functions - -// GenerateGenesisState creates a randomized GenState of the transfer module. -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - simulation.RandomizedGenState(simState) -} - -// RegisterStoreDecoder registers a decoder for transfer module's types -func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { - sdr[types.StoreKey] = simulation.NewDecodeStore(am.keeper) -} - -// WeightedOperations returns the all the transfer module operations with their respective weights. -func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { - return nil -} From 1156739be53d32323b3269b172226a80661b69e1 Mon Sep 17 00:00:00 2001 From: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> Date: Wed, 15 Nov 2023 09:31:20 -0800 Subject: [PATCH 5/5] small cleanup --- app/app.go | 4 +--- x/gmp/keeper/msg_server_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/app.go b/app/app.go index 1fdf9485..a8b5d79a 100644 --- a/app/app.go +++ b/app/app.go @@ -114,8 +114,6 @@ import ( airdroptypes "github.com/ojo-network/ojo/x/airdrop/types" customante "github.com/ojo-network/ojo/ante" - - ibctransfermod "github.com/cosmos/ibc-go/v7/modules/apps/transfer" ) const ( @@ -162,7 +160,7 @@ var ( ibc.AppModuleBasic{}, upgrade.AppModuleBasic{}, evidence.AppModuleBasic{}, - ibctransfermod.AppModuleBasic{}, + ibctransfer.AppModuleBasic{}, vesting.AppModuleBasic{}, oracle.AppModuleBasic{}, gmp.AppModuleBasic{}, diff --git a/x/gmp/keeper/msg_server_test.go b/x/gmp/keeper/msg_server_test.go index 16e49d11..87b0a856 100644 --- a/x/gmp/keeper/msg_server_test.go +++ b/x/gmp/keeper/msg_server_test.go @@ -70,18 +70,18 @@ func (s *IntegrationTestSuite) TestMsgServer_RelayPrices() { []byte{1, 2, 3, 4}, 1000, ) + app.GmpKeeper.RelayPrice(ctx, msg) + // Attempt a normal IBC transfer transferMsg := ibctransfertypes.NewMsgTransfer( ibctransfertypes.PortID, "channel-1", msg.Token, - msg.Relayer, - "addy", + addr.String(), + addr.String(), clienttypes.ZeroHeight(), uint64(1000), "memo", ) - app.OracleKeeper.SetExchangeRate(ctx, "ATOM", sdk.NewDecWithPrec(1, 1)) app.TransferKeeper.Transfer(ctx, transferMsg) - app.GmpKeeper.RelayPrice(ctx, msg) }