-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathhydro_test.go
645 lines (547 loc) · 30.8 KB
/
hydro_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
package interchain
import (
"fmt"
"log"
"strconv"
"strings"
"testing"
"time"
"hydro/test/interchain/chainsuite"
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/strangelove-ventures/interchaintest/v8/testutil"
"github.com/stretchr/testify/suite"
)
const (
DefaultDeploymentDuration = 1
DefaultMinLiquidityRequest = 100000000
)
func TestHydroSuite(t *testing.T) {
s := &HydroSuite{}
suite.Run(t, s)
}
// TestHappyPath tests:
// deployment of hydro contract
// registering of interchain queries for validators
// locking of liquid staked tokens on hydro contract
// creating and voting/revoting for hydro proposals
// pausing/disabling contract
func (s *HydroSuite) TestHappyPath() {
log.Println("==== Running happy path test")
hubNode := s.HubChain.GetNode()
// delegate tokens
log.Println("==== Delegating tokens")
s.DelegateTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[0].ValoperAddress, txAmountUatom(1000))
s.DelegateTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[1].ValoperAddress, txAmountUatom(1000))
// liquid stake tokens
log.Println("==== Tokenizing shares")
recordId1 := s.LiquidStakeTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[0].ValoperAddress,
s.HubChain.ValidatorWallets[0].Address, txAmountUatom(500))
recordId2 := s.LiquidStakeTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[1].ValoperAddress,
s.HubChain.ValidatorWallets[0].Address, txAmountUatom(500))
// transfer share tokens to neutron chain
log.Println("==== Transferring tokenized shares to Neutron")
sourceIbcDenom1 := fmt.Sprintf("%s/%s", strings.ToLower(s.HubChain.ValidatorWallets[0].ValoperAddress), recordId1)
dstIbcDenom1 := s.HubToNeutronShareTokenTransfer(0, math.NewInt(400), sourceIbcDenom1, s.NeutronChain.ValidatorWallets[0].Address)
sourceIbcDenom2 := fmt.Sprintf("%s/%s", strings.ToLower(s.HubChain.ValidatorWallets[1].ValoperAddress), recordId2)
dstIbcDenom2 := s.HubToNeutronShareTokenTransfer(0, math.NewInt(400), sourceIbcDenom2, s.NeutronChain.ValidatorWallets[0].Address)
// deploy hydro contract - instantiate code
contractAddr := s.InstantiateHydroContract(s.NeutronChain.ValidatorWallets[0].Moniker, hydroCodeId, s.NeutronChain.ValidatorWallets[0].Address, 3, 86400000000000)
// register interchain query
log.Println("==== Registering interchain queries")
s.RegisterInterchainQueries([]string{s.HubChain.ValidatorWallets[0].ValoperAddress, s.HubChain.ValidatorWallets[1].ValoperAddress},
contractAddr, s.NeutronChain.ValidatorWallets[0].Moniker)
// lock tokens
log.Println("==== Locking tokens in Hydro")
err := s.LockTokens(0, 86400000000000, "10", dstIbcDenom1, contractAddr)
s.Require().NoError(err)
err = s.LockTokens(0, 3*86400000000000, "10", dstIbcDenom2, contractAddr)
s.Require().NoError(err)
err = s.LockTokens(0, 6*86400000000000, "10", dstIbcDenom1, contractAddr)
s.Require().NoError(err)
err = s.LockTokens(0, 12*86400000000000, "10", dstIbcDenom2, contractAddr)
s.Require().NoError(err)
// Scale lockup power
// 1x if lockup is between 0 and 1 epochs
// 1.5x if lockup is between 1 and 3 epochs
// 2x if lockup is between 3 and 6 epochs
// 4x if lockup is between 6 and 12 epochs
votingPower := "85" // 10*1+10*1.5+10*2+10*4
// create hydro proposals
log.Println("==== Creating proposals")
err = s.SubmitHydroProposal(0, contractAddr, "tranche 1 prop 1", 1, DefaultDeploymentDuration, DefaultMinLiquidityRequest)
s.Require().NoError(err)
err = s.SubmitHydroProposal(0, contractAddr, "tranche 1 prop 2", 1, DefaultDeploymentDuration, DefaultMinLiquidityRequest)
s.Require().NoError(err)
err = s.SubmitHydroProposal(0, contractAddr, "tranche 2 prop 1", 2, DefaultDeploymentDuration, DefaultMinLiquidityRequest)
s.Require().NoError(err)
log.Println("==== Voting for proposals")
// vote for tranche 1 proposal 1
proposal, err := s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal("0", proposal.Power)
proposalsVotes := []ProposalToLockups{
{
ProposalId: proposal.ProposalID,
LockIds: []int{0, 1, 2, 3},
},
}
err = s.VoteForHydroProposal(0, contractAddr, 1, proposalsVotes)
s.Require().NoError(err)
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal(votingPower, proposal.Power)
// vote for tranche 2 proposal
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 2 prop 1", 2)
s.Require().NoError(err)
s.Require().Equal("0", proposal.Power)
// vote for proposal in tranche 2 with the same lockups
proposalsVotes[0].ProposalId = proposal.ProposalID
err = s.VoteForHydroProposal(0, contractAddr, 2, proposalsVotes)
s.Require().NoError(err)
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 2 prop 1", 2)
s.Require().NoError(err)
s.Require().Equal(votingPower, proposal.Power)
// power of tranche 1 proposal 1 is not changed after voting for proposal from different tranche
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal(votingPower, proposal.Power)
// revote for tranche 1 proposal 2
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 1 prop 2", 1)
s.Require().NoError(err)
s.Require().Equal("0", proposal.Power)
// vote for proposal 2 in tranche 1 with the same lockups
proposalsVotes[0].ProposalId = proposal.ProposalID
err = s.VoteForHydroProposal(0, contractAddr, 1, proposalsVotes)
s.Require().NoError(err)
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 1 prop 2", 1)
s.Require().NoError(err)
s.Require().Equal(votingPower, proposal.Power)
// power of tranche 1 proposal 1 is now 0, since we revoted for the proposal 2 from the first tranche
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal("0", proposal.Power)
}
func (s *HydroSuite) TestPauseContract() {
log.Println("==== Running pause contract test")
// delegate tokens
log.Println("==== Delegating tokens")
s.DelegateTokens(s.HubChain.Validators[0], s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[0].ValoperAddress, txAmountUatom(1000))
// liquid stake tokens
log.Println("==== Tokenizing shares")
recordId := s.LiquidStakeTokens(s.HubChain.Validators[0], s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[0].ValoperAddress, s.HubChain.ValidatorWallets[0].Address, txAmountUatom(500))
// transfer share tokens to neutron chain
log.Println("==== Transferring tokenized shares to Neutron")
sourceIbcDenom := fmt.Sprintf("%s/%s", strings.ToLower(s.HubChain.ValidatorWallets[0].ValoperAddress), recordId)
dstIbcDenom := s.HubToNeutronShareTokenTransfer(0, math.NewInt(400), sourceIbcDenom, s.NeutronChain.ValidatorWallets[0].Address)
// instantiate hydro contract
contractAddr := s.InstantiateHydroContract(s.NeutronChain.ValidatorWallets[0].Moniker, hydroCodeId, s.NeutronChain.ValidatorWallets[0].Address, 2, 86400000000000)
// pause the contract
log.Println("==== Pausing contract")
s.PauseTheHydroContract(s.NeutronChain.ValidatorWallets[0].Moniker, contractAddr)
// confirm that calling contract returns an error
log.Println("==== Confirming contract is paused")
err := s.LockTokens(0, 100000000000, "10", dstIbcDenom, contractAddr)
RequirePaused(s, err)
err = s.UnlockTokens(contractAddr)
RequirePaused(s, err)
err = s.RefreshLock(contractAddr, 0, 0)
RequirePaused(s, err)
err = s.SubmitHydroProposal(0, contractAddr, "tranche 2 prop 2", 2, DefaultDeploymentDuration, DefaultMinLiquidityRequest)
RequirePaused(s, err)
err = s.VoteForHydroProposal(0, contractAddr, 1, []ProposalToLockups{})
RequirePaused(s, err)
err = s.WhitelistAccount(contractAddr, s.NeutronChain.ValidatorWallets[1].Address)
RequirePaused(s, err)
err = s.RemoveFromWhitelist(contractAddr, s.NeutronChain.ValidatorWallets[0].Address)
RequirePaused(s, err)
err = s.AddTranche(contractAddr, "test", "test")
RequirePaused(s, err)
err = s.EditTranche(contractAddr, "test", "test", 1)
RequirePaused(s, err)
err = s.AddICQManager(contractAddr, s.NeutronChain.ValidatorWallets[1].Address)
RequirePaused(s, err)
err = s.RemoveICQManager(contractAddr, s.NeutronChain.ValidatorWallets[1].Address)
RequirePaused(s, err)
err = s.UpdateMaxLockedTokens(contractAddr, 100000000000, time.Now().UTC().Add(time.Hour).UnixNano())
RequirePaused(s, err)
}
func RequirePaused(s *HydroSuite, err error) {
s.Require().Error(err)
s.Require().Contains(err.Error(), "Paused")
}
// TestActiveValidatorChange tests dropping one validator from the active set, adding a new one, and checks its effect on the proposal voting power
func (s *HydroSuite) TestActiveValidatorChange() {
log.Println("==== Running active validator change test")
hubNode := s.HubChain.GetNode()
// val1 delegate tokens to validator 1(self delegate), 2 and 3
log.Println("==== Delegating tokens")
s.DelegateTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[0].ValoperAddress, txAmountUatom(1000))
s.DelegateTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[1].ValoperAddress, txAmountUatom(1000))
s.DelegateTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[2].ValoperAddress, txAmountUatom(1000))
// liquid stake tokens
log.Println("==== Tokenizing shares")
recordId1 := s.LiquidStakeTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[0].ValoperAddress,
s.HubChain.ValidatorWallets[0].Address, txAmountUatom(500))
recordId2 := s.LiquidStakeTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[1].ValoperAddress,
s.HubChain.ValidatorWallets[0].Address, txAmountUatom(500))
recordId3 := s.LiquidStakeTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[2].ValoperAddress,
s.HubChain.ValidatorWallets[0].Address, txAmountUatom(500))
// transfer share tokens to neutron chain
log.Println("==== Transferring tokenized shares to Neutron")
sourceIbcDenom1 := fmt.Sprintf("%s/%s", strings.ToLower(s.HubChain.ValidatorWallets[0].ValoperAddress), recordId1)
dstIbcDenom1 := s.HubToNeutronShareTokenTransfer(0, math.NewInt(400), sourceIbcDenom1, s.NeutronChain.ValidatorWallets[0].Address)
sourceIbcDenom2 := fmt.Sprintf("%s/%s", strings.ToLower(s.HubChain.ValidatorWallets[1].ValoperAddress), recordId2)
dstIbcDenom2 := s.HubToNeutronShareTokenTransfer(0, math.NewInt(400), sourceIbcDenom2, s.NeutronChain.ValidatorWallets[0].Address)
sourceIbcDenom3 := fmt.Sprintf("%s/%s", strings.ToLower(s.HubChain.ValidatorWallets[2].ValoperAddress), recordId3)
dstIbcDenom3 := s.HubToNeutronShareTokenTransfer(0, math.NewInt(400), sourceIbcDenom3, s.NeutronChain.ValidatorWallets[0].Address)
// deploy hydro contract - instantiate code
// active valset consists of 2 validators, currently val1 and val2
contractAddr := s.InstantiateHydroContract(s.NeutronChain.ValidatorWallets[0].Moniker, hydroCodeId, s.NeutronChain.ValidatorWallets[0].Address, 2, 86400000000000)
// register interchain query for val1 and val2
log.Println("==== Registering interchain queries for val1 and val2")
s.RegisterInterchainQueries([]string{s.HubChain.ValidatorWallets[0].ValoperAddress, s.HubChain.ValidatorWallets[1].ValoperAddress},
contractAddr, s.NeutronChain.ValidatorWallets[0].Moniker)
// lock tokens
log.Println("==== Locking tokens in Hydro")
err := s.LockTokens(0, 86400000000000, "10", dstIbcDenom1, contractAddr)
s.Require().NoError(err)
err = s.LockTokens(0, 3*86400000000000, "10", dstIbcDenom2, contractAddr)
s.Require().NoError(err)
votingPowerVal1Denom := "10"
votingPowerVal1Val2Denoms := "25"
votingPowerVal1Val3Denoms := "30"
// create hydro proposals
log.Println("==== Creating proposals")
err = s.SubmitHydroProposal(0, contractAddr, "tranche 1 prop 1", 1, DefaultDeploymentDuration, DefaultMinLiquidityRequest)
s.Require().NoError(err)
log.Println("==== Voting for proposals")
// vote for tranche 1 proposal 1
proposal, err := s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal("0", proposal.Power)
proposalsVotes := []ProposalToLockups{
{
ProposalId: proposal.ProposalID,
LockIds: []int{0, 1},
},
}
err = s.VoteForHydroProposal(0, contractAddr, 1, proposalsVotes)
s.Require().NoError(err)
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal(votingPowerVal1Val2Denoms, proposal.Power)
// increase stake for val3 on hub, so that val2 drops from active valset and val3 enters
log.Println("==== Increasing stake for val3 to drop val2 from active valset")
s.Require().NoError(s.HubChain.UpdateAndVerifyStakeChange(s.GetContext(), s.NeutronChain, s.Relayer, 1_000_000, 2))
// register icq for val3
log.Println("==== Registering interchain query for val3")
s.RegisterInterchainQueries([]string{s.HubChain.ValidatorWallets[2].ValoperAddress}, contractAddr, s.NeutronChain.ValidatorWallets[0].Moniker)
// check that voting power is equal to voting power of val1 because val2 is not among active vals anymore
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal(votingPowerVal1Denom, proposal.Power)
// lock token for val3
log.Println("==== Locking tokens for val3")
err = s.LockTokens(0, 6*86400000000000, "10", dstIbcDenom3, contractAddr)
s.Require().NoError(err)
// proposal power is increased with val3 shares right after the locking
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal(votingPowerVal1Val3Denoms, proposal.Power)
}
// TestValidatorSlashing tests that voting power of user, proposal, round is changed after validator is slashed
func (s *HydroSuite) TestValidatorSlashing() {
log.Println("==== Running validator slashing test")
hubNode := s.HubChain.GetNode()
// delegate tokens
log.Println("==== Delegating tokens")
s.DelegateTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[3].ValoperAddress, txAmountUatom(1000))
// liquid stake tokens
log.Println("==== Tokenizing shares")
recordId1 := s.LiquidStakeTokens(hubNode, s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[3].ValoperAddress,
s.HubChain.ValidatorWallets[0].Address, txAmountUatom(500))
// transfer share tokens to neutron chain
log.Println("==== Transferring tokenized shares to Neutron")
sourceIbcDenom1 := fmt.Sprintf("%s/%s", strings.ToLower(s.HubChain.ValidatorWallets[3].ValoperAddress), recordId1)
dstIbcDenom1 := s.HubToNeutronShareTokenTransfer(0, math.NewInt(400), sourceIbcDenom1, s.NeutronChain.ValidatorWallets[0].Address)
// deploy hydro contract - instantiate code
contractAddr := s.InstantiateHydroContract(s.NeutronChain.ValidatorWallets[0].Moniker, hydroCodeId, s.NeutronChain.ValidatorWallets[0].Address, 4, 86400000000000)
// register interchain query
s.RegisterInterchainQueries([]string{s.HubChain.ValidatorWallets[3].ValoperAddress},
contractAddr, s.NeutronChain.ValidatorWallets[0].Moniker)
// lock tokens
lockAmount := "300"
powerAfterSlashing := 271 // ceil rounding is used, which is why the power after slashing 10% of the tokens is 271 instead of 270
s.Require().NoError(s.LockTokens(0, 86400000000000, lockAmount, dstIbcDenom1, contractAddr))
// create hydro proposals
s.Require().NoError(s.SubmitHydroProposal(0, contractAddr, "tranche 1 prop 1", 1, DefaultDeploymentDuration, DefaultMinLiquidityRequest))
// vote for proposal
proposal, err := s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal("0", proposal.Power)
proposalsVotes := []ProposalToLockups{
{
ProposalId: proposal.ProposalID,
LockIds: []int{0},
},
}
s.Require().NoError(s.VoteForHydroProposal(0, contractAddr, 1, proposalsVotes))
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal(lockAmount, proposal.Power)
// stop the val4 and wait for it to be slashed
s.Require().NoError(s.HubChain.Validators[3].StopContainer(s.GetContext()))
// wait for confirmation that the node is slashed
s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), chainsuite.ProviderSlashingWindow+1, s.HubChain))
// wait for icq to get the updated data
height, err := s.HubChain.Height(s.GetContext())
s.Require().NoError(err)
s.WaitForQueryUpdate(contractAddr, height)
// restart the node - not mandatory for this test
s.Require().NoError(s.HubChain.Validators[3].StartContainer(s.GetContext()))
// check power after slashing
// power decrease is based on slash_fraction_downtime from genesis
proposal, err = s.GetProposalByTitle(contractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal(strconv.Itoa(powerAfterSlashing), proposal.Power)
// check user total voting power
userPower := s.GetUserVotingPower(contractAddr, s.NeutronChain.ValidatorWallets[0].Address)
s.Require().Equal(int64(powerAfterSlashing), userPower)
// check round total voting power
roundPower := s.GetRoundVotingPower(contractAddr, 0)
s.Require().Equal(int64(powerAfterSlashing), roundPower)
}
// TestTributeContract tests tribute creation and distribution
func (s *HydroSuite) TestTributeContract() {
log.Println("==== Running tribute test")
// delegate tokens
s.DelegateTokens(s.HubChain.Validators[0], s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[0].ValoperAddress, txAmountUatom(10000))
// liquid stake tokens
recordId := s.LiquidStakeTokens(s.HubChain.Validators[0], s.HubChain.ValidatorWallets[0].Moniker, s.HubChain.ValidatorWallets[0].ValoperAddress,
s.HubChain.ValidatorWallets[0].Address, txAmountUatom(5000))
// transfer share tokens to neutron chain
sourceIbcDenom := fmt.Sprintf("%s/%s", strings.ToLower(s.HubChain.ValidatorWallets[0].ValoperAddress), recordId)
dstIbcDenom := s.HubToNeutronShareTokenTransfer(0, math.NewInt(1000), sourceIbcDenom, s.NeutronChain.ValidatorWallets[1].Address)
s.HubToNeutronShareTokenTransfer(0, math.NewInt(1000), sourceIbcDenom, s.NeutronChain.ValidatorWallets[2].Address)
s.HubToNeutronShareTokenTransfer(0, math.NewInt(1000), sourceIbcDenom, s.NeutronChain.ValidatorWallets[3].Address)
// deploy hydro contract - instantiate code
roundLength := 300000000000
hydroContractAddr := s.InstantiateHydroContract(s.NeutronChain.ValidatorWallets[0].Moniker, hydroCodeId, s.NeutronChain.ValidatorWallets[0].Address, 4, roundLength)
// deploy tribute contract - instantiate code
tributeContractAddr := s.InstantiateTributeContract(tributeCodeId, hydroContractAddr, s.NeutronChain.ValidatorWallets[0].Address)
// register interchain query
s.RegisterInterchainQueries([]string{s.HubChain.ValidatorWallets[0].ValoperAddress},
hydroContractAddr, s.NeutronChain.ValidatorWallets[0].Moniker)
// lock tokens
lockAmountVal2 := "800"
lockAmountVal3 := "400"
lockAmountVal4 := "200"
s.Require().NoError(s.LockTokens(1, roundLength, lockAmountVal2, dstIbcDenom, hydroContractAddr))
s.Require().NoError(s.LockTokens(2, roundLength, lockAmountVal3, dstIbcDenom, hydroContractAddr))
s.Require().NoError(s.LockTokens(3, roundLength, lockAmountVal4, dstIbcDenom, hydroContractAddr))
// validator 1 creates hydro proposals
s.Require().NoError(s.SubmitHydroProposal(0, hydroContractAddr, "tranche 1 prop 1", 1, DefaultDeploymentDuration, DefaultMinLiquidityRequest))
s.Require().NoError(s.SubmitHydroProposal(0, hydroContractAddr, "tranche 1 prop 2", 1, DefaultDeploymentDuration, DefaultMinLiquidityRequest))
s.Require().NoError(s.SubmitHydroProposal(0, hydroContractAddr, "tranche 1 prop 3", 1, DefaultDeploymentDuration, DefaultMinLiquidityRequest))
s.Require().NoError(s.SubmitHydroProposal(0, hydroContractAddr, "tranche 1 prop 4", 1, DefaultDeploymentDuration, DefaultMinLiquidityRequest))
proposal1, err := s.GetProposalByTitle(hydroContractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal("0", proposal1.Power)
proposal2, err := s.GetProposalByTitle(hydroContractAddr, "tranche 1 prop 2", 1)
s.Require().NoError(err)
s.Require().Equal("0", proposal2.Power)
proposal3, err := s.GetProposalByTitle(hydroContractAddr, "tranche 1 prop 3", 1)
s.Require().NoError(err)
s.Require().Equal("0", proposal3.Power)
proposal4, err := s.GetProposalByTitle(hydroContractAddr, "tranche 1 prop 4", 1)
s.Require().NoError(err)
s.Require().Equal("0", proposal4.Power)
roundId := s.GetCurrentRound(hydroContractAddr) // all proposals are expected to be submitted in the same round
// validator 1 adds tribute for the first three proposals
tribute1Id, err := s.SubmitTribute(0, 10000, proposal1.RoundID, proposal1.TrancheID, proposal1.ProposalID, tributeContractAddr)
s.Require().NoError(err)
tribute2Id, err := s.SubmitTribute(0, 20000, proposal2.RoundID, proposal2.TrancheID, proposal2.ProposalID, tributeContractAddr)
s.Require().NoError(err)
tribute3Id, err := s.SubmitTribute(0, 30000, proposal3.RoundID, proposal3.TrancheID, proposal3.ProposalID, tributeContractAddr)
s.Require().NoError(err)
// validator 4 adds tribute for the fourth proposal
tribute4Id, err := s.SubmitTribute(3, 40000, proposal4.RoundID, proposal4.TrancheID, proposal4.ProposalID, tributeContractAddr)
s.Require().NoError(err)
// val2 votes for proposal 1
proposalsVotes := []ProposalToLockups{
{
ProposalId: proposal1.ProposalID,
LockIds: []int{0},
},
}
s.Require().NoError(s.VoteForHydroProposal(1, hydroContractAddr, int64(proposal1.TrancheID), proposalsVotes))
proposal1, err = s.GetProposalByTitle(hydroContractAddr, "tranche 1 prop 1", 1)
s.Require().NoError(err)
s.Require().Equal(lockAmountVal2, proposal1.Power)
// val3 votes for proposal 2
proposalsVotes = []ProposalToLockups{
{
ProposalId: proposal2.ProposalID,
LockIds: []int{1},
},
}
s.Require().NoError(s.VoteForHydroProposal(2, hydroContractAddr, int64(proposal2.TrancheID), proposalsVotes))
proposal2, err = s.GetProposalByTitle(hydroContractAddr, "tranche 1 prop 2", 1)
s.Require().NoError(err)
s.Require().Equal(lockAmountVal3, proposal2.Power)
// val4 votes for proposal 3
proposalsVotes = []ProposalToLockups{
{
ProposalId: proposal3.ProposalID,
LockIds: []int{2},
},
}
s.Require().NoError(s.VoteForHydroProposal(3, hydroContractAddr, int64(proposal3.TrancheID), proposalsVotes))
proposal3, err = s.GetProposalByTitle(hydroContractAddr, "tranche 1 prop 3", 1)
s.Require().NoError(err)
s.Require().Equal(lockAmountVal4, proposal3.Power)
// balance of the accounts before the round is finished
oldBalanceVal1, err := s.NeutronChain.GetBalance(s.GetContext(), s.NeutronChain.ValidatorWallets[0].Address, chainsuite.Untrn)
s.Require().NoError(err)
oldBalanceVal2, err := s.NeutronChain.GetBalance(s.GetContext(), s.NeutronChain.ValidatorWallets[1].Address, chainsuite.Untrn)
s.Require().NoError(err)
oldBalanceVal3, err := s.NeutronChain.GetBalance(s.GetContext(), s.NeutronChain.ValidatorWallets[2].Address, chainsuite.Untrn)
s.Require().NoError(err)
oldBalanceVal4, err := s.NeutronChain.GetBalance(s.GetContext(), s.NeutronChain.ValidatorWallets[3].Address, chainsuite.Untrn)
s.Require().NoError(err)
// verify that reward cannot be claimed nor refunded before the round is finished
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[1].Address, roundId, proposal1.TrancheID, tribute1Id)
s.Require().Error(err)
s.Require().Contains(err.Error(), "Round has not ended yet")
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[2].Address, roundId, proposal2.TrancheID, tribute2Id)
s.Require().Error(err)
s.Require().Contains(err.Error(), "Round has not ended yet")
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[3].Address, roundId, proposal3.TrancheID, tribute3Id)
s.Require().Error(err)
s.Require().Contains(err.Error(), "Round has not ended yet")
err = s.RefundTribute(3, tributeContractAddr, roundId, proposal4.TrancheID, tribute4Id, proposal4.ProposalID)
s.Require().Error(err)
s.Require().Contains(err.Error(), "Round has not ended yet")
// wait for new round to start, so that we can claim tribute rewards gained in previous round
s.WaitForRound(hydroContractAddr, roundId+1)
// verify that tributes can not be claimed nor refunded until information about liquidity deployment is stored in the hydro contract
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[1].Address, roundId, proposal1.TrancheID, tribute1Id)
s.Require().Error(err)
s.Require().Contains(err.Error(), "Tribute not claimable: Proposal did not have a liquidity deployment entered")
err = s.RefundTribute(3, tributeContractAddr, roundId, proposal4.TrancheID, tribute4Id, proposal4.ProposalID)
s.Require().Error(err)
s.Require().Contains(err.Error(), "Can't refund tribute for proposal that didn't have a liquidity deployment entered")
// enter the liquidity deployment info for each proposal from the previous round
liquidityDeployments := []struct {
TrancheID int
ProposalID int
DeployedFunds sdk.Coin
}{
{
TrancheID: proposal1.TrancheID,
ProposalID: proposal1.ProposalID,
DeployedFunds: sdk.Coin{
Amount: math.NewInt(100000000),
Denom: "uatom",
},
},
{
TrancheID: proposal2.TrancheID,
ProposalID: proposal2.ProposalID,
DeployedFunds: sdk.Coin{
Amount: math.NewInt(100000000),
Denom: "uatom",
},
},
{
TrancheID: proposal3.TrancheID,
ProposalID: proposal3.ProposalID,
DeployedFunds: sdk.Coin{
Amount: math.NewInt(0),
Denom: "uatom",
},
},
{
TrancheID: proposal4.TrancheID,
ProposalID: proposal4.ProposalID,
DeployedFunds: sdk.Coin{
Amount: math.NewInt(0),
Denom: "uatom",
},
},
}
for _, liquidityDeployment := range liquidityDeployments {
err = s.AddLiquidityDeployment(
0,
hydroContractAddr,
roundId,
liquidityDeployment.TrancheID,
liquidityDeployment.ProposalID,
liquidityDeployment.DeployedFunds)
s.Require().NoError(err)
}
// verify that proposal that received liquidity cannot be refunded
err = s.RefundTribute(0, tributeContractAddr, roundId, proposal1.TrancheID, tribute1Id, proposal1.ProposalID)
s.Require().Error(err)
s.Require().Contains(err.Error(), "Can't refund tribute for proposal that received a non-zero liquidity deployment")
// claim reward for proposal that received liquidity
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[1].Address, roundId, proposal1.TrancheID, tribute1Id)
s.Require().NoError(err)
// claim reward for proposal that received liquidity
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[2].Address, roundId, proposal2.TrancheID, tribute2Id)
s.Require().NoError(err)
// can not claim tribute for proposal that didn't receive any liquidity
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[3].Address, roundId, proposal3.TrancheID, tribute3Id)
s.Require().Error(err)
s.Require().Contains(err.Error(), "Tribute not claimable: Proposal did not receive a non-zero liquidity deployment")
// refund tribute for proposal that received votes, but didn't receive any liquidity
err = s.RefundTribute(0, tributeContractAddr, roundId, proposal3.TrancheID, tribute3Id, proposal3.ProposalID)
s.Require().NoError(err)
// refund tribute for the proposal that has no votes at all, and didn't receive any liquidity
err = s.RefundTribute(3, tributeContractAddr, roundId, proposal4.TrancheID, tribute4Id, proposal4.ProposalID)
s.Require().NoError(err)
// verify that the same proposal cannot be claimed twice
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[1].Address, roundId, proposal1.TrancheID, tribute1Id)
s.Require().Error(err)
// verify that same proposal cannot be refunded twice
err = s.RefundTribute(0, tributeContractAddr, roundId, proposal3.TrancheID, tribute3Id, proposal3.ProposalID)
s.Require().Error(err)
// refunded proposal cannot be claimed
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[1].Address, roundId, proposal3.TrancheID, tribute3Id)
s.Require().Error(err)
newBalanceVal1, err := s.NeutronChain.GetBalance(s.GetContext(), s.NeutronChain.ValidatorWallets[0].Address, chainsuite.Untrn)
s.Require().NoError(err)
newBalanceVal2, err := s.NeutronChain.GetBalance(s.GetContext(), s.NeutronChain.ValidatorWallets[1].Address, chainsuite.Untrn)
s.Require().NoError(err)
newBalanceVal3, err := s.NeutronChain.GetBalance(s.GetContext(), s.NeutronChain.ValidatorWallets[2].Address, chainsuite.Untrn)
s.Require().NoError(err)
newBalanceVal4, err := s.NeutronChain.GetBalance(s.GetContext(), s.NeutronChain.ValidatorWallets[3].Address, chainsuite.Untrn)
s.Require().NoError(err)
// proposal power after voting: proposal_1=800, proposal_2: 400, proposal_3: 200, proposal_4: 0
// proposal 1 and 2 received the liquidity, which means that only those voters get the tribute reward and tribute from the other proposals can be refunded
s.Require().True(newBalanceVal2.Sub(oldBalanceVal2).Equal(math.NewInt(10000))) // reward is tribute for proposal1 = 10000
s.Require().True(newBalanceVal3.Sub(oldBalanceVal3).Equal(math.NewInt(20000))) // reward is tribute for proposal2 = 20000
s.Require().True(newBalanceVal1.GT(oldBalanceVal1)) // refunded proposal3
s.Require().True(newBalanceVal4.GT(oldBalanceVal4)) // refunded proposal4
// verify that we can add a tribute to proposals even after the round has ended
tribute5Id, err := s.SubmitTribute(0, 50000, proposal1.RoundID, proposal1.TrancheID, proposal1.ProposalID, tributeContractAddr)
s.Require().NoError(err)
tribute6Id, err := s.SubmitTribute(0, 50000, proposal3.RoundID, proposal3.TrancheID, proposal3.ProposalID, tributeContractAddr)
s.Require().NoError(err)
// users can claim immediately, since the voting period for the proposal is over
// expect no error when claiming the tribute for prop 1
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[1].Address, proposal4.RoundID, proposal4.TrancheID, tribute5Id)
s.Require().NoError(err)
// expect an error when claiming the tribute for prop 4, since it it didn't get any liquidity deployed
err = s.ClaimTribute(0, tributeContractAddr, s.NeutronChain.ValidatorWallets[3].Address, proposal4.RoundID, proposal4.TrancheID, tribute6Id)
s.Require().Error(err)
s.Require().Contains(err.Error(), "Tribute not claimable: Proposal did not receive a non-zero liquidity deployment")
// also, tributes can be refund immediately, since the voting period for the proposal is over
// expect an error refunding tribute 5 since it was for a proposal that received some liquidity
err = s.RefundTribute(0, tributeContractAddr, proposal4.RoundID, proposal4.TrancheID, tribute5Id, proposal1.ProposalID)
s.Require().Error(err)
// expect no error when refunding tribute 6 since it was for a proposal that didn't receive any liquidity
err = s.RefundTribute(0, tributeContractAddr, proposal4.RoundID, proposal4.TrancheID, tribute6Id, proposal3.ProposalID)
s.Require().NoError(err)
}