diff --git a/app/app.go b/app/app.go index 061ad8b..4197809 100644 --- a/app/app.go +++ b/app/app.go @@ -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 { diff --git a/x/wasm/internal/keeper/genesis_test.go b/x/wasm/internal/keeper/genesis_test.go index 20ffbd4..0ac4014 100644 --- a/x/wasm/internal/keeper/genesis_test.go +++ b/x/wasm/internal/keeper/genesis_test.go @@ -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 diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index f3476f4..dcd9b56 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -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 } diff --git a/x/wasm/internal/keeper/query_plugins.go b/x/wasm/internal/keeper/query_plugins.go index dab32d0..862575f 100644 --- a/x/wasm/internal/keeper/query_plugins.go +++ b/x/wasm/internal/keeper/query_plugins.go @@ -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 { diff --git a/x/wasm/internal/keeper/staking_test.go b/x/wasm/internal/keeper/staking_test.go index bc44bd3..7e66405 100644 --- a/x/wasm/internal/keeper/staking_test.go +++ b/x/wasm/internal/keeper/staking_test.go @@ -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 diff --git a/x/wasm/internal/keeper/test_common.go b/x/wasm/internal/keeper/test_common.go index 07b348b..4c12259 100644 --- a/x/wasm/internal/keeper/test_common.go +++ b/x/wasm/internal/keeper/test_common.go @@ -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())