mirror of https://github.com/certusone/wasmd.git
767 lines
30 KiB
Go
767 lines
30 KiB
Go
package keeper
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"testing"
|
|
|
|
wasmvmtypes "github.com/CosmWasm/wasmvm/v3/types"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
sdkmath "cosmossdk.io/math"
|
|
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
|
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
|
distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
|
|
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
|
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
|
|
"github.com/CosmWasm/wasmd/x/wasm/keeper/testdata"
|
|
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
|
|
)
|
|
|
|
type StakingInitMsg struct {
|
|
Name string `json:"name"`
|
|
Symbol string `json:"symbol"`
|
|
Decimals uint8 `json:"decimals"`
|
|
Validator sdk.ValAddress `json:"validator"`
|
|
ExitTax sdkmath.LegacyDec `json:"exit_tax"`
|
|
// MinWithdrawal is uint128 encoded as a string (use math.Int?)
|
|
MinWithdrawal string `json:"min_withdrawal"`
|
|
}
|
|
|
|
// StakingHandleMsg is used to encode handle messages
|
|
type StakingHandleMsg struct {
|
|
Transfer *transferPayload `json:"transfer,omitempty"`
|
|
Bond *struct{} `json:"bond,omitempty"`
|
|
Unbond *unbondPayload `json:"unbond,omitempty"`
|
|
Claim *struct{} `json:"claim,omitempty"`
|
|
Reinvest *struct{} `json:"reinvest,omitempty"`
|
|
Change *testdata.OwnerPayload `json:"change_owner,omitempty"`
|
|
}
|
|
|
|
type transferPayload struct {
|
|
Recipient sdk.Address `json:"recipient"`
|
|
// uint128 encoded as string
|
|
Amount string `json:"amount"`
|
|
}
|
|
|
|
type unbondPayload struct {
|
|
// uint128 encoded as string
|
|
Amount string `json:"amount"`
|
|
}
|
|
|
|
// StakingQueryMsg is used to encode query messages
|
|
type StakingQueryMsg struct {
|
|
Balance *addressQuery `json:"balance,omitempty"`
|
|
Claims *addressQuery `json:"claims,omitempty"`
|
|
TokenInfo *struct{} `json:"token_info,omitempty"`
|
|
Investment *struct{} `json:"investment,omitempty"`
|
|
}
|
|
|
|
type addressQuery struct {
|
|
Address sdk.AccAddress `json:"address"`
|
|
}
|
|
|
|
type BalanceResponse struct {
|
|
Balance string `json:"balance,omitempty"`
|
|
}
|
|
|
|
type ClaimsResponse struct {
|
|
Claims string `json:"claims,omitempty"`
|
|
}
|
|
|
|
type TokenInfoResponse struct {
|
|
Name string `json:"name"`
|
|
Symbol string `json:"symbol"`
|
|
Decimals uint8 `json:"decimals"`
|
|
}
|
|
|
|
type InvestmentResponse struct {
|
|
TokenSupply string `json:"token_supply"`
|
|
StakedTokens sdk.Coin `json:"staked_tokens"`
|
|
NominalValue sdkmath.LegacyDec `json:"nominal_value"`
|
|
Owner sdk.AccAddress `json:"owner"`
|
|
Validator sdk.ValAddress `json:"validator"`
|
|
ExitTax sdkmath.LegacyDec `json:"exit_tax"`
|
|
// MinWithdrawal is uint128 encoded as a string (use math.Int?)
|
|
MinWithdrawal string `json:"min_withdrawal"`
|
|
}
|
|
|
|
func TestInitializeStaking(t *testing.T) {
|
|
ctx, k := CreateTestInput(t, false, AvailableCapabilities)
|
|
accKeeper, stakingKeeper, keeper, bankKeeper := k.AccountKeeper, k.StakingKeeper, k.ContractKeeper, k.BankKeeper
|
|
|
|
valAddr := addValidator(t, ctx, stakingKeeper, k.Faucet, sdk.NewInt64Coin("stake", 1234567))
|
|
ctx = nextBlock(ctx, stakingKeeper)
|
|
v, err := stakingKeeper.GetValidator(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, v.GetDelegatorShares(), sdkmath.LegacyNewDec(1234567))
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000), sdk.NewInt64Coin("stake", 500000))
|
|
creator := k.Faucet.NewFundedRandomAccount(ctx, deposit...)
|
|
|
|
// upload staking derivative code
|
|
stakingCode, err := os.ReadFile("./testdata/staking.wasm")
|
|
require.NoError(t, err)
|
|
stakingID, _, err := keeper.Create(ctx, creator, stakingCode, nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), stakingID)
|
|
|
|
// register to a valid address
|
|
initMsg := StakingInitMsg{
|
|
Name: "Staking Derivatives",
|
|
Symbol: "DRV",
|
|
Decimals: 0,
|
|
Validator: valAddr,
|
|
ExitTax: sdkmath.LegacyMustNewDecFromStr("0.10"),
|
|
MinWithdrawal: "100",
|
|
}
|
|
initBz, err := json.Marshal(&initMsg)
|
|
require.NoError(t, err)
|
|
|
|
stakingAddr, _, err := k.ContractKeeper.Instantiate(ctx, stakingID, creator, nil, initBz, "staking derivative - DRV", nil)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, stakingAddr)
|
|
|
|
// nothing spent here
|
|
checkAccount(t, ctx, accKeeper, bankKeeper, creator, deposit)
|
|
|
|
// try to register with a validator not on the list and it fails
|
|
_, bob := keyPubAddr()
|
|
badInitMsg := StakingInitMsg{
|
|
Name: "Missing Validator",
|
|
Symbol: "MISS",
|
|
Decimals: 0,
|
|
Validator: sdk.ValAddress(bob),
|
|
ExitTax: sdkmath.LegacyMustNewDecFromStr("0.10"),
|
|
MinWithdrawal: "100",
|
|
}
|
|
badBz, err := json.Marshal(&badInitMsg)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = k.ContractKeeper.Instantiate(ctx, stakingID, creator, nil, badBz, "missing validator", nil)
|
|
require.Error(t, err)
|
|
|
|
// no changes to bonding shares
|
|
val, _ := stakingKeeper.GetValidator(ctx, valAddr)
|
|
assert.Equal(t, val.GetDelegatorShares(), sdkmath.LegacyNewDec(1234567))
|
|
}
|
|
|
|
type initInfo struct {
|
|
valAddr sdk.ValAddress
|
|
creator sdk.AccAddress
|
|
contractAddr sdk.AccAddress
|
|
|
|
ctx sdk.Context
|
|
accKeeper authkeeper.AccountKeeper
|
|
stakingKeeper *stakingkeeper.Keeper
|
|
distKeeper distributionkeeper.Keeper
|
|
wasmKeeper Keeper
|
|
contractKeeper wasmtypes.ContractOpsKeeper
|
|
bankKeeper bankkeeper.Keeper
|
|
faucet *TestFaucet
|
|
}
|
|
|
|
func initializeStaking(t *testing.T) initInfo {
|
|
ctx, k := CreateTestInput(t, false, AvailableCapabilities)
|
|
accKeeper, stakingKeeper, keeper, bankKeeper := k.AccountKeeper, k.StakingKeeper, k.WasmKeeper, k.BankKeeper
|
|
|
|
valAddr := addValidator(t, ctx, stakingKeeper, k.Faucet, sdk.NewInt64Coin("stake", 1000000))
|
|
ctx = nextBlock(ctx, stakingKeeper)
|
|
|
|
v, err := stakingKeeper.GetValidator(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, v.GetDelegatorShares(), sdkmath.LegacyNewDec(1000000))
|
|
assert.Equal(t, v.Status, stakingtypes.Bonded)
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000), sdk.NewInt64Coin("stake", 500000))
|
|
creator := k.Faucet.NewFundedRandomAccount(ctx, deposit...)
|
|
|
|
// upload staking derivative code
|
|
stakingCode, err := os.ReadFile("./testdata/staking.wasm")
|
|
require.NoError(t, err)
|
|
stakingID, _, err := k.ContractKeeper.Create(ctx, creator, stakingCode, nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), stakingID)
|
|
|
|
// register to a valid address
|
|
initMsg := StakingInitMsg{
|
|
Name: "Staking Derivatives",
|
|
Symbol: "DRV",
|
|
Decimals: 0,
|
|
Validator: valAddr,
|
|
ExitTax: sdkmath.LegacyMustNewDecFromStr("0.10"),
|
|
MinWithdrawal: "100",
|
|
}
|
|
initBz, err := json.Marshal(&initMsg)
|
|
require.NoError(t, err)
|
|
|
|
stakingAddr, _, err := k.ContractKeeper.Instantiate(ctx, stakingID, creator, nil, initBz, "staking derivative - DRV", nil)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, stakingAddr)
|
|
|
|
return initInfo{
|
|
valAddr: valAddr,
|
|
creator: creator,
|
|
contractAddr: stakingAddr,
|
|
ctx: ctx,
|
|
accKeeper: accKeeper,
|
|
stakingKeeper: stakingKeeper,
|
|
wasmKeeper: *keeper,
|
|
distKeeper: k.DistKeeper,
|
|
bankKeeper: bankKeeper,
|
|
contractKeeper: k.ContractKeeper,
|
|
faucet: k.Faucet,
|
|
}
|
|
}
|
|
|
|
func TestBonding(t *testing.T) {
|
|
initInfo := initializeStaking(t)
|
|
ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr
|
|
keeper, stakingKeeper, accKeeper, bankKeeper := initInfo.wasmKeeper, initInfo.stakingKeeper, initInfo.accKeeper, initInfo.bankKeeper
|
|
|
|
// initial checks of bonding state
|
|
val, err := stakingKeeper.GetValidator(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
initPower := val.GetDelegatorShares()
|
|
|
|
// bob has 160k, putting 80k into the contract
|
|
full := sdk.NewCoins(sdk.NewInt64Coin("stake", 160000))
|
|
funds := sdk.NewCoins(sdk.NewInt64Coin("stake", 80000))
|
|
bob := initInfo.faucet.NewFundedRandomAccount(ctx, full...)
|
|
|
|
// check contract state before
|
|
assertBalance(t, ctx, keeper, contractAddr, bob, "0")
|
|
assertClaims(t, ctx, keeper, contractAddr, bob, "0")
|
|
assertSupply(t, ctx, keeper, contractAddr, "0", sdk.NewInt64Coin("stake", 0))
|
|
|
|
bond := StakingHandleMsg{
|
|
Bond: &struct{}{},
|
|
}
|
|
bondBz, err := json.Marshal(bond)
|
|
require.NoError(t, err)
|
|
_, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, bondBz, funds)
|
|
require.NoError(t, err)
|
|
|
|
// check some account values - the money is on neither account (cuz it is bonded)
|
|
checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.Coins{})
|
|
checkAccount(t, ctx, accKeeper, bankKeeper, bob, funds)
|
|
|
|
// make sure the proper number of tokens have been bonded
|
|
val, _ = stakingKeeper.GetValidator(ctx, valAddr)
|
|
finalPower := val.GetDelegatorShares()
|
|
assert.Equal(t, sdkmath.NewInt(80000), finalPower.Sub(initPower).TruncateInt())
|
|
|
|
// check the delegation itself
|
|
d, err := stakingKeeper.GetDelegation(ctx, contractAddr, valAddr)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, d.Shares, sdkmath.LegacyMustNewDecFromStr("80000"))
|
|
|
|
// check we have the desired balance
|
|
assertBalance(t, ctx, keeper, contractAddr, bob, "80000")
|
|
assertClaims(t, ctx, keeper, contractAddr, bob, "0")
|
|
assertSupply(t, ctx, keeper, contractAddr, "80000", sdk.NewInt64Coin("stake", 80000))
|
|
}
|
|
|
|
func TestUnbonding(t *testing.T) {
|
|
initInfo := initializeStaking(t)
|
|
ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr
|
|
keeper, stakingKeeper, accKeeper, bankKeeper := initInfo.wasmKeeper, initInfo.stakingKeeper, initInfo.accKeeper, initInfo.bankKeeper
|
|
|
|
// initial checks of bonding state
|
|
val, err := stakingKeeper.GetValidator(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
initPower := val.GetDelegatorShares()
|
|
|
|
// bob has 160k, putting 80k into the contract
|
|
full := sdk.NewCoins(sdk.NewInt64Coin("stake", 160000))
|
|
funds := sdk.NewCoins(sdk.NewInt64Coin("stake", 80000))
|
|
bob := initInfo.faucet.NewFundedRandomAccount(ctx, full...)
|
|
|
|
bond := StakingHandleMsg{
|
|
Bond: &struct{}{},
|
|
}
|
|
bondBz, err := json.Marshal(bond)
|
|
require.NoError(t, err)
|
|
_, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, bondBz, funds)
|
|
require.NoError(t, err)
|
|
|
|
// update height a bit
|
|
ctx = nextBlock(ctx, stakingKeeper)
|
|
|
|
// now unbond 30k - note that 3k (10%) goes to the owner as a tax, 27k unbonded and available as claims
|
|
unbond := StakingHandleMsg{
|
|
Unbond: &unbondPayload{
|
|
Amount: "30000",
|
|
},
|
|
}
|
|
unbondBz, err := json.Marshal(unbond)
|
|
require.NoError(t, err)
|
|
_, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, unbondBz, nil)
|
|
require.NoError(t, err)
|
|
|
|
// check some account values - the money is on neither account (cuz it is bonded)
|
|
// Note: why is this immediate? just test setup?
|
|
checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.Coins{})
|
|
checkAccount(t, ctx, accKeeper, bankKeeper, bob, funds)
|
|
|
|
// make sure the proper number of tokens have been bonded (80k - 27k = 53k)
|
|
val, _ = stakingKeeper.GetValidator(ctx, valAddr)
|
|
finalPower := val.GetDelegatorShares()
|
|
assert.Equal(t, sdkmath.NewInt(53000), finalPower.Sub(initPower).TruncateInt(), finalPower.String())
|
|
|
|
// check the delegation itself
|
|
d, err := stakingKeeper.GetDelegation(ctx, contractAddr, valAddr)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, d.Shares, sdkmath.LegacyMustNewDecFromStr("53000"))
|
|
|
|
// check there is unbonding in progress
|
|
un, err := stakingKeeper.GetUnbondingDelegation(ctx, contractAddr, valAddr)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(un.Entries))
|
|
assert.Equal(t, "27000", un.Entries[0].Balance.String())
|
|
|
|
// check we have the desired balance
|
|
assertBalance(t, ctx, keeper, contractAddr, bob, "50000")
|
|
assertBalance(t, ctx, keeper, contractAddr, initInfo.creator, "3000")
|
|
assertClaims(t, ctx, keeper, contractAddr, bob, "27000")
|
|
assertSupply(t, ctx, keeper, contractAddr, "53000", sdk.NewInt64Coin("stake", 53000))
|
|
}
|
|
|
|
func TestReinvest(t *testing.T) {
|
|
initInfo := initializeStaking(t)
|
|
ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr
|
|
keeper, stakingKeeper, accKeeper, bankKeeper := initInfo.wasmKeeper, initInfo.stakingKeeper, initInfo.accKeeper, initInfo.bankKeeper
|
|
distKeeper := initInfo.distKeeper
|
|
|
|
// initial checks of bonding state
|
|
val, err := stakingKeeper.GetValidator(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
initPower := val.GetDelegatorShares()
|
|
assert.Equal(t, val.Tokens, sdkmath.NewInt(1000000), "%s", 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 := initInfo.faucet.NewFundedRandomAccount(ctx, 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 = initInfo.contractKeeper.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")
|
|
|
|
// this should withdraw our outstanding 36k of rewards and reinvest them in the same delegation
|
|
reinvest := StakingHandleMsg{
|
|
Reinvest: &struct{}{},
|
|
}
|
|
reinvestBz, err := json.Marshal(reinvest)
|
|
require.NoError(t, err)
|
|
_, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, reinvestBz, nil)
|
|
require.NoError(t, err)
|
|
|
|
// check some account values - the money is on neither account (cuz it is bonded)
|
|
// Note: why is this immediate? just test setup?
|
|
checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.Coins{})
|
|
checkAccount(t, ctx, accKeeper, bankKeeper, bob, funds)
|
|
|
|
// check the delegation itself
|
|
d, err := stakingKeeper.GetDelegation(ctx, contractAddr, valAddr)
|
|
require.NoError(t, err)
|
|
// we started with 200k and added 36k
|
|
assert.Equal(t, d.Shares, sdkmath.LegacyMustNewDecFromStr("236000"))
|
|
|
|
// make sure the proper number of tokens have been bonded (80k + 40k = 120k)
|
|
val, _ = stakingKeeper.GetValidator(ctx, valAddr)
|
|
finalPower := val.GetDelegatorShares()
|
|
assert.Equal(t, sdkmath.NewInt(236000), finalPower.Sub(initPower).TruncateInt(), finalPower.String())
|
|
|
|
// check there is no unbonding in progress
|
|
_, err = stakingKeeper.GetUnbondingDelegation(ctx, contractAddr, valAddr)
|
|
require.ErrorIs(t, stakingtypes.ErrNoUnbondingDelegation, err)
|
|
|
|
// check we have the desired balance
|
|
assertBalance(t, ctx, keeper, contractAddr, bob, "200000")
|
|
assertBalance(t, ctx, keeper, contractAddr, initInfo.creator, "0")
|
|
assertClaims(t, ctx, keeper, contractAddr, bob, "0")
|
|
assertSupply(t, ctx, keeper, contractAddr, "200000", sdk.NewInt64Coin("stake", 236000))
|
|
}
|
|
|
|
func TestQueryStakingInfo(t *testing.T) {
|
|
// STEP 1: take a lot of setup from TestReinvest so we have non-zero info
|
|
initInfo := initializeStaking(t)
|
|
ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr
|
|
keeper, stakingKeeper := initInfo.wasmKeeper, initInfo.stakingKeeper
|
|
distKeeper := initInfo.distKeeper
|
|
|
|
// initial checks of bonding state
|
|
val, err := stakingKeeper.GetValidator(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, sdkmath.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 := initInfo.faucet.NewFundedRandomAccount(ctx, 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 = initInfo.contractKeeper.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, err := distKeeper.GetValidatorCurrentRewards(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
|
|
// STEP 2: Prepare the mask contract
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := initInfo.faucet.NewFundedRandomAccount(ctx, deposit...)
|
|
|
|
// upload mask code
|
|
maskID, _, err := initInfo.contractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(2), maskID)
|
|
|
|
// creator instantiates a contract and gives it tokens
|
|
maskAddr, _, err := initInfo.contractKeeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", nil)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, maskAddr)
|
|
|
|
// STEP 3: now, let's reflect some queries.
|
|
// let's get the bonded denom
|
|
reflectBondedQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
|
BondedDenom: &struct{}{},
|
|
}}}}
|
|
reflectBondedBin := buildReflectQuery(t, &reflectBondedQuery)
|
|
res, err := keeper.QuerySmart(ctx, maskAddr, reflectBondedBin)
|
|
require.NoError(t, err)
|
|
// first we pull out the data from chain response, before parsing the original response
|
|
var reflectRes testdata.ChainResponse
|
|
mustUnmarshal(t, res, &reflectRes)
|
|
var bondedRes wasmvmtypes.BondedDenomResponse
|
|
mustUnmarshal(t, reflectRes.Data, &bondedRes)
|
|
assert.Equal(t, "stake", bondedRes.Denom)
|
|
|
|
// now, let's reflect a smart query into the x/wasm handlers and see if we get the same result
|
|
reflectAllValidatorsQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
|
AllValidators: &wasmvmtypes.AllValidatorsQuery{},
|
|
}}}}
|
|
reflectAllValidatorsBin := buildReflectQuery(t, &reflectAllValidatorsQuery)
|
|
res, err = keeper.QuerySmart(ctx, maskAddr, reflectAllValidatorsBin)
|
|
require.NoError(t, err)
|
|
// first we pull out the data from chain response, before parsing the original response
|
|
mustUnmarshal(t, res, &reflectRes)
|
|
var allValidatorsRes wasmvmtypes.AllValidatorsResponse
|
|
mustUnmarshal(t, reflectRes.Data, &allValidatorsRes)
|
|
require.Len(t, allValidatorsRes.Validators, 1, string(res))
|
|
valInfo := allValidatorsRes.Validators[0]
|
|
// Note: this ValAddress not AccAddress, may change with #264
|
|
require.Equal(t, valAddr.String(), valInfo.Address)
|
|
require.Contains(t, valInfo.Commission, "0.100")
|
|
require.Contains(t, valInfo.MaxCommission, "0.200")
|
|
require.Contains(t, valInfo.MaxChangeRate, "0.010")
|
|
|
|
// find a validator
|
|
reflectValidatorQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
|
Validator: &wasmvmtypes.ValidatorQuery{
|
|
Address: valAddr.String(),
|
|
},
|
|
}}}}
|
|
reflectValidatorBin := buildReflectQuery(t, &reflectValidatorQuery)
|
|
res, err = keeper.QuerySmart(ctx, maskAddr, reflectValidatorBin)
|
|
require.NoError(t, err)
|
|
// first we pull out the data from chain response, before parsing the original response
|
|
mustUnmarshal(t, res, &reflectRes)
|
|
var validatorRes wasmvmtypes.ValidatorResponse
|
|
mustUnmarshal(t, reflectRes.Data, &validatorRes)
|
|
require.NotNil(t, validatorRes.Validator)
|
|
valInfo = *validatorRes.Validator
|
|
// Note: this ValAddress not AccAddress, may change with #264
|
|
require.Equal(t, valAddr.String(), valInfo.Address)
|
|
require.Contains(t, valInfo.Commission, "0.100")
|
|
require.Contains(t, valInfo.MaxCommission, "0.200")
|
|
require.Contains(t, valInfo.MaxChangeRate, "0.010")
|
|
|
|
// missing validator
|
|
noVal := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address())
|
|
reflectNoValidatorQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
|
Validator: &wasmvmtypes.ValidatorQuery{
|
|
Address: noVal.String(),
|
|
},
|
|
}}}}
|
|
reflectNoValidatorBin := buildReflectQuery(t, &reflectNoValidatorQuery)
|
|
res, err = keeper.QuerySmart(ctx, maskAddr, reflectNoValidatorBin)
|
|
require.NoError(t, err)
|
|
// first we pull out the data from chain response, before parsing the original response
|
|
mustUnmarshal(t, res, &reflectRes)
|
|
var noValidatorRes wasmvmtypes.ValidatorResponse
|
|
mustUnmarshal(t, reflectRes.Data, &noValidatorRes)
|
|
require.Nil(t, noValidatorRes.Validator)
|
|
|
|
// test to get all my delegations
|
|
reflectAllDelegationsQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
|
AllDelegations: &wasmvmtypes.AllDelegationsQuery{
|
|
Delegator: contractAddr.String(),
|
|
},
|
|
}}}}
|
|
reflectAllDelegationsBin := buildReflectQuery(t, &reflectAllDelegationsQuery)
|
|
res, err = keeper.QuerySmart(ctx, maskAddr, reflectAllDelegationsBin)
|
|
require.NoError(t, err)
|
|
// first we pull out the data from chain response, before parsing the original response
|
|
mustUnmarshal(t, res, &reflectRes)
|
|
var allDelegationsRes wasmvmtypes.AllDelegationsResponse
|
|
mustUnmarshal(t, reflectRes.Data, &allDelegationsRes)
|
|
require.Len(t, allDelegationsRes.Delegations, 1)
|
|
delInfo := allDelegationsRes.Delegations[0]
|
|
// 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)
|
|
|
|
// test to get one delegation
|
|
reflectDelegationQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
|
Delegation: &wasmvmtypes.DelegationQuery{
|
|
Validator: valAddr.String(),
|
|
Delegator: contractAddr.String(),
|
|
},
|
|
}}}}
|
|
reflectDelegationBin := buildReflectQuery(t, &reflectDelegationQuery)
|
|
res, err = keeper.QuerySmart(ctx, maskAddr, reflectDelegationBin)
|
|
require.NoError(t, err)
|
|
// first we pull out the data from chain response, before parsing the original response
|
|
mustUnmarshal(t, res, &reflectRes)
|
|
|
|
var delegationRes wasmvmtypes.DelegationResponse
|
|
mustUnmarshal(t, reflectRes.Data, &delegationRes)
|
|
assert.NotEmpty(t, delegationRes.Delegation)
|
|
delInfo2 := delegationRes.Delegation
|
|
// Note: this ValAddress not AccAddress, may change with #264
|
|
require.Equal(t, valAddr.String(), delInfo2.Validator)
|
|
// note this is not bob (who staked to the contract), but the contract itself
|
|
require.Equal(t, contractAddr.String(), delInfo2.Delegator)
|
|
// 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)
|
|
|
|
require.Equal(t, wasmvmtypes.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, wasmvmtypes.NewCoin(36000, "stake"), delInfo2.AccumulatedRewards[0])
|
|
|
|
// ensure rewards did not change when querying (neither amount nor period)
|
|
finalReward, err := distKeeper.GetValidatorCurrentRewards(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
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)
|
|
ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr
|
|
stakingKeeper := initInfo.stakingKeeper
|
|
distKeeper := initInfo.distKeeper
|
|
|
|
// initial checks of bonding state
|
|
val, err := stakingKeeper.GetValidator(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, sdkmath.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 := initInfo.faucet.NewFundedRandomAccount(ctx, 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 = initInfo.contractKeeper.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, err := distKeeper.GetValidatorCurrentRewards(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
|
|
// Step 2: Try out the query plugins
|
|
query := wasmvmtypes.StakingQuery{
|
|
Delegation: &wasmvmtypes.DelegationQuery{
|
|
Delegator: contractAddr.String(),
|
|
Validator: valAddr.String(),
|
|
},
|
|
}
|
|
raw, err := StakingQuerier(stakingKeeper, distributionkeeper.NewQuerier(distKeeper))(ctx, &query)
|
|
require.NoError(t, err)
|
|
var res wasmvmtypes.DelegationResponse
|
|
mustUnmarshal(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, wasmvmtypes.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, wasmvmtypes.NewCoin(36000, "stake"), delInfo.AccumulatedRewards[0])
|
|
|
|
// ensure rewards did not change when querying (neither amount nor period)
|
|
finalReward, err := distKeeper.GetValidatorCurrentRewards(ctx, valAddr)
|
|
require.NoError(t, err)
|
|
require.Equal(t, origReward, finalReward)
|
|
|
|
// with empty delegation (regression to ensure api stability)
|
|
query = wasmvmtypes.StakingQuery{
|
|
Delegation: &wasmvmtypes.DelegationQuery{
|
|
Delegator: RandomBech32AccountAddress(t),
|
|
Validator: valAddr.String(),
|
|
},
|
|
}
|
|
raw, err = StakingQuerier(stakingKeeper, distributionkeeper.NewQuerier(distKeeper))(ctx, &query)
|
|
require.NoError(t, err)
|
|
var res2 wasmvmtypes.DelegationResponse
|
|
mustUnmarshal(t, raw, &res2)
|
|
assert.Empty(t, res2.Delegation)
|
|
}
|
|
|
|
// adds a few validators and returns a list of validators that are registered
|
|
func addValidator(t *testing.T, ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper, faucet *TestFaucet, value sdk.Coin) sdk.ValAddress {
|
|
owner := faucet.NewFundedRandomAccount(ctx, value)
|
|
|
|
privKey := secp256k1.GenPrivKey()
|
|
pubKey := privKey.PubKey()
|
|
valAddr := sdk.ValAddress(owner)
|
|
|
|
pkAny, err := codectypes.NewAnyWithValue(pubKey)
|
|
require.NoError(t, err)
|
|
msg := &stakingtypes.MsgCreateValidator{
|
|
Description: stakingtypes.Description{
|
|
Moniker: "Validator power",
|
|
},
|
|
Commission: stakingtypes.CommissionRates{
|
|
Rate: sdkmath.LegacyMustNewDecFromStr("0.1"),
|
|
MaxRate: sdkmath.LegacyMustNewDecFromStr("0.2"),
|
|
MaxChangeRate: sdkmath.LegacyMustNewDecFromStr("0.01"),
|
|
},
|
|
MinSelfDelegation: sdkmath.OneInt(),
|
|
DelegatorAddress: owner.String(),
|
|
ValidatorAddress: valAddr.String(),
|
|
Pubkey: pkAny,
|
|
Value: value,
|
|
}
|
|
_, err = stakingkeeper.NewMsgServerImpl(stakingKeeper).CreateValidator(ctx, msg)
|
|
require.NoError(t, err)
|
|
return valAddr
|
|
}
|
|
|
|
// this will commit the current set, update the block height and set historic info
|
|
// basically, letting two blocks pass
|
|
func nextBlock(ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper) sdk.Context {
|
|
if _, err := stakingKeeper.EndBlocker(ctx); err != nil {
|
|
panic(err)
|
|
}
|
|
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
|
|
_ = stakingKeeper.BeginBlocker(ctx)
|
|
return ctx
|
|
}
|
|
|
|
func setValidatorRewards(ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper, distKeeper distributionkeeper.Keeper, valAddr sdk.ValAddress, reward string) {
|
|
// allocate some rewards
|
|
vali, err := stakingKeeper.Validator(ctx, valAddr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
amount, err := sdkmath.LegacyNewDecFromStr(reward)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
payout := sdk.DecCoins{{Denom: "stake", Amount: amount}}
|
|
err = distKeeper.AllocateTokensToValidator(ctx, vali, payout)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func assertBalance(t *testing.T, ctx sdk.Context, keeper Keeper, contract, addr sdk.AccAddress, expected string) {
|
|
query := StakingQueryMsg{
|
|
Balance: &addressQuery{
|
|
Address: addr,
|
|
},
|
|
}
|
|
queryBz, err := json.Marshal(query)
|
|
require.NoError(t, err)
|
|
res, err := keeper.QuerySmart(ctx, contract, queryBz)
|
|
require.NoError(t, err)
|
|
var balance BalanceResponse
|
|
err = json.Unmarshal(res, &balance)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expected, balance.Balance)
|
|
}
|
|
|
|
func assertClaims(t *testing.T, ctx sdk.Context, keeper Keeper, contract, addr sdk.AccAddress, expected string) {
|
|
query := StakingQueryMsg{
|
|
Claims: &addressQuery{
|
|
Address: addr,
|
|
},
|
|
}
|
|
queryBz, err := json.Marshal(query)
|
|
require.NoError(t, err)
|
|
res, err := keeper.QuerySmart(ctx, contract, queryBz)
|
|
require.NoError(t, err)
|
|
var claims ClaimsResponse
|
|
err = json.Unmarshal(res, &claims)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expected, claims.Claims)
|
|
}
|
|
|
|
func assertSupply(t *testing.T, ctx sdk.Context, keeper Keeper, contract sdk.AccAddress, expectedIssued string, expectedBonded sdk.Coin) {
|
|
query := StakingQueryMsg{Investment: &struct{}{}}
|
|
queryBz, err := json.Marshal(query)
|
|
require.NoError(t, err)
|
|
res, err := keeper.QuerySmart(ctx, contract, queryBz)
|
|
require.NoError(t, err)
|
|
var invest InvestmentResponse
|
|
err = json.Unmarshal(res, &invest)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expectedIssued, invest.TokenSupply)
|
|
assert.Equal(t, expectedBonded, invest.StakedTokens)
|
|
}
|