Skip to content

Commit e12ee74

Browse files
authored
Merge pull request #340 from AugurProject/codex/todo-1-staged-ops
Support multiple pending staged operation settlements
2 parents 38bb40e + 81b9edd commit e12ee74

16 files changed

Lines changed: 302 additions & 161 deletions

solidity/contracts/peripherals/SecurityPoolOracleCoordinator.sol

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct StagedOperation {
3232
}
3333

3434
contract SecurityPoolOracleCoordinator {
35+
uint256 public constant MAX_PENDING_SETTLEMENT_OPERATIONS = 4;
3536
uint256 public pendingReportId;
3637
uint256 public pendingOperationSlotId;
3738
uint256 public lastSettlementTimestamp;
@@ -71,9 +72,9 @@ contract SecurityPoolOracleCoordinator {
7172
);
7273
event ExecutedStagedOperation(uint256 operationId, OperationType operation, bool success, string errorMessage);
7374

74-
// This is not a FIFO queue. We keep an append-only operation record and a single
75-
// pending slot that settlement can auto-execute once a fresh oracle price arrives.
76-
// Active-operation paging is newest-first so UI previews remain stable after manual
75+
// This is not a FIFO queue. We keep append-only operation records plus a bounded
76+
// pending settlement list that auto-executes once a fresh oracle price arrives.
77+
// Active-operation paging is newest-first so UI previews remain stable after
7778
// execution removes older entries from the set.
7879
uint256 public stagedOperationCounter;
7980
mapping(uint256 => StagedOperation) public stagedOperations;
@@ -82,6 +83,7 @@ contract SecurityPoolOracleCoordinator {
8283
mapping(uint256 => uint256) private olderActiveStagedOperationIds;
8384
mapping(uint256 => uint256) private newerActiveStagedOperationIds;
8485
mapping(uint256 => bool) private isActiveStagedOperation;
86+
uint256[] private pendingSettlementOperationIds;
8587

8688
constructor(
8789
OpenOracle _openOracle,
@@ -138,9 +140,17 @@ contract SecurityPoolOracleCoordinator {
138140
}
139141

140142
function getRequestPriceEthCost() public view returns (uint256) {
141-
uint256 ethCost = block.basefee * 4 * (gasConsumedSettlement + gasConsumedOpenOracleReportPrice) + 101;
143+
uint256 ethCost =
144+
block.basefee * 4 * (getSettlementCallbackGasLimit() + gasConsumedOpenOracleReportPrice) + 101;
142145
return ethCost;
143146
}
147+
148+
function getSettlementCallbackGasLimit() public view returns (uint32) {
149+
uint256 callbackGasLimit = uint256(gasConsumedSettlement) * MAX_PENDING_SETTLEMENT_OPERATIONS;
150+
require(callbackGasLimit <= type(uint32).max, 'settlement gas too high');
151+
return uint32(callbackGasLimit);
152+
}
153+
144154
function requestPrice() public payable {
145155
require(pendingReportId == 0, 'Already pending request');
146156
uint256 ethCost = getRequestPriceEthCost();
@@ -161,7 +171,7 @@ contract SecurityPoolOracleCoordinator {
161171
disputeDelay: disputeDelay,
162172
protocolFee: protocolFee,
163173
token2Address: address(weth), // address of token2 in the oracle report instance
164-
callbackGasLimit: gasConsumedSettlement, // gas the settlement callback must use
174+
callbackGasLimit: getSettlementCallbackGasLimit(), // gas the settlement callback must use
165175
feePercentage: feePercentage,
166176
multiplier: multiplier,
167177
timeType: timeType,
@@ -211,19 +221,15 @@ contract SecurityPoolOracleCoordinator {
211221
price /
212222
ORACLE_BUDGET_BPS;
213223
emit PriceReported(reportId, lastPrice);
214-
if (pendingOperationSlotId != 0) {
215-
// TODO we maybe should allow executing couple operations?
216-
uint256 operationId = pendingOperationSlotId;
224+
if (pendingSettlementOperationIds.length != 0) {
225+
uint256[] memory operationIds = pendingSettlementOperationIds;
226+
delete pendingSettlementOperationIds;
217227
pendingOperationSlotId = 0;
218-
StagedOperation memory stagedOperation = stagedOperations[operationId];
219-
if (stagedOperation.initiatorVault == address(0)) return;
220-
if (block.timestamp > stagedOperation.queuedAt + settlementTime + stagedOperation.validForSeconds) {
221-
_consumeActiveStagedOperation(operationId);
222-
stagedOperations[operationId].initiatorVault = address(0);
223-
emit ExecutedStagedOperation(operationId, stagedOperation.operation, false, 'staged operation expired');
224-
return;
228+
for (uint256 index = 0; index < operationIds.length; index++) {
229+
if (stagedOperations[operationIds[index]].initiatorVault != address(0)) {
230+
executeStagedOperation(operationIds[index]);
231+
}
225232
}
226-
executeStagedOperation(operationId);
227233
}
228234
}
229235

@@ -289,25 +295,24 @@ contract SecurityPoolOracleCoordinator {
289295
emit StagedOperationQueued(operationId, operation, msg.sender, targetVault, amount, false);
290296
executeStagedOperation(operationId);
291297
// no cost when price is valid
292-
} else if (pendingReportId == 0 && pendingOperationSlotId == 0) {
293-
pendingOperationSlotId = operationId;
294-
emit StagedOperationQueued(operationId, operation, msg.sender, targetVault, amount, true);
295-
uint256 ethCost = getRequestPriceEthCost();
296-
require(msg.value >= ethCost, 'not enough eth to request price');
297-
retained += ethCost;
298-
// Forward exactly ethCost to requestPrice to create the report
299-
this.requestPrice{ value: ethCost }();
300-
} else if (pendingReportId == 0) {
301-
emit StagedOperationQueued(operationId, operation, msg.sender, targetVault, amount, false);
302-
} else if (pendingOperationSlotId == 0) {
303-
pendingOperationSlotId = operationId;
304-
emit StagedOperationQueued(operationId, operation, msg.sender, targetVault, amount, true);
305298
} else {
306-
emit StagedOperationQueued(operationId, operation, msg.sender, targetVault, amount, false);
307-
// This is intentional: only one staged operation is marked as the auto-execute
308-
// pending slot for the next fresh oracle report. Additional operations are still
309-
// recorded and can be executed manually via executeStagedOperation once the price
310-
// becomes valid again.
299+
bool shouldRequestPrice = pendingReportId == 0 && pendingSettlementOperationIds.length == 0;
300+
bool isPendingSettlementOperationId = _trackPendingSettlementOperation(operationId);
301+
emit StagedOperationQueued(
302+
operationId,
303+
operation,
304+
msg.sender,
305+
targetVault,
306+
amount,
307+
isPendingSettlementOperationId
308+
);
309+
if (shouldRequestPrice && isPendingSettlementOperationId) {
310+
uint256 ethCost = getRequestPriceEthCost();
311+
require(msg.value >= ethCost, 'not enough eth to request price');
312+
retained += ethCost;
313+
// Forward exactly ethCost to requestPrice to create the report
314+
this.requestPrice{ value: ethCost }();
315+
}
311316
}
312317

313318
// Refund the excess of msg.value that was not retained
@@ -471,6 +476,7 @@ contract SecurityPoolOracleCoordinator {
471476
}
472477

473478
function _consumeStagedOperation(uint256 operationId) private {
479+
_consumePendingSettlementOperation(operationId);
474480
_consumeActiveStagedOperation(operationId);
475481
stagedOperations[operationId].initiatorVault = address(0);
476482
}
@@ -483,6 +489,14 @@ contract SecurityPoolOracleCoordinator {
483489
return activeStagedOperationCount;
484490
}
485491

492+
function getPendingSettlementOperationCount() public view returns (uint256) {
493+
return pendingSettlementOperationIds.length;
494+
}
495+
496+
function getPendingSettlementOperationIds() public view returns (uint256[] memory) {
497+
return pendingSettlementOperationIds;
498+
}
499+
486500
function getActiveStagedOperations(
487501
uint256 startIndex,
488502
uint256 count
@@ -516,6 +530,28 @@ contract SecurityPoolOracleCoordinator {
516530
latestActiveStagedOperationId = operationId;
517531
}
518532

533+
function _trackPendingSettlementOperation(uint256 operationId) private returns (bool) {
534+
if (pendingSettlementOperationIds.length >= MAX_PENDING_SETTLEMENT_OPERATIONS) return false;
535+
pendingSettlementOperationIds.push(operationId);
536+
if (pendingOperationSlotId == 0) {
537+
pendingOperationSlotId = operationId;
538+
}
539+
return true;
540+
}
541+
542+
function _consumePendingSettlementOperation(uint256 operationId) private {
543+
uint256 operationCount = pendingSettlementOperationIds.length;
544+
for (uint256 index = 0; index < operationCount; index++) {
545+
if (pendingSettlementOperationIds[index] != operationId) continue;
546+
for (uint256 shiftIndex = index + 1; shiftIndex < operationCount; shiftIndex++) {
547+
pendingSettlementOperationIds[shiftIndex - 1] = pendingSettlementOperationIds[shiftIndex];
548+
}
549+
pendingSettlementOperationIds.pop();
550+
pendingOperationSlotId = pendingSettlementOperationIds.length == 0 ? 0 : pendingSettlementOperationIds[0];
551+
return;
552+
}
553+
}
554+
519555
function _consumeActiveStagedOperation(uint256 operationId) private {
520556
if (!isActiveStagedOperation[operationId]) return;
521557
uint256 olderOperationId = olderActiveStagedOperationIds[operationId];

0 commit comments

Comments
 (0)