diff --git a/app/app.go b/app/app.go index a7944526..e261158a 100644 --- a/app/app.go +++ b/app/app.go @@ -252,8 +252,10 @@ var ( // MigalooApp extended ABCI application type MigalooApp struct { *baseapp.BaseApp - legacyAmino *codec.LegacyAmino - appCodec codec.Codec + legacyAmino *codec.LegacyAmino + appCodec codec.Codec + txConfig client.TxConfig + interfaceRegistry types.InterfaceRegistry invCheckPeriod uint @@ -342,8 +344,9 @@ func NewMigalooApp( wasmOpts []wasmkeeper.Option, baseAppOptions ...func(*baseapp.BaseApp), ) *MigalooApp { - appCodec, legacyAmino, txConfig := encodingConfig.Codec, encodingConfig.Amino, encodingConfig.TxConfig + appCodec, legacyAmino := encodingConfig.Codec, encodingConfig.Amino interfaceRegistry := encodingConfig.InterfaceRegistry + txConfig := encodingConfig.TxConfig bApp := baseapp.NewBaseApp(appName, logger, db, encodingConfig.TxConfig.TxDecoder(), baseAppOptions...) bApp.SetCommitMultiStoreTracer(traceStore) @@ -368,6 +371,7 @@ func NewMigalooApp( BaseApp: bApp, legacyAmino: legacyAmino, appCodec: appCodec, + txConfig: txConfig, interfaceRegistry: interfaceRegistry, invCheckPeriod: invCheckPeriod, keys: keys, @@ -1112,6 +1116,11 @@ func (app *MigalooApp) AppCodec() codec.Codec { return app.appCodec } +// TxConfig returns MigalooApp's TxConfig +func (app *MigalooApp) TxConfig() client.TxConfig { + return app.txConfig +} + // RegisterSwaggerAPI registers swagger route with API Server func RegisterSwaggerAPI(rtr *mux.Router) { cosmosStatikFs, err := fs.New() diff --git a/app/test_helpers.go b/app/test_helpers.go index f54fe8ee..dd68ea14 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -6,6 +6,8 @@ import ( "testing" "time" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + "cosmossdk.io/math" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/client" @@ -18,7 +20,6 @@ import ( upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - config "github.com/White-Whale-Defi-Platform/migaloo-chain/v4/app/params" dbm "github.com/cometbft/cometbft-db" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/libs/log" @@ -108,10 +109,10 @@ func SetupApp(t *testing.T) *MigalooApp { acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) balance := banktypes.Balance{ Address: acc.GetAddress().String(), - Coins: sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewInt(100000000000000))), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000000000000))), } - app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, balance) + app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, "", nil, balance) return app } @@ -120,21 +121,28 @@ func SetupApp(t *testing.T) *MigalooApp { // 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 app from first genesis // account. A Nop logger is set in app. -func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *MigalooApp { +func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, opts []wasmkeeper.Option, balances ...banktypes.Balance) *MigalooApp { t.Helper() - migalooApp, genesisState := setup(true) + migalooApp, genesisState := setup(true, chainID, opts...) genesisState = genesisStateWithValSet(t, migalooApp, genesisState, valSet, genAccs, balances...) + consensusParams := simtestutil.DefaultConsensusParams + consensusParams.Block.MaxGas = 100 * simtestutil.DefaultGenTxGas + stateBytes, err := json.MarshalIndent(genesisState, "", " ") require.NoError(t, err) + if chainID == "" { + chainID = SimAppChainID + } + // init chain will set the validator set and initialize the genesis accounts migalooApp.InitChain( abci.RequestInitChain{ - ChainId: SimAppChainID, + ChainId: chainID, Validators: []abci.ValidatorUpdate{}, - ConsensusParams: DefaultConsensusParams, + ConsensusParams: consensusParams, AppStateBytes: stateBytes, }, ) @@ -142,7 +150,7 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs // commit genesis changes migalooApp.Commit() migalooApp.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{ - ChainID: SimAppChainID, + ChainID: chainID, Height: migalooApp.LastBlockHeight() + 1, AppHash: migalooApp.LastCommitID().Hash, ValidatorsHash: valSet.Hash(), @@ -152,10 +160,10 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs return migalooApp } -func setup(withGenesis bool, opts ...wasmkeeper.Option) (*MigalooApp, GenesisState) { +func setup(withGenesis bool, chainID string, opts ...wasmkeeper.Option) (*MigalooApp, GenesisState) { db := dbm.NewMemDB() migalooApp := NewMigalooApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, - DefaultNodeHome, 5, MakeEncodingConfig(), EmptyBaseAppOptions{}, opts) + DefaultNodeHome, 5, MakeEncodingConfig(), EmptyBaseAppOptions{}, opts, baseapp.SetChainID(chainID)) if withGenesis { return migalooApp, NewDefaultGenesisState() @@ -206,7 +214,7 @@ func genesisStateWithValSet(t *testing.T, defaultStParams.MaxValidators, defaultStParams.MaxEntries, defaultStParams.HistoricalEntries, - config.BaseDenom, + sdk.DefaultBondDenom, defaultStParams.MinCommissionRate, ) @@ -214,23 +222,28 @@ func genesisStateWithValSet(t *testing.T, stakingGenesis := stakingtypes.NewGenesisState(stParams, validators, delegations) genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis) - totalSupply := sdk.NewCoins() - for _, b := range balances { - // add genesis acc tokens to total supply - totalSupply = totalSupply.Add(b.Coins...) - } - - for range delegations { - // add delegated tokens to total supply - totalSupply = totalSupply.Add(sdk.NewCoin(config.BaseDenom, bondAmt)) + signingInfos := make([]slashingtypes.SigningInfo, len(valSet.Validators)) + for i, val := range valSet.Validators { + signingInfos[i] = slashingtypes.SigningInfo{ + Address: sdk.ConsAddress(val.Address).String(), + ValidatorSigningInfo: slashingtypes.ValidatorSigningInfo{}, + } } + slashingGenesis := slashingtypes.NewGenesisState(slashingtypes.DefaultParams(), signingInfos, nil) + genesisState[slashingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(slashingGenesis) // add bonded amount to bonded pool module account balances = append(balances, banktypes.Balance{ Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), - Coins: sdk.Coins{sdk.NewCoin(config.BaseDenom, bondAmt)}, + Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt.MulRaw(int64(len(valSet.Validators))))}, }) + totalSupply := sdk.NewCoins() + for _, b := range balances { + // add genesis acc tokens to total supply + totalSupply = totalSupply.Add(b.Coins...) + } + // update total supply bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}, banktypes.DefaultGenesisState().SendEnabled) @@ -386,13 +399,13 @@ func (s *KeeperTestHelper) AllocateRewardsToValidator(valAddr sdk.ValAddress, re s.Require().True(found) // allocate reward tokens to distribution module - coins := sdk.Coins{sdk.NewCoin(config.BaseDenom, rewardAmt)} + coins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, rewardAmt)} err := banktestutil.FundModuleAccount(s.App.BankKeeper, s.Ctx, distrtypes.ModuleName, coins) s.Require().NoError(err) // allocate rewards to validator s.Ctx = s.Ctx.WithBlockHeight(s.Ctx.BlockHeight() + 1) - decTokens := sdk.DecCoins{{Denom: config.BaseDenom, Amount: sdk.NewDec(20000)}} + decTokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDec(20000)}} s.App.DistrKeeper.AllocateTokensToValidator(s.Ctx, validator, decTokens) } diff --git a/app/test_support.go b/app/test_support.go new file mode 100644 index 00000000..33870d82 --- /dev/null +++ b/app/test_support.go @@ -0,0 +1,41 @@ +package app + +import ( + ibckeeper "github.com/cosmos/ibc-go/v7/modules/core/keeper" + + "github.com/cosmos/cosmos-sdk/baseapp" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" +) + +func (app *MigalooApp) GetIBCKeeper() *ibckeeper.Keeper { + return app.IBCKeeper +} + +func (app *MigalooApp) GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper { + return app.ScopedIBCKeeper +} + +func (app *MigalooApp) GetBaseApp() *baseapp.BaseApp { + return app.BaseApp +} + +func (app *MigalooApp) GetBankKeeper() bankkeeper.Keeper { + return app.BankKeeper +} + +func (app *MigalooApp) GetStakingKeeper() *stakingkeeper.Keeper { + return app.StakingKeeper +} + +func (app *MigalooApp) GetAccountKeeper() authkeeper.AccountKeeper { + return app.AccountKeeper +} + +func (app *MigalooApp) GetWasmKeeper() wasmkeeper.Keeper { + return app.WasmKeeper +} diff --git a/go.mod b/go.mod index 7cdb84c6..2f9da87b 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( require ( cosmossdk.io/errors v1.0.1 + github.com/CosmWasm/wasmvm v1.5.2 github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3 github.com/cosmos/ibc-apps/modules/async-icq/v7 v7.1.1 github.com/golang/protobuf v1.5.4 @@ -68,7 +69,6 @@ require ( github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect - github.com/CosmWasm/wasmvm v1.5.2 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go v1.44.203 // indirect diff --git a/tests/e2e/gov_test.go b/tests/e2e/gov_test.go new file mode 100644 index 00000000..e340d381 --- /dev/null +++ b/tests/e2e/gov_test.go @@ -0,0 +1,139 @@ +package e2e_test + +import ( + "testing" + "time" + + "github.com/White-Whale-Defi-Platform/migaloo-chain/v4/tests/e2e" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + + "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + "github.com/White-Whale-Defi-Platform/migaloo-chain/v4/app" +) + +func TestGovVoteByContract(t *testing.T) { + // Given a contract with delegation + // And a gov proposal + // When the contract sends a vote for the proposal + // Then the vote is taken into account + + coord := ibctesting.NewCoordinatorX(t, 1, e2e.DefaultMigalooAppFactory) + chain := coord.GetChain(ibctesting.GetChainID(1)) + contractAddr := e2e.InstantiateReflectContract(t, chain) + chain.Fund(contractAddr, sdk.NewIntFromUint64(1_000_000_000)) + // a contract with a high delegation amount + delegateMsg := wasmvmtypes.CosmosMsg{ + Staking: &wasmvmtypes.StakingMsg{ + Delegate: &wasmvmtypes.DelegateMsg{ + Validator: sdk.ValAddress(chain.Vals.Validators[0].Address).String(), + Amount: wasmvmtypes.Coin{ + Denom: sdk.DefaultBondDenom, + Amount: "1000000000", + }, + }, + }, + } + e2e.MustExecViaReflectContract(t, chain, contractAddr, delegateMsg) + + signer := chain.SenderAccount.GetAddress().String() + app := chain.App.(*app.MigalooApp) + govKeeper, accountKeeper := app.GovKeeper, app.AccountKeeper + communityPoolBalance := chain.Balance(accountKeeper.GetModuleAccount(chain.GetContext(), distributiontypes.ModuleName).GetAddress(), sdk.DefaultBondDenom) + require.False(t, communityPoolBalance.IsZero()) + + initialDeposit := govKeeper.GetParams(chain.GetContext()).MinDeposit + govAcctAddr := govKeeper.GetGovernanceAccount(chain.GetContext()).GetAddress() + + specs := map[string]struct { + vote *wasmvmtypes.VoteMsg + expPass bool + }{ + "yes": { + vote: &wasmvmtypes.VoteMsg{ + Vote: wasmvmtypes.Yes, + }, + expPass: true, + }, + "no": { + vote: &wasmvmtypes.VoteMsg{ + Vote: wasmvmtypes.No, + }, + expPass: false, + }, + "abstain": { + vote: &wasmvmtypes.VoteMsg{ + Vote: wasmvmtypes.Abstain, + }, + expPass: true, + }, + "no with veto": { + vote: &wasmvmtypes.VoteMsg{ + Vote: wasmvmtypes.NoWithVeto, + }, + expPass: false, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + // given a unique recipient + recipientAddr := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address().Bytes()) + // and a new proposal + payloadMsg := &distributiontypes.MsgCommunityPoolSpend{ + Authority: govAcctAddr.String(), + Recipient: recipientAddr.String(), + Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.OneInt())), + } + msg, err := v1.NewMsgSubmitProposal( + []sdk.Msg{payloadMsg}, + initialDeposit, + signer, + "", + "my proposal", + "testing", + ) + require.NoError(t, err) + rsp, gotErr := chain.SendMsgs(msg) + require.NoError(t, gotErr) + require.Len(t, rsp.MsgResponses, 1) + got, ok := rsp.MsgResponses[0].GetCachedValue().(*v1.MsgSubmitProposalResponse) + require.True(t, ok) + propID := got.ProposalId + + // with other delegators voted yes + _, err = chain.SendMsgs(v1.NewMsgVote(chain.SenderAccount.GetAddress(), propID, v1.VoteOption_VOTE_OPTION_YES, "")) + require.NoError(t, err) + + // when contract votes + spec.vote.ProposalId = propID + voteMsg := wasmvmtypes.CosmosMsg{ + Gov: &wasmvmtypes.GovMsg{ + Vote: spec.vote, + }, + } + e2e.MustExecViaReflectContract(t, chain, contractAddr, voteMsg) + + // then proposal executed after voting period + proposal, ok := govKeeper.GetProposal(chain.GetContext(), propID) + require.True(t, ok) + coord.IncrementTimeBy(proposal.VotingEndTime.Sub(chain.GetContext().BlockTime()) + time.Minute) + coord.CommitBlock(chain) + + // and recipient balance updated + recipientBalance := chain.Balance(recipientAddr, sdk.DefaultBondDenom) + if !spec.expPass { + assert.True(t, recipientBalance.IsZero()) + return + } + expBalanceAmount := sdk.NewCoin(sdk.DefaultBondDenom, sdk.OneInt()) + assert.Equal(t, expBalanceAmount.String(), recipientBalance.String()) + }) + } +} diff --git a/tests/e2e/grants_test.go b/tests/e2e/grants_test.go new file mode 100644 index 00000000..083dce24 --- /dev/null +++ b/tests/e2e/grants_test.go @@ -0,0 +1,252 @@ +package e2e_test + +import ( + "fmt" + "os" + "testing" + "time" + + wasmvm "github.com/CosmWasm/wasmvm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + errorsmod "cosmossdk.io/errors" + + "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" + "github.com/cosmos/cosmos-sdk/x/authz" + + "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + "github.com/CosmWasm/wasmd/x/wasm/types" + "github.com/White-Whale-Defi-Platform/migaloo-chain/v4/tests/e2e" +) + +func TestGrants(t *testing.T) { + // Given a contract by address A + // And a grant for address B by A created + // When B sends an execute with tokens from A + // Then the grant is executed as defined + // And + // - balance A reduced (on success) + // - balance B not touched + + coord := ibctesting.NewCoordinatorX(t, 1, e2e.DefaultMigalooAppFactory) + chain := coord.GetChain(ibctesting.GetChainID(1)) + contractAddr := e2e.InstantiateReflectContract(t, chain) + require.NotEmpty(t, contractAddr) + + granterAddr := chain.SenderAccount.GetAddress() + granteePrivKey := secp256k1.GenPrivKey() + granteeAddr := sdk.AccAddress(granteePrivKey.PubKey().Address().Bytes()) + otherPrivKey := secp256k1.GenPrivKey() + otherAddr := sdk.AccAddress(otherPrivKey.PubKey().Address().Bytes()) + + chain.Fund(granteeAddr, sdk.NewInt(1_000_000)) + chain.Fund(otherAddr, sdk.NewInt(1_000_000)) + assert.Equal(t, sdk.NewInt(1_000_000), chain.Balance(granteeAddr, sdk.DefaultBondDenom).Amount) + + myAmount := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(2_000_000)) + + specs := map[string]struct { + limit types.ContractAuthzLimitX + filter types.ContractAuthzFilterX + transferAmount sdk.Coin + senderKey cryptotypes.PrivKey + expErr *errorsmod.Error + }{ + "in limits and filter": { + limit: types.NewMaxFundsLimit(myAmount), + filter: types.NewAllowAllMessagesFilter(), + transferAmount: myAmount, + senderKey: granteePrivKey, + }, + "exceed limits": { + limit: types.NewMaxFundsLimit(myAmount), + filter: types.NewAllowAllMessagesFilter(), + transferAmount: myAmount.Add(sdk.NewCoin(sdk.DefaultBondDenom, sdk.OneInt())), + senderKey: granteePrivKey, + expErr: sdkerrors.ErrUnauthorized, + }, + "not match filter": { + limit: types.NewMaxFundsLimit(myAmount), + filter: types.NewAcceptedMessageKeysFilter("foo"), + transferAmount: sdk.NewCoin(sdk.DefaultBondDenom, sdk.OneInt()), + senderKey: granteePrivKey, + expErr: sdkerrors.ErrUnauthorized, + }, + "non authorized sender address": { // sanity check - testing sdk + limit: types.NewMaxFundsLimit(myAmount), + filter: types.NewAllowAllMessagesFilter(), + senderKey: otherPrivKey, + transferAmount: myAmount, + expErr: authz.ErrNoAuthorizationFound, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + // setup grant + grant, err := types.NewContractGrant(contractAddr, spec.limit, spec.filter) + require.NoError(t, err) + authorization := types.NewContractExecutionAuthorization(*grant) + expiry := time.Now().Add(time.Hour) + grantMsg, err := authz.NewMsgGrant(granterAddr, granteeAddr, authorization, &expiry) + require.NoError(t, err) + _, err = chain.SendMsgs(grantMsg) + require.NoError(t, err) + + granterStartBalance := chain.Balance(granterAddr, sdk.DefaultBondDenom).Amount + + // when + anyValidReflectMsg := []byte(fmt.Sprintf(`{"reflect_msg": {"msgs": [{"bank":{"burn":{"amount":[{"denom":%q, "amount": %q}]}}}]}}`, sdk.DefaultBondDenom, myAmount.Amount.String())) + execMsg := authz.NewMsgExec(spec.senderKey.PubKey().Address().Bytes(), []sdk.Msg{&types.MsgExecuteContract{ + Sender: granterAddr.String(), + Contract: contractAddr.String(), + Msg: anyValidReflectMsg, + Funds: sdk.NewCoins(spec.transferAmount), + }}) + _, gotErr := chain.SendNonDefaultSenderMsgs(spec.senderKey, &execMsg) + + // then + if spec.expErr != nil { + require.True(t, spec.expErr.Is(gotErr)) + assert.Equal(t, sdk.NewInt(1_000_000), chain.Balance(granteeAddr, sdk.DefaultBondDenom).Amount) + assert.Equal(t, granterStartBalance, chain.Balance(granterAddr, sdk.DefaultBondDenom).Amount) + return + } + require.NoError(t, gotErr) + assert.Equal(t, sdk.NewInt(1_000_000), chain.Balance(granteeAddr, sdk.DefaultBondDenom).Amount) + assert.Equal(t, granterStartBalance.Sub(spec.transferAmount.Amount), chain.Balance(granterAddr, sdk.DefaultBondDenom).Amount) + }) + } +} + +func TestStoreCodeGrant(t *testing.T) { + reflectWasmCode, err := os.ReadFile("testdata/reflect_1_1.wasm") + require.NoError(t, err) + + reflectCodeChecksum, err := wasmvm.CreateChecksum(reflectWasmCode) + require.NoError(t, err) + + coord := ibctesting.NewCoordinatorX(t, 1, e2e.DefaultMigalooAppFactory) + chain := coord.GetChain(ibctesting.GetChainID(1)) + + granterAddr := chain.SenderAccount.GetAddress() + granteePrivKey := secp256k1.GenPrivKey() + granteeAddr := sdk.AccAddress(granteePrivKey.PubKey().Address().Bytes()) + otherPrivKey := secp256k1.GenPrivKey() + otherAddr := sdk.AccAddress(otherPrivKey.PubKey().Address().Bytes()) + + chain.Fund(granteeAddr, sdk.NewInt(1_000_000)) + chain.Fund(otherAddr, sdk.NewInt(1_000_000)) + assert.Equal(t, sdk.NewInt(1_000_000), chain.Balance(granteeAddr, sdk.DefaultBondDenom).Amount) + + specs := map[string]struct { + codeHash []byte + instantiatePermission types.AccessConfig + senderKey cryptotypes.PrivKey + expErr *errorsmod.Error + }{ + "any code hash": { + codeHash: []byte("*"), + instantiatePermission: types.AllowEverybody, + senderKey: granteePrivKey, + }, + "match code hash and permission": { + codeHash: reflectCodeChecksum, + instantiatePermission: types.AllowEverybody, + senderKey: granteePrivKey, + }, + "not match code hash": { + codeHash: []byte("any_valid_checksum"), + instantiatePermission: types.AllowEverybody, + senderKey: granteePrivKey, + expErr: sdkerrors.ErrUnauthorized, + }, + "not match permission": { + codeHash: []byte("*"), + instantiatePermission: types.AllowNobody, + senderKey: granteePrivKey, + expErr: sdkerrors.ErrUnauthorized, + }, + "non authorized sender address": { + codeHash: []byte("*"), + instantiatePermission: types.AllowEverybody, + senderKey: otherPrivKey, + expErr: authz.ErrNoAuthorizationFound, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + // setup grant + tmp := spec + grant, err := types.NewCodeGrant(tmp.codeHash, &tmp.instantiatePermission) + require.NoError(t, err) + authorization := types.NewStoreCodeAuthorization(*grant) + expiry := time.Now().Add(time.Hour) + grantMsg, err := authz.NewMsgGrant(granterAddr, granteeAddr, authorization, &expiry) + require.NoError(t, err) + _, err = chain.SendMsgs(grantMsg) + require.NoError(t, err) + + // when + execMsg := authz.NewMsgExec(spec.senderKey.PubKey().Address().Bytes(), []sdk.Msg{&types.MsgStoreCode{ + Sender: granterAddr.String(), + WASMByteCode: reflectWasmCode, + InstantiatePermission: &types.AllowEverybody, + }}) + _, gotErr := chain.SendNonDefaultSenderMsgs(spec.senderKey, &execMsg) + + // then + if spec.expErr != nil { + require.True(t, spec.expErr.Is(gotErr)) + return + } + require.NoError(t, gotErr) + }) + } +} + +func TestBrokenGzipStoreCodeGrant(t *testing.T) { + brokenGzipWasmCode, err := os.ReadFile("testdata/broken_crc.gzip") + require.NoError(t, err) + + coord := ibctesting.NewCoordinatorX(t, 1, e2e.DefaultMigalooAppFactory) + chain := coord.GetChain(ibctesting.GetChainID(1)) + + granterAddr := chain.SenderAccount.GetAddress() + granteePrivKey := secp256k1.GenPrivKey() + granteeAddr := sdk.AccAddress(granteePrivKey.PubKey().Address().Bytes()) + otherPrivKey := secp256k1.GenPrivKey() + otherAddr := sdk.AccAddress(otherPrivKey.PubKey().Address().Bytes()) + + chain.Fund(granteeAddr, sdk.NewInt(1_000_000)) + chain.Fund(otherAddr, sdk.NewInt(1_000_000)) + assert.Equal(t, sdk.NewInt(1_000_000), chain.Balance(granteeAddr, sdk.DefaultBondDenom).Amount) + + codeHash := []byte("*") + instantiatePermission := types.AllowEverybody + senderKey := granteePrivKey + + // setup grant + grant, err := types.NewCodeGrant(codeHash, &instantiatePermission) + require.NoError(t, err) + authorization := types.NewStoreCodeAuthorization(*grant) + expiry := time.Now().Add(time.Hour) + grantMsg, err := authz.NewMsgGrant(granterAddr, granteeAddr, authorization, &expiry) + require.NoError(t, err) + _, err = chain.SendMsgs(grantMsg) + require.NoError(t, err) + + // when + execMsg := authz.NewMsgExec(senderKey.PubKey().Address().Bytes(), []sdk.Msg{&types.MsgStoreCode{ + Sender: granterAddr.String(), + WASMByteCode: brokenGzipWasmCode, + InstantiatePermission: &types.AllowEverybody, + }}) + _, gotErr := chain.SendNonDefaultSenderMsgs(senderKey, &execMsg) + + // then + require.Error(t, gotErr) +} diff --git a/tests/e2e/helper.go b/tests/e2e/helper.go new file mode 100644 index 00000000..e9a9b3ef --- /dev/null +++ b/tests/e2e/helper.go @@ -0,0 +1,18 @@ +package e2e + +import ( + "testing" + + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + "github.com/White-Whale-Defi-Platform/migaloo-chain/v4/app" + tmtypes "github.com/cometbft/cometbft/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// DefaultMigalooAppFactory instantiates and sets up the default Migaloo app +func DefaultMigalooAppFactory(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, opts []wasmkeeper.Option, balances ...banktypes.Balance) wasmibctesting.ChainApp { + t.Helper() + return app.SetupWithGenesisValSet(t, valSet, genAccs, chainID, opts, balances...) +} diff --git a/tests/e2e/ibc_fees_test.go b/tests/e2e/ibc_fees_test.go new file mode 100644 index 00000000..e8a78d25 --- /dev/null +++ b/tests/e2e/ibc_fees_test.go @@ -0,0 +1,224 @@ +package e2e + +import ( + "bytes" + "encoding/base64" + "fmt" + "testing" + "time" + + ibcfee "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/types" + 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" + ibctesting "github.com/cosmos/ibc-go/v7/testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + "github.com/White-Whale-Defi-Platform/migaloo-chain/v4/app" +) + +func TestIBCFeesTransfer(t *testing.T) { + // scenario: + // given 2 chains + // with an ics-20 channel established + // when an ics-29 fee is attached to an ibc package + // then the relayer's payee is receiving the fee(s) on success + codec := app.MakeEncodingConfig().Codec + // coord := wasmibctesting.NewCoordinator(t, 2) + coord := wasmibctesting.NewCoordinatorX(t, 2, DefaultMigalooAppFactory) + chainA := coord.GetChain(wasmibctesting.GetChainID(1)) + chainB := coord.GetChain(wasmibctesting.GetChainID(2)) + + actorChainA := sdk.AccAddress(chainA.SenderPrivKey.PubKey().Address()) + actorChainB := sdk.AccAddress(chainB.SenderPrivKey.PubKey().Address()) + receiver := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len)) + payee := sdk.AccAddress(bytes.Repeat([]byte{2}, address.Len)) + oneToken := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1))) + + path := wasmibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{ + PortID: ibctransfertypes.PortID, + Version: string(codec.MustMarshalJSON(&ibcfee.Metadata{FeeVersion: ibcfee.Version, AppVersion: ibctransfertypes.Version})), + Order: channeltypes.UNORDERED, + } + path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{ + PortID: ibctransfertypes.PortID, + Version: string(codec.MustMarshalJSON(&ibcfee.Metadata{FeeVersion: ibcfee.Version, AppVersion: ibctransfertypes.Version})), + Order: channeltypes.UNORDERED, + } + // with an ics-20 transfer channel setup between both chains + coord.Setup(path) + appA, ok := chainA.App.(*app.MigalooApp) + if !ok { + t.Fatalf("Expected *app.MigalooApp, got %T", coord.GetChain(wasmibctesting.GetChainID(1)).App) + } + + require.True(t, appA.IBCFeeKeeper.IsFeeEnabled(chainA.GetContext(), ibctransfertypes.PortID, path.EndpointA.ChannelID)) + // and with a payee registered on both chains + _, err := chainA.SendMsgs(ibcfee.NewMsgRegisterPayee(ibctransfertypes.PortID, path.EndpointA.ChannelID, actorChainA.String(), payee.String())) + require.NoError(t, err) + _, err = chainB.SendMsgs(ibcfee.NewMsgRegisterCounterpartyPayee(ibctransfertypes.PortID, path.EndpointB.ChannelID, actorChainB.String(), payee.String())) + require.NoError(t, err) + + // when a transfer package is sent + transferCoin := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1)) + ibcPayloadMsg := ibctransfertypes.NewMsgTransfer(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, transferCoin, actorChainA.String(), receiver.String(), clienttypes.Height{}, uint64(time.Now().Add(time.Minute).UnixNano()), "testing") + ibcPackageFee := ibcfee.NewFee(oneToken, oneToken, sdk.Coins{}) + feeMsg := ibcfee.NewMsgPayPacketFee(ibcPackageFee, ibctransfertypes.PortID, path.EndpointA.ChannelID, actorChainA.String(), nil) + _, err = chainA.SendMsgs(feeMsg, ibcPayloadMsg) + require.NoError(t, err) + pendingIncentivisedPackages := appA.IBCFeeKeeper.GetIdentifiedPacketFeesForChannel(chainA.GetContext(), ibctransfertypes.PortID, path.EndpointA.ChannelID) + assert.Len(t, pendingIncentivisedPackages, 1) + + // and packages relayed + require.NoError(t, coord.RelayAndAckPendingPackets(path)) + + // then + expBalance := ibctransfertypes.GetTransferCoin(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, transferCoin.Denom, transferCoin.Amount) + gotBalance := chainB.Balance(receiver, expBalance.Denom) + assert.Equal(t, expBalance.String(), gotBalance.String()) + payeeBalance := chainA.AllBalances(payee) + assert.Equal(t, oneToken.Add(oneToken...).String(), payeeBalance.String()) + + // and with a payee registered for chain B to A + _, err = chainA.SendMsgs(ibcfee.NewMsgRegisterCounterpartyPayee(ibctransfertypes.PortID, path.EndpointA.ChannelID, actorChainA.String(), payee.String())) + require.NoError(t, err) + _, err = chainB.SendMsgs(ibcfee.NewMsgRegisterPayee(ibctransfertypes.PortID, path.EndpointB.ChannelID, actorChainB.String(), payee.String())) + require.NoError(t, err) + + // and transfer from B to A + ibcPayloadMsg = ibctransfertypes.NewMsgTransfer(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, transferCoin, actorChainB.String(), receiver.String(), clienttypes.Height{}, uint64(time.Now().Add(time.Minute).UnixNano()), "more testing") + ibcPackageFee = ibcfee.NewFee(oneToken, oneToken, sdk.Coins{}) + feeMsg = ibcfee.NewMsgPayPacketFee(ibcPackageFee, ibctransfertypes.PortID, path.EndpointB.ChannelID, actorChainB.String(), nil) + _, err = chainB.SendMsgs(feeMsg, ibcPayloadMsg) + require.NoError(t, err) + appB := chainB.App.(*app.MigalooApp) + pendingIncentivisedPackages = appB.IBCFeeKeeper.GetIdentifiedPacketFeesForChannel(chainB.GetContext(), ibctransfertypes.PortID, path.EndpointB.ChannelID) + assert.Len(t, pendingIncentivisedPackages, 1) + + // when packages relayed + require.NoError(t, coord.RelayAndAckPendingPackets(path)) + + // then + expBalance = ibctransfertypes.GetTransferCoin(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, transferCoin.Denom, transferCoin.Amount) + gotBalance = chainA.Balance(receiver, expBalance.Denom) + assert.Equal(t, expBalance.String(), gotBalance.String()) + payeeBalance = chainB.AllBalances(payee) + assert.Equal(t, sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(2)).String(), payeeBalance.String()) +} + +func TestIBCFeesWasm(t *testing.T) { + // scenario: + // given 2 chains with cw20-ibc on chain A and native ics20 module on B + // and an ibc channel established + // when an ics-29 fee is attached to an ibc package + // then the relayer's payee is receiving the fee(s) on success + codec := app.MakeEncodingConfig().Codec + coord := wasmibctesting.NewCoordinatorX(t, 2, DefaultMigalooAppFactory) + chainA := coord.GetChain(wasmibctesting.GetChainID(1)) + chainB := coord.GetChain(ibctesting.GetChainID(2)) + actorChainA := sdk.AccAddress(chainA.SenderPrivKey.PubKey().Address()) + actorChainB := sdk.AccAddress(chainB.SenderPrivKey.PubKey().Address()) + + // setup chain A + codeID := chainA.StoreCodeFile("./testdata/cw20_base.wasm.gz").CodeID + + initMsg := []byte(fmt.Sprintf(`{"decimals": 6, "name": "test", "symbol":"ALX", "initial_balances": [{"address": %q,"amount":"100000000"}] }`, actorChainA.String())) + cw20ContractAddr := chainA.InstantiateContract(codeID, initMsg) + + initMsg = []byte(fmt.Sprintf(`{"default_timeout": 360, "gov_contract": %q, "allowlist":[{"contract":%q}]}`, actorChainA.String(), cw20ContractAddr.String())) + codeID = chainA.StoreCodeFile("./testdata/cw20_ics20.wasm.gz").CodeID + ibcContractAddr := chainA.InstantiateContract(codeID, initMsg) + ibcContractPortID := chainA.ContractInfo(ibcContractAddr).IBCPortID + + payee := sdk.AccAddress(bytes.Repeat([]byte{2}, address.Len)) + oneToken := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1))) + + path := wasmibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{ + PortID: ibcContractPortID, + Version: string(codec.MustMarshalJSON(&ibcfee.Metadata{FeeVersion: ibcfee.Version, AppVersion: ibctransfertypes.Version})), + Order: channeltypes.UNORDERED, + } + path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{ + PortID: ibctransfertypes.PortID, + Version: string(codec.MustMarshalJSON(&ibcfee.Metadata{FeeVersion: ibcfee.Version, AppVersion: ibctransfertypes.Version})), + Order: channeltypes.UNORDERED, + } + // with an ics-29 fee enabled channel setup between both chains + coord.Setup(path) + appA := chainA.App.(*app.MigalooApp) + appB := chainB.App.(*app.MigalooApp) + require.True(t, appA.IBCFeeKeeper.IsFeeEnabled(chainA.GetContext(), ibcContractPortID, path.EndpointA.ChannelID)) + require.True(t, appB.IBCFeeKeeper.IsFeeEnabled(chainB.GetContext(), ibctransfertypes.PortID, path.EndpointB.ChannelID)) + // and with a payee registered for A -> B + _, err := chainA.SendMsgs(ibcfee.NewMsgRegisterPayee(ibcContractPortID, path.EndpointA.ChannelID, actorChainA.String(), payee.String())) + require.NoError(t, err) + _, err = chainB.SendMsgs(ibcfee.NewMsgRegisterCounterpartyPayee(ibctransfertypes.PortID, path.EndpointB.ChannelID, actorChainB.String(), payee.String())) + require.NoError(t, err) + + // when a transfer package is sent from ics20 contract on A to B + transfer := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"channel": %q, "remote_address": %q}`, path.EndpointA.ChannelID, actorChainB.String()))) + exec := []byte(fmt.Sprintf(`{"send":{"contract": %q, "amount": "100", "msg": %q}}`, ibcContractAddr.String(), transfer)) + execMsg := wasmtypes.MsgExecuteContract{ + Sender: actorChainA.String(), + Contract: cw20ContractAddr.String(), + Msg: exec, + } + ibcPackageFee := ibcfee.NewFee(oneToken, oneToken, sdk.Coins{}) + feeMsg := ibcfee.NewMsgPayPacketFee(ibcPackageFee, ibcContractPortID, path.EndpointA.ChannelID, actorChainA.String(), nil) + _, err = chainA.SendMsgs(feeMsg, &execMsg) + require.NoError(t, err) + pendingIncentivisedPackages := appA.IBCFeeKeeper.GetIdentifiedPacketFeesForChannel(chainA.GetContext(), ibcContractPortID, path.EndpointA.ChannelID) + assert.Len(t, pendingIncentivisedPackages, 1) + + // and packages relayed + require.NoError(t, coord.RelayAndAckPendingPackets(path)) + + // then + // on chain A + gotCW20Balance, err := appA.WasmKeeper.QuerySmart(chainA.GetContext(), cw20ContractAddr, []byte(fmt.Sprintf(`{"balance":{"address": %q}}`, actorChainA.String()))) + require.NoError(t, err) + assert.JSONEq(t, `{"balance":"99999900"}`, string(gotCW20Balance)) + payeeBalance := chainA.AllBalances(payee) + assert.Equal(t, sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(2)).String(), payeeBalance.String()) + // and on chain B + pendingIncentivisedPackages = appA.IBCFeeKeeper.GetIdentifiedPacketFeesForChannel(chainA.GetContext(), ibcContractPortID, path.EndpointA.ChannelID) + assert.Len(t, pendingIncentivisedPackages, 0) + expBalance := ibctransfertypes.GetTransferCoin(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, "cw20:"+cw20ContractAddr.String(), sdk.NewInt(100)) + gotBalance := chainB.Balance(actorChainB, expBalance.Denom) + assert.Equal(t, expBalance.String(), gotBalance.String(), chainB.AllBalances(actorChainB)) + + // and with a payee registered for chain B to A + _, err = chainA.SendMsgs(ibcfee.NewMsgRegisterCounterpartyPayee(ibcContractPortID, path.EndpointA.ChannelID, actorChainA.String(), payee.String())) + require.NoError(t, err) + _, err = chainB.SendMsgs(ibcfee.NewMsgRegisterPayee(ibctransfertypes.PortID, path.EndpointB.ChannelID, actorChainB.String(), payee.String())) + require.NoError(t, err) + + // and when sent back from chain B to A + ibcPayloadMsg := ibctransfertypes.NewMsgTransfer(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, gotBalance, actorChainB.String(), actorChainA.String(), clienttypes.Height{}, uint64(time.Now().Add(time.Minute).UnixNano()), "even more tests") + ibcPackageFee = ibcfee.NewFee(oneToken, oneToken, sdk.Coins{}) + feeMsg = ibcfee.NewMsgPayPacketFee(ibcPackageFee, ibctransfertypes.PortID, path.EndpointB.ChannelID, actorChainB.String(), nil) + _, err = chainB.SendMsgs(feeMsg, ibcPayloadMsg) + require.NoError(t, err) + pendingIncentivisedPackages = appB.IBCFeeKeeper.GetIdentifiedPacketFeesForChannel(chainB.GetContext(), ibctransfertypes.PortID, path.EndpointB.ChannelID) + assert.Len(t, pendingIncentivisedPackages, 1) + + // when packages relayed + require.NoError(t, coord.RelayAndAckPendingPackets(path)) + + // then + // on chain A + gotCW20Balance, err = appA.WasmKeeper.QuerySmart(chainA.GetContext(), cw20ContractAddr, []byte(fmt.Sprintf(`{"balance":{"address": %q}}`, actorChainA.String()))) + require.NoError(t, err) + assert.JSONEq(t, `{"balance":"100000000"}`, string(gotCW20Balance)) + // and on chain B + payeeBalance = chainB.AllBalances(payee) + assert.Equal(t, sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(2)).String(), payeeBalance.String()) +} diff --git a/tests/e2e/ica_test.go b/tests/e2e/ica_test.go new file mode 100644 index 00000000..7a6df7c1 --- /dev/null +++ b/tests/e2e/ica_test.go @@ -0,0 +1,106 @@ +package e2e + +import ( + "bytes" + "testing" + "time" + + "github.com/cosmos/gogoproto/proto" + icacontrollertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types" + hosttypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v7/testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + "github.com/White-Whale-Defi-Platform/migaloo-chain/v4/app" +) + +func TestICA(t *testing.T) { + // scenario: + // given a host and controller chain + // when an ica is registered on the controller chain + // and the channel is established to the host chain + // then the ICA owner can submit a message via IBC + // to control their account on the host chain + coord := wasmibctesting.NewCoordinatorX(t, 2, DefaultMigalooAppFactory) + + hostChain := coord.GetChain(ibctesting.GetChainID(1)) + hostParams := hosttypes.NewParams(true, []string{sdk.MsgTypeURL(&banktypes.MsgSend{})}) + hostApp := hostChain.App.(*app.MigalooApp) + hostApp.ICAHostKeeper.SetParams(hostChain.GetContext(), hostParams) + + controllerChain := coord.GetChain(ibctesting.GetChainID(2)) + + path := wasmibctesting.NewPath(controllerChain, hostChain) + coord.SetupConnections(path) + + ownerAddr := sdk.AccAddress(controllerChain.SenderPrivKey.PubKey().Address()) + msg := icacontrollertypes.NewMsgRegisterInterchainAccount(path.EndpointA.ConnectionID, ownerAddr.String(), "") + res, err := controllerChain.SendMsgs(msg) + require.NoError(t, err) + chanID, portID, version := parseIBCChannelEvents(t, res) + + // next open channels on both sides + path.EndpointA.ChannelID = chanID + path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{ + PortID: portID, + Version: version, + Order: channeltypes.ORDERED, + } + path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{ + PortID: icatypes.HostPortID, + Version: icatypes.Version, + Order: channeltypes.ORDERED, + } + coord.CreateChannels(path) + + // assert ICA exists on controller + contApp := controllerChain.App.(*app.MigalooApp) + icaRsp, err := contApp.ICAControllerKeeper.InterchainAccount(sdk.WrapSDKContext(controllerChain.GetContext()), &icacontrollertypes.QueryInterchainAccountRequest{ + Owner: ownerAddr.String(), + ConnectionId: path.EndpointA.ConnectionID, + }) + require.NoError(t, err) + icaAddr := sdk.MustAccAddressFromBech32(icaRsp.GetAddress()) + hostChain.Fund(icaAddr, sdk.NewInt(1_000)) + + // submit a tx + targetAddr := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len)) + sendCoin := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) + payloadMsg := banktypes.NewMsgSend(icaAddr, targetAddr, sdk.NewCoins(sendCoin)) + rawPayloadData, err := icatypes.SerializeCosmosTx(controllerChain.Codec, []proto.Message{payloadMsg}) + require.NoError(t, err) + payloadPacket := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: rawPayloadData, + Memo: "testing", + } + relativeTimeout := uint64(time.Minute.Nanoseconds()) // note this is in nanoseconds + msgSendTx := icacontrollertypes.NewMsgSendTx(ownerAddr.String(), path.EndpointA.ConnectionID, relativeTimeout, payloadPacket) + _, err = controllerChain.SendMsgs(msgSendTx) + require.NoError(t, err) + + assert.Equal(t, 1, len(controllerChain.PendingSendPackets)) + require.NoError(t, coord.RelayAndAckPendingPackets(path)) + + gotBalance := hostChain.Balance(targetAddr, sdk.DefaultBondDenom) + assert.Equal(t, sendCoin.String(), gotBalance.String()) +} + +func parseIBCChannelEvents(t *testing.T, res *sdk.Result) (string, string, string) { + t.Helper() + chanID, err := ibctesting.ParseChannelIDFromEvents(res.GetEvents()) + require.NoError(t, err) + portID, err := wasmibctesting.ParsePortIDFromEvents(res.GetEvents()) + require.NoError(t, err) + version, err := wasmibctesting.ParseChannelVersionFromEvents(res.GetEvents()) + require.NoError(t, err) + return chanID, portID, version +} diff --git a/tests/e2e/reflect_helper.go b/tests/e2e/reflect_helper.go new file mode 100644 index 00000000..28c1867b --- /dev/null +++ b/tests/e2e/reflect_helper.go @@ -0,0 +1,73 @@ +package e2e + +import ( + "encoding/json" + "testing" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" + "github.com/CosmWasm/wasmd/x/wasm/types" +) + +// InstantiateReflectContract store and instantiate a reflect contract instance +func InstantiateReflectContract(t *testing.T, chain *ibctesting.TestChain) sdk.AccAddress { + t.Helper() + codeID := chain.StoreCodeFile("testdata/reflect_1_1.wasm").CodeID + contractAddr := chain.InstantiateContract(codeID, []byte(`{}`)) + require.NotEmpty(t, contractAddr) + return contractAddr +} + +// MustExecViaReflectContract submit execute message to send payload to reflect contract +func MustExecViaReflectContract(t *testing.T, chain *ibctesting.TestChain, contractAddr sdk.AccAddress, msgs ...wasmvmtypes.CosmosMsg) *sdk.Result { + t.Helper() + rsp, err := ExecViaReflectContract(t, chain, contractAddr, msgs) + require.NoError(t, err) + return rsp +} + +type sdkMessageType interface { + codec.ProtoMarshaler + sdk.Msg +} + +func MustExecViaStargateReflectContract[T sdkMessageType](t *testing.T, chain *ibctesting.TestChain, contractAddr sdk.AccAddress, msgs ...T) *sdk.Result { + t.Helper() + vmMsgs := make([]wasmvmtypes.CosmosMsg, len(msgs)) + for i, m := range msgs { + bz, err := chain.Codec.Marshal(m) + require.NoError(t, err) + vmMsgs[i] = wasmvmtypes.CosmosMsg{ + Stargate: &wasmvmtypes.StargateMsg{ + TypeURL: sdk.MsgTypeURL(m), + Value: bz, + }, + } + } + rsp, err := ExecViaReflectContract(t, chain, contractAddr, vmMsgs) + require.NoError(t, err) + return rsp +} + +// ExecViaReflectContract submit execute message to send payload to reflect contract +func ExecViaReflectContract(t *testing.T, chain *ibctesting.TestChain, contractAddr sdk.AccAddress, msgs []wasmvmtypes.CosmosMsg) (*sdk.Result, error) { + t.Helper() + require.NotEmpty(t, msgs) + reflectSend := testdata.ReflectHandleMsg{ + Reflect: &testdata.ReflectPayload{Msgs: msgs}, + } + reflectSendBz, err := json.Marshal(reflectSend) + require.NoError(t, err) + execMsg := &types.MsgExecuteContract{ + Sender: chain.SenderAccount.GetAddress().String(), + Contract: contractAddr.String(), + Msg: reflectSendBz, + } + return chain.SendMsgs(execMsg) +} diff --git a/tests/e2e/testdata/broken_crc.gzip b/tests/e2e/testdata/broken_crc.gzip new file mode 100644 index 00000000..378713e2 Binary files /dev/null and b/tests/e2e/testdata/broken_crc.gzip differ diff --git a/tests/e2e/testdata/burner.wasm b/tests/e2e/testdata/burner.wasm new file mode 100644 index 00000000..dbfd0aa5 Binary files /dev/null and b/tests/e2e/testdata/burner.wasm differ diff --git a/tests/e2e/testdata/contracts.go b/tests/e2e/testdata/contracts.go new file mode 100644 index 00000000..dc81aa23 --- /dev/null +++ b/tests/e2e/testdata/contracts.go @@ -0,0 +1,98 @@ +package testdata + +import ( + _ "embed" + + typwasmvmtypes "github.com/CosmWasm/wasmvm/types" + "github.com/cosmos/cosmos-sdk/types" +) + +const ( + ChecksumHackatom = "3f4cd47c39c57fe1733fb41ed176eebd9d5c67baf5df8a1eeda1455e758f8514" +) + +var ( + //go:embed reflect.wasm + reflectContract []byte + //go:embed reflect_1_1.wasm + migrateReflectContract []byte + //go:embed cyberpunk.wasm + cyberpunkContract []byte + //go:embed ibc_reflect.wasm + ibcReflectContract []byte + //go:embed burner.wasm + burnerContract []byte + //go:embed hackatom.wasm + hackatomContract []byte +) + +func ReflectContractWasm() []byte { + return reflectContract +} + +func MigrateReflectContractWasm() []byte { + return migrateReflectContract +} + +func CyberpunkContractWasm() []byte { + return cyberpunkContract +} + +func IBCReflectContractWasm() []byte { + return ibcReflectContract +} + +func BurnerContractWasm() []byte { + return burnerContract +} + +func HackatomContractWasm() []byte { + return hackatomContract +} + +// ReflectHandleMsg is used to encode handle messages +type ReflectHandleMsg struct { + Reflect *ReflectPayload `json:"reflect_msg,omitempty"` + ReflectSubMsg *ReflectSubPayload `json:"reflect_sub_msg,omitempty"` + ChangeOwner *OwnerPayload `json:"change_owner,omitempty"` +} + +type OwnerPayload struct { + Owner types.Address `json:"owner"` +} + +type ReflectPayload struct { + Msgs []typwasmvmtypes.CosmosMsg `json:"msgs"` +} + +type ReflectSubPayload struct { + Msgs []typwasmvmtypes.SubMsg `json:"msgs"` +} + +// ReflectQueryMsg is used to encode query messages +type ReflectQueryMsg struct { + Owner *struct{} `json:"owner,omitempty"` + Capitalized *Text `json:"capitalized,omitempty"` + Chain *ChainQuery `json:"chain,omitempty"` + SubMsgResult *SubCall `json:"sub_msg_result,omitempty"` +} + +type ChainQuery struct { + Request *typwasmvmtypes.QueryRequest `json:"request,omitempty"` +} + +type Text struct { + Text string `json:"text"` +} + +type SubCall struct { + ID uint64 `json:"id"` +} + +type OwnerResponse struct { + Owner string `json:"owner,omitempty"` +} + +type ChainResponse struct { + Data []byte `json:"data,omitempty"` +} diff --git a/tests/e2e/testdata/cw20_base.wasm.gz b/tests/e2e/testdata/cw20_base.wasm.gz new file mode 100644 index 00000000..7745e80f Binary files /dev/null and b/tests/e2e/testdata/cw20_base.wasm.gz differ diff --git a/tests/e2e/testdata/cw20_ics20.wasm.gz b/tests/e2e/testdata/cw20_ics20.wasm.gz new file mode 100644 index 00000000..07517a2f Binary files /dev/null and b/tests/e2e/testdata/cw20_ics20.wasm.gz differ diff --git a/tests/e2e/testdata/cyberpunk.wasm b/tests/e2e/testdata/cyberpunk.wasm new file mode 100644 index 00000000..809224e5 Binary files /dev/null and b/tests/e2e/testdata/cyberpunk.wasm differ diff --git a/tests/e2e/testdata/download_releases.sh b/tests/e2e/testdata/download_releases.sh new file mode 100755 index 00000000..544cdd2e --- /dev/null +++ b/tests/e2e/testdata/download_releases.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +command -v shellcheck > /dev/null && shellcheck "$0" + +if [ $# -ne 1 ]; then + echo "Usage: ./download_releases.sh RELEASE_TAG" + exit 1 +fi + +tag="$1" + +for contract in burner hackatom ibc_reflect ibc_reflect_send reflect staking cyberpunk; do + url="https://github.com/CosmWasm/cosmwasm/releases/download/$tag/${contract}.wasm" + echo "Downloading $url ..." + wget -O "${contract}.wasm" "$url" +done + +# create the zip variant +gzip -k hackatom.wasm +mv hackatom.wasm.gz hackatom.wasm.gzip + +rm -f version.txt +echo "$tag" >version.txt \ No newline at end of file diff --git a/tests/e2e/testdata/genesis.json b/tests/e2e/testdata/genesis.json new file mode 100644 index 00000000..08969c7d --- /dev/null +++ b/tests/e2e/testdata/genesis.json @@ -0,0 +1,219 @@ +{ + "genesis_time": "2020-07-13T07:49:08.2945876Z", + "chain_id": "testing", + "consensus_params": { + "block": { + "max_bytes": "22020096", + "max_gas": "-1", + "time_iota_ms": "1000" + }, + "evidence": { + "max_age_num_blocks": "100000", + "max_age_duration": "172800000000000" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + } + }, + "app_hash": "", + "app_state": { + "upgrade": {}, + "evidence": { + "params": { + "max_evidence_age": "120000000000" + }, + "evidence": [] + }, + "supply": { + "supply": [] + }, + "mint": { + "minter": { + "inflation": "0.130000000000000000", + "annual_provisions": "0.000000000000000000" + }, + "params": { + "mint_denom": "ustake", + "inflation_rate_change": "0.130000000000000000", + "inflation_max": "0.200000000000000000", + "inflation_min": "0.070000000000000000", + "goal_bonded": "0.670000000000000000", + "blocks_per_year": "6311520" + } + }, + "gov": { + "starting_proposal_id": "1", + "deposits": null, + "votes": null, + "proposals": null, + "deposit_params": { + "min_deposit": [ + { + "denom": "ustake", + "amount": "1" + } + ], + "max_deposit_period": "172800000000000" + }, + "voting_params": { + "voting_period": "60000000000", + "voting_period_desc": "1minute" + }, + "tally_params": { + "quorum": "0.000000000000000001", + "threshold": "0.000000000000000001", + "veto": "0.334000000000000000" + } + }, + "slashing": { + "params": { + "signed_blocks_window": "100", + "min_signed_per_window": "0.500000000000000000", + "downtime_jail_duration": "600000000000", + "slash_fraction_double_sign": "0.050000000000000000", + "slash_fraction_downtime": "0.010000000000000000" + }, + "signing_infos": {}, + "missed_blocks": {} + }, + "wasm": { + "params": { + "upload_access": { + "type": 3, + "address": "" + }, + "instantiate_default_permission": 3 + }, + "codes": null, + "contracts": null, + "sequences": null + }, + "bank": { + "send_enabled": true + }, + "distribution": { + "params": { + "community_tax": "0.020000000000000000", + "base_proposer_reward": "0.010000000000000000", + "bonus_proposer_reward": "0.040000000000000000", + "withdraw_addr_enabled": true + }, + "fee_pool": { + "community_pool": [] + }, + "delegator_withdraw_infos": [], + "previous_proposer": "", + "outstanding_rewards": [], + "validator_accumulated_commissions": [], + "validator_historical_rewards": [], + "validator_current_rewards": [], + "delegator_starting_infos": [], + "validator_slash_events": [] + }, + "crisis": { + "constant_fee": { + "denom": "ustake", + "amount": "1000" + } + }, + "genutil": { + "gentxs": [ + { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": [ + { + "type": "cosmos-sdk/MsgCreateValidator", + "value": { + "description": { + "moniker": "testing", + "identity": "", + "website": "", + "security_contact": "", + "details": "" + }, + "commission": { + "rate": "0.100000000000000000", + "max_rate": "0.200000000000000000", + "max_change_rate": "0.010000000000000000" + }, + "min_self_delegation": "1", + "delegator_address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np", + "validator_address": "cosmosvaloper1ve557a5g9yw2g2z57js3pdmcvd5my6g88d76lj", + "pubkey": "cosmosvalconspub1zcjduepqddfln4tujr2p8actpgqz4h2xnls9y7tu9c9tu5lqkdglmdjalzuqah4neg", + "value": { + "denom": "ustake", + "amount": "250000000" + } + } + } + ], + "fee": { + "amount": [], + "gas": "200000" + }, + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A//cqZxkpH1re0VrHBtH308nb5t8K+Y/hF0GeRdRBmaJ" + }, + "signature": "5QEEIuUVQTEBMuAtOOHnnKo6rPsIbmfzUxUqRnDFERVqwVr1Kg+ex4f/UGIK0yrOAvOG8zDADwFP4yF8lw+o5g==" + } + ], + "memo": "836fc54e9cad58f4ed6420223ec6290f75342afa@172.17.0.2:26656" + } + } + ] + }, + "auth": { + "params": { + "max_memo_characters": "256", + "tx_sig_limit": "7", + "tx_size_cost_per_byte": "10", + "sig_verify_cost_ed25519": "590", + "sig_verify_cost_secp256k1": "1000" + }, + "accounts": [ + { + "type": "cosmos-sdk/Account", + "value": { + "address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np", + "coins": [ + { + "denom": "ucosm", + "amount": "1000000000" + }, + { + "denom": "ustake", + "amount": "1000000000" + } + ], + "public_key": "", + "account_number": 0, + "sequence": 0 + } + } + ] + }, + "params": null, + "staking": { + "params": { + "unbonding_time": "1814400000000000", + "max_validators": 100, + "max_entries": 7, + "historical_entries": 0, + "bond_denom": "ustake" + }, + "last_total_power": "0", + "last_validator_powers": null, + "validators": null, + "delegations": null, + "unbonding_delegations": null, + "redelegations": null, + "exported": false + } + } +} \ No newline at end of file diff --git a/tests/e2e/testdata/hackatom.wasm b/tests/e2e/testdata/hackatom.wasm new file mode 100644 index 00000000..53337882 Binary files /dev/null and b/tests/e2e/testdata/hackatom.wasm differ diff --git a/tests/e2e/testdata/hackatom.wasm.gzip b/tests/e2e/testdata/hackatom.wasm.gzip new file mode 100644 index 00000000..70425704 Binary files /dev/null and b/tests/e2e/testdata/hackatom.wasm.gzip differ diff --git a/tests/e2e/testdata/ibc_reflect.wasm b/tests/e2e/testdata/ibc_reflect.wasm new file mode 100644 index 00000000..051be2a5 Binary files /dev/null and b/tests/e2e/testdata/ibc_reflect.wasm differ diff --git a/tests/e2e/testdata/ibc_reflect_send.wasm b/tests/e2e/testdata/ibc_reflect_send.wasm new file mode 100644 index 00000000..1d361d88 Binary files /dev/null and b/tests/e2e/testdata/ibc_reflect_send.wasm differ diff --git a/tests/e2e/testdata/reflect.wasm b/tests/e2e/testdata/reflect.wasm new file mode 100644 index 00000000..28a64acb Binary files /dev/null and b/tests/e2e/testdata/reflect.wasm differ diff --git a/tests/e2e/testdata/reflect.wasm.v1_0 b/tests/e2e/testdata/reflect.wasm.v1_0 new file mode 100644 index 00000000..312f4576 Binary files /dev/null and b/tests/e2e/testdata/reflect.wasm.v1_0 differ diff --git a/tests/e2e/testdata/reflect_1_1.wasm b/tests/e2e/testdata/reflect_1_1.wasm new file mode 100644 index 00000000..7383a6d6 Binary files /dev/null and b/tests/e2e/testdata/reflect_1_1.wasm differ diff --git a/tests/e2e/testdata/staking.wasm b/tests/e2e/testdata/staking.wasm new file mode 100644 index 00000000..4749d775 Binary files /dev/null and b/tests/e2e/testdata/staking.wasm differ diff --git a/tests/e2e/testdata/version.txt b/tests/e2e/testdata/version.txt new file mode 100644 index 00000000..0d0c52f8 --- /dev/null +++ b/tests/e2e/testdata/version.txt @@ -0,0 +1 @@ +v1.4.0 diff --git a/x/feeburn/keeper/integration_test.go b/x/feeburn/keeper/integration_test.go index 450c5a72..4e4c794c 100644 --- a/x/feeburn/keeper/integration_test.go +++ b/x/feeburn/keeper/integration_test.go @@ -3,7 +3,6 @@ package keeper_test import ( "fmt" - config "github.com/White-Whale-Defi-Platform/migaloo-chain/v4/app/params" abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -15,15 +14,15 @@ func (suite *KeeperTestSuite) TestBurnFeeCosmosTxDelegate() { suite.SetupTest() priv0 := secp256k1.GenPrivKey() addr := sdk.AccAddress(priv0.PubKey().Address()) - accBalance := sdk.Coins{{Denom: config.BaseDenom, Amount: sdk.NewInt(10000000000000)}} + accBalance := sdk.Coins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewInt(10000000000000)}} err := suite.FundAccount(suite.Ctx, addr, accBalance) suite.Require().NoError(err) - totalSupplyBefore := suite.App.BankKeeper.GetSupply(suite.Ctx, config.BaseDenom) + totalSupplyBefore := suite.App.BankKeeper.GetSupply(suite.Ctx, sdk.DefaultBondDenom) fmt.Println("totalSupply", totalSupplyBefore) - delegateAmount := sdk.NewCoin(config.BaseDenom, sdk.NewInt(1000)) + delegateAmount := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1000)) delegate(priv0, delegateAmount) mintedCoin := getMintedCoin() - totalSupplyAfter := suite.App.BankKeeper.GetSupply(suite.Ctx, config.BaseDenom) + totalSupplyAfter := suite.App.BankKeeper.GetSupply(suite.Ctx, sdk.DefaultBondDenom) fmt.Println("totalSupplyAfter", totalSupplyAfter) expectAmount := totalSupplyAfter.Amount.Sub(totalSupplyBefore.Amount) expectAmount = mintedCoin.Amount.Sub(expectAmount) diff --git a/x/feeburn/keeper/keeper_test.go b/x/feeburn/keeper/keeper_test.go index 2bce8ffd..817cfaac 100644 --- a/x/feeburn/keeper/keeper_test.go +++ b/x/feeburn/keeper/keeper_test.go @@ -69,7 +69,7 @@ func prepareCosmosTx(priv cryptotypes.PrivKey, msgs ...sdk.Msg) []byte { txBuilder.SetGasLimit(1000000) gasPrice := sdk.NewInt(1) - fees := &sdk.Coins{{Denom: config.BaseDenom, Amount: gasPrice.MulRaw(DefaultFee)}} + fees := &sdk.Coins{{Denom: sdk.DefaultBondDenom, Amount: gasPrice.MulRaw(DefaultFee)}} txBuilder.SetFeeAmount(*fees) err := txBuilder.SetMsgs(msgs...) s.Require().NoError(err)