@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,219 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity 0.8.23;
3
+
4
+ import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
5
+ // solhint-disable-next-line max-line-length
6
+ import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
7
+
8
+ import { IPTokenStaking } from "../../src/protocol/IPTokenStaking.sol";
9
+ import { UpgradeEntrypoint } from "../../src/protocol/UpgradeEntrypoint.sol";
10
+ import { UBIPool } from "../../src/protocol/UBIPool.sol";
11
+
12
+ import { EIP1967Helper } from "../../script/utils/EIP1967Helper.sol";
13
+ import { Predeploys } from "../../src/libraries/Predeploys.sol";
14
+ import { Test } from "../utils/Test.sol";
15
+
16
+ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
17
+
18
+ abstract contract MockNewFeatures {
19
+ function foo() external pure returns (string memory) {
20
+ return "bar";
21
+ }
22
+ }
23
+
24
+ contract IPTokenStakingV2 is IPTokenStaking, MockNewFeatures {
25
+ constructor(uint256 defaultMinFee, uint256 maxDataLength) IPTokenStaking(defaultMinFee, maxDataLength) {}
26
+ }
27
+
28
+ contract UpgradeEntrypointV2 is UpgradeEntrypoint, MockNewFeatures {}
29
+
30
+ contract UBIPoolV2 is UBIPool, MockNewFeatures {
31
+ constructor(uint32 maxUBIPercentage) UBIPool(maxUBIPercentage) {}
32
+ }
33
+
34
+ contract InitialImplementation {
35
+ function foo() external pure returns (string memory) {
36
+ return "bar";
37
+ }
38
+ }
39
+
40
+ /**
41
+ * @title PredeployUpgrades
42
+ * @dev A script to test upgrading the precompile contracts
43
+ */
44
+ contract PredeployUpgrades is Test {
45
+ function testUpgradeStaking() public {
46
+ // ---- Staking
47
+ address newImpl = address(
48
+ new IPTokenStakingV2(
49
+ 1 ether, // defaultMinFee
50
+ 256 // maxDataLength
51
+ )
52
+ );
53
+ ProxyAdmin proxyAdmin = ProxyAdmin(EIP1967Helper.getAdmin(Predeploys.Staking));
54
+ assertEq(proxyAdmin.owner(), address(timelock));
55
+
56
+ performTimelocked(
57
+ address(proxyAdmin),
58
+ abi.encodeWithSelector(
59
+ ProxyAdmin.upgradeAndCall.selector,
60
+ ITransparentUpgradeableProxy(Predeploys.Staking),
61
+ newImpl,
62
+ ""
63
+ )
64
+ );
65
+
66
+ assertEq(EIP1967Helper.getImplementation(Predeploys.Staking), newImpl, "Staking not upgraded");
67
+ assertEq(
68
+ keccak256(abi.encode(IPTokenStakingV2(Predeploys.Staking).foo())),
69
+ keccak256(abi.encode("bar")),
70
+ "Upgraded to wrong iface"
71
+ );
72
+ }
73
+
74
+ function testUpgradeUpgradeEntrypoint() public {
75
+ // ---- Upgrades
76
+ address newImpl = address(new UpgradeEntrypointV2());
77
+ ProxyAdmin proxyAdmin = ProxyAdmin(EIP1967Helper.getAdmin(Predeploys.Upgrades));
78
+ assertEq(proxyAdmin.owner(), address(timelock));
79
+
80
+ performTimelocked(
81
+ address(proxyAdmin),
82
+ abi.encodeWithSelector(
83
+ ProxyAdmin.upgradeAndCall.selector,
84
+ ITransparentUpgradeableProxy(Predeploys.Upgrades),
85
+ newImpl,
86
+ ""
87
+ )
88
+ );
89
+ assertEq(EIP1967Helper.getImplementation(Predeploys.Upgrades), newImpl, "Upgrades not upgraded");
90
+ assertEq(
91
+ keccak256(abi.encode(IPTokenStakingV2(Predeploys.Upgrades).foo())),
92
+ keccak256(abi.encode("bar")),
93
+ "Upgraded to wrong iface"
94
+ );
95
+ }
96
+
97
+ function testUpgradeUBIPool() public {
98
+ // ---- UBIPool
99
+ address newImpl = address(new UBIPoolV2(10_00));
100
+ ProxyAdmin proxyAdmin = ProxyAdmin(EIP1967Helper.getAdmin(Predeploys.UBIPool));
101
+ assertEq(proxyAdmin.owner(), address(timelock));
102
+
103
+ performTimelocked(
104
+ address(proxyAdmin),
105
+ abi.encodeWithSelector(
106
+ ProxyAdmin.upgradeAndCall.selector,
107
+ ITransparentUpgradeableProxy(Predeploys.UBIPool),
108
+ newImpl,
109
+ ""
110
+ )
111
+ );
112
+ assertEq(EIP1967Helper.getImplementation(Predeploys.UBIPool), newImpl, "Upgrades not upgraded");
113
+ assertEq(
114
+ keccak256(abi.encode(IPTokenStakingV2(Predeploys.UBIPool).foo())),
115
+ keccak256(abi.encode("bar")),
116
+ "Upgraded to wrong iface"
117
+ );
118
+ }
119
+
120
+ function testUpgradeUnusedProxies() public {
121
+ address initialImpl = address(new InitialImplementation());
122
+
123
+ for (
124
+ uint160 i = uint160(Predeploys.Upgrades) + uint160(1);
125
+ i <= uint160(Predeploys.Namespace) + Predeploys.NamespaceSize;
126
+ i++
127
+ ) {
128
+ // Verify predeploy is proxied and not upgraded
129
+ address predeploy = address(i);
130
+ assertTrue(Predeploys.proxied(predeploy), "Predeploy not proxied");
131
+ assertEq(
132
+ EIP1967Helper.getImplementation(predeploy),
133
+ Predeploys.getImplAddress(predeploy),
134
+ "Predeploy upgraded already"
135
+ );
136
+ ProxyAdmin proxyAdmin = ProxyAdmin(EIP1967Helper.getAdmin(predeploy));
137
+ assertEq(proxyAdmin.owner(), address(timelock));
138
+
139
+ // Revert if not owner
140
+ vm.expectRevert();
141
+ proxyAdmin.upgradeAndCall(ITransparentUpgradeableProxy(predeploy), initialImpl, "");
142
+
143
+ // Upgrade predeploy
144
+ performTimelocked(
145
+ address(proxyAdmin),
146
+ abi.encodeWithSelector(
147
+ ProxyAdmin.upgradeAndCall.selector,
148
+ ITransparentUpgradeableProxy(predeploy),
149
+ initialImpl,
150
+ ""
151
+ )
152
+ );
153
+
154
+ // Verify predeploy is upgraded
155
+ assertEq(EIP1967Helper.getImplementation(predeploy), initialImpl, "Predeploy not upgraded");
156
+ assertEq(
157
+ keccak256(abi.encode(InitialImplementation(predeploy).foo())),
158
+ keccak256(abi.encode("bar")),
159
+ "Upgraded to wrong iface"
160
+ );
161
+ }
162
+ }
163
+
164
+ function testRenounceUpgradeability() public {
165
+ address newImpl = address(new InitialImplementation());
166
+ // For each predeploy, renounce upgradeability
167
+ for (
168
+ uint160 i = uint160(Predeploys.Namespace) + uint160(1);
169
+ i <= uint160(Predeploys.Namespace) + Predeploys.NamespaceSize;
170
+ i++
171
+ ) {
172
+ address predeploy = address(i);
173
+ ProxyAdmin proxyAdmin = ProxyAdmin(EIP1967Helper.getAdmin(predeploy));
174
+ assertEq(proxyAdmin.owner(), address(timelock));
175
+ // Renounce ownership
176
+ performTimelocked(address(proxyAdmin), abi.encodeWithSelector(Ownable.renounceOwnership.selector));
177
+
178
+ // Verify ownership was renounced
179
+ assertEq(proxyAdmin.owner(), address(0), "Ownership not renounced");
180
+
181
+ // Try to upgrade
182
+ bytes memory upgradeAction = abi.encodeWithSelector(
183
+ ProxyAdmin.upgradeAndCall.selector,
184
+ ITransparentUpgradeableProxy(predeploy),
185
+ newImpl,
186
+ ""
187
+ );
188
+ bytes memory expectedReason = abi.encodeWithSelector(
189
+ Ownable.OwnableUnauthorizedAccount.selector,
190
+ address(timelock)
191
+ );
192
+ expectRevertTimelocked(address(proxyAdmin), upgradeAction, expectedReason);
193
+ }
194
+ }
195
+
196
+ function testUpgradeLastPredeploy() public {
197
+ address newImpl = address(new InitialImplementation());
198
+ address lastPredeploy = address(uint160(Predeploys.Namespace) + uint160(Predeploys.NamespaceSize));
199
+ ProxyAdmin proxyAdmin = ProxyAdmin(EIP1967Helper.getAdmin(lastPredeploy));
200
+ assertEq(proxyAdmin.owner(), address(timelock));
201
+
202
+ // Upgrade last predeploy to new implementation
203
+ performTimelocked(
204
+ address(proxyAdmin),
205
+ abi.encodeWithSelector(
206
+ ProxyAdmin.upgradeAndCall.selector,
207
+ ITransparentUpgradeableProxy(lastPredeploy),
208
+ newImpl,
209
+ ""
210
+ )
211
+ );
212
+ assertEq(EIP1967Helper.getImplementation(lastPredeploy), newImpl, "Last predeploy not upgraded");
213
+ assertEq(
214
+ keccak256(abi.encode(InitialImplementation(lastPredeploy).foo())),
215
+ keccak256(abi.encode("bar")),
216
+ "Upgraded to wrong iface"
217
+ );
218
+ }
219
+ }
@@ -0,0 +1,170 @@
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
+
6
+ import { Test as ForgeTest } from "forge-std/Test.sol";
7
+
8
+ import { IPTokenStaking } from "../../src/protocol/IPTokenStaking.sol";
9
+ import { UpgradeEntrypoint } from "../../src/protocol/UpgradeEntrypoint.sol";
10
+ import { UBIPool } from "../../src/protocol/UBIPool.sol";
11
+ import { Predeploys } from "../../src/libraries/Predeploys.sol";
12
+ import { Create3 } from "../../src/deploy/Create3.sol";
13
+ import { GenerateAlloc } from "../../script/GenerateAlloc.s.sol";
14
+ import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol";
15
+ import { ERC6551Registry } from "erc6551/ERC6551Registry.sol";
16
+ import { WIP } from "../../src/token/WIP.sol";
17
+
18
+ contract Test is ForgeTest {
19
+ address internal admin = address(0x123);
20
+ address internal executor = address(0x456);
21
+ address internal guardian = address(0x789);
22
+
23
+ address internal deployer = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd);
24
+
25
+ IPTokenStaking internal ipTokenStaking;
26
+ UpgradeEntrypoint internal upgradeEntrypoint;
27
+ UBIPool internal ubiPool;
28
+ Create3 internal create3;
29
+ ERC6551Registry internal erc6551Registry;
30
+ TimelockController internal timelock;
31
+ WIP internal wip;
32
+
33
+ function setUp() public virtual {
34
+ GenerateAlloc initializer = new GenerateAlloc();
35
+ initializer.disableStateDump(); // Faster tests. Don't call to verify JSON output
36
+ initializer.setAdminAddresses(admin, executor, guardian);
37
+ initializer.run();
38
+ ipTokenStaking = IPTokenStaking(Predeploys.Staking);
39
+ upgradeEntrypoint = UpgradeEntrypoint(Predeploys.Upgrades);
40
+ ubiPool = UBIPool(Predeploys.UBIPool);
41
+ create3 = Create3(Predeploys.Create3);
42
+ wip = WIP(payable(Predeploys.WIP));
43
+ erc6551Registry = ERC6551Registry(Predeploys.ERC6551Registry);
44
+ address timelockAddress = create3.getDeployed(deployer, keccak256("STORY_TIMELOCK_CONTROLLER"));
45
+ timelock = TimelockController(payable(timelockAddress));
46
+ require(timelockAddress.code.length > 0, "Timelock not deployed");
47
+ }
48
+
49
+ /// @notice schedules, waits for timelock and executes a timelocked call
50
+ /// @param target The address to call
51
+ /// @param data The data to call with
52
+ function performTimelocked(address target, bytes memory data) internal {
53
+ uint256 minDelay = timelock.getMinDelay();
54
+ vm.prank(admin);
55
+ timelock.schedule(
56
+ target,
57
+ 0, // value
58
+ data,
59
+ bytes32(0), // predecessor: Non Zero if order must be respected
60
+ bytes32(keccak256("SALT")), // salt
61
+ minDelay
62
+ );
63
+ vm.warp(block.timestamp + minDelay + 1);
64
+ vm.prank(executor);
65
+ timelock.execute(
66
+ target,
67
+ 0, // value
68
+ data,
69
+ bytes32(0), // predecessor: Non Zero if order must be respected
70
+ bytes32(keccak256("SALT")) // salt
71
+ );
72
+ }
73
+
74
+ /// @notice schedules, waits for timelock and executes a timelocked call, with a custom salt
75
+ /// @dev This is to be used if we want to call the same target with the same data multiple times
76
+ /// @param target The address to call
77
+ /// @param data The data to call with
78
+ /// @param salt The salt to use for the timelock
79
+ function performTimelocked(address target, bytes memory data, bytes32 salt) internal {
80
+ uint256 minDelay = timelock.getMinDelay();
81
+ vm.prank(admin);
82
+ timelock.schedule(
83
+ target,
84
+ 0, // value
85
+ data,
86
+ bytes32(0), // predecessor: Non Zero if order must be respected
87
+ bytes32(salt), // salt
88
+ minDelay
89
+ );
90
+ vm.warp(block.timestamp + minDelay + 1);
91
+ vm.prank(executor);
92
+ timelock.execute(
93
+ target,
94
+ 0, // value
95
+ data,
96
+ bytes32(0), // predecessor: Non Zero if order must be respected
97
+ bytes32(salt) // salt
98
+ );
99
+ }
100
+
101
+ /// @notice schedules a timelocked call
102
+ /// @param target The address to call
103
+ /// @param data The data to call with
104
+ function schedule(address target, bytes memory data) internal {
105
+ uint256 minDelay = timelock.getMinDelay();
106
+ vm.prank(admin);
107
+ timelock.schedule(
108
+ target,
109
+ 0, // value
110
+ data,
111
+ bytes32(0), // predecessor: Non Zero if order must be respected
112
+ bytes32(keccak256("SALT")), // salt
113
+ minDelay
114
+ );
115
+ }
116
+
117
+ /// @notice waits for timelock (minDelay)
118
+ /// @dev This is to be called after schedule()
119
+ /// If the scheduled time > minDelay, this wait time won't be enough
120
+ /// and the test will revert
121
+ function waitForTimelock() internal {
122
+ uint256 minDelay = timelock.getMinDelay();
123
+ vm.warp(block.timestamp + minDelay + 1);
124
+ }
125
+
126
+ /// @notice executes a timelocked call
127
+ /// @param target The address to call
128
+ /// @param data The data to call with
129
+ function executeTimelocked(address target, bytes memory data) internal {
130
+ vm.prank(executor);
131
+ timelock.execute(
132
+ target,
133
+ 0, // value
134
+ data,
135
+ bytes32(0), // predecessor: Non Zero if order must be respected
136
+ bytes32(keccak256("SALT")) // salt
137
+ );
138
+ }
139
+
140
+ /// @notice schedules, waits for timelock and executes a timelocked call that is expected to revert
141
+ /// @param target The address to call
142
+ /// @param data The data to call with
143
+ /// @param reason The expected revert reason
144
+ function expectRevertTimelocked(address target, bytes memory data, bytes memory reason) internal {
145
+ uint256 minDelay = timelock.getMinDelay();
146
+ vm.prank(admin);
147
+ timelock.schedule(
148
+ target,
149
+ 0, // value
150
+ data,
151
+ bytes32(0), // predecessor: Non Zero if order must be respected
152
+ bytes32(keccak256("SALT")), // salt
153
+ minDelay
154
+ );
155
+ vm.warp(block.timestamp + minDelay + 1);
156
+ vm.prank(executor);
157
+ vm.expectRevert(bytes(reason));
158
+ timelock.execute(
159
+ target,
160
+ 0, // value
161
+ data,
162
+ bytes32(0), // predecessor: Non Zero if order must be respected
163
+ bytes32(keccak256("SALT")) // salt
164
+ );
165
+ // Cancel the scheduled call to clean the hash in the timelock
166
+ bytes32 id = timelock.hashOperation(target, 0, data, bytes32(0), bytes32(keccak256("SALT")));
167
+ vm.prank(admin);
168
+ timelock.cancel(id);
169
+ }
170
+ }