@piplabs/story-contracts 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @piplabs/story-contracts might be problematic. Click here for more details.

@@ -0,0 +1,1022 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity 0.8.23;
3
+ /* solhint-disable no-console */
4
+ /* solhint-disable max-line-length */
5
+ /// NOTE: pragma allowlist-secret must be inline (same line as the pubkey hex string) to avoid false positive
6
+ /// flag "Hex High Entropy String" in CI run detect-secrets
7
+
8
+ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
9
+ import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
10
+
11
+ import { IPTokenStaking, IIPTokenStaking } from "../../src/protocol/IPTokenStaking.sol";
12
+ import { Test } from "../utils/Test.sol";
13
+
14
+ contract Staker {
15
+ IPTokenStaking private ipTokenStaking;
16
+ uint256 private fee;
17
+
18
+ constructor(address ipTokenStakingAddr) {
19
+ ipTokenStaking = IPTokenStaking(ipTokenStakingAddr);
20
+ fee = ipTokenStaking.fee();
21
+ }
22
+
23
+ function stake(bytes calldata validatorCmpPubkey) external payable {
24
+ ipTokenStaking.stake{ value: msg.value }(validatorCmpPubkey, IIPTokenStaking.StakingPeriod.FLEXIBLE, "");
25
+ }
26
+
27
+ function unstake(bytes calldata validatorCmpPubkey, uint256 amount) external {
28
+ ipTokenStaking.unstake{ value: fee }(validatorCmpPubkey, 0, amount, "");
29
+ }
30
+
31
+ function redelegate(
32
+ bytes calldata srcValidatorCmpPubkey,
33
+ bytes calldata dstValidatorCmpPubkey,
34
+ uint256 amount
35
+ ) external {
36
+ ipTokenStaking.redelegate{ value: fee }(srcValidatorCmpPubkey, dstValidatorCmpPubkey, 0, amount);
37
+ }
38
+ }
39
+
40
+ contract IPTokenStakingTest is Test {
41
+ address private delegatorAddr = address(0xf398C12A45Bc409b6C652E25bb0a3e702492A4ab);
42
+
43
+ bytes private validatorCmpPubkey = hex"0381513466154dfc0e702d8325d7a45284bc395f60b813230da299fab640c1eb08"; // pragma: allowlist-secret
44
+ address private validatorAddr = address(0xb5cb887155446f69b5e4D11C30755108AC87e9cD);
45
+
46
+ bytes private wrongValidatorCmpPubkey = hex"0342013466154dfc0e702d8325d7a45284bc395f60b813230da299fab640c1eb08"; // pragma: allowlist-secret
47
+
48
+ bytes private otherValidatorCmpPubkey = hex"03dd68a0a4923a1b9321d39f01425f7b631066514cb2e5e1b5ed91e5c327d30c53"; // pragma: allowlist-secret
49
+ // Address matching delegatorCmpPubkey
50
+ address private otherValidatorAddr = address(0xf89D606F67a267E9dbCc813c8169988aB8aAeB5E);
51
+
52
+ bytes private dataOverMaxLen;
53
+
54
+ event Received(address, uint256);
55
+
56
+ // For some tests, we need to receive the native token to this contract
57
+ receive() external payable {
58
+ emit Received(msg.sender, msg.value);
59
+ }
60
+
61
+ function setUp() public virtual override {
62
+ super.setUp();
63
+
64
+ for (uint256 i = 0; i < ipTokenStaking.MAX_DATA_LENGTH() + 1; i++) {
65
+ dataOverMaxLen = abi.encodePacked(dataOverMaxLen, "a");
66
+ }
67
+ }
68
+
69
+ function testIPTokenStaking_Constructor() public {
70
+ vm.expectRevert("IPTokenStaking: Invalid default min fee");
71
+ new IPTokenStaking(0 ether, 0);
72
+
73
+ address impl;
74
+ IIPTokenStaking.InitializerArgs memory args = IIPTokenStaking.InitializerArgs({
75
+ owner: admin,
76
+ minStakeAmount: 0,
77
+ minUnstakeAmount: 1 ether,
78
+ minCommissionRate: 500,
79
+ fee: 1 ether
80
+ });
81
+ impl = address(
82
+ new IPTokenStaking(
83
+ 1 ether, // Default min fee charged for adding to CL storage, 1 eth
84
+ 256 // maxDataLength
85
+ )
86
+ );
87
+ // IPTokenStaking: minStakeAmount cannot be 0
88
+ vm.expectRevert("IPTokenStaking: Zero min stake amount");
89
+ new ERC1967Proxy(impl, abi.encodeCall(IPTokenStaking.initialize, (args)));
90
+
91
+ // IPTokenStaking: minUnstakeAmount cannot be 0
92
+ vm.expectRevert("IPTokenStaking: Zero min unstake amount");
93
+ args.minStakeAmount = 1 ether;
94
+ args.minUnstakeAmount = 0;
95
+ new ERC1967Proxy(impl, abi.encodeCall(IPTokenStaking.initialize, (args)));
96
+
97
+ // IPTokenStaking: cannot be 0
98
+ vm.expectRevert("IPTokenStaking: Zero min commission rate");
99
+ args.minUnstakeAmount = 1 ether;
100
+ args.minCommissionRate = 0;
101
+ new ERC1967Proxy(impl, abi.encodeCall(IPTokenStaking.initialize, (args)));
102
+
103
+ vm.expectRevert("IPTokenStaking: Invalid min fee");
104
+ args.minCommissionRate = 10;
105
+ args.fee = 0;
106
+ new ERC1967Proxy(impl, abi.encodeCall(IPTokenStaking.initialize, (args)));
107
+ }
108
+
109
+ function testIPTokenStaking_Parameters() public view {
110
+ assertEq(ipTokenStaking.minStakeAmount(), 1024 ether);
111
+ assertEq(ipTokenStaking.minUnstakeAmount(), 1024 ether);
112
+ assertEq(ipTokenStaking.STAKE_ROUNDING(), 1 gwei);
113
+ assertEq(ipTokenStaking.minCommissionRate(), 500);
114
+ assertEq(ipTokenStaking.DEFAULT_MIN_FEE(), 1 ether);
115
+ assertEq(ipTokenStaking.MAX_DATA_LENGTH(), 256);
116
+ }
117
+
118
+ function testIPTokenStaking_CreateValidator() public {
119
+ uint256 stakeAmount = 0.5 ether;
120
+ vm.deal(validatorAddr, stakeAmount);
121
+ vm.prank(validatorAddr);
122
+ vm.expectRevert("IPTokenStaking: Stake amount under min");
123
+ ipTokenStaking.createValidator{ value: stakeAmount }({
124
+ validatorCmpPubkey: validatorCmpPubkey,
125
+ moniker: "delegator's validator",
126
+ commissionRate: 1000,
127
+ maxCommissionRate: 5000,
128
+ maxCommissionChangeRate: 100,
129
+ supportsUnlocked: false,
130
+ data: ""
131
+ });
132
+
133
+ // Network shall allow anyone to create a new validator by staking validator’s own tokens (self-delegation)
134
+ stakeAmount = ipTokenStaking.minStakeAmount();
135
+ vm.deal(validatorAddr, stakeAmount);
136
+ vm.prank(validatorAddr);
137
+ vm.expectEmit(address(ipTokenStaking));
138
+ emit IIPTokenStaking.CreateValidator(
139
+ validatorCmpPubkey,
140
+ "delegator's validator",
141
+ stakeAmount,
142
+ 1000,
143
+ 5000,
144
+ 100,
145
+ 1, // supportsUnlocked
146
+ validatorAddr, // self-delegation, validatorAddr = delegatorAddr
147
+ abi.encode("data")
148
+ );
149
+ ipTokenStaking.createValidator{ value: stakeAmount }({
150
+ validatorCmpPubkey: validatorCmpPubkey,
151
+ moniker: "delegator's validator",
152
+ commissionRate: 1000,
153
+ maxCommissionRate: 5000,
154
+ maxCommissionChangeRate: 100,
155
+ supportsUnlocked: true,
156
+ data: abi.encode("data")
157
+ });
158
+
159
+ // Network shall not allow a moniker longer than MAX_MONIKER_LENGTH
160
+ string memory moniker;
161
+ for (uint256 i = 0; i < ipTokenStaking.MAX_MONIKER_LENGTH() + 1; i++) {
162
+ moniker = string.concat(moniker, "a");
163
+ }
164
+ stakeAmount = ipTokenStaking.minStakeAmount();
165
+ vm.deal(validatorAddr, stakeAmount);
166
+ vm.prank(validatorAddr);
167
+ vm.expectRevert("IPTokenStaking: Moniker length over max");
168
+ ipTokenStaking.createValidator{ value: stakeAmount }({
169
+ validatorCmpPubkey: validatorCmpPubkey,
170
+ moniker: moniker,
171
+ commissionRate: 1000,
172
+ maxCommissionRate: 5000,
173
+ maxCommissionChangeRate: 100,
174
+ supportsUnlocked: false,
175
+ data: ""
176
+ });
177
+
178
+ stakeAmount = ipTokenStaking.minStakeAmount();
179
+ vm.deal(validatorAddr, stakeAmount);
180
+ vm.prank(validatorAddr);
181
+ vm.expectRevert("IPTokenStaking: Data length over max");
182
+ ipTokenStaking.createValidator{ value: stakeAmount }({
183
+ validatorCmpPubkey: validatorCmpPubkey,
184
+ moniker: "",
185
+ commissionRate: 1000,
186
+ maxCommissionRate: 5000,
187
+ maxCommissionChangeRate: 100,
188
+ supportsUnlocked: false,
189
+ data: dataOverMaxLen
190
+ });
191
+ }
192
+
193
+ function testIPTokenStaking_Stake_Periods() public {
194
+ // Flexible should produce 0 delegationId
195
+ IIPTokenStaking.StakingPeriod stkPeriod = IIPTokenStaking.StakingPeriod.FLEXIBLE;
196
+ vm.deal(delegatorAddr, 10_000 ether);
197
+ vm.prank(delegatorAddr);
198
+ uint256 delegationId = ipTokenStaking.stake{ value: 1024 ether }(validatorCmpPubkey, stkPeriod, "");
199
+ assertEq(delegationId, 0);
200
+ // Staking for short period should produce incremented delegationId and correct duration
201
+ // emitted event
202
+ uint256 stakeAmount = ipTokenStaking.minUnstakeAmount();
203
+ uint256 expectedDelegationId = 1;
204
+ vm.deal(delegatorAddr, 10_000_000_000 ether);
205
+ vm.prank(delegatorAddr);
206
+ vm.expectEmit(address(ipTokenStaking));
207
+ emit IIPTokenStaking.Deposit(
208
+ delegatorAddr,
209
+ validatorCmpPubkey,
210
+ stakeAmount,
211
+ uint32(uint8(IIPTokenStaking.StakingPeriod.SHORT)),
212
+ expectedDelegationId,
213
+ delegatorAddr,
214
+ ""
215
+ );
216
+ delegationId = ipTokenStaking.stake{ value: stakeAmount }(
217
+ validatorCmpPubkey,
218
+ IIPTokenStaking.StakingPeriod.SHORT,
219
+ ""
220
+ );
221
+ assertEq(delegationId, expectedDelegationId);
222
+ expectedDelegationId++;
223
+ // Staking for medium period should produce incremented delegationId and correct duration
224
+ // emitted event
225
+ vm.prank(delegatorAddr);
226
+ vm.expectEmit(address(ipTokenStaking));
227
+ emit IIPTokenStaking.Deposit(
228
+ delegatorAddr,
229
+ validatorCmpPubkey,
230
+ stakeAmount,
231
+ uint32(uint8(IIPTokenStaking.StakingPeriod.MEDIUM)),
232
+ expectedDelegationId,
233
+ delegatorAddr,
234
+ ""
235
+ );
236
+ delegationId = ipTokenStaking.stake{ value: stakeAmount }(
237
+ validatorCmpPubkey,
238
+ IIPTokenStaking.StakingPeriod.MEDIUM,
239
+ ""
240
+ );
241
+ assertEq(delegationId, expectedDelegationId);
242
+ expectedDelegationId++;
243
+ // Staking for long period should produce incremented delegationId and correct duration
244
+ // emitted event
245
+ vm.prank(delegatorAddr);
246
+ vm.expectEmit(address(ipTokenStaking));
247
+ emit IIPTokenStaking.Deposit(
248
+ delegatorAddr,
249
+ validatorCmpPubkey,
250
+ stakeAmount,
251
+ uint32(uint8(IIPTokenStaking.StakingPeriod.LONG)),
252
+ expectedDelegationId,
253
+ delegatorAddr,
254
+ ""
255
+ );
256
+ delegationId = ipTokenStaking.stake{ value: stakeAmount }(
257
+ validatorCmpPubkey,
258
+ IIPTokenStaking.StakingPeriod.LONG,
259
+ ""
260
+ );
261
+ assertEq(delegationId, expectedDelegationId);
262
+
263
+ // Test revert for invalid validatorCmpPubkey
264
+ vm.expectRevert("Secp256k1Verifier: Invalid cmp pubkey length");
265
+ bytes memory invalidValidatorCmpPubkey = hex"1234";
266
+ ipTokenStaking.stake{ value: stakeAmount }(
267
+ invalidValidatorCmpPubkey,
268
+ IIPTokenStaking.StakingPeriod.FLEXIBLE,
269
+ ""
270
+ );
271
+ vm.expectRevert("Secp256k1Verifier: pubkey not on curve");
272
+ ipTokenStaking.stake{ value: stakeAmount }(wrongValidatorCmpPubkey, IIPTokenStaking.StakingPeriod.FLEXIBLE, "");
273
+ }
274
+
275
+ function testIPTokenStaking_stake_remainder() public {
276
+ // No remainder if the stake amount has no values under STAKE_ROUNDING
277
+ uint256 stakeAmount = 1024 ether;
278
+ uint256 predeployInitialBalance = 1; // 1 wei, needed to have predeploy at genesis
279
+
280
+ vm.deal(delegatorAddr, stakeAmount);
281
+ vm.prank(delegatorAddr);
282
+ ipTokenStaking.stake{ value: stakeAmount }(validatorCmpPubkey, IIPTokenStaking.StakingPeriod.FLEXIBLE, "data");
283
+ assertEq(
284
+ address(ipTokenStaking).balance,
285
+ predeployInitialBalance,
286
+ "IPTokenStaking: Stake amount should be burned"
287
+ );
288
+ assertEq(address(delegatorAddr).balance, 0, "Delegator: No remainder should be sent back");
289
+
290
+ // Remainder if the stake amount has values under STAKE_ROUNDING
291
+ stakeAmount = 1024 ether + 1 wei;
292
+ vm.deal(delegatorAddr, stakeAmount);
293
+ vm.prank(delegatorAddr);
294
+ ipTokenStaking.stake{ value: stakeAmount }(validatorCmpPubkey, IIPTokenStaking.StakingPeriod.FLEXIBLE, "data");
295
+ assertEq(address(ipTokenStaking).balance, predeployInitialBalance);
296
+ assertEq(address(delegatorAddr).balance, 1 wei);
297
+ }
298
+
299
+ function testIPTokenStaking_Stake_data() public {
300
+ // Network shall not allow a data longer than MAX_DATA_LENGTH
301
+ uint256 stakeAmount = 1024 ether;
302
+ vm.deal(delegatorAddr, stakeAmount);
303
+ vm.prank(delegatorAddr);
304
+ vm.expectRevert("IPTokenStaking: Data length over max");
305
+ ipTokenStaking.stake{ value: stakeAmount }(
306
+ validatorCmpPubkey,
307
+ IIPTokenStaking.StakingPeriod.FLEXIBLE,
308
+ dataOverMaxLen
309
+ );
310
+
311
+ vm.prank(delegatorAddr);
312
+ vm.expectRevert("IPTokenStaking: Data length over max");
313
+ ipTokenStaking.stakeOnBehalf{ value: stakeAmount }(
314
+ delegatorAddr,
315
+ validatorCmpPubkey,
316
+ IIPTokenStaking.StakingPeriod.FLEXIBLE,
317
+ dataOverMaxLen
318
+ );
319
+ }
320
+
321
+ function testIPTokenStaking_Unstake_Flexible() public {
322
+ uint256 feeAmount = ipTokenStaking.fee();
323
+
324
+ // Network shall only allow the stake owner to withdraw from their stake pubkey
325
+ uint256 stakeAmount = ipTokenStaking.minUnstakeAmount();
326
+ uint256 delegationId = 1337;
327
+ // Use VM setStorage to set the counter to delegationId + 1
328
+ vm.store(
329
+ address(ipTokenStaking),
330
+ bytes32(uint256(3)), // _delegationIdCounter
331
+ bytes32(uint256(1338))
332
+ );
333
+
334
+ vm.deal(delegatorAddr, feeAmount);
335
+ vm.startPrank(delegatorAddr);
336
+ vm.expectEmit(address(ipTokenStaking));
337
+ emit IIPTokenStaking.Withdraw(delegatorAddr, validatorCmpPubkey, stakeAmount, delegationId, delegatorAddr, "");
338
+ ipTokenStaking.unstake{ value: feeAmount }(validatorCmpPubkey, delegationId, stakeAmount, "");
339
+ vm.stopPrank();
340
+
341
+ vm.deal(delegatorAddr, feeAmount);
342
+ vm.startPrank(delegatorAddr);
343
+ vm.expectRevert("IPTokenStaking: Unstake amount under min");
344
+ ipTokenStaking.unstake{ value: feeAmount }(validatorCmpPubkey, delegationId, stakeAmount - 1, "");
345
+ vm.stopPrank();
346
+
347
+ // Smart contract allows non-operators of a stake owner to withdraw from the stake owner’s public key,
348
+ // but this operation will fail in CL. Testing the event here
349
+ address operator = address(0xf398c12A45BC409b6C652e25bb0A3e702492A4AA);
350
+
351
+ vm.deal(operator, feeAmount);
352
+ vm.startPrank(operator);
353
+ vm.expectEmit(address(ipTokenStaking));
354
+ emit IIPTokenStaking.Withdraw(delegatorAddr, validatorCmpPubkey, stakeAmount, delegationId, operator, "");
355
+ ipTokenStaking.unstakeOnBehalf{ value: feeAmount }(
356
+ delegatorAddr,
357
+ validatorCmpPubkey,
358
+ delegationId,
359
+ stakeAmount,
360
+ ""
361
+ );
362
+ vm.stopPrank();
363
+
364
+ // Revert if delegationId is invalid
365
+ delegationId++;
366
+ vm.startPrank(delegatorAddr);
367
+ vm.expectRevert("IPTokenStaking: Invalid delegation id");
368
+ ipTokenStaking.unstake{ value: feeAmount }(validatorCmpPubkey, delegationId + 2, stakeAmount, "");
369
+ vm.stopPrank();
370
+
371
+ // Revert if fee is not paid
372
+ vm.startPrank(delegatorAddr);
373
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
374
+ ipTokenStaking.unstake{ value: feeAmount - 1 }(validatorCmpPubkey, delegationId + 2, stakeAmount, "");
375
+ vm.stopPrank();
376
+
377
+ // Round down to STAKE_ROUNDING if amount is not divisible by STAKE_ROUNDING
378
+ uint256 unroundedAmount = ipTokenStaking.minUnstakeAmount() + ipTokenStaking.STAKE_ROUNDING() + 1 wei;
379
+ uint256 expectedUnstakeAmount = unroundedAmount - 1 wei;
380
+ vm.deal(delegatorAddr, feeAmount);
381
+ vm.prank(delegatorAddr);
382
+ vm.expectEmit(address(ipTokenStaking));
383
+ emit IIPTokenStaking.Withdraw(delegatorAddr, validatorCmpPubkey, expectedUnstakeAmount, delegationId, delegatorAddr, "");
384
+ ipTokenStaking.unstake{ value: feeAmount }(validatorCmpPubkey, delegationId, unroundedAmount, "");
385
+
386
+ unroundedAmount = 1024000000000999999999 wei;
387
+ expectedUnstakeAmount = 1024 ether;
388
+ vm.deal(delegatorAddr, feeAmount);
389
+ vm.prank(delegatorAddr);
390
+ vm.expectEmit(address(ipTokenStaking));
391
+ emit IIPTokenStaking.Withdraw(delegatorAddr, validatorCmpPubkey, expectedUnstakeAmount, delegationId, delegatorAddr, "");
392
+ ipTokenStaking.unstake{ value: feeAmount }(validatorCmpPubkey, delegationId, unroundedAmount, "");
393
+
394
+ // Revert if validatorCmpPubkey is invalid
395
+ bytes memory invalidValidatorCmpPubkey = hex"1234";
396
+ vm.expectRevert("Secp256k1Verifier: Invalid cmp pubkey length");
397
+ ipTokenStaking.unstake{ value: feeAmount }(invalidValidatorCmpPubkey, delegationId, stakeAmount, "");
398
+ vm.expectRevert("Secp256k1Verifier: pubkey not on curve");
399
+ ipTokenStaking.unstake{ value: feeAmount }(wrongValidatorCmpPubkey, delegationId, stakeAmount, "");
400
+ }
401
+
402
+ function testIPTokenStaking_Unstake_data() public {
403
+ uint256 feeAmount = ipTokenStaking.fee();
404
+ uint256 stakeAmount = ipTokenStaking.minUnstakeAmount();
405
+ uint256 delegationId = 1337;
406
+ // Use VM setStorage to set the counter to delegationId + 1
407
+ vm.store(
408
+ address(ipTokenStaking),
409
+ bytes32(uint256(3)), // _delegationIdCounter
410
+ bytes32(uint256(1338))
411
+ );
412
+
413
+ // Network shall not allow a data longer than MAX_DATA_LENGTH
414
+ vm.deal(delegatorAddr, feeAmount);
415
+ vm.prank(delegatorAddr);
416
+ vm.expectRevert("IPTokenStaking: Data length over max");
417
+ ipTokenStaking.unstake{ value: feeAmount }(validatorCmpPubkey, delegationId, stakeAmount, dataOverMaxLen);
418
+
419
+ vm.prank(delegatorAddr);
420
+ vm.expectRevert("IPTokenStaking: Data length over max");
421
+ ipTokenStaking.unstakeOnBehalf{ value: feeAmount }(
422
+ delegatorAddr,
423
+ validatorCmpPubkey,
424
+ delegationId,
425
+ stakeAmount,
426
+ dataOverMaxLen
427
+ );
428
+ }
429
+
430
+ function testIPTokenStaking_Redelegation() public {
431
+ uint256 stakeAmount = ipTokenStaking.minStakeAmount();
432
+ uint256 delegationId = 1;
433
+ uint256 feeAmount = ipTokenStaking.fee();
434
+ // Use VM setStorage to set the counter to delegationId == 1
435
+ vm.store(
436
+ address(ipTokenStaking),
437
+ bytes32(uint256(3)), // _delegationIdCounter
438
+ bytes32(uint256(1))
439
+ );
440
+
441
+ vm.expectEmit(true, true, true, true);
442
+ emit IIPTokenStaking.Redelegate(
443
+ delegatorAddr,
444
+ validatorCmpPubkey,
445
+ otherValidatorCmpPubkey,
446
+ delegationId,
447
+ delegatorAddr,
448
+ stakeAmount
449
+ );
450
+ vm.deal(delegatorAddr, stakeAmount + feeAmount);
451
+ vm.prank(delegatorAddr);
452
+ ipTokenStaking.redelegate{ value: feeAmount }(
453
+ validatorCmpPubkey,
454
+ otherValidatorCmpPubkey,
455
+ delegationId,
456
+ stakeAmount
457
+ );
458
+
459
+ // Redelegating to same validator
460
+ vm.deal(delegatorAddr, stakeAmount + feeAmount);
461
+ vm.prank(delegatorAddr);
462
+ vm.expectRevert("IPTokenStaking: Redelegating to same validator");
463
+ ipTokenStaking.redelegate{ value: feeAmount }(
464
+ validatorCmpPubkey,
465
+ validatorCmpPubkey,
466
+ delegationId,
467
+ stakeAmount
468
+ );
469
+ // Invalid source validator address
470
+ bytes memory invalidValidatorCmpPubkey = hex"1234";
471
+ vm.deal(delegatorAddr, stakeAmount + feeAmount);
472
+ vm.prank(delegatorAddr);
473
+ vm.expectRevert("Secp256k1Verifier: Invalid cmp pubkey length");
474
+ ipTokenStaking.redelegate{ value: feeAmount }(
475
+ invalidValidatorCmpPubkey,
476
+ otherValidatorCmpPubkey,
477
+ delegationId,
478
+ stakeAmount
479
+ );
480
+ vm.expectRevert("Secp256k1Verifier: pubkey not on curve");
481
+ ipTokenStaking.redelegate{ value: feeAmount }(
482
+ wrongValidatorCmpPubkey,
483
+ otherValidatorCmpPubkey,
484
+ delegationId,
485
+ stakeAmount
486
+ );
487
+
488
+ // Invalid destination validator address
489
+ vm.deal(delegatorAddr, stakeAmount + feeAmount);
490
+ vm.prank(delegatorAddr);
491
+ vm.expectRevert("Secp256k1Verifier: Invalid cmp pubkey length");
492
+ ipTokenStaking.redelegate{ value: feeAmount }(
493
+ validatorCmpPubkey,
494
+ invalidValidatorCmpPubkey,
495
+ delegationId,
496
+ stakeAmount
497
+ );
498
+ vm.expectRevert("Secp256k1Verifier: pubkey not on curve");
499
+ ipTokenStaking.redelegate{ value: feeAmount }(
500
+ validatorCmpPubkey,
501
+ wrongValidatorCmpPubkey,
502
+ delegationId,
503
+ stakeAmount
504
+ );
505
+
506
+ // Revert if delegationId is invalid
507
+ delegationId++;
508
+ vm.prank(delegatorAddr);
509
+ vm.expectRevert("IPTokenStaking: Invalid delegation id");
510
+ ipTokenStaking.redelegate{ value: feeAmount }(
511
+ validatorCmpPubkey,
512
+ otherValidatorCmpPubkey,
513
+ delegationId,
514
+ stakeAmount
515
+ );
516
+
517
+ delegationId--;
518
+
519
+ // Revert if fee is not paid
520
+ vm.prank(delegatorAddr);
521
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
522
+ ipTokenStaking.redelegate{ value: feeAmount - 1 }(
523
+ validatorCmpPubkey,
524
+ otherValidatorCmpPubkey,
525
+ delegationId,
526
+ stakeAmount
527
+ );
528
+
529
+ // Stake < Min
530
+ vm.deal(delegatorAddr, stakeAmount);
531
+ vm.prank(delegatorAddr);
532
+ vm.expectRevert("IPTokenStaking: Stake amount under min");
533
+ ipTokenStaking.redelegate{ value: feeAmount }(
534
+ validatorCmpPubkey,
535
+ otherValidatorCmpPubkey,
536
+ delegationId,
537
+ stakeAmount - 1
538
+ );
539
+ }
540
+
541
+ function testIPTokenStaking_RedelegationOnBehalf() public {
542
+ uint256 stakeAmount = ipTokenStaking.minStakeAmount();
543
+ uint256 delegationId = 1;
544
+ uint256 feeAmount = ipTokenStaking.fee();
545
+ // Use VM setStorage to set the counter to delegationId == 1
546
+ vm.store(
547
+ address(ipTokenStaking),
548
+ bytes32(uint256(3)), // _delegationIdCounter
549
+ bytes32(uint256(1))
550
+ );
551
+
552
+ address operator = address(0xf398c12A45BC409b6C652e25bb0A3e702492A4AA);
553
+
554
+ vm.expectEmit(true, true, true, true);
555
+ emit IIPTokenStaking.Redelegate(
556
+ delegatorAddr,
557
+ validatorCmpPubkey,
558
+ otherValidatorCmpPubkey,
559
+ delegationId,
560
+ operator,
561
+ stakeAmount
562
+ );
563
+ vm.deal(operator, stakeAmount + feeAmount);
564
+ vm.prank(operator);
565
+ ipTokenStaking.redelegateOnBehalf{ value: feeAmount }(
566
+ delegatorAddr,
567
+ validatorCmpPubkey,
568
+ otherValidatorCmpPubkey,
569
+ delegationId,
570
+ stakeAmount
571
+ );
572
+
573
+ // Redelegating to same validator
574
+ vm.deal(operator, stakeAmount + feeAmount);
575
+ vm.prank(operator);
576
+ vm.expectRevert("IPTokenStaking: Redelegating to same validator");
577
+ ipTokenStaking.redelegateOnBehalf{ value: feeAmount }(
578
+ delegatorAddr,
579
+ validatorCmpPubkey,
580
+ validatorCmpPubkey,
581
+ delegationId,
582
+ stakeAmount
583
+ );
584
+ bytes memory invalidValidatorCmpPubkey = hex"1234";
585
+ // Invalid source validator
586
+ vm.deal(operator, stakeAmount + feeAmount);
587
+ vm.prank(operator);
588
+ vm.expectRevert("Secp256k1Verifier: Invalid cmp pubkey length");
589
+ ipTokenStaking.redelegateOnBehalf{ value: feeAmount }(
590
+ delegatorAddr,
591
+ invalidValidatorCmpPubkey,
592
+ otherValidatorCmpPubkey,
593
+ delegationId,
594
+ stakeAmount
595
+ );
596
+ vm.expectRevert("Secp256k1Verifier: pubkey not on curve");
597
+ ipTokenStaking.redelegateOnBehalf{ value: feeAmount }(
598
+ delegatorAddr,
599
+ wrongValidatorCmpPubkey,
600
+ otherValidatorCmpPubkey,
601
+ delegationId,
602
+ stakeAmount
603
+ );
604
+
605
+ // Invalid destination validator
606
+ vm.deal(operator, stakeAmount + feeAmount);
607
+ vm.prank(operator);
608
+ vm.expectRevert("Secp256k1Verifier: Invalid cmp pubkey length");
609
+ ipTokenStaking.redelegateOnBehalf{ value: feeAmount }(
610
+ delegatorAddr,
611
+ validatorCmpPubkey,
612
+ invalidValidatorCmpPubkey,
613
+ delegationId,
614
+ stakeAmount
615
+ );
616
+ vm.expectRevert("Secp256k1Verifier: pubkey not on curve");
617
+ ipTokenStaking.redelegateOnBehalf{ value: feeAmount }(
618
+ delegatorAddr,
619
+ validatorCmpPubkey,
620
+ wrongValidatorCmpPubkey,
621
+ delegationId,
622
+ stakeAmount
623
+ );
624
+
625
+ // Revert if delegationId is invalid
626
+ delegationId++;
627
+ vm.prank(operator);
628
+ vm.expectRevert("IPTokenStaking: Invalid delegation id");
629
+ ipTokenStaking.redelegateOnBehalf{ value: feeAmount }(
630
+ delegatorAddr,
631
+ validatorCmpPubkey,
632
+ otherValidatorCmpPubkey,
633
+ delegationId,
634
+ stakeAmount
635
+ );
636
+ delegationId--;
637
+
638
+ // Revert if fee is not paid
639
+ vm.prank(operator);
640
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
641
+ ipTokenStaking.redelegateOnBehalf{ value: feeAmount - 1 }(
642
+ delegatorAddr,
643
+ validatorCmpPubkey,
644
+ otherValidatorCmpPubkey,
645
+ delegationId,
646
+ stakeAmount
647
+ );
648
+
649
+ // Stake < Min
650
+ vm.deal(delegatorAddr, stakeAmount);
651
+ vm.prank(delegatorAddr);
652
+ vm.expectRevert("IPTokenStaking: Stake amount under min");
653
+ ipTokenStaking.redelegateOnBehalf{ value: feeAmount }(
654
+ delegatorAddr,
655
+ validatorCmpPubkey,
656
+ otherValidatorCmpPubkey,
657
+ delegationId,
658
+ stakeAmount - 1
659
+ );
660
+ }
661
+
662
+ function testIPTokenStaking_SetWithdrawalAddress() public {
663
+ uint256 feeAmount = ipTokenStaking.fee();
664
+ // Network shall allow the delegators to set their withdrawal address
665
+ vm.expectEmit(address(ipTokenStaking));
666
+ emit IIPTokenStaking.SetWithdrawalAddress(
667
+ delegatorAddr,
668
+ 0x0000000000000000000000000000000000000000000000000000000000000b0b
669
+ );
670
+ vm.prank(delegatorAddr);
671
+ ipTokenStaking.setWithdrawalAddress{ value: feeAmount }(address(0xb0b));
672
+
673
+ // Network shall not allow anyone to set withdrawal address with insufficient fee
674
+ vm.prank(delegatorAddr);
675
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
676
+ ipTokenStaking.setWithdrawalAddress{ value: feeAmount - 1 }(address(0xb0b));
677
+ }
678
+
679
+ function testIPTokenStaking_SetRewardsAddress() public {
680
+ uint256 feeAmount = ipTokenStaking.fee();
681
+ // Network shall allow the delegators to set their withdrawal address
682
+ vm.expectEmit(address(ipTokenStaking));
683
+ emit IIPTokenStaking.SetRewardAddress(
684
+ delegatorAddr,
685
+ 0x0000000000000000000000000000000000000000000000000000000000000b0b
686
+ );
687
+ vm.prank(delegatorAddr);
688
+ ipTokenStaking.setRewardsAddress{ value: feeAmount }(address(0xb0b));
689
+
690
+ // Network shall not allow anyone to set withdrawal address with insufficient fee
691
+ vm.prank(delegatorAddr);
692
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
693
+ ipTokenStaking.setRewardsAddress{ value: feeAmount - 1 }(address(0xb0b));
694
+ }
695
+
696
+ function testIPTokenStaking_updateValidatorCommission() public {
697
+ uint32 commissionRate = 100_000_000;
698
+ uint256 feeAmount = ipTokenStaking.fee();
699
+ vm.deal(validatorAddr, feeAmount * 10);
700
+ vm.prank(validatorAddr);
701
+ vm.expectEmit(address(ipTokenStaking));
702
+ emit IIPTokenStaking.UpdateValidatorCommission(validatorCmpPubkey, commissionRate);
703
+ ipTokenStaking.updateValidatorCommission{ value: feeAmount }(validatorCmpPubkey, commissionRate);
704
+
705
+ // Network shall not allow anyone to update the commission rate of a validator if it is less than minCommissionRate.
706
+ vm.prank(validatorAddr);
707
+ vm.expectRevert("IPTokenStaking: Commission rate under min");
708
+ ipTokenStaking.updateValidatorCommission{ value: feeAmount }(validatorCmpPubkey, 0);
709
+
710
+ // Network shall not allow anyone to update the commission rate of a validator if the fee is not paid.
711
+ vm.prank(validatorAddr);
712
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
713
+ ipTokenStaking.updateValidatorCommission{ value: feeAmount - 1 }(validatorCmpPubkey, commissionRate);
714
+ }
715
+
716
+ function testIPTokenStaking_setOperator() public {
717
+ // Network shall not allow anyone to add operators for a delegator if the fee is not paid.
718
+ address operator = address(0x420);
719
+ vm.prank(delegatorAddr);
720
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
721
+ ipTokenStaking.setOperator{ value: 0 }(operator);
722
+
723
+ // Network shall not allow anyone to add operators for a delegator if the fee is wrong
724
+ uint256 feeAmount = 1 ether;
725
+ vm.deal(delegatorAddr, feeAmount + 1);
726
+ vm.prank(delegatorAddr);
727
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
728
+ ipTokenStaking.setOperator{ value: feeAmount - 1 }(operator);
729
+ vm.prank(delegatorAddr);
730
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
731
+ ipTokenStaking.setOperator{ value: feeAmount + 1 }(operator);
732
+
733
+ // Network should allow delegators to add operators for themselves
734
+ feeAmount = 1 ether;
735
+ vm.deal(delegatorAddr, feeAmount);
736
+ vm.prank(delegatorAddr);
737
+ vm.expectEmit(address(ipTokenStaking));
738
+ emit IIPTokenStaking.SetOperator(delegatorAddr, operator);
739
+ ipTokenStaking.setOperator{ value: feeAmount }(operator);
740
+ }
741
+
742
+ function testIPTokenStaking_unsetOperator() public {
743
+ uint256 feeAmount = ipTokenStaking.fee();
744
+
745
+ // Network shall allow delegators to remove their operators
746
+ vm.deal(delegatorAddr, feeAmount);
747
+ vm.prank(delegatorAddr);
748
+ vm.expectEmit(address(ipTokenStaking));
749
+ emit IIPTokenStaking.UnsetOperator(delegatorAddr);
750
+ ipTokenStaking.unsetOperator{ value: feeAmount }();
751
+
752
+ // Revert if fee is not paid
753
+ vm.deal(delegatorAddr, feeAmount);
754
+ vm.prank(delegatorAddr);
755
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
756
+ ipTokenStaking.unsetOperator{ value: feeAmount - 1 }();
757
+ }
758
+
759
+ function testIPTokenStaking_setMinStakeAmount() public {
760
+ // Set amount that will be rounded down to 1 ether
761
+ performTimelocked(
762
+ address(ipTokenStaking),
763
+ abi.encodeWithSelector(IPTokenStaking.setMinStakeAmount.selector, 1 ether + 5 wei)
764
+ );
765
+ assertEq(ipTokenStaking.minStakeAmount(), 1 ether);
766
+
767
+ // Set amount that will not be rounded
768
+ schedule(address(ipTokenStaking), abi.encodeWithSelector(IPTokenStaking.setMinStakeAmount.selector, 1 ether));
769
+ waitForTimelock();
770
+ vm.expectEmit(address(ipTokenStaking));
771
+ emit IIPTokenStaking.MinStakeAmountSet(1 ether);
772
+ executeTimelocked(
773
+ address(ipTokenStaking),
774
+ abi.encodeWithSelector(IPTokenStaking.setMinStakeAmount.selector, 1 ether)
775
+ );
776
+ assertEq(ipTokenStaking.minStakeAmount(), 1 ether);
777
+
778
+ // Set 0
779
+ expectRevertTimelocked(
780
+ address(ipTokenStaking),
781
+ abi.encodeWithSelector(IPTokenStaking.setMinStakeAmount.selector, 0 ether),
782
+ "IPTokenStaking: Zero min stake amount"
783
+ );
784
+
785
+ // Set amount that will be rounded down to 0
786
+ expectRevertTimelocked(
787
+ address(ipTokenStaking),
788
+ abi.encodeWithSelector(IPTokenStaking.setMinStakeAmount.selector, 5 wei),
789
+ "IPTokenStaking: Zero min stake amount"
790
+ );
791
+
792
+ // Set using a non-owner address
793
+ vm.prank(delegatorAddr);
794
+ vm.expectRevert(
795
+ abi.encodeWithSelector(
796
+ Ownable.OwnableUnauthorizedAccount.selector,
797
+ address(0xf398C12A45Bc409b6C652E25bb0a3e702492A4ab)
798
+ )
799
+ );
800
+ ipTokenStaking.setMinStakeAmount(1 ether);
801
+ }
802
+
803
+ function testIPTokenStaking_setMinUnstakeAmount() public {
804
+ // Set amount that will be rounded down to 1 ether
805
+ performTimelocked(
806
+ address(ipTokenStaking),
807
+ abi.encodeWithSelector(IPTokenStaking.setMinUnstakeAmount.selector, 1 ether + 5 wei)
808
+ );
809
+ assertEq(ipTokenStaking.minUnstakeAmount(), 1 ether);
810
+
811
+ // Set amount that will not be rounded
812
+ schedule(address(ipTokenStaking), abi.encodeWithSelector(IPTokenStaking.setMinUnstakeAmount.selector, 1 ether));
813
+ waitForTimelock();
814
+ vm.expectEmit(address(ipTokenStaking));
815
+ emit IIPTokenStaking.MinUnstakeAmountSet(1 ether);
816
+ executeTimelocked(
817
+ address(ipTokenStaking),
818
+ abi.encodeWithSelector(IPTokenStaking.setMinUnstakeAmount.selector, 1 ether)
819
+ );
820
+ assertEq(ipTokenStaking.minUnstakeAmount(), 1 ether);
821
+
822
+ // Set 0
823
+ vm.prank(admin);
824
+ expectRevertTimelocked(
825
+ address(ipTokenStaking),
826
+ abi.encodeWithSelector(IPTokenStaking.setMinUnstakeAmount.selector, 0 ether),
827
+ "IPTokenStaking: Zero min unstake amount"
828
+ );
829
+
830
+ // Set amount that will be rounded down to 0 ether
831
+ vm.prank(admin);
832
+ expectRevertTimelocked(
833
+ address(ipTokenStaking),
834
+ abi.encodeWithSelector(IPTokenStaking.setMinUnstakeAmount.selector, 5 wei),
835
+ "IPTokenStaking: Zero min unstake amount"
836
+ );
837
+
838
+ // Set using a non-owner address
839
+ vm.prank(delegatorAddr);
840
+ vm.expectRevert();
841
+ ipTokenStaking.setMinUnstakeAmount(1 ether);
842
+ }
843
+
844
+ function testIPTokenStaking_Unjail() public {
845
+ uint256 feeAmount = 1 ether;
846
+ vm.deal(validatorAddr, feeAmount);
847
+
848
+ // Network shall not allow anyone to unjail a validator if the fee is not paid.
849
+ vm.prank(validatorAddr);
850
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
851
+ ipTokenStaking.unjail(validatorCmpPubkey, "");
852
+
853
+ // Network shall not allow anyone to unjail a validator if the fee is not sufficient.
854
+ feeAmount = 0.9 ether;
855
+ vm.deal(validatorAddr, feeAmount);
856
+ vm.prank(validatorAddr);
857
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
858
+ ipTokenStaking.unjail{ value: feeAmount }(validatorCmpPubkey, "");
859
+
860
+ // Network shall allow anyone to unjail a validator if the fee is paid.
861
+ feeAmount = 1 ether;
862
+ vm.deal(validatorAddr, feeAmount);
863
+ vm.prank(validatorAddr);
864
+ vm.expectEmit(address(ipTokenStaking));
865
+ emit IIPTokenStaking.Unjail(validatorAddr, validatorCmpPubkey, "");
866
+ ipTokenStaking.unjail{ value: feeAmount }(validatorCmpPubkey, "");
867
+
868
+ // Network shall not allow anyone to unjail a validator if the fee is over.
869
+ feeAmount = 1.1 ether;
870
+ vm.deal(validatorAddr, feeAmount);
871
+ vm.prank(validatorAddr);
872
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
873
+ ipTokenStaking.unjail{ value: feeAmount }(validatorCmpPubkey, "");
874
+
875
+ // Network shall not allow anyone to unjail with invalid pubkey
876
+ feeAmount = 1 ether;
877
+ vm.deal(validatorAddr, feeAmount);
878
+ vm.prank(validatorAddr);
879
+ vm.expectRevert("Secp256k1Verifier: Invalid cmp pubkey length");
880
+ ipTokenStaking.unjail{ value: feeAmount }(hex"", "");
881
+ vm.expectRevert("Secp256k1Verifier: pubkey not on curve");
882
+ ipTokenStaking.unjail{ value: feeAmount }(wrongValidatorCmpPubkey, "");
883
+ }
884
+
885
+ function testIPTokenStaking_UnjailOnBehalf() public {
886
+ uint256 feeAmount = 1 ether;
887
+ vm.deal(delegatorAddr, feeAmount);
888
+
889
+ // Network shall not allow anyone to unjail a validator if the fee is not paid.
890
+ vm.prank(delegatorAddr);
891
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
892
+ ipTokenStaking.unjailOnBehalf(validatorCmpPubkey, "");
893
+
894
+ // Network shall not allow anyone to unjail a validator if the fee is not sufficient.
895
+ feeAmount = 0.9 ether;
896
+ vm.deal(delegatorAddr, feeAmount);
897
+ vm.prank(delegatorAddr);
898
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
899
+ ipTokenStaking.unjailOnBehalf{ value: feeAmount }(validatorCmpPubkey, "");
900
+
901
+ // Network shall allow anyone to unjail a validator if the fee is paid.
902
+ feeAmount = 1 ether;
903
+ vm.deal(delegatorAddr, feeAmount);
904
+ vm.prank(delegatorAddr);
905
+ vm.expectEmit(address(ipTokenStaking));
906
+ emit IIPTokenStaking.Unjail(delegatorAddr, validatorCmpPubkey, "");
907
+ ipTokenStaking.unjailOnBehalf{ value: feeAmount }(validatorCmpPubkey, "");
908
+
909
+ // Network shall not allow anyone to unjail a validator if the fee is over.
910
+ feeAmount = 1.1 ether;
911
+ vm.deal(delegatorAddr, feeAmount);
912
+ vm.prank(delegatorAddr);
913
+ vm.expectRevert("IPTokenStaking: Invalid fee amount");
914
+ ipTokenStaking.unjailOnBehalf{ value: feeAmount }(validatorCmpPubkey, "");
915
+
916
+ // Network shall not allow anyone to unjail with invalid pubkey
917
+ feeAmount = 1 ether;
918
+ vm.deal(delegatorAddr, feeAmount);
919
+ vm.prank(delegatorAddr);
920
+ vm.expectRevert("Secp256k1Verifier: Invalid cmp pubkey length");
921
+ ipTokenStaking.unjailOnBehalf{ value: feeAmount }(hex"", "");
922
+ vm.expectRevert("Secp256k1Verifier: pubkey not on curve");
923
+ ipTokenStaking.unjailOnBehalf{ value: feeAmount }(wrongValidatorCmpPubkey, "");
924
+ }
925
+
926
+ function testIPTokenStaking_Unjail_data() public {
927
+ uint256 feeAmount = ipTokenStaking.DEFAULT_MIN_FEE();
928
+ vm.deal(validatorAddr, feeAmount);
929
+ vm.prank(validatorAddr);
930
+ vm.expectRevert("IPTokenStaking: Data length over max");
931
+ ipTokenStaking.unjail{ value: feeAmount }(validatorCmpPubkey, dataOverMaxLen);
932
+
933
+ vm.prank(delegatorAddr);
934
+ vm.expectRevert("IPTokenStaking: Data length over max");
935
+ ipTokenStaking.unjailOnBehalf{ value: feeAmount }(validatorCmpPubkey, dataOverMaxLen);
936
+ }
937
+
938
+ function testIPTokenStaking_SetFee() public {
939
+ // Network shall allow the owner to set the fee charged for adding to CL storage.
940
+ uint256 newFee = 2 ether;
941
+ schedule(address(ipTokenStaking), abi.encodeWithSelector(IPTokenStaking.setFee.selector, newFee));
942
+ waitForTimelock();
943
+ vm.expectEmit(address(ipTokenStaking));
944
+ emit IIPTokenStaking.FeeSet(newFee);
945
+ executeTimelocked(address(ipTokenStaking), abi.encodeWithSelector(IPTokenStaking.setFee.selector, newFee));
946
+ assertEq(ipTokenStaking.fee(), newFee);
947
+
948
+ // Network shall not allow non-owner to set the fee charged for adding to CL storage.
949
+ vm.prank(address(0xf398c12A45BC409b6C652e25bb0A3e702492A4AA));
950
+ vm.expectRevert();
951
+ ipTokenStaking.setFee(1 ether);
952
+ assertEq(ipTokenStaking.fee(), newFee);
953
+
954
+ // Network shall not allow fees < default
955
+ expectRevertTimelocked(
956
+ address(ipTokenStaking),
957
+ abi.encodeWithSelector(IPTokenStaking.setFee.selector, 1),
958
+ "IPTokenStaking: Invalid min fee"
959
+ );
960
+ }
961
+
962
+ function testIPTokenStaking_fromSmartContract() public {
963
+ // Network shall allow anyone to create a new validator by staking validator’s own tokens (self-delegation)
964
+ uint256 stakeAmount = ipTokenStaking.minStakeAmount();
965
+ vm.deal(validatorAddr, stakeAmount);
966
+ vm.prank(validatorAddr);
967
+ ipTokenStaking.createValidator{ value: stakeAmount }({
968
+ validatorCmpPubkey: validatorCmpPubkey,
969
+ moniker: "delegator's validator",
970
+ commissionRate: 1000,
971
+ maxCommissionRate: 5000,
972
+ maxCommissionChangeRate: 100,
973
+ supportsUnlocked: true,
974
+ data: abi.encode("data")
975
+ });
976
+ uint256 expectedDelegationId = 0;
977
+
978
+ // Deploy Staker contract
979
+ Staker staker = new Staker(address(ipTokenStaking));
980
+
981
+ // Test staking
982
+ vm.deal(address(staker), 10_000_000_000 ether);
983
+ vm.expectEmit(address(ipTokenStaking));
984
+ emit IIPTokenStaking.Deposit(
985
+ address(staker),
986
+ validatorCmpPubkey,
987
+ stakeAmount,
988
+ uint32(uint8(IIPTokenStaking.StakingPeriod.FLEXIBLE)),
989
+ expectedDelegationId,
990
+ address(staker),
991
+ ""
992
+ );
993
+ staker.stake{ value: stakeAmount }(validatorCmpPubkey);
994
+
995
+ vm.deal(address(staker), 1 ether); // fee
996
+ // Test redelegating
997
+ vm.expectEmit(address(ipTokenStaking));
998
+ emit IIPTokenStaking.Redelegate(
999
+ address(staker),
1000
+ validatorCmpPubkey,
1001
+ otherValidatorCmpPubkey,
1002
+ expectedDelegationId,
1003
+ address(staker),
1004
+ stakeAmount
1005
+ );
1006
+ staker.redelegate(validatorCmpPubkey, otherValidatorCmpPubkey, stakeAmount);
1007
+
1008
+ // Test unstaking
1009
+ vm.deal(address(staker), 1 ether); // fee
1010
+
1011
+ vm.expectEmit(address(ipTokenStaking));
1012
+ emit IIPTokenStaking.Withdraw(
1013
+ address(staker),
1014
+ validatorCmpPubkey,
1015
+ stakeAmount,
1016
+ expectedDelegationId,
1017
+ address(staker),
1018
+ ""
1019
+ );
1020
+ staker.unstake(validatorCmpPubkey, stakeAmount);
1021
+ }
1022
+ }