package keeper import ( "bytes" "encoding/json" "errors" "fmt" "os" "testing" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/cerc-io/laconicd/x/wasm/types" ) func TestLegacyQueryContractState(t *testing.T) { ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) keeper := keepers.WasmKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit.Add(deposit...)...) anyAddr := keepers.Faucet.NewFundedRandomAccount(ctx, sdk.NewInt64Coin("denom", 5000)) wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") require.NoError(t, err) contractID, _, err := keepers.ContractKeeper.Create(ctx, creator, wasmCode, nil) require.NoError(t, err) _, _, bob := keyPubAddr() initMsg := HackatomExampleInitMsg{ Verifier: anyAddr, Beneficiary: bob, } initMsgBz, err := json.Marshal(initMsg) require.NoError(t, err) addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract to query", deposit) require.NoError(t, err) contractModel := []types.Model{ {Key: []byte("foo"), Value: []byte(`"bar"`)}, {Key: []byte{0x0, 0x1}, Value: []byte(`{"count":8}`)}, } keeper.importContractState(ctx, addr, contractModel) // this gets us full error, not redacted sdk.Error var defaultQueryGasLimit sdk.Gas = 3000000 q := NewLegacyQuerier(keeper, defaultQueryGasLimit) specs := map[string]struct { srcPath []string srcReq abci.RequestQuery // smart and raw queries (not all queries) return raw bytes from contract not []types.Model // if this is set, then we just compare - (should be json encoded string) expRes []byte // if success and expSmartRes is not set, we parse into []types.Model and compare (all state) expModelLen int expModelContains []types.Model expErr error }{ "query all": { srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateAll}, expModelLen: 3, expModelContains: []types.Model{ {Key: []byte("foo"), Value: []byte(`"bar"`)}, {Key: []byte{0x0, 0x1}, Value: []byte(`{"count":8}`)}, }, }, "query raw key": { srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, srcReq: abci.RequestQuery{Data: []byte("foo")}, expRes: []byte(`"bar"`), }, "query raw binary key": { srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, srcReq: abci.RequestQuery{Data: []byte{0x0, 0x1}}, expRes: []byte(`{"count":8}`), }, "query smart": { srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateSmart}, srcReq: abci.RequestQuery{Data: []byte(`{"verifier":{}}`)}, expRes: []byte(fmt.Sprintf(`{"verifier":"%s"}`, anyAddr.String())), }, "query smart invalid request": { srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateSmart}, srcReq: abci.RequestQuery{Data: []byte(`{"raw":{"key":"config"}}`)}, expErr: types.ErrQueryFailed, }, "query smart with invalid json": { srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateSmart}, srcReq: abci.RequestQuery{Data: []byte(`not a json string`)}, expErr: types.ErrInvalid, }, "query non-existent raw key": { srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, srcReq: abci.RequestQuery{Data: []byte("i do not exist")}, expRes: nil, }, "query empty raw key": { srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, srcReq: abci.RequestQuery{Data: []byte("")}, expRes: nil, }, "query nil raw key": { srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, srcReq: abci.RequestQuery{Data: nil}, expRes: nil, }, "query raw with unknown address": { srcPath: []string{QueryGetContractState, anyAddr.String(), QueryMethodContractStateRaw}, expRes: nil, }, "query all with unknown address": { srcPath: []string{QueryGetContractState, anyAddr.String(), QueryMethodContractStateAll}, expModelLen: 0, }, "query smart with unknown address": { srcPath: []string{QueryGetContractState, anyAddr.String(), QueryMethodContractStateSmart}, srcReq: abci.RequestQuery{Data: []byte(`{}`)}, expModelLen: 0, expErr: types.ErrNotFound, }, } for msg, spec := range specs { t.Run(msg, func(t *testing.T) { binResult, err := q(ctx, spec.srcPath, spec.srcReq) // require.True(t, spec.expErr.Is(err), "unexpected error") require.True(t, errors.Is(err, spec.expErr), err) // if smart query, check custom response if spec.srcPath[2] != QueryMethodContractStateAll { require.Equal(t, spec.expRes, binResult) return } // otherwise, check returned models var r []types.Model if spec.expErr == nil { require.NoError(t, json.Unmarshal(binResult, &r)) require.NotNil(t, r) } require.Len(t, r, spec.expModelLen) // and in result set for _, v := range spec.expModelContains { assert.Contains(t, r, v) } }) } } func TestLegacyQueryContractListByCodeOrdering(t *testing.T) { ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) keeper := keepers.WasmKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000000)) topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 500)) creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit.Add(deposit...)...) anyAddr := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...) wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") require.NoError(t, err) codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, wasmCode, nil) require.NoError(t, err) _, _, bob := keyPubAddr() initMsg := HackatomExampleInitMsg{ Verifier: anyAddr, Beneficiary: bob, } initMsgBz, err := json.Marshal(initMsg) require.NoError(t, err) // manage some realistic block settings var h int64 = 10 setBlock := func(ctx sdk.Context, height int64) sdk.Context { ctx = ctx.WithBlockHeight(height) meter := sdk.NewGasMeter(1000000) ctx = ctx.WithGasMeter(meter) ctx = ctx.WithBlockGasMeter(meter) return ctx } // create 10 contracts with real block/gas setup for i := range [10]int{} { // 3 tx per block, so we ensure both comparisons work if i%3 == 0 { ctx = setBlock(ctx, h) h++ } _, _, err = keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, initMsgBz, fmt.Sprintf("contract %d", i), topUp) require.NoError(t, err) } // query and check the results are properly sorted var defaultQueryGasLimit sdk.Gas = 3000000 q := NewLegacyQuerier(keeper, defaultQueryGasLimit) query := []string{QueryListContractByCode, fmt.Sprintf("%d", codeID)} data := abci.RequestQuery{} res, err := q(ctx, query, data) require.NoError(t, err) var contracts []string err = json.Unmarshal(res, &contracts) require.NoError(t, err) require.Equal(t, 10, len(contracts)) for _, contract := range contracts { assert.NotEmpty(t, contract) } } func TestLegacyQueryContractHistory(t *testing.T) { ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) keeper := keepers.WasmKeeper var otherAddr sdk.AccAddress = bytes.Repeat([]byte{0x2}, types.ContractAddrLen) specs := map[string]struct { srcQueryAddr sdk.AccAddress srcHistory []types.ContractCodeHistoryEntry expContent []types.ContractCodeHistoryEntry }{ "response with internal fields cleared": { srcHistory: []types.ContractCodeHistoryEntry{{ Operation: types.ContractCodeHistoryOperationTypeGenesis, CodeID: firstCodeID, Updated: types.NewAbsoluteTxPosition(ctx), Msg: []byte(`"init message"`), }}, expContent: []types.ContractCodeHistoryEntry{{ Operation: types.ContractCodeHistoryOperationTypeGenesis, CodeID: firstCodeID, Msg: []byte(`"init message"`), }}, }, "response with multiple entries": { srcHistory: []types.ContractCodeHistoryEntry{{ Operation: types.ContractCodeHistoryOperationTypeInit, CodeID: firstCodeID, Updated: types.NewAbsoluteTxPosition(ctx), Msg: []byte(`"init message"`), }, { Operation: types.ContractCodeHistoryOperationTypeMigrate, CodeID: 2, Updated: types.NewAbsoluteTxPosition(ctx), Msg: []byte(`"migrate message 1"`), }, { Operation: types.ContractCodeHistoryOperationTypeMigrate, CodeID: 3, Updated: types.NewAbsoluteTxPosition(ctx), Msg: []byte(`"migrate message 2"`), }}, expContent: []types.ContractCodeHistoryEntry{{ Operation: types.ContractCodeHistoryOperationTypeInit, CodeID: firstCodeID, Msg: []byte(`"init message"`), }, { Operation: types.ContractCodeHistoryOperationTypeMigrate, CodeID: 2, Msg: []byte(`"migrate message 1"`), }, { Operation: types.ContractCodeHistoryOperationTypeMigrate, CodeID: 3, Msg: []byte(`"migrate message 2"`), }}, }, "unknown contract address": { srcQueryAddr: otherAddr, srcHistory: []types.ContractCodeHistoryEntry{{ Operation: types.ContractCodeHistoryOperationTypeGenesis, CodeID: firstCodeID, Updated: types.NewAbsoluteTxPosition(ctx), Msg: []byte(`"init message"`), }}, expContent: []types.ContractCodeHistoryEntry{}, }, } for msg, spec := range specs { t.Run(msg, func(t *testing.T) { _, _, myContractAddr := keyPubAddr() keeper.appendToContractHistory(ctx, myContractAddr, spec.srcHistory...) var defaultQueryGasLimit sdk.Gas = 3000000 q := NewLegacyQuerier(keeper, defaultQueryGasLimit) queryContractAddr := spec.srcQueryAddr if queryContractAddr == nil { queryContractAddr = myContractAddr } // when query := []string{QueryContractHistory, queryContractAddr.String()} data := abci.RequestQuery{} resData, err := q(ctx, query, data) // then require.NoError(t, err) var got []types.ContractCodeHistoryEntry err = json.Unmarshal(resData, &got) require.NoError(t, err) assert.Equal(t, spec.expContent, got) }) } } func TestLegacyQueryCodeList(t *testing.T) { wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") require.NoError(t, err) specs := map[string]struct { codeIDs []uint64 }{ "none": {}, "no gaps": { codeIDs: []uint64{1, 2, 3}, }, "with gaps": { codeIDs: []uint64{2, 4, 6}, }, } for msg, spec := range specs { t.Run(msg, func(t *testing.T) { ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) keeper := keepers.WasmKeeper for _, codeID := range spec.codeIDs { require.NoError(t, keeper.importCode(ctx, codeID, types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)), wasmCode), ) } var defaultQueryGasLimit sdk.Gas = 3000000 q := NewLegacyQuerier(keeper, defaultQueryGasLimit) // when query := []string{QueryListCode} data := abci.RequestQuery{} resData, err := q(ctx, query, data) // then require.NoError(t, err) if len(spec.codeIDs) == 0 { require.Nil(t, resData) return } var got []map[string]interface{} err = json.Unmarshal(resData, &got) require.NoError(t, err) require.Len(t, got, len(spec.codeIDs)) for i, exp := range spec.codeIDs { assert.EqualValues(t, exp, got[i]["id"]) } }) } }