diff --git a/demo/app/app.go b/demo/app/app.go index 50b2cd8d..8b3ba75e 100644 --- a/demo/app/app.go +++ b/demo/app/app.go @@ -609,7 +609,7 @@ func NewMeshApp( ) }) wasmOpts = append(wasmOpts, meshMessageHandler, - wasmkeeper.WithQueryHandlerDecorator(meshseckeeper.NewQueryDecorator(app.MeshSecKeeper)), + wasmkeeper.WithQueryHandlerDecorator(meshseckeeper.NewQueryDecorator(app.MeshSecKeeper, app.SlashingKeeper)), ) // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks diff --git a/x/meshsecurity/contract/query.go b/x/meshsecurity/contract/query.go index ebd3b8d4..62bca853 100644 --- a/x/meshsecurity/contract/query.go +++ b/x/meshsecurity/contract/query.go @@ -8,6 +8,7 @@ type CustomQuery struct { type VirtualStakeQuery struct { BondStatus *BondStatusQuery `json:"bond_status,omitempty"` + SlashRatio *struct{} `json:"slash_ratio,omitempty"` } type BondStatusQuery struct { @@ -20,3 +21,8 @@ type BondStatusResponse struct { // Delegated is the used amount of the max cap Delegated wasmvmtypes.Coin `json:"delegated"` } + +type SlashRatioResponse struct { + SlashFractionDowntime string `json:"slash_fraction_downtime"` + SlashFractionDoubleSign string `json:"slash_fraction_double_sign"` +} diff --git a/x/meshsecurity/keeper/common_test.go b/x/meshsecurity/keeper/common_test.go index a620d710..a642bd31 100644 --- a/x/meshsecurity/keeper/common_test.go +++ b/x/meshsecurity/keeper/common_test.go @@ -46,6 +46,7 @@ import ( paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/cosmos-sdk/x/staking" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" @@ -99,6 +100,7 @@ func makeEncodingConfig(_ testing.TB) encodingConfig { type TestKeepers struct { StakingKeeper *stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper BankKeeper bankkeeper.Keeper StoreKey *storetypes.KVStoreKey EncodingConfig encodingConfig @@ -181,6 +183,15 @@ func CreateDefaultTestInput(t testing.TB) (sdk.Context, TestKeepers) { ) require.NoError(t, stakingKeeper.SetParams(ctx, stakingtypes.DefaultParams())) + slashingKeeper := slashingkeeper.NewKeeper( + appCodec, + encConfig.Amino, + keys[slashingtypes.StoreKey], + stakingKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + require.NoError(t, slashingKeeper.SetParams(ctx, slashingtypes.DefaultParams())) + distKeeper := distributionkeeper.NewKeeper( appCodec, keys[distributiontypes.StoreKey], @@ -268,6 +279,7 @@ func CreateDefaultTestInput(t testing.TB) (sdk.Context, TestKeepers) { return ctx, TestKeepers{ AccountKeeper: accountKeeper, StakingKeeper: stakingKeeper, + SlashingKeeper: slashingKeeper, BankKeeper: bankKeeper, StoreKey: keys[types.StoreKey], EncodingConfig: encConfig, diff --git a/x/meshsecurity/keeper/query_plugin.go b/x/meshsecurity/keeper/query_plugin.go index 347593ec..2d5f784d 100644 --- a/x/meshsecurity/keeper/query_plugin.go +++ b/x/meshsecurity/keeper/query_plugin.go @@ -14,11 +14,17 @@ import ( "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/contract" ) -// abstract query keeper -type viewKeeper interface { - GetMaxCapLimit(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin - GetTotalDelegated(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin -} +type ( + // abstract query keeper + viewKeeper interface { + GetMaxCapLimit(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin + GetTotalDelegated(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin + } + slashingKeeper interface { + SlashFractionDoubleSign(ctx sdk.Context) (res sdk.Dec) + SlashFractionDowntime(ctx sdk.Context) (res sdk.Dec) + } +) // NewQueryDecorator constructor to build a chained custom querier. // The mesh-security custom query handler is placed at the first position @@ -26,9 +32,9 @@ type viewKeeper interface { // the mesh-security custom query namespace. // // To be used with `wasmkeeper.WithQueryHandlerDecorator(meshseckeeper.NewQueryDecorator(app.MeshSecKeeper)))` -func NewQueryDecorator(k viewKeeper) func(wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { +func NewQueryDecorator(k viewKeeper, sk slashingKeeper) func(wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { return func(next wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { - return ChainedCustomQuerier(k, next) + return ChainedCustomQuerier(k, sk, next) } } @@ -38,9 +44,12 @@ func NewQueryDecorator(k viewKeeper) func(wasmkeeper.WasmVMQueryHandler) wasmkee // // This CustomQuerier is designed as an extension point. See the NewQueryDecorator impl how to // set this up for wasmd. -func ChainedCustomQuerier(k viewKeeper, next wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { +func ChainedCustomQuerier(k viewKeeper, sk slashingKeeper, next wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { if k == nil { - panic("keeper must not be nil") + panic("ms keeper must not be nil") + } + if sk == nil { + panic("slashing Keeper must not be nil") } if next == nil { panic("next handler must not be nil") @@ -53,22 +62,31 @@ func ChainedCustomQuerier(k viewKeeper, next wasmkeeper.WasmVMQueryHandler) wasm if err := json.Unmarshal(request.Custom, &contractQuery); err != nil { return nil, errorsmod.Wrap(err, "mesh-security query") } - if contractQuery.VirtualStake == nil || contractQuery.VirtualStake.BondStatus == nil { + query := contractQuery.VirtualStake + if query == nil { return next.HandleQuery(ctx, caller, request) } - contractAddr, err := sdk.AccAddressFromBech32(contractQuery.VirtualStake.BondStatus.Contract) - if err != nil { - return nil, sdkerrors.ErrInvalidAddress.Wrap(contractQuery.VirtualStake.BondStatus.Contract) - } - res := contract.BondStatusResponse{ - MaxCap: wasmkeeper.ConvertSdkCoinToWasmCoin(k.GetMaxCapLimit(ctx, contractAddr)), - Delegated: wasmkeeper.ConvertSdkCoinToWasmCoin(k.GetTotalDelegated(ctx, contractAddr)), - } - bz, err := json.Marshal(res) - if err != nil { - return nil, errorsmod.Wrap(err, "mesh-security max cap query response") + + var res any + switch { + case query.BondStatus != nil: + contractAddr, err := sdk.AccAddressFromBech32(query.BondStatus.Contract) + if err != nil { + return nil, sdkerrors.ErrInvalidAddress.Wrap(query.BondStatus.Contract) + } + res = contract.BondStatusResponse{ + MaxCap: wasmkeeper.ConvertSdkCoinToWasmCoin(k.GetMaxCapLimit(ctx, contractAddr)), + Delegated: wasmkeeper.ConvertSdkCoinToWasmCoin(k.GetTotalDelegated(ctx, contractAddr)), + } + case query.SlashRatio != nil: + res = contract.SlashRatioResponse{ + SlashFractionDowntime: sk.SlashFractionDowntime(ctx).String(), + SlashFractionDoubleSign: sk.SlashFractionDoubleSign(ctx).String(), + } + default: + return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown virtual_stake query variant"} } - return bz, nil + return json.Marshal(res) }) } diff --git a/x/meshsecurity/keeper/query_plugin_test.go b/x/meshsecurity/keeper/query_plugin_test.go index c8467422..fd439eae 100644 --- a/x/meshsecurity/keeper/query_plugin_test.go +++ b/x/meshsecurity/keeper/query_plugin_test.go @@ -16,37 +16,49 @@ import ( func TestChainedCustomQuerier(t *testing.T) { myContractAddr := sdk.AccAddress(rand.Bytes(32)) + pCtx, keepers := CreateDefaultTestInput(t) + specs := map[string]struct { - src wasmvmtypes.QueryRequest - mockMaxCapLimit func(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin - mockTotalDelegated func(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin - expData []byte - expErr bool - expMocNextCalled bool + src wasmvmtypes.QueryRequest + viewKeeper viewKeeper + expData []byte + expErr bool + expNextCalled bool }{ - "all good": { + "bond status query": { src: wasmvmtypes.QueryRequest{ Custom: []byte(fmt.Sprintf(`{"virtual_stake":{"bond_status":{"contract":%q}}}`, myContractAddr.String())), }, - mockMaxCapLimit: func(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin { - return sdk.NewCoin("ALX", math.NewInt(123)) - }, - mockTotalDelegated: func(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin { - return sdk.NewCoin("ALX", math.NewInt(456)) + viewKeeper: &MockViewKeeper{ + GetMaxCapLimitFn: func(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin { + return sdk.NewCoin("ALX", math.NewInt(123)) + }, + GetTotalDelegatedFn: func(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin { + return sdk.NewCoin("ALX", math.NewInt(456)) + }, }, expData: []byte(`{"cap":{"denom":"ALX","amount":"123"},"delegated":{"denom":"ALX","amount":"456"}}`), }, + "slash ratio query": { + src: wasmvmtypes.QueryRequest{ + Custom: []byte(`{"virtual_stake":{"slash_ratio":{}}}`), + }, + viewKeeper: keepers.MeshKeeper, + expData: []byte(`{"slash_fraction_downtime":"0.010000000000000000","slash_fraction_double_sign":"0.050000000000000000"}`), + }, "non custom query": { src: wasmvmtypes.QueryRequest{ Bank: &wasmvmtypes.BankQuery{}, }, - expMocNextCalled: true, + viewKeeper: keepers.MeshKeeper, + expNextCalled: true, }, "custom non mesh query": { src: wasmvmtypes.QueryRequest{ Custom: []byte(`{"foo":{}}`), }, - expMocNextCalled: true, + viewKeeper: keepers.MeshKeeper, + expNextCalled: true, }, } for name, spec := range specs { @@ -56,16 +68,16 @@ func TestChainedCustomQuerier(t *testing.T) { nextCalled = true return nil, nil }) - mock := &MockViewKeeper{GetMaxCapLimitFn: spec.mockMaxCapLimit, GetTotalDelegatedFn: spec.mockTotalDelegated} - ctx := sdk.Context{} - gotData, gotErr := ChainedCustomQuerier(mock, next).HandleQuery(ctx, myContractAddr, spec.src) + + ctx, _ := pCtx.CacheContext() + gotData, gotErr := ChainedCustomQuerier(spec.viewKeeper, keepers.SlashingKeeper, next).HandleQuery(ctx, myContractAddr, spec.src) if spec.expErr { require.Error(t, gotErr) return } require.NoError(t, gotErr) - assert.Equal(t, spec.expData, gotData) - assert.Equal(t, spec.expMocNextCalled, nextCalled) + assert.Equal(t, spec.expData, gotData, string(gotData)) + assert.Equal(t, spec.expNextCalled, nextCalled) }) } }