From bb0656d52a4ac3eebd307b8957f15dd111a16be1 Mon Sep 17 00:00:00 2001 From: Khanh Hoa Date: Mon, 27 May 2024 16:24:51 +0700 Subject: [PATCH 1/5] fix: add test and fix miss BeginBlock --- scripts/run-node.sh | 1 + tests/interchaintest/chain_core_test.go | 766 ++++++++++++++++++ .../chain_miscellaneous_test.go | 353 ++++++++ .../interchaintest/contracts/cw_template.wasm | Bin 0 -> 132664 bytes x/mint/abci.go | 24 +- x/mint/module.go | 5 + x/ratelimit/keeper/abci.go | 22 +- x/ratelimit/module.go | 10 +- x/stakingmiddleware/keeper/genesis.go | 2 + x/transfermiddleware/keeper/abci.go | 12 +- x/transfermiddleware/module.go | 5 +- 11 files changed, 1175 insertions(+), 25 deletions(-) create mode 100644 tests/interchaintest/chain_core_test.go create mode 100644 tests/interchaintest/chain_miscellaneous_test.go create mode 100644 tests/interchaintest/contracts/cw_template.wasm diff --git a/scripts/run-node.sh b/scripts/run-node.sh index eb685ff8c..f0342382c 100755 --- a/scripts/run-node.sh +++ b/scripts/run-node.sh @@ -58,6 +58,7 @@ $BINARY gentx $KEY 10030009994127689ppica --keyring-backend $KEYRING --chain-id update_test_genesis '.app_state["gov"]["params"]["voting_period"]="20s"' update_test_genesis '.app_state["gov"]["params"]["expedited_voting_period"]="10s"' +update_test_genesis '.app_state["stakingmiddleware"]["params"]["blocks_per_epoch"]="5"' update_test_genesis '.app_state["mint"]["params"]["mint_denom"]="'$DENOM'"' update_test_genesis '.app_state["gov"]["params"]["min_deposit"]=[{"denom":"'$DENOM'","amount": "1"}]' update_test_genesis '.app_state["crisis"]["constant_fee"]={"denom":"'$DENOM'","amount":"1000"}' diff --git a/tests/interchaintest/chain_core_test.go b/tests/interchaintest/chain_core_test.go new file mode 100644 index 000000000..8efd2b47e --- /dev/null +++ b/tests/interchaintest/chain_core_test.go @@ -0,0 +1,766 @@ +package interchaintest + +import ( + "context" + "fmt" + "math" + "testing" + "time" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/strangelove-ventures/interchaintest/v8" + "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v8/ibc" + "github.com/strangelove-ventures/interchaintest/v8/testutil" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + vestingcli "github.com/cosmos/cosmos-sdk/x/auth/vesting/client/cli" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +var ( + numValsOne = 1 + numFullNodesZero = 0 + genesisAmt = sdkmath.NewInt(10_000_000_000) + + mnemonic = "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" + + denomMetadata = banktypes.Metadata{ + Description: "Denom metadata for TOK (token)", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "stake", + Exponent: 0, + Aliases: []string{}, + }, + { + Denom: "TOK", + Exponent: 6, + Aliases: []string{}, + }, + }, + Base: "stake", + Display: "TOK", + Name: "TOK", + Symbol: "TOK", + URI: "", + URIHash: "", + } + + baseBech32 = "pica" +) + +func TestCoreSDKCommands(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + t.Parallel() + + cosmos.SetSDKConfig(baseBech32) + + sdk47Genesis := []cosmos.GenesisKV{ + cosmos.NewGenesisKV("app_state.gov.params.voting_period", "15s"), + cosmos.NewGenesisKV("app_state.gov.params.max_deposit_period", "10s"), + cosmos.NewGenesisKV("app_state.stakingmiddleware.params.blocks_per_epoch", 5), + cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.denom", "stake"), + cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.amount", "1"), + cosmos.NewGenesisKV("app_state.bank.denom_metadata", []banktypes.Metadata{denomMetadata}), + } + + config := CentauriConfig + config.ModifyGenesis = cosmos.ModifyGenesis(sdk47Genesis) + + cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{ + { + Name: "centauri", + ChainConfig: config, + NumValidators: &numVals, + NumFullNodes: &numFullNodes, + }, + }) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + + chain := chains[0].(*cosmos.CosmosChain) + + ic := interchaintest.NewInterchain(). + AddChain(chain) + + ctx := context.Background() + client, network := interchaintest.DockerSetup(t) + + require.NoError(t, ic.Build(ctx, nil, interchaintest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + SkipPathCreation: true, + })) + t.Cleanup(func() { + _ = ic.Close() + }) + + // used in circuit + superAdmin, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, "acc0", mnemonic, genesisAmt, chain) + require.NoError(t, err) + + t.Run("authz", func(t *testing.T) { + users := interchaintest.GetAndFundTestUsers(t, ctx, "default", genesisAmt, chain, chain, chain) + testAuthz(ctx, t, chain, users) + }) + + t.Run("bank", func(t *testing.T) { + users := interchaintest.GetAndFundTestUsers(t, ctx, "default", genesisAmt, chain, chain, chain) + testBank(ctx, t, chain, users) + }) + + t.Run("distribution", func(t *testing.T) { + users := interchaintest.GetAndFundTestUsers(t, ctx, "default", genesisAmt, chain, chain, chain) + testDistribution(ctx, t, chain, users) + }) + + t.Run("feegrant", func(t *testing.T) { + users := interchaintest.GetAndFundTestUsers(t, ctx, "default", genesisAmt, chain, chain, chain, chain) + testFeeGrant(ctx, t, chain, users) + }) + + t.Run("gov", func(t *testing.T) { + users := interchaintest.GetAndFundTestUsers(t, ctx, "default", genesisAmt, chain, chain, chain) + testGov(ctx, t, chain, users) + }) + + t.Run("auth-vesting", func(t *testing.T) { + testAuth(ctx, t, chain) + testVesting(ctx, t, chain, superAdmin) + }) + + t.Run("upgrade", func(t *testing.T) { + testUpgrade(ctx, t, chain) + }) + + t.Run("staking", func(t *testing.T) { + users := interchaintest.GetAndFundTestUsers(t, ctx, "default", genesisAmt, chain, chain, chain) + testStaking(ctx, t, chain, users) + }) + + t.Run("slashing", func(t *testing.T) { + testSlashing(ctx, t, chain) + }) +} + +func testAuth(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + // get gov address + govAddr, err := chain.AuthQueryModuleAddress(ctx, "gov") + require.NoError(t, err) + require.NotEmpty(t, govAddr) + + // convert gov addr to bytes + govBz, err := chain.AccAddressFromBech32(govAddr) + require.NoError(t, err) + + // convert gov bytes back to string address + strAddr, err := chain.AuthAddressBytesToString(ctx, govBz) + require.NoError(t, err) + require.EqualValues(t, govAddr, strAddr) + + // convert gov string address back to bytes + bz, err := chain.AuthAddressStringToBytes(ctx, strAddr) + require.NoError(t, err) + require.EqualValues(t, govBz, bz) + + // params + p, err := chain.AuthQueryParams(ctx) + require.NoError(t, err) + require.NotNil(t, p) + require.True(t, p.MaxMemoCharacters > 0) + + // get all module accounts + accs, err := chain.AuthQueryModuleAccounts(ctx) + require.NoError(t, err) + require.NotEmpty(t, accs) + + // get the global bech32 prefix + bech32, err := chain.AuthQueryBech32Prefix(ctx) + require.NoError(t, err) + require.EqualValues(t, baseBech32, bech32) + + // get base info about an account + accInfo, err := chain.AuthQueryAccountInfo(ctx, govAddr) + require.NoError(t, err) + require.EqualValues(t, govAddr, accInfo.Address) + +} + +// testUpgrade test the queries for upgrade information. Actual upgrades take place in other test. +func testUpgrade(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + v, err := chain.UpgradeQueryAllModuleVersions(ctx) + require.NoError(t, err) + require.NotEmpty(t, v) + + // UpgradeQueryModuleVersion + authority, err := chain.UpgradeQueryAuthority(ctx) + require.NoError(t, err) + require.NotEmpty(t, authority) + + plan, err := chain.UpgradeQueryPlan(ctx) + require.NoError(t, err) + require.Nil(t, plan) + + _, err = chain.UpgradeQueryAppliedPlan(ctx, "") + require.NoError(t, err) +} + +func testAuthz(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + granter := users[0].FormattedAddress() + grantee := users[1].FormattedAddress() + + node := chain.GetNode() + + txRes, _ := node.AuthzGrant(ctx, users[0], grantee, "generic", "--msg-type", "/cosmos.bank.v1beta1.MsgSend") + require.EqualValues(t, 0, txRes.Code) + + grants, err := chain.AuthzQueryGrants(ctx, granter, grantee, "") + require.NoError(t, err) + require.Len(t, grants, 1) + require.EqualValues(t, grants[0].Authorization.TypeUrl, "/cosmos.authz.v1beta1.GenericAuthorization") + require.Contains(t, string(grants[0].Authorization.Value), "/cosmos.bank.v1beta1.MsgSend") + + byGrantee, err := chain.AuthzQueryGrantsByGrantee(ctx, grantee, "") + require.NoError(t, err) + require.Len(t, byGrantee, 1) + require.EqualValues(t, byGrantee[0].Granter, granter) + require.EqualValues(t, byGrantee[0].Grantee, grantee) + + byGranter, err := chain.AuthzQueryGrantsByGranter(ctx, granter, "") + require.NoError(t, err) + require.Len(t, byGranter, 1) + require.EqualValues(t, byGranter[0].Granter, granter) + require.EqualValues(t, byGranter[0].Grantee, grantee) + + fmt.Printf("grants: %+v %+v %+v\n", grants, byGrantee, byGranter) + + balanceBefore, err := chain.GetBalance(ctx, granter, chain.Config().Denom) + require.NoError(t, err) + fmt.Printf("balanceBefore: %+v\n", balanceBefore) + + sendAmt := 1234 + + nestedCmd := []string{ + chain.Config().Bin, + "tx", "bank", "send", granter, grantee, fmt.Sprintf("%d%s", sendAmt, chain.Config().Denom), + "--from", granter, "--generate-only", + "--chain-id", chain.GetNode().Chain.Config().ChainID, + "--node", chain.GetNode().Chain.GetRPCAddress(), + "--home", chain.GetNode().HomeDir(), + "--keyring-backend", keyring.BackendTest, + "--output", "json", + "--yes", + } + + resp, err := node.AuthzExec(ctx, users[1], nestedCmd) + require.NoError(t, err) + require.EqualValues(t, 0, resp.Code) + + balanceAfter, err := chain.GetBalance(ctx, granter, chain.Config().Denom) + require.NoError(t, err) + + fmt.Printf("balanceAfter: %+v\n", balanceAfter) + require.EqualValues(t, balanceBefore.SubRaw(int64(sendAmt)), balanceAfter) +} + +func testBank(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + user0 := users[0].FormattedAddress() + user1 := users[1].FormattedAddress() + user2 := users[2].FormattedAddress() + + b2, err := chain.BankQueryBalance(ctx, user1, chain.Config().Denom) + require.NoError(t, err) + + // send 1 token + sendAmt := int64(1) + _, err = sendTokens(ctx, chain, users[0], users[1], "", sendAmt) + require.NoError(t, err) + + // send multiple + err = chain.GetNode().BankMultiSend(ctx, users[0].KeyName(), []string{user1, user2}, sdkmath.NewInt(sendAmt), chain.Config().Denom) + require.NoError(t, err) + + // == balances == + // sendAmt*2 because of the multisend as well + b2New, err := chain.GetBalance(ctx, user1, chain.Config().Denom) + require.NoError(t, err) + require.Equal(t, b2.Add(sdkmath.NewInt(sendAmt*2)), b2New) + + b2All, err := chain.BankQueryAllBalances(ctx, user1) + require.NoError(t, err) + require.Equal(t, b2New, b2All.AmountOf(chain.Config().Denom)) + + // == spendable balances == + spendableBal, err := chain.BankQuerySpendableBalance(ctx, user0, chain.Config().Denom) + require.NoError(t, err) + + spendableBals, err := chain.BankQuerySpendableBalances(ctx, user0) + require.NoError(t, err) + require.Equal(t, spendableBal.Amount, spendableBals.AmountOf(chain.Config().Denom)) + + // == metadata == + meta, err := chain.BankQueryDenomMetadata(ctx, chain.Config().Denom) + require.NoError(t, err) + + meta2, err := chain.BankQueryDenomMetadataByQueryString(ctx, chain.Config().Denom) + require.NoError(t, err) + require.EqualValues(t, meta, meta2) + + allMeta, err := chain.BankQueryDenomsMetadata(ctx) + require.NoError(t, err) + require.Len(t, allMeta, 1) + require.EqualValues(t, allMeta[0].Display, meta.Display) + + // == params == + params, err := chain.BankQueryParams(ctx) + require.NoError(t, err) + require.True(t, params.DefaultSendEnabled) + + sendEnabled, err := chain.BankQuerySendEnabled(ctx, []string{chain.Config().Denom}) + require.NoError(t, err) + require.Len(t, sendEnabled, 0) + + // == supply == + supply, err := chain.BankQueryTotalSupply(ctx) + require.NoError(t, err) + + supplyOf, err := chain.BankQueryTotalSupplyOf(ctx, chain.Config().Denom) + require.NoError(t, err) + require.True(t, supplyOf.IsGTE(sdk.NewCoin(chain.Config().Denom, supply.AmountOf(chain.Config().Denom)))) + + // == denom owner == + denomOwner, err := chain.BankQueryDenomOwners(ctx, chain.Config().Denom) + require.NoError(t, err) + + found := false + for _, owner := range denomOwner { + if owner.Address == user0 { + found = true + break + } + } + require.True(t, found) +} + +func testDistribution(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + var err error + node := chain.GetNode() + err = testutil.WaitForBlocks(ctx, 10, node) + + acc := authtypes.NewModuleAddress("distribution") + require := require.New(t) + + vals, err := chain.StakingQueryValidators(ctx, stakingtypes.Bonded.String()) + require.NoError(err) + fmt.Printf("validators: %+v\n", vals) + + del, err := chain.StakingQueryDelegationsTo(ctx, vals[0].OperatorAddress) + require.NoError(err) + + delAddr := del[0].Delegation.DelegatorAddress + valAddr := del[0].Delegation.ValidatorAddress + + newWithdrawAddr := "pica1hj83l3auyqgy5qcp52l6sp2e67xwq9xxun76km" + + t.Run("misc queries", func(t *testing.T) { + slashes, err := chain.DistributionQueryValidatorSlashes(ctx, valAddr) + require.NoError(err) + require.EqualValues(0, len(slashes)) + + valDistInfo, err := chain.DistributionQueryValidatorDistributionInfo(ctx, valAddr) + require.NoError(err) + fmt.Printf("valDistInfo: %+v\n", valDistInfo) + require.EqualValues(1, valDistInfo.Commission.Len()) + + valOutRewards, err := chain.DistributionQueryValidatorOutstandingRewards(ctx, valAddr) + require.NoError(err) + require.EqualValues(1, valOutRewards.Rewards.Len()) + + params, err := chain.DistributionQueryParams(ctx) + require.NoError(err) + require.True(params.WithdrawAddrEnabled) + + comm, err := chain.DistributionQueryCommission(ctx, valAddr) + require.NoError(err) + require.EqualValues(chain.Config().Denom, comm.Commission[0].Denom) + }) + + t.Run("withdraw-all-rewards", func(t *testing.T) { + err = node.StakingDelegate(ctx, users[2].KeyName(), valAddr, fmt.Sprintf("%d%s", uint64(100*math.Pow10(6)), chain.Config().Denom)) + require.NoError(err) + + before, err := chain.BankQueryBalance(ctx, acc.String(), chain.Config().Denom) + require.NoError(err) + fmt.Printf("before: %+v\n", before) + + err = node.DistributionWithdrawAllRewards(ctx, users[2].KeyName()) + require.NoError(err) + + after, err := chain.BankQueryBalance(ctx, acc.String(), chain.Config().Denom) + require.NoError(err) + fmt.Printf("after: %+v\n", after) + require.True(after.GT(before)) + }) + + t.Run("fund-pools", func(t *testing.T) { + bal, err := chain.BankQueryBalance(ctx, acc.String(), chain.Config().Denom) + require.NoError(err) + fmt.Printf("CP balance: %+v\n", bal) + + amount := uint64(9_000 * math.Pow10(6)) + + err = node.DistributionFundCommunityPool(ctx, users[0].KeyName(), fmt.Sprintf("%d%s", amount, chain.Config().Denom)) + require.NoError(err) + + err = node.DistributionFundValidatorRewardsPool(ctx, users[0].KeyName(), valAddr, fmt.Sprintf("%d%s", uint64(100*math.Pow10(6)), chain.Config().Denom)) + require.NoError(err) + + bal2, err := chain.BankQueryBalance(ctx, acc.String(), chain.Config().Denom) + require.NoError(err) + fmt.Printf("New CP balance: %+v\n", bal2) // 9147579661 + + require.True(bal2.Sub(bal).GT(sdkmath.NewInt(int64(amount)))) + + // queries + coins, err := chain.DistributionQueryCommunityPool(ctx) + require.NoError(err) + require.True(coins.AmountOf(chain.Config().Denom).GT(sdkmath.LegacyNewDec(int64(amount)))) + }) + + t.Run("set-custiom-withdraw-address", func(t *testing.T) { + err = node.DistributionSetWithdrawAddr(ctx, users[0].KeyName(), newWithdrawAddr) + require.NoError(err) + + withdrawAddr, err := chain.DistributionQueryDelegatorWithdrawAddress(ctx, users[0].FormattedAddress()) + require.NoError(err) + require.EqualValues(withdrawAddr, newWithdrawAddr) + }) + + t.Run("delegator", func(t *testing.T) { + delRewards, err := chain.DistributionQueryDelegationTotalRewards(ctx, delAddr) + require.NoError(err) + r := delRewards.Rewards[0] + require.EqualValues(valAddr, r.ValidatorAddress) + require.EqualValues(chain.Config().Denom, r.Reward[0].Denom) + + delegatorVals, err := chain.DistributionQueryDelegatorValidators(ctx, delAddr) + require.NoError(err) + require.EqualValues(valAddr, delegatorVals.Validators[0]) + + rewards, err := chain.DistributionQueryRewards(ctx, delAddr, valAddr) + require.NoError(err) + require.EqualValues(1, rewards.Len()) + }) +} + +func testFeeGrant(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + var err error + node := chain.GetNode() + + denom := chain.Config().Denom + + t.Run("successful grant and queries", func(t *testing.T) { + granter := users[0] + grantee := users[1] + + err = node.FeeGrant(ctx, granter.KeyName(), grantee.FormattedAddress(), fmt.Sprintf("%d%s", 1000, chain.Config().Denom), []string{"/cosmos.bank.v1beta1.MsgSend"}, time.Now().Add(time.Hour*24*365)) + require.NoError(t, err) + + g, err := chain.FeeGrantQueryAllowance(ctx, granter.FormattedAddress(), grantee.FormattedAddress()) + require.NoError(t, err) + fmt.Printf("g: %+v\n", g) + require.EqualValues(t, granter.FormattedAddress(), g.Granter) + require.EqualValues(t, grantee.FormattedAddress(), g.Grantee) + require.EqualValues(t, "/cosmos.feegrant.v1beta1.AllowedMsgAllowance", g.Allowance.TypeUrl) + require.Contains(t, string(g.Allowance.Value), "/cosmos.bank.v1beta1.MsgSend") + + all, err := chain.FeeGrantQueryAllowances(ctx, grantee.FormattedAddress()) + require.NoError(t, err) + require.Len(t, all, 1) + require.EqualValues(t, granter.FormattedAddress(), all[0].Granter) + + all2, err := chain.FeeGrantQueryAllowancesByGranter(ctx, granter.FormattedAddress()) + require.NoError(t, err) + require.Len(t, all2, 1) + require.EqualValues(t, grantee.FormattedAddress(), all2[0].Grantee) + }) + + t.Run("successful execution", func(t *testing.T) { + granter2 := users[2] + grantee2 := users[3] + + err = node.FeeGrant(ctx, granter2.KeyName(), grantee2.FormattedAddress(), fmt.Sprintf("%d%s", 100_000, denom), nil, time.Unix(0, 0)) + require.NoError(t, err) + + bal, err := chain.BankQueryBalance(ctx, granter2.FormattedAddress(), denom) + require.NoError(t, err) + + fee := 500 + sendAmt := 501 + sendCoin := fmt.Sprintf("%d%s", sendAmt, denom) + feeCoin := fmt.Sprintf("%d%s", fee, denom) + + _, err = node.ExecTx(ctx, + grantee2.KeyName(), "bank", "send", grantee2.KeyName(), granter2.FormattedAddress(), sendCoin, + "--fees", feeCoin, "--fee-granter", granter2.FormattedAddress(), + ) + require.NoError(t, err) + + newBal, err := chain.BankQueryBalance(ctx, granter2.FormattedAddress(), denom) + require.NoError(t, err) + require.EqualValues(t, bal.AddRaw(int64(sendAmt-fee)), newBal) + }) +} + +func testGov(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + node := chain.GetNode() + + govModule := "pica10d07y265gmmuvt4z0w9aw880jnsr700jp7sqj5" + coin := sdk.NewCoin(chain.Config().Denom, sdkmath.NewInt(1)) + + bankMsg := &banktypes.MsgSend{ + FromAddress: govModule, + ToAddress: users[1].FormattedAddress(), + Amount: sdk.NewCoins(coin), + } + + // submit governance proposal + title := "Test Proposal" + prop, err := chain.BuildProposal([]cosmos.ProtoMessage{bankMsg}, title, title+" Summary", "none", "500"+chain.Config().Denom, govModule, false) + require.NoError(t, err) + + _, err = node.GovSubmitProposal(ctx, users[0].KeyName(), prop) + require.NoError(t, err) + + proposal, err := chain.GovQueryProposalV1(ctx, 1) + require.NoError(t, err) + require.EqualValues(t, proposal.Title, title) + + // vote on the proposal + err = node.VoteOnProposal(ctx, users[0].KeyName(), 1, "yes") + require.NoError(t, err) + + v, err := chain.GovQueryVote(ctx, 1, users[0].FormattedAddress()) + require.NoError(t, err) + require.EqualValues(t, v.Options[0].Option, govv1.VoteOption_VOTE_OPTION_YES) + + // pass vote with all validators + err = chain.VoteOnProposalAllValidators(ctx, "1", "yes") + require.NoError(t, err) + + // GovQueryProposalsV1 + proposals, err := chain.GovQueryProposalsV1(ctx, govv1.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD) + require.NoError(t, err) + require.Len(t, proposals, 1) + + require.NoError(t, testutil.WaitForBlocks(ctx, 10, chain)) + + // Proposal fails due to gov not having any funds + proposals, err = chain.GovQueryProposalsV1(ctx, govv1.ProposalStatus_PROPOSAL_STATUS_FAILED) + require.NoError(t, err) + require.Len(t, proposals, 1) +} + +func testSlashing(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + p, err := chain.SlashingQueryParams(ctx) + require.NoError(t, err) + require.NotNil(t, p) + + infos, err := chain.SlashingQuerySigningInfos(ctx) + require.NoError(t, err) + require.NotEmpty(t, infos) + + si, err := chain.SlashingQuerySigningInfo(ctx, infos[0].Address) + require.NoError(t, err) + require.NotNil(t, si) +} + +func testStaking(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + vals, err := chain.StakingQueryValidators(ctx, stakingtypes.Bonded.String()) + require.NoError(t, err) + require.NotEmpty(t, vals) + + val := vals[0].OperatorAddress + user := users[0].FormattedAddress() + + t.Run("query validators", func(t *testing.T) { + valInfo, err := chain.StakingQueryValidator(ctx, val) + require.NoError(t, err) + require.EqualValues(t, val, valInfo.OperatorAddress) + require.EqualValues(t, stakingtypes.Bonded.String(), valInfo.Status.String()) + + del, err := chain.StakingQueryDelegationsTo(ctx, val) + require.NoError(t, err) + require.NotEmpty(t, del) + + del0 := del[0].Delegation.DelegatorAddress + + allDels, err := chain.StakingQueryDelegations(ctx, del0) + require.NoError(t, err) + require.NotEmpty(t, allDels) + + singleDel, err := chain.StakingQueryDelegation(ctx, val, del0) + require.NoError(t, err) + require.EqualValues(t, del0, singleDel.Delegation.DelegatorAddress) + + // StakingQueryDelegatorValidator + delVal, err := chain.StakingQueryDelegatorValidator(ctx, del0, val) + require.NoError(t, err) + require.True(t, delVal.OperatorAddress == val) + + delVals, err := chain.StakingQueryDelegatorValidators(ctx, del0) + require.NoError(t, err) + require.NotEmpty(t, delVals) + require.True(t, delVals[0].OperatorAddress == val) + + }) + + t.Run("misc", func(t *testing.T) { + params, err := chain.StakingQueryParams(ctx) + require.NoError(t, err) + require.EqualValues(t, "stake", params.BondDenom) + + pool, err := chain.StakingQueryPool(ctx) + require.NoError(t, err) + require.True(t, pool.BondedTokens.GT(sdkmath.NewInt(0))) + + height, err := chain.Height(ctx) + require.NoError(t, err) + + searchHeight := int64(height - 1) + + hi, err := chain.StakingQueryHistoricalInfo(ctx, searchHeight) + require.NoError(t, err) + require.EqualValues(t, searchHeight, hi.Header.Height) + }) + + t.Run("delegations", func(t *testing.T) { + node := chain.GetNode() + + err := node.StakingDelegate(ctx, users[0].KeyName(), val, "1000"+chain.Config().Denom) + require.NoError(t, err) + + dels, err := chain.StakingQueryDelegations(ctx, users[0].FormattedAddress()) + require.NoError(t, err) + found := false + for _, d := range dels { + if d.Balance.Amount.Equal(sdkmath.NewInt(1000)) { + found = true + break + } + } + require.True(t, found) + + // unbond + err = node.StakingUnbond(ctx, users[0].KeyName(), val, "25"+chain.Config().Denom) + require.NoError(t, err) + + unbonding, err := chain.StakingQueryUnbondingDelegation(ctx, user, val) + require.NoError(t, err) + require.EqualValues(t, user, unbonding.DelegatorAddress) + require.EqualValues(t, val, unbonding.ValidatorAddress) + + height := unbonding.Entries[0].CreationHeight + + unbondings, err := chain.StakingQueryUnbondingDelegations(ctx, user) + require.NoError(t, err) + require.NotEmpty(t, unbondings) + require.EqualValues(t, user, unbondings[0].DelegatorAddress) + + // StakingQueryUnbondingDelegationsFrom + unbondingsFrom, err := chain.StakingQueryUnbondingDelegationsFrom(ctx, val) + require.NoError(t, err) + require.NotEmpty(t, unbondingsFrom) + require.EqualValues(t, user, unbondingsFrom[0].DelegatorAddress) + + // StakingCancelUnbond + err = node.StakingCancelUnbond(ctx, user, val, "25"+chain.Config().Denom, height) + require.NoError(t, err) + + // ensure unbonding delegation is gone + unbondings, err = chain.StakingQueryUnbondingDelegations(ctx, user) + require.NoError(t, err) + require.Empty(t, unbondings) + }) +} + +func testVesting(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, admin ibc.Wallet) { + t.Parallel() + + var err error + var acc string + node := chain.GetNode() + + currentUnixSeconds := time.Now().Unix() + endTime := currentUnixSeconds + 60 + + t.Run("normal vesting account", func(t *testing.T) { + acc = "pica1w8arfu23uygwse72ym3krk2nhntlgdfv97pez9" + + err = node.VestingCreateAccount(ctx, admin.KeyName(), acc, "111stake", endTime) + require.NoError(t, err) + + res, err := chain.AuthQueryAccount(ctx, acc) + require.NoError(t, err) + require.EqualValues(t, "/cosmos.vesting.v1beta1.ContinuousVestingAccount", res.TypeUrl) + chain.AuthPrintAccountInfo(chain, res) + }) + + t.Run("perm locked account", func(t *testing.T) { + acc = "pica135e3r3l5333094zd37kw8s7htn7087pr8s9mj7" + + err = node.VestingCreatePermanentLockedAccount(ctx, admin.KeyName(), acc, "112stake") + require.NoError(t, err) + + res, err := chain.AuthQueryAccount(ctx, acc) + require.NoError(t, err) + require.EqualValues(t, "/cosmos.vesting.v1beta1.PermanentLockedAccount", res.TypeUrl) + chain.AuthPrintAccountInfo(chain, res) + }) + + t.Run("periodic account", func(t *testing.T) { + acc = "pica1hkar47a0ysml3fhw2jgyrnrvwq9z8tk7ead5k9" + + err = node.VestingCreatePeriodicAccount(ctx, admin.KeyName(), acc, vestingcli.VestingData{ + StartTime: currentUnixSeconds, + Periods: []vestingcli.InputPeriod{ + { + Coins: "100stake", + Length: 30, // 30 seconds + }, + { + Coins: "101stake", + Length: 30, // 30 seconds + }, + { + Coins: "102stake", + Length: 30, // 30 seconds + }, + }, + }) + require.NoError(t, err) + + res, err := chain.AuthQueryAccount(ctx, acc) + require.NoError(t, err) + require.EqualValues(t, "/cosmos.vesting.v1beta1.PeriodicVestingAccount", res.TypeUrl) + chain.AuthPrintAccountInfo(chain, res) + }) + + t.Run("Base Account", func(t *testing.T) { + res, err := chain.AuthQueryAccount(ctx, admin.FormattedAddress()) + require.NoError(t, err) + require.EqualValues(t, "/cosmos.auth.v1beta1.BaseAccount", res.TypeUrl) + chain.AuthPrintAccountInfo(chain, res) + }) +} diff --git a/tests/interchaintest/chain_miscellaneous_test.go b/tests/interchaintest/chain_miscellaneous_test.go new file mode 100644 index 000000000..9e7a7a1e9 --- /dev/null +++ b/tests/interchaintest/chain_miscellaneous_test.go @@ -0,0 +1,353 @@ +package interchaintest + +import ( + "context" + "fmt" + "testing" + + "cosmossdk.io/math" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + testutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/strangelove-ventures/interchaintest/v8" + "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v8/ibc" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +var ( + numVals = 1 + numFullNodes = 0 +) + +func TestICTestMiscellaneous(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + cosmos.SetSDKConfig("pica") + + sdk47Genesis := []cosmos.GenesisKV{ + cosmos.NewGenesisKV("app_state.gov.params.voting_period", "15s"), + cosmos.NewGenesisKV("app_state.gov.params.max_deposit_period", "10s"), + cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.denom", "ppica"), + cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.amount", "1"), + } + + config := CentauriConfig + config.ModifyGenesis = cosmos.ModifyGenesis(sdk47Genesis) + + cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{ + { + Name: "centauri", + ChainConfig: config, + NumValidators: &numVals, + NumFullNodes: &numFullNodes, + }, + }) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + + chain := chains[0].(*cosmos.CosmosChain) + + ic := interchaintest.NewInterchain(). + AddChain(chain) + + ctx := context.Background() + client, network := interchaintest.DockerSetup(t) + + require.NoError(t, ic.Build(ctx, nil, interchaintest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + SkipPathCreation: true, + })) + t.Cleanup(func() { + _ = ic.Close() + }) + + users := interchaintest.GetAndFundTestUsers(t, ctx, "default", math.NewInt(10_000_000_000), chain, chain) + + testWalletKeys(ctx, t, chain) + testSendingTokens(ctx, t, chain, users) + testFindTxs(ctx, t, chain, users) // not supported with CometMock + testPollForBalance(ctx, t, chain, users) + testRangeBlockMessages(ctx, t, chain, users) + testBroadcaster(ctx, t, chain, users) + testQueryCmd(ctx, t, chain) + testHasCommand(ctx, t, chain) + testFailedCWExecute(ctx, t, chain, users) + testAddingNode(ctx, t, chain) + testGetGovernanceAddress(ctx, t, chain) + testTXFailsOnBlockInclusion(ctx, t, chain, users) +} + +func wasmEncoding() *testutil.TestEncodingConfig { + cfg := cosmos.DefaultEncoding() + wasmtypes.RegisterInterfaces(cfg.InterfaceRegistry) + return &cfg +} + +func testFailedCWExecute(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + user := users[0] + keyName := user.KeyName() + + codeId, err := chain.StoreContract(ctx, keyName, "contracts/cw_template.wasm") + if err != nil { + t.Fatal(err) + } + + contractAddr, err := chain.InstantiateContract(ctx, keyName, codeId, `{"count":0}`, true) + if err != nil { + t.Fatal(err) + } + + // execute on the contract with the wrong message (err) + txResp, err := chain.ExecuteContract(ctx, keyName, contractAddr, `{"not_a_func":{}}`) + require.Error(t, err) + fmt.Printf("txResp.RawLog: %+v\n", txResp.RawLog) + fmt.Printf("err: %+v\n", err) + require.Contains(t, err.Error(), "failed to execute message") +} + +func testWalletKeys(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + // create a general key + randKey := "randkey123" + err := chain.CreateKey(ctx, randKey) + require.NoError(t, err) + + // verify key was created properly + _, err = chain.GetAddress(ctx, randKey) + require.NoError(t, err) + + // recover a key + // juno1hj5fveer5cjtn4wd6wstzugjfdxzl0xps73ftl + keyName := "key-abc" + testMnemonic := "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" + wallet, err := chain.BuildWallet(ctx, keyName, testMnemonic) + require.NoError(t, err) + + // verify + addr, err := chain.GetAddress(ctx, keyName) + require.NoError(t, err) + require.Equal(t, wallet.Address(), addr) + + tn := chain.Validators[0] + a, err := tn.KeyBech32(ctx, "key-abc", "val") + require.NoError(t, err) + require.Equal(t, a, "picavaloper1hj5fveer5cjtn4wd6wstzugjfdxzl0xpaf78xu") + + a, err = tn.KeyBech32(ctx, "key-abc", "acc") + require.NoError(t, err) + require.Equal(t, a, wallet.FormattedAddress()) + + a, err = tn.AccountKeyBech32(ctx, "key-abc") + require.NoError(t, err) + require.Equal(t, a, wallet.FormattedAddress()) +} + +func testSendingTokens(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + _, err := chain.GetBalance(ctx, users[0].FormattedAddress(), chain.Config().Denom) + require.NoError(t, err) + b2, err := chain.GetBalance(ctx, users[1].FormattedAddress(), chain.Config().Denom) + require.NoError(t, err) + + sendAmt := int64(1) + _, err = sendTokens(ctx, chain, users[0], users[1], "", sendAmt) + require.NoError(t, err) + + b2New, err := chain.GetBalance(ctx, users[1].FormattedAddress(), chain.Config().Denom) + require.NoError(t, err) + + require.Equal(t, b2.Add(math.NewInt(sendAmt)), b2New) +} + +func testFindTxs(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + height, _ := chain.Height(ctx) + + _, err := sendTokens(ctx, chain, users[0], users[1], "", 1) + require.NoError(t, err) + + txs, err := chain.FindTxs(ctx, height+1) + require.NoError(t, err) + require.NotEmpty(t, txs) + require.Equal(t, txs[0].Events[0].Type, "tx") +} + +func testPollForBalance(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + bal2, err := chain.GetBalance(ctx, users[1].FormattedAddress(), chain.Config().Denom) + require.NoError(t, err) + + amt := ibc.WalletAmount{ + Address: users[1].FormattedAddress(), + Denom: chain.Config().Denom, + Amount: math.NewInt(1), + } + + delta := int64(3) + + ch := make(chan error) + go func() { + new := amt + new.Amount = bal2.Add(math.NewInt(1)) + ch <- cosmos.PollForBalance(ctx, chain, delta, new) + }() + + err = chain.SendFunds(ctx, users[0].KeyName(), amt) + require.NoError(t, err) + require.NoError(t, <-ch) +} + +func testRangeBlockMessages(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + height, _ := chain.Height(ctx) + + _, err := sendTokens(ctx, chain, users[0], users[1], "", 1) + require.NoError(t, err) + + var bankMsgs []*banktypes.MsgSend + err = cosmos.RangeBlockMessages(ctx, chain.Config().EncodingConfig.InterfaceRegistry, chain.Validators[0].Client, height+1, func(msg sdk.Msg) bool { + found, ok := msg.(*banktypes.MsgSend) + if ok { + bankMsgs = append(bankMsgs, found) + } + return false + }) + require.NoError(t, err) +} + +func testAddingNode(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + // This should be tested last or else Txs will fail on the new full node. + nodesAmt := len(chain.Nodes()) + chain.AddFullNodes(ctx, nil, 1) + require.Equal(t, nodesAmt+1, len(chain.Nodes())) +} + +func testBroadcaster(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + from := users[0].FormattedAddress() + addr1 := "pica190g5j8aszqhvtg7cprmev8xcxs6csra7tak0s2" + addr2 := "pica1a53udazy8ayufvy0s434pfwjcedzqv34dspq0f" + + c1 := sdk.NewCoins(sdk.NewCoin(chain.Config().Denom, math.NewInt(1))) + c2 := sdk.NewCoins(sdk.NewCoin(chain.Config().Denom, math.NewInt(2))) + + b := cosmos.NewBroadcaster(t, chain) + + in := banktypes.Input{ + Address: from, + Coins: c1.Add(c2[0]), + } + out := []banktypes.Output{ + { + Address: addr1, + Coins: c1, + }, + { + Address: addr2, + Coins: c2, + }, + } + + txResp, err := cosmos.BroadcastTx( + ctx, + b, + users[0], + banktypes.NewMsgMultiSend(in, out), + ) + require.NoError(t, err) + require.NotEmpty(t, txResp.TxHash) + fmt.Printf("txResp: %+v\n", txResp) + + updatedBal1, err := chain.GetBalance(ctx, addr1, chain.Config().Denom) + require.NoError(t, err) + require.Equal(t, math.NewInt(1), updatedBal1) + + updatedBal2, err := chain.GetBalance(ctx, addr2, chain.Config().Denom) + require.NoError(t, err) + require.Equal(t, math.NewInt(2), updatedBal2) + + txResp, err = cosmos.BroadcastTx( + ctx, + b, + users[0], + banktypes.NewMsgMultiSend(banktypes.Input{ + Address: addr1, + Coins: c1.Add(c2[0]), + }, out), + ) + require.Error(t, err) +} + +func testQueryCmd(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + tn := chain.Validators[0] + stdout, stderr, err := tn.ExecQuery(ctx, "slashing", "params") + require.NoError(t, err) + require.NotEmpty(t, stdout) + require.Empty(t, stderr) +} + +func testHasCommand(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + tn := chain.Validators[0] + res := tn.HasCommand(ctx, "query") + require.True(t, res) + + if tn.IsAboveSDK47(ctx) { + require.True(t, tn.HasCommand(ctx, "genesis")) + } else { + // 45 does not have this + require.False(t, tn.HasCommand(ctx, "genesis")) + } + + require.True(t, tn.HasCommand(ctx, "tx", "ibc")) + require.True(t, tn.HasCommand(ctx, "q", "ibc")) + require.True(t, tn.HasCommand(ctx, "keys")) + require.True(t, tn.HasCommand(ctx, "help")) + require.True(t, tn.HasCommand(ctx, "tx", "bank", "send")) + + require.False(t, tn.HasCommand(ctx, "tx", "bank", "send2notrealcmd")) + require.False(t, tn.HasCommand(ctx, "incorrectcmd")) +} + +func testGetGovernanceAddress(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + govAddr, err := chain.GetGovernanceAddress(ctx) + require.NoError(t, err) + _, err = chain.AccAddressFromBech32(govAddr) + require.NoError(t, err) +} + +func testTXFailsOnBlockInclusion(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + // this isn't a real validator, but is well formed, so it will only fail once a validator checks the staking transaction + fakeValoper, err := chain.GetNode().KeyBech32(ctx, users[0].KeyName(), "val") + require.NoError(t, err) + + txHash, err := chain.GetNode().ExecTx(ctx, users[0].FormattedAddress(), + "staking", "delegate", fakeValoper, "100"+chain.Config().Denom) + transaction, err := chain.GetTransaction(txHash) + require.NoError(t, err) + require.Equal(t, "failed to execute message; message index: 0: validator does not exist", transaction.RawLog) +} + +// helpers +func sendTokens(ctx context.Context, chain *cosmos.CosmosChain, from, to ibc.Wallet, token string, amount int64) (ibc.WalletAmount, error) { + if token == "" { + token = chain.Config().Denom + } + + sendAmt := ibc.WalletAmount{ + Address: to.FormattedAddress(), + Denom: token, + Amount: math.NewInt(amount), + } + err := chain.SendFunds(ctx, from.KeyName(), sendAmt) + return sendAmt, err +} + +func validateBalance(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, user ibc.Wallet, tfDenom string, expected int64) { + balance, err := chain.GetBalance(ctx, user.FormattedAddress(), tfDenom) + require.NoError(t, err) + require.Equal(t, balance, math.NewInt(expected)) +} diff --git a/tests/interchaintest/contracts/cw_template.wasm b/tests/interchaintest/contracts/cw_template.wasm new file mode 100644 index 0000000000000000000000000000000000000000..909cbdd198caf9ececdf0c288ceda0b1a4d912f2 GIT binary patch literal 132664 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 M(t{<21#`lG0|9DE)c^nh literal 0 HcmV?d00001 diff --git a/x/mint/abci.go b/x/mint/abci.go index d322e6723..6fc61d605 100644 --- a/x/mint/abci.go +++ b/x/mint/abci.go @@ -1,6 +1,7 @@ package mint import ( + "context" "time" "github.com/cosmos/cosmos-sdk/telemetry" @@ -11,37 +12,40 @@ import ( ) // BeginBlocker mints new tokens for the previous block. -func BeginBlocker(ctx sdk.Context, k keeper.Keeper, ic types.InflationCalculationFn) { +func BeginBlocker(ctx context.Context, k keeper.Keeper, ic types.InflationCalculationFn) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + sdkCtx := sdk.UnwrapSDKContext(ctx) + // fetch stored minter & params - minter := k.GetMinter(ctx) - params := k.GetParams(ctx) + minter := k.GetMinter(sdkCtx) + params := k.GetParams(sdkCtx) // recalculate inflation rate - totalStakingSupply, _ := k.StakingTokenSupply(ctx) - bondedRatio, _ := k.BondedRatio(ctx) - minter.Inflation = ic(ctx, minter, params, bondedRatio, totalStakingSupply) + totalStakingSupply, _ := k.StakingTokenSupply(sdkCtx) + bondedRatio, _ := k.BondedRatio(sdkCtx) + minter.Inflation = ic(sdkCtx, minter, params, bondedRatio, totalStakingSupply) minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalStakingSupply) - k.SetMinter(ctx, minter) + k.SetMinter(sdkCtx, minter) // calculate how many we would mint, but we dont mint them, we take them from the prefunded account mintedCoin := minter.BlockProvision(params) mintedCoins := sdk.NewCoins(mintedCoin) // send the minted coins to the fee collector account - err := k.AddCollectedFees(ctx, mintedCoins) + err := k.AddCollectedFees(sdkCtx, mintedCoins) if err != nil { - k.Logger(ctx).Info("Not enough incentive tokens in the mint pool to distribute") + k.Logger(sdkCtx).Info("Not enough incentive tokens in the mint pool to distribute") } if mintedCoin.Amount.IsInt64() { defer telemetry.ModuleSetGauge(types.ModuleName, float32(mintedCoin.Amount.Int64()), "minted_tokens") } - ctx.EventManager().EmitEvent( + sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeReward, sdk.NewAttribute(sdk.AttributeKeyAmount, mintedCoin.Amount.String()), ), ) + return nil } diff --git a/x/mint/module.go b/x/mint/module.go index 194fc198c..5a2711eae 100644 --- a/x/mint/module.go +++ b/x/mint/module.go @@ -149,6 +149,11 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion implements AppModule/ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 1 } +// BeginBlock returns the begin blocker for the mint module. +func (am AppModule) BeginBlock(ctx context.Context) error { + return BeginBlocker(ctx, am.keeper, am.inflationCalculator) +} + // AppModuleSimulation functions // GenerateGenesisState creates a randomized GenState of the mint module. func (AppModule) GenerateGenesisState(simState *module.SimulationState) { diff --git a/x/ratelimit/keeper/abci.go b/x/ratelimit/keeper/abci.go index a3789e223..e05e79d55 100644 --- a/x/ratelimit/keeper/abci.go +++ b/x/ratelimit/keeper/abci.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "fmt" "time" @@ -11,25 +12,27 @@ import ( ) // BeginBlocker of epochs module. -func (k Keeper) BeginBlocker(ctx sdk.Context) { +func (k Keeper) BeginBlocker(ctx context.Context) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) - k.IterateEpochInfo(ctx, func(index int64, epochInfo types.EpochInfo) (stop bool) { - logger := k.Logger(ctx) + k.IterateEpochInfo(sdkCtx, func(index int64, epochInfo types.EpochInfo) (stop bool) { + logger := k.Logger(sdkCtx) // If blocktime < initial epoch start time, return - if ctx.BlockTime().Before(epochInfo.StartTime) { + if sdkCtx.BlockTime().Before(epochInfo.StartTime) { return true } // if epoch counting hasn't started, signal we need to start. shouldInitialEpochStart := !epochInfo.EpochCountingStarted epochEndTime := epochInfo.CurrentEpochStartTime.Add(epochInfo.Duration) - shouldEpochStart := (ctx.BlockTime().After(epochEndTime)) || shouldInitialEpochStart + shouldEpochStart := (sdkCtx.BlockTime().After(epochEndTime)) || shouldInitialEpochStart if !shouldEpochStart { return false } - epochInfo.CurrentEpochStartHeight = ctx.BlockHeight() + epochInfo.CurrentEpochStartHeight = sdkCtx.BlockHeight() if shouldInitialEpochStart { epochInfo.EpochCountingStarted = true @@ -37,22 +40,23 @@ func (k Keeper) BeginBlocker(ctx sdk.Context) { epochInfo.CurrentEpochStartTime = epochInfo.StartTime logger.Info(fmt.Sprintf("Starting new epoch with identifier %s epoch number %d", epochInfo.Identifier, epochInfo.CurrentEpoch)) } else { - k.AfterEpochEnd(ctx, epochInfo) + k.AfterEpochEnd(sdkCtx, epochInfo) epochInfo.CurrentEpoch++ epochInfo.CurrentEpochStartTime = epochInfo.CurrentEpochStartTime.Add(epochInfo.Duration) logger.Info(fmt.Sprintf("Starting epoch with identifier %s epoch number %d", epochInfo.Identifier, epochInfo.CurrentEpoch)) } // emit new epoch start event, set epoch info, and run BeforeEpochStart hook - ctx.EventManager().EmitEvent( + sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeEpochStart, sdk.NewAttribute(types.AttributeEpochNumber, fmt.Sprintf("%d", epochInfo.CurrentEpoch)), sdk.NewAttribute(types.AttributeEpochStartTime, fmt.Sprintf("%d", epochInfo.CurrentEpochStartTime.Unix())), ), ) - k.setEpochInfo(ctx, epochInfo) + k.setEpochInfo(sdkCtx, epochInfo) return false }) + return nil } diff --git a/x/ratelimit/module.go b/x/ratelimit/module.go index c17e7b48c..9d5e140eb 100644 --- a/x/ratelimit/module.go +++ b/x/ratelimit/module.go @@ -136,7 +136,15 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion implements AppModule/ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 1 } -// AppModuleSimulation functions +// BeginBlock implements the AppModule interface +func (am AppModule) BeginBlock(ctx context.Context) error { + return am.keeper.BeginBlocker(ctx) +} + +// EndBlock implements the AppModule interface +func (am AppModule) EndBlock(_ context.Context) ([]abci.ValidatorUpdate, error) { + return []abci.ValidatorUpdate{}, nil +} // GenerateGenesisState creates a randomized GenState of the router module. func (AppModule) GenerateGenesisState(_ *module.SimulationState) {} diff --git a/x/stakingmiddleware/keeper/genesis.go b/x/stakingmiddleware/keeper/genesis.go index c50d10dcf..ab160a7ba 100644 --- a/x/stakingmiddleware/keeper/genesis.go +++ b/x/stakingmiddleware/keeper/genesis.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/notional-labs/composable/v6/x/stakingmiddleware/types" ) @@ -8,6 +9,7 @@ import ( // InitGenesis new stake middleware genesis func (keeper Keeper) InitGenesis(ctx sdk.Context, data *types.GenesisState) { if err := keeper.SetParams(ctx, data.Params); err != nil { + fmt.Println(err) panic(err) } } diff --git a/x/transfermiddleware/keeper/abci.go b/x/transfermiddleware/keeper/abci.go index a3f0479d8..ff6f24b08 100644 --- a/x/transfermiddleware/keeper/abci.go +++ b/x/transfermiddleware/keeper/abci.go @@ -1,19 +1,23 @@ package keeper import ( + "context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/notional-labs/composable/v6/x/transfermiddleware/types" ) // BeginBlocker of epochs module. -func (k Keeper) BeginBlocker(ctx sdk.Context) { +func (k Keeper) BeginBlocker(ctx context.Context) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + // Iterate over remove list - k.IterateRemoveListInfo(ctx, func(removeList types.RemoveParachainIBCTokenInfo) (stop bool) { + k.IterateRemoveListInfo(sdkCtx, func(removeList types.RemoveParachainIBCTokenInfo) (stop bool) { // If pass the duration, remove parachain token info - if ctx.BlockTime().After(removeList.RemoveTime) { - k.RemoveParachainIBCInfo(ctx, removeList.NativeDenom) + if sdkCtx.BlockTime().After(removeList.RemoveTime) { + k.RemoveParachainIBCInfo(sdkCtx, removeList.NativeDenom) } return false }) + return nil } diff --git a/x/transfermiddleware/module.go b/x/transfermiddleware/module.go index f02a7926b..fa016eedc 100644 --- a/x/transfermiddleware/module.go +++ b/x/transfermiddleware/module.go @@ -129,7 +129,10 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion implements AppModule/ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 1 } -// AppModuleSimulation functions +// BeginBlock implements the AppModule interface +func (am AppModule) BeginBlock(ctx context.Context) error { + return am.keeper.BeginBlocker(ctx) +} // GenerateGenesisState creates a randomized GenState of the router module. func (AppModule) GenerateGenesisState(_ *module.SimulationState) {} From f247a743cd69dabdca923bc9ad9ba1bdf7298ff1 Mon Sep 17 00:00:00 2001 From: Khanh Hoa Date: Mon, 27 May 2024 16:29:15 +0700 Subject: [PATCH 2/5] fix lint --- x/stakingmiddleware/keeper/genesis.go | 1 + x/transfermiddleware/keeper/abci.go | 1 + 2 files changed, 2 insertions(+) diff --git a/x/stakingmiddleware/keeper/genesis.go b/x/stakingmiddleware/keeper/genesis.go index ab160a7ba..c02a5a178 100644 --- a/x/stakingmiddleware/keeper/genesis.go +++ b/x/stakingmiddleware/keeper/genesis.go @@ -2,6 +2,7 @@ package keeper import ( "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/notional-labs/composable/v6/x/stakingmiddleware/types" ) diff --git a/x/transfermiddleware/keeper/abci.go b/x/transfermiddleware/keeper/abci.go index ff6f24b08..6eaba259d 100644 --- a/x/transfermiddleware/keeper/abci.go +++ b/x/transfermiddleware/keeper/abci.go @@ -2,6 +2,7 @@ package keeper import ( "context" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/notional-labs/composable/v6/x/transfermiddleware/types" From 35a05292b96c93bc99b23607528771caccda5ca1 Mon Sep 17 00:00:00 2001 From: Khanh Hoa Date: Mon, 27 May 2024 17:45:52 +0700 Subject: [PATCH 3/5] test: add interchaintest --- Makefile | 24 +++++++++++++++++++++++- x/ratelimit/relay_test.go | 2 ++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e5ec23302..ce1c335d6 100644 --- a/Makefile +++ b/Makefile @@ -150,12 +150,34 @@ ictest-upgrade: cd tests/interchaintest && go test -timeout=25m -race -v -run TestCentauriUpgrade . # Executes all tests via interchaintest after compling a local image as juno:local -ictest-all: ictest-start-cosmos ictest-start-polkadot ictest-ibc +ictest-all: ictest-start-cosmos ictest-start-polkadot ictest-ibc ictest-ibc-cosmos ictest-chain-core ictest-pfm ictest-pfm-router ictest-pfm-timeout ictest-miscellaneous ictest-ibc-hooks # Executes push wasm client tests via interchaintest ictest-push-wasm: cd tests/interchaintest && go test -race -v -run TestPushWasmClientCode . +ictest-ibc-cosmos: + cd tests/interchaintest && go test -race -v -run TestComposableGaiaIBCTransfer . + +ictest-chain-core: + cd tests/interchaintest && go test -race -v -run TestCoreSDKCommands . + +ictest-pfm-timeout: + cd tests/interchaintest && go test -race -v -run TestTimeoutOnForward . + +ictest-pfm: + cd tests/interchaintest && go test -race -v -run TestPacketForwardMiddleware . + +ictest-pfm-router: + cd tests/interchaintest && go test -race -v -run TestPacketForwardMiddlewareRouter . + +ictest-ibc-hooks: + cd tests/interchaintest && go test -race -v -run TestComposableIBCHooks . + +ictest-miscellaneous: + cd tests/interchaintest && go test -race -v -run TestICTestMiscellaneous . + + # Init 2 cosmos chains and setup ibc between them init-test-interchain: clean-testing-data install ./scripts/test-upgrade-cosmos-chains.sh diff --git a/x/ratelimit/relay_test.go b/x/ratelimit/relay_test.go index 855125c4f..9a8e1c554 100644 --- a/x/ratelimit/relay_test.go +++ b/x/ratelimit/relay_test.go @@ -1,6 +1,7 @@ package ratelimit_test import ( + "fmt" "testing" sdkmath "cosmossdk.io/math" @@ -390,6 +391,7 @@ func (suite *RateLimitTestSuite) TestReceiveIBCTokenWithMinRateLimitAmount() { // not receive token because catch the threshold => balances have no change gotBalance = suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress()) + fmt.Println(expBalance.String(), gotBalance.String()) suite.Require().Equal(expBalance, gotBalance) } From 04bd952d88bafe18614ca134c6a61e883096d915 Mon Sep 17 00:00:00 2001 From: Khanh Hoa Date: Mon, 27 May 2024 17:53:16 +0700 Subject: [PATCH 4/5] test: add interchaintest --- .github/workflows/interchaintest.yml | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/.github/workflows/interchaintest.yml b/.github/workflows/interchaintest.yml index 214cd0a8a..97e62ed2d 100644 --- a/.github/workflows/interchaintest.yml +++ b/.github/workflows/interchaintest.yml @@ -98,6 +98,102 @@ jobs: env: BRANCH_CI: "latest" + test-chain-core: + runs-on: ubuntu-latest + needs: build-and-push-image + steps: + - name: Set up Go 1.22 + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: checkout code + uses: actions/checkout@v4 + + - run: make ictest-chain-core + env: + BRANCH_CI: "latest" + + test-ibc-cosmos: + runs-on: ubuntu-latest + needs: build-and-push-image + steps: + - name: Set up Go 1.22 + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: checkout code + uses: actions/checkout@v4 + + - run: make ictest-ibc-cosmos + env: + BRANCH_CI: "latest" + + test-ictest-ibc-hooks: + runs-on: ubuntu-latest + needs: build-and-push-image + steps: + - name: Set up Go 1.22 + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: checkout code + uses: actions/checkout@v4 + + - run: make ictest-ibc-hooks + env: + BRANCH_CI: "latest" + + test-ictest-miscellaneous: + runs-on: ubuntu-latest + needs: build-and-push-image + steps: + - name: Set up Go 1.22 + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: checkout code + uses: actions/checkout@v4 + + - run: make ictest-miscellaneous + env: + BRANCH_CI: "latest" + + test-ictest-pfm-timeout: + runs-on: ubuntu-latest + needs: build-and-push-image + steps: + - name: Set up Go 1.22 + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: checkout code + uses: actions/checkout@v4 + + - run: make ictest-pfm-timeout + env: + BRANCH_CI: "latest" + + test-ictest-pfm: + runs-on: ubuntu-latest + needs: build-and-push-image + steps: + - name: Set up Go 1.22 + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: checkout code + uses: actions/checkout@v4 + + - run: make ictest-pfm + env: + BRANCH_CI: "latest" + # test-ibc-transfer: # runs-on: ubuntu-latest # needs: build-and-push-image From 59559a864853867941f09fcfce9ebc82bf7e36b2 Mon Sep 17 00:00:00 2001 From: Khanh Hoa Date: Mon, 27 May 2024 20:33:02 +0700 Subject: [PATCH 5/5] fix miss time --- app/test_helpers.go | 1 + 1 file changed, 1 insertion(+) diff --git a/app/test_helpers.go b/app/test_helpers.go index 2b47c09c8..56b26030d 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -171,6 +171,7 @@ func SetupWithGenesisValSet( // init chain will set the validator set and initialize the genesis accounts _, err = app.InitChain( &abci.RequestInitChain{ + Time: time.Now(), ChainId: chainID, Validators: []abci.ValidatorUpdate{}, ConsensusParams: consensusParams,