Skip to content

Commit 76364d0

Browse files
committed
Refactor to single deployPhase() entrypoint
Address review feedback: - Replace deployPhase1/2/3() with single deployPhase() entrypoint - Make internal _deployPhase1/2/3() functions - Remove deployOnce() (not needed for one-off deployment) - Remove WrongPhase and AlreadyDeployed errors - Calling deployPhase() after completion is a no-op Added transparency for callers: - Return bool indicating if deployment is complete - Emit PhaseCompleted event after each phase
1 parent d8790ea commit 76364d0

File tree

3 files changed

+67
-143
lines changed

3 files changed

+67
-143
lines changed

script/Deploy.s.sol

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,8 @@ contract DeployScript is Script {
9999
factory = new ProtocolFactory(buildFactoryParams());
100100
vm.label(address(factory), "ProtocolFactory");
101101

102-
// Deploy in phases to stay within gas limits
103-
factory.deployPhase1();
104-
factory.deployPhase2();
105-
factory.deployPhase3();
102+
// Deploy in phases to stay within EIP-7825 gas limits
103+
while (!factory.deployPhase()) {}
106104

107105
// Done
108106
printDeployment();

src/ProtocolFactory.sol

Lines changed: 28 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,14 @@ contract ProtocolFactory {
130130
Complete
131131
}
132132

133-
/// @notice Emitted when deployOnce() has been called and the deployment is complete.
133+
/// @notice Emitted when a deployment phase completes.
134+
/// @param phase The phase that was completed.
135+
event PhaseCompleted(DeploymentPhase phase);
136+
137+
/// @notice Emitted when all deployment phases are complete.
134138
/// @param factory The address of the factory contract where the parameters and addresses can be retrieved.
135139
event ProtocolDeployed(ProtocolFactory factory);
136140

137-
/// @notice Thrown when attempting to call deployOnce() when the protocol is already deployed.
138-
error AlreadyDeployed();
139-
140-
/// @notice Thrown when attempting to call a deployment phase out of order
141-
error WrongPhase(DeploymentPhase expected, DeploymentPhase actual);
142-
143141
/// @notice Thrown when the Management DAO has less members than minApprovals
144142
error MemberListIsTooSmall();
145143

@@ -153,72 +151,45 @@ contract ProtocolFactory {
153151
parameters = _parameters;
154152
}
155153

156-
/// @notice Phase 1: Create Management DAO and deploy ENS infrastructure (~5.3M gas)
157-
function deployPhase1() external {
158-
if (currentPhase != DeploymentPhase.NotStarted) {
159-
revert WrongPhase(DeploymentPhase.NotStarted, currentPhase);
154+
/// @notice Executes the next deployment phase. Call repeatedly until deployment is complete.
155+
/// @dev Due to EIP-7825 gas limits, deployment is split into 3 phases.
156+
/// @return complete True if deployment is complete, false if more phases remain.
157+
function deployPhase() external returns (bool complete) {
158+
if (currentPhase == DeploymentPhase.NotStarted) {
159+
_deployPhase1();
160+
} else if (currentPhase == DeploymentPhase.Phase1Complete) {
161+
_deployPhase2();
162+
} else if (currentPhase == DeploymentPhase.Phase2Complete) {
163+
_deployPhase3();
160164
}
165+
// else: already complete, nop
161166

162-
// Create the DAO that will own the registries and the core plugin repo's
163-
prepareRawManagementDao();
164-
165-
// Set up the ENS registry and the requested domains
166-
prepareEnsRegistry();
167-
168-
currentPhase = DeploymentPhase.Phase1Complete;
167+
return currentPhase == DeploymentPhase.Complete;
169168
}
170169

171-
/// @notice Phase 2: Deploy OSx core contracts (~13.0M gas)
172-
function deployPhase2() external {
173-
if (currentPhase != DeploymentPhase.Phase1Complete) {
174-
revert WrongPhase(DeploymentPhase.Phase1Complete, currentPhase);
175-
}
176-
177-
// Deploy the OSx core contracts
178-
prepareOSx();
179-
180-
currentPhase = DeploymentPhase.Phase2Complete;
181-
}
182-
183-
/// @notice Phase 3: Set up permissions, deploy plugin repos and finalize Management DAO (~6.3M gas)
184-
function deployPhase3() external {
185-
if (currentPhase != DeploymentPhase.Phase2Complete) {
186-
revert WrongPhase(DeploymentPhase.Phase2Complete, currentPhase);
187-
}
188-
189-
preparePermissions();
190-
191-
// Prepare the plugin repo's and their versions
192-
prepareCorePluginRepos();
193-
194-
// Drop the factory's permissions on the management DAO
195-
concludeManagementDao();
196-
removePermissions();
197-
198-
currentPhase = DeploymentPhase.Complete;
199-
emit ProtocolDeployed(this);
200-
}
201-
202-
/// @notice Performs all deployment phases in a single transaction (~25M gas) (backwards compatibility)
203-
/// @dev WARNING: This may exceed gas limits on some networks. Use phased deployment instead.
204-
function deployOnce() external {
205-
if (currentPhase != DeploymentPhase.NotStarted) {
206-
revert AlreadyDeployed();
207-
}
208-
170+
/// @dev Phase 1: Create Management DAO and deploy ENS infrastructure (~1.9M gas)
171+
function _deployPhase1() internal {
209172
// Create the DAO that will own the registries and the core plugin repo's
210173
prepareRawManagementDao();
211174

212175
// Set up the ENS registry and the requested domains
213176
prepareEnsRegistry();
214177

215178
currentPhase = DeploymentPhase.Phase1Complete;
179+
emit PhaseCompleted(currentPhase);
180+
}
216181

182+
/// @dev Phase 2: Deploy OSx core contracts (~4.5M gas)
183+
function _deployPhase2() internal {
217184
// Deploy the OSx core contracts
218185
prepareOSx();
219186

220187
currentPhase = DeploymentPhase.Phase2Complete;
188+
emit PhaseCompleted(currentPhase);
189+
}
221190

191+
/// @dev Phase 3: Set up permissions, deploy plugin repos and finalize Management DAO (~7.9M gas)
192+
function _deployPhase3() internal {
222193
preparePermissions();
223194

224195
// Prepare the plugin repo's and their versions
@@ -229,6 +200,7 @@ contract ProtocolFactory {
229200
removePermissions();
230201

231202
currentPhase = DeploymentPhase.Complete;
203+
emit PhaseCompleted(currentPhase);
232204
emit ProtocolDeployed(this);
233205
}
234206

test/ProtocolFactory.t.sol

Lines changed: 37 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ contract ProtocolFactoryTest is AragonTest {
7575

7676
builder.withManagementDaoMembers(mgmtDaoMembers).withManagementDaoMinApprovals(2);
7777

78-
// Build the factory (deploys factory contract but doesn't call deployOnce yet)
78+
// Build the factory (deploys factory contract but doesn't call deployPhase yet)
7979
factory = builder.build();
8080

8181
deploymentParams = builder.getDeploymentParams();
@@ -139,12 +139,13 @@ contract ProtocolFactoryTest is AragonTest {
139139
}
140140

141141
function test_GivenNoPriorDeploymentOnTheFactory() external whenInvokingDeployOnce {
142-
// It Should emit an event with the factory address
142+
// It Should emit an event with the factory address on final phase
143+
factory.deployPhase(); // Phase 1
144+
factory.deployPhase(); // Phase 2
145+
143146
vm.expectEmit(true, true, true, true);
144147
emit ProtocolFactory.ProtocolDeployed(factory);
145-
146-
// Deploy the protocol
147-
factory.deployOnce();
148+
factory.deployPhase(); // Phase 3
148149

149150
// It The deployment addresses are filled with the new contracts
150151
deployment = factory.getDeployment();
@@ -170,7 +171,7 @@ contract ProtocolFactoryTest is AragonTest {
170171
assertEq(deployment.globalExecutor, deploymentParams.osxImplementations.globalExecutor);
171172
assertEq(deployment.placeholderSetup, deploymentParams.osxImplementations.placeholderSetup);
172173

173-
// It Parameters should remain immutable after deployOnce is invoked
174+
// It Parameters should remain immutable after deployment
174175
ProtocolFactory.DeploymentParameters memory currentParams = factory.getParameters();
175176

176177
assertEq(keccak256(abi.encode(currentParams)), keccak256(abi.encode(deploymentParams)));
@@ -235,17 +236,16 @@ contract ProtocolFactoryTest is AragonTest {
235236
);
236237
}
237238

238-
function test_RevertGiven_TheFactoryAlreadyMadeADeployment() external whenInvokingDeployOnce {
239+
function test_GivenTheFactoryAlreadyMadeADeployment_CallingDeployPhaseIsNoop() external whenInvokingDeployOnce {
239240
// Do a first deployment
240241
ProtocolFactory.DeploymentParameters memory params0 = factory.getParameters();
241-
factory.deployOnce();
242+
while (!factory.deployPhase()) {}
242243

243244
ProtocolFactory.DeploymentParameters memory params1 = factory.getParameters();
244245
ProtocolFactory.Deployment memory deployment1 = factory.getDeployment();
245246

246-
// It Should revert
247-
vm.expectRevert(ProtocolFactory.AlreadyDeployed.selector);
248-
factory.deployOnce();
247+
// Calling deployPhase after completion should be a no-op (not revert)
248+
factory.deployPhase();
249249

250250
// It Parameters should remain unchanged
251251
ProtocolFactory.DeploymentParameters memory params2 = factory.getParameters();
@@ -255,6 +255,9 @@ contract ProtocolFactoryTest is AragonTest {
255255
// It Deployment addresses should remain unchanged
256256
ProtocolFactory.Deployment memory deployment2 = factory.getDeployment();
257257
assertEq(keccak256(abi.encode(deployment1)), keccak256(abi.encode(deployment2)));
258+
259+
// Phase should still be Complete
260+
assertEq(uint256(factory.currentPhase()), uint256(ProtocolFactory.DeploymentPhase.Complete));
258261
}
259262

260263
function test_RevertGiven_TheManagementDAOMinApprovalsIsTooSmall() external whenInvokingDeployOnce {
@@ -268,11 +271,15 @@ contract ProtocolFactoryTest is AragonTest {
268271
builder.withManagementDaoMembers(mgmtDaoMembers).withManagementDaoMinApprovals(2);
269272
factory = builder.build();
270273

271-
// Fail
274+
// Phase 1 and 2 succeed
275+
factory.deployPhase();
276+
factory.deployPhase();
277+
278+
// Fail on phase 3 (when concludeManagementDao is called)
272279
vm.expectRevert(ProtocolFactory.MemberListIsTooSmall.selector);
273-
factory.deployOnce();
280+
factory.deployPhase();
274281

275-
// OK
282+
// OK with valid config
276283
mgmtDaoMembers = new address[](2);
277284
mgmtDaoMembers[0] = alice;
278285
mgmtDaoMembers[1] = bob;
@@ -291,17 +298,17 @@ contract ProtocolFactoryTest is AragonTest {
291298
assertEq(uint256(factory.currentPhase()), uint256(ProtocolFactory.DeploymentPhase.NotStarted));
292299

293300
// Deploy phase 1
294-
factory.deployPhase1();
301+
factory.deployPhase();
295302
assertEq(uint256(factory.currentPhase()), uint256(ProtocolFactory.DeploymentPhase.Phase1Complete));
296303

297304
// Deploy phase 2
298-
factory.deployPhase2();
305+
factory.deployPhase();
299306
assertEq(uint256(factory.currentPhase()), uint256(ProtocolFactory.DeploymentPhase.Phase2Complete));
300307

301308
// Deploy phase 3 - expect event
302309
vm.expectEmit(true, true, true, true);
303310
emit ProtocolFactory.ProtocolDeployed(factory);
304-
factory.deployPhase3();
311+
factory.deployPhase();
305312
assertEq(uint256(factory.currentPhase()), uint256(ProtocolFactory.DeploymentPhase.Complete));
306313

307314
// Verify deployment completed
@@ -311,79 +318,26 @@ contract ProtocolFactoryTest is AragonTest {
311318
assertNotEq(deployment.pluginRepoRegistry, address(0));
312319
}
313320

314-
function test_RevertWhen_CallingPhase1OutOfOrder() external whenInvokingPhasedDeployment {
315-
// Complete phase 1
316-
factory.deployPhase1();
317-
318-
// Try calling phase 1 again - should revert
319-
vm.expectRevert(
320-
abi.encodeWithSelector(
321-
ProtocolFactory.WrongPhase.selector,
322-
ProtocolFactory.DeploymentPhase.NotStarted,
323-
ProtocolFactory.DeploymentPhase.Phase1Complete
324-
)
325-
);
326-
factory.deployPhase1();
327-
}
328-
329-
function test_RevertWhen_CallingPhase2BeforePhase1() external whenInvokingPhasedDeployment {
330-
// Try calling phase 2 without phase 1 - should revert
331-
vm.expectRevert(
332-
abi.encodeWithSelector(
333-
ProtocolFactory.WrongPhase.selector,
334-
ProtocolFactory.DeploymentPhase.Phase1Complete,
335-
ProtocolFactory.DeploymentPhase.NotStarted
336-
)
337-
);
338-
factory.deployPhase2();
339-
}
340-
341-
function test_RevertWhen_CallingPhase3BeforePhase2() external whenInvokingPhasedDeployment {
342-
// Complete phase 1 only
343-
factory.deployPhase1();
344-
345-
// Try calling phase 3 without phase 2 - should revert
346-
vm.expectRevert(
347-
abi.encodeWithSelector(
348-
ProtocolFactory.WrongPhase.selector,
349-
ProtocolFactory.DeploymentPhase.Phase2Complete,
350-
ProtocolFactory.DeploymentPhase.Phase1Complete
351-
)
352-
);
353-
factory.deployPhase3();
354-
}
355-
356-
function test_RevertWhen_CallingDeployOnceAfterPhasedDeployment() external whenInvokingPhasedDeployment {
321+
function test_DeployPhaseIsNoopAfterCompletion() external whenInvokingPhasedDeployment {
357322
// Complete all phases
358-
factory.deployPhase1();
359-
factory.deployPhase2();
360-
factory.deployPhase3();
323+
while (!factory.deployPhase()) {}
361324

362-
// Try calling deployOnce - should revert with AlreadyDeployed
363-
vm.expectRevert(ProtocolFactory.AlreadyDeployed.selector);
364-
factory.deployOnce();
365-
}
325+
ProtocolFactory.Deployment memory deployment1 = factory.getDeployment();
366326

367-
function test_RevertWhen_CallingPhase1AfterDeployOnce() external whenInvokingPhasedDeployment {
368-
// Do a complete deployment with deployOnce
369-
factory.deployOnce();
327+
// Calling deployPhase after completion should be a no-op
328+
factory.deployPhase();
370329

371-
// Try calling phase 1 - should revert with WrongPhase
372-
vm.expectRevert(
373-
abi.encodeWithSelector(
374-
ProtocolFactory.WrongPhase.selector,
375-
ProtocolFactory.DeploymentPhase.NotStarted,
376-
ProtocolFactory.DeploymentPhase.Complete
377-
)
378-
);
379-
factory.deployPhase1();
330+
// Phase should still be Complete
331+
assertEq(uint256(factory.currentPhase()), uint256(ProtocolFactory.DeploymentPhase.Complete));
332+
333+
// Deployment should be unchanged
334+
ProtocolFactory.Deployment memory deployment2 = factory.getDeployment();
335+
assertEq(keccak256(abi.encode(deployment1)), keccak256(abi.encode(deployment2)));
380336
}
381337

382338
modifier givenAProtocolDeployment() {
383-
// Use phased deployment to stay within gas limits
384-
factory.deployPhase1();
385-
factory.deployPhase2();
386-
factory.deployPhase3();
339+
// Use phased deployment to stay within EIP-7825 gas limits
340+
while (!factory.deployPhase()) {}
387341

388342
deployment = factory.getDeployment();
389343
deploymentParams = builder.getDeploymentParams();

0 commit comments

Comments
 (0)