Skip to content

Commit

Permalink
Vesting integration tests (#303)
Browse files Browse the repository at this point in the history
* Start integration tests

* Upgrade tg4 stake contract to dev version

(cherry picked from commit 0315d601bc6ada6407f8d868f587e91673cbd6bc)

* Enable vesting integration tests

* Testing

* Review feedback
  • Loading branch information
alpe authored Mar 22, 2022
1 parent 2c62cae commit 7446e8d
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 57 deletions.
158 changes: 135 additions & 23 deletions x/poe/contract/contractio_integration_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package contract_test

import (
"github.com/confio/tgrade/x/poe/keeper"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
"testing"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
Expand Down Expand Up @@ -35,44 +39,152 @@ func TestSetEngagementPoints(t *testing.T) {
func TestBondDelegation(t *testing.T) {
// setup contracts and seed some data
ctx, example, vals, _ := setupPoEContracts(t)

myOperatorAddr, _ := sdk.AccAddressFromBech32(vals[0].OperatorAddress)
// fund account
example.Faucet.Fund(ctx, myOperatorAddr, sdk.NewCoin(types.DefaultBondDenom, sdk.OneInt()))
stakingContractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeStaking)
require.NoError(t, err)
example.Faucet.Fund(ctx, myOperatorAddr, sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(2)))

// when
err = contract.BondDelegation(ctx, stakingContractAddr, myOperatorAddr, sdk.NewCoins(sdk.NewCoin("utgd", sdk.OneInt())), nil, example.TWasmKeeper.GetContractKeeper())
convertToFullVestingAccount(t, example, ctx, myOperatorAddr)
// and one liquid token
example.Faucet.Fund(ctx, myOperatorAddr, sdk.NewCoin(types.DefaultBondDenom, sdk.OneInt()))

// then
stakingContractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeStaking)
require.NoError(t, err)
specs := map[string]struct {
liquid sdk.Coins
vesting *sdk.Coin
expErr bool
}{
"liquid only": {
liquid: sdk.NewCoins(sdk.NewCoin("utgd", sdk.OneInt())),
},
"vesting only": {
vesting: &sdk.Coin{Denom: "utgd", Amount: sdk.NewInt(2)},
},
"both": {
liquid: sdk.NewCoins(sdk.NewCoin("utgd", sdk.NewInt(1))),
vesting: &sdk.Coin{Denom: "utgd", Amount: sdk.NewInt(2)},
},
"insufficient liquid tokens": {
liquid: sdk.NewCoins(sdk.NewCoin("utgd", sdk.NewInt(3))),
expErr: true,
},
"insufficient vesting tokens": {
vesting: &sdk.Coin{Denom: "utgd", Amount: sdk.NewInt(100000)},
expErr: true,
},
"both zero amounts": {
liquid: sdk.NewCoins(sdk.NewCoin("utgd", sdk.ZeroInt())),
vesting: &sdk.Coin{Denom: "utgd", Amount: sdk.ZeroInt()},
expErr: true,
},
}
parentCtx := ctx
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ = parentCtx.CacheContext()
// when
gotErr := contract.BondDelegation(ctx, stakingContractAddr, myOperatorAddr, spec.liquid, spec.vesting, example.TWasmKeeper.GetContractKeeper())

// then
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)

gotRes, err := contract.QueryStakedAmount(ctx, example.TWasmKeeper, stakingContractAddr, myOperatorAddr)
require.NoError(t, err)
expAmount := vals[0].Tokens.Add(spec.liquid.AmountOf("utgd")).String()
assert.Equal(t, expAmount, gotRes.Liquid.Amount)
assert.Equal(t, "utgd", gotRes.Liquid.Denom)

expAmount = "0"
if spec.vesting != nil {
expAmount = spec.vesting.Amount.String()
}
assert.Equal(t, expAmount, gotRes.Vesting.Amount)
assert.Equal(t, "utgd", gotRes.Vesting.Denom)
})
}

gotRes, err := contract.QueryStakedAmount(ctx, example.TWasmKeeper, stakingContractAddr, myOperatorAddr)
require.NoError(t, err)
expAmount := vals[0].Tokens.Add(sdk.OneInt())
assert.Equal(t, gotRes.Liquid.Amount, expAmount.String())
}

func TestUnbondDelegation(t *testing.T) {
// setup contracts and seed some data
ctx, example, vals, _ := setupPoEContracts(t)
stakingContractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeStaking)
require.NoError(t, err)

myOperatorAddr, _ := sdk.AccAddressFromBech32(vals[0].OperatorAddress)
stakingContractAddr, err := example.PoEKeeper.GetPoEContractAddress(ctx, types.PoEContractTypeStaking)
genTxStakedAmount := vals[0].Tokens

vestingAmount := sdk.NewCoin(types.DefaultBondDenom, sdk.OneInt())
example.Faucet.Fund(ctx, myOperatorAddr, vestingAmount)
convertToFullVestingAccount(t, example, ctx, myOperatorAddr)

// and bond from vesting amount
err = contract.BondDelegation(ctx, stakingContractAddr, myOperatorAddr, sdk.NewCoins(), &sdk.Coin{Denom: types.DefaultBondDenom, Amount: sdk.OneInt()}, example.TWasmKeeper.GetContractKeeper())
require.NoError(t, err)
unbodingPeriod, err := example.PoEKeeper.StakeContract(ctx).QueryStakingUnbondingPeriod(ctx)
require.NoError(t, err)

// when
completionTime, err := contract.UnbondDelegation(ctx, stakingContractAddr, myOperatorAddr, sdk.NewCoin(types.DefaultBondDenom, sdk.OneInt()), example.TWasmKeeper.GetContractKeeper())

// then
require.NoError(t, err)
assert.Equal(t, ctx.BlockTime().Add(unbodingPeriod).UTC(), *completionTime)
parentCtx := ctx.WithBlockHeight(ctx.BlockHeight() + 1)
specs := map[string]struct {
unbondAmount sdk.Coin
expLiquidBond sdk.Int
expVestingBond sdk.Int
expErr bool
}{
"some liquid": {
unbondAmount: sdk.NewCoin(types.DefaultBondDenom, sdk.OneInt()),
expLiquidBond: genTxStakedAmount.Sub(sdk.OneInt()),
expVestingBond: sdk.OneInt(),
},
"all liquid": {
unbondAmount: sdk.NewCoin(types.DefaultBondDenom, genTxStakedAmount),
expLiquidBond: sdk.ZeroInt(),
expVestingBond: sdk.OneInt(),
},
"all liquid + vesting": {
unbondAmount: sdk.NewCoin(types.DefaultBondDenom, genTxStakedAmount.AddRaw(1)),
expLiquidBond: sdk.ZeroInt(),
expVestingBond: sdk.ZeroInt(),
},
"more than staked": {
unbondAmount: sdk.NewCoin(types.DefaultBondDenom, genTxStakedAmount.AddRaw(10)),
expErr: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ = parentCtx.CacheContext()
// when
completionTime, gotErr := contract.UnbondDelegation(ctx, stakingContractAddr, myOperatorAddr, spec.unbondAmount, example.TWasmKeeper.GetContractKeeper())

// then
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, ctx.BlockTime().Add(unbodingPeriod).UTC(), *completionTime)

gotRes, gotErr := contract.QueryStakedAmount(ctx, example.TWasmKeeper, stakingContractAddr, myOperatorAddr)
require.NoError(t, gotErr)
assert.Equal(t, spec.expLiquidBond.String(), gotRes.Liquid.Amount)
assert.Equal(t, spec.expVestingBond.String(), gotRes.Vesting.Amount)
})
}
}

gotRes, err := contract.QueryStakedAmount(ctx, example.TWasmKeeper, stakingContractAddr, myOperatorAddr)
require.NoError(t, err)
expAmount := vals[0].Tokens.Sub(sdk.OneInt())
assert.Equal(t, gotRes.Liquid.Amount, expAmount.String())
// make vesting account with current balance as vested amount
func convertToFullVestingAccount(t *testing.T, example keeper.TestKeepers, ctx sdk.Context, addr sdk.AccAddress) {
vestingtypes.RegisterInterfaces(example.EncodingConfig.InterfaceRegistry)
acc := example.AccountKeeper.GetAccount(ctx, addr)
require.NotNil(t, acc)
bAcc, ok := acc.(*authtypes.BaseAccount)
require.True(t, ok)
balance := example.BankKeeper.GetBalance(ctx, addr, types.DefaultBondDenom)
// setup vesting account with old balance into vesting
vAcct := vestingtypes.NewDelayedVestingAccount(bAcc, sdk.NewCoins(balance), time.Now().Add(time.Hour).UnixNano())
example.AccountKeeper.SetAccount(ctx, vAcct)
}
Binary file modified x/poe/contract/tg4_stake.wasm
Binary file not shown.
1 change: 1 addition & 0 deletions x/poe/contract/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
v0.6.2
tg4_stake.wasm custom build: b7b2ec7
9 changes: 7 additions & 2 deletions x/poe/types/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,15 @@ func (msg MsgDelegate) ValidateBasic() error {
return sdkerrors.Wrap(ErrEmpty, "operator address")
}

if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() {
if !msg.Amount.IsValid() {
return sdkerrors.Wrap(ErrInvalid, "delegation amount")
}

if !msg.VestingAmount.IsValid() {
return sdkerrors.Wrap(ErrInvalid, "vesting amount")
}
if msg.Amount.IsZero() && msg.VestingAmount.IsZero() {
return sdkerrors.Wrap(ErrInvalid, "empty amounts")
}
return nil
}

Expand Down
11 changes: 7 additions & 4 deletions x/poe/types/msg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,13 @@ func TestMsgDelegate(t *testing.T) {
vestingBond sdk.Coin
expectPass bool
}{
{"basic good", sdk.AccAddress(valAddr1), coinPos, sdk.Coin{}, true},
{"empty operator", sdk.AccAddress(emptyAddr), coinPos, sdk.Coin{}, false},
{"empty bond", sdk.AccAddress(valAddr1), coinZero, sdk.Coin{}, false},
{"nil bold", sdk.AccAddress(valAddr1), sdk.Coin{}, sdk.Coin{}, false},
{"liquid and vesting bond", sdk.AccAddress(valAddr1), coinPos, coinPos, true},
{"empty operator", sdk.AccAddress(emptyAddr), coinPos, coinZero, false},
{"vesting only", sdk.AccAddress(valAddr1), coinZero, coinPos, true},
{"liquid only", sdk.AccAddress(valAddr1), coinPos, coinZero, true},
{"empty bond and vesting", sdk.AccAddress(valAddr1), coinZero, coinZero, false},
{"nil bond", sdk.AccAddress(valAddr1), sdk.Coin{}, coinPos, false},
{"nil vesting", sdk.AccAddress(valAddr1), coinPos, sdk.Coin{}, false},
}
for _, tc := range tests {
msg := NewMsgDelegate(tc.operatorAddr, tc.bond, tc.vestingBond)
Expand Down
30 changes: 2 additions & 28 deletions x/twasm/keeper/handler_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func (h TgradeHandler) handleDelegate(ctx sdk.Context, contractAddr sdk.AccAddre
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "fromAddr")
}
amt, err := convertWasmCoinsToSdkCoins(wasmvmtypes.Coins{delegate.Funds})
amt, err := wasmkeeper.ConvertWasmCoinsToSdkCoins(wasmvmtypes.Coins{delegate.Funds})
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
Expand Down Expand Up @@ -284,7 +284,7 @@ func (h TgradeHandler) handleUndelegate(ctx sdk.Context, contractAddr sdk.AccAdd
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "recipient")
}
amt, err := convertWasmCoinsToSdkCoins(wasmvmtypes.Coins{undelegate.Funds})
amt, err := wasmkeeper.ConvertWasmCoinsToSdkCoins(wasmvmtypes.Coins{undelegate.Funds})
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
Expand Down Expand Up @@ -361,29 +361,3 @@ func (d restrictedParamsRouter) AddRoute(r string, h govtypes.Handler) (rtr govt
func (d restrictedParamsRouter) Seal() {
panic("not supported")
}

// FIXME: Use public wasmd helper function when available
func convertWasmCoinsToSdkCoins(coins []wasmvmtypes.Coin) (sdk.Coins, error) {
var toSend sdk.Coins
for _, coin := range coins {
c, err := convertWasmCoinToSdkCoin(coin)
if err != nil {
return nil, err
}
toSend = append(toSend, c)
}
return toSend, nil
}

// FIXME: Use public wasmd helper function when available
func convertWasmCoinToSdkCoin(coin wasmvmtypes.Coin) (sdk.Coin, error) {
amount, ok := sdk.NewIntFromString(coin.Amount)
if !ok {
return sdk.Coin{}, sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, coin.Amount+coin.Denom)
}
r := sdk.Coin{
Denom: coin.Denom,
Amount: amount,
}
return r, r.Validate()
}

0 comments on commit 7446e8d

Please sign in to comment.