Skip to content

Commit 3a1451e

Browse files
authored
correct unstake/changeCand/deposit/restake handling for unstaked bucket (iotexproject#2369)
1 parent 3227b7f commit 3a1451e

File tree

4 files changed

+110
-46
lines changed

4 files changed

+110
-46
lines changed

action/protocol/staking/handlers.go

+33-1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ func (p *Protocol) handleUnstake(ctx context.Context, act *action.Unstake, csm C
152152
return log, errCandNotExist
153153
}
154154

155+
if p.hu.IsPost(config.Greenland, blkCtx.BlockHeight) && bucket.isUnstaked() {
156+
return log, &handleError{
157+
err: errors.New("unstake an already unstaked bucket again not allowed"),
158+
failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
159+
}
160+
}
161+
155162
if bucket.AutoStake {
156163
return log, &handleError{
157164
err: errors.New("AutoStake should be disabled first in order to unstake"),
@@ -210,7 +217,11 @@ func (p *Protocol) handleWithdrawStake(ctx context.Context, act *action.Withdraw
210217
log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucket.Index), bucket.Candidate.Bytes())
211218

212219
// check unstake time
213-
if bucket.UnstakeStartTime.Unix() == 0 {
220+
cannotWithdraw := bucket.UnstakeStartTime.Unix() == 0
221+
if p.hu.IsPost(config.Greenland, blkCtx.BlockHeight) {
222+
cannotWithdraw = !bucket.isUnstaked()
223+
}
224+
if cannotWithdraw {
214225
return log, nil, &handleError{
215226
err: errors.New("bucket has not been unstaked"),
216227
failureStatus: iotextypes.ReceiptStatus_ErrWithdrawBeforeUnstake,
@@ -295,6 +306,13 @@ func (p *Protocol) handleChangeCandidate(ctx context.Context, act *action.Change
295306
return log, errCandNotExist
296307
}
297308

309+
if p.hu.IsPost(config.Greenland, blkCtx.BlockHeight) && bucket.isUnstaked() {
310+
return log, &handleError{
311+
err: errors.New("change candidate for an unstaked bucket not allowed"),
312+
failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
313+
}
314+
}
315+
298316
// update bucket index
299317
if err := delCandBucketIndex(csm, bucket.Candidate, act.BucketIndex()); err != nil {
300318
return log, errors.Wrapf(err, "failed to delete candidate bucket index for candidate %s", bucket.Candidate.String())
@@ -439,6 +457,13 @@ func (p *Protocol) handleDepositToStake(ctx context.Context, act *action.Deposit
439457
return log, nil, errCandNotExist
440458
}
441459

460+
if p.hu.IsPost(config.Greenland, blkCtx.BlockHeight) && bucket.isUnstaked() {
461+
return log, nil, &handleError{
462+
err: errors.New("deposit to an unstaked bucket not allowed"),
463+
failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
464+
}
465+
}
466+
442467
prevWeightedVotes := p.calculateVoteWeight(bucket, csm.ContainsSelfStakingBucket(act.BucketIndex()))
443468
// update bucket
444469
bucket.StakedAmount.Add(bucket.StakedAmount, act.Amount())
@@ -525,6 +550,13 @@ func (p *Protocol) handleRestake(ctx context.Context, act *action.Restake, csm C
525550
return log, errCandNotExist
526551
}
527552

553+
if p.hu.IsPost(config.Greenland, blkCtx.BlockHeight) && bucket.isUnstaked() {
554+
return log, &handleError{
555+
err: errors.New("restake an unstaked bucket not allowed"),
556+
failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
557+
}
558+
}
559+
528560
prevWeightedVotes := p.calculateVoteWeight(bucket, csm.ContainsSelfStakingBucket(act.BucketIndex()))
529561
// update bucket
530562
actDuration := time.Duration(act.Duration()) * 24 * time.Hour

action/protocol/staking/handlers_test.go

+71-45
Original file line numberDiff line numberDiff line change
@@ -927,15 +927,10 @@ func TestProtocol_HandleUnstake(t *testing.T) {
927927
initBalance int64
928928
selfstaking bool
929929
// action fields
930-
index uint64
931-
gasPrice *big.Int
932-
gasLimit uint64
933-
nonce uint64
930+
index uint64
934931
// block context
935-
blkHeight uint64
936932
blkTimestamp time.Time
937933
ctxTimestamp time.Time
938-
blkGasLimit uint64
939934
// clear flag for inMemCandidates
940935
clear bool
941936
// need new p
@@ -953,13 +948,8 @@ func TestProtocol_HandleUnstake(t *testing.T) {
953948
101,
954949
false,
955950
0,
956-
big.NewInt(unit.Qev),
957-
10000,
958-
1,
959-
1,
960951
time.Now(),
961952
time.Now(),
962-
10000,
963953
false,
964954
true,
965955
nil,
@@ -974,13 +964,8 @@ func TestProtocol_HandleUnstake(t *testing.T) {
974964
101,
975965
false,
976966
0,
977-
big.NewInt(unit.Qev),
978-
10000,
979-
1,
980-
1,
981967
time.Now(),
982968
time.Now(),
983-
10000,
984969
false,
985970
false,
986971
nil,
@@ -996,13 +981,8 @@ func TestProtocol_HandleUnstake(t *testing.T) {
996981
101,
997982
false,
998983
1,
999-
big.NewInt(unit.Qev),
1000-
10000,
1001-
1,
1002-
1,
1003984
time.Now(),
1004985
time.Now(),
1005-
10000,
1006986
false,
1007987
true,
1008988
nil,
@@ -1017,13 +997,8 @@ func TestProtocol_HandleUnstake(t *testing.T) {
1017997
101,
1018998
false,
1019999
0,
1020-
big.NewInt(unit.Qev),
1021-
10000,
1022-
1,
1023-
1,
10241000
time.Now(),
10251001
time.Now(),
1026-
10000,
10271002
true,
10281003
true,
10291004
nil,
@@ -1038,13 +1013,8 @@ func TestProtocol_HandleUnstake(t *testing.T) {
10381013
101,
10391014
false,
10401015
0,
1041-
big.NewInt(unit.Qev),
1042-
10000,
1043-
1,
1044-
1,
10451016
time.Now(),
10461017
time.Now(),
1047-
10000,
10481018
false,
10491019
true,
10501020
nil,
@@ -1059,13 +1029,8 @@ func TestProtocol_HandleUnstake(t *testing.T) {
10591029
101,
10601030
false,
10611031
0,
1062-
big.NewInt(unit.Qev),
1063-
10000,
1064-
1,
1065-
1,
10661032
time.Now(),
10671033
time.Now().Add(time.Duration(1) * 24 * time.Hour),
1068-
10000,
10691034
false,
10701035
true,
10711036
nil,
@@ -1081,29 +1046,34 @@ func TestProtocol_HandleUnstake(t *testing.T) {
10811046
101,
10821047
false,
10831048
0,
1084-
big.NewInt(unit.Qev),
1085-
10000,
1086-
1,
1087-
1,
10881049
time.Now(),
10891050
time.Now().Add(time.Duration(1) * 24 * time.Hour),
1090-
10000,
10911051
false,
10921052
true,
10931053
nil,
10941054
iotextypes.ReceiptStatus_Success,
10951055
},
10961056
}
10971057

1058+
var (
1059+
ctx context.Context
1060+
gasPrice = big.NewInt(unit.Qev)
1061+
gasLimit uint64 = 10000
1062+
nonce uint64 = 1
1063+
blkHeight uint64 = 1
1064+
)
1065+
10981066
for _, test := range tests {
10991067
if test.newProtocol {
11001068
sm, p, candidate, _ = initAll(t, ctrl)
11011069
} else {
11021070
candidate = candidate2
11031071
}
1104-
ctx, createCost := initCreateStake(t, sm, test.caller, test.initBalance, big.NewInt(unit.Qev), test.gasLimit, test.nonce, test.blkHeight, test.blkTimestamp, test.blkGasLimit, p, candidate, test.amount, test.autoStake)
1105-
act, err := action.NewUnstake(test.nonce, test.index,
1106-
nil, test.gasLimit, test.gasPrice)
1072+
1073+
var createCost *big.Int
1074+
ctx, createCost = initCreateStake(t, sm, test.caller, test.initBalance, big.NewInt(unit.Qev), gasLimit, nonce, blkHeight, test.blkTimestamp, gasLimit, p, candidate, test.amount, test.autoStake)
1075+
act, err := action.NewUnstake(nonce, test.index,
1076+
nil, gasLimit, gasPrice)
11071077
require.NoError(err)
11081078
if test.blkTimestamp != test.ctxTimestamp {
11091079
blkCtx := protocol.MustGetBlockCtx(ctx)
@@ -1160,11 +1130,67 @@ func TestProtocol_HandleUnstake(t *testing.T) {
11601130
require.NoError(err)
11611131
actCost, err := act.Cost()
11621132
require.NoError(err)
1163-
require.Equal(test.nonce, caller.Nonce)
1133+
require.Equal(nonce, caller.Nonce)
11641134
total := big.NewInt(0)
11651135
require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost))
11661136
}
11671137
}
1138+
1139+
// verify bucket unstaked
1140+
vb, err := getBucket(sm, 0)
1141+
require.NoError(err)
1142+
require.True(vb.isUnstaked())
1143+
1144+
unstake, err := action.NewUnstake(nonce+1, 0, nil, gasLimit, gasPrice)
1145+
require.NoError(err)
1146+
changeCand, err := action.NewChangeCandidate(nonce+1, candidate2.Name, 0, nil, gasLimit, gasPrice)
1147+
require.NoError(err)
1148+
deposit, err := action.NewDepositToStake(nonce+1, 0, "10000", nil, gasLimit, gasPrice)
1149+
require.NoError(err)
1150+
restake, err := action.NewRestake(nonce+1, 0, 0, false, nil, gasLimit, gasPrice)
1151+
require.NoError(err)
1152+
1153+
unstakedBucketTests := []struct {
1154+
act action.Action
1155+
greenland bool
1156+
status iotextypes.ReceiptStatus
1157+
}{
1158+
// unstake an already unstaked bucket again not allowed
1159+
{unstake, true, iotextypes.ReceiptStatus_ErrInvalidBucketType},
1160+
// change candidate for an unstaked bucket not allowed
1161+
{changeCand, true, iotextypes.ReceiptStatus_ErrInvalidBucketType},
1162+
// deposit to unstaked bucket not allowed
1163+
{deposit, true, iotextypes.ReceiptStatus_ErrInvalidBucketType},
1164+
// restake an unstaked bucket not allowed
1165+
{restake, true, iotextypes.ReceiptStatus_ErrInvalidBucketType},
1166+
// restake an unstaked bucket is allowed pre-Greenland
1167+
{restake, false, iotextypes.ReceiptStatus_ErrNotEnoughBalance},
1168+
}
1169+
1170+
for _, v := range unstakedBucketTests {
1171+
greenland := genesis.Default
1172+
if v.greenland {
1173+
blkCtx := protocol.MustGetBlockCtx(ctx)
1174+
greenland.GreenlandBlockHeight = blkCtx.BlockHeight
1175+
}
1176+
ctx = protocol.WithBlockchainCtx(ctx, protocol.BlockchainCtx{
1177+
Genesis: greenland,
1178+
})
1179+
_, err = p.Start(ctx, sm)
1180+
require.NoError(err)
1181+
r, err := p.Handle(ctx, v.act, sm)
1182+
require.NoError(err)
1183+
require.EqualValues(v.status, r.Status)
1184+
1185+
if !v.greenland {
1186+
// pre-Greenland allows restaking an unstaked bucket, and it is considered staked afterwards
1187+
vb, err := getBucket(sm, 0)
1188+
require.NoError(err)
1189+
require.True(vb.StakeStartTime.Unix() != 0)
1190+
require.True(vb.UnstakeStartTime.Unix() != 0)
1191+
require.False(vb.isUnstaked())
1192+
}
1193+
}
11681194
}
11691195

11701196
func TestProtocol_HandleWithdrawStake(t *testing.T) {

action/protocol/staking/vote_bucket.go

+4
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ func (vb *VoteBucket) Serialize() ([]byte, error) {
179179
return proto.Marshal(pb)
180180
}
181181

182+
func (vb *VoteBucket) isUnstaked() bool {
183+
return vb.UnstakeStartTime.After(vb.StakeStartTime)
184+
}
185+
182186
// Deserialize deserializes bytes into bucket count
183187
func (tc *totalBucketCount) Deserialize(data []byte) error {
184188
tc.count = byteutil.BytesToUint64BigEndian(data)

action/protocol/staking/vote_bucket_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ func TestGetPutStaking(t *testing.T) {
8888
require.NoError(err)
8989
vb.AutoStake = false
9090
vb.StakedAmount.Sub(vb.StakedAmount, big.NewInt(100))
91+
vb.UnstakeStartTime = time.Now().UTC()
92+
require.True(vb.isUnstaked())
9193
require.NoError(updateBucket(sm, 2, vb))
9294
vb1, err := getBucket(sm, 2)
9395
require.NoError(err)

0 commit comments

Comments
 (0)