@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.
- package/README.md +65 -0
- package/index.js +43 -0
- package/package.json +51 -0
- package/script/GenerateAlloc.s.sol +448 -0
- package/script/upgrades/DeployNewIPTokenStaking_V1_0_1.s.sol +42 -0
- package/script/utils/ChainIds.sol +12 -0
- package/script/utils/EIP1967Helper.sol +37 -0
- package/script/utils/InitializableHelper.sol +66 -0
- package/src/deploy/Create3.sol +62 -0
- package/src/interfaces/IIPTokenStaking.sol +303 -0
- package/src/interfaces/IUBIPool.sol +37 -0
- package/src/interfaces/IUpgradeEntrypoint.sol +33 -0
- package/src/libraries/Predeploys.sol +50 -0
- package/src/protocol/IPTokenStaking.sol +509 -0
- package/src/protocol/Secp256k1Verifier.sol +109 -0
- package/src/protocol/UBIPool.sol +98 -0
- package/src/protocol/UpgradeEntrypoint.sol +40 -0
- package/src/token/WIP.sol +90 -0
- package/test/data/ValidatorData.sol +122 -0
- package/test/deploy/Create3.t.sol +63 -0
- package/test/erc6551/Erc6551Registry.t.sol +33 -0
- package/test/secp256k/Secp256k1PubKeyVerifier.t.sol +50 -0
- package/test/stake/IPTokenStaking.t.sol +1022 -0
- package/test/timelock/Timelock.t.sol +136 -0
- package/test/token/WIP.t.sol +198 -0
- package/test/ubipool/UBIPool.t.sol +239 -0
- package/test/upgrade-entrypoint/UpgradeEntryPoint.t.sol +37 -0
- package/test/upgrades/PredeployUpgrades.t.sol +219 -0
- package/test/utils/Test.sol +170 -0
@@ -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
|
+
}
|