diff --git a/app/wasm.go b/app/wasm.go index 0b2358f3..d3f94d8d 100644 --- a/app/wasm.go +++ b/app/wasm.go @@ -21,6 +21,10 @@ func SetupWasmHandlers(cdc codec.Marshaler, result twasmkeeper.TgradeWasmHandlerKeeper, poeKeeper poewasm.ViewKeeper, ) []wasmkeeper.Option { + queryPluginOpt := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ + Staking: poewasm.StakingQuerier(poeKeeper), + }) + extMessageHandlerOpt := wasmkeeper.WithMessageHandlerDecorator(func(nested wasmkeeper.Messenger) wasmkeeper.Messenger { return wasmkeeper.NewMessageHandlerChain( // disable staking messages @@ -35,16 +39,8 @@ func SetupWasmHandlers(cdc codec.Marshaler, twasmkeeper.NewTgradeHandler(cdc, result, bankKeeper, govRouter), ) }) - extQueryHandlerOpt := wasmkeeper.WithQueryHandlerDecorator(func(nested wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { - return wasmkeeper.WasmVMQueryHandlerFn(func(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { - if request.Staking != nil { - return poewasm.StakingQuerier(poeKeeper)(ctx, request.Staking) - } - return nested.HandleQuery(ctx, caller, request) - }) - }) return []wasm.Option{ + queryPluginOpt, extMessageHandlerOpt, - extQueryHandlerOpt, } } diff --git a/x/poe/keeper/poetesting/mock_contracts.go b/x/poe/keeper/poetesting/mock_contracts.go index 627adf50..e49c5603 100644 --- a/x/poe/keeper/poetesting/mock_contracts.go +++ b/x/poe/keeper/poetesting/mock_contracts.go @@ -51,7 +51,7 @@ func (m ValsetContractMock) QueryConfig(ctx types.Context) (*contract.ValsetConf return m.QueryConfigFn(ctx) } -//var _ keeper.StakeContract = StakeContractMock{} +// var _ keeper.StakeContract = StakeContractMock{} type StakeContractMock struct { QueryStakingUnbondingPeriodFn func(ctx types.Context) (time.Duration, error) diff --git a/x/poe/wasm/query_plugin.go b/x/poe/wasm/query_plugin.go index 087109bf..ba05b499 100644 --- a/x/poe/wasm/query_plugin.go +++ b/x/poe/wasm/query_plugin.go @@ -26,6 +26,7 @@ func StakingQuerier(poeKeeper ViewKeeper) func(ctx sdk.Context, request *wasmvmt } return json.Marshal(res) } + zero := sdk.ZeroDec().String() if request.AllValidators != nil { validators, err := poeKeeper.ValsetContract(ctx).ListValidators(ctx) if err != nil { @@ -35,9 +36,9 @@ func StakingQuerier(poeKeeper ViewKeeper) func(ctx sdk.Context, request *wasmvmt for i, v := range validators { wasmVals[i] = wasmvmtypes.Validator{ Address: v.OperatorAddress, - Commission: v.Commission.Rate.String(), - MaxCommission: v.Commission.MaxRate.String(), - MaxChangeRate: v.Commission.MaxChangeRate.String(), + Commission: zero, + MaxCommission: zero, + MaxChangeRate: zero, } } res := wasmvmtypes.AllValidatorsResponse{ @@ -58,9 +59,9 @@ func StakingQuerier(poeKeeper ViewKeeper) func(ctx sdk.Context, request *wasmvmt if v != nil { res.Validator = &wasmvmtypes.Validator{ Address: v.OperatorAddress, - Commission: v.Commission.Rate.String(), - MaxCommission: v.Commission.MaxRate.String(), - MaxChangeRate: v.Commission.MaxChangeRate.String(), + Commission: zero, + MaxCommission: zero, + MaxChangeRate: zero, } } return json.Marshal(res) @@ -89,7 +90,7 @@ func StakingQuerier(poeKeeper ViewKeeper) func(ctx sdk.Context, request *wasmvmt if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, request.Delegation.Delegator) } - validator, err := sdk.ValAddressFromBech32(request.Delegation.Validator) + validator, err := sdk.AccAddressFromBech32(request.Delegation.Validator) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, request.Delegation.Validator) } diff --git a/x/poe/wasm/query_plugin_test.go b/x/poe/wasm/query_plugin_test.go new file mode 100644 index 00000000..4113c2fb --- /dev/null +++ b/x/poe/wasm/query_plugin_test.go @@ -0,0 +1,222 @@ +package wasm + +import ( + "testing" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/confio/tgrade/x/poe/keeper" + "github.com/confio/tgrade/x/poe/keeper/poetesting" + poetypes "github.com/confio/tgrade/x/poe/types" +) + +func TestStakingQuerier(t *testing.T) { + specs := map[string]struct { + src wasmvmtypes.StakingQuery + mock ViewKeeper + expJson string + expErr bool + }{ + "bonded denum": { + src: wasmvmtypes.StakingQuery{BondedDenom: &struct{}{}}, + mock: ViewKeeperMock{GetBondDenomFn: func(ctx sdk.Context) string { + return "alx" + }}, + expJson: `{"denom": "alx"}`, + }, + "all validators - single": { + src: wasmvmtypes.StakingQuery{AllValidators: &wasmvmtypes.AllValidatorsQuery{}}, + mock: ViewKeeperMock{ValsetContractFn: func(ctx sdk.Context) keeper.ValsetContract { + return poetesting.ValsetContractMock{ + ListValidatorsFn: func(ctx sdk.Context) ([]stakingtypes.Validator, error) { + resp := []stakingtypes.Validator{ + poetypes.ValidatorFixture(func(m *stakingtypes.Validator) { + m.OperatorAddress = "myOperatorAddress" + }), + } + return resp, nil + }, + } + }}, + expJson: `{"validators":[{"address":"myOperatorAddress","commission":"0.000000000000000000","max_commission":"0.000000000000000000","max_change_rate":"0.000000000000000000"}]}`, + }, + "all validators - multiple": { + src: wasmvmtypes.StakingQuery{AllValidators: &wasmvmtypes.AllValidatorsQuery{}}, + mock: ViewKeeperMock{ValsetContractFn: func(ctx sdk.Context) keeper.ValsetContract { + return poetesting.ValsetContractMock{ + ListValidatorsFn: func(ctx sdk.Context) ([]stakingtypes.Validator, error) { + resp := []stakingtypes.Validator{ + poetypes.ValidatorFixture(func(m *stakingtypes.Validator) { + m.OperatorAddress = "myOperatorAddress" + }), + poetypes.ValidatorFixture(func(m *stakingtypes.Validator) { + m.OperatorAddress = "myOtherOperatorAddress" + }), + } + return resp, nil + }, + } + }}, + expJson: `{"validators":[ +{"address":"myOperatorAddress","commission":"0.000000000000000000","max_commission":"0.000000000000000000","max_change_rate":"0.000000000000000000"}, +{"address":"myOtherOperatorAddress","commission":"0.000000000000000000","max_commission":"0.000000000000000000","max_change_rate":"0.000000000000000000"} + ]}`, + }, + "query validator": { + src: wasmvmtypes.StakingQuery{Validator: &wasmvmtypes.ValidatorQuery{Address: "cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3"}}, + mock: ViewKeeperMock{ValsetContractFn: func(ctx sdk.Context) keeper.ValsetContract { + return poetesting.ValsetContractMock{ + QueryValidatorFn: func(ctx sdk.Context, opAddr sdk.AccAddress) (*stakingtypes.Validator, error) { + val := poetypes.ValidatorFixture(func(m *stakingtypes.Validator) { + m.OperatorAddress = "cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3" + }) + return &val, nil + }, + } + }}, + expJson: `{"validator":{"address":"cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3","commission":"0.000000000000000000","max_commission":"0.000000000000000000","max_change_rate":"0.000000000000000000"}}`, + }, + "query validator - unknown address": { + src: wasmvmtypes.StakingQuery{Validator: &wasmvmtypes.ValidatorQuery{Address: "cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3"}}, + mock: ViewKeeperMock{ValsetContractFn: func(ctx sdk.Context) keeper.ValsetContract { + return poetesting.ValsetContractMock{ + QueryValidatorFn: func(ctx sdk.Context, opAddr sdk.AccAddress) (*stakingtypes.Validator, error) { + return nil, nil + }, + } + }}, + expJson: `{"validator": null}`, + }, + "query validator - invalid address": { + src: wasmvmtypes.StakingQuery{Validator: &wasmvmtypes.ValidatorQuery{Address: "not a valid address"}}, + expErr: true, + }, + "all delegations": { + src: wasmvmtypes.StakingQuery{AllDelegations: &wasmvmtypes.AllDelegationsQuery{Delegator: "cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3"}}, + mock: ViewKeeperMock{StakeContractFn: func(ctx sdk.Context) keeper.StakeContract { + return poetesting.StakeContractMock{ + QueryStakedAmountFn: func(ctx sdk.Context, opAddr sdk.AccAddress) (*sdk.Int, error) { + myValue := sdk.OneInt() + return &myValue, nil + }, + } + }, + GetBondDenomFn: func(ctx sdk.Context) string { + return "alx" + }}, + expJson: `{"delegations":[{"delegator":"cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3","validator":"cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3","amount":{"denom":"alx","amount":"1"}}]}`, + }, + "all delegations - unknown address": { + src: wasmvmtypes.StakingQuery{AllDelegations: &wasmvmtypes.AllDelegationsQuery{Delegator: "cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3"}}, + mock: ViewKeeperMock{StakeContractFn: func(ctx sdk.Context) keeper.StakeContract { + return poetesting.StakeContractMock{ + QueryStakedAmountFn: func(ctx sdk.Context, opAddr sdk.AccAddress) (*sdk.Int, error) { + return nil, nil + }, + } + }}, + expJson: `{"delegations":[]}`, + }, + "all delegations - invalid address": { + src: wasmvmtypes.StakingQuery{AllDelegations: &wasmvmtypes.AllDelegationsQuery{Delegator: "not a valid address"}}, + expErr: true, + }, + "query delegation": { + src: wasmvmtypes.StakingQuery{Delegation: &wasmvmtypes.DelegationQuery{Delegator: "cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3", Validator: "cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3"}}, + mock: ViewKeeperMock{ + StakeContractFn: func(ctx sdk.Context) keeper.StakeContract { + return poetesting.StakeContractMock{ + QueryStakedAmountFn: func(ctx sdk.Context, opAddr sdk.AccAddress) (*sdk.Int, error) { + myValue := sdk.OneInt() + return &myValue, nil + }, + } + }, + GetBondDenomFn: func(ctx sdk.Context) string { + return "alx" + }, + DistributionContractFn: func(ctx sdk.Context) keeper.DistributionContract { + return poetesting.DistributionContractMock{ValidatorOutstandingRewardFn: func(ctx sdk.Context, addr sdk.AccAddress) (sdk.Coin, error) { + return sdk.NewCoin("alx", sdk.NewInt(2)), nil + }} + }, + }, + expJson: `{ + "delegation": { + "delegator": "cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3", + "validator": "cosmos1yq8zt83jznmp94jkj65yvfz9n52akmxt52ehm3", + "amount": { + "denom": "alx", + "amount": "1" + }, + "accumulated_rewards": [ + { + "denom": "alx", + "amount": "2" + } + ], + "can_redelegate": { + "denom": "alx", + "amount": "1" + } + } +} +`, + // todo: + // - check address do not match + // - unknown address + // - invalid address + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + q := StakingQuerier(spec.mock) + gotRsp, gotErr := q(sdk.Context{}, &spec.src) + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + assert.JSONEq(t, spec.expJson, string(gotRsp), string(gotRsp)) + }) + } +} + +type ViewKeeperMock struct { + GetBondDenomFn func(ctx sdk.Context) string + DistributionContractFn func(ctx sdk.Context) keeper.DistributionContract + ValsetContractFn func(ctx sdk.Context) keeper.ValsetContract + StakeContractFn func(ctx sdk.Context) keeper.StakeContract +} + +func (m ViewKeeperMock) GetBondDenom(ctx sdk.Context) string { + if m.GetBondDenomFn == nil { + panic("not expected to be called") + } + return m.GetBondDenomFn(ctx) +} + +func (m ViewKeeperMock) DistributionContract(ctx sdk.Context) keeper.DistributionContract { + if m.DistributionContractFn == nil { + panic("not expected to be called") + } + return m.DistributionContractFn(ctx) +} + +func (m ViewKeeperMock) ValsetContract(ctx sdk.Context) keeper.ValsetContract { + if m.ValsetContractFn == nil { + panic("not expected to be called") + } + return m.ValsetContractFn(ctx) +} + +func (m ViewKeeperMock) StakeContract(ctx sdk.Context) keeper.StakeContract { + if m.StakeContractFn == nil { + panic("not expected to be called") + } + return m.StakeContractFn(ctx) +}