Skip to content

Commit ac7e125

Browse files
authored
Merge pull request #123 from HorizenOfficial/st/HZN-2801-multi-app
St/hzn 2801 multi app
2 parents 9d12c65 + 2e08498 commit ac7e125

29 files changed

Lines changed: 1523 additions & 374 deletions

contracts/contracts/ProcessorEndpoint.sol

Lines changed: 104 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
1717
bytes32 public constant ADMIN = keccak256('ADMIN');
1818
bytes32 public constant DEPLOYER_ROLE = keccak256('DEPLOYER_ROLE');
1919
uint8 public constant PROTOCOL_VERSION = 0;
20-
uint64 public constant APPLICATION_ID = 1;
21-
2220
//state variables
23-
bytes32 public stateRoot;
21+
mapping(uint64 => bytes32) public applicationStateRoots;
22+
uint256 public maxNumOfApplications = 10;
23+
uint256 public availableDeploySlots = maxNumOfApplications;
2424

2525
mapping(bytes32 => Structs.PendingRequest) public requestById;
2626
mapping(uint256 => bytes32) private _requestIdByOrder;
@@ -44,7 +44,7 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
4444
}
4545

4646
modifier validApplicationId(uint64 applicationId) {
47-
if (applicationId != APPLICATION_ID) revert InvalidApplicationId();
47+
if (applicationStateRoots[applicationId] == bytes32(0)) revert InvalidApplicationId();
4848
_;
4949
}
5050

@@ -84,7 +84,7 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
8484
Structs.RequestType requestType,
8585
bytes calldata payload,
8686
uint256 depositAmount, // part of the sent value forwarded to the application, for app logic
87-
uint256 maxFeeValue // part ot the sent value reserved for fee payment
87+
uint256 maxFeeValue // part of the sent value reserved for fee payment
8888
)
8989
external
9090
payable
@@ -93,16 +93,16 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
9393
nonReentrant
9494
returns (bytes32)
9595
{
96+
if (requestType == Structs.RequestType.DEPLOYAPP) revert InvalidRequestType();
97+
9698
//check values
9799
if (msg.value != depositAmount + maxFeeValue) revert InvalidValue();
98100
if (maxFeeValue < minFeePerRequest) revert FeeValueBelowMinimum();
99101

100102
//check queue size
101103
if (getPendingRequestsSize() >= maxQueueSize) revert QueueThresholdExceeded();
102104

103-
if (requestType == Structs.RequestType.DEPLOYAPP) {
104-
if (!hasRole(DEPLOYER_ROLE, msg.sender)) revert DeployerNotAllowed();
105-
} else if (requestType == Structs.RequestType.ASSOCIATEKEY) {
105+
if (requestType == Structs.RequestType.ASSOCIATEKEY) {
106106
//if requestype is associatekey, the payload must be 133 bytes long (contains a Secp521r1_PubKey)
107107
if (payload.length != 133) revert InvalidPayload();
108108
} else if (requestType == Structs.RequestType.DEANONYMIZATION) {
@@ -139,7 +139,55 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
139139
}
140140

141141
//emit event
142-
emit RequestSubmitted(requestId, msg.sender);
142+
emit RequestSubmitted(applicationId, requestId, msg.sender);
143+
144+
return requestId;
145+
}
146+
147+
/// @inheritdoc IProcessorEndpoint
148+
function submitDeployRequest(
149+
uint8 protocolVersion,
150+
bytes calldata payload
151+
) external payable validProtocolVersion(protocolVersion) nonReentrant returns (bytes32) {
152+
if (!hasRole(DEPLOYER_ROLE, msg.sender)) revert DeployerNotAllowed();
153+
if (availableDeploySlots == 0) revert MaxNumOfApplicationsExceeded();
154+
//check queue size
155+
if (getPendingRequestsSize() >= maxQueueSize) revert QueueThresholdExceeded();
156+
if (msg.value < minFeePerRequest) revert FeeValueBelowMinimum();
157+
158+
--availableDeploySlots;
159+
160+
Structs.RequestType requestType = Structs.RequestType.DEPLOYAPP;
161+
//create request
162+
bytes32 requestId = generateRequestId(
163+
msg.sender,
164+
0, // deploy requests have applicationId 0, a unique applicationId will be derived from the requestId for each deploy request to avoid collisions with regular requests and to group deploy requests together
165+
requestType,
166+
payload,
167+
0,
168+
_tail
169+
);
170+
171+
uint64 applicationId = uint64(bytes8(requestId)); // Derive a unique application ID from the request ID for deploy requests
172+
requestById[requestId] = Structs.PendingRequest({
173+
timestamp: block.timestamp,
174+
depositAmount: 0,
175+
maxFeeValue: msg.value,
176+
requestId: requestId,
177+
payload: payload,
178+
sender: msg.sender,
179+
applicationId: applicationId,
180+
protocolVersion: protocolVersion,
181+
requestType: requestType
182+
});
183+
_requestIdByOrder[_tail] = requestId;
184+
185+
unchecked {
186+
++_tail;
187+
}
188+
189+
//emit event
190+
emit DeployRequestSubmitted(applicationId, requestId, msg.sender);
143191

144192
return requestId;
145193
}
@@ -153,15 +201,28 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
153201
}
154202

155203
function _markRequestCompleted(
204+
uint64 applicationId,
156205
bytes32 requestId,
157206
uint256 applicationFees,
158207
Structs.RequestResult result,
159208
Structs.ErrorCode errCode,
160-
string memory errorMsg
209+
string memory errorMsg,
210+
Structs.RequestType requestType
161211
) private {
162212
_removeRequest();
163213

164-
emit RequestCompleted(requestId, applicationFees, result, errCode, errorMsg);
214+
if (requestType == Structs.RequestType.DEPLOYAPP) {
215+
emit DeployRequestCompleted(
216+
applicationId,
217+
requestId,
218+
applicationFees,
219+
result,
220+
errCode,
221+
errorMsg
222+
);
223+
} else {
224+
emit RequestCompleted(applicationId, requestId, applicationFees, result, errCode, errorMsg);
225+
}
165226

166227
_asyncTransfer(feeCollector, applicationFees);
167228
}
@@ -251,7 +312,7 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
251312
if (applicationId != requestInfo.applicationId) revert InvalidApplicationId();
252313

253314
//check prev state root
254-
if (prevStateRoot != stateRoot) revert InvalidStateRoot();
315+
if (prevStateRoot != applicationStateRoots[applicationId]) revert InvalidStateRoot();
255316

256317
uint256 eventsLength = events.length;
257318
uint256 eventSubTypesLength = eventSubTypes.length;
@@ -283,7 +344,7 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
283344
// For errors: state unchanged (prevStateRoot == newStateRoot), no events, no withdrawals
284345
// Refund user (minus minimum fee) and collect minimum fee
285346
if (eventsLength != 0 || withdrawalRequests.length != 0) revert InvalidPayload();
286-
if (stateRoot != newStateRoot) revert InvalidStateRoot();
347+
if (applicationStateRoots[applicationId] != newStateRoot) revert InvalidStateRoot();
287348

288349
if (requestInfo.depositAmount + requestInfo.maxFeeValue > _getAvailableBalance())
289350
revert InsufficientBalance();
@@ -298,20 +359,28 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
298359
emit Refund(applicationId, processedRequestId, sender, totalRefund);
299360
}
300361

362+
if (requestInfo.requestType == Structs.RequestType.DEPLOYAPP) {
363+
unchecked {
364+
++availableDeploySlots;
365+
}
366+
}
367+
301368
_markRequestCompleted(
369+
applicationId,
302370
processedRequestId,
303371
minFeePerRequest,
304372
Structs.RequestResult.FAILED,
305373
Structs.ErrorCode(errorCode),
306-
errorMsg
374+
errorMsg,
375+
requestInfo.requestType
307376
);
308377

309378
return;
310379
}
311380

312381
// Handle success case
313382
// State cannot remain the same
314-
if (stateRoot == newStateRoot) revert InvalidStateRoot();
383+
if (applicationStateRoots[applicationId] == newStateRoot) revert InvalidStateRoot();
315384

316385
if (refund + applicationFees != maxFeeValue) revert InvalidValue();
317386
if (applicationFees < minFeePerRequest) {
@@ -348,7 +417,7 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
348417
}
349418

350419
//update state root and request
351-
stateRoot = newStateRoot;
420+
applicationStateRoots[applicationId] = newStateRoot;
352421
emit StateRootUpdate(applicationId, processedRequestId, prevStateRoot, newStateRoot);
353422

354423
//credit refund to sender's pending balance (pull pattern)
@@ -359,7 +428,7 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
359428

360429
//credit withdrawals to receivers' pending balances
361430
i = 0;
362-
while (i < withdrawalRequests.length) {
431+
while (i < withdrawalsLength) {
363432
_asyncTransfer(withdrawalRequests[i].receiver, withdrawalRequests[i].amount);
364433
emit Withdrawal(
365434
applicationId,
@@ -374,11 +443,13 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
374443

375444
//set requests as completed
376445
_markRequestCompleted(
446+
applicationId,
377447
processedRequestId,
378448
applicationFees,
379449
Structs.RequestResult.COMPLETED,
380450
Structs.ErrorCode.NO_ERROR,
381-
''
451+
'',
452+
reqType
382453
);
383454
}
384455

@@ -389,6 +460,17 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
389460
emit QueueThresholdUpdated(newThreshold);
390461
}
391462

463+
/// @inheritdoc IProcessorEndpoint
464+
function updateMaxNumOfApplications(uint256 newMax) external onlyRole(ADMIN) {
465+
if (newMax == 0) revert InvalidValue();
466+
uint256 deployedApps = maxNumOfApplications - availableDeploySlots;
467+
if (newMax < deployedApps) revert InvalidValue();
468+
uint256 oldMax = maxNumOfApplications;
469+
maxNumOfApplications = newMax;
470+
availableDeploySlots = newMax - deployedApps;
471+
emit MaxNumberOfAppUpdated(oldMax, newMax);
472+
}
473+
392474
/// @inheritdoc IProcessorEndpoint
393475
function updateFeeCollector(address payable newFeeCollector) external onlyRole(ADMIN) {
394476
if (newFeeCollector == address(0)) revert AddressCantBeZero();
@@ -422,11 +504,12 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
422504
uint256 numOfRequests = getPendingRequestsSize();
423505
if (numOfRequests > 0) {
424506
bytes32 requestId = _requestIdByOrder[_head];
425-
return (requestById[requestId], stateRoot, true);
507+
Structs.PendingRequest storage req = requestById[requestId];
508+
return (req, applicationStateRoots[req.applicationId], true);
426509
}
427510

428511
Structs.PendingRequest memory emptyReq;
429-
return (emptyReq, stateRoot, false);
512+
return (emptyReq, bytes32(0), false);
430513
}
431514

432515
/// @inheritdoc IProcessorEndpoint
@@ -468,7 +551,7 @@ contract ProcessorEndpoint is AccessControl, IProcessorEndpoint, ReentrancyGuard
468551
uint256 idx
469552
) public pure returns (bytes32) {
470553
bytes32 requestId = keccak256(
471-
abi.encodePacked(sender, applicationId, requestType, payload, depositAmount, idx)
554+
abi.encode(sender, applicationId, requestType, payload, depositAmount, idx)
472555
);
473556

474557
return requestId;

contracts/contracts/interfaces/IProcessorEndpoint.sol

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,43 @@ interface IProcessorEndpoint {
2424
uint256 amount
2525
);
2626
/// @notice Emitted when a new request enters the queue.
27+
/// @param applicationId Application identifier.
28+
/// @param requestId Request identifier.
29+
/// @param sender Request sender.
30+
event RequestSubmitted(uint64 indexed applicationId, bytes32 requestId, address indexed sender);
31+
/// @notice Emitted when a new deploy request enters the queue.
32+
/// @param applicationId Application identifier.
2733
/// @param requestId Request identifier.
2834
/// @param sender Request sender.
29-
event RequestSubmitted(bytes32 indexed requestId, address indexed sender);
35+
event DeployRequestSubmitted(
36+
uint64 indexed applicationId,
37+
bytes32 requestId,
38+
address indexed sender
39+
);
3040
/// @notice Emitted when a request is finalized.
41+
/// @param applicationId Application identifier.
3142
/// @param requestId Request identifier.
3243
/// @param applicationFees Fees collected for the application.
3344
/// @param status Completion status for the request.
3445
/// @param errorCode Error code when the request failed.
3546
/// @param errorMessage Human-readable error message.
3647
event RequestCompleted(
48+
uint64 indexed applicationId,
49+
bytes32 indexed requestId,
50+
uint256 applicationFees,
51+
Structs.RequestResult status,
52+
Structs.ErrorCode errorCode,
53+
string errorMessage
54+
);
55+
/// @notice Emitted when a deploy request is finalized.
56+
/// @param applicationId Application identifier.
57+
/// @param requestId Request identifier.
58+
/// @param applicationFees Fees collected for the application.
59+
/// @param status Completion status for the request.
60+
/// @param errorCode Error code when the request failed.
61+
/// @param errorMessage Human-readable error message.
62+
event DeployRequestCompleted(
63+
uint64 indexed applicationId,
3764
bytes32 indexed requestId,
3865
uint256 applicationFees,
3966
Structs.RequestResult status,
@@ -69,6 +96,10 @@ interface IProcessorEndpoint {
6996
/// @notice Emitted when the queue size threshold is changed.
7097
/// @param newThreshold New maximum queue size.
7198
event QueueThresholdUpdated(uint256 newThreshold);
99+
/// @notice Emitted when the maximum number of applications is updated.
100+
/// @param oldMax Previous maximum.
101+
/// @param newMax New maximum.
102+
event MaxNumberOfAppUpdated(uint256 oldMax, uint256 newMax);
72103
/// @notice Emitted when the fee collector address is updated.
73104
/// @param newFeeCollector New fee collector address.
74105
event FeeCollectorUpdated(address newFeeCollector);
@@ -103,8 +134,12 @@ interface IProcessorEndpoint {
103134
error DeployerNotAllowed();
104135
/// @notice Queue size exceeds the configured threshold.
105136
error QueueThresholdExceeded();
137+
/// @notice Maximum number of deployed applications has been reached.
138+
error MaxNumOfApplicationsExceeded();
106139
/// @notice An ETH transfer failed.
107140
error TransferFailed();
141+
/// @notice The provided request type is not allowed.
142+
error InvalidRequestType();
108143

109144
/// @notice Submits a new request and enqueues it for processing.
110145
/// @param protocolVersion Protocol version.
@@ -123,6 +158,15 @@ interface IProcessorEndpoint {
123158
uint256 maxFeeValue
124159
) external payable returns (bytes32);
125160

161+
/// @notice Submits a new deploy request and enqueues it for processing.
162+
/// @param protocolVersion Protocol version.
163+
/// @param payload Request payload.
164+
/// @return requestId Generated request id.
165+
function submitDeployRequest(
166+
uint8 protocolVersion,
167+
bytes calldata payload
168+
) external payable returns (bytes32);
169+
126170
/// @notice Returns the number of pending requests in the queue.
127171
/// @return size Current pending request count.
128172
function getPendingRequestsSize() external view returns (uint256);
@@ -172,6 +216,10 @@ interface IProcessorEndpoint {
172216
/// @param newThreshold New queue size limit.
173217
function updateQueueThreshold(uint256 newThreshold) external;
174218

219+
/// @notice Updates the maximum number of deployable applications.
220+
/// @param newMax New maximum. Must be >= currently deployed count.
221+
function updateMaxNumOfApplications(uint256 newMax) external;
222+
175223
/// @notice Updates the fee collector address.
176224
/// @param newFeeCollector New fee collector address.
177225
function updateFeeCollector(address payable newFeeCollector) external;

0 commit comments

Comments
 (0)