@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,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
|
+
}
|