Skip to content

Commit

Permalink
Add test coverage for liquid vesting module (#295)
Browse files Browse the repository at this point in the history
* add test coverage liquid vesting module

* add test for liquidation of partially unlocked tokens

---------

Co-authored-by: Petr Ivanov <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 15, 2024
1 parent e1447a3 commit 8372d3d
Showing 1 changed file with 164 additions and 0 deletions.
164 changes: 164 additions & 0 deletions x/liquidvesting/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,37 @@ func (suite *KeeperTestSuite) TestLiquidate() {
amount: sdk.NewCoin("aISLM", amount.AmountOf("aISLM")),
expectPass: true,
},
{
name: "fail - liquidate amount bigger than locked but less than total",
malleate: func() {
funder := sdk.AccAddress(types.ModuleName)
baseAccount := authtypes.NewBaseAccountWithAddress(addr1)
startTime := suite.ctx.BlockTime().Add(-10 * time.Second)
clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, startTime, lockupPeriods, vestingPeriods, nil)
testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck
testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck
s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount)
},
from: addr1,
to: addr2,
amount: sdk.NewCoin("aISLM", amount.AmountOf("aISLM").Add(math.NewInt(1_500_000))),
expectPass: false,
},
{
name: "fail - liquidate tokens partially unlocked",
malleate: func() {
funder := sdk.AccAddress(types.ModuleName)
baseAccount := authtypes.NewBaseAccountWithAddress(addr1)
startTime := suite.ctx.BlockTime().Add(-201 * time.Second)
clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, startTime, lockupPeriods, vestingPeriods, nil)
testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck
s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount)
},
from: addr1,
to: addr2,
amount: sdk.NewCoin("aISLM", math.NewInt(1_500_000)),
expectPass: false,
},
{
name: "fail - amount exceeded",
malleate: func() {
Expand Down Expand Up @@ -171,6 +202,90 @@ func (suite *KeeperTestSuite) TestLiquidate() {
}
}

func (suite *KeeperTestSuite) TestMultipleLiquidationsFromOneAccount() {
var (
from = addr1
to = addr2
liquidationAmount = sdk.NewCoin("aISLM", third.AmountOf("aISLM"))
funder = sdk.AccAddress(types.ModuleName)
)
suite.SetupTest() // Reset
ctx := sdk.WrapSDKContext(suite.ctx)

baseAccount := authtypes.NewBaseAccountWithAddress(addr1)
startTime := suite.ctx.BlockTime().Add(-10 * time.Second)
clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, startTime, lockupPeriods, vestingPeriods, nil)
testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck
s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount)

// FIRST LIQUIDATION
msg := types.NewMsgLiquidate(from, to, liquidationAmount)
resp, err := suite.app.LiquidVestingKeeper.Liquidate(ctx, msg)
expRes := &types.MsgLiquidateResponse{}

// check returns
suite.Require().NoError(err)
suite.Require().Equal(expRes, resp)

// check target account exists and has liquid token
accIto := suite.app.AccountKeeper.GetAccount(suite.ctx, to)
suite.Require().NotNil(accIto)
balanceTarget := suite.app.BankKeeper.GetBalance(suite.ctx, to, types.DenomBaseNameFromID(0))
suite.Require().Equal(sdk.NewCoin(types.DenomBaseNameFromID(0), math.ZeroInt()).String(), balanceTarget.String())

// check liquidated vesting locked coins are decreased on initial account
accIFrom := suite.app.AccountKeeper.GetAccount(suite.ctx, from)
suite.Require().NotNil(accIFrom)
cva, isClawback := accIFrom.(*vestingtypes.ClawbackVestingAccount)
suite.Require().True(isClawback)
suite.Require().Equal(cva.GetLockedOnly(suite.ctx.BlockTime()), lockupPeriods.TotalAmount().Sub(liquidationAmount))

// check erc20 token contract
pair0Resp, err := s.app.Erc20Keeper.TokenPair(s.ctx, &erc20types.QueryTokenPairRequest{Token: types.DenomBaseNameFromID(0)})
s.Require().NoError(err)
s.Require().True(pair0Resp.TokenPair.Enabled)
ethAccTo, isEthAccount := accIto.(*haqqtypes.EthAccount)
s.Require().True(isEthAccount)
balanceOfLiquidTokeErc20Pair0 := s.app.Erc20Keeper.BalanceOf(
s.ctx,
contracts.ERC20MinterBurnerDecimalsContract.ABI,
pair0Resp.TokenPair.GetERC20Contract(),
common.BytesToAddress(ethAccTo.GetAddress().Bytes()),
)
s.Require().Equal(liquidationAmount.Amount.String(), balanceOfLiquidTokeErc20Pair0.String())

// SECOND LIQUIDATION
msg = types.NewMsgLiquidate(from, to, liquidationAmount)
resp, err = suite.app.LiquidVestingKeeper.Liquidate(ctx, msg)

// check returns
suite.Require().NoError(err)
suite.Require().Equal(expRes, resp)

// check target account exists and has liquid token
balanceTarget = suite.app.BankKeeper.GetBalance(suite.ctx, to, types.DenomBaseNameFromID(1))
suite.Require().Equal(sdk.NewCoin(types.DenomBaseNameFromID(1), math.ZeroInt()).String(), balanceTarget.String())

// check liquidated vesting locked coins are decreased on initial account
accIFrom = suite.app.AccountKeeper.GetAccount(suite.ctx, from)
suite.Require().NotNil(accIFrom)
cva, isClawback = accIFrom.(*vestingtypes.ClawbackVestingAccount)
suite.Require().True(isClawback)
suite.Require().Equal(cva.GetLockedOnly(suite.ctx.BlockTime()), sdk.NewCoins(liquidationAmount))

// check erc20 token contract
pair1Resp, err := s.app.Erc20Keeper.TokenPair(s.ctx, &erc20types.QueryTokenPairRequest{Token: types.DenomBaseNameFromID(1)})
s.Require().NoError(err)
s.Require().True(pair1Resp.TokenPair.Enabled)
balanceOfLiquidTokeErc20Pair1 := s.app.Erc20Keeper.BalanceOf(
s.ctx,
contracts.ERC20MinterBurnerDecimalsContract.ABI,
pair1Resp.TokenPair.GetERC20Contract(),
common.BytesToAddress(ethAccTo.GetAddress().Bytes()),
)
s.Require().Equal(liquidationAmount.Amount.String(), balanceOfLiquidTokeErc20Pair1.String())
}

func (suite *KeeperTestSuite) TestRedeem() {
testCases := []struct {
name string
Expand Down Expand Up @@ -263,6 +378,55 @@ func (suite *KeeperTestSuite) TestRedeem() {
expectedLockedAmount: 400_000,
expectPass: true,
},
{
name: "ok - redeem token partially from evm and cosmos layers",
malleate: func() {
// fund liquid vesting module
testutil.FundModuleAccount(s.ctx, s.app.BankKeeper, types.ModuleName, amount) //nolint:errcheck
// create liquid vesting denom
s.app.LiquidVestingKeeper.SetDenom(s.ctx, types.Denom{
BaseDenom: "aLIQUID0",
DisplayDenom: "LIQUID0",
OriginalDenom: "aISLM",
LockupPeriods: lockupPeriods,
})
// create accounts
acc1 := &haqqtypes.EthAccount{
BaseAccount: authtypes.NewBaseAccountWithAddress(addr1),
CodeHash: common.BytesToHash(crypto.Keccak256(nil)).String(),
}
s.app.AccountKeeper.SetAccount(s.ctx, acc1)
acc2 := &haqqtypes.EthAccount{
BaseAccount: authtypes.NewBaseAccountWithAddress(addr2),
CodeHash: common.BytesToHash(crypto.Keccak256(nil)).String(),
}
s.app.AccountKeeper.SetAccount(s.ctx, acc2)
// fund account with liquid denom token
testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, liquidDenomAmount) //nolint:errcheck

liquidTokenMetadata := banktypes.Metadata{
Description: "Liquid vesting token",
DenomUnits: []*banktypes.DenomUnit{{Denom: "aLIQUID0", Exponent: 0}, {Denom: "LIQUID0", Exponent: 18}},
Base: "aLIQUID0",
Display: "LIQUID0",
Name: "LIQUID0",
Symbol: "LIQUID0",
}

suite.app.BankKeeper.SetDenomMetaData(suite.ctx, liquidTokenMetadata)
suite.app.Erc20Keeper.RegisterCoin(suite.ctx, liquidTokenMetadata) //nolint:errcheck

// transfer half of liquid token to evm layer
evmLiquidateToAddress := common.BytesToAddress(addr1.Bytes())
msgConvert := erc20types.NewMsgConvertCoin(sdk.NewInt64Coin("aLIQUID0", 1_500_000), evmLiquidateToAddress, addr1)
_, err := suite.app.Erc20Keeper.ConvertCoin(sdk.WrapSDKContext(suite.ctx), msgConvert)
suite.Require().NoError(err)
},
redeemFrom: addr1,
redeemTo: addr2,
redeemAmount: 3_000_000,
expectPass: true,
},
{
name: "fail - insufficient liquid token balance",
malleate: func() {
Expand Down

0 comments on commit 8372d3d

Please sign in to comment.