Skip to content

Commit 776ead9

Browse files
Vanja/feat/767 code coverage staker (#1279)
* Increase coverage in staker package * fix after merge * Fix weight bug, add tests * Fix after merge * Fix comment fixes * Add more tests
1 parent bdac63e commit 776ead9

4 files changed

Lines changed: 421 additions & 1 deletion

File tree

builtin/staker/transition_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) 2025 The VeChainThor developers
2+
//
3+
// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
4+
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>
5+
package staker
6+
7+
import (
8+
"math/big"
9+
"testing"
10+
11+
"github.com/ethereum/go-ethereum/rlp"
12+
"github.com/stretchr/testify/assert"
13+
14+
"github.com/vechain/thor/v2/builtin/params"
15+
"github.com/vechain/thor/v2/muxdb"
16+
"github.com/vechain/thor/v2/state"
17+
"github.com/vechain/thor/v2/test/datagen"
18+
"github.com/vechain/thor/v2/thor"
19+
"github.com/vechain/thor/v2/trie"
20+
)
21+
22+
func TestTransition(t *testing.T) {
23+
db := muxdb.NewMem()
24+
st := state.New(db, trie.Root{})
25+
26+
param := params.New(thor.BytesToAddress([]byte("params")), st)
27+
28+
assert.NoError(t, param.Set(thor.KeyMaxBlockProposers, big.NewInt(2)))
29+
stakerAddr := thor.BytesToAddress([]byte("stkr"))
30+
staker := New(stakerAddr, st, param, nil)
31+
32+
isExecuted, err := staker.transition(thor.EpochLength())
33+
assert.NoError(t, err)
34+
assert.False(t, isExecuted)
35+
36+
node1 := datagen.RandAddress()
37+
stake := RandomStake()
38+
err = staker.AddValidation(node1, node1, uint32(360)*24*15, stake)
39+
assert.NoError(t, err)
40+
41+
isExecuted, err = staker.transition(thor.EpochLength())
42+
assert.NoError(t, err)
43+
assert.False(t, isExecuted)
44+
45+
node2 := datagen.RandAddress()
46+
err = staker.AddValidation(node2, node2, uint32(360)*24*15, stake)
47+
assert.NoError(t, err)
48+
49+
staker.params.Set(thor.KeyMaxBlockProposers, big.NewInt(0))
50+
51+
isExecuted, err = staker.transition(thor.EpochLength())
52+
assert.NoError(t, err)
53+
assert.False(t, isExecuted)
54+
55+
staker.params.Set(thor.KeyMaxBlockProposers, big.NewInt(2))
56+
57+
isExecuted, err = staker.transition(thor.EpochLength())
58+
assert.NoError(t, err)
59+
assert.True(t, isExecuted)
60+
61+
isExecuted, err = staker.transition(thor.EpochLength())
62+
assert.NoError(t, err)
63+
assert.False(t, isExecuted)
64+
65+
activeCountSlot := thor.BytesToBytes32([]byte(("validations-active-group-size")))
66+
st.SetRawStorage(stakerAddr, activeCountSlot, rlp.RawValue{0xFF})
67+
isExecuted, err = staker.transition(thor.EpochLength())
68+
assert.Error(t, err)
69+
assert.False(t, isExecuted)
70+
71+
queuedCountSlot := thor.BytesToBytes32([]byte(("validations-queued-group-size")))
72+
st.SetRawStorage(stakerAddr, activeCountSlot, rlp.RawValue{0x0})
73+
st.SetRawStorage(stakerAddr, queuedCountSlot, rlp.RawValue{0xFF})
74+
}

builtin/staker/validation/service_test.go

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ func poisonValidationSlot(st *state.State, contract thor.Address, id thor.Addres
4343
st.SetRawStorage(contract, slot, rlp.RawValue{0xFF})
4444
}
4545

46+
func poisonQueueSlot(st *state.State, contract thor.Address) {
47+
st.SetRawStorage(contract, slotQueuedGroupSize, rlp.RawValue{0xFF})
48+
}
49+
4650
func TestService_SetGetValidation_RoundTrip(t *testing.T) {
4751
svc, _, _ := newSvc()
4852

@@ -630,3 +634,220 @@ func TestService_GetCompletedPeriods(t *testing.T) {
630634
assert.NoError(t, err)
631635
assert.Equal(t, uint32(0), periods)
632636
}
637+
638+
func TestService_GetQueuedAndLeaderGroups(t *testing.T) {
639+
svc, _, _ := newSvc()
640+
641+
a1 := thor.BytesToAddress([]byte("a1"))
642+
a2 := thor.BytesToAddress([]byte("a2"))
643+
a3 := thor.BytesToAddress([]byte("a3"))
644+
for _, id := range []thor.Address{a1, a2, a3} {
645+
assert.NoError(t, svc.Add(id, id, thor.LowStakingPeriod(), big.NewInt(1)))
646+
}
647+
648+
queuedCnt, err := svc.QueuedGroupSize()
649+
assert.NoError(t, err)
650+
assert.Equal(t, big.NewInt(3), queuedCnt)
651+
652+
leaderCnt, err := svc.LeaderGroupSize()
653+
assert.NoError(t, err)
654+
assert.Equal(t, big.NewInt(0).String(), leaderCnt.String())
655+
656+
idPtr, err := svc.NextToActivate(big.NewInt(10))
657+
assert.NoError(t, err)
658+
assert.Equal(t, a1, *idPtr)
659+
_, err = svc.ActivateValidator(*idPtr, 1, &delta.Renewal{NewLockedWeight: big.NewInt(0)})
660+
assert.NoError(t, err)
661+
662+
queuedCnt, err = svc.QueuedGroupSize()
663+
assert.NoError(t, err)
664+
assert.Equal(t, big.NewInt(2), queuedCnt)
665+
666+
leaderCnt, err = svc.LeaderGroupSize()
667+
assert.NoError(t, err)
668+
assert.Equal(t, big.NewInt(1), leaderCnt)
669+
670+
val, err := svc.GetLeaderGroupHead()
671+
assert.NoError(t, err)
672+
assert.Equal(t, a1, val.Endorser)
673+
assert.Nil(t, val.Beneficiary)
674+
assert.Equal(t, big.NewInt(1), val.LockedVET)
675+
assert.Equal(t, big.NewInt(1), val.Weight)
676+
assert.Equal(t, thor.LowStakingPeriod(), val.Period)
677+
assert.Equal(t, uint32(0), val.CompleteIterations)
678+
assert.Equal(t, StatusActive, val.Status)
679+
assert.Equal(t, uint32(1), val.StartBlock)
680+
assert.Nil(t, val.ExitBlock)
681+
assert.Nil(t, val.OfflineBlock)
682+
assert.Equal(t, big.NewInt(0), val.PendingUnlockVET)
683+
assert.Equal(t, big.NewInt(0), val.QueuedVET)
684+
assert.Equal(t, big.NewInt(0), val.CooldownVET)
685+
assert.Equal(t, big.NewInt(0), val.WithdrawableVET)
686+
}
687+
688+
func TestService_Add_Error(t *testing.T) {
689+
svc, addr, st := newSvc()
690+
id1 := thor.BytesToAddress([]byte("id1"))
691+
id2 := thor.BytesToAddress([]byte("id2"))
692+
693+
assert.ErrorContains(t, svc.Add(id1, id1, uint32(1), big.NewInt(1)), "period is out of boundaries")
694+
assert.ErrorContains(t, svc.Add(id1, id1, thor.LowStakingPeriod(), big.NewInt(0)), "stake is out of range")
695+
assert.NoError(t, svc.Add(id1, id1, thor.LowStakingPeriod(), big.NewInt(1)))
696+
assert.ErrorContains(t, svc.Add(id1, id1, thor.LowStakingPeriod(), big.NewInt(1)), "validator already exists")
697+
698+
poisonValidationSlot(st, addr, id1)
699+
assert.Error(t, svc.Add(id1, id1, thor.LowStakingPeriod(), big.NewInt(1)))
700+
701+
slot := thor.Blake2b(id1.Bytes(), slotValidations.Bytes())
702+
st.SetRawStorage(addr, slot, rlp.RawValue{0x0})
703+
poisonQueueSlot(st, addr)
704+
assert.Error(t, svc.Add(id2, id2, thor.LowStakingPeriod(), big.NewInt(1)))
705+
}
706+
707+
func TestService_Evict(t *testing.T) {
708+
svc, addr, st := newSvc()
709+
id1 := thor.BytesToAddress([]byte("id1"))
710+
711+
assert.NoError(t, svc.Add(id1, id1, thor.LowStakingPeriod(), big.NewInt(1)))
712+
713+
assert.NoError(t, svc.Evict(id1, 5))
714+
val, err := svc.GetValidation(id1)
715+
assert.NoError(t, err)
716+
expectedExitBlock := uint32(5) + thor.EpochLength()
717+
assert.Equal(t, &expectedExitBlock, val.ExitBlock)
718+
719+
assert.NoError(t, svc.Evict(id1, 7))
720+
val, err = svc.GetValidation(id1)
721+
assert.NoError(t, err)
722+
assert.Equal(t, &expectedExitBlock, val.ExitBlock)
723+
724+
poisonExitSlot(st, addr, 7+thor.EpochLength())
725+
assert.Error(t, svc.Evict(id1, 7))
726+
727+
poisonValidationSlot(st, addr, id1)
728+
assert.Error(t, svc.Evict(id1, 8))
729+
}
730+
731+
func TestService_SetBeneficiary(t *testing.T) {
732+
svc, addr, st := newSvc()
733+
id1 := thor.BytesToAddress([]byte("id1"))
734+
assert.NoError(t, svc.Add(id1, id1, thor.LowStakingPeriod(), big.NewInt(1)))
735+
736+
val, err := svc.GetValidation(id1)
737+
assert.NoError(t, err)
738+
assert.Nil(t, val.Beneficiary)
739+
740+
assert.NoError(t, svc.SetBeneficiary(id1, id1, id1))
741+
val, err = svc.GetValidation(id1)
742+
assert.NoError(t, err)
743+
assert.Equal(t, &id1, val.Beneficiary)
744+
745+
assert.NoError(t, svc.SetBeneficiary(id1, id1, thor.Address{}))
746+
val, err = svc.GetValidation(id1)
747+
assert.NoError(t, err)
748+
assert.Nil(t, val.Beneficiary)
749+
750+
assert.ErrorContains(t, svc.SetBeneficiary(id1, thor.Address{}, id1), "invalid endorser")
751+
assert.NoError(t, svc.Evict(id1, 2))
752+
assert.ErrorContains(t, svc.SetBeneficiary(id1, id1, id1), "validator has exited or signaled exit, cannot set beneficiary")
753+
754+
poisonValidationSlot(st, addr, id1)
755+
assert.Error(t, svc.SetBeneficiary(id1, id1, id1))
756+
}
757+
758+
func TestService_UpdateOfflineBlock(t *testing.T) {
759+
svc, _, _ := newSvc()
760+
761+
id1 := thor.BytesToAddress([]byte("id1"))
762+
assert.NoError(t, svc.Add(id1, id1, thor.LowStakingPeriod(), big.NewInt(1)))
763+
764+
val, err := svc.GetValidation(id1)
765+
assert.NoError(t, err)
766+
assert.Nil(t, val.OfflineBlock)
767+
768+
assert.NoError(t, svc.UpdateOfflineBlock(id1, 2, false))
769+
770+
expectedOfflineBlk := uint32(2)
771+
val, err = svc.GetValidation(id1)
772+
assert.NoError(t, err)
773+
assert.Equal(t, &expectedOfflineBlk, val.OfflineBlock)
774+
775+
assert.NoError(t, svc.UpdateOfflineBlock(id1, 2, true))
776+
777+
val, err = svc.GetValidation(id1)
778+
assert.NoError(t, err)
779+
assert.Nil(t, val.OfflineBlock)
780+
}
781+
782+
func TestService_Renew(t *testing.T) {
783+
svc, _, _ := newSvc()
784+
785+
id1 := thor.BytesToAddress([]byte("id1"))
786+
assert.NoError(t, svc.Add(id1, id1, thor.LowStakingPeriod(), big.NewInt(50)))
787+
788+
err := svc.IncreaseStake(id1, id1, big.NewInt(600))
789+
assert.NoError(t, err)
790+
_, err = svc.DecreaseStake(id1, id1, big.NewInt(300))
791+
assert.NoError(t, err)
792+
793+
val, err := svc.GetValidation(id1)
794+
assert.NoError(t, err)
795+
assert.Equal(t, big.NewInt(0), val.LockedVET)
796+
assert.Equal(t, big.NewInt(0), val.Weight)
797+
assert.Equal(t, big.NewInt(0), val.PendingUnlockVET)
798+
assert.Equal(t, big.NewInt(350), val.QueuedVET)
799+
assert.Equal(t, big.NewInt(0), val.CooldownVET)
800+
assert.Equal(t, big.NewInt(300), val.WithdrawableVET)
801+
802+
renewal := delta.Renewal{
803+
NewLockedVET: big.NewInt(1000),
804+
NewLockedWeight: big.NewInt(1500),
805+
QueuedDecrease: big.NewInt(100),
806+
QueuedDecreaseWeight: big.NewInt(15),
807+
}
808+
delta, err := svc.Renew(id1, &renewal, false)
809+
assert.NoError(t, err)
810+
assert.Equal(t, big.NewInt(350), delta.NewLockedVET)
811+
assert.Equal(t, big.NewInt(350), delta.NewLockedWeight)
812+
assert.Equal(t, big.NewInt(350), delta.QueuedDecrease)
813+
assert.Equal(t, big.NewInt(350), delta.QueuedDecreaseWeight)
814+
815+
val, err = svc.GetValidation(id1)
816+
assert.NoError(t, err)
817+
assert.Equal(t, big.NewInt(350), val.LockedVET)
818+
assert.Equal(t, big.NewInt(1850), val.Weight)
819+
assert.Equal(t, big.NewInt(0), val.PendingUnlockVET)
820+
assert.Equal(t, big.NewInt(0), val.QueuedVET)
821+
assert.Equal(t, big.NewInt(0), val.CooldownVET)
822+
assert.Equal(t, big.NewInt(300), val.WithdrawableVET)
823+
824+
err = svc.IncreaseStake(id1, id1, big.NewInt(400))
825+
assert.NoError(t, err)
826+
_, err = svc.DecreaseStake(id1, id1, big.NewInt(200))
827+
assert.NoError(t, err)
828+
829+
val, err = svc.GetValidation(id1)
830+
assert.NoError(t, err)
831+
assert.Equal(t, big.NewInt(350), val.LockedVET)
832+
assert.Equal(t, big.NewInt(1850), val.Weight)
833+
assert.Equal(t, big.NewInt(0), val.PendingUnlockVET)
834+
assert.Equal(t, big.NewInt(200), val.QueuedVET)
835+
assert.Equal(t, big.NewInt(0), val.CooldownVET)
836+
assert.Equal(t, big.NewInt(500), val.WithdrawableVET)
837+
838+
delta, err = svc.Renew(id1, &renewal, true)
839+
assert.NoError(t, err)
840+
assert.Equal(t, big.NewInt(200), delta.NewLockedVET)
841+
assert.Equal(t, big.NewInt(200), delta.NewLockedWeight)
842+
assert.Equal(t, big.NewInt(200), delta.QueuedDecrease)
843+
assert.Equal(t, big.NewInt(200), delta.QueuedDecreaseWeight)
844+
845+
val, err = svc.GetValidation(id1)
846+
assert.NoError(t, err)
847+
assert.Equal(t, big.NewInt(550), val.LockedVET)
848+
assert.Equal(t, big.NewInt(3550), val.Weight)
849+
assert.Equal(t, big.NewInt(0), val.PendingUnlockVET)
850+
assert.Equal(t, big.NewInt(0), val.QueuedVET)
851+
assert.Equal(t, big.NewInt(0), val.CooldownVET)
852+
assert.Equal(t, big.NewInt(500), val.WithdrawableVET)
853+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) 2025 The VeChainThor developers
2+
//
3+
// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
4+
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>
5+
package validation
6+
7+
import (
8+
"math/big"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
13+
"github.com/vechain/thor/v2/builtin/staker/aggregation"
14+
"github.com/vechain/thor/v2/thor"
15+
)
16+
17+
var baseVal = Validation{
18+
Endorser: thor.Address{},
19+
Beneficiary: nil,
20+
Period: 5,
21+
CompleteIterations: 0,
22+
Status: StatusActive,
23+
StartBlock: 0,
24+
ExitBlock: nil,
25+
OfflineBlock: nil,
26+
LockedVET: big.NewInt(1000),
27+
PendingUnlockVET: big.NewInt(900),
28+
QueuedVET: big.NewInt(800),
29+
CooldownVET: big.NewInt(700),
30+
WithdrawableVET: big.NewInt(600),
31+
Weight: big.NewInt(1000),
32+
}
33+
34+
func TestValidation_Totals(t *testing.T) {
35+
agg := aggregation.Aggregation{
36+
LockedVET: big.NewInt(500),
37+
LockedWeight: big.NewInt(1000),
38+
PendingVET: big.NewInt(400),
39+
PendingWeight: big.NewInt(800),
40+
ExitingVET: big.NewInt(300),
41+
ExitingWeight: big.NewInt(600),
42+
}
43+
totals := baseVal.Totals(&agg)
44+
assert.Equal(t, big.NewInt(1500), totals.TotalLockedStake)
45+
assert.Equal(t, big.NewInt(1000), totals.TotalLockedWeight)
46+
assert.Equal(t, big.NewInt(1200), totals.TotalQueuedStake)
47+
assert.Equal(t, big.NewInt(1600), totals.TotalQueuedWeight)
48+
assert.Equal(t, big.NewInt(1200), totals.TotalExitingStake)
49+
assert.Equal(t, big.NewInt(1500), totals.TotalExitingWeight)
50+
51+
exitBlock := uint32(2)
52+
val := baseVal
53+
val.ExitBlock = &exitBlock
54+
totals = val.Totals(&agg)
55+
assert.Equal(t, big.NewInt(1500), totals.TotalLockedStake)
56+
assert.Equal(t, big.NewInt(1000), totals.TotalLockedWeight)
57+
assert.Equal(t, big.NewInt(1200), totals.TotalQueuedStake)
58+
assert.Equal(t, big.NewInt(1600), totals.TotalQueuedWeight)
59+
assert.Equal(t, big.NewInt(1500), totals.TotalExitingStake)
60+
assert.Equal(t, big.NewInt(1000), totals.TotalExitingWeight)
61+
}
62+
63+
func TestValidation_IsPeriodEnd(t *testing.T) {
64+
assert.True(t, baseVal.IsPeriodEnd(5))
65+
assert.False(t, baseVal.IsPeriodEnd(6))
66+
}
67+
68+
func TestValidation_NextPeriodTVL(t *testing.T) {
69+
assert.Equal(t, big.NewInt(900), baseVal.NextPeriodTVL())
70+
}
71+
72+
func TestValidation_Exit(t *testing.T) {
73+
val := baseVal
74+
delta := val.exit()
75+
assert.Equal(t, StatusExit, val.Status)
76+
assert.Equal(t, big.NewInt(1000), val.CooldownVET)
77+
assert.Equal(t, big.NewInt(0), val.LockedVET)
78+
assert.Equal(t, big.NewInt(0), val.PendingUnlockVET)
79+
assert.Equal(t, big.NewInt(0), val.Weight)
80+
81+
assert.Equal(t, big.NewInt(1000), delta.ExitedTVL)
82+
assert.Equal(t, big.NewInt(1000), delta.ExitedTVLWeight)
83+
assert.Equal(t, big.NewInt(800), delta.QueuedDecrease)
84+
assert.Equal(t, big.NewInt(800), delta.QueuedDecreaseWeight)
85+
}

0 commit comments

Comments
 (0)