diff --git a/app/app.go b/app/app.go index 4c33358f..b4055d24 100644 --- a/app/app.go +++ b/app/app.go @@ -112,6 +112,7 @@ import ( ics20wrapper "github.com/Lorenzo-Protocol/lorenzo/x/ibctransfer" ics20wrapperkeeper "github.com/Lorenzo-Protocol/lorenzo/x/ibctransfer/keeper" + "github.com/Lorenzo-Protocol/lorenzo/x/token" tokenkeeper "github.com/Lorenzo-Protocol/lorenzo/x/token/keeper" tokentypes "github.com/Lorenzo-Protocol/lorenzo/x/token/types" ) @@ -314,7 +315,6 @@ func NewLorenzoApp( // grant capabilities for the ibc and ibc-transfer modules scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) - // this line is used by starport scaffolding # stargate/app/scopedKeeper app.CapabilityKeeper.Seal() @@ -516,7 +516,8 @@ func NewLorenzoApp( app.TokenKeeper, ) - transferStack := ics20wrapper.NewIBCModule(app.ICS20WrapperKeeper) + ics20wrapperModule := ics20wrapper.NewIBCModule(app.ICS20WrapperKeeper) + transferStack := token.NewIBCMiddleware(ics20wrapperModule, app.TokenKeeper) // Create static IBC router, add transfer route, then set and seal it ibcRouter := ibcporttypes.NewRouter() diff --git a/app/helpers/test_helper.go b/app/test_helper.go similarity index 88% rename from app/helpers/test_helper.go rename to app/test_helper.go index c6dc4fec..5f9fb468 100644 --- a/app/helpers/test_helper.go +++ b/app/test_helper.go @@ -1,15 +1,13 @@ -package helpers +package app import ( "bytes" "encoding/json" + "math/rand" "strconv" "testing" "time" - appparams "github.com/Lorenzo-Protocol/lorenzo/app/params" - - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/spf13/cast" "github.com/stretchr/testify/require" @@ -19,6 +17,8 @@ import ( tmproto "github.com/cometbft/cometbft/proto/tendermint/types" tmtypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" @@ -31,7 +31,7 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/Lorenzo-Protocol/lorenzo/app" + appparams "github.com/Lorenzo-Protocol/lorenzo/app/params" ) // SimAppChainID hardcoded chainID for simulation @@ -39,6 +39,8 @@ const ( SimAppChainID = "lorenzo_8329-1" ) +var DefaultAppVersion uint64 = 1 + // DefaultConsensusParams defines the default Tendermint consensus params used // in LorenzoApp testing. var DefaultConsensusParams = &tmproto.ConsensusParams{ @@ -56,6 +58,7 @@ var DefaultConsensusParams = &tmproto.ConsensusParams{ tmtypes.ABCIPubKeyTypeEd25519, }, }, + Version: &tmproto.VersionParams{App: DefaultAppVersion}, } // PV implements PrivValidator interface @@ -75,7 +78,7 @@ func (EmptyAppOptions) Get(_ string) interface{} { return nil } // - t: A testing.T instance for testing purposes. // Returns: // - *app.LorenzoApp: The initialized instance of app.LorenzoApp. -func Setup(t *testing.T) *app.LorenzoApp { +func Setup(t *testing.T) *LorenzoApp { t.Helper() privVal := mock.NewPV() @@ -105,7 +108,7 @@ func Setup(t *testing.T) *app.LorenzoApp { // - merge: A function to merge override module genesis data with the default genesis data. // Returns: // - *app.LorenzoApp: The initialized instance of app.LorenzoApp. -func SetupWithGenesisMergeFn(t *testing.T, merge func(cdc codec.Codec, state map[string]json.RawMessage)) *app.LorenzoApp { +func SetupWithGenesisMergeFn(t *testing.T, merge func(cdc codec.Codec, state map[string]json.RawMessage)) *LorenzoApp { t.Helper() privVal := mock.NewPV() @@ -162,7 +165,7 @@ func SetupWithGenesisMergeFn(t *testing.T, merge func(cdc codec.Codec, state map // that also act as delegators. For simplicity, each validator is bonded with a delegation // of one consensus engine unit in the default token of the LorenzoApp from first genesis // account. A Nop logger is set in LorenzoApp. -func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *app.LorenzoApp { +func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *LorenzoApp { t.Helper() lorenzoApp, genesisState := setup() @@ -213,7 +216,7 @@ func CreateTestAddrs(numAddrs int) []sdk.AccAddress { return addresses } -func setup() (*app.LorenzoApp, app.GenesisState) { +func setup() (*LorenzoApp, GenesisState) { db := dbm.NewMemDB() appOptions := make(simtestutil.AppOptionsMap) appOptions[server.FlagInvCheckPeriod] = 5 @@ -221,27 +224,27 @@ func setup() (*app.LorenzoApp, app.GenesisState) { appOptions["btc-config.network"] = "simnet" invCheckPeriod := cast.ToUint(appOptions.Get(server.FlagInvCheckPeriod)) - encConfig := app.MakeEncodingConfig() - LorenzoApp := app.NewLorenzoApp( + encConfig := MakeEncodingConfig() + LorenzoApp := NewLorenzoApp( log.NewNopLogger(), db, nil, true, map[int64]bool{}, - app.DefaultNodeHome, + DefaultNodeHome, invCheckPeriod, encConfig, appOptions, baseapp.SetChainID(SimAppChainID), ) - return LorenzoApp, app.NewDefaultGenesisState(encConfig.Codec) + return LorenzoApp, NewDefaultGenesisState(encConfig.Codec) } func genesisStateWithValSet(t *testing.T, - app *app.LorenzoApp, genesisState app.GenesisState, + app *LorenzoApp, genesisState GenesisState, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance, -) app.GenesisState { +) GenesisState { t.Helper() // set genesis accounts authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) @@ -321,3 +324,34 @@ func testAddr(addr string, bech string) sdk.AccAddress { return res } + +func SignAndDeliver( + t *testing.T, txCfg client.TxConfig, app *baseapp.BaseApp, header tmproto.Header, msgs []sdk.Msg, + chainID string, accNums, accSeqs []uint64, expSimPass, expPass bool, priv ...cryptotypes.PrivKey, +) (sdk.GasInfo, *sdk.Result, error) { + tx, err := simtestutil.GenSignedMockTx( + rand.New(rand.NewSource(time.Now().UnixNano())), //nolint: gosec + txCfg, + msgs, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, + simtestutil.DefaultGenTxGas, + chainID, + accNums, + accSeqs, + priv..., + ) + require.NoError(t, err) + + // Simulate a sending a transaction + gInfo, res, err := app.SimDeliver(txCfg.TxEncoder(), tx) + + if expPass { + require.NoError(t, err) + require.NotNil(t, res) + } else { + require.Error(t, err) + require.Nil(t, res) + } + + return gInfo, res, err +} diff --git a/proto/lorenzo/token/v1/genesis.proto b/proto/lorenzo/token/v1/genesis.proto index 148eb797..d94303d3 100644 --- a/proto/lorenzo/token/v1/genesis.proto +++ b/proto/lorenzo/token/v1/genesis.proto @@ -14,6 +14,6 @@ message GenesisState { // Params defines the token module parameters. message Params { - bool enable_convert = 1; + bool enable_conversion = 1; bool enable_evm_hook = 2 [(gogoproto.customname) = "EnableEVMHook"]; } \ No newline at end of file diff --git a/proto/lorenzo/token/v1/token.proto b/proto/lorenzo/token/v1/token.proto index fdd29250..42965296 100644 --- a/proto/lorenzo/token/v1/token.proto +++ b/proto/lorenzo/token/v1/token.proto @@ -6,17 +6,17 @@ import "gogoproto/gogo.proto"; option go_package = "github.com/Lorenzo-Protocol/lorenzo/x/token/types"; -// Ownership defines the ownership of an erc20 contract, if ownership belongs to: -// - token module: the token origin is sdk coin. -// - external account: the token origin is erc20 contract. -enum Ownership { +// Source defines the source type of token asset, if source is: +// - module: token origin is sdk module; +// - contract: token origin is erc20 contract; +enum Source { option (gogoproto.goproto_enum_prefix) = false; - // undefined owner + // undefined source OWNER_UNDEFINED = 0; - // contract owned by convert module + // token source is module OWNER_MODULE = 1; - // contract owned by external account - OWNER_EXTERNAL = 2; + // token source is erc20 contract + OWNER_CONTRACT = 2; } // TokenPair defines a pairing of a cosmos coin and an erc20 token @@ -28,6 +28,6 @@ message TokenPair { string denom = 2; // allows for token conversion bool enabled = 3; - // ownership of the contract - Ownership ownership = 4; + // source of token asset + Source source = 4; } diff --git a/simulation/simapp.go b/simulation/simapp.go deleted file mode 100644 index 02eddda2..00000000 --- a/simulation/simapp.go +++ /dev/null @@ -1 +0,0 @@ -package simulation diff --git a/testutil/ibc/LICENSE b/testutil/ibc/LICENSE new file mode 100644 index 00000000..fde8c684 --- /dev/null +++ b/testutil/ibc/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2022 COSMOS + +Copyright (c) 2024 Lorenzo Protocol + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/testutil/ibc/README.md b/testutil/ibc/README.md new file mode 100644 index 00000000..1931b202 --- /dev/null +++ b/testutil/ibc/README.md @@ -0,0 +1,7 @@ +# IBC Testing + +This package contains the necessary code to test the IBC modules. It is a forked version of IBC Testing, customized for Lorenzo. + +The customization aims to maximize compatibility with the original source code while ensuring it is suitably adapted for Lorenzo’s specific requirements. + +The maintainer must keep it updated with the currently used version of the IBC code repository to ensure ongoing compatibility and functionality. \ No newline at end of file diff --git a/testutil/ibc/app.go b/testutil/ibc/app.go new file mode 100644 index 00000000..c26f6bd8 --- /dev/null +++ b/testutil/ibc/app.go @@ -0,0 +1,195 @@ +package ibc + +import ( + "encoding/json" + "testing" + "time" + + feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" + "github.com/spf13/cast" + "github.com/stretchr/testify/require" + + dbm "github.com/cometbft/cometbft-db" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmprotoversion "github.com/cometbft/cometbft/proto/tendermint/version" + tmtypes "github.com/cometbft/cometbft/types" + + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/server" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/cosmos/ibc-go/v7/modules/core/keeper" + ibctestingtypes "github.com/cosmos/ibc-go/v7/testing/types" + + "github.com/Lorenzo-Protocol/lorenzo/app" + appparams "github.com/Lorenzo-Protocol/lorenzo/app/params" + evmtypes "github.com/evmos/ethermint/x/evm/types" +) + +var DefaultTestingAppInit = SetupTestingApp + +type TestingApp interface { + abci.Application + + // ibc-go additions + GetBaseApp() *baseapp.BaseApp + GetStakingKeeper() ibctestingtypes.StakingKeeper + GetIBCKeeper() *keeper.Keeper + GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper + GetTxConfig() client.TxConfig + + // Implemented by SimApp + AppCodec() codec.Codec + + // Implemented by BaseApp + LastCommitID() storetypes.CommitID + LastBlockHeight() int64 +} + +// SetupTestingApp is a test helper function to initialize a new Lorenzo app. +func SetupTestingApp() (TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + appOptions := make(simtestutil.AppOptionsMap) + appOptions[server.FlagInvCheckPeriod] = 5 + appOptions[server.FlagMinGasPrices] = "10alrz" + appOptions["btc-config.network"] = "simnet" + + invCheckPeriod := cast.ToUint(appOptions.Get(server.FlagInvCheckPeriod)) + encodingConfig := app.MakeEncodingConfig() + lorenzoApp := app.NewLorenzoApp( + log.NewNopLogger(), + db, + nil, + true, + map[int64]bool{}, + app.DefaultNodeHome, + invCheckPeriod, + encodingConfig, + appOptions, + ) + + return lorenzoApp, app.NewDefaultGenesisState(encodingConfig.Codec) +} + +// SetupWithGenesisValSet initializes a new LorenzoApp with a validator set and genesis accounts +// that also act as delegators. +func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, powerReduction math.Int, balances ...banktypes.Balance) TestingApp { + return SetupWithGenesisValSetAndConsensusParams(t, app.DefaultConsensusParams, valSet, genAccs, chainID, powerReduction, balances...) +} + +func SetupWithGenesisValSetAndConsensusParams(t *testing.T, consensusParams *tmproto.ConsensusParams, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, powerReduction math.Int, balances ...banktypes.Balance) TestingApp { + app, genesisState := DefaultTestingAppInit() + + // ensure baseapp has a chain-id set before running InitChain + baseapp.SetChainID(chainID)(app.GetBaseApp()) + + // set genesis accounts + authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) + genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis) + + validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) + delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) + + bondAmt := sdk.TokensFromConsensusPower(1, powerReduction) + + for _, val := range valSet.Validators { + pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) + require.NoError(t, err) + pkAny, err := codectypes.NewAnyWithValue(pk) + require.NoError(t, err) + validator := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: bondAmt, + DelegatorShares: sdk.OneDec(), + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + MinSelfDelegation: sdk.ZeroInt(), + } + + validators = append(validators, validator) + delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec())) + } + + // set validators and delegations + var stakingGenesis stakingtypes.GenesisState + app.AppCodec().MustUnmarshalJSON(genesisState[stakingtypes.ModuleName], &stakingGenesis) + + bondDenom := stakingGenesis.Params.BondDenom + + // add bonded amount to bonded pool module account + balances = append(balances, banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), + Coins: sdk.Coins{sdk.NewCoin(bondDenom, bondAmt.Mul(sdk.NewInt(int64(len(valSet.Validators)))))}, + }) + + // set validators and delegations + stakingGenesis = *stakingtypes.NewGenesisState(stakingGenesis.Params, validators, delegations) + genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(&stakingGenesis) + + // update total supply + bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, sdk.NewCoins(), []banktypes.Metadata{}, []banktypes.SendEnabled{}) + genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis) + + // NOTE: add lorenzo custom modules genesis here. + + // set evm genesis: use lrz as evm denom. + evmParams := evmtypes.DefaultParams() + evmParams.EvmDenom = appparams.BaseDenom + evmGenesis := &evmtypes.GenesisState{Params: evmParams} + genesisState[evmtypes.ModuleName] = app.AppCodec().MustMarshalJSON(evmGenesis) + + // set feemarket: disable fee market + feemarketGenesis := feemarkettypes.NewGenesisState(feemarkettypes.DefaultParams(), 0) + feemarketGenesis.Params.NoBaseFee = true + genesisState[feemarkettypes.ModuleName] = app.AppCodec().MustMarshalJSON(feemarketGenesis) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + require.NoError(t, err) + + // init chain will set the validator set and initialize the genesis accounts + app.InitChain( + abci.RequestInitChain{ + ChainId: chainID, + Validators: []abci.ValidatorUpdate{}, + AppStateBytes: stateBytes, + }, + ) + + // commit genesis changes + app.Commit() + app.BeginBlock( + abci.RequestBeginBlock{ + Header: tmproto.Header{ + Version: tmprotoversion.Consensus{ + App: consensusParams.Version.App, + }, + ChainID: chainID, + Height: app.LastBlockHeight() + 1, + AppHash: app.LastCommitID().Hash, + ValidatorsHash: valSet.Hash(), + NextValidatorsHash: valSet.Hash(), + ProposerAddress: valSet.Proposer.Address.Bytes(), + }, + }, + ) + + return app +} diff --git a/testutil/ibc/chain.go b/testutil/ibc/chain.go new file mode 100644 index 00000000..2c628a31 --- /dev/null +++ b/testutil/ibc/chain.go @@ -0,0 +1,613 @@ +package ibc + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/crypto/tmhash" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmprotoversion "github.com/cometbft/cometbft/proto/tendermint/version" + tmtypes "github.com/cometbft/cometbft/types" + tmversion "github.com/cometbft/cometbft/version" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + "github.com/cosmos/cosmos-sdk/x/staking/testutil" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v7/modules/core/24-host" + "github.com/cosmos/ibc-go/v7/modules/core/exported" + "github.com/cosmos/ibc-go/v7/modules/core/types" + ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + + lorenzoapp "github.com/Lorenzo-Protocol/lorenzo/app" + "github.com/Lorenzo-Protocol/lorenzo/testutil/ibc/mock" +) + +var MaxAccounts = 10 + +type SenderAccount struct { + SenderPrivKey cryptotypes.PrivKey + SenderAccount authtypes.AccountI +} + +// TestChain is a testing struct that wraps a simapp with the last TM Header, the current ABCI +// header and the validators of the TestChain. It also contains a field called ChainID. This +// is the clientID that *other* chains use to refer to this TestChain. The SenderAccount +// is used for delivering transactions through the application state. +// NOTE: the actual application uses an empty chain-id for ease of testing. +type TestChain struct { + *testing.T + + Coordinator *Coordinator + App TestingApp + ChainID string + LastHeader *ibctm.Header // header for last block height committed + CurrentHeader tmproto.Header // header for current block height + QueryServer types.QueryServer + TxConfig client.TxConfig + Codec codec.BinaryCodec + + Vals *tmtypes.ValidatorSet + NextVals *tmtypes.ValidatorSet + + // Signers is a map from validator address to the PrivValidator + // The map is converted into an array that is the same order as the validators right before signing commit + // This ensures that signers will always be in correct order even as validator powers change. + // If a test adds a new validator after chain creation, then the signer map must be updated to include + // the new PrivValidator entry. + Signers map[string]tmtypes.PrivValidator + + // autogenerated sender private key + SenderPrivKey cryptotypes.PrivKey + SenderAccount authtypes.AccountI + + SenderAccounts []SenderAccount + + // Short-term solution to override the logic of the standard SendMsgs function. + // See issue https://github.com/cosmos/ibc-go/issues/3123 for more information. + SendMsgsOverride func(msgs ...sdk.Msg) (*sdk.Result, error) +} + +// NewTestChainWithValSet initializes a new TestChain instance with the given validator set +// and signer array. It also initializes 10 Sender accounts with a balance of 10000000000000000000 coins of +// bond denom to use for tests. +// +// The first block height is committed to state in order to allow for client creations on +// counterparty chains. The TestChain will return with a block height starting at 2. +// +// Time management is handled by the Coordinator in order to ensure synchrony between chains. +// Each update of any chain increments the block header time for all chains by 5 seconds. +// +// NOTE: to use a custom sender privkey and account for testing purposes, replace and modify this +// constructor function. +// +// CONTRACT: Validator array must be provided in the order expected by Tendermint. +// i.e. sorted first by power and then lexicographically by address. +func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, valSet *tmtypes.ValidatorSet, signers map[string]tmtypes.PrivValidator) *TestChain { + genAccs := []authtypes.GenesisAccount{} + genBals := []banktypes.Balance{} + senderAccs := []SenderAccount{} + + // generate genesis accounts + for i := 0; i < MaxAccounts; i++ { + senderPrivKey := secp256k1.GenPrivKey() + acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), uint64(i), 0) + amount, ok := sdk.NewIntFromString("10000000000000000000") + require.True(t, ok) + + // add sender account + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)), + } + + genAccs = append(genAccs, acc) + genBals = append(genBals, balance) + + senderAcc := SenderAccount{ + SenderAccount: acc, + SenderPrivKey: senderPrivKey, + } + + senderAccs = append(senderAccs, senderAcc) + } + + app := SetupWithGenesisValSet(t, valSet, genAccs, chainID, sdk.DefaultPowerReduction, genBals...) + + // create current header and call begin block + header := tmproto.Header{ + Version: tmprotoversion.Consensus{ + App: lorenzoapp.DefaultAppVersion, + }, + ChainID: chainID, + Height: 1, + Time: coord.CurrentTime.UTC(), + ProposerAddress: valSet.Proposer.Address.Bytes(), + } + + txConfig := app.GetTxConfig() + + // create an account to send transactions from + chain := &TestChain{ + T: t, + Coordinator: coord, + ChainID: chainID, + App: app, + CurrentHeader: header, + QueryServer: app.GetIBCKeeper(), + TxConfig: txConfig, + Codec: app.AppCodec(), + Vals: valSet, + NextVals: valSet, + Signers: signers, + SenderPrivKey: senderAccs[0].SenderPrivKey, + SenderAccount: senderAccs[0].SenderAccount, + SenderAccounts: senderAccs, + } + + coord.CommitBlock(chain) + + return chain +} + +// NewTestChain initializes a new test chain with a default of 4 validators +// Use this function if the tests do not need custom control over the validator set +func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { + // generate validators private/public key + var ( + validatorsPerChain = 4 + validators []*tmtypes.Validator + signersByAddress = make(map[string]tmtypes.PrivValidator, validatorsPerChain) + ) + + for i := 0; i < validatorsPerChain; i++ { + privVal := mock.NewPV() + pubKey, err := privVal.GetPubKey() + require.NoError(t, err) + validators = append(validators, tmtypes.NewValidator(pubKey, 1)) + signersByAddress[pubKey.Address().String()] = privVal + } + + // construct validator set; + // Note that the validators are sorted by voting power + // or, if equal, by address lexical order + valSet := tmtypes.NewValidatorSet(validators) + + return NewTestChainWithValSet(t, coord, chainID, valSet, signersByAddress) +} + +// GetContext returns the current context for the application. +func (chain *TestChain) GetContext() sdk.Context { + return chain.App.GetBaseApp().NewContext(false, chain.CurrentHeader) +} + +// GetSimApp returns the SimApp to allow usage ofnon-interface fields. +// CONTRACT: This function should not be called by third parties implementing +// their own SimApp. +func (chain *TestChain) GetSimApp() *lorenzoapp.LorenzoApp { + app, ok := chain.App.(*lorenzoapp.LorenzoApp) + require.True(chain.T, ok) + + return app +} + +// QueryProof performs an abci query with the given key and returns the proto encoded merkle proof +// for the query and the height at which the proof will succeed on a tendermint verifier. +func (chain *TestChain) QueryProof(key []byte) ([]byte, clienttypes.Height) { + return chain.QueryProofAtHeight(key, chain.App.LastBlockHeight()) +} + +// QueryProofAtHeight performs an abci query with the given key and returns the proto encoded merkle proof +// for the query and the height at which the proof will succeed on a tendermint verifier. Only the IBC +// store is supported +func (chain *TestChain) QueryProofAtHeight(key []byte, height int64) ([]byte, clienttypes.Height) { + return chain.QueryProofForStore(exported.StoreKey, key, height) +} + +// QueryProofForStore performs an abci query with the given key and returns the proto encoded merkle proof +// for the query and the height at which the proof will succeed on a tendermint verifier. +func (chain *TestChain) QueryProofForStore(storeKey string, key []byte, height int64) ([]byte, clienttypes.Height) { + res := chain.App.Query(abci.RequestQuery{ + Path: fmt.Sprintf("store/%s/key", storeKey), + Height: height - 1, + Data: key, + Prove: true, + }) + + merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) + require.NoError(chain.T, err) + + proof, err := chain.App.AppCodec().Marshal(&merkleProof) + require.NoError(chain.T, err) + + revision := clienttypes.ParseChainID(chain.ChainID) + + // proof height + 1 is returned as the proof created corresponds to the height the proof + // was created in the IAVL tree. Tendermint and subsequently the clients that rely on it + // have heights 1 above the IAVL tree. Thus we return proof height + 1 + return proof, clienttypes.NewHeight(revision, uint64(res.Height)+1) +} + +// QueryUpgradeProof performs an abci query with the given key and returns the proto encoded merkle proof +// for the query and the height at which the proof will succeed on a tendermint verifier. +func (chain *TestChain) QueryUpgradeProof(key []byte, height uint64) ([]byte, clienttypes.Height) { + res := chain.App.Query(abci.RequestQuery{ + Path: "store/upgrade/key", + Height: int64(height - 1), + Data: key, + Prove: true, + }) + + merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) + require.NoError(chain.T, err) + + proof, err := chain.App.AppCodec().Marshal(&merkleProof) + require.NoError(chain.T, err) + + revision := clienttypes.ParseChainID(chain.ChainID) + + // proof height + 1 is returned as the proof created corresponds to the height the proof + // was created in the IAVL tree. Tendermint and subsequently the clients that rely on it + // have heights 1 above the IAVL tree. Thus we return proof height + 1 + return proof, clienttypes.NewHeight(revision, uint64(res.Height+1)) +} + +// QueryConsensusStateProof performs an abci query for a consensus state +// stored on the given clientID. The proof and consensusHeight are returned. +func (chain *TestChain) QueryConsensusStateProof(clientID string) ([]byte, clienttypes.Height) { + clientState := chain.GetClientState(clientID) + + consensusHeight := clientState.GetLatestHeight().(clienttypes.Height) //nolint:errcheck + consensusKey := host.FullConsensusStateKey(clientID, consensusHeight) + proofConsensus, _ := chain.QueryProof(consensusKey) + + return proofConsensus, consensusHeight +} + +// NextBlock sets the last header to the current header and increments the current header to be +// at the next block height. It does not update the time as that is handled by the Coordinator. +// It will call Endblock and Commit and apply the validator set changes to the next validators +// of the next block being created. This follows the Tendermint protocol of applying valset changes +// returned on block `n` to the validators of block `n+2`. +// It calls BeginBlock with the new block created before returning. +func (chain *TestChain) NextBlock() { + res := chain.App.EndBlock(abci.RequestEndBlock{Height: chain.CurrentHeader.Height}) + + chain.App.Commit() + + // set the last header to the current header + // use nil trusted fields + chain.LastHeader = chain.CurrentTMClientHeader() + + // val set changes returned from previous block get applied to the next validators + // of this block. See tendermint spec for details. + chain.Vals = chain.NextVals + chain.NextVals = ApplyValSetChanges(chain.T, chain.Vals, res.ValidatorUpdates) + + // increment the current header + chain.CurrentHeader = tmproto.Header{ + ChainID: chain.ChainID, + Height: chain.App.LastBlockHeight() + 1, + Version: chain.CurrentHeader.Version, + AppHash: chain.App.LastCommitID().Hash, + // NOTE: the time is increased by the coordinator to maintain time synchrony amongst + // chains. + Time: chain.CurrentHeader.Time, + ValidatorsHash: chain.Vals.Hash(), + NextValidatorsHash: chain.NextVals.Hash(), + ProposerAddress: chain.CurrentHeader.ProposerAddress, + } + + chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) +} + +// sendMsgs delivers a transaction through the application without returning the result. +func (chain *TestChain) sendMsgs(msgs ...sdk.Msg) error { + _, err := chain.SendMsgs(msgs...) + return err +} + +// SendMsgs delivers a transaction through the application. It updates the senders sequence +// number and updates the TestChain's headers. It returns the result and error if one +// occurred. +func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*sdk.Result, error) { + if chain.SendMsgsOverride != nil { + return chain.SendMsgsOverride(msgs...) + } + + // ensure the chain has the latest time + chain.Coordinator.UpdateTimeForChain(chain) + + _, r, err := lorenzoapp.SignAndDeliver( + chain.T, + chain.TxConfig, + chain.App.GetBaseApp(), + chain.GetContext().BlockHeader(), + msgs, + chain.ChainID, + []uint64{chain.SenderAccount.GetAccountNumber()}, + []uint64{chain.SenderAccount.GetSequence()}, + true, true, chain.SenderPrivKey, + ) + if err != nil { + return nil, err + } + + // NextBlock calls app.Commit() + chain.NextBlock() + + // increment sequence for successful transaction execution + err = chain.SenderAccount.SetSequence(chain.SenderAccount.GetSequence() + 1) + if err != nil { + return nil, err + } + + chain.Coordinator.IncrementTime() + + return r, nil +} + +// GetClientState retrieves the client state for the provided clientID. The client is +// expected to exist otherwise testing will fail. +func (chain *TestChain) GetClientState(clientID string) exported.ClientState { + clientState, found := chain.App.GetIBCKeeper().ClientKeeper.GetClientState(chain.GetContext(), clientID) + require.True(chain.T, found) + + return clientState +} + +// GetConsensusState retrieves the consensus state for the provided clientID and height. +// It will return a success boolean depending on if consensus state exists or not. +func (chain *TestChain) GetConsensusState(clientID string, height exported.Height) (exported.ConsensusState, bool) { + return chain.App.GetIBCKeeper().ClientKeeper.GetClientConsensusState(chain.GetContext(), clientID, height) +} + +// GetValsAtHeight will return the trusted validator set of the chain for the given trusted height. It will return +// a success boolean depending on if the validator set exists or not at that height. +func (chain *TestChain) GetValsAtHeight(trustedHeight int64) (*tmtypes.ValidatorSet, bool) { + // historical information does not store the validator set which committed the header at + // height h. During BeginBlock, it stores the last updated validator set. This is equivalent to + // the next validator set at height h. This is because cometbft processes the validator set + // as follows: + // + // valSetChanges := endBlock() + // chain.Vals = chain.NextVals + // chain.NextVals = applyValSetChanges(chain.NextVals, valSetChanges) + // + // At height h, the validators in the historical information are the: + // validators used to sign height h + 1 (next validator set) + // + // Since we want to return the trusted validator set, which is the next validator set + // for height h, we can simply query using the trusted height. + histInfo, ok := chain.App.GetStakingKeeper().GetHistoricalInfo(chain.GetContext(), trustedHeight) + if !ok { + return nil, false + } + + valSet := stakingtypes.Validators(histInfo.Valset) + + tmValidators, err := testutil.ToTmValidators(valSet, sdk.DefaultPowerReduction) + if err != nil { + panic(err) + } + return tmtypes.NewValidatorSet(tmValidators), true +} + +// GetAcknowledgement retrieves an acknowledgement for the provided packet. If the +// acknowledgement does not exist then testing will fail. +func (chain *TestChain) GetAcknowledgement(packet exported.PacketI) []byte { + ack, found := chain.App.GetIBCKeeper().ChannelKeeper.GetPacketAcknowledgement(chain.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + require.True(chain.T, found) + + return ack +} + +// GetPrefix returns the prefix for used by a chain in connection creation +func (chain *TestChain) GetPrefix() commitmenttypes.MerklePrefix { + return commitmenttypes.NewMerklePrefix(chain.App.GetIBCKeeper().ConnectionKeeper.GetCommitmentPrefix().Bytes()) +} + +// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the +// light client on the source chain. +func (chain *TestChain) ConstructUpdateTMClientHeader(counterparty *TestChain, clientID string) (*ibctm.Header, error) { + return chain.ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty, clientID, clienttypes.ZeroHeight()) +} + +// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the +// light client on the source chain. +func (chain *TestChain) ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibctm.Header, error) { + header := counterparty.LastHeader + // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set + if trustedHeight.IsZero() { + trustedHeight = chain.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) //nolint:errcheck + } + var ( + tmTrustedVals *tmtypes.ValidatorSet + ok bool + ) + + tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.RevisionHeight)) + if !ok { + return nil, sdkerrors.Wrapf(ibctm.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) + } + + // inject trusted fields into last header + // for now assume revision number is 0 + header.TrustedHeight = trustedHeight + + trustedVals, err := tmTrustedVals.ToProto() + if err != nil { + return nil, err + } + header.TrustedValidators = trustedVals + + return header, nil +} + +// ExpireClient fast forwards the chain's block time by the provided amount of time which will +// expire any clients with a trusting period less than or equal to this amount of time. +func (chain *TestChain) ExpireClient(amount time.Duration) { + chain.Coordinator.IncrementTimeBy(amount) +} + +// CurrentTMClientHeader creates a TM header using the current header parameters +// on the chain. The trusted fields in the header are set to nil. +func (chain *TestChain) CurrentTMClientHeader() *ibctm.Header { + return chain.CreateTMClientHeader(chain.ChainID, chain.CurrentHeader.Height, clienttypes.Height{}, chain.CurrentHeader.Time, chain.Vals, chain.NextVals, nil, chain.Signers) +} + +// CreateTMClientHeader creates a TM header to update the TM client. Args are passed in to allow +// caller flexibility to use params that differ from the chain. +func (chain *TestChain) CreateTMClientHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, nextVals, tmTrustedVals *tmtypes.ValidatorSet, signers map[string]tmtypes.PrivValidator) *ibctm.Header { + var ( + valSet *tmproto.ValidatorSet + trustedVals *tmproto.ValidatorSet + ) + require.NotNil(chain.T, tmValSet) + + vsetHash := tmValSet.Hash() + nextValHash := nextVals.Hash() + + tmHeader := tmtypes.Header{ + Version: tmprotoversion.Consensus{Block: tmversion.BlockProtocol, App: lorenzoapp.DefaultAppVersion}, + ChainID: chainID, + Height: blockHeight, + Time: timestamp, + LastBlockID: MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), + LastCommitHash: chain.App.LastCommitID().Hash, + DataHash: tmhash.Sum([]byte("data_hash")), + ValidatorsHash: vsetHash, + NextValidatorsHash: nextValHash, + ConsensusHash: tmhash.Sum([]byte("consensus_hash")), + AppHash: chain.CurrentHeader.AppHash, + LastResultsHash: tmhash.Sum([]byte("last_results_hash")), + EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck + } + + hhash := tmHeader.Hash() + blockID := MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) + voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) + + // MakeCommit expects a signer array in the same order as the validator array. + // Thus we iterate over the ordered validator set and construct a signer array + // from the signer map in the same order. + var signerArr []tmtypes.PrivValidator //nolint:prealloc // using prealloc here would be needlessly complex + for _, v := range tmValSet.Validators { //nolint:staticcheck // need to check for nil validator set + signerArr = append(signerArr, signers[v.Address.String()]) + } + + commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signerArr, timestamp) + require.NoError(chain.T, err) + + signedHeader := &tmproto.SignedHeader{ + Header: tmHeader.ToProto(), + Commit: commit.ToProto(), + } + + if tmValSet != nil { //nolint:staticcheck + valSet, err = tmValSet.ToProto() + require.NoError(chain.T, err) + } + + if tmTrustedVals != nil { + trustedVals, err = tmTrustedVals.ToProto() + require.NoError(chain.T, err) + } + + // The trusted fields may be nil. They may be filled before relaying messages to a client. + // The relayer is responsible for querying client and injecting appropriate trusted fields. + return &ibctm.Header{ + SignedHeader: signedHeader, + ValidatorSet: valSet, + TrustedHeight: trustedHeight, + TrustedValidators: trustedVals, + } +} + +// MakeBlockID copied unimported test functions from tmtypes to use them here +func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.BlockID { + return tmtypes.BlockID{ + Hash: hash, + PartSetHeader: tmtypes.PartSetHeader{ + Total: partSetSize, + Hash: partSetHash, + }, + } +} + +// CreatePortCapability binds and claims a capability for the given portID if it does not +// already exist. This function will fail testing on any resulting error. +// NOTE: only creation of a capability for a transfer or mock port is supported +// Other applications must bind to the port in InitGenesis or modify this code. +func (chain *TestChain) CreatePortCapability(scopedKeeper capabilitykeeper.ScopedKeeper, portID string) { + // check if the portId is already binded, if not bind it + _, ok := chain.App.GetScopedIBCKeeper().GetCapability(chain.GetContext(), host.PortPath(portID)) + if !ok { + // create capability using the IBC capability keeper + cap, err := chain.App.GetScopedIBCKeeper().NewCapability(chain.GetContext(), host.PortPath(portID)) + require.NoError(chain.T, err) + + // claim capability using the scopedKeeper + err = scopedKeeper.ClaimCapability(chain.GetContext(), cap, host.PortPath(portID)) + require.NoError(chain.T, err) + } + + chain.NextBlock() +} + +// GetPortCapability returns the port capability for the given portID. The capability must +// exist, otherwise testing will fail. +func (chain *TestChain) GetPortCapability(portID string) *capabilitytypes.Capability { + cap, ok := chain.App.GetScopedIBCKeeper().GetCapability(chain.GetContext(), host.PortPath(portID)) + require.True(chain.T, ok) + + return cap +} + +// CreateChannelCapability binds and claims a capability for the given portID and channelID +// if it does not already exist. This function will fail testing on any resulting error. The +// scoped keeper passed in will claim the new capability. +func (chain *TestChain) CreateChannelCapability(scopedKeeper capabilitykeeper.ScopedKeeper, portID, channelID string) { + capName := host.ChannelCapabilityPath(portID, channelID) + // check if the portId is already binded, if not bind it + _, ok := chain.App.GetScopedIBCKeeper().GetCapability(chain.GetContext(), capName) + if !ok { + cap, err := chain.App.GetScopedIBCKeeper().NewCapability(chain.GetContext(), capName) + require.NoError(chain.T, err) + err = scopedKeeper.ClaimCapability(chain.GetContext(), cap, capName) + require.NoError(chain.T, err) + } + + chain.NextBlock() +} + +// GetChannelCapability returns the channel capability for the given portID and channelID. +// The capability must exist, otherwise testing will fail. +func (chain *TestChain) GetChannelCapability(portID, channelID string) *capabilitytypes.Capability { + cap, ok := chain.App.GetScopedIBCKeeper().GetCapability(chain.GetContext(), host.ChannelCapabilityPath(portID, channelID)) + require.True(chain.T, ok) + + return cap +} + +// GetTimeoutHeight is a convenience function which returns a IBC packet timeout height +// to be used for testing. It returns the current IBC height + 100 blocks +func (chain *TestChain) GetTimeoutHeight() clienttypes.Height { + return clienttypes.NewHeight(clienttypes.ParseChainID(chain.ChainID), uint64(chain.GetContext().BlockHeight())+100) +} diff --git a/testutil/ibc/config.go b/testutil/ibc/config.go new file mode 100644 index 00000000..69375785 --- /dev/null +++ b/testutil/ibc/config.go @@ -0,0 +1,61 @@ +package ibc + +import ( + "time" + + connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v7/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + "github.com/cosmos/ibc-go/v7/testing/mock" +) + +type ClientConfig interface { + GetClientType() string +} + +type TendermintConfig struct { + TrustLevel ibctm.Fraction + TrustingPeriod time.Duration + UnbondingPeriod time.Duration + MaxClockDrift time.Duration +} + +func NewTendermintConfig() *TendermintConfig { + return &TendermintConfig{ + TrustLevel: DefaultTrustLevel, + TrustingPeriod: TrustingPeriod, + UnbondingPeriod: UnbondingPeriod, + MaxClockDrift: MaxClockDrift, + } +} + +func (tmcfg *TendermintConfig) GetClientType() string { + return exported.Tendermint +} + +type ConnectionConfig struct { + DelayPeriod uint64 + Version *connectiontypes.Version +} + +func NewConnectionConfig() *ConnectionConfig { + return &ConnectionConfig{ + DelayPeriod: DefaultDelayPeriod, + Version: ConnectionVersion, + } +} + +type ChannelConfig struct { + PortID string + Version string + Order channeltypes.Order +} + +func NewChannelConfig() *ChannelConfig { + return &ChannelConfig{ + PortID: mock.PortID, + Version: DefaultChannelVersion, + Order: channeltypes.UNORDERED, + } +} diff --git a/testutil/ibc/coordinator.go b/testutil/ibc/coordinator.go new file mode 100644 index 00000000..b9fd984b --- /dev/null +++ b/testutil/ibc/coordinator.go @@ -0,0 +1,198 @@ +package ibc + +import ( + "fmt" + "strconv" + "testing" + "time" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/stretchr/testify/require" +) + +var ( + ChainIDPrefix = "lorenzo_8329" + + ChainIDSuffix = "-1" + globalStartTime = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) + TimeIncrement = time.Second * 5 +) + +// Coordinator is a testing struct which contains N TestChain's. It handles keeping all chains +// in sync with regards to time. +type Coordinator struct { + *testing.T + + CurrentTime time.Time + Chains map[string]*TestChain +} + +// NewCoordinator initializes Coordinator with N TestChain's +func NewCoordinator(t *testing.T, n int) *Coordinator { + chains := make(map[string]*TestChain) + coord := &Coordinator{ + T: t, + CurrentTime: globalStartTime, + } + + for i := 1; i <= n; i++ { + chainID := GetChainID(i) + chains[chainID] = NewTestChain(t, coord, chainID) + } + coord.Chains = chains + + return coord +} + +// IncrementTime iterates through all the TestChain's and increments their current header time +// by 5 seconds. +// +// CONTRACT: this function must be called after every Commit on any TestChain. +func (coord *Coordinator) IncrementTime() { + coord.IncrementTimeBy(TimeIncrement) +} + +// IncrementTimeBy iterates through all the TestChain's and increments their current header time +// by specified time. +func (coord *Coordinator) IncrementTimeBy(increment time.Duration) { + coord.CurrentTime = coord.CurrentTime.Add(increment).UTC() + coord.UpdateTime() +} + +// UpdateTime updates all clocks for the TestChains to the current global time. +func (coord *Coordinator) UpdateTime() { + for _, chain := range coord.Chains { + coord.UpdateTimeForChain(chain) + } +} + +// UpdateTimeForChain updates the clock for a specific chain. +func (coord *Coordinator) UpdateTimeForChain(chain *TestChain) { + chain.CurrentHeader.Time = coord.CurrentTime.UTC() + chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) +} + +// Setup constructs a TM client, connection, and channel on both chains provided. It will +// fail if any error occurs. The clientID's, TestConnections, and TestChannels are returned +// for both chains. The channels created are connected to the ibc-transfer application. +func (coord *Coordinator) Setup(path *Path) { + coord.SetupConnections(path) + + // channels can also be referenced through the returned connections + coord.CreateChannels(path) +} + +// SetupClients is a helper function to create clients on both chains. It assumes the +// caller does not anticipate any errors. +func (coord *Coordinator) SetupClients(path *Path) { + err := path.EndpointA.CreateClient() + require.NoError(coord.T, err) + + err = path.EndpointB.CreateClient() + require.NoError(coord.T, err) +} + +// SetupClientConnections is a helper function to create clients and the appropriate +// connections on both the source and counterparty chain. It assumes the caller does not +// anticipate any errors. +func (coord *Coordinator) SetupConnections(path *Path) { + coord.SetupClients(path) + + coord.CreateConnections(path) +} + +// CreateConnection constructs and executes connection handshake messages in order to create +// OPEN channels on chainA and chainB. The connection information of for chainA and chainB +// are returned within a TestConnection struct. The function expects the connections to be +// successfully opened otherwise testing will fail. +func (coord *Coordinator) CreateConnections(path *Path) { + err := path.EndpointA.ConnOpenInit() + require.NoError(coord.T, err) + + err = path.EndpointB.ConnOpenTry() + require.NoError(coord.T, err) + + err = path.EndpointA.ConnOpenAck() + require.NoError(coord.T, err) + + err = path.EndpointB.ConnOpenConfirm() + require.NoError(coord.T, err) + + // ensure counterparty is up to date + err = path.EndpointA.UpdateClient() + require.NoError(coord.T, err) +} + +// CreateMockChannels constructs and executes channel handshake messages to create OPEN +// channels that use a mock application module that returns nil on all callbacks. This +// function is expects the channels to be successfully opened otherwise testing will +// fail. +func (coord *Coordinator) CreateMockChannels(path *Path) { + path.EndpointA.ChannelConfig.PortID = MockPort + path.EndpointB.ChannelConfig.PortID = MockPort + + coord.CreateChannels(path) +} + +// CreateTransferChannels constructs and executes channel handshake messages to create OPEN +// ibc-transfer channels on chainA and chainB. The function expects the channels to be +// successfully opened otherwise testing will fail. +func (coord *Coordinator) CreateTransferChannels(path *Path) { + path.EndpointA.ChannelConfig.PortID = TransferPort + path.EndpointB.ChannelConfig.PortID = TransferPort + + coord.CreateChannels(path) +} + +// CreateChannel constructs and executes channel handshake messages in order to create +// OPEN channels on chainA and chainB. The function expects the channels to be successfully +// opened otherwise testing will fail. +func (coord *Coordinator) CreateChannels(path *Path) { + err := path.EndpointA.ChanOpenInit() + require.NoError(coord.T, err) + + err = path.EndpointB.ChanOpenTry() + require.NoError(coord.T, err) + + err = path.EndpointA.ChanOpenAck() + require.NoError(coord.T, err) + + err = path.EndpointB.ChanOpenConfirm() + require.NoError(coord.T, err) + + // ensure counterparty is up to date + err = path.EndpointA.UpdateClient() + require.NoError(coord.T, err) +} + +// GetChain returns the TestChain using the given chainID and returns an error if it does +// not exist. +func (coord *Coordinator) GetChain(chainID string) *TestChain { + chain, found := coord.Chains[chainID] + require.True(coord.T, found, fmt.Sprintf("%s chain does not exist", chainID)) + return chain +} + +// GetChainID returns the chainID used for the provided index. +func GetChainID(index int) string { + return ChainIDPrefix + strconv.Itoa(index) + ChainIDSuffix +} + +// CommitBlock commits a block on the provided indexes and then increments the global time. +// +// CONTRACT: the passed in list of indexes must not contain duplicates +func (coord *Coordinator) CommitBlock(chains ...*TestChain) { + for _, chain := range chains { + chain.NextBlock() + } + coord.IncrementTime() +} + +// CommitNBlocks commits n blocks to state and updates the block height by 1 for each commit. +func (coord *Coordinator) CommitNBlocks(chain *TestChain, n uint64) { + for i := uint64(0); i < n; i++ { + chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) + chain.NextBlock() + coord.IncrementTime() + } +} diff --git a/testutil/ibc/endpoint.go b/testutil/ibc/endpoint.go new file mode 100644 index 00000000..d66fbf0c --- /dev/null +++ b/testutil/ibc/endpoint.go @@ -0,0 +1,649 @@ +package ibc + +import ( + "fmt" + "strings" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v7/modules/core/24-host" + "github.com/cosmos/ibc-go/v7/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" +) + +// Endpoint is a which represents a channel endpoint and its associated +// client and connections. It contains client, connection, and channel +// configuration parameters. Endpoint functions will utilize the parameters +// set in the configuration structs when executing IBC messages. +type Endpoint struct { + Chain *TestChain + Counterparty *Endpoint + ClientID string + ConnectionID string + ChannelID string + + ClientConfig ClientConfig + ConnectionConfig *ConnectionConfig + ChannelConfig *ChannelConfig +} + +// NewEndpoint constructs a new endpoint without the counterparty. +// CONTRACT: the counterparty endpoint must be set by the caller. +func NewEndpoint( + chain *TestChain, clientConfig ClientConfig, + connectionConfig *ConnectionConfig, channelConfig *ChannelConfig, +) *Endpoint { + return &Endpoint{ + Chain: chain, + ClientConfig: clientConfig, + ConnectionConfig: connectionConfig, + ChannelConfig: channelConfig, + } +} + +// NewDefaultEndpoint constructs a new endpoint using default values. +// CONTRACT: the counterparty endpoitn must be set by the caller. +func NewDefaultEndpoint(chain *TestChain) *Endpoint { + return &Endpoint{ + Chain: chain, + ClientConfig: NewTendermintConfig(), + ConnectionConfig: NewConnectionConfig(), + ChannelConfig: NewChannelConfig(), + } +} + +// QueryProof queries proof associated with this endpoint using the lastest client state +// height on the counterparty chain. +func (endpoint *Endpoint) QueryProof(key []byte) ([]byte, clienttypes.Height) { + // obtain the counterparty client representing the chain associated with the endpoint + clientState := endpoint.Counterparty.Chain.GetClientState(endpoint.Counterparty.ClientID) + + // query proof on the counterparty using the latest height of the IBC client + return endpoint.QueryProofAtHeight(key, clientState.GetLatestHeight().GetRevisionHeight()) +} + +// QueryProofAtHeight queries proof associated with this endpoint using the proof height +// provided +func (endpoint *Endpoint) QueryProofAtHeight(key []byte, height uint64) ([]byte, clienttypes.Height) { + // query proof on the counterparty using the latest height of the IBC client + return endpoint.Chain.QueryProofAtHeight(key, int64(height)) +} + +// CreateClient creates an IBC client on the endpoint. It will update the +// clientID for the endpoint if the message is successfully executed. +// NOTE: a solo machine client will be created with an empty diversifier. +func (endpoint *Endpoint) CreateClient() (err error) { + // ensure counterparty has committed state + endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) + + var ( + clientState exported.ClientState + consensusState exported.ConsensusState + ) + + switch endpoint.ClientConfig.GetClientType() { + case exported.Tendermint: + tmConfig, ok := endpoint.ClientConfig.(*TendermintConfig) + require.True(endpoint.Chain.T, ok) + + height := endpoint.Counterparty.Chain.LastHeader.GetHeight().(clienttypes.Height) //nolint:errcheck + clientState = ibctm.NewClientState( + endpoint.Counterparty.Chain.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + height, commitmenttypes.GetSDKSpecs(), UpgradePath) + consensusState = endpoint.Counterparty.Chain.LastHeader.ConsensusState() + case exported.Solomachine: + // TODO + // solo := NewSolomachine(endpoint.Chain.T, endpoint.Chain.Codec, clientID, "", 1) + // clientState = solo.ClientState() + // consensusState = solo.ConsensusState() + + default: + err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) + } + + if err != nil { + return err + } + + msg, err := clienttypes.NewMsgCreateClient( + clientState, consensusState, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + require.NoError(endpoint.Chain.T, err) + + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + endpoint.ClientID, err = ParseClientIDFromEvents(res.GetEvents()) + require.NoError(endpoint.Chain.T, err) + + return nil +} + +// UpdateClient updates the IBC client associated with the endpoint. +func (endpoint *Endpoint) UpdateClient() (err error) { + // ensure counterparty has committed state + endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) + + var header exported.ClientMessage + + switch endpoint.ClientConfig.GetClientType() { + case exported.Tendermint: + header, err = endpoint.Chain.ConstructUpdateTMClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) + + default: + err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) + } + + if err != nil { + return err + } + + msg, err := clienttypes.NewMsgUpdateClient( + endpoint.ClientID, header, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + require.NoError(endpoint.Chain.T, err) + + return endpoint.Chain.sendMsgs(msg) +} + +// UpgradeChain will upgrade a chain's chainID to the next revision number. +// It will also update the counterparty client. +// TODO: implement actual upgrade chain functionality via scheduling an upgrade +// and upgrading the client via MsgUpgradeClient +// see reference https://github.com/cosmos/ibc-go/pull/1169 +func (endpoint *Endpoint) UpgradeChain() error { + if strings.TrimSpace(endpoint.Counterparty.ClientID) == "" { + return fmt.Errorf("cannot upgrade chain if there is no counterparty client") + } + + clientState := endpoint.Counterparty.GetClientState().(*ibctm.ClientState) //nolint:errcheck + + // increment revision number in chainID + + oldChainID := clientState.ChainId + if !clienttypes.IsRevisionFormat(oldChainID) { + return fmt.Errorf("cannot upgrade chain which is not of revision format: %s", oldChainID) + } + + revisionNumber := clienttypes.ParseChainID(oldChainID) + newChainID, err := clienttypes.SetRevisionNumber(oldChainID, revisionNumber+1) + if err != nil { + return err + } + + // update chain + baseapp.SetChainID(newChainID)(endpoint.Chain.GetSimApp().GetBaseApp()) + endpoint.Chain.ChainID = newChainID + endpoint.Chain.CurrentHeader.ChainID = newChainID + endpoint.Chain.NextBlock() // commit changes + + // update counterparty client manually + clientState.ChainId = newChainID + clientState.LatestHeight = clienttypes.NewHeight(revisionNumber+1, clientState.LatestHeight.GetRevisionHeight()+1) + endpoint.Counterparty.SetClientState(clientState) + + consensusState := &ibctm.ConsensusState{ + Timestamp: endpoint.Chain.LastHeader.GetTime(), + Root: commitmenttypes.NewMerkleRoot(endpoint.Chain.LastHeader.Header.GetAppHash()), + NextValidatorsHash: endpoint.Chain.LastHeader.Header.NextValidatorsHash, + } + endpoint.Counterparty.SetConsensusState(consensusState, clientState.GetLatestHeight()) + + // ensure the next update isn't identical to the one set in state + endpoint.Chain.Coordinator.IncrementTime() + endpoint.Chain.NextBlock() + + if err = endpoint.Counterparty.UpdateClient(); err != nil { + return err + } + + return nil +} + +// ConnOpenInit will construct and execute a MsgConnectionOpenInit on the associated endpoint. +func (endpoint *Endpoint) ConnOpenInit() error { + msg := connectiontypes.NewMsgConnectionOpenInit( + endpoint.ClientID, + endpoint.Counterparty.ClientID, + endpoint.Counterparty.Chain.GetPrefix(), DefaultOpenInitVersion, endpoint.ConnectionConfig.DelayPeriod, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + endpoint.ConnectionID, err = ParseConnectionIDFromEvents(res.GetEvents()) + require.NoError(endpoint.Chain.T, err) + + return nil +} + +// ConnOpenTry will construct and execute a MsgConnectionOpenTry on the associated endpoint. +func (endpoint *Endpoint) ConnOpenTry() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) + + counterpartyClient, proofClient, proofConsensus, consensusHeight, proofInit, proofHeight := endpoint.QueryConnectionHandshakeProof() + + msg := connectiontypes.NewMsgConnectionOpenTry( + endpoint.ClientID, endpoint.Counterparty.ConnectionID, endpoint.Counterparty.ClientID, + counterpartyClient, endpoint.Counterparty.Chain.GetPrefix(), []*connectiontypes.Version{ConnectionVersion}, endpoint.ConnectionConfig.DelayPeriod, + proofInit, proofClient, proofConsensus, + proofHeight, consensusHeight, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + if endpoint.ConnectionID == "" { + endpoint.ConnectionID, err = ParseConnectionIDFromEvents(res.GetEvents()) + require.NoError(endpoint.Chain.T, err) + } + + return nil +} + +// ConnOpenAck will construct and execute a MsgConnectionOpenAck on the associated endpoint. +func (endpoint *Endpoint) ConnOpenAck() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) + + counterpartyClient, proofClient, proofConsensus, consensusHeight, proofTry, proofHeight := endpoint.QueryConnectionHandshakeProof() + + msg := connectiontypes.NewMsgConnectionOpenAck( + endpoint.ConnectionID, endpoint.Counterparty.ConnectionID, counterpartyClient, // testing doesn't use flexible selection + proofTry, proofClient, proofConsensus, + proofHeight, consensusHeight, + ConnectionVersion, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + return endpoint.Chain.sendMsgs(msg) +} + +// ConnOpenConfirm will construct and execute a MsgConnectionOpenConfirm on the associated endpoint. +func (endpoint *Endpoint) ConnOpenConfirm() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) + + connectionKey := host.ConnectionKey(endpoint.Counterparty.ConnectionID) + proof, height := endpoint.Counterparty.Chain.QueryProof(connectionKey) + + msg := connectiontypes.NewMsgConnectionOpenConfirm( + endpoint.ConnectionID, + proof, height, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + return endpoint.Chain.sendMsgs(msg) +} + +// QueryConnectionHandshakeProof returns all the proofs necessary to execute OpenTry or Open Ack of +// the connection handshakes. It returns the counterparty client state, proof of the counterparty +// client state, proof of the counterparty consensus state, the consensus state height, proof of +// the counterparty connection, and the proof height for all the proofs returned. +func (endpoint *Endpoint) QueryConnectionHandshakeProof() ( + clientState exported.ClientState, proofClient, + proofConsensus []byte, consensusHeight clienttypes.Height, + proofConnection []byte, proofHeight clienttypes.Height, +) { + // obtain the client state on the counterparty chain + clientState = endpoint.Counterparty.Chain.GetClientState(endpoint.Counterparty.ClientID) + + // query proof for the client state on the counterparty + clientKey := host.FullClientStateKey(endpoint.Counterparty.ClientID) + proofClient, proofHeight = endpoint.Counterparty.QueryProof(clientKey) + + consensusHeight = clientState.GetLatestHeight().(clienttypes.Height) //nolint:errcheck + + // query proof for the consensus state on the counterparty + consensusKey := host.FullConsensusStateKey(endpoint.Counterparty.ClientID, consensusHeight) + proofConsensus, _ = endpoint.Counterparty.QueryProofAtHeight(consensusKey, proofHeight.GetRevisionHeight()) + + // query proof for the connection on the counterparty + connectionKey := host.ConnectionKey(endpoint.Counterparty.ConnectionID) + proofConnection, _ = endpoint.Counterparty.QueryProofAtHeight(connectionKey, proofHeight.GetRevisionHeight()) + + return +} + +// ChanOpenInit will construct and execute a MsgChannelOpenInit on the associated endpoint. +func (endpoint *Endpoint) ChanOpenInit() error { + msg := channeltypes.NewMsgChannelOpenInit( + endpoint.ChannelConfig.PortID, + endpoint.ChannelConfig.Version, endpoint.ChannelConfig.Order, []string{endpoint.ConnectionID}, + endpoint.Counterparty.ChannelConfig.PortID, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + endpoint.ChannelID, err = ParseChannelIDFromEvents(res.GetEvents()) + require.NoError(endpoint.Chain.T, err) + + // update version to selected app version + // NOTE: this update must be performed after SendMsgs() + endpoint.ChannelConfig.Version = endpoint.GetChannel().Version + endpoint.Counterparty.ChannelConfig.Version = endpoint.GetChannel().Version + + return nil +} + +// ChanOpenTry will construct and execute a MsgChannelOpenTry on the associated endpoint. +func (endpoint *Endpoint) ChanOpenTry() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) + + channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) + proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) + + msg := channeltypes.NewMsgChannelOpenTry( + endpoint.ChannelConfig.PortID, + endpoint.ChannelConfig.Version, endpoint.ChannelConfig.Order, []string{endpoint.ConnectionID}, + endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID, endpoint.Counterparty.ChannelConfig.Version, + proof, height, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + if endpoint.ChannelID == "" { + endpoint.ChannelID, err = ParseChannelIDFromEvents(res.GetEvents()) + require.NoError(endpoint.Chain.T, err) + } + + // update version to selected app version + // NOTE: this update must be performed after the endpoint channelID is set + endpoint.ChannelConfig.Version = endpoint.GetChannel().Version + endpoint.Counterparty.ChannelConfig.Version = endpoint.GetChannel().Version + + return nil +} + +// ChanOpenAck will construct and execute a MsgChannelOpenAck on the associated endpoint. +func (endpoint *Endpoint) ChanOpenAck() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) + + channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) + proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) + + msg := channeltypes.NewMsgChannelOpenAck( + endpoint.ChannelConfig.PortID, endpoint.ChannelID, + endpoint.Counterparty.ChannelID, endpoint.Counterparty.ChannelConfig.Version, // testing doesn't use flexible selection + proof, height, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + + if err = endpoint.Chain.sendMsgs(msg); err != nil { + return err + } + + endpoint.ChannelConfig.Version = endpoint.GetChannel().Version + + return nil +} + +// ChanOpenConfirm will construct and execute a MsgChannelOpenConfirm on the associated endpoint. +func (endpoint *Endpoint) ChanOpenConfirm() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) + + channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) + proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) + + msg := channeltypes.NewMsgChannelOpenConfirm( + endpoint.ChannelConfig.PortID, endpoint.ChannelID, + proof, height, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + return endpoint.Chain.sendMsgs(msg) +} + +// ChanCloseInit will construct and execute a MsgChannelCloseInit on the associated endpoint. +// +// NOTE: does not work with ibc-transfer module +func (endpoint *Endpoint) ChanCloseInit() error { + msg := channeltypes.NewMsgChannelCloseInit( + endpoint.ChannelConfig.PortID, endpoint.ChannelID, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + return endpoint.Chain.sendMsgs(msg) +} + +// SendPacket sends a packet through the channel keeper using the associated endpoint +// The counterparty client is updated so proofs can be sent to the counterparty chain. +// The packet sequence generated for the packet to be sent is returned. An error +// is returned if one occurs. +func (endpoint *Endpoint) SendPacket( + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { + channelCap := endpoint.Chain.GetChannelCapability(endpoint.ChannelConfig.PortID, endpoint.ChannelID) + + // no need to send message, acting as a module + sequence, err := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.SendPacket(endpoint.Chain.GetContext(), channelCap, endpoint.ChannelConfig.PortID, endpoint.ChannelID, timeoutHeight, timeoutTimestamp, data) + if err != nil { + return 0, err + } + + // commit changes since no message was sent + endpoint.Chain.Coordinator.CommitBlock(endpoint.Chain) + + err = endpoint.Counterparty.UpdateClient() + if err != nil { + return 0, err + } + + return sequence, nil +} + +// RecvPacket receives a packet on the associated endpoint. +// The counterparty client is updated. +func (endpoint *Endpoint) RecvPacket(packet channeltypes.Packet) error { + _, err := endpoint.RecvPacketWithResult(packet) + if err != nil { + return err + } + + return nil +} + +// RecvPacketWithResult receives a packet on the associated endpoint and the result +// of the transaction is returned. The counterparty client is updated. +func (endpoint *Endpoint) RecvPacketWithResult(packet channeltypes.Packet) (*sdk.Result, error) { + // get proof of packet commitment on source + packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := endpoint.Counterparty.Chain.QueryProof(packetKey) + + recvMsg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String()) + + // receive on counterparty and update source client + res, err := endpoint.Chain.SendMsgs(recvMsg) + if err != nil { + return nil, err + } + + if err := endpoint.Counterparty.UpdateClient(); err != nil { + return nil, err + } + + return res, nil +} + +// WriteAcknowledgement writes an acknowledgement on the channel associated with the endpoint. +// The counterparty client is updated. +func (endpoint *Endpoint) WriteAcknowledgement(ack exported.Acknowledgement, packet exported.PacketI) error { + channelCap := endpoint.Chain.GetChannelCapability(packet.GetDestPort(), packet.GetDestChannel()) + + // no need to send message, acting as a handler + err := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.WriteAcknowledgement(endpoint.Chain.GetContext(), channelCap, packet, ack) + if err != nil { + return err + } + + // commit changes since no message was sent + endpoint.Chain.Coordinator.CommitBlock(endpoint.Chain) + + return endpoint.Counterparty.UpdateClient() +} + +// AcknowledgePacket sends a MsgAcknowledgement to the channel associated with the endpoint. +func (endpoint *Endpoint) AcknowledgePacket(packet channeltypes.Packet, ack []byte) error { + // get proof of acknowledgement on counterparty + packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) + + ackMsg := channeltypes.NewMsgAcknowledgement(packet, ack, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String()) + + return endpoint.Chain.sendMsgs(ackMsg) +} + +// TimeoutPacket sends a MsgTimeout to the channel associated with the endpoint. +func (endpoint *Endpoint) TimeoutPacket(packet channeltypes.Packet) error { + // get proof for timeout based on channel order + var packetKey []byte + + switch endpoint.ChannelConfig.Order { + case channeltypes.ORDERED: + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + case channeltypes.UNORDERED: + packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + default: + return fmt.Errorf("unsupported order type %s", endpoint.ChannelConfig.Order) + } + + counterparty := endpoint.Counterparty + proof, proofHeight := counterparty.QueryProof(packetKey) + nextSeqRecv, found := counterparty.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceRecv(counterparty.Chain.GetContext(), counterparty.ChannelConfig.PortID, counterparty.ChannelID) + require.True(endpoint.Chain.T, found) + + timeoutMsg := channeltypes.NewMsgTimeout( + packet, nextSeqRecv, + proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + + return endpoint.Chain.sendMsgs(timeoutMsg) +} + +// TimeoutOnClose sends a MsgTimeoutOnClose to the channel associated with the endpoint. +func (endpoint *Endpoint) TimeoutOnClose(packet channeltypes.Packet) error { + // get proof for timeout based on channel order + var packetKey []byte + + switch endpoint.ChannelConfig.Order { + case channeltypes.ORDERED: + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + case channeltypes.UNORDERED: + packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + default: + return fmt.Errorf("unsupported order type %s", endpoint.ChannelConfig.Order) + } + + proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) + + channelKey := host.ChannelKey(packet.GetDestPort(), packet.GetDestChannel()) + proofClosed, _ := endpoint.Counterparty.QueryProof(channelKey) + + nextSeqRecv, found := endpoint.Counterparty.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceRecv(endpoint.Counterparty.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID) + require.True(endpoint.Chain.T, found) + + timeoutOnCloseMsg := channeltypes.NewMsgTimeoutOnClose( + packet, nextSeqRecv, + proof, proofClosed, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + + return endpoint.Chain.sendMsgs(timeoutOnCloseMsg) +} + +// SetChannelState sets a channel state +func (endpoint *Endpoint) SetChannelState(state channeltypes.State) error { + channel := endpoint.GetChannel() + + channel.State = state + endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.SetChannel(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID, channel) + + endpoint.Chain.Coordinator.CommitBlock(endpoint.Chain) + + return endpoint.Counterparty.UpdateClient() +} + +// GetClientState retrieves the Client State for this endpoint. The +// client state is expected to exist otherwise testing will fail. +func (endpoint *Endpoint) GetClientState() exported.ClientState { + return endpoint.Chain.GetClientState(endpoint.ClientID) +} + +// SetClientState sets the client state for this endpoint. +func (endpoint *Endpoint) SetClientState(clientState exported.ClientState) { + endpoint.Chain.App.GetIBCKeeper().ClientKeeper.SetClientState(endpoint.Chain.GetContext(), endpoint.ClientID, clientState) +} + +// GetConsensusState retrieves the Consensus State for this endpoint at the provided height. +// The consensus state is expected to exist otherwise testing will fail. +func (endpoint *Endpoint) GetConsensusState(height exported.Height) exported.ConsensusState { + consensusState, found := endpoint.Chain.GetConsensusState(endpoint.ClientID, height) + require.True(endpoint.Chain.T, found) + + return consensusState +} + +// SetConsensusState sets the consensus state for this endpoint. +func (endpoint *Endpoint) SetConsensusState(consensusState exported.ConsensusState, height exported.Height) { + endpoint.Chain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(endpoint.Chain.GetContext(), endpoint.ClientID, height, consensusState) +} + +// GetConnection retrieves an IBC Connection for the endpoint. The +// connection is expected to exist otherwise testing will fail. +func (endpoint *Endpoint) GetConnection() connectiontypes.ConnectionEnd { + connection, found := endpoint.Chain.App.GetIBCKeeper().ConnectionKeeper.GetConnection(endpoint.Chain.GetContext(), endpoint.ConnectionID) + require.True(endpoint.Chain.T, found) + + return connection +} + +// SetConnection sets the connection for this endpoint. +func (endpoint *Endpoint) SetConnection(connection connectiontypes.ConnectionEnd) { + endpoint.Chain.App.GetIBCKeeper().ConnectionKeeper.SetConnection(endpoint.Chain.GetContext(), endpoint.ConnectionID, connection) +} + +// GetChannel retrieves an IBC Channel for the endpoint. The channel +// is expected to exist otherwise testing will fail. +func (endpoint *Endpoint) GetChannel() channeltypes.Channel { + channel, found := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetChannel(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID) + require.True(endpoint.Chain.T, found) + + return channel +} + +// SetChannel sets the channel for this endpoint. +func (endpoint *Endpoint) SetChannel(channel channeltypes.Channel) { + endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.SetChannel(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID, channel) +} + +// QueryClientStateProof performs and abci query for a client stat associated +// with this endpoint and returns the ClientState along with the proof. +func (endpoint *Endpoint) QueryClientStateProof() (exported.ClientState, []byte) { + // retrieve client state to provide proof for + clientState := endpoint.GetClientState() + + clientKey := host.FullClientStateKey(endpoint.ClientID) + proofClient, _ := endpoint.QueryProof(clientKey) + + return clientState, proofClient +} diff --git a/testutil/ibc/events.go b/testutil/ibc/events.go new file mode 100644 index 00000000..f25b646d --- /dev/null +++ b/testutil/ibc/events.go @@ -0,0 +1,164 @@ +package ibc + +import ( + "fmt" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" +) + +type EventsMap map[string]map[string]string + +// ParseClientIDFromEvents parses events emitted from a MsgCreateClient and returns the +// client identifier. +func ParseClientIDFromEvents(events sdk.Events) (string, error) { + for _, ev := range events { + if ev.Type == clienttypes.EventTypeCreateClient { + for _, attr := range ev.Attributes { + if attr.Key == clienttypes.AttributeKeyClientID { + return attr.Value, nil + } + } + } + } + return "", fmt.Errorf("client identifier event attribute not found") +} + +// ParseConnectionIDFromEvents parses events emitted from a MsgConnectionOpenInit or +// MsgConnectionOpenTry and returns the connection identifier. +func ParseConnectionIDFromEvents(events sdk.Events) (string, error) { + for _, ev := range events { + if ev.Type == connectiontypes.EventTypeConnectionOpenInit || + ev.Type == connectiontypes.EventTypeConnectionOpenTry { + for _, attr := range ev.Attributes { + if attr.Key == connectiontypes.AttributeKeyConnectionID { + return attr.Value, nil + } + } + } + } + return "", fmt.Errorf("connection identifier event attribute not found") +} + +// ParseChannelIDFromEvents parses events emitted from a MsgChannelOpenInit or +// MsgChannelOpenTry and returns the channel identifier. +func ParseChannelIDFromEvents(events sdk.Events) (string, error) { + for _, ev := range events { + if ev.Type == channeltypes.EventTypeChannelOpenInit || ev.Type == channeltypes.EventTypeChannelOpenTry { + for _, attr := range ev.Attributes { + if attr.Key == channeltypes.AttributeKeyChannelID { + return attr.Value, nil + } + } + } + } + return "", fmt.Errorf("channel identifier event attribute not found") +} + +// ParsePacketFromEvents parses events emitted from a MsgRecvPacket and returns the +// acknowledgement. +func ParsePacketFromEvents(events sdk.Events) (channeltypes.Packet, error) { + for _, ev := range events { + if ev.Type == channeltypes.EventTypeSendPacket { + packet := channeltypes.Packet{} + for _, attr := range ev.Attributes { + switch attr.Key { + case channeltypes.AttributeKeyData: //nolint:staticcheck // DEPRECATED + packet.Data = []byte(attr.Value) + + case channeltypes.AttributeKeySequence: + seq, err := strconv.ParseUint(attr.Value, 10, 64) + if err != nil { + return channeltypes.Packet{}, err + } + + packet.Sequence = seq + + case channeltypes.AttributeKeySrcPort: + packet.SourcePort = attr.Value + + case channeltypes.AttributeKeySrcChannel: + packet.SourceChannel = attr.Value + + case channeltypes.AttributeKeyDstPort: + packet.DestinationPort = attr.Value + + case channeltypes.AttributeKeyDstChannel: + packet.DestinationChannel = attr.Value + + case channeltypes.AttributeKeyTimeoutHeight: + height, err := clienttypes.ParseHeight(attr.Value) + if err != nil { + return channeltypes.Packet{}, err + } + + packet.TimeoutHeight = height + + case channeltypes.AttributeKeyTimeoutTimestamp: + timestamp, err := strconv.ParseUint(attr.Value, 10, 64) + if err != nil { + return channeltypes.Packet{}, err + } + + packet.TimeoutTimestamp = timestamp + + default: + continue + } + } + + return packet, nil + } + } + return channeltypes.Packet{}, fmt.Errorf("acknowledgement event attribute not found") +} + +// ParseAckFromEvents parses events emitted from a MsgRecvPacket and returns the +// acknowledgement. +func ParseAckFromEvents(events sdk.Events) ([]byte, error) { + for _, ev := range events { + if ev.Type == channeltypes.EventTypeWriteAck { + for _, attr := range ev.Attributes { + if attr.Key == channeltypes.AttributeKeyAck { //nolint:staticcheck // DEPRECATED + return []byte(attr.Value), nil + } + } + } + } + return nil, fmt.Errorf("acknowledgement event attribute not found") +} + +// AssertEvents asserts that expected events are present in the actual events. +// Expected map needs to be a subset of actual events to pass. +func AssertEvents( + suite *suite.Suite, + expected EventsMap, + actual sdk.Events, +) { + hasEvents := make(map[string]bool) + for eventType := range expected { + hasEvents[eventType] = false + } + + for _, event := range actual { + expEvent, eventFound := expected[event.Type] + if eventFound { + hasEvents[event.Type] = true + suite.Require().Len(event.Attributes, len(expEvent)) + for _, attr := range event.Attributes { + expValue, found := expEvent[attr.Key] + suite.Require().True(found) + suite.Require().Equal(expValue, attr.Value) + } + } + } + + for eventName, hasEvent := range hasEvents { + suite.Require().True(hasEvent, "event: %s was not found in events", eventName) + } +} diff --git a/testutil/ibc/mock/ack.go b/testutil/ibc/mock/ack.go new file mode 100644 index 00000000..280e848b --- /dev/null +++ b/testutil/ibc/mock/ack.go @@ -0,0 +1,23 @@ +package mock + +// EmptyAcknowledgement implements the exported.Acknowledgement interface and always returns an empty byte string as Response +type EmptyAcknowledgement struct { + Response []byte +} + +// NewEmptyAcknowledgement returns a new instance of EmptyAcknowledgement +func NewEmptyAcknowledgement() EmptyAcknowledgement { + return EmptyAcknowledgement{ + Response: []byte{}, + } +} + +// Success implements the Acknowledgement interface +func (ack EmptyAcknowledgement) Success() bool { + return true +} + +// Acknowledgement implements the Acknowledgement interface +func (ack EmptyAcknowledgement) Acknowledgement() []byte { + return []byte{} +} diff --git a/testutil/ibc/mock/doc.go b/testutil/ibc/mock/doc.go new file mode 100644 index 00000000..eaaa42b2 --- /dev/null +++ b/testutil/ibc/mock/doc.go @@ -0,0 +1,9 @@ +/* +This package is only intended to be used for testing core IBC. In order to maintain secure +testing, we need to do message passing and execution which requires connecting an IBC application +module that fulfills all the callbacks. We cannot connect to ibc-transfer which does not support +all channel types so instead we create a mock application module which does nothing. It simply +return nil in all cases so no error ever occurs. It is intended to be as minimal and lightweight +as possible and should never import simapp. +*/ +package mock diff --git a/testutil/ibc/mock/events.go b/testutil/ibc/mock/events.go new file mode 100644 index 00000000..e92592c0 --- /dev/null +++ b/testutil/ibc/mock/events.go @@ -0,0 +1,39 @@ +package mock + +import sdk "github.com/cosmos/cosmos-sdk/types" + +const ( + MockEventTypeRecvPacket = "mock-recv-packet" + MockEventTypeAckPacket = "mock-ack-packet" + MockEventTypeTimeoutPacket = "mock-timeout" + + MockAttributeKey1 = "mock-attribute-key-1" + MockAttributeKey2 = "mock-attribute-key-2" + + MockAttributeValue1 = "mock-attribute-value-1" + MockAttributeValue2 = "mock-attribute-value-2" +) + +// NewMockRecvPacketEvent returns a mock receive packet event +func NewMockRecvPacketEvent() sdk.Event { + return newMockEvent(MockEventTypeRecvPacket) +} + +// NewMockAckPacketEvent returns a mock acknowledgement packet event +func NewMockAckPacketEvent() sdk.Event { + return newMockEvent(MockEventTypeAckPacket) +} + +// NewMockTimeoutPacketEvent emits a mock timeout packet event +func NewMockTimeoutPacketEvent() sdk.Event { + return newMockEvent(MockEventTypeTimeoutPacket) +} + +// emitMockEvent returns a mock event with the given event type +func newMockEvent(eventType string) sdk.Event { + return sdk.NewEvent( + eventType, + sdk.NewAttribute(MockAttributeKey1, MockAttributeValue1), + sdk.NewAttribute(MockAttributeKey2, MockAttributeValue2), + ) +} diff --git a/testutil/ibc/mock/ibc_app.go b/testutil/ibc/mock/ibc_app.go new file mode 100644 index 00000000..d3bac888 --- /dev/null +++ b/testutil/ibc/mock/ibc_app.go @@ -0,0 +1,96 @@ +package mock + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v7/modules/core/exported" +) + +// IBCApp contains IBC application module callbacks as defined in 05-port. +type IBCApp struct { + PortID string + ScopedKeeper capabilitykeeper.ScopedKeeper + + OnChanOpenInit func( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, + ) (string, error) + + OnChanOpenTry func( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, + ) (version string, err error) + + OnChanOpenAck func( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, + ) error + + OnChanOpenConfirm func( + ctx sdk.Context, + portID, + channelID string, + ) error + + OnChanCloseInit func( + ctx sdk.Context, + portID, + channelID string, + ) error + + OnChanCloseConfirm func( + ctx sdk.Context, + portID, + channelID string, + ) error + + // OnRecvPacket must return an acknowledgement that implements the Acknowledgement interface. + // In the case of an asynchronous acknowledgement, nil should be returned. + // If the acknowledgement returned is successful, the state changes on callback are written, + // otherwise the application state changes are discarded. In either case the packet is received + // and the acknowledgement is written (in synchronous cases). + OnRecvPacket func( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, + ) exported.Acknowledgement + + OnAcknowledgementPacket func( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + ) error + + OnTimeoutPacket func( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, + ) error +} + +// NewIBCApp returns a IBCApp. An empty PortID indicates the mock app doesn't bind/claim ports. +func NewIBCApp(portID string, scopedKeeper capabilitykeeper.ScopedKeeper) *IBCApp { + return &IBCApp{ + PortID: portID, + ScopedKeeper: scopedKeeper, + } +} diff --git a/testutil/ibc/mock/ibc_module.go b/testutil/ibc/mock/ibc_module.go new file mode 100644 index 00000000..ae136e34 --- /dev/null +++ b/testutil/ibc/mock/ibc_module.go @@ -0,0 +1,207 @@ +package mock + +import ( + "bytes" + "fmt" + "reflect" + "strconv" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/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" +) + +var ( + _ porttypes.IBCModule = (*IBCModule)(nil) + _ porttypes.PacketDataUnmarshaler = (*IBCModule)(nil) +) + +// applicationCallbackError is a custom error type that will be unique for testing purposes. +type applicationCallbackError struct{} + +func (e applicationCallbackError) Error() string { + return "mock application callback failed" +} + +// IBCModule implements the ICS26 callbacks for testing/mock. +type IBCModule struct { + appModule *AppModule + IBCApp *IBCApp // base application of an IBC middleware stack +} + +// NewIBCModule creates a new IBCModule given the underlying mock IBC application and scopedKeeper. +func NewIBCModule(appModule *AppModule, app *IBCApp) IBCModule { + appModule.ibcApps = append(appModule.ibcApps, app) + return IBCModule{ + appModule: appModule, + IBCApp: app, + } +} + +// 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 strings.TrimSpace(version) == "" { + version = Version + } + + if im.IBCApp.OnChanOpenInit != nil { + return im.IBCApp.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version) + } + + if chanCap != nil { + // Claim channel capability passed back by IBC module + if err := im.IBCApp.ScopedKeeper.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 string, + channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string, +) (version string, err error) { + if im.IBCApp.OnChanOpenTry != nil { + return im.IBCApp.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion) + } + + if chanCap != nil { + // Claim channel capability passed back by IBC module + if err := im.IBCApp.ScopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + } + + return Version, nil +} + +// OnChanOpenAck implements the IBCModule interface. +func (im IBCModule) OnChanOpenAck(ctx sdk.Context, portID string, channelID string, counterpartyChannelID string, counterpartyVersion string) error { + if im.IBCApp.OnChanOpenAck != nil { + return im.IBCApp.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + return nil +} + +// OnChanOpenConfirm implements the IBCModule interface. +func (im IBCModule) OnChanOpenConfirm(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanOpenConfirm != nil { + return im.IBCApp.OnChanOpenConfirm(ctx, portID, channelID) + } + + return nil +} + +// OnChanCloseInit implements the IBCModule interface. +func (im IBCModule) OnChanCloseInit(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanCloseInit != nil { + return im.IBCApp.OnChanCloseInit(ctx, portID, channelID) + } + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface. +func (im IBCModule) OnChanCloseConfirm(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanCloseConfirm != nil { + return im.IBCApp.OnChanCloseConfirm(ctx, portID, channelID) + } + + return nil +} + +// OnRecvPacket implements the IBCModule interface. +func (im IBCModule) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) exported.Acknowledgement { + if im.IBCApp.OnRecvPacket != nil { + return im.IBCApp.OnRecvPacket(ctx, packet, relayer) + } + + // set state by claiming capability to check if revert happens return + capName := GetMockRecvCanaryCapabilityName(packet) + if _, err := im.IBCApp.ScopedKeeper.NewCapability(ctx, capName); err != nil { + // application callback called twice on same packet sequence + // must never occur + panic(err) + } + + ctx.EventManager().EmitEvent(NewMockRecvPacketEvent()) + + if bytes.Equal(MockPacketData, packet.GetData()) { + return MockAcknowledgement + } else if bytes.Equal(MockAsyncPacketData, packet.GetData()) { + return nil + } + + return MockFailAcknowledgement +} + +// OnAcknowledgementPacket implements the IBCModule interface. +func (im IBCModule) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { + if im.IBCApp.OnAcknowledgementPacket != nil { + return im.IBCApp.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) + } + + capName := GetMockAckCanaryCapabilityName(packet) + if _, err := im.IBCApp.ScopedKeeper.NewCapability(ctx, capName); err != nil { + // application callback called twice on same packet sequence + // must never occur + panic(err) + } + + ctx.EventManager().EmitEvent(NewMockAckPacketEvent()) + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im IBCModule) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { + if im.IBCApp.OnTimeoutPacket != nil { + return im.IBCApp.OnTimeoutPacket(ctx, packet, relayer) + } + + capName := GetMockTimeoutCanaryCapabilityName(packet) + if _, err := im.IBCApp.ScopedKeeper.NewCapability(ctx, capName); err != nil { + // application callback called twice on same packet sequence + // must never occur + panic(err) + } + + ctx.EventManager().EmitEvent(NewMockTimeoutPacketEvent()) + + return nil +} + +// UnmarshalPacketData returns the MockPacketData. This function implements the optional +// PacketDataUnmarshaler interface required for ADR 008 support. +func (im IBCModule) UnmarshalPacketData(bz []byte) (interface{}, error) { + if reflect.DeepEqual(bz, MockPacketData) { + return MockPacketData, nil + } + return nil, MockApplicationCallbackError +} + +// GetMockRecvCanaryCapabilityName generates a capability name for testing OnRecvPacket functionality. +func GetMockRecvCanaryCapabilityName(packet channeltypes.Packet) string { + return fmt.Sprintf("%s%s%s%s", MockRecvCanaryCapabilityName, packet.GetDestPort(), packet.GetDestChannel(), strconv.Itoa(int(packet.GetSequence()))) +} + +// GetMockAckCanaryCapabilityName generates a capability name for OnAcknowledgementPacket functionality. +func GetMockAckCanaryCapabilityName(packet channeltypes.Packet) string { + return fmt.Sprintf("%s%s%s%s", MockAckCanaryCapabilityName, packet.GetSourcePort(), packet.GetSourceChannel(), strconv.Itoa(int(packet.GetSequence()))) +} + +// GetMockTimeoutCanaryCapabilityName generates a capability name for OnTimeoutacket functionality. +func GetMockTimeoutCanaryCapabilityName(packet channeltypes.Packet) string { + return fmt.Sprintf("%s%s%s%s", MockTimeoutCanaryCapabilityName, packet.GetSourcePort(), packet.GetSourceChannel(), strconv.Itoa(int(packet.GetSequence()))) +} diff --git a/testutil/ibc/mock/ibc_module_test.go b/testutil/ibc/mock/ibc_module_test.go new file mode 100644 index 00000000..6eec0c11 --- /dev/null +++ b/testutil/ibc/mock/ibc_module_test.go @@ -0,0 +1,33 @@ +package mock_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v7/testing/mock" +) + +func TestCreateCapabilityName(t *testing.T) { + packet := channeltypes.NewPacket( + []byte{}, + 1, + mock.PortID, + "channel-0", + mock.PortID, + "channel-0", + clienttypes.NewHeight(0, 100), + 0, + ) + + name := mock.GetMockRecvCanaryCapabilityName(packet) + require.Equal(t, "mock receive canary capability namemockchannel-01", name) + + name = mock.GetMockAckCanaryCapabilityName(packet) + require.Equal(t, "mock acknowledgement canary capability namemockchannel-01", name) + + name = mock.GetMockTimeoutCanaryCapabilityName(packet) + require.Equal(t, "mock timeout canary capability namemockchannel-01", name) +} diff --git a/testutil/ibc/mock/mock.go b/testutil/ibc/mock/mock.go new file mode 100644 index 00000000..12f1b148 --- /dev/null +++ b/testutil/ibc/mock/mock.go @@ -0,0 +1,158 @@ +package mock + +import ( + "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" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + 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" +) + +const ( + ModuleName = "mock" + + MemStoreKey = "memory:mock" + + PortID = ModuleName + Version = "mock-version" +) + +var ( + MockAcknowledgement = channeltypes.NewResultAcknowledgement([]byte("mock acknowledgement")) + MockFailAcknowledgement = channeltypes.NewErrorAcknowledgement(fmt.Errorf("mock failed acknowledgement")) + MockPacketData = []byte("mock packet data") + MockFailPacketData = []byte("mock failed packet data") + MockAsyncPacketData = []byte("mock async packet data") + MockRecvCanaryCapabilityName = "mock receive canary capability name" + MockAckCanaryCapabilityName = "mock acknowledgement canary capability name" + MockTimeoutCanaryCapabilityName = "mock timeout canary capability name" + // MockApplicationCallbackError should be returned when an application callback should fail. It is possible to + // test that this error was returned using ErrorIs. + MockApplicationCallbackError error = &applicationCallbackError{} +) + +var _ porttypes.IBCModule = (*IBCModule)(nil) + +// Expected Interface +// PortKeeper defines the expected IBC port keeper +type PortKeeper interface { + BindPort(ctx sdk.Context, portID string) *capabilitytypes.Capability + IsBound(ctx sdk.Context, portID string) bool +} + +// AppModuleBasic is the mock AppModuleBasic. +type AppModuleBasic struct{} + +// Name implements AppModuleBasic interface. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// RegisterLegacyAminoCodec implements AppModuleBasic interface. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces implements AppModuleBasic interface. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {} + +// DefaultGenesis implements AppModuleBasic interface. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// ValidateGenesis implements the AppModuleBasic interface. +func (AppModuleBasic) ValidateGenesis(codec.JSONCodec, client.TxEncodingConfig, json.RawMessage) error { + return nil +} + +// RegisterGRPCGatewayRoutes implements AppModuleBasic interface. +func (a AppModuleBasic) RegisterGRPCGatewayRoutes(_ client.Context, _ *runtime.ServeMux) {} + +// GetTxCmd implements AppModuleBasic interface. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd implements AppModuleBasic interface. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} + +// AppModule represents the AppModule for the mock module. +type AppModule struct { + AppModuleBasic + ibcApps []*IBCApp + portKeeper PortKeeper +} + +// NewAppModule returns a mock AppModule instance. +func NewAppModule(pk PortKeeper) AppModule { + return AppModule{ + portKeeper: pk, + } +} + +// RegisterInvariants implements the AppModule interface. +func (AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {} + +// RegisterServices implements the AppModule interface. +func (am AppModule) RegisterServices(module.Configurator) {} + +// InitGenesis implements the AppModule interface. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + for _, ibcApp := range am.ibcApps { + if ibcApp.PortID != "" && !am.portKeeper.IsBound(ctx, ibcApp.PortID) { + // bind mock portID + cap := am.portKeeper.BindPort(ctx, ibcApp.PortID) + err := ibcApp.ScopedKeeper.ClaimCapability(ctx, cap, host.PortPath(ibcApp.PortID)) + if err != nil { + panic(err) + } + } + } + + return []abci.ValidatorUpdate{} +} + +// ExportGenesis implements the AppModule interface. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// 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{} +} + +var _ exported.Path = KeyPath{} + +// KeyPath defines a placeholder struct which implements the exported.Path interface +type KeyPath struct{} + +// String implements the exported.Path interface +func (KeyPath) String() string { + return "" +} + +// Empty implements the exported.Path interface +func (KeyPath) Empty() bool { + return false +} diff --git a/testutil/ibc/mock/privval.go b/testutil/ibc/mock/privval.go new file mode 100644 index 00000000..a92cc1fc --- /dev/null +++ b/testutil/ibc/mock/privval.go @@ -0,0 +1,49 @@ +package mock + +import ( + "github.com/cometbft/cometbft/crypto" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmtypes "github.com/cometbft/cometbft/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +var _ tmtypes.PrivValidator = PV{} + +// MockPV implements PrivValidator without any safety or persistence. +// Only use it for testing. +type PV struct { + PrivKey cryptotypes.PrivKey +} + +func NewPV() PV { + return PV{ed25519.GenPrivKey()} +} + +// GetPubKey implements PrivValidator interface +func (pv PV) GetPubKey() (crypto.PubKey, error) { + return cryptocodec.ToTmPubKeyInterface(pv.PrivKey.PubKey()) +} + +// SignVote implements PrivValidator interface +func (pv PV) SignVote(chainID string, vote *tmproto.Vote) error { + signBytes := tmtypes.VoteSignBytes(chainID, vote) + sig, err := pv.PrivKey.Sign(signBytes) + if err != nil { + return err + } + vote.Signature = sig + return nil +} + +// SignProposal implements PrivValidator interface +func (pv PV) SignProposal(chainID string, proposal *tmproto.Proposal) error { + signBytes := tmtypes.ProposalSignBytes(chainID, proposal) + sig, err := pv.PrivKey.Sign(signBytes) + if err != nil { + return err + } + proposal.Signature = sig + return nil +} diff --git a/testutil/ibc/mock/privval_test.go b/testutil/ibc/mock/privval_test.go new file mode 100644 index 00000000..d8ef0e44 --- /dev/null +++ b/testutil/ibc/mock/privval_test.go @@ -0,0 +1,46 @@ +package mock_test + +import ( + "testing" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v7/testing/mock" +) + +const chainID = "testChain" + +func TestGetPubKey(t *testing.T) { + pv := mock.NewPV() + pk, err := pv.GetPubKey() + require.NoError(t, err) + require.Equal(t, "ed25519", pk.Type()) +} + +func TestSignVote(t *testing.T) { + pv := mock.NewPV() + pk, _ := pv.GetPubKey() + + vote := &tmproto.Vote{Height: 2} + err := pv.SignVote(chainID, vote) + require.NoError(t, err) + + msg := tmtypes.VoteSignBytes(chainID, vote) + ok := pk.VerifySignature(msg, vote.Signature) + require.True(t, ok) +} + +func TestSignProposal(t *testing.T) { + pv := mock.NewPV() + pk, _ := pv.GetPubKey() + + proposal := &tmproto.Proposal{Round: 2} + err := pv.SignProposal(chainID, proposal) + require.NoError(t, err) + + msg := tmtypes.ProposalSignBytes(chainID, proposal) + ok := pk.VerifySignature(msg, proposal.Signature) + require.True(t, ok) +} diff --git a/testutil/ibc/path.go b/testutil/ibc/path.go new file mode 100644 index 00000000..63740988 --- /dev/null +++ b/testutil/ibc/path.go @@ -0,0 +1,92 @@ +package ibc + +import ( + "bytes" + "fmt" + + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" +) + +// Path contains two endpoints representing two chains connected over IBC +type Path struct { + EndpointA *Endpoint + EndpointB *Endpoint +} + +// NewPath constructs an endpoint for each chain using the default values +// for the endpoints. Each endpoint is updated to have a pointer to the +// counterparty endpoint. +func NewPath(chainA, chainB *TestChain) *Path { + endpointA := NewDefaultEndpoint(chainA) + endpointB := NewDefaultEndpoint(chainB) + + endpointA.Counterparty = endpointB + endpointB.Counterparty = endpointA + + return &Path{ + EndpointA: endpointA, + EndpointB: endpointB, + } +} + +// SetChannelOrdered sets the channel order for both endpoints to ORDERED. +func (path *Path) SetChannelOrdered() { + path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED + path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED +} + +// RelayPacket attempts to relay the packet first on EndpointA and then on EndpointB +// if EndpointA does not contain a packet commitment for that packet. An error is returned +// if a relay step fails or the packet commitment does not exist on either endpoint. +func (path *Path) RelayPacket(packet channeltypes.Packet) error { + pc := path.EndpointA.Chain.App.GetIBCKeeper().ChannelKeeper.GetPacketCommitment(path.EndpointA.Chain.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if bytes.Equal(pc, channeltypes.CommitPacket(path.EndpointA.Chain.App.AppCodec(), packet)) { + + // packet found, relay from A to B + if err := path.EndpointB.UpdateClient(); err != nil { + return err + } + + res, err := path.EndpointB.RecvPacketWithResult(packet) + if err != nil { + return err + } + + ack, err := ParseAckFromEvents(res.GetEvents()) + if err != nil { + return err + } + + if err := path.EndpointA.AcknowledgePacket(packet, ack); err != nil { + return err + } + + return nil + } + + pc = path.EndpointB.Chain.App.GetIBCKeeper().ChannelKeeper.GetPacketCommitment(path.EndpointB.Chain.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if bytes.Equal(pc, channeltypes.CommitPacket(path.EndpointB.Chain.App.AppCodec(), packet)) { + + // packet found, relay B to A + if err := path.EndpointA.UpdateClient(); err != nil { + return err + } + + res, err := path.EndpointA.RecvPacketWithResult(packet) + if err != nil { + return err + } + + ack, err := ParseAckFromEvents(res.GetEvents()) + if err != nil { + return err + } + + if err := path.EndpointB.AcknowledgePacket(packet, ack); err != nil { + return err + } + return nil + } + + return fmt.Errorf("packet commitment does not exist on either endpoint for provided packet") +} diff --git a/testutil/ibc/solomachine.go b/testutil/ibc/solomachine.go new file mode 100644 index 00000000..d782c441 --- /dev/null +++ b/testutil/ibc/solomachine.go @@ -0,0 +1,735 @@ +package ibc + +import ( + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/stretchr/testify/require" + + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v7/modules/core/24-host" + "github.com/cosmos/ibc-go/v7/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v7/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" +) + +var ( + clientIDSolomachine = "client-on-solomachine" // clientID generated on solo machine side + connectionIDSolomachine = "connection-on-solomachine" // connectionID generated on solo machine side + channelIDSolomachine = "channel-on-solomachine" // channelID generated on solo machine side +) + +// Solomachine is a testing helper used to simulate a counterparty +// solo machine client. +type Solomachine struct { + t *testing.T + + cdc codec.BinaryCodec + ClientID string + PrivateKeys []cryptotypes.PrivKey // keys used for signing + PublicKeys []cryptotypes.PubKey // keys used for generating solo machine pub key + PublicKey cryptotypes.PubKey // key used for verification + Sequence uint64 + Time uint64 + Diversifier string +} + +// NewSolomachine returns a new solomachine instance with an `nKeys` amount of +// generated private/public key pairs and a sequence starting at 1. If nKeys +// is greater than 1 then a multisig public key is used. +func NewSolomachine(t *testing.T, cdc codec.BinaryCodec, clientID, diversifier string, nKeys uint64) *Solomachine { + privKeys, pubKeys, pk := GenerateKeys(t, nKeys) + + return &Solomachine{ + t: t, + cdc: cdc, + ClientID: clientID, + PrivateKeys: privKeys, + PublicKeys: pubKeys, + PublicKey: pk, + Sequence: 1, + Time: 10, + Diversifier: diversifier, + } +} + +// GenerateKeys generates a new set of secp256k1 private keys and public keys. +// If the number of keys is greater than one then the public key returned represents +// a multisig public key. The private keys are used for signing, the public +// keys are used for generating the public key and the public key is used for +// solo machine verification. The usage of secp256k1 is entirely arbitrary. +// The key type can be swapped for any key type supported by the PublicKey +// interface, if needed. The same is true for the amino based Multisignature +// public key. +func GenerateKeys(t *testing.T, n uint64) ([]cryptotypes.PrivKey, []cryptotypes.PubKey, cryptotypes.PubKey) { + require.NotEqual(t, uint64(0), n, "generation of zero keys is not allowed") + + privKeys := make([]cryptotypes.PrivKey, n) + pubKeys := make([]cryptotypes.PubKey, n) + for i := uint64(0); i < n; i++ { + privKeys[i] = secp256k1.GenPrivKey() + pubKeys[i] = privKeys[i].PubKey() + } + + var pk cryptotypes.PubKey + if len(privKeys) > 1 { + // generate multi sig pk + pk = kmultisig.NewLegacyAminoPubKey(int(n), pubKeys) + } else { + pk = privKeys[0].PubKey() + } + + return privKeys, pubKeys, pk +} + +// ClientState returns a new solo machine ClientState instance. +func (solo *Solomachine) ClientState() *solomachine.ClientState { + return solomachine.NewClientState(solo.Sequence, solo.ConsensusState()) +} + +// ConsensusState returns a new solo machine ConsensusState instance +func (solo *Solomachine) ConsensusState() *solomachine.ConsensusState { + publicKey, err := codectypes.NewAnyWithValue(solo.PublicKey) + require.NoError(solo.t, err) + + return &solomachine.ConsensusState{ + PublicKey: publicKey, + Diversifier: solo.Diversifier, + Timestamp: solo.Time, + } +} + +// GetHeight returns an exported.Height with Sequence as RevisionHeight +func (solo *Solomachine) GetHeight() exported.Height { + return clienttypes.NewHeight(0, solo.Sequence) +} + +// CreateClient creates an on-chain client on the provided chain. +func (solo *Solomachine) CreateClient(chain *TestChain) string { + msgCreateClient, err := clienttypes.NewMsgCreateClient(solo.ClientState(), solo.ConsensusState(), chain.SenderAccount.GetAddress().String()) + require.NoError(solo.t, err) + + res, err := chain.SendMsgs(msgCreateClient) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) + + clientID, err := ParseClientIDFromEvents(res.GetEvents()) + require.NoError(solo.t, err) + + return clientID +} + +// UpdateClient sends a MsgUpdateClient to the provided chain and updates the given clientID. +func (solo *Solomachine) UpdateClient(chain *TestChain, clientID string) { + smHeader := solo.CreateHeader(solo.Diversifier) + msgUpdateClient, err := clienttypes.NewMsgUpdateClient(clientID, smHeader, chain.SenderAccount.GetAddress().String()) + require.NoError(solo.t, err) + + res, err := chain.SendMsgs(msgUpdateClient) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// CreateHeader generates a new private/public key pair and creates the +// necessary signature to construct a valid solo machine header. +// A new diversifier will be used as well +func (solo *Solomachine) CreateHeader(newDiversifier string) *solomachine.Header { + // generate new private keys and signature for header + newPrivKeys, newPubKeys, newPubKey := GenerateKeys(solo.t, uint64(len(solo.PrivateKeys))) + + publicKey, err := codectypes.NewAnyWithValue(newPubKey) + require.NoError(solo.t, err) + + data := &solomachine.HeaderData{ + NewPubKey: publicKey, + NewDiversifier: newDiversifier, + } + + dataBz, err := solo.cdc.Marshal(data) + require.NoError(solo.t, err) + + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: []byte(solomachine.SentinelHeaderPath), + Data: dataBz, + } + + bz, err := solo.cdc.Marshal(signBytes) + require.NoError(solo.t, err) + + sig := solo.GenerateSignature(bz) + + header := &solomachine.Header{ + Timestamp: solo.Time, + Signature: sig, + NewPublicKey: publicKey, + NewDiversifier: newDiversifier, + } + + // assumes successful header update + solo.Sequence++ + solo.Time++ + solo.PrivateKeys = newPrivKeys + solo.PublicKeys = newPubKeys + solo.PublicKey = newPubKey + solo.Diversifier = newDiversifier + + return header +} + +// CreateMisbehaviour constructs testing misbehaviour for the solo machine client +// by signing over two different data bytes at the same sequence. +func (solo *Solomachine) CreateMisbehaviour() *solomachine.Misbehaviour { + merklePath := solo.GetClientStatePath("counterparty") + path, err := solo.cdc.Marshal(&merklePath) + require.NoError(solo.t, err) + + data, err := solo.cdc.Marshal(solo.ClientState()) + require.NoError(solo.t, err) + + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: path, + Data: data, + } + + bz, err := solo.cdc.Marshal(signBytes) + require.NoError(solo.t, err) + + sig := solo.GenerateSignature(bz) + signatureOne := solomachine.SignatureAndData{ + Signature: sig, + Path: path, + Data: data, + Timestamp: solo.Time, + } + + // misbehaviour signaturess can have different timestamps + solo.Time++ + + merklePath = solo.GetConsensusStatePath("counterparty", clienttypes.NewHeight(0, 1)) + path, err = solo.cdc.Marshal(&merklePath) + require.NoError(solo.t, err) + + data, err = solo.cdc.Marshal(solo.ConsensusState()) + require.NoError(solo.t, err) + + signBytes = &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: path, + Data: data, + } + + bz, err = solo.cdc.Marshal(signBytes) + require.NoError(solo.t, err) + + sig = solo.GenerateSignature(bz) + signatureTwo := solomachine.SignatureAndData{ + Signature: sig, + Path: path, + Data: data, + Timestamp: solo.Time, + } + + return &solomachine.Misbehaviour{ + Sequence: solo.Sequence, + SignatureOne: &signatureOne, + SignatureTwo: &signatureTwo, + } +} + +// ConnOpenInit initializes a connection on the provided chain given a solo machine clientID. +func (solo *Solomachine) ConnOpenInit(chain *TestChain, clientID string) string { + msgConnOpenInit := connectiontypes.NewMsgConnectionOpenInit( + clientID, + clientIDSolomachine, // clientID generated on solo machine side + chain.GetPrefix(), DefaultOpenInitVersion, DefaultDelayPeriod, + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgConnOpenInit) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) + + connectionID, err := ParseConnectionIDFromEvents(res.GetEvents()) + require.NoError(solo.t, err) + + return connectionID +} + +// ConnOpenAck performs the connection open ack handshake step on the tendermint chain for the associated +// solo machine client. +func (solo *Solomachine) ConnOpenAck(chain *TestChain, clientID, connectionID string) { + proofTry := solo.GenerateConnOpenTryProof(clientID, connectionID) + + clientState := ibctm.NewClientState(chain.ChainID, DefaultTrustLevel, TrustingPeriod, UnbondingPeriod, MaxClockDrift, chain.LastHeader.GetHeight().(clienttypes.Height), commitmenttypes.GetSDKSpecs(), UpgradePath) + proofClient := solo.GenerateClientStateProof(clientState) + + consensusState := chain.LastHeader.ConsensusState() + consensusHeight := chain.LastHeader.GetHeight() + proofConsensus := solo.GenerateConsensusStateProof(consensusState, consensusHeight) + + msgConnOpenAck := connectiontypes.NewMsgConnectionOpenAck( + connectionID, connectionIDSolomachine, clientState, + proofTry, proofClient, proofConsensus, + clienttypes.ZeroHeight(), clientState.GetLatestHeight().(clienttypes.Height), + ConnectionVersion, + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgConnOpenAck) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// ChanOpenInit initializes a channel on the provided chain given a solo machine connectionID. +func (solo *Solomachine) ChanOpenInit(chain *TestChain, connectionID string) string { + msgChanOpenInit := channeltypes.NewMsgChannelOpenInit( + transfertypes.PortID, + transfertypes.Version, + channeltypes.UNORDERED, + []string{connectionID}, + transfertypes.PortID, + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgChanOpenInit) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) + + if res, ok := res.MsgResponses[0].GetCachedValue().(*channeltypes.MsgChannelOpenInitResponse); ok { + return res.ChannelId + } + + return "" +} + +// ChanOpenAck performs the channel open ack handshake step on the tendermint chain for the associated +// solo machine client. +func (solo *Solomachine) ChanOpenAck(chain *TestChain, channelID string) { + proofTry := solo.GenerateChanOpenTryProof(transfertypes.PortID, transfertypes.Version, channelID) + msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( + transfertypes.PortID, + channelID, + channelIDSolomachine, + transfertypes.Version, + proofTry, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgChanOpenAck) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// ChanCloseConfirm performs the channel close confirm handshake step on the tendermint chain for the associated +// solo machine client. +func (solo *Solomachine) ChanCloseConfirm(chain *TestChain, portID, channelID string) { + proofInit := solo.GenerateChanClosedProof(portID, transfertypes.Version, channelID) + msgChanCloseConfirm := channeltypes.NewMsgChannelCloseConfirm( + portID, + channelID, + proofInit, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgChanCloseConfirm) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// SendTransfer constructs a MsgTransfer and sends the message to the given chain. Any number of optional +// functions can be provided which will modify the MsgTransfer before SendMsgs is called. +func (solo *Solomachine) SendTransfer(chain *TestChain, portID, channelID string, fns ...func(*transfertypes.MsgTransfer)) channeltypes.Packet { + msgTransfer := transfertypes.MsgTransfer{ + SourcePort: portID, + SourceChannel: channelID, + Token: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), + Sender: chain.SenderAccount.GetAddress().String(), + Receiver: chain.SenderAccount.GetAddress().String(), + TimeoutHeight: clienttypes.ZeroHeight(), + TimeoutTimestamp: uint64(chain.GetContext().BlockTime().Add(time.Hour).UnixNano()), + } + + for _, fn := range fns { + fn(&msgTransfer) + } + + res, err := chain.SendMsgs(&msgTransfer) + require.NoError(solo.t, err) + + packet, err := ParsePacketFromEvents(res.GetEvents()) + require.NoError(solo.t, err) + + return packet +} + +// RecvPacket creates a commitment proof and broadcasts a new MsgRecvPacket. +func (solo *Solomachine) RecvPacket(chain *TestChain, packet channeltypes.Packet) { + proofCommitment := solo.GenerateCommitmentProof(packet) + msgRecvPacket := channeltypes.NewMsgRecvPacket( + packet, + proofCommitment, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgRecvPacket) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// AcknowledgePacket creates an acknowledgement proof and broadcasts a MsgAcknowledgement. +func (solo *Solomachine) AcknowledgePacket(chain *TestChain, packet channeltypes.Packet) { + proofAck := solo.GenerateAcknowledgementProof(packet) + transferAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() + msgAcknowledgement := channeltypes.NewMsgAcknowledgement( + packet, transferAck, + proofAck, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgAcknowledgement) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// TimeoutPacket creates a unreceived packet proof and broadcasts a MsgTimeout. +func (solo *Solomachine) TimeoutPacket(chain *TestChain, packet channeltypes.Packet) { + proofUnreceived := solo.GenerateReceiptAbsenceProof(packet) + msgTimeout := channeltypes.NewMsgTimeout( + packet, + 1, // nextSequenceRecv is unused for UNORDERED channels + proofUnreceived, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgTimeout) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// TimeoutPacket creates a channel closed and unreceived packet proof and broadcasts a MsgTimeoutOnClose. +func (solo *Solomachine) TimeoutPacketOnClose(chain *TestChain, packet channeltypes.Packet, channelID string) { + proofClosed := solo.GenerateChanClosedProof(transfertypes.PortID, transfertypes.Version, channelID) + proofUnreceived := solo.GenerateReceiptAbsenceProof(packet) + msgTimeout := channeltypes.NewMsgTimeoutOnClose( + packet, + 1, // nextSequenceRecv is unused for UNORDERED channels + proofUnreceived, + proofClosed, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgTimeout) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// GenerateSignature uses the stored private keys to generate a signature +// over the sign bytes with each key. If the amount of keys is greater than +// 1 then a multisig data type is returned. +func (solo *Solomachine) GenerateSignature(signBytes []byte) []byte { + sigs := make([]signing.SignatureData, len(solo.PrivateKeys)) + for i, key := range solo.PrivateKeys { + sig, err := key.Sign(signBytes) + require.NoError(solo.t, err) + + sigs[i] = &signing.SingleSignatureData{ + Signature: sig, + } + } + + var sigData signing.SignatureData + if len(sigs) == 1 { + // single public key + sigData = sigs[0] + } else { + // generate multi signature data + multiSigData := multisig.NewMultisig(len(sigs)) + for i, sig := range sigs { + multisig.AddSignature(multiSigData, sig, i) + } + + sigData = multiSigData + } + + protoSigData := signing.SignatureDataToProto(sigData) + bz, err := solo.cdc.Marshal(protoSigData) + require.NoError(solo.t, err) + + return bz +} + +// GenerateProof takes in solo machine sign bytes, generates a signature and marshals it as a proof. +// The solo machine sequence is incremented. +func (solo *Solomachine) GenerateProof(signBytes *solomachine.SignBytes) []byte { + bz, err := solo.cdc.Marshal(signBytes) + require.NoError(solo.t, err) + + sig := solo.GenerateSignature(bz) + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: solo.Time, + } + proof, err := solo.cdc.Marshal(signatureDoc) + require.NoError(solo.t, err) + + solo.Sequence++ + + return proof +} + +// GenerateClientStateProof generates the proof of the client state required for the connection open try and ack handshake steps. +// The client state should be the self client states of the tendermint chain. +func (solo *Solomachine) GenerateClientStateProof(clientState exported.ClientState) []byte { + data, err := clienttypes.MarshalClientState(solo.cdc, clientState) + require.NoError(solo.t, err) + + merklePath := solo.GetClientStatePath(clientIDSolomachine) + key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + require.NoError(solo.t, err) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: key, + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateConsensusStateProof generates the proof of the consensus state required for the connection open try and ack handshake steps. +// The consensus state should be the self consensus states of the tendermint chain. +func (solo *Solomachine) GenerateConsensusStateProof(consensusState exported.ConsensusState, consensusHeight exported.Height) []byte { + data, err := clienttypes.MarshalConsensusState(solo.cdc, consensusState) + require.NoError(solo.t, err) + + merklePath := solo.GetConsensusStatePath(clientIDSolomachine, consensusHeight) + key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + require.NoError(solo.t, err) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: key, + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateConnOpenTryProof generates the proofTry required for the connection open ack handshake step. +// The clientID, connectionID provided represent the clientID and connectionID created on the counterparty chain, that is the tendermint chain. +func (solo *Solomachine) GenerateConnOpenTryProof(counterpartyClientID, counterpartyConnectionID string) []byte { + counterparty := connectiontypes.NewCounterparty(counterpartyClientID, counterpartyConnectionID, prefix) + connection := connectiontypes.NewConnectionEnd(connectiontypes.TRYOPEN, clientIDSolomachine, counterparty, []*connectiontypes.Version{ConnectionVersion}, DefaultDelayPeriod) + + data, err := solo.cdc.Marshal(&connection) + require.NoError(solo.t, err) + + merklePath := solo.GetConnectionStatePath(connectionIDSolomachine) + key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + require.NoError(solo.t, err) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: key, + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateChanOpenTryProof generates the proofTry required for the channel open ack handshake step. +// The channelID provided represents the channelID created on the counterparty chain, that is the tendermint chain. +func (solo *Solomachine) GenerateChanOpenTryProof(portID, version, counterpartyChannelID string) []byte { + counterparty := channeltypes.NewCounterparty(portID, counterpartyChannelID) + channel := channeltypes.NewChannel(channeltypes.TRYOPEN, channeltypes.UNORDERED, counterparty, []string{connectionIDSolomachine}, version) + + data, err := solo.cdc.Marshal(&channel) + require.NoError(solo.t, err) + + merklePath := solo.GetChannelStatePath(portID, channelIDSolomachine) + key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + require.NoError(solo.t, err) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: key, + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateChanClosedProof generates a channel closed proof. +// The channelID provided represents the channelID created on the counterparty chain, that is the tendermint chain. +func (solo *Solomachine) GenerateChanClosedProof(portID, version, counterpartyChannelID string) []byte { + counterparty := channeltypes.NewCounterparty(portID, counterpartyChannelID) + channel := channeltypes.NewChannel(channeltypes.CLOSED, channeltypes.UNORDERED, counterparty, []string{connectionIDSolomachine}, version) + + data, err := solo.cdc.Marshal(&channel) + require.NoError(solo.t, err) + + merklePath := solo.GetChannelStatePath(portID, channelIDSolomachine) + key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + require.NoError(solo.t, err) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: key, + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateCommitmentProof generates a commitment proof for the provided packet. +func (solo *Solomachine) GenerateCommitmentProof(packet channeltypes.Packet) []byte { + commitment := channeltypes.CommitPacket(solo.cdc, packet) + + merklePath := solo.GetPacketCommitmentPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + require.NoError(solo.t, err) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: key, + Data: commitment, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateAcknowledgementProof generates an acknowledgement proof. +func (solo *Solomachine) GenerateAcknowledgementProof(packet channeltypes.Packet) []byte { + transferAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() + + merklePath := solo.GetPacketAcknowledgementPath(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + require.NoError(solo.t, err) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: key, + Data: channeltypes.CommitAcknowledgement(transferAck), + } + + return solo.GenerateProof(signBytes) +} + +// GenerateReceiptAbsenceProof generates a receipt absence proof for the provided packet. +func (solo *Solomachine) GenerateReceiptAbsenceProof(packet channeltypes.Packet) []byte { + merklePath := solo.GetPacketReceiptPath(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + require.NoError(solo.t, err) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: key, + Data: nil, + } + return solo.GenerateProof(signBytes) +} + +// GetClientStatePath returns the commitment path for the client state. +func (solo *Solomachine) GetClientStatePath(counterpartyClientIdentifier string) commitmenttypes.MerklePath { + path, err := commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(host.FullClientStatePath(counterpartyClientIdentifier))) + require.NoError(solo.t, err) + + return path +} + +// GetConsensusStatePath returns the commitment path for the consensus state. +func (solo *Solomachine) GetConsensusStatePath(counterpartyClientIdentifier string, consensusHeight exported.Height) commitmenttypes.MerklePath { + path, err := commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(host.FullConsensusStatePath(counterpartyClientIdentifier, consensusHeight))) + require.NoError(solo.t, err) + + return path +} + +// GetConnectionStatePath returns the commitment path for the connection state. +func (solo *Solomachine) GetConnectionStatePath(connID string) commitmenttypes.MerklePath { + connectionPath := commitmenttypes.NewMerklePath(host.ConnectionPath(connID)) + path, err := commitmenttypes.ApplyPrefix(prefix, connectionPath) + require.NoError(solo.t, err) + + return path +} + +// GetChannelStatePath returns the commitment path for that channel state. +func (solo *Solomachine) GetChannelStatePath(portID, channelID string) commitmenttypes.MerklePath { + channelPath := commitmenttypes.NewMerklePath(host.ChannelPath(portID, channelID)) + path, err := commitmenttypes.ApplyPrefix(prefix, channelPath) + require.NoError(solo.t, err) + + return path +} + +// GetPacketCommitmentPath returns the commitment path for a packet commitment. +func (solo *Solomachine) GetPacketCommitmentPath(portID, channelID string, sequence uint64) commitmenttypes.MerklePath { + commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentPath(portID, channelID, sequence)) + path, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath) + require.NoError(solo.t, err) + + return path +} + +// GetPacketAcknowledgementPath returns the commitment path for a packet acknowledgement. +func (solo *Solomachine) GetPacketAcknowledgementPath(portID, channelID string, sequence uint64) commitmenttypes.MerklePath { + ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementPath(portID, channelID, sequence)) + path, err := commitmenttypes.ApplyPrefix(prefix, ackPath) + require.NoError(solo.t, err) + + return path +} + +// GetPacketReceiptPath returns the commitment path for a packet receipt +// and an absent receipts. +func (solo *Solomachine) GetPacketReceiptPath(portID, channelID string, sequence uint64) commitmenttypes.MerklePath { + receiptPath := commitmenttypes.NewMerklePath(host.PacketReceiptPath(portID, channelID, sequence)) + path, err := commitmenttypes.ApplyPrefix(prefix, receiptPath) + require.NoError(solo.t, err) + + return path +} + +// GetNextSequenceRecvPath returns the commitment path for the next sequence recv counter. +func (solo *Solomachine) GetNextSequenceRecvPath(portID, channelID string) commitmenttypes.MerklePath { + nextSequenceRecvPath := commitmenttypes.NewMerklePath(host.NextSequenceRecvPath(portID, channelID)) + path, err := commitmenttypes.ApplyPrefix(prefix, nextSequenceRecvPath) + require.NoError(solo.t, err) + + return path +} diff --git a/testutil/ibc/types/expected_keepers.go b/testutil/ibc/types/expected_keepers.go new file mode 100644 index 00000000..07034b57 --- /dev/null +++ b/testutil/ibc/types/expected_keepers.go @@ -0,0 +1,12 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// StakingKeeper defines the expected staking keeper interface used in the +// IBC testing package +type StakingKeeper interface { + GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool) +} diff --git a/testutil/ibc/utils.go b/testutil/ibc/utils.go new file mode 100644 index 00000000..63b9da7c --- /dev/null +++ b/testutil/ibc/utils.go @@ -0,0 +1,23 @@ +package ibc + +import ( + "testing" + + abci "github.com/cometbft/cometbft/abci/types" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/stretchr/testify/require" +) + +// ApplyValSetChanges takes in tmtypes.ValidatorSet and []abci.ValidatorUpdate and will return a new tmtypes.ValidatorSet which has the +// provided validator updates applied to the provided validator set. +func ApplyValSetChanges(t *testing.T, valSet *tmtypes.ValidatorSet, valUpdates []abci.ValidatorUpdate) *tmtypes.ValidatorSet { + updates, err := tmtypes.PB2TM.ValidatorUpdates(valUpdates) + require.NoError(t, err) + + // must copy since validator set will mutate with UpdateWithChangeSet + newVals := valSet.Copy() + err = newVals.UpdateWithChangeSet(updates) + require.NoError(t, err) + + return newVals +} diff --git a/testutil/ibc/values.go b/testutil/ibc/values.go new file mode 100644 index 00000000..43029616 --- /dev/null +++ b/testutil/ibc/values.go @@ -0,0 +1,66 @@ +/* +This file contains the variables, constants, and default values +used in the testing package and commonly defined in tests. +*/ +package ibc + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types" + ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + "github.com/cosmos/ibc-go/v7/testing/mock" + "github.com/cosmos/ibc-go/v7/testing/simapp" +) + +const ( + FirstClientID = "07-tendermint-0" + FirstChannelID = "channel-0" + FirstConnectionID = "connection-0" + + // Default params constants used to create a TM client + TrustingPeriod time.Duration = time.Hour * 24 * 7 * 2 + UnbondingPeriod time.Duration = time.Hour * 24 * 7 * 3 + MaxClockDrift time.Duration = time.Second * 10 + DefaultDelayPeriod uint64 = 0 + + DefaultChannelVersion = mock.Version + InvalidID = "IDisInvalid" + + // Application Ports + TransferPort = ibctransfertypes.ModuleName + MockPort = mock.ModuleName + MockFeePort = simapp.MockFeePort + + // used for testing proposals + Title = "title" + Description = "description" + + LongString = "LoremipsumdolorsitameconsecteturadipiscingeliseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquUtenimadminimveniamquisnostrudexercitationullamcolaborisnisiutaliquipexeacommodoconsequDuisauteiruredolorinreprehenderitinvoluptateelitsseillumoloreufugiatnullaariaturEcepteurintoccaectupidatatonroidentuntnulpauifficiaeseruntmollitanimidestlaborum" +) + +var ( + DefaultOpenInitVersion *connectiontypes.Version + + // DefaultTrustLevel sets params variables used to create a TM client + DefaultTrustLevel = ibctm.DefaultTrustLevel + + TestAccAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + TestCoin = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) + TestCoins = sdk.NewCoins(TestCoin) + + UpgradePath = []string{"upgrade", "upgradedIBCState"} + + ConnectionVersion = connectiontypes.ExportedVersionsToProto(connectiontypes.GetCompatibleVersions())[0] + + MockAcknowledgement = mock.MockAcknowledgement.Acknowledgement() + MockPacketData = mock.MockPacketData + MockFailPacketData = mock.MockFailPacketData + MockRecvCanaryCapabilityName = mock.MockRecvCanaryCapabilityName + + prefix = commitmenttypes.NewMerklePrefix([]byte("ibc")) +) diff --git a/x/agent/keeper/keeper_test.go b/x/agent/keeper/keeper_test.go index 7026bf8d..9545acc7 100644 --- a/x/agent/keeper/keeper_test.go +++ b/x/agent/keeper/keeper_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "testing" + "github.com/Lorenzo-Protocol/lorenzo/app" "github.com/stretchr/testify/suite" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -12,13 +13,12 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/Lorenzo-Protocol/lorenzo/app/helpers" "github.com/Lorenzo-Protocol/lorenzo/x/agent/keeper" "github.com/Lorenzo-Protocol/lorenzo/x/agent/types" ) var ( - testAdmin = helpers.CreateTestAddrs(1)[0] + testAdmin = app.CreateTestAddrs(1)[0] agents = []types.Agent{ { Id: 1, @@ -54,7 +54,7 @@ func (suite *KeeperTestSuite) SetupTest() { state[types.ModuleName] = cdc.MustMarshalJSON(genesis) } - app := helpers.SetupWithGenesisMergeFn(suite.T(), merge) + app := app.SetupWithGenesisMergeFn(suite.T(), merge) ctx := app.GetBaseApp().NewContext(false, tmproto.Header{}) queryHelper := baseapp.NewQueryServerTestHelper(ctx, app.InterfaceRegistry()) diff --git a/x/agent/keeper/msg_server_test.go b/x/agent/keeper/msg_server_test.go index ed1f3a92..61c1b426 100644 --- a/x/agent/keeper/msg_server_test.go +++ b/x/agent/keeper/msg_server_test.go @@ -1,7 +1,7 @@ package keeper_test import ( - "github.com/Lorenzo-Protocol/lorenzo/app/helpers" + "github.com/Lorenzo-Protocol/lorenzo/app" "github.com/Lorenzo-Protocol/lorenzo/x/agent/keeper" "github.com/Lorenzo-Protocol/lorenzo/x/agent/types" ) @@ -23,7 +23,7 @@ func (suite *KeeperTestSuite) TestMsgServer_AddAgent() { suite.Run("not admin", func() { _, err := msgServer.AddAgent(suite.ctx, &types.MsgAddAgent{ - Sender: helpers.CreateTestAddrs(2)[1].String(), + Sender: app.CreateTestAddrs(2)[1].String(), Name: "agent1", BtcReceivingAddress: "tb1ptt9gnjnvje343y47z2wd7r8w6mnuylp8z0w74qftv7p5x323vxeq9jrn6f", EthAddr: "0xBAb28FF7659481F1c8516f616A576339936AFB06", @@ -63,7 +63,7 @@ func (suite *KeeperTestSuite) TestMsgServer_EditAgent() { msgServer := keeper.NewMsgServerImpl(suite.keeper) suite.Run("not admin", func() { _, err := msgServer.EditAgent(suite.ctx, &types.MsgEditAgent{ - Sender: helpers.CreateTestAddrs(2)[1].String(), + Sender: app.CreateTestAddrs(2)[1].String(), Id: 1, }) suite.Require().Error(err) @@ -157,7 +157,7 @@ func (suite *KeeperTestSuite) TestMsgServer_RemoveAgent() { msgServer := keeper.NewMsgServerImpl(suite.keeper) suite.Run("not admin", func() { _, err := msgServer.RemoveAgent(suite.ctx, &types.MsgRemoveAgent{ - Sender: helpers.CreateTestAddrs(2)[1].String(), + Sender: app.CreateTestAddrs(2)[1].String(), Id: 1, }) suite.Require().Error(err) diff --git a/x/fee/keeper/keeper_test.go b/x/fee/keeper/keeper_test.go index d9e5b6f6..7e75ec9c 100644 --- a/x/fee/keeper/keeper_test.go +++ b/x/fee/keeper/keeper_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "testing" + "github.com/Lorenzo-Protocol/lorenzo/app" "github.com/stretchr/testify/suite" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -10,7 +11,6 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/Lorenzo-Protocol/lorenzo/app/helpers" "github.com/Lorenzo-Protocol/lorenzo/x/fee/keeper" "github.com/Lorenzo-Protocol/lorenzo/x/fee/types" ) @@ -29,7 +29,7 @@ func TestKeeperTestSuite(t *testing.T) { } func (suite *KeeperTestSuite) SetupTest() { - app := helpers.Setup(suite.T()) + app := app.Setup(suite.T()) suite.cdc = app.LegacyAmino() suite.ifr = app.InterfaceRegistry() diff --git a/x/ibctransfer/ibc_module.go b/x/ibctransfer/ibc_module.go index 9176a71c..309fd882 100644 --- a/x/ibctransfer/ibc_module.go +++ b/x/ibctransfer/ibc_module.go @@ -15,9 +15,9 @@ type IBCModule struct { } // NewIBCModule creates a new IBCModule given the keeper -func NewIBCModule(k *keeper.Keeper) IBCModule { +func NewIBCModule(k *keeper.Keeper) *IBCModule { transferModule := ibctransfer.NewIBCModule(*k.Keeper) - return IBCModule{ + return &IBCModule{ IBCModule: &transferModule, } } diff --git a/x/ibctransfer/keeper/msg_server_test.go b/x/ibctransfer/keeper/msg_server_test.go index b99c3fb5..5535f57a 100644 --- a/x/ibctransfer/keeper/msg_server_test.go +++ b/x/ibctransfer/keeper/msg_server_test.go @@ -94,7 +94,7 @@ func (suite *KeeperTestSuite) TestTransfer() { suite.Commit() params := suite.app.TokenKeeper.GetParams(suite.ctx) - params.EnableConvert = false + params.EnableConversion = false suite.app.TokenKeeper.SetParams(suite.ctx, params) suite.Commit() @@ -121,7 +121,7 @@ func (suite *KeeperTestSuite) TestTransfer() { suite.Commit() params := suite.app.TokenKeeper.GetParams(suite.ctx) - params.EnableConvert = false + params.EnableConversion = false suite.app.TokenKeeper.SetParams(suite.ctx, params) suite.Commit() diff --git a/x/ibctransfer/keeper/setup_test.go b/x/ibctransfer/keeper/setup_test.go index c71de41c..0bf32a0f 100644 --- a/x/ibctransfer/keeper/setup_test.go +++ b/x/ibctransfer/keeper/setup_test.go @@ -20,7 +20,6 @@ import ( clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" "github.com/Lorenzo-Protocol/lorenzo/app" - "github.com/Lorenzo-Protocol/lorenzo/app/helpers" "github.com/Lorenzo-Protocol/lorenzo/contracts/erc20" "github.com/Lorenzo-Protocol/lorenzo/testutil" utiltx "github.com/Lorenzo-Protocol/lorenzo/testutil/tx" @@ -68,9 +67,9 @@ func (suite *KeeperTestSuite) execSetupTest() { consAddress := sdk.ConsAddress(privCons.PubKey().Address()) // init app - suite.app = helpers.SetupWithGenesisMergeFn(suite.T(), nil) + suite.app = app.SetupWithGenesisMergeFn(suite.T(), nil) header := testutil.NewHeader( - suite.app.LastBlockHeight()+1, time.Now().UTC(), helpers.SimAppChainID, consAddress, nil, nil, + suite.app.LastBlockHeight()+1, time.Now().UTC(), app.SimAppChainID, consAddress, nil, nil, ) suite.ctx = suite.app.GetBaseApp().NewContext(false, header) diff --git a/x/plan/keeper/keeper_test.go b/x/plan/keeper/keeper_test.go index 50976b7d..e829c7a3 100644 --- a/x/plan/keeper/keeper_test.go +++ b/x/plan/keeper/keeper_test.go @@ -21,7 +21,6 @@ import ( "github.com/Lorenzo-Protocol/lorenzo/app" - "github.com/Lorenzo-Protocol/lorenzo/app/helpers" "github.com/Lorenzo-Protocol/lorenzo/x/plan/keeper" "github.com/Lorenzo-Protocol/lorenzo/x/plan/types" @@ -31,7 +30,7 @@ import ( "github.com/stretchr/testify/suite" ) -var testAdmin = helpers.CreateTestAddrs(1)[0] +var testAdmin = app.CreateTestAddrs(1)[0] type KeeperTestSuite struct { suite.Suite @@ -73,14 +72,14 @@ func (suite *KeeperTestSuite) SetupTest() { state[evmtypes.ModuleName] = cdc.MustMarshalJSON(evmGenesis) } - lorenzoApp := helpers.SetupWithGenesisMergeFn(suite.T(), merge) + lorenzoApp := app.SetupWithGenesisMergeFn(suite.T(), merge) // consensus key privCons, err := ethsecp256k1.GenerateKey() suite.Require().NoError(err) consAddress := sdk.ConsAddress(privCons.PubKey().Address()) header := testutil.NewHeader( - lorenzoApp.LastBlockHeight()+1, time.Now().UTC(), helpers.SimAppChainID, consAddress, nil, nil, + lorenzoApp.LastBlockHeight()+1, time.Now().UTC(), app.SimAppChainID, consAddress, nil, nil, ) ctx := lorenzoApp.GetBaseApp().NewContext(false, header) diff --git a/x/token/client/cli/tx.go b/x/token/client/cli/tx.go index 47bd9572..cafc3f32 100644 --- a/x/token/client/cli/tx.go +++ b/x/token/client/cli/tx.go @@ -3,13 +3,14 @@ package cli import ( "fmt" - "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/cobra" + "github.com/ethereum/go-ethereum/common" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" "github.com/Lorenzo-Protocol/lorenzo/x/token/types" ) @@ -18,7 +19,7 @@ import ( func NewTxCmd() *cobra.Command { txCmd := &cobra.Command{ Use: types.ModuleName, - Short: "convert subcommands", + Short: "token module subcommands", DisableFlagParsing: true, SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, diff --git a/x/token/ibc_middleware.go b/x/token/ibc_middleware.go index 0f50b7a3..1f9180fc 100644 --- a/x/token/ibc_middleware.go +++ b/x/token/ibc_middleware.go @@ -1,26 +1,32 @@ package token import ( - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - ibctransfer "github.com/cosmos/ibc-go/v7/modules/apps/transfer" - transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/cosmos/ibc-go/v7/modules/core/04-channel/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" ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + ibctransfer "github.com/Lorenzo-Protocol/lorenzo/x/ibctransfer" "github.com/Lorenzo-Protocol/lorenzo/x/token/keeper" ) var _ porttypes.IBCModule = &IBCMiddleware{} -// IBCMiddleware implements the ICS26 callbacks for the ibctransfer module. +// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application +func NewIBCMiddleware(module *ibctransfer.IBCModule, k *keeper.Keeper) IBCMiddleware { + return IBCMiddleware{ + IBCModule: module, + keeper: k, + } +} + +// IBCMiddleware implements the ICS26 callbacks for the ibc-transfer module. type IBCMiddleware struct { *ibctransfer.IBCModule - keeper keeper.Keeper + keeper *keeper.Keeper } // OnRecvPacket implements the ICS-26 interface. If it successfully handles OnRecvPacket, a @@ -47,22 +53,15 @@ func (im IBCMiddleware) OnAcknowledgementPacket( acknowledgement []byte, relayer sdk.AccAddress, ) error { - var ack channeltypes.Acknowledgement - if err := transfertypes.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { - return errorsmod.Wrapf(errortypes.ErrUnknownRequest, - "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err) - } - - var data transfertypes.FungibleTokenPacketData - if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - return errorsmod.Wrapf(errortypes.ErrUnknownRequest, - "cannot unmarshal ICS-20 transfer packet data: %s", err.Error()) - } - if err := im.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer); err != nil { return err } + var ack channeltypes.Acknowledgement + var data transfertypes.FungibleTokenPacketData + transfertypes.ModuleCdc.MustUnmarshalJSON(packet.GetData(), &data) + transfertypes.ModuleCdc.MustUnmarshalJSON(acknowledgement, &ack) + // post-processing return im.keeper.OnAcknowledgementPacket(ctx, packet, data, ack) } @@ -74,16 +73,13 @@ func (im IBCMiddleware) OnTimeoutPacket( packet types.Packet, relayer sdk.AccAddress, ) error { - var data transfertypes.FungibleTokenPacketData - if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - return errorsmod.Wrapf(errortypes.ErrUnknownRequest, - "cannot unmarshal ICS-20 transfer packet data: %s", err.Error()) - } - if err := im.IBCModule.OnTimeoutPacket(ctx, packet, relayer); err != nil { return err } + var data transfertypes.FungibleTokenPacketData + transfertypes.ModuleCdc.MustUnmarshalJSON(packet.GetData(), &data) + // post-processing return im.keeper.OnTimeoutPacket(ctx, packet, data) } diff --git a/x/token/ibc_middleware_test.go b/x/token/ibc_middleware_test.go new file mode 100644 index 00000000..13df76dd --- /dev/null +++ b/x/token/ibc_middleware_test.go @@ -0,0 +1,227 @@ +package token_test + +import ( + "github.com/ethereum/go-ethereum/common" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + + "github.com/Lorenzo-Protocol/lorenzo/contracts/erc20" + tokentypes "github.com/Lorenzo-Protocol/lorenzo/x/token/types" +) + +// TestOnRecvPacket tests the OnRecvPacket function of the middleware. +// Note: mock packet received instead sending an actual packet. +func (suite *MiddlewareTestSuite) TestOnRecvPacket() { + testCases := []struct { + name string + expectConverted bool + baseDenom string + pair tokentypes.TokenPair + }{ + { + name: "token pair not registered", + baseDenom: "coin", + expectConverted: false, + }, + { + name: "token pair registered", + baseDenom: "coin", + expectConverted: true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + module, _, err := suite.LorenzoChainB.App.GetIBCKeeper().PortKeeper.LookupModuleByPort( + suite.LorenzoChainB.GetContext(), ibctransfertypes.ModuleName) + suite.Require().NoError(err) + + cbs, ok := suite.LorenzoChainB.App.GetIBCKeeper().Router.GetRoute(module) + suite.Require().True(ok) + + sender := suite.LorenzoChainA.SenderAccount.GetAddress() + receiver := suite.LorenzoChainB.SenderAccount.GetAddress() + + // construct package sent from chain-a + data := ibctransfertypes.FungibleTokenPacketData{ + Denom: tc.baseDenom, + Amount: "1000", + Sender: sender.String(), + Receiver: receiver.String(), + Memo: "none", + } + packet := suite.NewMockTransferPacket(data.GetBytes()) + + // register in advance + if tc.expectConverted { + ibcDenom := suite.utilsCreateIBCDenom( + suite.Path.EndpointB.ChannelID, + suite.Path.EndpointB.ChannelConfig.PortID, + "coin") + + // NOTE: mint coin before register, avoid empty supply. + err := suite.chainB.BankKeeper.MintCoins(suite.LorenzoChainB.GetContext(), + tokentypes.ModuleName, sdk.NewCoins(sdk.NewCoin(ibcDenom, sdk.NewInt(1)))) + suite.Require().NoError(err) + + // register pair for this token. + pair, err := suite.chainB.TokenKeeper.RegisterCoin(suite.LorenzoChainB.GetContext(), banktypes.Metadata{ + Description: "", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: ibcDenom, + Exponent: 0, + }, + }, + Base: ibcDenom, + Display: ibcDenom, + Name: "coin", + Symbol: "coin", + }) + suite.Require().NoError(err) + resPair, found := suite.chainB.TokenKeeper.GetTokenPair(suite.LorenzoChainB.GetContext(), pair.GetID()) + suite.Require().True(found) + suite.Require().Equal(pair, &resPair) + tc.pair = *pair + } + + ack := cbs.OnRecvPacket(suite.LorenzoChainB.GetContext(), packet, suite.LorenzoChainB.SenderAccount.GetAddress()) + suite.Require().True(ack.Success()) + + if tc.expectConverted { + balance := suite.chainB.TokenKeeper.ERC20BalanceOf( + suite.LorenzoChainB.GetContext(), + erc20.ERC20MinterBurnerDecimalsContract.ABI, + common.HexToAddress(tc.pair.ContractAddress), + common.BytesToAddress(receiver.Bytes()), + ) + suite.Require().Equal(data.Amount, balance.String()) + } + }) + } +} + +// TestOnAcknowledgementPacket tests the OnAcknowledgementPacket function of the middleware. +// Note: mock packet timeout instead sending an actual packet. +func (suite *MiddlewareTestSuite) TestOnAcknowledgementPacket() { + testCases := []struct { + name string + malleate func() + ack []byte + invalidAck bool + expectConvert bool + }{ + { + name: "invalid ack", + ack: []byte("any-value"), + invalidAck: true, + }, + { + name: "ack confirmed", + ack: suite.utilsMockAcknowledgement(true), + }, + { + name: "ack error, token registered", + ack: suite.utilsMockAcknowledgement(false), + malleate: func() { + _, err := suite.chainA.TokenKeeper.RegisterCoin(suite.LorenzoChainA.GetContext(), banktypes.Metadata{ + Description: "", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "coin", + Exponent: 0, + }, + }, + Base: "coin", + Display: "coin", + Name: "coin", + Symbol: "coin", + }) + suite.Require().NoError(err) + }, + expectConvert: true, + }, + { + name: "ack error, token not registered", + ack: suite.utilsMockAcknowledgement(false), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + // get route callback + module, _, err := suite.LorenzoChainA.App.GetIBCKeeper().PortKeeper.LookupModuleByPort( + suite.LorenzoChainA.GetContext(), ibctransfertypes.ModuleName) + suite.Require().NoError(err) + + cbs, ok := suite.LorenzoChainA.App.GetIBCKeeper().Router.GetRoute(module) + suite.Require().True(ok) + + sender := suite.LorenzoChainA.SenderAccount.GetAddress() + receiver := suite.LorenzoChainB.SenderAccount.GetAddress() + + // fund sender + err = suite.chainA.BankKeeper.MintCoins(suite.LorenzoChainA.GetContext(), + tokentypes.ModuleName, sdk.NewCoins(sdk.NewCoin("coin", sdk.NewInt(1000)))) + suite.Require().NoError(err) + err = suite.chainA.BankKeeper.SendCoinsFromModuleToAccount(suite.LorenzoChainA.GetContext(), + tokentypes.ModuleName, sender, sdk.NewCoins(sdk.NewCoin("coin", sdk.NewInt(1000)))) + suite.Require().NoError(err) + + if tc.malleate != nil { + tc.malleate() + } + + // lock coin + msg := &ibctransfertypes.MsgTransfer{ + SourcePort: suite.Path.EndpointA.ChannelConfig.PortID, + SourceChannel: suite.Path.EndpointA.ChannelID, + Token: sdk.NewCoin("coin", sdk.NewInt(1000)), + Sender: sender.String(), + Receiver: receiver.String(), + TimeoutHeight: clienttypes.NewHeight(0, 100), + TimeoutTimestamp: 0, + Memo: "", + } + + _, _ = suite.chainA.ICS20WrapperKeeper.Transfer(suite.LorenzoChainA.GetContext(), msg) // nolint:errcheck + + // get packet + data := ibctransfertypes.FungibleTokenPacketData{ + Denom: "coin", + Amount: "1000", + Sender: sender.String(), + Receiver: receiver.String(), + Memo: "none", + } + packet := suite.NewMockTransferPacket(data.GetBytes()) + + err = cbs.OnAcknowledgementPacket(suite.LorenzoChainA.GetContext(), packet, tc.ack, sender) + if tc.invalidAck { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + + if tc.expectConvert { + id := suite.chainA.TokenKeeper.GetTokenPairIdByDenom(suite.LorenzoChainA.GetContext(), "coin") + pair, found := suite.chainA.TokenKeeper.GetTokenPair(suite.LorenzoChainA.GetContext(), id) + suite.Require().True(found) + + balance := suite.chainA.TokenKeeper.ERC20BalanceOf( + suite.LorenzoChainA.GetContext(), + erc20.ERC20MinterBurnerDecimalsContract.ABI, + common.HexToAddress(pair.ContractAddress), + common.BytesToAddress(sender.Bytes()), + ) + suite.Require().Equal("1000", balance.String()) + } + } + }) + } +} diff --git a/x/token/keeper/convert.go b/x/token/keeper/convert.go index 2652d8b6..8f2605e7 100644 --- a/x/token/keeper/convert.go +++ b/x/token/keeper/convert.go @@ -3,12 +3,11 @@ package keeper import ( "math/big" + "github.com/ethereum/go-ethereum/common" + errorsmod "cosmossdk.io/errors" - "github.com/armon/go-metrics" - "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum/common" "github.com/Lorenzo-Protocol/lorenzo/contracts/erc20" "github.com/Lorenzo-Protocol/lorenzo/x/token/types" @@ -54,26 +53,6 @@ func (k Keeper) ConvertNativeCoinToVoucherERC20( "invalid token balance - expected: %v, actual: %v", balanceExpected, balanceAfter) } - defer func() { - telemetry.IncrCounterWithLabels( - []string{"tx", "msg", "convert", "coin", "total"}, - 1, - []metrics.Label{ - telemetry.NewLabel("denom", pair.Denom), - }, - ) - - if msg.Coin.Amount.IsInt64() { - telemetry.IncrCounterWithLabels( - []string{"tx", "msg", "convert", "coin", "amount", "total"}, - float32(msg.Coin.Amount.Int64()), - []metrics.Label{ - telemetry.NewLabel("denom", pair.Denom), - }, - ) - } - }() - ctx.EventManager().EmitEvents( sdk.Events{ sdk.NewEvent( @@ -140,26 +119,6 @@ func (k Keeper) ConvertVoucherERC20ToNativeCoin( ) } - defer func() { - telemetry.IncrCounterWithLabels( - []string{"tx", "msg", "convert", "erc20", "total"}, - 1, - []metrics.Label{ - telemetry.NewLabel("denom", pair.Denom), - }, - ) - - if msg.Amount.IsInt64() { - telemetry.IncrCounterWithLabels( - []string{"tx", "msg", "convert", "erc20", "amount", "total"}, - float32(msg.Amount.Int64()), - []metrics.Label{ - telemetry.NewLabel("denom", pair.Denom), - }, - ) - } - }() - ctx.EventManager().EmitEvents( sdk.Events{ sdk.NewEvent( @@ -238,26 +197,6 @@ func (k Keeper) ConvertVoucherCoinToNativeERC20( return nil, err } - defer func() { - telemetry.IncrCounterWithLabels( - []string{"tx", "msg", "convert", "coin", "total"}, - 1, - []metrics.Label{ - telemetry.NewLabel("denom", pair.Denom), - }, - ) - - if msg.Coin.Amount.IsInt64() { - telemetry.IncrCounterWithLabels( - []string{"tx", "msg", "convert", "coin", "amount", "total"}, - float32(msg.Coin.Amount.Int64()), - []metrics.Label{ - telemetry.NewLabel("denom", pair.Denom), - }, - ) - } - }() - ctx.EventManager().EmitEvents( sdk.Events{ sdk.NewEvent( @@ -340,26 +279,6 @@ func (k Keeper) ConvertNativeERC20ToVoucherCoin( return nil, err } - defer func() { - telemetry.IncrCounterWithLabels( - []string{"tx", "msg", "convert", "erc20", "total"}, - 1, - []metrics.Label{ - telemetry.NewLabel("coin", pair.Denom), - }, - ) - - if msg.Amount.IsInt64() { - telemetry.IncrCounterWithLabels( - []string{"tx", "msg", "convert", "erc20", "amount", "total"}, - float32(msg.Amount.Int64()), - []metrics.Label{ - telemetry.NewLabel("denom", pair.Denom), - }, - ) - } - }() - ctx.EventManager().EmitEvents( sdk.Events{ sdk.NewEvent( diff --git a/x/token/keeper/evm_hooks.go b/x/token/keeper/evm_hooks.go index 14554ce8..1208d124 100644 --- a/x/token/keeper/evm_hooks.go +++ b/x/token/keeper/evm_hooks.go @@ -36,7 +36,7 @@ func (k Keeper) PostTxProcessing( receipt *ethtypes.Receipt, ) error { params := k.GetParams(ctx) - if !params.EnableConvert || !params.EnableEVMHook { + if !params.EnableConversion || !params.EnableEVMHook { return nil } @@ -106,10 +106,10 @@ func (k Keeper) PostTxProcessing( // create the corresponding sdk.Coin that is paired with ERC20 coins := sdk.Coins{{Denom: pair.Denom, Amount: sdk.NewIntFromBigInt(tokens)}} - switch pair.Ownership { + switch pair.Source { case types.OWNER_MODULE: _, err = k.CallEVM(ctx, erc20, types.ModuleAddress, contractAddr, true, "burn", tokens) - case types.OWNER_EXTERNAL: + case types.OWNER_CONTRACT: err = k.bankKeeper.MintCoins(ctx, types.ModuleName, coins) default: err = types.ErrUndefinedOwner diff --git a/x/token/keeper/evm_hooks_test.go b/x/token/keeper/evm_hooks_test.go index d29bfee0..33184ba5 100644 --- a/x/token/keeper/evm_hooks_test.go +++ b/x/token/keeper/evm_hooks_test.go @@ -174,7 +174,7 @@ func (suite *KeeperTestSuite) TestPostTxProcessing() { resPair, contractAddr := suite.utilsFundAndRegisterERC20("coin", "token", erc20Decimals, account, 1000) pair = &resPair - pair.Ownership = types.OWNER_UNDEFINED + pair.Source = types.OWNER_UNDEFINED suite.app.TokenKeeper.SetTokenPair(suite.ctx, *pair) topics := []common.Hash{transferEvent.ID, account.Hash(), types.ModuleAddress.Hash()} diff --git a/x/token/keeper/genesis_test.go b/x/token/keeper/genesis_test.go index 4e00edba..561df142 100644 --- a/x/token/keeper/genesis_test.go +++ b/x/token/keeper/genesis_test.go @@ -18,7 +18,7 @@ func (suite *KeeperTestSuite) TestInitGenesis() { { ContractAddress: "0x1D1530e3A3719BE0BEe1abba5016Cf2e236f3277", Denom: coinBaseDenom, - Ownership: types.OWNER_MODULE, + Source: types.OWNER_MODULE, Enabled: true, }, }, @@ -64,7 +64,7 @@ func (suite *KeeperTestSuite) TestExportGenesis() { { ContractAddress: "0x1D1530e3A3719BE0BEe1abba5016Cf2e236f3277", Denom: coinBaseDenom, - Ownership: types.OWNER_MODULE, + Source: types.OWNER_MODULE, Enabled: true, }, }, diff --git a/x/token/keeper/grpc_query.go b/x/token/keeper/grpc_query.go index 9da83a01..27e54a8b 100644 --- a/x/token/keeper/grpc_query.go +++ b/x/token/keeper/grpc_query.go @@ -3,13 +3,16 @@ package keeper import ( "context" - "github.com/Lorenzo-Protocol/lorenzo/x/token/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/ethereum/go-ethereum/common" + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" - "github.com/ethereum/go-ethereum/common" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + + "github.com/Lorenzo-Protocol/lorenzo/x/token/types" ) var _ types.QueryServer = Querier{} diff --git a/x/token/keeper/grpc_query_test.go b/x/token/keeper/grpc_query_test.go index 3978503d..37a95bf9 100644 --- a/x/token/keeper/grpc_query_test.go +++ b/x/token/keeper/grpc_query_test.go @@ -15,7 +15,7 @@ func (suite *KeeperTestSuite) TestQueryParams() { actual := suite.app.TokenKeeper.GetParams(suite.ctx) suite.Require().Equal(resp.Params.EnableEVMHook, actual.EnableEVMHook) - suite.Require().Equal(resp.Params.EnableConvert, actual.EnableConvert) + suite.Require().Equal(resp.Params.EnableConversion, actual.EnableConversion) } func (suite *KeeperTestSuite) TestQueryTokenPair() { diff --git a/x/token/keeper/ibc_callbacks.go b/x/token/keeper/ibc_callbacks.go index b27a3c95..8719ab88 100644 --- a/x/token/keeper/ibc_callbacks.go +++ b/x/token/keeper/ibc_callbacks.go @@ -1,14 +1,10 @@ package keeper import ( - errorsmod "cosmossdk.io/errors" - "github.com/ethereum/go-ethereum/common" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/cosmos/ibc-go/v7/modules/core/exported" @@ -23,10 +19,7 @@ func (k Keeper) OnRecvPacket( ack exported.Acknowledgement, ) exported.Acknowledgement { var data transfertypes.FungibleTokenPacketData - if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - err = errorsmod.Wrapf(errortypes.ErrInvalidType, "cannot unmarshal ICS-20 transfer packet data") - return channeltypes.NewErrorAcknowledgement(err) - } + transfertypes.ModuleCdc.MustUnmarshalJSON(packet.GetData(), &data) receiver, err := sdk.AccAddressFromBech32(data.Receiver) if err != nil { @@ -36,11 +29,17 @@ func (k Keeper) OnRecvPacket( // avoid extra costs for relayers. ctx = ctx.WithKVGasConfig(storetypes.GasConfig{}).WithTransientKVGasConfig(storetypes.GasConfig{}) - coin := k.getReceivedCoin( + coin := k.GetReceivedCoin( packet.SourcePort, packet.SourceChannel, packet.DestinationPort, packet.DestinationChannel, data.Denom, data.Amount) + id := k.GetTokenPairId(ctx, coin.Denom) + _, found := k.GetTokenPair(ctx, id) + if !found { + return ack + } + if _, err := k.ConvertCoin(sdk.WrapSDKContext(ctx), &types.MsgConvertCoin{ Coin: sdk.Coin{ Denom: coin.Denom, @@ -90,13 +89,19 @@ func (k Keeper) ConvertCoinFromPacket( } // avoid extra costs for relayers. - ctx = ctx.WithKVGasConfig(storetypes.GasConfig{}). - WithTransientKVGasConfig(storetypes.GasConfig{}) + ctx = ctx.WithKVGasConfig(storetypes.GasConfig{}).WithTransientKVGasConfig(storetypes.GasConfig{}) // get ibc (or just original) denom denom := transfertypes.ParseDenomTrace(data.Denom).IBCDenom() amount, _ := sdk.NewIntFromString(data.Amount) + // if denom is not registered, return + id := k.GetTokenPairId(ctx, data.Denom) + _, found := k.GetTokenPair(ctx, id) + if !found { + return nil + } + // ConvertCoin will help to check if the denom is registered if _, err := k.ConvertCoin(sdk.WrapSDKContext(ctx), &types.MsgConvertCoin{ Coin: sdk.Coin{Denom: denom, Amount: amount}, @@ -109,8 +114,8 @@ func (k Keeper) ConvertCoinFromPacket( return nil } -// getReceivedCoin returns the transferred coin from an ICS20 FungibleTokenPacketData -func (k Keeper) getReceivedCoin(srcPort, srcChannel, dstPort, dstChannel, rawDenom, rawAmt string) sdk.Coin { +// GetReceivedCoin returns the transferred coin from an ICS20 FungibleTokenPacketData +func (k Keeper) GetReceivedCoin(srcPort, srcChannel, dstPort, dstChannel, rawDenom, rawAmt string) sdk.Coin { // NOTE: Denom and amount are already validated amount, _ := sdk.NewIntFromString(rawAmt) diff --git a/x/token/keeper/keeper.go b/x/token/keeper/keeper.go index 0d30be3c..1c283a4a 100644 --- a/x/token/keeper/keeper.go +++ b/x/token/keeper/keeper.go @@ -3,13 +3,13 @@ package keeper import ( "fmt" - errorsmod "cosmossdk.io/errors" "github.com/cometbft/cometbft/libs/log" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/Lorenzo-Protocol/lorenzo/x/token/types" ) diff --git a/x/token/keeper/msg_server_test.go b/x/token/keeper/msg_server_test.go index 6154648c..5748467f 100644 --- a/x/token/keeper/msg_server_test.go +++ b/x/token/keeper/msg_server_test.go @@ -1,14 +1,15 @@ package keeper_test import ( + "github.com/Lorenzo-Protocol/lorenzo/app" + "github.com/ethereum/go-ethereum/common" + evmtypes "github.com/evmos/ethermint/x/evm/types" + sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - "github.com/ethereum/go-ethereum/common" - evmtypes "github.com/evmos/ethermint/x/evm/types" - "github.com/Lorenzo-Protocol/lorenzo/app/helpers" "github.com/Lorenzo-Protocol/lorenzo/x/token/types" ) @@ -27,8 +28,8 @@ const ( var ( authority = authtypes.NewModuleAddress(govtypes.ModuleName) - tester = helpers.CreateTestAddrs(1)[0] - tester2 = helpers.CreateTestAddrs(1)[0] + tester = app.CreateTestAddrs(1)[0] + tester2 = app.CreateTestAddrs(1)[0] coinMetadata = banktypes.Metadata{ Description: "", @@ -67,8 +68,8 @@ func (suite *KeeperTestSuite) TestRegisterCoin() { expectPass: false, malleate: func() { suite.app.TokenKeeper.SetParams(suite.ctx, types.Params{ - EnableConvert: false, - EnableEVMHook: true, + EnableConversion: false, + EnableEVMHook: true, }) suite.Commit() }, @@ -181,8 +182,8 @@ func (suite *KeeperTestSuite) TestRegisterERC20() { expectPass: false, malleate: func(_ string) { suite.app.TokenKeeper.SetParams(suite.ctx, types.Params{ - EnableConvert: false, - EnableEVMHook: true, + EnableConversion: false, + EnableEVMHook: true, }) suite.Commit() }, @@ -302,7 +303,7 @@ func (suite *KeeperTestSuite) TestToggleConversion() { sender: authority.String(), malleate: func() { suite.app.TokenKeeper.SetParams(suite.ctx, types.Params{ - EnableConvert: false, + EnableConversion: false, }) suite.Commit() }, @@ -387,8 +388,8 @@ func (suite *KeeperTestSuite) TestUpdateParams() { _, err := suite.msgServer.UpdateParams(suite.ctx, &types.MsgUpdateParams{ Authority: tc.sender, Params: types.Params{ - EnableConvert: false, - EnableEVMHook: true, + EnableConversion: false, + EnableEVMHook: true, }, }) @@ -414,7 +415,7 @@ func (suite *KeeperTestSuite) TestConvertNativeCoinToVoucherERC20() { expectPass: false, malleateMintEnable: func() { suite.app.TokenKeeper.SetParams(suite.ctx, types.Params{ - EnableConvert: false, + EnableConversion: false, }) suite.Commit() }, @@ -587,7 +588,7 @@ func (suite *KeeperTestSuite) TestConvertNativeERC20ToVoucherCoin() { expectPass: false, malleateMintEnable: func(pair types.TokenPair) { suite.app.TokenKeeper.SetParams(suite.ctx, types.Params{ - EnableConvert: false, + EnableConversion: false, }) suite.Commit() }, diff --git a/x/token/keeper/params.go b/x/token/keeper/params.go index 6f49af4f..39945679 100644 --- a/x/token/keeper/params.go +++ b/x/token/keeper/params.go @@ -27,7 +27,7 @@ func (k Keeper) GetParams(ctx sdk.Context) types.Params { // IsConvertEnabled returns true if the ERC20 module is enabled. func (k Keeper) IsConvertEnabled(ctx sdk.Context) bool { - return k.GetParams(ctx).EnableConvert + return k.GetParams(ctx).EnableConversion } // IsEVMHookEnabled returns true if the EVM hook is enabled. diff --git a/x/token/keeper/register.go b/x/token/keeper/register.go index f1b4aad3..2138f2ec 100644 --- a/x/token/keeper/register.go +++ b/x/token/keeper/register.go @@ -64,7 +64,7 @@ func (k Keeper) RegisterERC20(ctx sdk.Context, contract common.Address) (*types. ) } - pair := types.NewTokenPair(contract, metadata.Name, types.OWNER_EXTERNAL) + pair := types.NewTokenPair(contract, metadata.Name, types.OWNER_CONTRACT) id := pair.GetID() k.SetTokenPair(ctx, pair) k.SetTokenPairIdByDenom(ctx, pair.Denom, id) diff --git a/x/token/keeper/setup_test.go b/x/token/keeper/setup_test.go index e04ee196..376b0972 100644 --- a/x/token/keeper/setup_test.go +++ b/x/token/keeper/setup_test.go @@ -16,11 +16,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - ibcgotesting "github.com/cosmos/ibc-go/v7/testing" "github.com/Lorenzo-Protocol/lorenzo/app" - "github.com/Lorenzo-Protocol/lorenzo/app/helpers" "github.com/Lorenzo-Protocol/lorenzo/testutil" + utiltx "github.com/Lorenzo-Protocol/lorenzo/testutil/tx" "github.com/Lorenzo-Protocol/lorenzo/x/token/keeper" "github.com/Lorenzo-Protocol/lorenzo/x/token/types" @@ -42,11 +41,6 @@ type KeeperTestSuite struct { msgServer types.MsgServer queryClient types.QueryClient queryClientEvm evmtypes.QueryClient - - // ibc-go testing - ibcTestingEnabled bool - LorenzoChain *ibcgotesting.TestChain - CosmosChain *ibcgotesting.TestChain } var s *KeeperTestSuite @@ -75,9 +69,9 @@ func (suite *KeeperTestSuite) execSetupTest() { // init app // TODO: setup with genesis merge fn need recheck, it's probably not errorless. - suite.app = helpers.SetupWithGenesisMergeFn(suite.T(), nil) + suite.app = app.SetupWithGenesisMergeFn(suite.T(), nil) header := testutil.NewHeader( - suite.app.LastBlockHeight()+1, time.Now().UTC(), helpers.SimAppChainID, consAddress, nil, nil, + suite.app.LastBlockHeight()+1, time.Now().UTC(), app.SimAppChainID, consAddress, nil, nil, ) suite.ctx = suite.app.GetBaseApp().NewContext(false, header) @@ -99,11 +93,6 @@ func (suite *KeeperTestSuite) execSetupTest() { evmtypes.RegisterQueryServer(queryHelper, suite.app.EvmKeeper) suite.queryClient = types.NewQueryClient(queryHelper) suite.queryClientEvm = evmtypes.NewQueryClient(queryHelper) - - // ibc-go testing - if suite.ibcTestingEnabled { - suite.SetupIBCTest() - } } // Commit commits and starts a new block with an updated context. diff --git a/x/token/keeper/token_pair.go b/x/token/keeper/token_pair.go index f9bb1227..5df1fa38 100644 --- a/x/token/keeper/token_pair.go +++ b/x/token/keeper/token_pair.go @@ -1,10 +1,12 @@ package keeper import ( - "github.com/Lorenzo-Protocol/lorenzo/x/token/types" + "github.com/ethereum/go-ethereum/common" + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" + + "github.com/Lorenzo-Protocol/lorenzo/x/token/types" ) // RemoveTokenPair removes a token pair and its id mappings. diff --git a/x/token/keeper/token_pair_test.go b/x/token/keeper/token_pair_test.go index a41a90f8..bf2cd0b0 100644 --- a/x/token/keeper/token_pair_test.go +++ b/x/token/keeper/token_pair_test.go @@ -1,9 +1,10 @@ package keeper_test import ( + "github.com/ethereum/go-ethereum/common" + utiltx "github.com/Lorenzo-Protocol/lorenzo/testutil/tx" "github.com/Lorenzo-Protocol/lorenzo/x/token/types" - "github.com/ethereum/go-ethereum/common" ) func (suite *KeeperTestSuite) TestRemoveTokenPair() { diff --git a/x/token/keeper/utils_test.go b/x/token/keeper/utils_test.go index 282c63ee..fe7757c0 100644 --- a/x/token/keeper/utils_test.go +++ b/x/token/keeper/utils_test.go @@ -86,7 +86,3 @@ func (suite *KeeperTestSuite) utilsERC20Mint(contract, from, to common.Address, from, contract, true, "mint", to, sdk.NewInt(amount).BigInt()) suite.Require().NoError(err) } - -func (suite *KeeperTestSuite) SetupIBCTest() { - // TODO: Implement this function -} diff --git a/x/token/module.go b/x/token/module.go index 2fd377e5..6fbf2362 100644 --- a/x/token/module.go +++ b/x/token/module.go @@ -20,6 +20,7 @@ import ( "github.com/Lorenzo-Protocol/lorenzo/x/token/types" ) +// ConsensusVersion defines the current token module consensus version. const consensusVersion = 1 var ( @@ -29,6 +30,7 @@ var ( _ module.HasServices = AppModule{} ) +// AppModuleBasic defines the basic application module used by the token module. type AppModuleBasic struct { cdc codec.Codec } @@ -72,6 +74,7 @@ func (am AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodin return genesisState.Validate() } +// AppModule defines the token module. type AppModule struct { AppModuleBasic diff --git a/x/token/setup_test.go b/x/token/setup_test.go new file mode 100644 index 00000000..92da3bc5 --- /dev/null +++ b/x/token/setup_test.go @@ -0,0 +1,83 @@ +package token_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/suite" + + ibctransfertypes "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" + + "github.com/Lorenzo-Protocol/lorenzo/app" + ibctesting "github.com/Lorenzo-Protocol/lorenzo/testutil/ibc" +) + +type MiddlewareTestSuite struct { + suite.Suite + + chainA *app.LorenzoApp + chainB *app.LorenzoApp + + Coordinator *ibctesting.Coordinator + + LorenzoChainA *ibctesting.TestChain + LorenzoChainB *ibctesting.TestChain + + Path *ibctesting.Path +} + +func (suite *MiddlewareTestSuite) SetupTest() { + suite.Coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.LorenzoChainA = suite.Coordinator.GetChain(ibctesting.GetChainID(1)) + suite.LorenzoChainB = suite.Coordinator.GetChain(ibctesting.GetChainID(2)) + + suite.Path = ibctesting.NewPath(suite.LorenzoChainA, suite.LorenzoChainB) + suite.Path.EndpointA.ChannelConfig.PortID = ibctransfertypes.ModuleName + suite.Path.EndpointB.ChannelConfig.PortID = ibctransfertypes.ModuleName + suite.Path.EndpointA.ChannelConfig.Version = ibctransfertypes.Version + suite.Path.EndpointB.ChannelConfig.Version = ibctransfertypes.Version + + suite.Coordinator.Setup(suite.Path) + + suite.chainA = suite.LorenzoChainA.App.(*app.LorenzoApp) //nolint:errcheck + suite.chainB = suite.LorenzoChainB.App.(*app.LorenzoApp) //nolint:errcheck +} + +func TestMiddlewareTestSuite(t *testing.T) { + suite.Run(t, new(MiddlewareTestSuite)) +} + +// NewMockPacket returns a new mock packet sending from chain-a to chain-b +func (suite *MiddlewareTestSuite) NewMockTransferPacket(data []byte) channeltypes.Packet { + return channeltypes.NewPacket( + data, + suite.LorenzoChainA.SenderAccount.GetSequence(), + suite.Path.EndpointA.ChannelConfig.PortID, + suite.Path.EndpointA.ChannelID, + suite.Path.EndpointB.ChannelConfig.PortID, + suite.Path.EndpointB.ChannelID, + clienttypes.NewHeight(0, 100), + 0, + ) +} + +// utilsCreateIBCDenom creates an IBC denom from the given channel and port identifiers and raw denom. +// NOTE: it's only used for test purpose and not safe for multiple hop denom. +func (suite *MiddlewareTestSuite) utilsCreateIBCDenom(destChan, destPort, baseDenom string) string { + trace := ibctransfertypes.DenomTrace{ + Path: fmt.Sprintf("%s/%s", destPort, destChan), + BaseDenom: baseDenom, + } + return trace.IBCDenom() +} + +func (suite *MiddlewareTestSuite) utilsMockAcknowledgement(success bool) []byte { + if !success { + ack := channeltypes.NewErrorAcknowledgement(errors.New("ics-20 error acknowledgement")) + return ack.Acknowledgement() + } + return channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() +} diff --git a/x/token/types/errors.go b/x/token/types/errors.go index aef50bd3..358361c3 100644 --- a/x/token/types/errors.go +++ b/x/token/types/errors.go @@ -14,9 +14,8 @@ var ( ErrUnexpectedEvent = errorsmod.Register(ModuleName, 8, "unexpected event") ErrABIPack = errorsmod.Register(ModuleName, 9, "contract ABI pack failed") ErrABIUnpack = errorsmod.Register(ModuleName, 10, "contract ABI unpack failed") - ErrEVMDenom = errorsmod.Register(ModuleName, 11, "EVM denomination registration") - ErrEVMCall = errorsmod.Register(ModuleName, 12, "EVM call unexpected error") - ErrTokenPairDisabled = errorsmod.Register(ModuleName, 13, "token pair is disabled") - ErrInvalidToken = errorsmod.Register(ModuleName, 14, "invalid token") - ErrInvalidDenom = errorsmod.Register(ModuleName, 15, "invalid denom") + ErrEVMCall = errorsmod.Register(ModuleName, 11, "EVM call unexpected error") + ErrTokenPairDisabled = errorsmod.Register(ModuleName, 12, "token pair is disabled") + ErrInvalidToken = errorsmod.Register(ModuleName, 13, "invalid token") + ErrInvalidDenom = errorsmod.Register(ModuleName, 14, "invalid denom") ) diff --git a/x/token/types/events.go b/x/token/types/events.go index a2bf7c64..58d54bf4 100644 --- a/x/token/types/events.go +++ b/x/token/types/events.go @@ -1,12 +1,8 @@ package types const ( - EventTypeTokenLock = "token_lock" - EventTypeTokenUnlock = "token_unlock" - EventTypeMint = "mint" EventTypeConvertCoin = "convert_coin" EventTypeConvertERC20 = "convert_erc20" - EventTypeBurn = "burn" EventTypeRegisterCoin = "register_coin" EventTypeRegisterERC20 = "register_erc20" EventTypeToggleTokenConversion = "toggle_token_conversion" // #nosec diff --git a/x/token/types/expected_keeper.go b/x/token/types/expected_keeper.go index 6443fe98..3c3aed45 100644 --- a/x/token/types/expected_keeper.go +++ b/x/token/types/expected_keeper.go @@ -7,13 +7,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" + "github.com/evmos/ethermint/x/evm/statedb" + evmtypes "github.com/evmos/ethermint/x/evm/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - - "github.com/evmos/ethermint/x/evm/statedb" - evmtypes "github.com/evmos/ethermint/x/evm/types" ) // AccountKeeper defines the expected interface needed to retrieve account info. diff --git a/x/token/types/genesis.pb.go b/x/token/types/genesis.pb.go index 4c18e8a1..59a98fc9 100644 --- a/x/token/types/genesis.pb.go +++ b/x/token/types/genesis.pb.go @@ -78,8 +78,8 @@ func (m *GenesisState) GetTokenPairs() []TokenPair { // Params defines the token module parameters. type Params struct { - EnableConvert bool `protobuf:"varint,1,opt,name=enable_convert,json=enableConvert,proto3" json:"enable_convert,omitempty"` - EnableEVMHook bool `protobuf:"varint,2,opt,name=enable_evm_hook,json=enableEvmHook,proto3" json:"enable_evm_hook,omitempty"` + EnableConversion bool `protobuf:"varint,1,opt,name=enable_conversion,json=enableConversion,proto3" json:"enable_conversion,omitempty"` + EnableEVMHook bool `protobuf:"varint,2,opt,name=enable_evm_hook,json=enableEvmHook,proto3" json:"enable_evm_hook,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -115,9 +115,9 @@ func (m *Params) XXX_DiscardUnknown() { var xxx_messageInfo_Params proto.InternalMessageInfo -func (m *Params) GetEnableConvert() bool { +func (m *Params) GetEnableConversion() bool { if m != nil { - return m.EnableConvert + return m.EnableConversion } return false } @@ -137,27 +137,27 @@ func init() { func init() { proto.RegisterFile("lorenzo/token/v1/genesis.proto", fileDescriptor_25246fea731fdeaa) } var fileDescriptor_25246fea731fdeaa = []byte{ - // 313 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcb, 0xc9, 0x2f, 0x4a, - 0xcd, 0xab, 0xca, 0xd7, 0x2f, 0xc9, 0xcf, 0x4e, 0xcd, 0xd3, 0x2f, 0x33, 0xd4, 0x4f, 0x4f, 0xcd, - 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x80, 0xca, 0xeb, 0x81, - 0xe5, 0xf5, 0xca, 0x0c, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0x92, 0xfa, 0x20, 0x16, 0x44, - 0x9d, 0x94, 0x0c, 0x86, 0x39, 0x10, 0x0d, 0x60, 0x59, 0xa5, 0x2e, 0x46, 0x2e, 0x1e, 0x77, 0x88, - 0xb9, 0xc1, 0x25, 0x89, 0x25, 0xa9, 0x42, 0x66, 0x5c, 0x6c, 0x05, 0x89, 0x45, 0x89, 0xb9, 0xc5, - 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xdc, 0x46, 0x12, 0x7a, 0xe8, 0xf6, 0xe8, 0x05, 0x80, 0xe5, 0x9d, - 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0xaa, 0x16, 0x72, 0xe2, 0xe2, 0x06, 0x2b, 0x88, 0x2f, - 0x48, 0xcc, 0x2c, 0x2a, 0x96, 0x60, 0x52, 0x60, 0xd6, 0xe0, 0x36, 0x92, 0xc6, 0xd4, 0x1c, 0x02, - 0x62, 0x04, 0x24, 0x66, 0x16, 0x41, 0xf5, 0x73, 0x95, 0xc0, 0x04, 0x8a, 0x95, 0xb2, 0xb8, 0xd8, - 0x20, 0x66, 0x0b, 0xa9, 0x72, 0xf1, 0xa5, 0xe6, 0x25, 0x26, 0xe5, 0xa4, 0xc6, 0x27, 0xe7, 0xe7, - 0x95, 0xa5, 0x16, 0x95, 0x80, 0x5d, 0xc3, 0x11, 0xc4, 0x0b, 0x11, 0x75, 0x86, 0x08, 0x0a, 0x59, - 0x72, 0xf1, 0x43, 0x95, 0xa5, 0x96, 0xe5, 0xc6, 0x67, 0xe4, 0xe7, 0x67, 0x4b, 0x30, 0x81, 0xd4, - 0x39, 0x09, 0x3e, 0xba, 0x27, 0xcf, 0xeb, 0x0a, 0x96, 0x72, 0x0d, 0xf3, 0xf5, 0xc8, 0xcf, 0xcf, - 0x86, 0x69, 0x75, 0x2d, 0xcb, 0x05, 0x71, 0x9d, 0xbc, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, - 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, - 0x58, 0x8e, 0x21, 0xca, 0x30, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0xdf, - 0x07, 0xe2, 0x7c, 0xdd, 0x00, 0x50, 0x60, 0x25, 0xe7, 0xe7, 0xe8, 0xc3, 0x02, 0xb3, 0x02, 0x1a, - 0x9c, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0xc0, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, - 0xff, 0x81, 0x10, 0xc8, 0x51, 0xb4, 0x01, 0x00, 0x00, + // 316 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x41, 0x4b, 0xc3, 0x30, + 0x14, 0xc7, 0xdb, 0x29, 0x43, 0x32, 0x87, 0x5b, 0xf1, 0x50, 0xa6, 0x64, 0x63, 0xa7, 0x81, 0x98, + 0xb0, 0x09, 0x82, 0xd7, 0xca, 0x50, 0x50, 0x61, 0x4c, 0xf1, 0xe0, 0x65, 0x64, 0x23, 0x74, 0x65, + 0x6b, 0x5e, 0x49, 0x62, 0x51, 0x3f, 0x82, 0x27, 0x3f, 0xd6, 0x8e, 0x3b, 0x7a, 0x1a, 0xd2, 0x7e, + 0x11, 0x69, 0xd2, 0x79, 0x70, 0xb7, 0x97, 0xf7, 0xfb, 0xbd, 0x97, 0xe4, 0x8f, 0xf0, 0x12, 0x24, + 0x17, 0x1f, 0x40, 0x35, 0x2c, 0xb8, 0xa0, 0x69, 0x9f, 0x86, 0x5c, 0x70, 0x15, 0x29, 0x92, 0x48, + 0xd0, 0xe0, 0x35, 0x4a, 0x4e, 0x0c, 0x27, 0x69, 0xbf, 0x75, 0x1c, 0x42, 0x08, 0x06, 0xd2, 0xa2, + 0xb2, 0x5e, 0xeb, 0x74, 0x67, 0x8f, 0x1d, 0x30, 0xb4, 0xfb, 0xe9, 0xa2, 0xc3, 0x1b, 0xbb, 0xf7, + 0x51, 0x33, 0xcd, 0xbd, 0x4b, 0x54, 0x4d, 0x98, 0x64, 0xb1, 0xf2, 0xdd, 0x8e, 0xdb, 0xab, 0x0d, + 0x7c, 0xf2, 0xff, 0x1e, 0x32, 0x32, 0x3c, 0xd8, 0x5f, 0x6d, 0xda, 0xce, 0xb8, 0xb4, 0xbd, 0x00, + 0xd5, 0x8c, 0x30, 0x49, 0x58, 0x24, 0x95, 0x5f, 0xe9, 0xec, 0xf5, 0x6a, 0x83, 0x93, 0xdd, 0xe1, + 0xa7, 0xa2, 0x18, 0xb1, 0x48, 0x96, 0xf3, 0x48, 0x6f, 0x1b, 0xaa, 0x9b, 0xa0, 0xaa, 0xdd, 0xed, + 0x9d, 0xa1, 0x26, 0x17, 0x6c, 0xba, 0xe4, 0x93, 0x19, 0x88, 0x94, 0x4b, 0x15, 0x81, 0x30, 0x0f, + 0x3a, 0x18, 0x37, 0x2c, 0xb8, 0xfe, 0xeb, 0x7b, 0x57, 0xe8, 0xa8, 0x94, 0x79, 0x1a, 0x4f, 0xe6, + 0x00, 0x0b, 0xbf, 0x52, 0xa8, 0x41, 0x33, 0xdb, 0xb4, 0xeb, 0x43, 0x83, 0x86, 0xcf, 0x0f, 0xb7, + 0x00, 0x8b, 0x71, 0xdd, 0x9a, 0xc3, 0x34, 0x2e, 0x8e, 0xc1, 0xdd, 0x2a, 0xc3, 0xee, 0x3a, 0xc3, + 0xee, 0x4f, 0x86, 0xdd, 0xaf, 0x1c, 0x3b, 0xeb, 0x1c, 0x3b, 0xdf, 0x39, 0x76, 0x5e, 0xfa, 0x61, + 0xa4, 0xe7, 0xaf, 0x53, 0x32, 0x83, 0x98, 0xde, 0xdb, 0x4f, 0x9c, 0x8f, 0x8a, 0xc8, 0x66, 0xb0, + 0xa4, 0xdb, 0x48, 0xdf, 0xca, 0x50, 0xf5, 0x7b, 0xc2, 0xd5, 0xb4, 0x6a, 0x22, 0xbd, 0xf8, 0x0d, + 0x00, 0x00, 0xff, 0xff, 0xc4, 0x54, 0xbc, 0x22, 0xba, 0x01, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -237,9 +237,9 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x10 } - if m.EnableConvert { + if m.EnableConversion { i-- - if m.EnableConvert { + if m.EnableConversion { dAtA[i] = 1 } else { dAtA[i] = 0 @@ -284,7 +284,7 @@ func (m *Params) Size() (n int) { } var l int _ = l - if m.EnableConvert { + if m.EnableConversion { n += 2 } if m.EnableEVMHook { @@ -447,7 +447,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field EnableConvert", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field EnableConversion", wireType) } var v int for shift := uint(0); ; shift += 7 { @@ -464,7 +464,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } - m.EnableConvert = bool(v != 0) + m.EnableConversion = bool(v != 0) case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field EnableEVMHook", wireType) diff --git a/x/token/types/msg_test.go b/x/token/types/msg_test.go index aebf82e2..1d326b73 100644 --- a/x/token/types/msg_test.go +++ b/x/token/types/msg_test.go @@ -3,9 +3,10 @@ package types_test import ( "testing" + "github.com/stretchr/testify/suite" + sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/stretchr/testify/suite" "github.com/Lorenzo-Protocol/lorenzo/x/token/types" ) @@ -385,8 +386,8 @@ func (suite *MsgTestSuite) TestMsgUpdateParams() { msg: &types.MsgUpdateParams{ Authority: "invalid authority", Params: types.Params{ - EnableConvert: false, - EnableEVMHook: false, + EnableConversion: false, + EnableEVMHook: false, }, }, expPass: false, @@ -396,8 +397,8 @@ func (suite *MsgTestSuite) TestMsgUpdateParams() { msg: &types.MsgUpdateParams{ Authority: "cosmos1qperwt9wrnkg5k9e5gzfgjppzpqhyav5j24d66", Params: types.Params{ - EnableConvert: false, - EnableEVMHook: false, + EnableConversion: false, + EnableEVMHook: false, }, }, expPass: true, diff --git a/x/token/types/params.go b/x/token/types/params.go index 534ad922..7cd26078 100644 --- a/x/token/types/params.go +++ b/x/token/types/params.go @@ -1,17 +1,17 @@ package types // NewParams create Params object for token module. -func NewParams(enableConvert, enableEVMHook bool) Params { +func NewParams(enableConversion, enableEVMHook bool) Params { return Params{ - EnableConvert: enableConvert, - EnableEVMHook: enableEVMHook, + EnableConversion: enableConversion, + EnableEVMHook: enableEVMHook, } } // DefaultParams returns default Params for token module. func DefaultParams() Params { return Params{ - EnableConvert: true, - EnableEVMHook: true, + EnableConversion: true, + EnableEVMHook: true, } } diff --git a/x/token/types/token.pb.go b/x/token/types/token.pb.go index ab944769..558cc68a 100644 --- a/x/token/types/token.pb.go +++ b/x/token/types/token.pb.go @@ -24,37 +24,37 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// Ownership defines the ownership of an erc20 contract, if ownership belongs to: -// - token module: the token origin is sdk coin. -// - external account: the token origin is erc20 contract. -type Ownership int32 +// Source defines the source type of token asset, if source is: +// - module: token origin is sdk module; +// - contract: token origin is erc20 contract; +type Source int32 const ( - // undefined owner - OWNER_UNDEFINED Ownership = 0 - // contract owned by convert module - OWNER_MODULE Ownership = 1 - // contract owned by external account - OWNER_EXTERNAL Ownership = 2 + // undefined source + OWNER_UNDEFINED Source = 0 + // token source is module + OWNER_MODULE Source = 1 + // token source is erc20 contract + OWNER_CONTRACT Source = 2 ) -var Ownership_name = map[int32]string{ +var Source_name = map[int32]string{ 0: "OWNER_UNDEFINED", 1: "OWNER_MODULE", - 2: "OWNER_EXTERNAL", + 2: "OWNER_CONTRACT", } -var Ownership_value = map[string]int32{ +var Source_value = map[string]int32{ "OWNER_UNDEFINED": 0, "OWNER_MODULE": 1, - "OWNER_EXTERNAL": 2, + "OWNER_CONTRACT": 2, } -func (x Ownership) String() string { - return proto.EnumName(Ownership_name, int32(x)) +func (x Source) String() string { + return proto.EnumName(Source_name, int32(x)) } -func (Ownership) EnumDescriptor() ([]byte, []int) { +func (Source) EnumDescriptor() ([]byte, []int) { return fileDescriptor_1095540cf3b10f35, []int{0} } @@ -66,8 +66,8 @@ type TokenPair struct { Denom string `protobuf:"bytes,2,opt,name=denom,proto3" json:"denom,omitempty"` // allows for token conversion Enabled bool `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"` - // ownership of the contract - Ownership Ownership `protobuf:"varint,4,opt,name=ownership,proto3,enum=lorenzo.token.v1.Ownership" json:"ownership,omitempty"` + // source of token asset + Source Source `protobuf:"varint,4,opt,name=source,proto3,enum=lorenzo.token.v1.Source" json:"source,omitempty"` } func (m *TokenPair) Reset() { *m = TokenPair{} } @@ -124,44 +124,44 @@ func (m *TokenPair) GetEnabled() bool { return false } -func (m *TokenPair) GetOwnership() Ownership { +func (m *TokenPair) GetSource() Source { if m != nil { - return m.Ownership + return m.Source } return OWNER_UNDEFINED } func init() { - proto.RegisterEnum("lorenzo.token.v1.Ownership", Ownership_name, Ownership_value) + proto.RegisterEnum("lorenzo.token.v1.Source", Source_name, Source_value) proto.RegisterType((*TokenPair)(nil), "lorenzo.token.v1.TokenPair") } func init() { proto.RegisterFile("lorenzo/token/v1/token.proto", fileDescriptor_1095540cf3b10f35) } var fileDescriptor_1095540cf3b10f35 = []byte{ - // 344 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xc9, 0xc9, 0x2f, 0x4a, - 0xcd, 0xab, 0xca, 0xd7, 0x2f, 0xc9, 0xcf, 0x4e, 0xcd, 0xd3, 0x2f, 0x33, 0x84, 0x30, 0xf4, 0x0a, - 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x04, 0xa0, 0xb2, 0x7a, 0x10, 0xc1, 0x32, 0x43, 0x29, 0xb9, 0xe4, - 0xfc, 0xe2, 0xdc, 0xfc, 0x62, 0xfd, 0xa4, 0xc4, 0xbc, 0x6c, 0xfd, 0x32, 0xc3, 0xa4, 0xd4, 0x92, - 0x44, 0x43, 0x30, 0x07, 0xa2, 0x43, 0x4a, 0x24, 0x3d, 0x3f, 0x3d, 0x1f, 0xcc, 0xd4, 0x07, 0xb1, - 0x20, 0xa2, 0x4a, 0xcb, 0x19, 0xb9, 0x38, 0x43, 0x40, 0x46, 0x04, 0x24, 0x66, 0x16, 0x09, 0x69, - 0x72, 0x09, 0x24, 0xe7, 0xe7, 0x95, 0x14, 0x25, 0x26, 0x97, 0xc4, 0x27, 0xa6, 0xa4, 0x14, 0xa5, - 0x16, 0x17, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0xf1, 0xc3, 0xc4, 0x1d, 0x21, 0xc2, 0x42, - 0x22, 0x5c, 0xac, 0x29, 0xa9, 0x79, 0xf9, 0xb9, 0x12, 0x4c, 0x60, 0x79, 0x08, 0x47, 0x48, 0x82, - 0x8b, 0x3d, 0x35, 0x2f, 0x31, 0x29, 0x27, 0x35, 0x45, 0x82, 0x59, 0x81, 0x51, 0x83, 0x23, 0x08, - 0xc6, 0x15, 0xb2, 0xe4, 0xe2, 0xcc, 0x2f, 0xcf, 0x4b, 0x2d, 0x2a, 0xce, 0xc8, 0x2c, 0x90, 0x60, - 0x51, 0x60, 0xd4, 0xe0, 0x33, 0x92, 0xd6, 0x43, 0xf7, 0x84, 0x9e, 0x3f, 0x4c, 0x49, 0x10, 0x42, - 0xb5, 0x15, 0xcb, 0x8b, 0x05, 0xf2, 0x8c, 0x5a, 0x3e, 0x5c, 0x9c, 0x70, 0x59, 0x21, 0x61, 0x2e, - 0x7e, 0xff, 0x70, 0x3f, 0xd7, 0xa0, 0xf8, 0x50, 0x3f, 0x17, 0x57, 0x37, 0x4f, 0x3f, 0x57, 0x17, - 0x01, 0x06, 0x21, 0x01, 0x2e, 0x1e, 0x88, 0xa0, 0xaf, 0xbf, 0x4b, 0xa8, 0x8f, 0xab, 0x00, 0xa3, - 0x90, 0x10, 0x17, 0x1f, 0x44, 0xc4, 0x35, 0x22, 0xc4, 0x35, 0xc8, 0xcf, 0xd1, 0x47, 0x80, 0x49, - 0x8a, 0xa5, 0x63, 0xb1, 0x1c, 0x83, 0x93, 0xf7, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, - 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, - 0x31, 0x44, 0x19, 0xa6, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0xfb, 0x40, - 0xdc, 0xa7, 0x1b, 0x00, 0x0a, 0xab, 0xe4, 0xfc, 0x1c, 0x7d, 0x58, 0x9c, 0x54, 0x40, 0x63, 0xa5, - 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0x1c, 0x96, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x71, 0xa4, 0xd7, 0x19, 0xb3, 0x01, 0x00, 0x00, + // 340 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x91, 0x4f, 0x4b, 0x02, 0x41, + 0x18, 0xc6, 0x77, 0xcc, 0x2c, 0x87, 0xd0, 0x65, 0xf2, 0xb0, 0x48, 0x4c, 0xd2, 0xc9, 0x82, 0x76, + 0xda, 0xba, 0x75, 0x33, 0xdd, 0x40, 0xb2, 0x55, 0x36, 0x25, 0xe8, 0x22, 0xfb, 0x67, 0x30, 0x51, + 0xf7, 0x95, 0x9d, 0x51, 0xaa, 0x4f, 0xd0, 0xb1, 0x4f, 0x10, 0x41, 0x5f, 0xa6, 0xa3, 0xc7, 0x8e, + 0xa1, 0x97, 0x3e, 0x46, 0xb8, 0xe3, 0x5e, 0xba, 0x3d, 0xcf, 0xef, 0x99, 0x19, 0xe6, 0x7d, 0x1f, + 0x7c, 0x30, 0x86, 0x98, 0x47, 0x2f, 0xc0, 0x24, 0x8c, 0x78, 0xc4, 0xe6, 0x96, 0x12, 0xe6, 0x34, + 0x06, 0x09, 0x44, 0xdf, 0xa4, 0xa6, 0x82, 0x73, 0xab, 0x4c, 0x03, 0x10, 0x13, 0x10, 0xcc, 0xf7, + 0xa2, 0x11, 0x9b, 0x5b, 0x3e, 0x97, 0x9e, 0x95, 0x18, 0x75, 0xa3, 0x5c, 0x1a, 0xc0, 0x00, 0x12, + 0xc9, 0xd6, 0x4a, 0xd1, 0xa3, 0x77, 0x84, 0xf3, 0xdd, 0xf5, 0x13, 0x1d, 0x6f, 0x18, 0x93, 0x63, + 0xac, 0x07, 0x10, 0xc9, 0xd8, 0x0b, 0x64, 0xdf, 0x0b, 0xc3, 0x98, 0x0b, 0x61, 0xa0, 0x0a, 0xaa, + 0xe6, 0xdd, 0x62, 0xca, 0x6b, 0x0a, 0x93, 0x12, 0xde, 0x0e, 0x79, 0x04, 0x13, 0x23, 0x93, 0xe4, + 0xca, 0x10, 0x03, 0xef, 0xf0, 0xc8, 0xf3, 0xc7, 0x3c, 0x34, 0xb6, 0x2a, 0xa8, 0xba, 0xeb, 0xa6, + 0x96, 0x9c, 0xe1, 0x9c, 0x80, 0x59, 0x1c, 0x70, 0x23, 0x5b, 0x41, 0xd5, 0xc2, 0xb9, 0x61, 0xfe, + 0x9f, 0xc0, 0xbc, 0x4b, 0x72, 0x77, 0x73, 0xee, 0x32, 0xfb, 0xfb, 0x71, 0x88, 0x4e, 0x9a, 0x38, + 0xa7, 0x38, 0xd9, 0xc7, 0xc5, 0xf6, 0xbd, 0x63, 0xbb, 0xfd, 0x9e, 0xd3, 0xb0, 0xaf, 0x9b, 0x8e, + 0xdd, 0xd0, 0x35, 0xa2, 0xe3, 0x3d, 0x05, 0x6f, 0xdb, 0x8d, 0x5e, 0xcb, 0xd6, 0x11, 0x21, 0xb8, + 0xa0, 0x48, 0xbd, 0xed, 0x74, 0xdd, 0x5a, 0xbd, 0xab, 0x67, 0xca, 0xd9, 0xd7, 0x4f, 0xaa, 0x5d, + 0xdd, 0x7c, 0x2d, 0x29, 0x5a, 0x2c, 0x29, 0xfa, 0x59, 0x52, 0xf4, 0xb6, 0xa2, 0xda, 0x62, 0x45, + 0xb5, 0xef, 0x15, 0xd5, 0x1e, 0xac, 0xc1, 0x50, 0x3e, 0xce, 0x7c, 0x33, 0x80, 0x09, 0x6b, 0xa9, + 0x6f, 0x9d, 0x76, 0xd6, 0xfb, 0x09, 0x60, 0xcc, 0xd2, 0x1e, 0x9e, 0x36, 0x4d, 0xc8, 0xe7, 0x29, + 0x17, 0x7e, 0x2e, 0xd9, 0xdf, 0xc5, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1d, 0x39, 0x77, 0x0a, + 0xa7, 0x01, 0x00, 0x00, } func (this *TokenPair) Equal(that interface{}) bool { @@ -192,7 +192,7 @@ func (this *TokenPair) Equal(that interface{}) bool { if this.Enabled != that1.Enabled { return false } - if this.Ownership != that1.Ownership { + if this.Source != that1.Source { return false } return true @@ -217,8 +217,8 @@ func (m *TokenPair) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.Ownership != 0 { - i = encodeVarintToken(dAtA, i, uint64(m.Ownership)) + if m.Source != 0 { + i = encodeVarintToken(dAtA, i, uint64(m.Source)) i-- dAtA[i] = 0x20 } @@ -277,8 +277,8 @@ func (m *TokenPair) Size() (n int) { if m.Enabled { n += 2 } - if m.Ownership != 0 { - n += 1 + sovToken(uint64(m.Ownership)) + if m.Source != 0 { + n += 1 + sovToken(uint64(m.Source)) } return n } @@ -404,9 +404,9 @@ func (m *TokenPair) Unmarshal(dAtA []byte) error { m.Enabled = bool(v != 0) case 4: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Ownership", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Source", wireType) } - m.Ownership = 0 + m.Source = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowToken @@ -416,7 +416,7 @@ func (m *TokenPair) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Ownership |= Ownership(b&0x7F) << shift + m.Source |= Source(b&0x7F) << shift if b < 0x80 { break } diff --git a/x/token/types/token_pair.go b/x/token/types/token_pair.go index 2847d63d..14a7ccb6 100644 --- a/x/token/types/token_pair.go +++ b/x/token/types/token_pair.go @@ -3,19 +3,18 @@ package types import ( "fmt" - "github.com/ethereum/go-ethereum/common" - "github.com/cometbft/cometbft/crypto/tmhash" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" ) // NewTokenPair creates a new TokenPair -func NewTokenPair(erc20Addr common.Address, denom string, ownership Ownership) TokenPair { +func NewTokenPair(erc20Addr common.Address, denom string, source Source) TokenPair { return TokenPair{ ContractAddress: erc20Addr.String(), Denom: denom, Enabled: true, - Ownership: ownership, + Source: source, } } @@ -29,8 +28,8 @@ func (tp *TokenPair) Validate() error { return fmt.Errorf("invalid contract address: %s", tp.ContractAddress) } - if tp.Ownership != OWNER_MODULE && tp.Ownership != OWNER_EXTERNAL { - return fmt.Errorf("invalid ownership: %s", tp.Ownership) + if tp.Source != OWNER_MODULE && tp.Source != OWNER_CONTRACT { + return fmt.Errorf("invalid token source: %s", tp.Source) } return nil @@ -44,12 +43,12 @@ func (tp *TokenPair) GetID() []byte { // IsNativeCoin checks if the token is sdk coin originated func (tp *TokenPair) IsNativeCoin() bool { - return tp.Ownership == OWNER_MODULE + return tp.Source == OWNER_MODULE } // IsNativeERC20 checks if the token is erc20 contract originated func (tp *TokenPair) IsNativeERC20() bool { - return tp.Ownership == OWNER_EXTERNAL + return tp.Source == OWNER_CONTRACT } // GetERC20ContractAddress return the common address