Merge pull request #278 from CosmWasm/fix-staking-queries

Better calculation of Delegation data
This commit is contained in:
Ethan Frey 2020-10-07 14:49:10 +02:00 committed by GitHub
commit 3571625d9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 161 additions and 28 deletions

View File

@ -299,7 +299,10 @@ func NewWasmApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
// The last arguments can contain custom message handlers, and custom query handlers,
// if we want to allow any custom callbacks
supportedFeatures := "staking"
app.wasmKeeper = wasm.NewKeeper(app.cdc, keys[wasm.StoreKey], app.subspaces[wasm.ModuleName], app.accountKeeper, app.bankKeeper, app.stakingKeeper, wasmRouter, wasmDir, wasmConfig, supportedFeatures, nil, nil)
app.wasmKeeper = wasm.NewKeeper(
app.cdc, keys[wasm.StoreKey], app.subspaces[wasm.ModuleName], app.accountKeeper,
app.bankKeeper, app.stakingKeeper, app.distrKeeper, wasmRouter, wasmDir, wasmConfig,
supportedFeatures, nil, nil)
// The gov proposal types can be individually enabled
if len(enabledProposals) != 0 {

View File

@ -6,6 +6,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/cosmos/cosmos-sdk/x/distribution"
"io/ioutil"
"math/rand"
"os"
@ -496,7 +497,7 @@ func setupKeeper(t *testing.T) (Keeper, sdk.Context, []sdk.StoreKey, func()) {
cdc := MakeTestCodec()
pk := params.NewKeeper(cdc, keyParams, tkeyParams)
wasmConfig := wasmTypes.DefaultWasmConfig()
srcKeeper := NewKeeper(cdc, keyWasm, pk.Subspace(wasmTypes.DefaultParamspace), auth.AccountKeeper{}, nil, staking.Keeper{}, nil, tempDir, wasmConfig, "", nil, nil)
srcKeeper := NewKeeper(cdc, keyWasm, pk.Subspace(wasmTypes.DefaultParamspace), auth.AccountKeeper{}, nil, staking.Keeper{}, distribution.Keeper{}, nil, tempDir, wasmConfig, "", nil, nil)
srcKeeper.setParams(ctx, wasmTypes.DefaultParams())
return srcKeeper, ctx, []sdk.StoreKey{keyWasm, keyParams}, cleanup

View File

@ -3,6 +3,7 @@ package keeper
import (
"bytes"
"encoding/binary"
"github.com/cosmos/cosmos-sdk/x/distribution"
"path/filepath"
"github.com/cosmos/cosmos-sdk/x/params/subspace"
@ -61,7 +62,7 @@ type Keeper struct {
// NewKeeper creates a new contract Keeper instance
// If customEncoders is non-nil, we can use this to override some of the message handler, especially custom
func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, paramSpace params.Subspace, accountKeeper auth.AccountKeeper, bankKeeper bank.Keeper,
stakingKeeper staking.Keeper,
stakingKeeper staking.Keeper, distKeeper distribution.Keeper,
router sdk.Router, homeDir string, wasmConfig types.WasmConfig, supportedFeatures string, customEncoders *MessageEncoders, customPlugins *QueryPlugins) Keeper {
wasmer, err := wasm.NewWasmer(filepath.Join(homeDir, "wasm"), supportedFeatures)
if err != nil {
@ -84,7 +85,7 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, paramSpace params.Subspa
authZPolicy: DefaultAuthorizationPolicy{},
paramSpace: paramSpace,
}
keeper.queryPlugins = DefaultQueryPlugins(bankKeeper, stakingKeeper, &keeper).Merge(customPlugins)
keeper.queryPlugins = DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, &keeper).Merge(customPlugins)
return keeper
}

View File

@ -6,7 +6,9 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/staking"
abci "github.com/tendermint/tendermint/abci/types"
)
type QueryHandler struct {
@ -55,11 +57,11 @@ type QueryPlugins struct {
Wasm func(ctx sdk.Context, request *wasmTypes.WasmQuery) ([]byte, error)
}
func DefaultQueryPlugins(bank bank.ViewKeeper, staking staking.Keeper, wasm *Keeper) QueryPlugins {
func DefaultQueryPlugins(bank bank.ViewKeeper, staking staking.Keeper, distKeeper distribution.Keeper, wasm *Keeper) QueryPlugins {
return QueryPlugins{
Bank: BankQuerier(bank),
Custom: NoCustomQuerier,
Staking: StakingQuerier(staking),
Staking: StakingQuerier(staking, distKeeper),
Wasm: WasmQuerier(wasm),
}
}
@ -120,7 +122,7 @@ func NoCustomQuerier(sdk.Context, json.RawMessage) ([]byte, error) {
return nil, wasmTypes.UnsupportedRequest{Kind: "custom"}
}
func StakingQuerier(keeper staking.Keeper) func(ctx sdk.Context, request *wasmTypes.StakingQuery) ([]byte, error) {
func StakingQuerier(keeper staking.Keeper, distKeeper distribution.Keeper) func(ctx sdk.Context, request *wasmTypes.StakingQuery) ([]byte, error) {
return func(ctx sdk.Context, request *wasmTypes.StakingQuery) ([]byte, error) {
if request.BondedDenom != nil {
denom := keeper.BondDenom(ctx)
@ -174,7 +176,7 @@ func StakingQuerier(keeper staking.Keeper) func(ctx sdk.Context, request *wasmTy
var res wasmTypes.DelegationResponse
d, found := keeper.GetDelegation(ctx, delegator, validator)
if found {
res.Delegation, err = sdkToFullDelegation(ctx, keeper, d)
res.Delegation, err = sdkToFullDelegation(ctx, keeper, distKeeper, d)
if err != nil {
return nil, err
}
@ -198,11 +200,6 @@ func sdkToDelegations(ctx sdk.Context, keeper staking.Keeper, delegations []stak
}
amount := sdk.NewCoin(bondDenom, val.TokensFromShares(d.Shares).TruncateInt())
// Accumulated Rewards???
// can relegate? other query for redelegations?
// keeper.GetRedelegation
result[i] = wasmTypes.Delegation{
Delegator: d.DelegatorAddress.String(),
Validator: d.ValidatorAddress.String(),
@ -212,7 +209,7 @@ func sdkToDelegations(ctx sdk.Context, keeper staking.Keeper, delegations []stak
return result, nil
}
func sdkToFullDelegation(ctx sdk.Context, keeper staking.Keeper, delegation staking.Delegation) (*wasmTypes.FullDelegation, error) {
func sdkToFullDelegation(ctx sdk.Context, keeper staking.Keeper, distKeeper distribution.Keeper, delegation staking.Delegation) (*wasmTypes.FullDelegation, error) {
val, found := keeper.GetValidator(ctx, delegation.ValidatorAddress)
if !found {
return nil, sdkerrors.Wrap(staking.ErrNoValidatorFound, "can't load validator for delegation")
@ -220,20 +217,75 @@ func sdkToFullDelegation(ctx sdk.Context, keeper staking.Keeper, delegation stak
bondDenom := keeper.BondDenom(ctx)
amount := sdk.NewCoin(bondDenom, val.TokensFromShares(delegation.Shares).TruncateInt())
// can relegate? other query for redelegations?
// keeper.GetRedelegation
delegationCoins := convertSdkCoinToWasmCoin(amount)
// FIXME: this is very rough but better than nothing...
// https://github.com/CosmWasm/wasmd/issues/282
// if this (val, delegate) pair is receiving a redelegation, it cannot redelegate more
// otherwise, it can redelegate the full amount
// (there are cases of partial funds redelegated, but this is a start)
redelegateCoins := wasmTypes.NewCoin(0, bondDenom)
if !keeper.HasReceivingRedelegation(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) {
redelegateCoins = delegationCoins
}
// FIXME: make a cleaner way to do this (modify the sdk)
// we need the info from `distKeeper.calculateDelegationRewards()`, but it is not public
// neither is `queryDelegationRewards(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper)`
// so we go through the front door of the querier....
accRewards, err := getAccumulatedRewards(ctx, distKeeper, delegation)
if err != nil {
return nil, err
}
return &wasmTypes.FullDelegation{
Delegator: delegation.DelegatorAddress.String(),
Validator: delegation.ValidatorAddress.String(),
Amount: convertSdkCoinToWasmCoin(amount),
// TODO: AccumulatedRewards
AccumulatedRewards: wasmTypes.Coins{},
// TODO: Determine redelegate
CanRedelegate: wasmTypes.NewCoin(0, bondDenom),
Delegator: delegation.DelegatorAddress.String(),
Validator: delegation.ValidatorAddress.String(),
Amount: delegationCoins,
AccumulatedRewards: accRewards,
CanRedelegate: redelegateCoins,
}, nil
}
// FIXME: simplify this enormously when
// https://github.com/cosmos/cosmos-sdk/issues/7466 is merged
func getAccumulatedRewards(ctx sdk.Context, distKeeper distribution.Keeper, delegation staking.Delegation) ([]wasmTypes.Coin, error) {
// Try to get *delegator* reward info!
params := distribution.QueryDelegationRewardsParams{
DelegatorAddress: delegation.DelegatorAddress,
ValidatorAddress: delegation.ValidatorAddress,
}
data, err := json.Marshal(params)
if err != nil {
return nil, err
}
req := abci.RequestQuery{Data: data}
// just to be safe... ensure we do not accidentally write in the querier (which does some funky things)
cache, _ := ctx.CacheContext()
qres, err := distribution.NewQuerier(distKeeper)(cache, []string{distribution.QueryDelegationRewards}, req)
if err != nil {
return nil, err
}
var decRewards sdk.DecCoins
err = json.Unmarshal(qres, &decRewards)
if err != nil {
return nil, err
}
// **** all this above should be ONE method call
// now we have it, convert it into wasmTypes
rewards := make([]wasmTypes.Coin, len(decRewards))
for i, r := range decRewards {
rewards[i] = wasmTypes.Coin{
Denom: r.Denom,
Amount: r.Amount.TruncateInt().String(),
}
}
return rewards, nil
}
func WasmQuerier(wasm *Keeper) func(ctx sdk.Context, request *wasmTypes.WasmQuery) ([]byte, error) {
return func(ctx sdk.Context, request *wasmTypes.WasmQuery) ([]byte, error) {
if request.Smart != nil {

View File

@ -369,7 +369,7 @@ func TestReinvest(t *testing.T) {
// we get 1/6, our share should be 40k minus 10% commission = 36k
setValidatorRewards(ctx, stakingKeeper, distKeeper, valAddr, "240000")
// this should withdraw our outstanding 40k of rewards and reinvest them in the same delegation
// this should withdraw our outstanding 36k of rewards and reinvest them in the same delegation
reinvest := StakingHandleMsg{
Reinvest: &struct{}{},
}
@ -438,6 +438,9 @@ func TestQueryStakingInfo(t *testing.T) {
// we get 1/6, our share should be 40k minus 10% commission = 36k
setValidatorRewards(ctx, stakingKeeper, distKeeper, valAddr, "240000")
// see what the current rewards are
origReward := distKeeper.GetValidatorCurrentRewards(ctx, valAddr)
// STEP 2: Prepare the mask contract
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit)
@ -534,9 +537,82 @@ func TestQueryStakingInfo(t *testing.T) {
// 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"))
require.Equal(t, wasmTypes.NewCoin(200000, "stake"), delInfo2.CanRedelegate)
require.Len(t, delInfo2.AccumulatedRewards, 1)
// see bonding above to see how we calculate 36000 (240000 / 6 - 10% commission)
require.Equal(t, wasmTypes.NewCoin(36000, "stake"), delInfo2.AccumulatedRewards[0])
// ensure rewards did not change when querying (neither amount nor period)
finalReward := distKeeper.GetValidatorCurrentRewards(ctx, valAddr)
require.Equal(t, origReward, finalReward)
}
func TestQueryStakingPlugin(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")
// see what the current rewards are
origReward := distKeeper.GetValidatorCurrentRewards(ctx, valAddr)
// Step 2: Try out the query plugins
query := wasmTypes.StakingQuery{
Delegation: &wasmTypes.DelegationQuery{
Delegator: contractAddr.String(),
Validator: valAddr.String(),
},
}
raw, err := StakingQuerier(stakingKeeper, distKeeper)(ctx, &query)
require.NoError(t, err)
var res wasmTypes.DelegationResponse
mustParse(t, raw, &res)
assert.NotEmpty(t, res.Delegation)
delInfo := res.Delegation
// 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)
require.Equal(t, wasmTypes.NewCoin(200000, "stake"), delInfo.CanRedelegate)
require.Len(t, delInfo.AccumulatedRewards, 1)
// see bonding above to see how we calculate 36000 (240000 / 6 - 10% commission)
require.Equal(t, wasmTypes.NewCoin(36000, "stake"), delInfo.AccumulatedRewards[0])
// ensure rewards did not change when querying (neither amount nor period)
finalReward := distKeeper.GetValidatorCurrentRewards(ctx, valAddr)
require.Equal(t, origReward, finalReward)
}
// adds a few validators and returns a list of validators that are registered

View File

@ -171,7 +171,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat
// Load default wasm config
wasmConfig := wasmtypes.DefaultWasmConfig()
keeper := NewKeeper(cdc, keyContract, paramsKeeper.Subspace(wasmtypes.DefaultParamspace),
accountKeeper, bankKeeper, stakingKeeper, router, tempDir, wasmConfig,
accountKeeper, bankKeeper, stakingKeeper, distKeeper, router, tempDir, wasmConfig,
supportedFeatures, encoders, queriers,
)
keeper.setParams(ctx, wasmtypes.DefaultParams())