feat: finish up challenges
Some checks failed
Lint / ci (lts/*, ubuntu-latest) (push) Has been cancelled
Some checks failed
Lint / ci (lts/*, ubuntu-latest) (push) Has been cancelled
This commit is contained in:
@@ -60,7 +60,11 @@ contract WhitelistOracle {
|
||||
* @dev Creates a new SimpleOracle instance and adds it to the oracles array.
|
||||
* @param _owner The address that will own the newly created oracle and can update its price
|
||||
*/
|
||||
function addOracle(address _owner) public onlyOwner {}
|
||||
function addOracle(address _owner) public onlyOwner {
|
||||
SimpleOracle newOracle = new SimpleOracle(_owner);
|
||||
oracles.push(newOracle);
|
||||
emit OracleAdded(address(newOracle), _owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Removes an oracle from the whitelist by its array index (only contract owner)
|
||||
@@ -68,7 +72,18 @@ contract WhitelistOracle {
|
||||
* Reverts with IndexOutOfBounds, if the provided index is >= oracles.length.
|
||||
* @param index The index of the oracle to remove from the oracles array
|
||||
*/
|
||||
function removeOracle(uint256 index) public onlyOwner {}
|
||||
function removeOracle(uint256 index) public onlyOwner {
|
||||
uint256 oraclesSize = oracles.length;
|
||||
if (index >= oraclesSize) revert IndexOutOfBounds();
|
||||
|
||||
address deletedOracle = address(oracles[index]);
|
||||
if (index != oraclesSize - 1) {
|
||||
oracles[index] = oracles[oraclesSize - 1];
|
||||
}
|
||||
|
||||
oracles.pop();
|
||||
emit OracleRemoved(deletedOracle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the aggregated price from all active oracles using median calculation
|
||||
@@ -76,7 +91,28 @@ contract WhitelistOracle {
|
||||
* of remaining valid prices. Uses StatisticsUtils for sorting and median calculation.
|
||||
* @return The median price from all active oracles
|
||||
*/
|
||||
function getPrice() public view returns (uint256) {}
|
||||
function getPrice() public view returns (uint256) {
|
||||
if (oracles.length == 0) revert NoOraclesAvailable();
|
||||
uint256[] memory prices = new uint256[](oracles.length);
|
||||
uint256 priceIndex = 0;
|
||||
uint256 currentTime = block.timestamp;
|
||||
|
||||
for (uint256 i = 0; i < oracles.length; i++) {
|
||||
(uint256 price, uint256 timestamp) = oracles[i].getPrice();
|
||||
if (currentTime - timestamp < STALE_DATA_WINDOW) {
|
||||
prices[priceIndex] = price;
|
||||
priceIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
uint256[] memory fixedPrices = new uint256[](priceIndex);
|
||||
for (uint256 i = 0; i < priceIndex; i++) {
|
||||
fixedPrices[i] = prices[i];
|
||||
}
|
||||
|
||||
fixedPrices.sort();
|
||||
return fixedPrices.getMedian();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the addresses of all oracles that have updated their price within the last STALE_DATA_WINDOW
|
||||
@@ -85,5 +121,24 @@ contract WhitelistOracle {
|
||||
* for gas optimization.
|
||||
* @return An array of addresses representing the currently active oracle contracts
|
||||
*/
|
||||
function getActiveOracleNodes() public view returns (address[] memory) {}
|
||||
function getActiveOracleNodes() public view returns (address[] memory) {
|
||||
address[] memory activeOracles = new address[](oracles.length);
|
||||
uint256 oracleIndex = 0;
|
||||
uint256 currentTime = block.timestamp;
|
||||
|
||||
for (uint256 i = 0; i < oracles.length; i++) {
|
||||
(, uint256 timestamp) = oracles[i].getPrice();
|
||||
if (currentTime - timestamp < STALE_DATA_WINDOW) {
|
||||
activeOracles[oracleIndex] = address(oracles[i]);
|
||||
oracleIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
address[] memory fixedActiveOracles = new address[](oracleIndex);
|
||||
for (uint256 i = 0; i < oracleIndex; i++) {
|
||||
fixedActiveOracles[i] = activeOracles[i];
|
||||
}
|
||||
|
||||
return fixedActiveOracles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,25 @@ contract StakingOracle {
|
||||
* @dev Creates a new OracleNode struct and adds the sender to the nodeAddresses array.
|
||||
* Requires minimum stake amount and prevents duplicate registrations.
|
||||
*/
|
||||
function registerNode(uint256 amount) public {}
|
||||
function registerNode(uint256 amount) public {
|
||||
if (amount < MINIMUM_STAKE) revert InsufficientStake();
|
||||
if (nodes[msg.sender].active) revert NodeAlreadyRegistered();
|
||||
|
||||
bool success = oracleToken.transferFrom(msg.sender, address(this), amount);
|
||||
if (!success) revert TransferFailed();
|
||||
|
||||
nodes[msg.sender] = OracleNode({
|
||||
stakedAmount: amount,
|
||||
lastReportedBucket: 0,
|
||||
reportCount: 0,
|
||||
claimedReportCount: 0,
|
||||
firstBucket: getCurrentBucketNumber(),
|
||||
active: true
|
||||
});
|
||||
|
||||
nodeAddresses.push(msg.sender);
|
||||
emit NodeRegistered(msg.sender, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Updates the price reported by an oracle node (only registered nodes)
|
||||
@@ -114,25 +132,69 @@ contract StakingOracle {
|
||||
* This creates a chain of finalized buckets, ensuring all past reports are accountable.
|
||||
* @param price The new price value to report
|
||||
*/
|
||||
function reportPrice(uint256 price) public onlyNode {}
|
||||
function reportPrice(uint256 price) public onlyNode {
|
||||
if (price == 0) revert InvalidPrice();
|
||||
if (getEffectiveStake(msg.sender) < MINIMUM_STAKE) revert InsufficientStake();
|
||||
|
||||
uint256 currentBucket = getCurrentBucketNumber();
|
||||
OracleNode storage n = nodes[msg.sender];
|
||||
if (n.lastReportedBucket == currentBucket) revert AlreadyReportedInCurrentBucket();
|
||||
|
||||
BlockBucket storage bucket = blockBuckets[currentBucket];
|
||||
bucket.reporters.push(msg.sender);
|
||||
bucket.prices.push(price);
|
||||
|
||||
n.lastReportedBucket = currentBucket;
|
||||
n.reportCount++;
|
||||
emit PriceReported(msg.sender, price, currentBucket);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Allows active and inactive nodes to claim accumulated ORA token rewards
|
||||
* @dev Calculates rewards based on time elapsed since last claim.
|
||||
*/
|
||||
function claimReward() public {}
|
||||
function claimReward() public {
|
||||
OracleNode storage node = nodes[msg.sender];
|
||||
uint256 delta = node.reportCount - node.claimedReportCount;
|
||||
if (delta == 0) revert NoRewardsAvailable();
|
||||
|
||||
node.claimedReportCount = node.reportCount;
|
||||
oracleToken.mint(msg.sender, delta * REWARD_PER_REPORT);
|
||||
emit NodeRewarded(msg.sender, delta * REWARD_PER_REPORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Allows a registered node to increase its ORA token stake
|
||||
*/
|
||||
function addStake(uint256 amount) public onlyNode {}
|
||||
function addStake(uint256 amount) public onlyNode {
|
||||
if (amount == 0) revert InsufficientStake();
|
||||
|
||||
bool success = oracleToken.transferFrom(msg.sender, address(this), amount);
|
||||
if (!success) revert TransferFailed();
|
||||
|
||||
nodes[msg.sender].stakedAmount += amount;
|
||||
emit StakeAdded(msg.sender, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Records the median price for a bucket once sufficient reports are available
|
||||
* @dev Anyone who uses the oracle's price feed can call this function to record the median price for a bucket.
|
||||
* @param bucketNumber The bucket number to finalize
|
||||
*/
|
||||
function recordBucketMedian(uint256 bucketNumber) public {}
|
||||
function recordBucketMedian(uint256 bucketNumber) public {
|
||||
uint256 currentBucket = getCurrentBucketNumber();
|
||||
if (bucketNumber == currentBucket) revert OnlyPastBucketsAllowed();
|
||||
|
||||
BlockBucket storage bucket = blockBuckets[bucketNumber];
|
||||
if (bucket.medianPrice != 0) revert BucketMedianAlreadyRecorded();
|
||||
|
||||
uint256[] memory prices = bucket.prices;
|
||||
prices.sort();
|
||||
uint256 medianPrice = prices.getMedian();
|
||||
bucket.medianPrice = medianPrice;
|
||||
|
||||
emit BucketMedianRecorded(bucketNumber, medianPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Slashes a node for giving a price that is deviated too far from the average
|
||||
@@ -146,7 +208,39 @@ contract StakingOracle {
|
||||
uint256 bucketNumber,
|
||||
uint256 reportIndex,
|
||||
uint256 nodeAddressesIndex
|
||||
) public {}
|
||||
) public {
|
||||
if (bucketNumber == getCurrentBucketNumber()) revert OnlyPastBucketsAllowed();
|
||||
if (nodeAddressesIndex >= nodeAddresses.length) revert IndexOutOfBounds();
|
||||
|
||||
BlockBucket storage bucket = blockBuckets[bucketNumber];
|
||||
address reporter = bucket.reporters[reportIndex];
|
||||
if (bucket.slashedOffenses[reporter]) revert NodeAlreadySlashed();
|
||||
if (bucket.medianPrice == 0) revert MedianNotRecorded();
|
||||
if (reportIndex >= bucket.prices.length) revert IndexOutOfBounds();
|
||||
if (reporter != nodeToSlash) revert NodeNotAtGivenIndex();
|
||||
|
||||
uint256 reportedPrice = bucket.prices[reportIndex];
|
||||
if (reportedPrice == 0) revert NodeDidNotReport();
|
||||
bool isOutlier = _checkPriceDeviated(reportedPrice, bucket.medianPrice);
|
||||
if (!isOutlier) revert NotDeviated();
|
||||
|
||||
bucket.slashedOffenses[reporter] = true;
|
||||
OracleNode storage node = nodes[nodeToSlash];
|
||||
uint256 actualPenalty = MISREPORT_PENALTY > node.stakedAmount ? node.stakedAmount : MISREPORT_PENALTY;
|
||||
node.stakedAmount -= actualPenalty;
|
||||
|
||||
if (node.stakedAmount == 0) {
|
||||
_removeNode(nodeToSlash, nodeAddressesIndex);
|
||||
emit NodeExited(nodeToSlash, 0);
|
||||
}
|
||||
|
||||
uint256 reward = (actualPenalty * SLASHER_REWARD_PERCENTAGE) / 100;
|
||||
|
||||
bool rewardSent = oracleToken.transfer(msg.sender, reward);
|
||||
if (!rewardSent) revert TransferFailed();
|
||||
|
||||
emit NodeSlashed(nodeToSlash, actualPenalty);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Allows a registered node to exit the system and withdraw their stake
|
||||
@@ -155,7 +249,24 @@ contract StakingOracle {
|
||||
* node has been slashed if it reported a bad price before allowing it to exit.
|
||||
* @param index The index of the node to remove in nodeAddresses
|
||||
*/
|
||||
function exitNode(uint256 index) public onlyNode {}
|
||||
function exitNode(uint256 index) public onlyNode {
|
||||
if (index >= nodeAddresses.length) revert IndexOutOfBounds();
|
||||
if (nodeAddresses[index] != msg.sender) revert NodeNotAtGivenIndex();
|
||||
|
||||
OracleNode storage node = nodes[msg.sender];
|
||||
if (!node.active) revert NodeNotRegistered();
|
||||
if (node.lastReportedBucket + WAITING_PERIOD > getCurrentBucketNumber()) revert WaitingPeriodNotOver();
|
||||
|
||||
uint256 effectiveStake = getEffectiveStake(msg.sender);
|
||||
_removeNode(msg.sender, index);
|
||||
|
||||
node.stakedAmount = 0;
|
||||
node.active = false;
|
||||
bool success = oracleToken.transfer(msg.sender, effectiveStake);
|
||||
if (!success) revert TransferFailed();
|
||||
|
||||
emit NodeExited(msg.sender, effectiveStake);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
/// View Functions /////
|
||||
@@ -174,21 +285,34 @@ contract StakingOracle {
|
||||
* @notice Returns the list of registered oracle node addresses
|
||||
* @return Array of registered oracle node addresses
|
||||
*/
|
||||
function getNodeAddresses() public view returns (address[] memory) {}
|
||||
function getNodeAddresses() public view returns (address[] memory) {
|
||||
return nodeAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the stored median price from the most recently completed bucket
|
||||
* @dev Requires that the median for the bucket be recorded via recordBucketMedian
|
||||
* @return The median price for the last finalized bucket
|
||||
*/
|
||||
function getLatestPrice() public view returns (uint256) {}
|
||||
function getLatestPrice() public view returns (uint256) {
|
||||
uint256 bucketNumber = getCurrentBucketNumber() - 1;
|
||||
BlockBucket storage bucket = blockBuckets[bucketNumber];
|
||||
if (bucket.medianPrice == 0) revert MedianNotRecorded();
|
||||
|
||||
return bucket.medianPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the stored median price from a specified bucket
|
||||
* @param bucketNumber The bucket number to read the median price from
|
||||
* @return The median price stored for the bucket
|
||||
*/
|
||||
function getPastPrice(uint256 bucketNumber) public view returns (uint256) {}
|
||||
function getPastPrice(uint256 bucketNumber) public view returns (uint256) {
|
||||
BlockBucket storage bucket = blockBuckets[bucketNumber];
|
||||
if (bucket.medianPrice == 0) revert MedianNotRecorded();
|
||||
|
||||
return bucket.medianPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the price and slashed status of a node at a given bucket
|
||||
@@ -200,20 +324,70 @@ contract StakingOracle {
|
||||
function getSlashedStatus(
|
||||
address nodeAddress,
|
||||
uint256 bucketNumber
|
||||
) public view returns (uint256 price, bool slashed) {}
|
||||
) public view returns (uint256 price, bool slashed) {
|
||||
BlockBucket storage bucket = blockBuckets[bucketNumber];
|
||||
for (uint256 i = 0; i < bucket.reporters.length; i++) {
|
||||
if (bucket.reporters[i] == nodeAddress) {
|
||||
price = bucket.prices[i];
|
||||
slashed = bucket.slashedOffenses[nodeAddress];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the effective stake accounting for inactivity penalties via missed buckets
|
||||
* @dev Effective stake = stakedAmount - (missedBuckets * INACTIVITY_PENALTY), floored at 0
|
||||
*/
|
||||
function getEffectiveStake(address nodeAddress) public view returns (uint256) {}
|
||||
function getEffectiveStake(address nodeAddress) public view returns (uint256) {
|
||||
OracleNode memory n = nodes[nodeAddress]; // get node detail
|
||||
if (!n.active) return 0; // inactive node
|
||||
|
||||
uint256 currentBucket = getCurrentBucketNumber();
|
||||
if (currentBucket == n.firstBucket) return n.stakedAmount; // basically the node just registered itself
|
||||
|
||||
uint256 expectedReports = currentBucket - n.firstBucket; // get the amount of "buckets" since registration
|
||||
uint256 actualReportsCompleted = n.reportCount;
|
||||
if (n.lastReportedBucket == currentBucket && actualReportsCompleted > 0) {
|
||||
actualReportsCompleted -= 1; // we remove the report from current bucket
|
||||
}
|
||||
if (actualReportsCompleted >= expectedReports) return n.stakedAmount; // no penalty
|
||||
uint256 missed = expectedReports - actualReportsCompleted;
|
||||
uint256 penalty = missed * INACTIVITY_PENALTY;
|
||||
if (penalty > n.stakedAmount) return 0; // the amount of penalty is more than the staked amount
|
||||
return n.stakedAmount - penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the addresses of nodes in a bucket whose reported price deviates beyond the threshold
|
||||
* @param bucketNumber The bucket number to get the outliers from
|
||||
* @return Array of node addresses considered outliers
|
||||
*/
|
||||
function getOutlierNodes(uint256 bucketNumber) public view returns (address[] memory) {}
|
||||
function getOutlierNodes(uint256 bucketNumber) public view returns (address[] memory) {
|
||||
BlockBucket storage bucket = blockBuckets[bucketNumber];
|
||||
if (bucket.medianPrice == 0) revert MedianNotRecorded();
|
||||
|
||||
address[] memory outliers = new address[](bucket.reporters.length);
|
||||
uint256 outlierCount = 0;
|
||||
for (uint256 i = 0; i < bucket.reporters.length; i++) {
|
||||
address reporter = bucket.reporters[i];
|
||||
if (bucket.slashedOffenses[reporter]) continue;
|
||||
|
||||
uint256 reportedPrice = bucket.prices[i];
|
||||
if (reportedPrice == 0) continue;
|
||||
|
||||
bool isOutlier = _checkPriceDeviated(reportedPrice, bucket.medianPrice);
|
||||
if (isOutlier) {
|
||||
outliers[outlierCount] = reporter;
|
||||
outlierCount++;
|
||||
}
|
||||
}
|
||||
|
||||
address[] memory fixedOutliers = new address[](outlierCount);
|
||||
for (uint256 i = 0; i < outlierCount; i++) {
|
||||
fixedOutliers[i] = outliers[i];
|
||||
}
|
||||
return fixedOutliers;
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
/// Internal Functions ///
|
||||
@@ -224,7 +398,18 @@ contract StakingOracle {
|
||||
* @param nodeAddress The address of the node to remove
|
||||
* @param index The index of the node to remove
|
||||
*/
|
||||
function _removeNode(address nodeAddress, uint256 index) internal {}
|
||||
function _removeNode(address nodeAddress, uint256 index) internal {
|
||||
if (index >= nodeAddresses.length) revert IndexOutOfBounds();
|
||||
|
||||
address storedNodeAddress = nodeAddresses[index];
|
||||
if (storedNodeAddress != nodeAddress) revert NodeNotAtGivenIndex();
|
||||
|
||||
if (index != nodeAddresses.length - 1) {
|
||||
nodeAddresses[index] = nodeAddresses[nodeAddresses.length - 1];
|
||||
}
|
||||
nodeAddresses.pop();
|
||||
nodes[nodeAddress].active = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Checks if the price deviation is greater than the threshold
|
||||
@@ -232,5 +417,9 @@ contract StakingOracle {
|
||||
* @param medianPrice The average price of the bucket
|
||||
* @return True if the price deviation is greater than the threshold, false otherwise
|
||||
*/
|
||||
function _checkPriceDeviated(uint256 reportedPrice, uint256 medianPrice) internal pure returns (bool) {}
|
||||
function _checkPriceDeviated(uint256 reportedPrice, uint256 medianPrice) internal pure returns (bool) {
|
||||
uint256 deviation = reportedPrice > medianPrice ? reportedPrice - medianPrice : medianPrice - reportedPrice;
|
||||
uint256 deviationBps = (deviation * 10_000) / medianPrice;
|
||||
return deviationBps > MAX_DEVIATION_BPS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,35 @@ contract OptimisticOracle {
|
||||
string memory description,
|
||||
uint256 startTime,
|
||||
uint256 endTime
|
||||
) external payable returns (uint256) {}
|
||||
) external payable returns (uint256) {
|
||||
uint256 assertionId = nextAssertionId;
|
||||
nextAssertionId++;
|
||||
if (msg.value == 0) revert InvalidValue();
|
||||
|
||||
if (startTime == 0) startTime = block.timestamp;
|
||||
if (endTime == 0) endTime = block.timestamp + MINIMUM_ASSERTION_WINDOW;
|
||||
|
||||
if (startTime < block.timestamp) revert InvalidTime();
|
||||
if (endTime < startTime + MINIMUM_ASSERTION_WINDOW) revert InvalidTime();
|
||||
|
||||
assertions[assertionId] = EventAssertion({
|
||||
asserter: msg.sender,
|
||||
proposer: address(0),
|
||||
disputer: address(0),
|
||||
proposedOutcome: false,
|
||||
resolvedOutcome: false,
|
||||
reward: msg.value,
|
||||
bond: msg.value * 2,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
claimed: false,
|
||||
winner: address(0),
|
||||
description: description
|
||||
});
|
||||
|
||||
emit EventAsserted(assertionId, msg.sender, description, msg.value);
|
||||
return assertionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Proposes the outcome (true or false) for an asserted event
|
||||
@@ -150,7 +178,22 @@ contract OptimisticOracle {
|
||||
* @param assertionId The unique identifier of the assertion to propose an outcome for
|
||||
* @param outcome The proposed boolean outcome (true or false) for the event
|
||||
*/
|
||||
function proposeOutcome(uint256 assertionId, bool outcome) external payable {}
|
||||
function proposeOutcome(uint256 assertionId, bool outcome) external payable {
|
||||
if (assertionId >= nextAssertionId) revert AssertionNotFound();
|
||||
|
||||
EventAssertion storage eventAssertion = assertions[assertionId];
|
||||
|
||||
if (eventAssertion.proposer != address(0)) revert AssertionProposed();
|
||||
uint256 currentTime = block.timestamp;
|
||||
if (currentTime < eventAssertion.startTime || currentTime > eventAssertion.endTime) revert InvalidTime();
|
||||
if (msg.value != eventAssertion.bond) revert InvalidValue();
|
||||
|
||||
eventAssertion.proposer = msg.sender;
|
||||
eventAssertion.proposedOutcome = outcome;
|
||||
eventAssertion.endTime = currentTime + DISPUTE_WINDOW;
|
||||
|
||||
emit OutcomeProposed(assertionId, msg.sender, outcome);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Disputes a proposed outcome by bonding ETH
|
||||
@@ -158,7 +201,19 @@ contract OptimisticOracle {
|
||||
* and must be within the dispute window after proposal.
|
||||
* @param assertionId The unique identifier of the assertion to dispute
|
||||
*/
|
||||
function disputeOutcome(uint256 assertionId) external payable {}
|
||||
function disputeOutcome(uint256 assertionId) external payable {
|
||||
EventAssertion storage eventAssertion = assertions[assertionId];
|
||||
|
||||
if (eventAssertion.asserter == address(0)) revert AssertionNotFound();
|
||||
if (eventAssertion.proposer == address(0)) revert NotProposedAssertion();
|
||||
if (eventAssertion.disputer != address(0)) revert ProposalDisputed();
|
||||
if (block.timestamp > eventAssertion.endTime) revert InvalidTime();
|
||||
if (msg.value != eventAssertion.bond) revert InvalidValue();
|
||||
|
||||
eventAssertion.disputer = msg.sender;
|
||||
|
||||
emit OutcomeDisputed(assertionId, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Claims reward for undisputed assertions after dispute window expires
|
||||
@@ -166,7 +221,22 @@ contract OptimisticOracle {
|
||||
* Can only be called after dispute window has passed without disputes.
|
||||
* @param assertionId The unique identifier of the assertion to claim rewards for
|
||||
*/
|
||||
function claimUndisputedReward(uint256 assertionId) external {}
|
||||
function claimUndisputedReward(uint256 assertionId) external {
|
||||
EventAssertion storage assertion = assertions[assertionId];
|
||||
if (assertion.proposer == address(0)) revert NotProposedAssertion();
|
||||
if (assertion.disputer != address(0)) revert ProposalDisputed();
|
||||
if (block.timestamp <= assertion.endTime) revert InvalidTime();
|
||||
if (assertion.claimed) revert AlreadyClaimed();
|
||||
|
||||
assertion.claimed = true;
|
||||
assertion.resolvedOutcome = assertion.proposedOutcome;
|
||||
assertion.winner = assertion.proposer;
|
||||
uint256 totalValue = assertion.reward + assertion.bond;
|
||||
(bool success,) = payable(assertion.proposer).call{value: totalValue}("");
|
||||
if (!success) revert TransferFailed();
|
||||
|
||||
emit RewardClaimed(assertionId, assertion.proposer, totalValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Claims reward for disputed assertions after decider settlement
|
||||
@@ -174,7 +244,23 @@ contract OptimisticOracle {
|
||||
* Can only be called after decider has settled the dispute.
|
||||
* @param assertionId The unique identifier of the disputed assertion to claim rewards for
|
||||
*/
|
||||
function claimDisputedReward(uint256 assertionId) external {}
|
||||
function claimDisputedReward(uint256 assertionId) external {
|
||||
EventAssertion storage assertion = assertions[assertionId];
|
||||
if (assertion.proposer == address(0)) revert NotProposedAssertion();
|
||||
if (assertion.disputer == address(0)) revert NotDisputedAssertion();
|
||||
if (assertion.winner == address(0)) revert AwaitingDecider();
|
||||
if (assertion.claimed) revert AlreadyClaimed();
|
||||
|
||||
assertion.claimed = true;
|
||||
(bool successDecider,) = payable(decider).call{value: assertion.bond}("");
|
||||
if (!successDecider) revert TransferFailed();
|
||||
|
||||
uint256 totalValue = assertion.reward + assertion.bond;
|
||||
(bool success,) = payable(assertion.winner).call{value: totalValue}("");
|
||||
if (!success) revert TransferFailed();
|
||||
|
||||
emit RewardClaimed(assertionId, assertion.winner, totalValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Claims refund for assertions that receive no proposals before deadline
|
||||
@@ -182,7 +268,20 @@ contract OptimisticOracle {
|
||||
* Can only be called after assertion deadline has passed without any proposals.
|
||||
* @param assertionId The unique identifier of the expired assertion to claim refund for
|
||||
*/
|
||||
function claimRefund(uint256 assertionId) external {}
|
||||
function claimRefund(uint256 assertionId) external {
|
||||
EventAssertion storage assertion = assertions[assertionId];
|
||||
if (assertion.asserter == address(0)) revert AssertionNotFound();
|
||||
if (assertion.proposer != address(0)) revert AssertionProposed();
|
||||
if (block.timestamp <= assertion.endTime) revert InvalidTime();
|
||||
if (assertion.claimed) revert AlreadyClaimed();
|
||||
|
||||
assertion.claimed = true;
|
||||
uint256 totalValue = assertion.reward;
|
||||
(bool success,) = payable(assertion.asserter).call{value: totalValue}("");
|
||||
if (!success) revert TransferFailed();
|
||||
|
||||
emit RefundClaimed(assertionId, assertion.asserter, totalValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Resolves disputed assertions by determining the correct outcome (only decider)
|
||||
@@ -190,7 +289,17 @@ contract OptimisticOracle {
|
||||
* @param assertionId The unique identifier of the disputed assertion to settle
|
||||
* @param resolvedOutcome The decider's determination of the true outcome
|
||||
*/
|
||||
function settleAssertion(uint256 assertionId, bool resolvedOutcome) external onlyDecider {}
|
||||
function settleAssertion(uint256 assertionId, bool resolvedOutcome) external onlyDecider {
|
||||
EventAssertion storage assertion = assertions[assertionId];
|
||||
if (assertion.proposer == address(0)) revert NotProposedAssertion();
|
||||
if (assertion.disputer == address(0)) revert NotDisputedAssertion();
|
||||
if (assertion.winner != address(0)) revert AlreadySettled();
|
||||
|
||||
assertion.resolvedOutcome = resolvedOutcome;
|
||||
assertion.winner = assertion.proposedOutcome == resolvedOutcome ? assertion.proposer : assertion.disputer;
|
||||
|
||||
emit AssertionSettled(assertionId, resolvedOutcome, assertion.winner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the current state of an assertion based on its lifecycle stage
|
||||
@@ -198,7 +307,19 @@ contract OptimisticOracle {
|
||||
* @param assertionId The unique identifier of the assertion to check state for
|
||||
* @return The current State enum value representing the assertion's status
|
||||
*/
|
||||
function getState(uint256 assertionId) external view returns (State) {}
|
||||
function getState(uint256 assertionId) external view returns (State) {
|
||||
EventAssertion memory assertion = assertions[assertionId];
|
||||
|
||||
if (assertion.asserter == address(0)) return State.Invalid;
|
||||
if (assertion.winner != address(0)) return State.Settled;
|
||||
if (assertion.disputer != address(0)) return State.Disputed;
|
||||
if (assertion.proposer != address(0)) {
|
||||
if (block.timestamp >= assertion.endTime) return State.Settled;
|
||||
return State.Proposed;
|
||||
}
|
||||
if (block.timestamp >= assertion.endTime) return State.Expired;
|
||||
return State.Asserted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the final resolved outcome of a settled assertion
|
||||
@@ -207,5 +328,18 @@ contract OptimisticOracle {
|
||||
* @param assertionId The unique identifier of the assertion to get resolution for
|
||||
* @return The final boolean outcome of the assertion
|
||||
*/
|
||||
function getResolution(uint256 assertionId) external view returns (bool) {}
|
||||
function getResolution(uint256 assertionId) external view returns (bool) {
|
||||
EventAssertion memory assertion = assertions[assertionId];
|
||||
|
||||
if (assertion.asserter == address(0)) revert AssertionNotFound();
|
||||
if (assertion.proposer == address(0)) revert NotProposedAssertion();
|
||||
|
||||
if (assertion.winner != address(0)) return assertion.resolvedOutcome;
|
||||
if (assertion.disputer == address(0)) {
|
||||
if (block.timestamp <= assertion.endTime) revert InvalidTime();
|
||||
return assertion.proposedOutcome;
|
||||
}
|
||||
|
||||
revert AwaitingDecider();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user