diff --git a/x/wasm/internal/keeper/recurse_test.go b/x/wasm/internal/keeper/recurse_test.go index 6cf2cb773b..4a9bf1454c 100644 --- a/x/wasm/internal/keeper/recurse_test.go +++ b/x/wasm/internal/keeper/recurse_test.go @@ -24,7 +24,7 @@ type recurseWrapper struct { Recurse Recurse `json:"recurse"` } -func buildQuery(t *testing.T, msg Recurse) []byte { +func buildRecurseQuery(t *testing.T, msg Recurse) []byte { wrapper := recurseWrapper{Recurse: msg} bz, err := json.Marshal(wrapper) require.NoError(t, err) @@ -147,7 +147,7 @@ func TestGasCostOnQuery(t *testing.T) { // do the query recurse := tc.msg recurse.Contract = contractAddr - msg := buildQuery(t, recurse) + msg := buildRecurseQuery(t, recurse) data, err := keeper.QuerySmart(ctx, contractAddr, msg) require.NoError(t, err) @@ -219,7 +219,7 @@ func TestGasOnExternalQuery(t *testing.T) { recurse := tc.msg recurse.Contract = contractAddr - msg := buildQuery(t, recurse) + msg := buildRecurseQuery(t, recurse) // do the query path := []string{QueryGetContractState, contractAddr.String(), QueryMethodContractStateSmart} @@ -310,7 +310,7 @@ func TestLimitRecursiveQueryGas(t *testing.T) { // prepare the query recurse := tc.msg recurse.Contract = contractAddr - msg := buildQuery(t, recurse) + msg := buildRecurseQuery(t, recurse) // if we expect out of gas, make sure this panics if tc.expectOutOfGas { diff --git a/x/wasm/internal/keeper/reflect_test.go b/x/wasm/internal/keeper/reflect_test.go index 19f0bdb9f6..0df8157e11 100644 --- a/x/wasm/internal/keeper/reflect_test.go +++ b/x/wasm/internal/keeper/reflect_test.go @@ -37,9 +37,13 @@ type reflectPayload struct { // MaskQueryMsg is used to encode query messages type MaskQueryMsg struct { - Owner *struct{} `json:"owner,omitempty"` - Capitalized *Text `json:"capitalized,omitempty"` - Chain *wasmTypes.QueryRequest `json:"chain,omitempty"` + Owner *struct{} `json:"owner,omitempty"` + Capitalized *Text `json:"capitalized,omitempty"` + Chain *ChainQuery `json:"chain,omitempty"` +} + +type ChainQuery struct { + Request *wasmTypes.QueryRequest `json:"request,omitempty"` } type Text struct { @@ -50,6 +54,21 @@ type OwnerResponse struct { Owner string `json:"owner,omitempty"` } +type ChainResponse struct { + Data []byte `json:"data,omitempty"` +} + +func buildMaskQuery(t *testing.T, query *MaskQueryMsg) []byte { + bz, err := json.Marshal(query) + require.NoError(t, err) + return bz +} + +func mustParse(t *testing.T, data []byte, res interface{}) { + err := json.Unmarshal(data, res) + require.NoError(t, err) +} + const MaskFeatures = "staking,mask" func TestMaskReflectContractSend(t *testing.T) { @@ -284,6 +303,90 @@ func TestMaskReflectCustomQuery(t *testing.T) { assert.Equal(t, resp.Text, "ALL CAPS NOW") } +type maskState struct { + Owner []byte `json:"owner"` +} + +func TestMaskReflectWasmQueries(t *testing.T) { + tempDir, err := ioutil.TempDir("", "wasm") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + ctx, keepers := CreateTestInput(t, false, tempDir, MaskFeatures, maskEncoders(MakeTestCodec()), nil) + accKeeper, keeper := keepers.AccountKeeper, keepers.WasmKeeper + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + creator := createFakeFundedAccount(ctx, accKeeper, deposit) + + // upload mask code + maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + require.NoError(t, err) + maskID, err := keeper.Create(ctx, creator, maskCode, "", "", nil) + require.NoError(t, err) + require.Equal(t, uint64(1), maskID) + + // creator instantiates a contract and gives it tokens + maskStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) + maskAddr, err := keeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", maskStart) + require.NoError(t, err) + require.NotEmpty(t, maskAddr) + + // for control, let's make some queries directly on the mask + ownerQuery := buildMaskQuery(t, &MaskQueryMsg{Owner: &struct{}{}}) + res, err := keeper.QuerySmart(ctx, maskAddr, ownerQuery) + require.NoError(t, err) + var ownerRes OwnerResponse + mustParse(t, res, &ownerRes) + require.Equal(t, ownerRes.Owner, creator.String()) + + // and a raw query: cosmwasm_storage::Singleton uses 2 byte big-endian length-prefixed to store data + configKey := append([]byte{0, 6}, []byte("config")...) + models := keeper.QueryRaw(ctx, maskAddr, configKey) + require.Len(t, models, 1) + var stateRes maskState + mustParse(t, models[0].Value, &stateRes) + require.Equal(t, stateRes.Owner, []byte(creator)) + + // now, let's reflect a smart query into the x/wasm handlers and see if we get the same result + reflectOwnerQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmTypes.QueryRequest{Wasm: &wasmTypes.WasmQuery{ + Smart: &wasmTypes.SmartQuery{ + ContractAddr: maskAddr.String(), + Msg: ownerQuery, + }, + }}}} + reflectOwnerBin := buildMaskQuery(t, &reflectOwnerQuery) + res, err = keeper.QuerySmart(ctx, maskAddr, reflectOwnerBin) + require.NoError(t, err) + // first we pull out the data from chain response, before parsing the original response + var reflectRes ChainResponse + mustParse(t, res, &reflectRes) + var reflectOwnerRes OwnerResponse + mustParse(t, reflectRes.Data, &reflectOwnerRes) + require.Equal(t, reflectOwnerRes.Owner, creator.String()) + + // and with queryRaw + reflectStateQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmTypes.QueryRequest{Wasm: &wasmTypes.WasmQuery{ + Raw: &wasmTypes.RawQuery{ + ContractAddr: maskAddr.String(), + Key: configKey, + }, + }}}} + reflectStateBin := buildMaskQuery(t, &reflectStateQuery) + res, err = keeper.QuerySmart(ctx, maskAddr, reflectStateBin) + require.NoError(t, err) + // first we pull out the data from chain response, before parsing the original response + var reflectRawRes ChainResponse + mustParse(t, res, &reflectRawRes) + // this returns []Model... we need to parse this to actually get the key-value info + var reflectModels []types.Model + mustParse(t, reflectRawRes.Data, &reflectModels) + require.Len(t, reflectModels, 1) + // now, with the raw data, we can parse it into state + var reflectStateRes maskState + mustParse(t, reflectModels[0].Value, &reflectStateRes) + require.Equal(t, reflectStateRes.Owner, []byte(creator)) + +} + func checkAccount(t *testing.T, ctx sdk.Context, accKeeper auth.AccountKeeper, addr sdk.AccAddress, expected sdk.Coins) { acct := accKeeper.GetAccount(ctx, addr) if expected == nil { diff --git a/x/wasm/internal/keeper/staking_test.go b/x/wasm/internal/keeper/staking_test.go index 01b8d4172a..bc44bd3b9b 100644 --- a/x/wasm/internal/keeper/staking_test.go +++ b/x/wasm/internal/keeper/staking_test.go @@ -2,6 +2,7 @@ package keeper import ( "encoding/json" + wasmTypes "github.com/CosmWasm/go-cosmwasm/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/staking" @@ -404,6 +405,140 @@ func TestReinvest(t *testing.T) { assertSupply(t, ctx, keeper, contractAddr, "200000", sdk.NewInt64Coin("stake", 236000)) } +func TestQueryStakingInfo(t *testing.T) { + // STEP 1: take a lot of setup from TestReinvest so we have non-zero info + initInfo := initializeStaking(t) + defer initInfo.cleanup() + ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr + keeper, stakingKeeper, accKeeper := initInfo.wasmKeeper, initInfo.stakingKeeper, initInfo.accKeeper + distKeeper := initInfo.distKeeper + + // initial checks of bonding state + val, found := stakingKeeper.GetValidator(ctx, valAddr) + require.True(t, found) + assert.Equal(t, sdk.NewInt(1000000), val.Tokens) + + // full is 2x funds, 1x goes to the contract, other stays on his wallet + full := sdk.NewCoins(sdk.NewInt64Coin("stake", 400000)) + funds := sdk.NewCoins(sdk.NewInt64Coin("stake", 200000)) + bob := createFakeFundedAccount(ctx, accKeeper, full) + + // we will stake 200k to a validator with 1M self-bond + // this means we should get 1/6 of the rewards + bond := StakingHandleMsg{ + Bond: &struct{}{}, + } + bondBz, err := json.Marshal(bond) + require.NoError(t, err) + _, err = keeper.Execute(ctx, contractAddr, bob, bondBz, funds) + require.NoError(t, err) + + // update height a bit to solidify the delegation + ctx = nextBlock(ctx, stakingKeeper) + // we get 1/6, our share should be 40k minus 10% commission = 36k + setValidatorRewards(ctx, stakingKeeper, distKeeper, valAddr, "240000") + + // STEP 2: Prepare the mask contract + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + creator := createFakeFundedAccount(ctx, accKeeper, deposit) + + // upload mask code + maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + require.NoError(t, err) + maskID, err := keeper.Create(ctx, creator, maskCode, "", "", nil) + require.NoError(t, err) + require.Equal(t, uint64(2), maskID) + + // creator instantiates a contract and gives it tokens + maskAddr, err := keeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", nil) + require.NoError(t, err) + require.NotEmpty(t, maskAddr) + + // STEP 3: now, let's reflect some queries. + // let's get the bonded denom + reflectBondedQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmTypes.QueryRequest{Staking: &wasmTypes.StakingQuery{ + BondedDenom: &struct{}{}, + }}}} + reflectBondedBin := buildMaskQuery(t, &reflectBondedQuery) + res, err := keeper.QuerySmart(ctx, maskAddr, reflectBondedBin) + require.NoError(t, err) + // first we pull out the data from chain response, before parsing the original response + var reflectRes ChainResponse + mustParse(t, res, &reflectRes) + var bondedRes wasmTypes.BondedDenomResponse + mustParse(t, reflectRes.Data, &bondedRes) + assert.Equal(t, "stake", bondedRes.Denom) + + // now, let's reflect a smart query into the x/wasm handlers and see if we get the same result + reflectValidatorsQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmTypes.QueryRequest{Staking: &wasmTypes.StakingQuery{ + Validators: &wasmTypes.ValidatorsQuery{}, + }}}} + reflectValidatorsBin := buildMaskQuery(t, &reflectValidatorsQuery) + res, err = keeper.QuerySmart(ctx, maskAddr, reflectValidatorsBin) + require.NoError(t, err) + // first we pull out the data from chain response, before parsing the original response + mustParse(t, res, &reflectRes) + var validatorRes wasmTypes.ValidatorsResponse + mustParse(t, reflectRes.Data, &validatorRes) + require.Len(t, validatorRes.Validators, 1) + valInfo := validatorRes.Validators[0] + // Note: this ValAddress not AccAddress, may change with #264 + require.Equal(t, valAddr.String(), valInfo.Address) + require.Contains(t, valInfo.Commission, "0.100") + require.Contains(t, valInfo.MaxCommission, "0.200") + require.Contains(t, valInfo.MaxChangeRate, "0.010") + + // test to get all my delegations + reflectAllDelegationsQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmTypes.QueryRequest{Staking: &wasmTypes.StakingQuery{ + AllDelegations: &wasmTypes.AllDelegationsQuery{ + Delegator: contractAddr.String(), + }, + }}}} + reflectAllDelegationsBin := buildMaskQuery(t, &reflectAllDelegationsQuery) + res, err = keeper.QuerySmart(ctx, maskAddr, reflectAllDelegationsBin) + require.NoError(t, err) + // first we pull out the data from chain response, before parsing the original response + mustParse(t, res, &reflectRes) + var allDelegationsRes wasmTypes.AllDelegationsResponse + mustParse(t, reflectRes.Data, &allDelegationsRes) + require.Len(t, allDelegationsRes.Delegations, 1) + delInfo := allDelegationsRes.Delegations[0] + // Note: this ValAddress not AccAddress, may change with #264 + require.Equal(t, valAddr.String(), delInfo.Validator) + // note this is not bob (who staked to the contract), but the contract itself + require.Equal(t, contractAddr.String(), delInfo.Delegator) + // this is a different Coin type, with String not BigInt, compare field by field + require.Equal(t, funds[0].Denom, delInfo.Amount.Denom) + require.Equal(t, funds[0].Amount.String(), delInfo.Amount.Amount) + + // test to get one delegations + reflectDelegationQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmTypes.QueryRequest{Staking: &wasmTypes.StakingQuery{ + Delegation: &wasmTypes.DelegationQuery{ + Validator: valAddr.String(), + Delegator: contractAddr.String(), + }, + }}}} + reflectDelegationBin := buildMaskQuery(t, &reflectDelegationQuery) + res, err = keeper.QuerySmart(ctx, maskAddr, reflectDelegationBin) + require.NoError(t, err) + // first we pull out the data from chain response, before parsing the original response + mustParse(t, res, &reflectRes) + var delegationRes wasmTypes.DelegationResponse + mustParse(t, reflectRes.Data, &delegationRes) + assert.NotEmpty(t, delegationRes.Delegation) + delInfo2 := delegationRes.Delegation + // Note: this ValAddress not AccAddress, may change with #264 + require.Equal(t, valAddr.String(), delInfo2.Validator) + // note this is not bob (who staked to the contract), but the contract itself + require.Equal(t, contractAddr.String(), delInfo2.Delegator) + // this is a different Coin type, with String not BigInt, compare field by field + require.Equal(t, funds[0].Denom, delInfo2.Amount.Denom) + require.Equal(t, funds[0].Amount.String(), delInfo2.Amount.Amount) + // TODO: fix this - these should return real values!!! Issue #263 + require.Len(t, delInfo2.AccumulatedRewards, 0) + require.Equal(t, delInfo2.CanRedelegate, wasmTypes.NewCoin(0, "stake")) +} + // adds a few validators and returns a list of validators that are registered func addValidator(ctx sdk.Context, stakingKeeper staking.Keeper, accountKeeper auth.AccountKeeper, value sdk.Coin) sdk.ValAddress { _, pub, accAddr := keyPubAddr()