Skip to content

Commit 8372d3d

Browse files
ivanovpetrPetr Ivanovkodiakhq[bot]
authored
Add test coverage for liquid vesting module (#295)
* 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>
1 parent e1447a3 commit 8372d3d

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

Diff for: x/liquidvesting/keeper/msg_server_test.go

+164
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,37 @@ func (suite *KeeperTestSuite) TestLiquidate() {
7676
amount: sdk.NewCoin("aISLM", amount.AmountOf("aISLM")),
7777
expectPass: true,
7878
},
79+
{
80+
name: "fail - liquidate amount bigger than locked but less than total",
81+
malleate: func() {
82+
funder := sdk.AccAddress(types.ModuleName)
83+
baseAccount := authtypes.NewBaseAccountWithAddress(addr1)
84+
startTime := suite.ctx.BlockTime().Add(-10 * time.Second)
85+
clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, startTime, lockupPeriods, vestingPeriods, nil)
86+
testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck
87+
testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck
88+
s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount)
89+
},
90+
from: addr1,
91+
to: addr2,
92+
amount: sdk.NewCoin("aISLM", amount.AmountOf("aISLM").Add(math.NewInt(1_500_000))),
93+
expectPass: false,
94+
},
95+
{
96+
name: "fail - liquidate tokens partially unlocked",
97+
malleate: func() {
98+
funder := sdk.AccAddress(types.ModuleName)
99+
baseAccount := authtypes.NewBaseAccountWithAddress(addr1)
100+
startTime := suite.ctx.BlockTime().Add(-201 * time.Second)
101+
clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, startTime, lockupPeriods, vestingPeriods, nil)
102+
testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck
103+
s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount)
104+
},
105+
from: addr1,
106+
to: addr2,
107+
amount: sdk.NewCoin("aISLM", math.NewInt(1_500_000)),
108+
expectPass: false,
109+
},
79110
{
80111
name: "fail - amount exceeded",
81112
malleate: func() {
@@ -171,6 +202,90 @@ func (suite *KeeperTestSuite) TestLiquidate() {
171202
}
172203
}
173204

205+
func (suite *KeeperTestSuite) TestMultipleLiquidationsFromOneAccount() {
206+
var (
207+
from = addr1
208+
to = addr2
209+
liquidationAmount = sdk.NewCoin("aISLM", third.AmountOf("aISLM"))
210+
funder = sdk.AccAddress(types.ModuleName)
211+
)
212+
suite.SetupTest() // Reset
213+
ctx := sdk.WrapSDKContext(suite.ctx)
214+
215+
baseAccount := authtypes.NewBaseAccountWithAddress(addr1)
216+
startTime := suite.ctx.BlockTime().Add(-10 * time.Second)
217+
clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, startTime, lockupPeriods, vestingPeriods, nil)
218+
testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck
219+
s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount)
220+
221+
// FIRST LIQUIDATION
222+
msg := types.NewMsgLiquidate(from, to, liquidationAmount)
223+
resp, err := suite.app.LiquidVestingKeeper.Liquidate(ctx, msg)
224+
expRes := &types.MsgLiquidateResponse{}
225+
226+
// check returns
227+
suite.Require().NoError(err)
228+
suite.Require().Equal(expRes, resp)
229+
230+
// check target account exists and has liquid token
231+
accIto := suite.app.AccountKeeper.GetAccount(suite.ctx, to)
232+
suite.Require().NotNil(accIto)
233+
balanceTarget := suite.app.BankKeeper.GetBalance(suite.ctx, to, types.DenomBaseNameFromID(0))
234+
suite.Require().Equal(sdk.NewCoin(types.DenomBaseNameFromID(0), math.ZeroInt()).String(), balanceTarget.String())
235+
236+
// check liquidated vesting locked coins are decreased on initial account
237+
accIFrom := suite.app.AccountKeeper.GetAccount(suite.ctx, from)
238+
suite.Require().NotNil(accIFrom)
239+
cva, isClawback := accIFrom.(*vestingtypes.ClawbackVestingAccount)
240+
suite.Require().True(isClawback)
241+
suite.Require().Equal(cva.GetLockedOnly(suite.ctx.BlockTime()), lockupPeriods.TotalAmount().Sub(liquidationAmount))
242+
243+
// check erc20 token contract
244+
pair0Resp, err := s.app.Erc20Keeper.TokenPair(s.ctx, &erc20types.QueryTokenPairRequest{Token: types.DenomBaseNameFromID(0)})
245+
s.Require().NoError(err)
246+
s.Require().True(pair0Resp.TokenPair.Enabled)
247+
ethAccTo, isEthAccount := accIto.(*haqqtypes.EthAccount)
248+
s.Require().True(isEthAccount)
249+
balanceOfLiquidTokeErc20Pair0 := s.app.Erc20Keeper.BalanceOf(
250+
s.ctx,
251+
contracts.ERC20MinterBurnerDecimalsContract.ABI,
252+
pair0Resp.TokenPair.GetERC20Contract(),
253+
common.BytesToAddress(ethAccTo.GetAddress().Bytes()),
254+
)
255+
s.Require().Equal(liquidationAmount.Amount.String(), balanceOfLiquidTokeErc20Pair0.String())
256+
257+
// SECOND LIQUIDATION
258+
msg = types.NewMsgLiquidate(from, to, liquidationAmount)
259+
resp, err = suite.app.LiquidVestingKeeper.Liquidate(ctx, msg)
260+
261+
// check returns
262+
suite.Require().NoError(err)
263+
suite.Require().Equal(expRes, resp)
264+
265+
// check target account exists and has liquid token
266+
balanceTarget = suite.app.BankKeeper.GetBalance(suite.ctx, to, types.DenomBaseNameFromID(1))
267+
suite.Require().Equal(sdk.NewCoin(types.DenomBaseNameFromID(1), math.ZeroInt()).String(), balanceTarget.String())
268+
269+
// check liquidated vesting locked coins are decreased on initial account
270+
accIFrom = suite.app.AccountKeeper.GetAccount(suite.ctx, from)
271+
suite.Require().NotNil(accIFrom)
272+
cva, isClawback = accIFrom.(*vestingtypes.ClawbackVestingAccount)
273+
suite.Require().True(isClawback)
274+
suite.Require().Equal(cva.GetLockedOnly(suite.ctx.BlockTime()), sdk.NewCoins(liquidationAmount))
275+
276+
// check erc20 token contract
277+
pair1Resp, err := s.app.Erc20Keeper.TokenPair(s.ctx, &erc20types.QueryTokenPairRequest{Token: types.DenomBaseNameFromID(1)})
278+
s.Require().NoError(err)
279+
s.Require().True(pair1Resp.TokenPair.Enabled)
280+
balanceOfLiquidTokeErc20Pair1 := s.app.Erc20Keeper.BalanceOf(
281+
s.ctx,
282+
contracts.ERC20MinterBurnerDecimalsContract.ABI,
283+
pair1Resp.TokenPair.GetERC20Contract(),
284+
common.BytesToAddress(ethAccTo.GetAddress().Bytes()),
285+
)
286+
s.Require().Equal(liquidationAmount.Amount.String(), balanceOfLiquidTokeErc20Pair1.String())
287+
}
288+
174289
func (suite *KeeperTestSuite) TestRedeem() {
175290
testCases := []struct {
176291
name string
@@ -263,6 +378,55 @@ func (suite *KeeperTestSuite) TestRedeem() {
263378
expectedLockedAmount: 400_000,
264379
expectPass: true,
265380
},
381+
{
382+
name: "ok - redeem token partially from evm and cosmos layers",
383+
malleate: func() {
384+
// fund liquid vesting module
385+
testutil.FundModuleAccount(s.ctx, s.app.BankKeeper, types.ModuleName, amount) //nolint:errcheck
386+
// create liquid vesting denom
387+
s.app.LiquidVestingKeeper.SetDenom(s.ctx, types.Denom{
388+
BaseDenom: "aLIQUID0",
389+
DisplayDenom: "LIQUID0",
390+
OriginalDenom: "aISLM",
391+
LockupPeriods: lockupPeriods,
392+
})
393+
// create accounts
394+
acc1 := &haqqtypes.EthAccount{
395+
BaseAccount: authtypes.NewBaseAccountWithAddress(addr1),
396+
CodeHash: common.BytesToHash(crypto.Keccak256(nil)).String(),
397+
}
398+
s.app.AccountKeeper.SetAccount(s.ctx, acc1)
399+
acc2 := &haqqtypes.EthAccount{
400+
BaseAccount: authtypes.NewBaseAccountWithAddress(addr2),
401+
CodeHash: common.BytesToHash(crypto.Keccak256(nil)).String(),
402+
}
403+
s.app.AccountKeeper.SetAccount(s.ctx, acc2)
404+
// fund account with liquid denom token
405+
testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, liquidDenomAmount) //nolint:errcheck
406+
407+
liquidTokenMetadata := banktypes.Metadata{
408+
Description: "Liquid vesting token",
409+
DenomUnits: []*banktypes.DenomUnit{{Denom: "aLIQUID0", Exponent: 0}, {Denom: "LIQUID0", Exponent: 18}},
410+
Base: "aLIQUID0",
411+
Display: "LIQUID0",
412+
Name: "LIQUID0",
413+
Symbol: "LIQUID0",
414+
}
415+
416+
suite.app.BankKeeper.SetDenomMetaData(suite.ctx, liquidTokenMetadata)
417+
suite.app.Erc20Keeper.RegisterCoin(suite.ctx, liquidTokenMetadata) //nolint:errcheck
418+
419+
// transfer half of liquid token to evm layer
420+
evmLiquidateToAddress := common.BytesToAddress(addr1.Bytes())
421+
msgConvert := erc20types.NewMsgConvertCoin(sdk.NewInt64Coin("aLIQUID0", 1_500_000), evmLiquidateToAddress, addr1)
422+
_, err := suite.app.Erc20Keeper.ConvertCoin(sdk.WrapSDKContext(suite.ctx), msgConvert)
423+
suite.Require().NoError(err)
424+
},
425+
redeemFrom: addr1,
426+
redeemTo: addr2,
427+
redeemAmount: 3_000_000,
428+
expectPass: true,
429+
},
266430
{
267431
name: "fail - insufficient liquid token balance",
268432
malleate: func() {

0 commit comments

Comments
 (0)