Skip to content

Commit

Permalink
Contract abstraction (#169)
Browse files Browse the repository at this point in the history
* Add POE setup to genesis

* Move bonded denum up in genesis

* Start refactoring into contract adapters

* Introduce poetesting package
  • Loading branch information
alpe authored Oct 28, 2021
1 parent 87727f4 commit 50b098e
Show file tree
Hide file tree
Showing 24 changed files with 740 additions and 670 deletions.
4 changes: 3 additions & 1 deletion x/poe/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func ClearEmbeddedContracts() {
type poeKeeper interface {
keeper.ContractSource
SetPoEContractAddress(ctx sdk.Context, ctype types.PoEContractType, contractAddr sdk.AccAddress)
ValsetContract(ctx sdk.Context) keeper.ValsetContract
}

// bootstrapPoEContracts stores and instantiates all PoE contracts:
Expand Down Expand Up @@ -135,8 +136,9 @@ func bootstrapPoEContracts(ctx sdk.Context, k wasmtypes.ContractOpsKeeper, tk tw
return sdkerrors.Wrap(err, "instantiate valset")
}
poeKeeper.SetPoEContractAddress(ctx, types.PoEContractTypeValset, valsetContractAddr)

// store distribution contract address
valsetCfg, err := contract.QueryValsetConfig(ctx, tk, valsetContractAddr)
valsetCfg, err := poeKeeper.ValsetContract(ctx).QueryConfig(ctx)
if err != nil {
return sdkerrors.Wrap(err, "query valset config")
}
Expand Down
9 changes: 8 additions & 1 deletion x/poe/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"path/filepath"
"testing"

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

wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -76,7 +78,7 @@ func TestBootstrapPoEContracts(t *testing.T) {
InitialKeys: []contract.Validator{},
ValidatorsRewardRatio: contract.DecimalFromPercentage(sdk.NewDec(50)),
RewardsCodeId: 1,
DistributionContract: "cosmos156r47kpk4va938pmtpuee4fh77847gqcq4xu6e",
DistributionContract: engagementContractAddr.String(),
},
},
}
Expand Down Expand Up @@ -105,6 +107,11 @@ func TestBootstrapPoEContracts(t *testing.T) {
sFn, capSetAddr := keeper.CaptureSetPoEContractAddressFn()
pm := keeper.PoEKeeperMock{
SetPoEContractAddressFn: sFn,
ValsetContractFn: func(ctx sdk.Context) keeper.ValsetContract {
return poetesting.ValsetContractMock{QueryConfigFn: func(ctx sdk.Context) (*contract.ValsetConfigResponse, error) {
return &contract.ValsetConfigResponse{DistributionContract: distributionContractAddr.String()}, nil
}}
},
}
// when
ctx := sdk.Context{}
Expand Down
209 changes: 1 addition & 208 deletions x/poe/contract/query_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package contract_test

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

wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
Expand All @@ -22,149 +20,6 @@ import (
"github.com/confio/tgrade/x/poe/types"
)

func TestListValidators(t *testing.T) {
// setup contracts and seed some data
ctx, example := keeper.CreateDefaultTestInput(t)
deliverTXFn := unAuthorizedDeliverTXFn(t, ctx, example.PoEKeeper, example.TWasmKeeper.GetContractKeeper(), example.EncodingConfig.TxConfig.TxDecoder())
module := poe.NewAppModule(example.PoEKeeper, example.TWasmKeeper, deliverTXFn, example.EncodingConfig.TxConfig, example.TWasmKeeper.GetContractKeeper())

mutator, expValidators := withRandomValidators(t, ctx, example, 3)
gs := types.GenesisStateFixture(mutator)
expValidators = resetTokenAmount(expValidators)

genesisBz := example.EncodingConfig.Marshaler.MustMarshalJSON(&gs)
module.InitGenesis(ctx, example.EncodingConfig.Marshaler, genesisBz)

// when
contractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeValset)
require.NoError(t, err)
vals, err := contract.ListValidators(ctx, example.TWasmKeeper, contractAddr)

// then
require.NoError(t, err)
sort.Slice(expValidators, func(i, j int) bool {
return expValidators[i].OperatorAddress < expValidators[j].OperatorAddress
})
gotValidators := make([]stakingtypes.Validator, len(vals))
for i, v := range vals {
gotValidators[i], err = v.ToValidator()
require.NoError(t, err)
}
assert.Equal(t, expValidators, gotValidators)
}

func TestGetValidator(t *testing.T) {
// setup contracts and seed some data
ctx, example := keeper.CreateDefaultTestInput(t)
deliverTXFn := unAuthorizedDeliverTXFn(t, ctx, example.PoEKeeper, example.TWasmKeeper.GetContractKeeper(), example.EncodingConfig.TxConfig.TxDecoder())
module := poe.NewAppModule(example.PoEKeeper, example.TWasmKeeper, deliverTXFn, example.EncodingConfig.TxConfig, example.TWasmKeeper.GetContractKeeper())

mutator, expValidators := withRandomValidators(t, ctx, example, 2)
gs := types.GenesisStateFixture(mutator)
expValidators = resetTokenAmount(expValidators)

genesisBz := example.EncodingConfig.Marshaler.MustMarshalJSON(&gs)
module.InitGenesis(ctx, example.EncodingConfig.Marshaler, genesisBz)

specs := map[string]struct {
operatorAddr string
expVal stakingtypes.Validator
expEmpty bool
}{
"query one validator": {
operatorAddr: expValidators[0].OperatorAddress,
expVal: expValidators[0],
},
"query other validator": {
operatorAddr: expValidators[1].OperatorAddress,
expVal: expValidators[1],
},
"query with unknown address": {
operatorAddr: sdk.AccAddress(rand.Bytes(sdk.AddrLen)).String(),
expEmpty: true,
},
"query with invalid address": {
operatorAddr: "not an address",
expEmpty: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
contractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeValset)
require.NoError(t, err)
opAddr, _ := sdk.AccAddressFromBech32(spec.operatorAddr)

// when
val, err := contract.QueryValidator(ctx, example.TWasmKeeper, contractAddr, opAddr)

// then
if spec.expEmpty {
assert.Nil(t, val)
return
}
gotVal, err := val.ToValidator()
require.NoError(t, err)
assert.Equal(t, spec.expVal, gotVal)
})
}
}

func TestQueryUnbondingPeriod(t *testing.T) {
// setup contracts and seed some data
ctx, example := keeper.CreateDefaultTestInput(t)
deliverTXFn := unAuthorizedDeliverTXFn(t, ctx, example.PoEKeeper, example.TWasmKeeper.GetContractKeeper(), example.EncodingConfig.TxConfig.TxDecoder())
module := poe.NewAppModule(example.PoEKeeper, example.TWasmKeeper, deliverTXFn, example.EncodingConfig.TxConfig, example.TWasmKeeper.GetContractKeeper())

mutator, _ := withRandomValidators(t, ctx, example, 1)
gs := types.GenesisStateFixture(mutator)
genesisBz := example.EncodingConfig.Marshaler.MustMarshalJSON(&gs)
module.InitGenesis(ctx, example.EncodingConfig.Marshaler, genesisBz)

contractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeStaking)
require.NoError(t, err)
// when
res, err := contract.QueryStakingUnbondingPeriod(ctx, example.TWasmKeeper, contractAddr)

// then
const configuredTime uint64 = 21 * 24 * 60 * 60 // in bootstrap
assert.Equal(t, configuredTime, res)
}

func TestQueryValsetConfig(t *testing.T) {
// setup contracts and seed some data
ctx, example := keeper.CreateDefaultTestInput(t)
deliverTXFn := unAuthorizedDeliverTXFn(t, ctx, example.PoEKeeper, example.TWasmKeeper.GetContractKeeper(), example.EncodingConfig.TxConfig.TxDecoder())
module := poe.NewAppModule(example.PoEKeeper, example.TWasmKeeper, deliverTXFn, example.EncodingConfig.TxConfig, example.TWasmKeeper.GetContractKeeper())

mutator, _ := withRandomValidators(t, ctx, example, 1)
gs := types.GenesisStateFixture(mutator)
genesisBz := example.EncodingConfig.Marshaler.MustMarshalJSON(&gs)
module.InitGenesis(ctx, example.EncodingConfig.Marshaler, genesisBz)

valsetContractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeValset)
require.NoError(t, err)
mixerContractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeMixer)
require.NoError(t, err)

// when
res, err := contract.QueryValsetConfig(ctx, example.TWasmKeeper, valsetContractAddr)

// then
expConfig := &contract.ValsetConfigResponse{
Membership: mixerContractAddr.String(),
MinWeight: 1,
MaxValidators: 100,
Scaling: 1,
EpochReward: sdk.NewInt64Coin("utgd", 100000),
FeePercentage: sdk.MustNewDecFromStr("0.50"),
ValidatorsRewardRatio: sdk.MustNewDecFromStr("0.50"),
DistributionContract: "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhuc53mp6",
RewardsContract: "cosmos1cnuw3f076wgdyahssdkd0g3nr96ckq8caf5mdm",
AutoUnjail: false,
}
assert.Equal(t, expConfig, res)
}

func TestQueryValidatorSelfDelegation(t *testing.T) {
// setup contracts and seed some data
ctx, example := keeper.CreateDefaultTestInput(t)
Expand Down Expand Up @@ -204,68 +59,6 @@ func TestQueryValidatorSelfDelegation(t *testing.T) {
}
}

func TestQueryValidatorUnboding(t *testing.T) {
// setup contracts and seed some data
ctx, example := keeper.CreateDefaultTestInput(t)
deliverTXFn := unAuthorizedDeliverTXFn(t, ctx, example.PoEKeeper, example.TWasmKeeper.GetContractKeeper(), example.EncodingConfig.TxConfig.TxDecoder())
module := poe.NewAppModule(example.PoEKeeper, example.TWasmKeeper, deliverTXFn, example.EncodingConfig.TxConfig, example.TWasmKeeper.GetContractKeeper())

mutator, vals := withRandomValidators(t, ctx, example, 2)
gs := types.GenesisStateFixture(mutator)
genesisBz := example.EncodingConfig.Marshaler.MustMarshalJSON(&gs)
module.InitGenesis(ctx, example.EncodingConfig.Marshaler, genesisBz)

contractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeStaking)
require.NoError(t, err)
op1Addr, err := sdk.AccAddressFromBech32(vals[0].OperatorAddress)
require.NoError(t, err)

// unbond some tokens for operator 1
now := time.Now().UTC()
ctx = ctx.WithBlockTime(now).WithBlockHeight(12)
unbondedAmount := sdk.NewInt(10)
contract.UnbondDelegation(ctx, contractAddr, op1Addr, unbondedAmount, example.TWasmKeeper.GetContractKeeper())

op2Addr, err := sdk.AccAddressFromBech32(vals[1].OperatorAddress)
require.NoError(t, err)
unbodingPeriod, err := contract.QueryStakingUnbondingPeriod(ctx, example.TWasmKeeper, contractAddr)
require.NoError(t, err)
specs := map[string]struct {
srcOpAddr sdk.AccAddress
expResult contract.TG4StakeClaimsResponse
}{
"unbondings": {
srcOpAddr: op1Addr,
expResult: contract.TG4StakeClaimsResponse{Claims: []contract.TG4StakeClaim{
{
Addr: op1Addr.String(),
Amount: sdk.NewInt(10),
ReleaseAt: uint64(now.Add(time.Duration(unbodingPeriod) * time.Second).UTC().UnixNano()),
CreationHeight: 12,
},
}},
},
"no unbondings with existing operator": {
srcOpAddr: op2Addr,
expResult: contract.TG4StakeClaimsResponse{Claims: []contract.TG4StakeClaim{}},
},
"unknown operator": {
srcOpAddr: rand.Bytes(sdk.AddrLen),
expResult: contract.TG4StakeClaimsResponse{Claims: []contract.TG4StakeClaim{}},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
// when
gotRes, gotErr := contract.QueryStakingUnbonding(ctx, example.TWasmKeeper, contractAddr, spec.srcOpAddr)
// then
require.NoError(t, gotErr)
require.NotNil(t, gotRes)
assert.Equal(t, spec.expResult, gotRes)
})
}
}

// unAuthorizedDeliverTXFn applies the TX without ante handler checks for testing purpose
func unAuthorizedDeliverTXFn(t *testing.T, ctx sdk.Context, k keeper.Keeper, contractKeeper wasmtypes.ContractOpsKeeper, txDecoder sdk.TxDecoder) func(tx abci.RequestDeliverTx) abci.ResponseDeliverTx {
t.Helper()
Expand Down Expand Up @@ -297,7 +90,7 @@ func withRandomValidators(t *testing.T, ctx sdk.Context, example keeper.TestKeep
desc stakingtypes.Description
)
f.NilChance(0).Fuzz(&power) // must be > 0 so that staked amount is > 0
f.Fuzz(&engagement)
f.NilChance(0).Fuzz(&engagement)
for len(desc.Moniker) < 3 { // ensure min length is met
f.Fuzz(&desc)
}
Expand Down
89 changes: 72 additions & 17 deletions x/poe/contract/tg4_stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package contract
import (
"encoding/json"
"testing"
"time"

stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

wasmvmtypes "github.com/CosmWasm/wasmvm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -86,23 +91,6 @@ type UnbondingPeriodResponse struct {
UnbondingPeriod uint64 `json:"unbonding_period"`
}

// QueryStakingUnbondingPeriod query the unbonding period from PoE staking contract
func QueryStakingUnbondingPeriod(ctx sdk.Context, k types.SmartQuerier, stakeAddr sdk.AccAddress) (uint64, error) {
query := TG4StakeQuery{UnbondingPeriod: &struct{}{}}
var response UnbondingPeriodResponse
err := doQuery(ctx, k, stakeAddr, query, &response)
return response.UnbondingPeriod, err
}

// QueryStakingUnbonding query PoE staking contract for unbonded self delegations
// TODO: add pagination support here!
func QueryStakingUnbonding(ctx sdk.Context, k types.SmartQuerier, stakeAddr sdk.AccAddress, opAddr sdk.AccAddress) (TG4StakeClaimsResponse, error) {
query := TG4StakeQuery{Claims: &ListClaimsQuery{Address: opAddr.String()}}
var response TG4StakeClaimsResponse
err := doQuery(ctx, k, stakeAddr, query, &response)
return response, err
}

// QueryStakedAmount query PoE staking contract for bonded self delegation amount
func QueryStakedAmount(ctx sdk.Context, k types.SmartQuerier, stakeAddr sdk.AccAddress, opAddr sdk.AccAddress) (TG4StakedAmountsResponse, error) {
query := TG4StakeQuery{Staked: &StakedQuery{Address: opAddr.String()}}
Expand All @@ -122,3 +110,70 @@ func doQuery(ctx sdk.Context, k types.SmartQuerier, contractAddr sdk.AccAddress,
}
return json.Unmarshal(res, result)
}

type StakeContractAdapter struct {
contractAddr sdk.AccAddress
contractQuerier types.SmartQuerier
addressLookupErr error
}

// NewStakeContractAdapter constructor
func NewStakeContractAdapter(contractAddr sdk.AccAddress, contractQuerier types.SmartQuerier, addressLookupErr error) *StakeContractAdapter {
return &StakeContractAdapter{contractAddr: contractAddr, contractQuerier: contractQuerier, addressLookupErr: addressLookupErr}
}

// QueryStakingUnbondingPeriod query the unbonding period from PoE staking contract
func (v StakeContractAdapter) QueryStakingUnbondingPeriod(ctx sdk.Context) (time.Duration, error) {
if v.addressLookupErr != nil {
return 0, v.addressLookupErr
}

query := TG4StakeQuery{UnbondingPeriod: &struct{}{}}
var resp UnbondingPeriodResponse
err := doQuery(ctx, v.contractQuerier, v.contractAddr, query, &resp)
if err != nil {
return 0, sdkerrors.Wrap(err, "contract query")
}
return time.Duration(resp.UnbondingPeriod) * time.Second, nil
}

func (v StakeContractAdapter) QueryStakedAmount(ctx sdk.Context, opAddr sdk.AccAddress) (*sdk.Int, error) {
query := TG4Query{Member: &MemberQuery{Addr: opAddr.String()}}
var resp TG4MemberResponse
err := doQuery(ctx, v.contractQuerier, v.contractAddr, query, &resp)
if err != nil {
return nil, sdkerrors.Wrap(err, "contract query")
}
if resp.Weight == nil {
return nil, nil
}
amount := sdk.NewInt(int64(*resp.Weight))
// we should return Coin instead: https://github.com/confio/tgrade-contracts/issues/265
return &amount, nil
}

// QueryStakingUnbonding query PoE staking contract for unbonded self delegations
// TODO: add pagination support here!
func (v StakeContractAdapter) QueryStakingUnbonding(ctx sdk.Context, opAddr sdk.AccAddress) ([]stakingtypes.UnbondingDelegationEntry, error) {
if v.addressLookupErr != nil {
return nil, v.addressLookupErr
}
query := TG4StakeQuery{Claims: &ListClaimsQuery{Address: opAddr.String()}}
var resp TG4StakeClaimsResponse
err := doQuery(ctx, v.contractQuerier, v.contractAddr, query, &resp)
if err != nil {
return nil, sdkerrors.Wrap(err, "contract query")
}

// add all unbonded amounts
unbodings := make([]stakingtypes.UnbondingDelegationEntry, len(resp.Claims))
for i, v := range resp.Claims {
unbodings[i] = stakingtypes.UnbondingDelegationEntry{
InitialBalance: v.Amount,
CompletionTime: time.Unix(0, int64(v.ReleaseAt)).UTC(),
Balance: v.Amount,
CreationHeight: int64(v.CreationHeight),
}
}
return unbodings, nil
}
Loading

0 comments on commit 50b098e

Please sign in to comment.