@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,136 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity 0.8.23;
3
+
4
+ import { Test } from "../utils/Test.sol";
5
+ import { IPTokenStaking } from "../../src/protocol/IPTokenStaking.sol";
6
+ import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol";
7
+
8
+ contract TimelockTest is Test {
9
+ function setUp() public override {
10
+ super.setUp();
11
+ }
12
+
13
+ function testCancelBeforeExecute() public {
14
+ // Prepare a sample operation
15
+ address target = address(ipTokenStaking);
16
+ uint256 value = 0;
17
+ bytes memory data = abi.encodeWithSelector(IPTokenStaking.setFee.selector, 2 ether);
18
+ bytes32 predecessor = bytes32(0);
19
+ bytes32 salt = keccak256("TEST_SALT");
20
+ uint256 delay = timelock.getMinDelay();
21
+
22
+ // Schedule the operation
23
+ vm.prank(admin);
24
+ timelock.schedule(target, value, data, predecessor, salt, delay);
25
+
26
+ // Ensure the operation is pending
27
+ bytes32 operationId = timelock.hashOperation(target, value, data, predecessor, salt);
28
+ assertTrue(timelock.isOperationPending(operationId));
29
+
30
+ // Cancel the operation
31
+ vm.prank(admin);
32
+ timelock.cancel(operationId);
33
+
34
+ // Ensure the operation is no longer pending
35
+ assertFalse(timelock.isOperationPending(operationId));
36
+
37
+ // Wait for the delay to pass
38
+ vm.warp(block.timestamp + delay + 1);
39
+
40
+ // Try to execute the cancelled operation
41
+ vm.prank(executor);
42
+ vm.expectRevert(
43
+ abi.encodeWithSelector(
44
+ TimelockController.TimelockUnexpectedOperationState.selector,
45
+ operationId,
46
+ bytes32(1 << uint8(TimelockController.OperationState.Ready))
47
+ )
48
+ );
49
+
50
+ timelock.execute(target, value, data, predecessor, salt);
51
+
52
+ // Verify that the fee wasn't changed
53
+ assertEq(ipTokenStaking.fee(), 1 ether);
54
+ }
55
+
56
+ function testExecuteSequenceWithPredecessors() public {
57
+ // Prepare sample operations
58
+ address target = address(ipTokenStaking);
59
+ uint256 value = 0;
60
+ bytes memory data1 = abi.encodeWithSelector(IPTokenStaking.setFee.selector, 2 ether);
61
+ bytes memory data2 = abi.encodeWithSelector(IPTokenStaking.setFee.selector, 3 ether);
62
+ bytes memory data3 = abi.encodeWithSelector(IPTokenStaking.setFee.selector, 4 ether);
63
+ bytes32 salt1 = keccak256("SALT_1");
64
+ bytes32 salt2 = keccak256("SALT_2");
65
+ bytes32 salt3 = keccak256("SALT_3");
66
+ uint256 delay = timelock.getMinDelay();
67
+
68
+ // Schedule the first operation
69
+ vm.prank(admin);
70
+ timelock.schedule(target, value, data1, bytes32(0), salt1, delay);
71
+ bytes32 id1 = timelock.hashOperation(target, value, data1, bytes32(0), salt1);
72
+
73
+ // Schedule the second operation with the first as predecessor
74
+ vm.prank(admin);
75
+ timelock.schedule(target, value, data2, id1, salt2, delay);
76
+ bytes32 id2 = timelock.hashOperation(target, value, data2, id1, salt2);
77
+
78
+ // Schedule the third operation with the second as predecessor
79
+ vm.prank(admin);
80
+ timelock.schedule(target, value, data3, id2, salt3, delay);
81
+
82
+ // Wait for the delay to pass
83
+ vm.warp(block.timestamp + delay + 1);
84
+
85
+ // Execute the first operation
86
+ vm.prank(executor);
87
+ timelock.execute(target, value, data1, bytes32(0), salt1);
88
+ assertEq(ipTokenStaking.fee(), 2 ether);
89
+
90
+ // Try to execute the third operation (should fail due to unexecuted predecessor)
91
+ vm.prank(executor);
92
+ vm.expectRevert(abi.encodeWithSelector(TimelockController.TimelockUnexecutedPredecessor.selector, id2));
93
+ timelock.execute(target, value, data3, id2, salt3);
94
+
95
+ // Execute the second operation
96
+ vm.prank(executor);
97
+ timelock.execute(target, value, data2, id1, salt2);
98
+ assertEq(ipTokenStaking.fee(), 3 ether);
99
+
100
+ // Finally, execute the third operation
101
+ vm.prank(executor);
102
+ timelock.execute(target, value, data3, id2, salt3);
103
+ assertEq(ipTokenStaking.fee(), 4 ether);
104
+ }
105
+
106
+ function testTimelockMinDelay() public {
107
+ address target = address(ipTokenStaking);
108
+ uint256 value = 0;
109
+ bytes memory data1 = abi.encodeWithSelector(IPTokenStaking.setFee.selector, 2 ether);
110
+ bytes32 salt1 = keccak256("SALT_1");
111
+ uint256 minDelay = timelock.getMinDelay();
112
+
113
+ // Scheduling under the min delay should fail
114
+ vm.expectRevert(
115
+ abi.encodeWithSelector(TimelockController.TimelockInsufficientDelay.selector, minDelay - 1, minDelay)
116
+ );
117
+ vm.prank(admin);
118
+ timelock.schedule(target, value, data1, bytes32(0), salt1, minDelay - 1);
119
+
120
+ // Scheduling over the min delay should succeed
121
+ vm.prank(admin);
122
+ timelock.schedule(target, value, data1, bytes32(0), salt1, minDelay + 1 minutes);
123
+
124
+ // Should revert if we try to execute before scheduled time
125
+ vm.warp(block.timestamp + minDelay);
126
+ vm.expectRevert();
127
+ vm.prank(executor);
128
+ timelock.execute(target, value, data1, bytes32(0), salt1);
129
+
130
+ // Should succeed if we wait for the delay to pass
131
+ vm.warp(block.timestamp + minDelay + 1 minutes);
132
+ vm.prank(executor);
133
+ timelock.execute(target, value, data1, bytes32(0), salt1);
134
+ assertEq(ipTokenStaking.fee(), 2 ether);
135
+ }
136
+ }
@@ -0,0 +1,198 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.23;
3
+
4
+ import { Test } from "../utils/Test.sol";
5
+ import { WIP } from "../../src/token/WIP.sol";
6
+
7
+ contract ContractWithoutReceive {}
8
+
9
+ contract WIPTest is Test {
10
+ function testMetadata() public view {
11
+ assertEq(wip.name(), "Wrapped IP");
12
+ assertEq(wip.symbol(), "WIP");
13
+ assertEq(wip.decimals(), 18);
14
+ }
15
+
16
+ function testFallbackDeposit() public {
17
+ assertEq(wip.balanceOf(address(this)), 0);
18
+ assertEq(wip.totalSupply(), 0);
19
+
20
+ (bool success, ) = address(wip).call{ value: 1 ether }("");
21
+ assertTrue(success);
22
+
23
+ assertEq(wip.balanceOf(address(this)), 1 ether);
24
+ assertEq(wip.totalSupply(), 1 ether);
25
+ }
26
+
27
+ function testDeposit() public {
28
+ assertEq(wip.balanceOf(address(this)), 0);
29
+ assertEq(wip.totalSupply(), 0);
30
+
31
+ wip.deposit{ value: 1 ether }();
32
+
33
+ assertEq(wip.balanceOf(address(this)), 1 ether);
34
+ assertEq(wip.totalSupply(), 1 ether);
35
+ }
36
+
37
+ function testWithdraw() public {
38
+ uint256 startingBalance = address(this).balance;
39
+
40
+ wip.deposit{ value: 1 ether }();
41
+
42
+ wip.withdraw(1 ether);
43
+
44
+ uint256 balanceAfterWithdraw = address(this).balance;
45
+
46
+ assertEq(balanceAfterWithdraw, startingBalance);
47
+ assertEq(wip.balanceOf(address(this)), 0);
48
+ assertEq(wip.totalSupply(), 0);
49
+ }
50
+
51
+ function testPartialWithdraw() public {
52
+ wip.deposit{ value: 1 ether }();
53
+
54
+ uint256 balanceBeforeWithdraw = address(this).balance;
55
+
56
+ wip.withdraw(0.5 ether);
57
+
58
+ uint256 balanceAfterWithdraw = address(this).balance;
59
+
60
+ assertEq(balanceAfterWithdraw, balanceBeforeWithdraw + 0.5 ether);
61
+ assertEq(wip.balanceOf(address(this)), 0.5 ether);
62
+ assertEq(wip.totalSupply(), 0.5 ether);
63
+ }
64
+
65
+ function testWithdrawToContractWithoutReceiveReverts() public {
66
+ address owner = address(new ContractWithoutReceive());
67
+
68
+ vm.deal(owner, 1 ether);
69
+
70
+ vm.prank(owner);
71
+ wip.deposit{ value: 1 ether }();
72
+
73
+ assertEq(wip.balanceOf(owner), 1 ether);
74
+
75
+ vm.expectRevert(WIP.IPTransferFailed.selector);
76
+ vm.prank(owner);
77
+ wip.withdraw(1 ether);
78
+ }
79
+
80
+ function testFallbackDeposit(uint256 amount) public {
81
+ amount = _bound(amount, 0, address(this).balance);
82
+
83
+ assertEq(wip.balanceOf(address(this)), 0);
84
+ assertEq(wip.totalSupply(), 0);
85
+
86
+ (bool success, ) = address(wip).call{ value: amount }("");
87
+ assertTrue(success);
88
+
89
+ assertEq(wip.balanceOf(address(this)), amount);
90
+ assertEq(wip.totalSupply(), amount);
91
+ }
92
+
93
+ function testDeposit(uint256 amount) public {
94
+ amount = _bound(amount, 0, address(this).balance);
95
+
96
+ assertEq(wip.balanceOf(address(this)), 0);
97
+ assertEq(wip.totalSupply(), 0);
98
+
99
+ wip.deposit{ value: amount }();
100
+
101
+ assertEq(wip.balanceOf(address(this)), amount);
102
+ assertEq(wip.totalSupply(), amount);
103
+ }
104
+
105
+ function testWithdraw(uint256 depositAmount, uint256 withdrawAmount) public {
106
+ depositAmount = _bound(depositAmount, 0, address(this).balance);
107
+ withdrawAmount = _bound(withdrawAmount, 0, depositAmount);
108
+
109
+ wip.deposit{ value: depositAmount }();
110
+
111
+ uint256 balanceBeforeWithdraw = address(this).balance;
112
+
113
+ wip.withdraw(withdrawAmount);
114
+
115
+ uint256 balanceAfterWithdraw = address(this).balance;
116
+
117
+ assertEq(balanceAfterWithdraw, balanceBeforeWithdraw + withdrawAmount);
118
+ assertEq(wip.balanceOf(address(this)), depositAmount - withdrawAmount);
119
+ assertEq(wip.totalSupply(), depositAmount - withdrawAmount);
120
+ }
121
+
122
+ function testTransferToZeroAddressReverts() public {
123
+ address owner = address(0x123);
124
+
125
+ vm.deal(owner, 1 ether);
126
+
127
+ vm.prank(owner);
128
+ wip.deposit{ value: 1 ether }();
129
+
130
+ assertEq(wip.balanceOf(owner), 1 ether);
131
+
132
+ vm.expectRevert(abi.encodeWithSelector(WIP.ERC20InvalidReceiver.selector, address(0)));
133
+ vm.prank(owner);
134
+ wip.transfer(address(0), 1 ether);
135
+ }
136
+
137
+ function testTransferToWIPContractReverts() public {
138
+ address owner = address(0x123);
139
+
140
+ vm.deal(owner, 1 ether);
141
+
142
+ vm.prank(owner);
143
+ wip.deposit{ value: 1 ether }();
144
+
145
+ assertEq(wip.balanceOf(owner), 1 ether);
146
+
147
+ vm.expectRevert(abi.encodeWithSelector(WIP.ERC20InvalidReceiver.selector, address(wip)));
148
+ vm.prank(owner);
149
+ wip.transfer(address(wip), 1 ether);
150
+ }
151
+
152
+ function testTransferFromReceiverIsWIPContractReverts() public {
153
+ address owner = address(0x123);
154
+
155
+ vm.deal(owner, 1 ether);
156
+
157
+ vm.prank(owner);
158
+ wip.deposit{ value: 1 ether }();
159
+
160
+ assertEq(wip.balanceOf(owner), 1 ether);
161
+
162
+ vm.expectRevert(abi.encodeWithSelector(WIP.ERC20InvalidReceiver.selector, address(wip)));
163
+ vm.prank(owner);
164
+ wip.transferFrom(owner, address(wip), 1 ether);
165
+ }
166
+
167
+ function testTransferFromReceiverIsZeroAddressReverts() public {
168
+ address owner = address(0x123);
169
+
170
+ vm.deal(owner, 1 ether);
171
+
172
+ vm.prank(owner);
173
+ wip.deposit{ value: 1 ether }();
174
+
175
+ assertEq(wip.balanceOf(owner), 1 ether);
176
+
177
+ vm.expectRevert(abi.encodeWithSelector(WIP.ERC20InvalidReceiver.selector, address(0)));
178
+ vm.prank(owner);
179
+ wip.transferFrom(owner, address(0), 1 ether);
180
+ }
181
+
182
+ function testApprovalToSelfReverts() public {
183
+ address owner = address(0x123);
184
+
185
+ vm.deal(owner, 1 ether);
186
+
187
+ vm.prank(owner);
188
+ wip.deposit{ value: 1 ether }();
189
+
190
+ assertEq(wip.balanceOf(owner), 1 ether);
191
+
192
+ vm.expectRevert(abi.encodeWithSelector(WIP.ERC20InvalidSpender.selector, owner));
193
+ vm.prank(owner);
194
+ wip.approve(owner, 1 ether);
195
+ }
196
+
197
+ receive() external payable {}
198
+ }
@@ -0,0 +1,239 @@
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
+ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
8
+
9
+ import { IUBIPool } from "../../src/interfaces/IUBIPool.sol";
10
+ import { Test } from "../utils/Test.sol";
11
+ import { ValidatorData } from "../data/ValidatorData.sol";
12
+
13
+ contract UBIPoolTest is Test, ValidatorData {
14
+ function setUp() public virtual override {
15
+ super.setUp();
16
+ }
17
+
18
+ function test_setUBIPercentage() public {
19
+ // Fail if not protocol admin
20
+ vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, address(this)));
21
+ ubiPool.setUBIPercentage(12_00);
22
+
23
+ // Fail if percentage too high
24
+ uint32 tooMuch = ubiPool.MAX_UBI_PERCENTAGE() + 1;
25
+ expectRevertTimelocked(
26
+ address(ubiPool),
27
+ abi.encodeWithSelector(IUBIPool.setUBIPercentage.selector, tooMuch),
28
+ "UBIPool: percentage too high"
29
+ );
30
+
31
+ // Set percentage
32
+ schedule(address(ubiPool), abi.encodeWithSelector(IUBIPool.setUBIPercentage.selector, 12_00));
33
+ waitForTimelock();
34
+ vm.expectEmit(true, true, true, true);
35
+ emit IUBIPool.UBIPercentageSet(12_00);
36
+ executeTimelocked(address(ubiPool), abi.encodeWithSelector(IUBIPool.setUBIPercentage.selector, 12_00));
37
+ }
38
+
39
+ function test_setUBIDistribution_claimUBI() public {
40
+ uint256[] memory amounts = new uint256[](validators.length);
41
+ bytes[] memory validatorCmpPubKeys = new bytes[](validators.length);
42
+ uint256 totalAmount = 0;
43
+ for (uint256 i = 0; i < validators.length; i++) {
44
+ amounts[i] = 100 ether + i * 10 ether;
45
+ validatorCmpPubKeys[i] = validators[i].compressedHex;
46
+ totalAmount += amounts[i];
47
+ vm.prank(validators[i].evmAddress);
48
+ vm.expectRevert("UBIPool: no UBI to claim");
49
+ ubiPool.claimUBI(1, validatorCmpPubKeys[i]);
50
+ }
51
+ vm.deal(address(ubiPool), totalAmount);
52
+ performTimelocked(
53
+ address(ubiPool),
54
+ abi.encodeWithSelector(IUBIPool.setUBIDistribution.selector, totalAmount, validatorCmpPubKeys, amounts)
55
+ );
56
+ assertEq(ubiPool.currentDistributionId(), 1);
57
+
58
+ for (uint256 i = 0; i < validators.length; i++) {
59
+ uint256 amount = amounts[i];
60
+ assertEq(ubiPool.validatorUBIAmounts(1, validatorCmpPubKeys[i]), amount);
61
+ vm.prank(validators[i].evmAddress);
62
+ uint256 balanceBefore = address(validators[i].evmAddress).balance;
63
+ uint256 poolBalanceBefore = address(ubiPool).balance;
64
+ ubiPool.claimUBI(1, validatorCmpPubKeys[i]);
65
+ assertEq(address(validators[i].evmAddress).balance, balanceBefore + amount);
66
+ assertEq(address(ubiPool).balance, poolBalanceBefore - amount);
67
+ assertEq(ubiPool.validatorUBIAmounts(1, validatorCmpPubKeys[i]), 0);
68
+ }
69
+ vm.deal(address(ubiPool), totalAmount);
70
+ performTimelocked(
71
+ address(ubiPool),
72
+ abi.encodeWithSelector(IUBIPool.setUBIDistribution.selector, totalAmount, validatorCmpPubKeys, amounts),
73
+ bytes32(keccak256("setUBIDistribution 2nd time"))
74
+ );
75
+ assertEq(ubiPool.currentDistributionId(), 2);
76
+
77
+ for (uint256 i = 0; i < validators.length; i++) {
78
+ uint256 amount = amounts[i];
79
+ assertEq(ubiPool.validatorUBIAmounts(2, validatorCmpPubKeys[i]), amount);
80
+ vm.prank(validators[i].evmAddress);
81
+ uint256 balanceBefore = address(validators[i].evmAddress).balance;
82
+ uint256 poolBalanceBefore = address(ubiPool).balance;
83
+ ubiPool.claimUBI(2, validatorCmpPubKeys[i]);
84
+ assertEq(address(validators[i].evmAddress).balance, balanceBefore + amount);
85
+ assertEq(address(ubiPool).balance, poolBalanceBefore - amount);
86
+ assertEq(ubiPool.validatorUBIAmounts(2, validatorCmpPubKeys[i]), 0);
87
+ }
88
+ }
89
+
90
+ function test_setUBIDistribution_reverts() public {
91
+ // Fail if not protocol admin
92
+ vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, address(this)));
93
+ ubiPool.setUBIDistribution(100 ether, new bytes[](0), new uint256[](0));
94
+
95
+ // Fail if validatorCmpPubKeys is empty
96
+ expectRevertTimelocked(
97
+ address(ubiPool),
98
+ abi.encodeWithSelector(IUBIPool.setUBIDistribution.selector, 100 ether, new bytes[](0), new uint256[](0)),
99
+ "UBIPool: validatorCmpPubKeys cannot be empty"
100
+ );
101
+
102
+ // Fail if validatorCmpPubKeys and percentages do not match
103
+ expectRevertTimelocked(
104
+ address(ubiPool),
105
+ abi.encodeWithSelector(IUBIPool.setUBIDistribution.selector, 100 ether, new bytes[](1), new uint256[](0)),
106
+ "UBIPool: length mismatch"
107
+ );
108
+
109
+ // Fail if UBIPool: not enough balance
110
+ expectRevertTimelocked(
111
+ address(ubiPool),
112
+ abi.encodeWithSelector(IUBIPool.setUBIDistribution.selector, 100 ether, new bytes[](1), new uint256[](1)),
113
+ "UBIPool: not enough balance"
114
+ );
115
+
116
+ // Fail if amounts do not sum to totalUBI
117
+
118
+ uint256[] memory amounts = new uint256[](1);
119
+ bytes[] memory validatorCmpPubKeys = new bytes[](1);
120
+ validatorCmpPubKeys[0] = validators[0].compressedHex;
121
+ amounts[0] = 1 ether;
122
+ vm.deal(address(ubiPool), 100 ether);
123
+ expectRevertTimelocked(
124
+ address(ubiPool),
125
+ abi.encodeWithSelector(IUBIPool.setUBIDistribution.selector, 100 ether, validatorCmpPubKeys, amounts),
126
+ "UBIPool: total amount mismatch"
127
+ );
128
+
129
+ // Fail if one amount is zero
130
+ vm.deal(address(ubiPool), 100 ether);
131
+ amounts = new uint256[](1);
132
+ amounts[0] = 0;
133
+ validatorCmpPubKeys = new bytes[](1);
134
+ validatorCmpPubKeys[0] = validators[0].compressedHex;
135
+ expectRevertTimelocked(
136
+ address(ubiPool),
137
+ abi.encodeWithSelector(IUBIPool.setUBIDistribution.selector, 100 ether, validatorCmpPubKeys, amounts),
138
+ "UBIPool: amounts cannot be zero"
139
+ );
140
+
141
+ // Fail if pubkey is not valid
142
+ vm.deal(address(ubiPool), 100 ether);
143
+ amounts = new uint256[](1);
144
+ amounts[0] = 100 ether;
145
+ validatorCmpPubKeys = new bytes[](1);
146
+ // Invalid pubkey
147
+ validatorCmpPubKeys[
148
+ 0
149
+ ] = hex"0482782124bc9cd03c38aa4cac234dc4e4e3cecf04d57914371baf7fa78ffb975f6d58e245bea952dd039f0fec4e9db418c3b00000"; // pragma: allowlist secret
150
+ expectRevertTimelocked(
151
+ address(ubiPool),
152
+ abi.encodeWithSelector(IUBIPool.setUBIDistribution.selector, 100 ether, validatorCmpPubKeys, amounts),
153
+ "Secp256k1Verifier: Invalid cmp pubkey length"
154
+ );
155
+ }
156
+
157
+ function test_claimUBI_revert_respect_unclaimed_tokens() public {
158
+ // Reverts if balance < totalPendingClaims + totalUBI
159
+ // Set initial distribution and claim some
160
+ uint256 totalAmount = 100 ether;
161
+ uint256[] memory amounts = new uint256[](2);
162
+ amounts[0] = 60 ether;
163
+ amounts[1] = 40 ether;
164
+ bytes[] memory validatorCmpPubKeys = new bytes[](2);
165
+ validatorCmpPubKeys[0] = validators[0].compressedHex;
166
+ validatorCmpPubKeys[1] = validators[1].compressedHex;
167
+
168
+ vm.deal(address(ubiPool), totalAmount);
169
+ performTimelocked(
170
+ address(ubiPool),
171
+ abi.encodeWithSelector(IUBIPool.setUBIDistribution.selector, totalAmount, validatorCmpPubKeys, amounts),
172
+ bytes32(keccak256("setUBIDistribution"))
173
+ );
174
+
175
+ // First validator claims their UBI
176
+ vm.prank(validators[0].evmAddress);
177
+ ubiPool.claimUBI(1, validatorCmpPubKeys[0]);
178
+
179
+ // Try to set new distribution with not enough balance
180
+ // Only 40 ether left in contract but 60 ether still pending claims
181
+ uint256 newTotalAmount = 1 ether;
182
+ uint256[] memory newAmounts = new uint256[](1);
183
+ newAmounts[0] = 1 ether;
184
+ bytes[] memory newValidatorUncmpPubKeys = new bytes[](1);
185
+ newValidatorUncmpPubKeys[0] = validators[0].compressedHex;
186
+
187
+ expectRevertTimelocked(
188
+ address(ubiPool),
189
+ abi.encodeWithSelector(
190
+ IUBIPool.setUBIDistribution.selector,
191
+ newTotalAmount,
192
+ newValidatorUncmpPubKeys,
193
+ newAmounts
194
+ ),
195
+ "UBIPool: not enough balance"
196
+ );
197
+ }
198
+
199
+ function test_claimUBI_respect_unclaimed_tokens() public {
200
+ // Set initial distribution
201
+ uint256 totalAmount = 100 ether;
202
+ uint256[] memory amounts = new uint256[](2);
203
+ amounts[0] = 60 ether;
204
+ amounts[1] = 40 ether;
205
+ bytes[] memory validatorCmpPubKeys = new bytes[](2);
206
+ validatorCmpPubKeys[0] = validators[0].compressedHex;
207
+ validatorCmpPubKeys[1] = validators[1].compressedHex;
208
+
209
+ vm.deal(address(ubiPool), totalAmount + 10 ether); // Extra balance for next distribution
210
+ performTimelocked(
211
+ address(ubiPool),
212
+ abi.encodeWithSelector(IUBIPool.setUBIDistribution.selector, totalAmount, validatorCmpPubKeys, amounts),
213
+ bytes32(keccak256("setUBIDistribution"))
214
+ );
215
+
216
+ // First validator claims their UBI
217
+ vm.prank(validators[0].evmAddress);
218
+ ubiPool.claimUBI(1, validatorCmpPubKeys[0]);
219
+
220
+ // Set new distribution with enough balance
221
+ // 40 ether pending claims + 10 ether extra balance = enough for 5 ether new distribution
222
+ uint256 newTotalAmount = 5 ether;
223
+ uint256[] memory newAmounts = new uint256[](1);
224
+ newAmounts[0] = 5 ether;
225
+ bytes[] memory newValidatorUncmpPubKeys = new bytes[](1);
226
+ newValidatorUncmpPubKeys[0] = validators[0].compressedHex;
227
+
228
+ performTimelocked(
229
+ address(ubiPool),
230
+ abi.encodeWithSelector(
231
+ IUBIPool.setUBIDistribution.selector,
232
+ newTotalAmount,
233
+ newValidatorUncmpPubKeys,
234
+ newAmounts
235
+ ),
236
+ bytes32(keccak256("setUBIDistribution"))
237
+ );
238
+ }
239
+ }
@@ -0,0 +1,37 @@
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 { IUpgradeEntrypoint } from "../../src/protocol/UpgradeEntrypoint.sol";
9
+
10
+ import { Test } from "../utils/Test.sol";
11
+
12
+ contract UpgradeEntrypointTest is Test {
13
+ function testUpgradeEntrypoint_planUpgrade() public {
14
+ // Network shall allow the protocol owner to submit an upgrade plan.
15
+ string memory name = "upgrade";
16
+ int64 height = 1;
17
+ string memory info = "info";
18
+
19
+ schedule(
20
+ address(upgradeEntrypoint),
21
+ abi.encodeWithSelector(IUpgradeEntrypoint.planUpgrade.selector, name, height, info)
22
+ );
23
+ waitForTimelock();
24
+ vm.expectEmit(address(upgradeEntrypoint));
25
+ emit IUpgradeEntrypoint.SoftwareUpgrade(name, height, info);
26
+ executeTimelocked(
27
+ address(upgradeEntrypoint),
28
+ abi.encodeWithSelector(IUpgradeEntrypoint.planUpgrade.selector, name, height, info)
29
+ );
30
+
31
+ // Network shall not allow non-protocol owner to submit an upgrade plan.
32
+ address otherAddr = address(0xf398C12A45Bc409b6C652E25bb0a3e702492A4ab);
33
+ vm.prank(otherAddr);
34
+ vm.expectRevert();
35
+ upgradeEntrypoint.planUpgrade(name, height, info);
36
+ }
37
+ }