Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom query for slash ratios #84

Merged
merged 1 commit into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demo/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions x/meshsecurity/contract/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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"`
}
12 changes: 12 additions & 0 deletions x/meshsecurity/keeper/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -181,6 +183,15 @@ func CreateDefaultTestInput(t testing.TB) (sdk.Context, TestKeepers) {
)
require.NoError(t, stakingKeeper.SetParams(ctx, stakingtypes.DefaultParams()))

slashingKeeper := slashingkeeper.NewKeeper(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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],
Expand Down Expand Up @@ -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,
Expand Down
62 changes: 40 additions & 22 deletions x/meshsecurity/keeper/query_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,27 @@ 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
// and delegates to the next in chain for any queries that do not match
// 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)
}
}

Expand All @@ -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")
Expand All @@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

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)
})
}

Expand Down
50 changes: 31 additions & 19 deletions x/meshsecurity/keeper/query_plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
})
}
}
Expand Down
Loading