Skip to content

Commit

Permalink
Merge pull request #170 from confio/156-handlers
Browse files Browse the repository at this point in the history
Add PoE staking query plugin
  • Loading branch information
ethanfrey authored Nov 8, 2021
2 parents aafc95a + 48be901 commit b9fa236
Show file tree
Hide file tree
Showing 14 changed files with 593 additions and 44 deletions.
2 changes: 2 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ func NewTgradeApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest
// TODO: add tgrade here soon
supportedFeatures := "staking,stargate,iterator"

wasmOpts = append(SetupWasmHandlers(appCodec, app.bankKeeper, govRouter, &app.twasmKeeper, &app.poeKeeper), wasmOpts...)

stakingAdapter := stakingKeeper
app.twasmKeeper = twasmkeeper.NewKeeper(
appCodec,
Expand Down
46 changes: 46 additions & 0 deletions app/wasm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package app

import (
"github.com/CosmWasm/wasmd/x/wasm"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"

poewasm "github.com/confio/tgrade/x/poe/wasm"
twasmkeeper "github.com/confio/tgrade/x/twasm/keeper"
twasmtypes "github.com/confio/tgrade/x/twasm/types"
)

func SetupWasmHandlers(cdc codec.Marshaler,
bankKeeper twasmtypes.BankKeeper,
govRouter govtypes.Router,
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
wasmkeeper.MessageHandlerFunc(func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
if msg.Staking != nil {
return nil, nil, sdkerrors.Wrap(wasmtypes.ErrExecuteFailed, "not supported, yet")
}
return nil, nil, wasmtypes.ErrUnknownMsg
}),
nested,
// append our custom message handler
twasmkeeper.NewTgradeHandler(cdc, result, bankKeeper, govRouter),
)
})
return []wasm.Option{
queryPluginOpt,
extMessageHandlerOpt,
}
}
5 changes: 0 additions & 5 deletions x/poe/contract/tg4_stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package contract

import (
"encoding/json"
"testing"
"time"

stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
Expand All @@ -28,10 +27,6 @@ type TG4StakeInitMsg struct {
Preauths uint64 `json:"preauths,omitempty"`
}

func (m TG4StakeInitMsg) Json(t *testing.T) string {
return asJson(t, m)
}

// TG4StakeExecute staking contract execute messages
// See https://github.com/confio/tgrade-contracts/blob/v0.5.0-alpha/contracts/tg4-stake/src/msg.rs
type TG4StakeExecute struct {
Expand Down
60 changes: 60 additions & 0 deletions x/poe/contract/tg4_stake_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,66 @@ func TestQueryUnbondingPeriod(t *testing.T) {
assert.Equal(t, configuredTime, res)
}

func TestQueryStakedAmount(t *testing.T) {
// setup contracts and seed some data
ctx, example, _ := setupPoEContracts(t)
contractKeeper := example.TWasmKeeper.GetContractKeeper()
stakingContractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeStaking)
require.NoError(t, err)
contractAdapter := contract.NewStakeContractAdapter(stakingContractAddr, example.TWasmKeeper, nil)

// fund account
var myOperatorAddr sdk.AccAddress = rand.Bytes(sdk.AddrLen)
example.BankKeeper.SetBalances(ctx, myOperatorAddr, sdk.NewCoins(sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(100))))

var oneInt = sdk.OneInt()
specs := map[string]struct {
addr sdk.AccAddress
expAmount *sdk.Int
setup func(ctx sdk.Context)
expErr bool
}{
"address has staked amount": {
addr: myOperatorAddr,
setup: func(ctx sdk.Context) {
err := contract.BondDelegation(ctx, stakingContractAddr, myOperatorAddr, sdk.NewCoins(sdk.NewCoin("utgd", sdk.OneInt())), contractKeeper)
require.NoError(t, err)
},
expAmount: &oneInt,
},
"address had formerly staked amount": {
addr: myOperatorAddr,
setup: func(ctx sdk.Context) {
err := contract.BondDelegation(ctx, stakingContractAddr, myOperatorAddr, sdk.NewCoins(sdk.NewCoin("utgd", sdk.OneInt())), contractKeeper)
require.NoError(t, err)
err = contract.UnbondDelegation(ctx, stakingContractAddr, myOperatorAddr, sdk.OneInt(), contractKeeper)
require.NoError(t, err)
},
expAmount: nil,
},
"unknown address": {
addr: rand.Bytes(sdk.AddrLen),
setup: func(ctx sdk.Context) {},
expAmount: nil,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
tCtx, _ := ctx.CacheContext()
spec.setup(tCtx)
// when
gotAmount, gotErr := contractAdapter.QueryStakedAmount(tCtx, spec.addr)
// then
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.expAmount, gotAmount, "exp %s but got %s", spec.expAmount, gotAmount)
})
}
}

func TestQueryValidatorUnboding(t *testing.T) {
// setup contracts and seed some data
ctx, example, vals := setupPoEContracts(t)
Expand Down
2 changes: 1 addition & 1 deletion x/poe/contract/tgrade_valset.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ type ValsetEpochResponse struct {
// The last time we updated the validator set - block height
LastUpdateHeight uint64 `json:"last_update_height"`
// TODO: add this if you want it, not in current code
/// Seconds (UTC UNIX time) of next timestamp that will trigger a validator recalculation
// Seconds (UTC UNIX time) of next timestamp that will trigger a validator recalculation
//NextUpdateTime int `json:"next_update_time"`
}

Expand Down
3 changes: 3 additions & 0 deletions x/poe/keeper/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
)

type DistributionContract interface {
// ValidatorOutstandingReward returns amount or 0 for an unknown address
ValidatorOutstandingReward(ctx sdk.Context, addr sdk.AccAddress) (sdk.Coin, error)
}

Expand All @@ -31,8 +32,10 @@ func (k Keeper) ValsetContract(ctx sdk.Context) ValsetContract {
}

type StakeContract interface {
// QueryStakedAmount returns amount in default denom or nil value for an unknown address
QueryStakedAmount(ctx sdk.Context, opAddr sdk.AccAddress) (*sdk.Int, error)
QueryStakingUnbondingPeriod(ctx sdk.Context) (time.Duration, error)
// QueryStakingUnbonding returns the unbondings or empty list for an unknown address
QueryStakingUnbonding(ctx sdk.Context, opAddr sdk.AccAddress) ([]stakingtypes.UnbondingDelegationEntry, error)
}

Expand Down
2 changes: 1 addition & 1 deletion x/poe/keeper/poetesting/mock_contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 24 additions & 1 deletion x/poe/keeper/test_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import (
"testing"
"time"

wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

"github.com/CosmWasm/wasmd/x/wasm"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
Expand Down Expand Up @@ -202,7 +206,26 @@ func createTestInput(

stakingAdapter := stakingadapter.NewStakingAdapter(nil, nil)
twasmSubspace := paramsKeeper.Subspace(twasmtypes.DefaultParamspace)
twasmKeeper := twasmkeeper.NewKeeper(

var twasmKeeper twasmkeeper.Keeper
handler := wasmkeeper.WithMessageHandlerDecorator(func(nested wasmkeeper.Messenger) wasmkeeper.Messenger {
return wasmkeeper.NewMessageHandlerChain(
// disable staking messages
wasmkeeper.MessageHandlerFunc(func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
if msg.Staking != nil {
return nil, nil, sdkerrors.Wrap(wasmtypes.ErrExecuteFailed, "not supported, yet")
}
return nil, nil, wasmtypes.ErrUnknownMsg
}),
nested,
// append our custom message handler
twasmkeeper.NewTgradeHandler(appCodec, &twasmKeeper, bankKeeper, nil),
)
})

opts = append([]wasmkeeper.Option{handler}, opts...)

twasmKeeper = twasmkeeper.NewKeeper(
appCodec,
keyWasm,
twasmSubspace,
Expand Down
131 changes: 131 additions & 0 deletions x/poe/wasm/query_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package wasm

import (
"encoding/json"

wasmvmtypes "github.com/CosmWasm/wasmvm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

"github.com/confio/tgrade/x/poe/keeper"
)

type ViewKeeper interface {
GetBondDenom(ctx sdk.Context) string
DistributionContract(ctx sdk.Context) keeper.DistributionContract
ValsetContract(ctx sdk.Context) keeper.ValsetContract
StakeContract(ctx sdk.Context) keeper.StakeContract
}

func StakingQuerier(poeKeeper ViewKeeper) func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) {
return func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) {
if request.BondedDenom != nil {
denom := poeKeeper.GetBondDenom(ctx)
res := wasmvmtypes.BondedDenomResponse{
Denom: denom,
}
return json.Marshal(res)
}
zero := sdk.ZeroDec().String()
if request.AllValidators != nil {
validators, err := poeKeeper.ValsetContract(ctx).ListValidators(ctx)
if err != nil {
return nil, err
}
wasmVals := make([]wasmvmtypes.Validator, len(validators))
for i, v := range validators {
wasmVals[i] = wasmvmtypes.Validator{
Address: v.OperatorAddress,
Commission: zero,
MaxCommission: zero,
MaxChangeRate: zero,
}
}
res := wasmvmtypes.AllValidatorsResponse{
Validators: wasmVals,
}
return json.Marshal(res)
}
if request.Validator != nil {
valAddr, err := sdk.AccAddressFromBech32(request.Validator.Address)
if err != nil {
return nil, err
}
v, err := poeKeeper.ValsetContract(ctx).QueryValidator(ctx, valAddr)
if err != nil {
return nil, err
}
res := wasmvmtypes.ValidatorResponse{}
if v != nil {
res.Validator = &wasmvmtypes.Validator{
Address: v.OperatorAddress,
Commission: zero,
MaxCommission: zero,
MaxChangeRate: zero,
}
}
return json.Marshal(res)
}
if request.AllDelegations != nil {
delegator, err := sdk.AccAddressFromBech32(request.AllDelegations.Delegator)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, request.AllDelegations.Delegator)
}
stakedAmount, err := poeKeeper.StakeContract(ctx).QueryStakedAmount(ctx, delegator)
if err != nil {
return nil, err
}
var res wasmvmtypes.AllDelegationsResponse
if stakedAmount != nil {
res.Delegations = []wasmvmtypes.Delegation{{
Delegator: delegator.String(),
Validator: delegator.String(),
Amount: wasmvmtypes.NewCoin(stakedAmount.Uint64(), poeKeeper.GetBondDenom(ctx)),
}}
}
return json.Marshal(res)
}
if request.Delegation != nil {
delegator, err := sdk.AccAddressFromBech32(request.Delegation.Delegator)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, request.Delegation.Delegator)
}
validator, err := sdk.AccAddressFromBech32(request.Delegation.Validator)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, request.Delegation.Validator)
}

var res wasmvmtypes.DelegationResponse
if !delegator.Equals(validator) { // no match
return json.Marshal(res)
}
stakeContract := poeKeeper.StakeContract(ctx)
stakedAmount, err := stakeContract.QueryStakedAmount(ctx, delegator)
if err != nil {
return nil, sdkerrors.Wrap(err, "query staked amount")
}
reward, err := poeKeeper.DistributionContract(ctx).ValidatorOutstandingReward(ctx, delegator)
if err != nil {
return nil, sdkerrors.Wrap(err, "query outstanding reward")
}
if stakedAmount == nil {
zeroInt := sdk.ZeroInt()
stakedAmount = &zeroInt
}
// there can be unclaimed rewards while all stacked amounts were unbound
if stakedAmount.GT(sdk.ZeroInt()) || reward.Amount.GT(sdk.ZeroInt()) {
bondDenom := poeKeeper.GetBondDenom(ctx)
stakedCoin := wasmvmtypes.NewCoin(stakedAmount.Uint64(), bondDenom)
res.Delegation = &wasmvmtypes.FullDelegation{
Delegator: delegator.String(),
Validator: delegator.String(),
Amount: stakedCoin,
CanRedelegate: wasmvmtypes.NewCoin(0, bondDenom),
AccumulatedRewards: wasmvmtypes.Coins{wasmvmtypes.NewCoin(reward.Amount.Uint64(), reward.Denom)},
}
}
return json.Marshal(res)
}
return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown Staking variant"}
}
}
Loading

0 comments on commit b9fa236

Please sign in to comment.