@@ -76,6 +76,37 @@ func (suite *KeeperTestSuite) TestLiquidate() {
76
76
amount : sdk .NewCoin ("aISLM" , amount .AmountOf ("aISLM" )),
77
77
expectPass : true ,
78
78
},
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
+ },
79
110
{
80
111
name : "fail - amount exceeded" ,
81
112
malleate : func () {
@@ -171,6 +202,90 @@ func (suite *KeeperTestSuite) TestLiquidate() {
171
202
}
172
203
}
173
204
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
+
174
289
func (suite * KeeperTestSuite ) TestRedeem () {
175
290
testCases := []struct {
176
291
name string
@@ -263,6 +378,55 @@ func (suite *KeeperTestSuite) TestRedeem() {
263
378
expectedLockedAmount : 400_000 ,
264
379
expectPass : true ,
265
380
},
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
+ },
266
430
{
267
431
name : "fail - insufficient liquid token balance" ,
268
432
malleate : func () {
0 commit comments