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.
|
* @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
|
* @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)
|
* @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.
|
* Reverts with IndexOutOfBounds, if the provided index is >= oracles.length.
|
||||||
* @param index The index of the oracle to remove from the oracles array
|
* @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
|
* @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.
|
* of remaining valid prices. Uses StatisticsUtils for sorting and median calculation.
|
||||||
* @return The median price from all active oracles
|
* @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
|
* @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.
|
* for gas optimization.
|
||||||
* @return An array of addresses representing the currently active oracle contracts
|
* @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.
|
* @dev Creates a new OracleNode struct and adds the sender to the nodeAddresses array.
|
||||||
* Requires minimum stake amount and prevents duplicate registrations.
|
* 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)
|
* @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.
|
* This creates a chain of finalized buckets, ensuring all past reports are accountable.
|
||||||
* @param price The new price value to report
|
* @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
|
* @notice Allows active and inactive nodes to claim accumulated ORA token rewards
|
||||||
* @dev Calculates rewards based on time elapsed since last claim.
|
* @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
|
* @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
|
* @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.
|
* @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
|
* @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
|
* @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 bucketNumber,
|
||||||
uint256 reportIndex,
|
uint256 reportIndex,
|
||||||
uint256 nodeAddressesIndex
|
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
|
* @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.
|
* 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
|
* @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 /////
|
/// View Functions /////
|
||||||
@@ -174,21 +285,34 @@ contract StakingOracle {
|
|||||||
* @notice Returns the list of registered oracle node addresses
|
* @notice Returns the list of registered oracle node addresses
|
||||||
* @return Array 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
|
* @notice Returns the stored median price from the most recently completed bucket
|
||||||
* @dev Requires that the median for the bucket be recorded via recordBucketMedian
|
* @dev Requires that the median for the bucket be recorded via recordBucketMedian
|
||||||
* @return The median price for the last finalized bucket
|
* @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
|
* @notice Returns the stored median price from a specified bucket
|
||||||
* @param bucketNumber The bucket number to read the median price from
|
* @param bucketNumber The bucket number to read the median price from
|
||||||
* @return The median price stored for the bucket
|
* @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
|
* @notice Returns the price and slashed status of a node at a given bucket
|
||||||
@@ -200,20 +324,70 @@ contract StakingOracle {
|
|||||||
function getSlashedStatus(
|
function getSlashedStatus(
|
||||||
address nodeAddress,
|
address nodeAddress,
|
||||||
uint256 bucketNumber
|
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
|
* @notice Returns the effective stake accounting for inactivity penalties via missed buckets
|
||||||
* @dev Effective stake = stakedAmount - (missedBuckets * INACTIVITY_PENALTY), floored at 0
|
* @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
|
* @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
|
* @param bucketNumber The bucket number to get the outliers from
|
||||||
* @return Array of node addresses considered outliers
|
* @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 ///
|
/// Internal Functions ///
|
||||||
@@ -224,7 +398,18 @@ contract StakingOracle {
|
|||||||
* @param nodeAddress The address of the node to remove
|
* @param nodeAddress The address of the node to remove
|
||||||
* @param index The index 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
|
* @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
|
* @param medianPrice The average price of the bucket
|
||||||
* @return True if the price deviation is greater than the threshold, false otherwise
|
* @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,
|
string memory description,
|
||||||
uint256 startTime,
|
uint256 startTime,
|
||||||
uint256 endTime
|
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
|
* @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 assertionId The unique identifier of the assertion to propose an outcome for
|
||||||
* @param outcome The proposed boolean outcome (true or false) for the event
|
* @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
|
* @notice Disputes a proposed outcome by bonding ETH
|
||||||
@@ -158,7 +201,19 @@ contract OptimisticOracle {
|
|||||||
* and must be within the dispute window after proposal.
|
* and must be within the dispute window after proposal.
|
||||||
* @param assertionId The unique identifier of the assertion to dispute
|
* @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
|
* @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.
|
* Can only be called after dispute window has passed without disputes.
|
||||||
* @param assertionId The unique identifier of the assertion to claim rewards for
|
* @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
|
* @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.
|
* Can only be called after decider has settled the dispute.
|
||||||
* @param assertionId The unique identifier of the disputed assertion to claim rewards for
|
* @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
|
* @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.
|
* 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
|
* @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)
|
* @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 assertionId The unique identifier of the disputed assertion to settle
|
||||||
* @param resolvedOutcome The decider's determination of the true outcome
|
* @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
|
* @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
|
* @param assertionId The unique identifier of the assertion to check state for
|
||||||
* @return The current State enum value representing the assertion's status
|
* @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
|
* @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
|
* @param assertionId The unique identifier of the assertion to get resolution for
|
||||||
* @return The final boolean outcome of the assertion
|
* @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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
packages/hardhat/deployments-backup/localhost/.chainId
Normal file
1
packages/hardhat/deployments-backup/localhost/.chainId
Normal file
@@ -0,0 +1 @@
|
|||||||
|
31337
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"language": "Solidity",
|
||||||
|
"sources": {
|
||||||
|
"contracts/00_Whitelist/SimpleOracle.sol": {
|
||||||
|
"content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\ncontract SimpleOracle {\n /////////////////\n /// Errors //////\n /////////////////\n\n error OnlyOwner();\n\n //////////////////////\n /// State Variables //\n //////////////////////\n\n uint256 public price;\n uint256 public timestamp;\n address public owner;\n\n ////////////////\n /// Events /////\n ////////////////\n\n event PriceUpdated(uint256 newPrice);\n\n ///////////////////\n /// Constructor ///\n ///////////////////\n\n constructor(address _owner) {\n owner = _owner;\n }\n\n ///////////////////\n /// Modifiers /////\n ///////////////////\n\n /**\n * @notice Modifier to restrict function access to the contract owner\n * @dev Currently disabled to make it easy for you to impersonate the owner\n */\n modifier onlyOwner() {\n // Intentionally removing the owner requirement to make it easy for you to impersonate the owner\n // if (msg.sender != owner) revert OnlyOwner();\n _;\n }\n\n ///////////////////\n /// Functions /////\n ///////////////////\n\n /**\n * @notice Updates the oracle price with a new value (only contract owner)\n * @dev Sets the price and records the current block timestamp for freshness tracking.\n * Emits PriceUpdated event upon successful update.\n * @param _newPrice The new price value to set for this oracle\n */\n function setPrice(uint256 _newPrice) public onlyOwner {\n price = _newPrice;\n timestamp = block.timestamp;\n emit PriceUpdated(_newPrice);\n }\n\n /**\n * @notice Returns the current price and its timestamp\n * @dev Provides both the stored price value and when it was last updated.\n * Used by aggregators to determine price freshness.\n * @return price The current price stored in this oracle\n * @return timestamp The block timestamp when the price was last updated\n */\n function getPrice() public view returns (uint256, uint256) {\n return (price, timestamp);\n }\n}\n"
|
||||||
|
},
|
||||||
|
"contracts/00_Whitelist/WhitelistOracle.sol": {
|
||||||
|
"content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\nimport \"./SimpleOracle.sol\";\nimport { StatisticsUtils } from \"../utils/StatisticsUtils.sol\";\n\ncontract WhitelistOracle {\n using StatisticsUtils for uint256[];\n\n /////////////////\n /// Errors //////\n /////////////////\n\n error OnlyOwner();\n error IndexOutOfBounds();\n error NoOraclesAvailable();\n\n //////////////////////\n /// State Variables //\n //////////////////////\n\n address public owner;\n SimpleOracle[] public oracles;\n uint256 public constant STALE_DATA_WINDOW = 24 seconds;\n\n ////////////////\n /// Events /////\n ////////////////\n\n event OracleAdded(address oracleAddress, address oracleOwner);\n event OracleRemoved(address oracleAddress);\n\n ///////////////////\n /// Modifiers /////\n ///////////////////\n\n /**\n * @notice Modifier to restrict function access to the contract owner\n * @dev Currently disabled to make it easy for you to impersonate the owner\n */\n modifier onlyOwner() {\n // if (msg.sender != owner) revert OnlyOwner();\n _;\n }\n\n ///////////////////\n /// Constructor ///\n ///////////////////\n\n constructor() {\n owner = msg.sender;\n }\n\n ///////////////////\n /// Functions /////\n ///////////////////\n\n /**\n * @notice Adds a new oracle to the whitelist by deploying a SimpleOracle contract (only contract owner)\n * @dev Creates a new SimpleOracle instance and adds it to the oracles array.\n * @param _owner The address that will own the newly created oracle and can update its price\n */\n function addOracle(address _owner) public onlyOwner {\n SimpleOracle newOracle = new SimpleOracle(_owner);\n oracles.push(newOracle);\n emit OracleAdded(address(newOracle), _owner);\n }\n\n /**\n * @notice Removes an oracle from the whitelist by its array index (only contract owner)\n * @dev Uses swap-and-pop pattern for gas-efficient removal. Order is not preserved.\n * Reverts with IndexOutOfBounds, if the provided index is >= oracles.length.\n * @param index The index of the oracle to remove from the oracles array\n */\n function removeOracle(uint256 index) public onlyOwner {\n uint256 oraclesSize = oracles.length;\n if (index >= oraclesSize) revert IndexOutOfBounds();\n\n address deletedOracle = address(oracles[index]);\n if (index != oraclesSize - 1) {\n oracles[index] = oracles[oraclesSize - 1];\n }\n \n oracles.pop();\n emit OracleRemoved(deletedOracle);\n }\n\n /**\n * @notice Returns the aggregated price from all active oracles using median calculation\n * @dev Filters oracles with timestamps older than STALE_DATA_WINDOW, then calculates median\n * of remaining valid prices. Uses StatisticsUtils for sorting and median calculation.\n * @return The median price from all active oracles\n */\n function getPrice() public view returns (uint256) {\n if (oracles.length == 0) revert NoOraclesAvailable();\n uint256[] memory prices = new uint256[](oracles.length);\n uint256 priceIndex = 0;\n uint256 currentTime = block.timestamp;\n\n for (uint256 i = 0; i < oracles.length; i++) {\n (uint256 price, uint256 timestamp) = oracles[i].getPrice();\n\tif (currentTime - timestamp < STALE_DATA_WINDOW) {\n\t prices[priceIndex] = price;\n\t priceIndex++;\n\t}\n }\n\n uint256[] memory fixedPrices = new uint256[](priceIndex);\n for (uint256 i = 0; i < priceIndex; i++) {\n fixedPrices[i] = prices[i];\n }\n\n fixedPrices.sort();\n return fixedPrices.getMedian();\n }\n\n /**\n * @notice Returns the addresses of all oracles that have updated their price within the last STALE_DATA_WINDOW\n * @dev Iterates through all oracles and filters those with recent timestamps (within STALE_DATA_WINDOW).\n * Uses a temporary array to collect active nodes, then creates a right-sized return array\n * for gas optimization.\n * @return An array of addresses representing the currently active oracle contracts\n */\n function getActiveOracleNodes() public view returns (address[] memory) {\n address[] memory activeOracles = new address[](oracles.length);\n uint256 oracleIndex = 0;\n uint256 currentTime = block.timestamp;\n\n for (uint256 i = 0; i < oracles.length; i++) {\n (, uint256 timestamp) = oracles[i].getPrice();\n\tif (currentTime - timestamp < STALE_DATA_WINDOW) {\n\t activeOracles[oracleIndex] = address(oracles[i]);\n\t oracleIndex++;\n\t}\n }\n\n address[] memory fixedActiveOracles = new address[](oracleIndex);\n for (uint256 i = 0; i < oracleIndex; i++) {\n fixedActiveOracles[i] = activeOracles[i];\n }\n\n return fixedActiveOracles;\n }\n}\n"
|
||||||
|
},
|
||||||
|
"contracts/utils/StatisticsUtils.sol": {
|
||||||
|
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\nlibrary StatisticsUtils {\n /////////////////\n /// Errors //////\n /////////////////\n\n error EmptyArray();\n\n ///////////////////\n /// Functions /////\n ///////////////////\n\n /**\n * @notice Sorts an array of uint256 values in ascending order using selection sort\n * @dev Uses selection sort algorithm which is not gas-efficient but acceptable for small arrays.\n * This implementation mimics the early MakerDAO Medianizer exactly.\n * Modifies the input array in-place.\n * @param arr The array of uint256 values to sort in ascending order\n */\n function sort(uint256[] memory arr) internal pure {\n uint256 n = arr.length;\n for (uint256 i = 0; i < n; i++) {\n uint256 minIndex = i;\n for (uint256 j = i + 1; j < n; j++) {\n if (arr[j] < arr[minIndex]) {\n minIndex = j;\n }\n }\n if (minIndex != i) {\n (arr[i], arr[minIndex]) = (arr[minIndex], arr[i]);\n }\n }\n }\n\n /**\n * @notice Calculates the median value from a sorted array of uint256 values\n * @dev For arrays with even length, returns the average of the two middle elements.\n * For arrays with odd length, returns the middle element.\n * Assumes the input array is already sorted in ascending order.\n * @param arr The sorted array of uint256 values to calculate median from\n * @return The median value as a uint256\n */\n function getMedian(uint256[] memory arr) internal pure returns (uint256) {\n uint256 length = arr.length;\n if (length == 0) revert EmptyArray();\n if (length % 2 == 0) {\n return (arr[length / 2 - 1] + arr[length / 2]) / 2;\n } else {\n return arr[length / 2];\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"optimizer": {
|
||||||
|
"enabled": true,
|
||||||
|
"runs": 200
|
||||||
|
},
|
||||||
|
"evmVersion": "paris",
|
||||||
|
"outputSelection": {
|
||||||
|
"*": {
|
||||||
|
"*": [
|
||||||
|
"abi",
|
||||||
|
"evm.bytecode",
|
||||||
|
"evm.deployedBytecode",
|
||||||
|
"evm.methodIdentifiers",
|
||||||
|
"metadata",
|
||||||
|
"devdoc",
|
||||||
|
"userdoc",
|
||||||
|
"storageLayout",
|
||||||
|
"evm.gasEstimates"
|
||||||
|
],
|
||||||
|
"": [
|
||||||
|
"ast"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"useLiteralContent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/hardhat/deployments/sepolia/.chainId
Normal file
1
packages/hardhat/deployments/sepolia/.chainId
Normal file
@@ -0,0 +1 @@
|
|||||||
|
11155111
|
||||||
173
packages/hardhat/deployments/sepolia/Decider.json
Normal file
173
packages/hardhat/deployments/sepolia/Decider.json
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
{
|
||||||
|
"address": "0x0e59BE27d04248289DE58287473E5096F5B0487C",
|
||||||
|
"abi": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"internalType": "address",
|
||||||
|
"name": "_oracle",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "constructor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "uint256",
|
||||||
|
"name": "assertionId",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "bool",
|
||||||
|
"name": "resolvedValue",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "DisputeSettled",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [],
|
||||||
|
"name": "oracle",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"internalType": "contract IOptimisticOracle",
|
||||||
|
"name": "",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [],
|
||||||
|
"name": "owner",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"internalType": "address",
|
||||||
|
"name": "",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"internalType": "address",
|
||||||
|
"name": "newOracle",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "setOracle",
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"internalType": "uint256",
|
||||||
|
"name": "assertionId",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"internalType": "bool",
|
||||||
|
"name": "resolvedValue",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "settleDispute",
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"stateMutability": "payable",
|
||||||
|
"type": "receive"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transactionHash": "0xa91a5d382d894d17d7cbb46620b53c568bb143438008ea10103dc1151f64d620",
|
||||||
|
"receipt": {
|
||||||
|
"to": null,
|
||||||
|
"from": "0x23E53302800Ccd8a5b35cFA576B6eB73dab297B4",
|
||||||
|
"contractAddress": "0x0e59BE27d04248289DE58287473E5096F5B0487C",
|
||||||
|
"transactionIndex": 32,
|
||||||
|
"gasUsed": "263881",
|
||||||
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"blockHash": "0x5ca6f755b57b850f8d00c326e7c5bfc6abe1007b930a2cd279c35a37123c2087",
|
||||||
|
"transactionHash": "0xa91a5d382d894d17d7cbb46620b53c568bb143438008ea10103dc1151f64d620",
|
||||||
|
"logs": [],
|
||||||
|
"blockNumber": 10119863,
|
||||||
|
"cumulativeGasUsed": "4616639",
|
||||||
|
"status": 1,
|
||||||
|
"byzantium": true
|
||||||
|
},
|
||||||
|
"args": [
|
||||||
|
"0x4E23664C19a80Bc8Cc12EF03d0286B9A6C88874E"
|
||||||
|
],
|
||||||
|
"numDeployments": 1,
|
||||||
|
"solcInputHash": "9fcc1190b90bc26fb4272727bfdaa3b9",
|
||||||
|
"metadata": "{\"compiler\":{\"version\":\"0.8.20+commit.a1b79de6\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_oracle\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"assertionId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"resolvedValue\",\"type\":\"bool\"}],\"name\":\"DisputeSettled\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"oracle\",\"outputs\":[{\"internalType\":\"contract IOptimisticOracle\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOracle\",\"type\":\"address\"}],\"name\":\"setOracle\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"assertionId\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"resolvedValue\",\"type\":\"bool\"}],\"name\":\"settleDispute\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"settleDispute(uint256,bool)\":{\"params\":{\"assertionId\":\"The ID of the assertion to settle\",\"resolvedValue\":\"The true/false outcome determined by the decider\"}}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"settleDispute(uint256,bool)\":{\"notice\":\"Settle a dispute by determining the true/false outcome\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/02_Optimistic/Decider.sol\":\"Decider\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"contracts/02_Optimistic/Decider.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity >=0.8.0 <0.9.0;\\n\\ncontract Decider {\\n address public owner;\\n IOptimisticOracle public oracle;\\n\\n event DisputeSettled(uint256 indexed assertionId, bool resolvedValue);\\n\\n constructor(address _oracle) {\\n owner = msg.sender;\\n oracle = IOptimisticOracle(_oracle);\\n }\\n\\n /**\\n * @notice Settle a dispute by determining the true/false outcome\\n * @param assertionId The ID of the assertion to settle\\n * @param resolvedValue The true/false outcome determined by the decider\\n */\\n function settleDispute(uint256 assertionId, bool resolvedValue) external {\\n require(assertionId >= 1, \\\"Invalid assertion ID\\\");\\n\\n // Call the oracle's settleAssertion function\\n oracle.settleAssertion(assertionId, resolvedValue);\\n\\n emit DisputeSettled(assertionId, resolvedValue);\\n }\\n\\n function setOracle(address newOracle) external {\\n require(msg.sender == owner, \\\"Only owner can set oracle\\\");\\n oracle = IOptimisticOracle(newOracle);\\n }\\n\\n /**\\n * @notice Allow the contract to receive ETH\\n */\\n receive() external payable {}\\n}\\n\\ninterface IOptimisticOracle {\\n function settleAssertion(uint256, bool) external;\\n}\\n\",\"keccak256\":\"0x83fb5fb04f9e279460ed8c2becd4f2ed3947a4b337aa2b2c2be72943363b4cfa\",\"license\":\"MIT\"}},\"version\":1}",
|
||||||
|
"bytecode": "0x608060405234801561001057600080fd5b5060405161039838038061039883398101604081905261002f91610062565b60008054336001600160a01b031991821617909155600180549091166001600160a01b0392909216919091179055610092565b60006020828403121561007457600080fd5b81516001600160a01b038116811461008b57600080fd5b9392505050565b6102f7806100a16000396000f3fe6080604052600436106100435760003560e01c80637adbf9731461004f5780637dc0d1d0146100715780638da5cb5b146100ad57806392305435146100cd57600080fd5b3661004a57005b600080fd5b34801561005b57600080fd5b5061006f61006a36600461025c565b6100ed565b005b34801561007d57600080fd5b50600154610091906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b3480156100b957600080fd5b50600054610091906001600160a01b031681565b3480156100d957600080fd5b5061006f6100e836600461028c565b61016e565b6000546001600160a01b0316331461014c5760405162461bcd60e51b815260206004820152601960248201527f4f6e6c79206f776e65722063616e20736574206f7261636c650000000000000060448201526064015b60405180910390fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b60018210156101b65760405162461bcd60e51b8152602060048201526014602482015273125b9d985b1a5908185cdcd95c9d1a5bdb88125160621b6044820152606401610143565b60015460405163db4295b760e01b81526004810184905282151560248201526001600160a01b039091169063db4295b790604401600060405180830381600087803b15801561020457600080fd5b505af1158015610218573d6000803e3d6000fd5b50505050817faa76612075dcf5624c76b21b3b0f6babbeee4e6b6197fb4fb4a2a5654724812a82604051610250911515815260200190565b60405180910390a25050565b60006020828403121561026e57600080fd5b81356001600160a01b038116811461028557600080fd5b9392505050565b6000806040838503121561029f57600080fd5b82359150602083013580151581146102b657600080fd5b80915050925092905056fea2646970667358221220df429ac8e47150c8c85951bd8fc77176f0666a75f4a159519e06068afd5a16d164736f6c63430008140033",
|
||||||
|
"deployedBytecode": "0x6080604052600436106100435760003560e01c80637adbf9731461004f5780637dc0d1d0146100715780638da5cb5b146100ad57806392305435146100cd57600080fd5b3661004a57005b600080fd5b34801561005b57600080fd5b5061006f61006a36600461025c565b6100ed565b005b34801561007d57600080fd5b50600154610091906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b3480156100b957600080fd5b50600054610091906001600160a01b031681565b3480156100d957600080fd5b5061006f6100e836600461028c565b61016e565b6000546001600160a01b0316331461014c5760405162461bcd60e51b815260206004820152601960248201527f4f6e6c79206f776e65722063616e20736574206f7261636c650000000000000060448201526064015b60405180910390fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b60018210156101b65760405162461bcd60e51b8152602060048201526014602482015273125b9d985b1a5908185cdcd95c9d1a5bdb88125160621b6044820152606401610143565b60015460405163db4295b760e01b81526004810184905282151560248201526001600160a01b039091169063db4295b790604401600060405180830381600087803b15801561020457600080fd5b505af1158015610218573d6000803e3d6000fd5b50505050817faa76612075dcf5624c76b21b3b0f6babbeee4e6b6197fb4fb4a2a5654724812a82604051610250911515815260200190565b60405180910390a25050565b60006020828403121561026e57600080fd5b81356001600160a01b038116811461028557600080fd5b9392505050565b6000806040838503121561029f57600080fd5b82359150602083013580151581146102b657600080fd5b80915050925092905056fea2646970667358221220df429ac8e47150c8c85951bd8fc77176f0666a75f4a159519e06068afd5a16d164736f6c63430008140033",
|
||||||
|
"devdoc": {
|
||||||
|
"kind": "dev",
|
||||||
|
"methods": {
|
||||||
|
"settleDispute(uint256,bool)": {
|
||||||
|
"params": {
|
||||||
|
"assertionId": "The ID of the assertion to settle",
|
||||||
|
"resolvedValue": "The true/false outcome determined by the decider"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"userdoc": {
|
||||||
|
"kind": "user",
|
||||||
|
"methods": {
|
||||||
|
"settleDispute(uint256,bool)": {
|
||||||
|
"notice": "Settle a dispute by determining the true/false outcome"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"storageLayout": {
|
||||||
|
"storage": [
|
||||||
|
{
|
||||||
|
"astId": 1562,
|
||||||
|
"contract": "contracts/02_Optimistic/Decider.sol:Decider",
|
||||||
|
"label": "owner",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "0",
|
||||||
|
"type": "t_address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 1565,
|
||||||
|
"contract": "contracts/02_Optimistic/Decider.sol:Decider",
|
||||||
|
"label": "oracle",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "1",
|
||||||
|
"type": "t_contract(IOptimisticOracle)1650"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": {
|
||||||
|
"t_address": {
|
||||||
|
"encoding": "inplace",
|
||||||
|
"label": "address",
|
||||||
|
"numberOfBytes": "20"
|
||||||
|
},
|
||||||
|
"t_contract(IOptimisticOracle)1650": {
|
||||||
|
"encoding": "inplace",
|
||||||
|
"label": "contract IOptimisticOracle",
|
||||||
|
"numberOfBytes": "20"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
784
packages/hardhat/deployments/sepolia/ORA.json
Normal file
784
packages/hardhat/deployments/sepolia/ORA.json
Normal file
File diff suppressed because one or more lines are too long
972
packages/hardhat/deployments/sepolia/OptimisticOracle.json
Normal file
972
packages/hardhat/deployments/sepolia/OptimisticOracle.json
Normal file
File diff suppressed because one or more lines are too long
1028
packages/hardhat/deployments/sepolia/StakingOracle.json
Normal file
1028
packages/hardhat/deployments/sepolia/StakingOracle.json
Normal file
File diff suppressed because one or more lines are too long
290
packages/hardhat/deployments/sepolia/WhitelistOracle.json
Normal file
290
packages/hardhat/deployments/sepolia/WhitelistOracle.json
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"language": "Solidity",
|
||||||
|
"sources": {
|
||||||
|
"contracts/00_Whitelist/SimpleOracle.sol": {
|
||||||
|
"content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\ncontract SimpleOracle {\n /////////////////\n /// Errors //////\n /////////////////\n\n error OnlyOwner();\n\n //////////////////////\n /// State Variables //\n //////////////////////\n\n uint256 public price;\n uint256 public timestamp;\n address public owner;\n\n ////////////////\n /// Events /////\n ////////////////\n\n event PriceUpdated(uint256 newPrice);\n\n ///////////////////\n /// Constructor ///\n ///////////////////\n\n constructor(address _owner) {\n owner = _owner;\n }\n\n ///////////////////\n /// Modifiers /////\n ///////////////////\n\n /**\n * @notice Modifier to restrict function access to the contract owner\n * @dev Currently disabled to make it easy for you to impersonate the owner\n */\n modifier onlyOwner() {\n // Intentionally removing the owner requirement to make it easy for you to impersonate the owner\n // if (msg.sender != owner) revert OnlyOwner();\n _;\n }\n\n ///////////////////\n /// Functions /////\n ///////////////////\n\n /**\n * @notice Updates the oracle price with a new value (only contract owner)\n * @dev Sets the price and records the current block timestamp for freshness tracking.\n * Emits PriceUpdated event upon successful update.\n * @param _newPrice The new price value to set for this oracle\n */\n function setPrice(uint256 _newPrice) public onlyOwner {\n price = _newPrice;\n timestamp = block.timestamp;\n emit PriceUpdated(_newPrice);\n }\n\n /**\n * @notice Returns the current price and its timestamp\n * @dev Provides both the stored price value and when it was last updated.\n * Used by aggregators to determine price freshness.\n * @return price The current price stored in this oracle\n * @return timestamp The block timestamp when the price was last updated\n */\n function getPrice() public view returns (uint256, uint256) {\n return (price, timestamp);\n }\n}\n"
|
||||||
|
},
|
||||||
|
"contracts/00_Whitelist/WhitelistOracle.sol": {
|
||||||
|
"content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\nimport \"./SimpleOracle.sol\";\nimport { StatisticsUtils } from \"../utils/StatisticsUtils.sol\";\n\ncontract WhitelistOracle {\n using StatisticsUtils for uint256[];\n\n /////////////////\n /// Errors //////\n /////////////////\n\n error OnlyOwner();\n error IndexOutOfBounds();\n error NoOraclesAvailable();\n\n //////////////////////\n /// State Variables //\n //////////////////////\n\n address public owner;\n SimpleOracle[] public oracles;\n uint256 public constant STALE_DATA_WINDOW = 24 seconds;\n\n ////////////////\n /// Events /////\n ////////////////\n\n event OracleAdded(address oracleAddress, address oracleOwner);\n event OracleRemoved(address oracleAddress);\n\n ///////////////////\n /// Modifiers /////\n ///////////////////\n\n /**\n * @notice Modifier to restrict function access to the contract owner\n * @dev Currently disabled to make it easy for you to impersonate the owner\n */\n modifier onlyOwner() {\n // if (msg.sender != owner) revert OnlyOwner();\n _;\n }\n\n ///////////////////\n /// Constructor ///\n ///////////////////\n\n constructor() {\n owner = msg.sender;\n }\n\n ///////////////////\n /// Functions /////\n ///////////////////\n\n /**\n * @notice Adds a new oracle to the whitelist by deploying a SimpleOracle contract (only contract owner)\n * @dev Creates a new SimpleOracle instance and adds it to the oracles array.\n * @param _owner The address that will own the newly created oracle and can update its price\n */\n function addOracle(address _owner) public onlyOwner {\n SimpleOracle newOracle = new SimpleOracle(_owner);\n oracles.push(newOracle);\n emit OracleAdded(address(newOracle), _owner);\n }\n\n /**\n * @notice Removes an oracle from the whitelist by its array index (only contract owner)\n * @dev Uses swap-and-pop pattern for gas-efficient removal. Order is not preserved.\n * Reverts with IndexOutOfBounds, if the provided index is >= oracles.length.\n * @param index The index of the oracle to remove from the oracles array\n */\n function removeOracle(uint256 index) public onlyOwner {\n uint256 oraclesSize = oracles.length;\n if (index >= oraclesSize) revert IndexOutOfBounds();\n\n address deletedOracle = address(oracles[index]);\n if (index != oraclesSize - 1) {\n oracles[index] = oracles[oraclesSize - 1];\n }\n \n oracles.pop();\n emit OracleRemoved(deletedOracle);\n }\n\n /**\n * @notice Returns the aggregated price from all active oracles using median calculation\n * @dev Filters oracles with timestamps older than STALE_DATA_WINDOW, then calculates median\n * of remaining valid prices. Uses StatisticsUtils for sorting and median calculation.\n * @return The median price from all active oracles\n */\n function getPrice() public view returns (uint256) {\n if (oracles.length == 0) revert NoOraclesAvailable();\n uint256[] memory prices = new uint256[](oracles.length);\n uint256 priceIndex = 0;\n uint256 currentTime = block.timestamp;\n\n for (uint256 i = 0; i < oracles.length; i++) {\n (uint256 price, uint256 timestamp) = oracles[i].getPrice();\n\tif (currentTime - timestamp < STALE_DATA_WINDOW) {\n\t prices[priceIndex] = price;\n\t priceIndex++;\n\t}\n }\n\n uint256[] memory fixedPrices = new uint256[](priceIndex);\n for (uint256 i = 0; i < priceIndex; i++) {\n fixedPrices[i] = prices[i];\n }\n\n fixedPrices.sort();\n return fixedPrices.getMedian();\n }\n\n /**\n * @notice Returns the addresses of all oracles that have updated their price within the last STALE_DATA_WINDOW\n * @dev Iterates through all oracles and filters those with recent timestamps (within STALE_DATA_WINDOW).\n * Uses a temporary array to collect active nodes, then creates a right-sized return array\n * for gas optimization.\n * @return An array of addresses representing the currently active oracle contracts\n */\n function getActiveOracleNodes() public view returns (address[] memory) {\n address[] memory activeOracles = new address[](oracles.length);\n uint256 oracleIndex = 0;\n uint256 currentTime = block.timestamp;\n\n for (uint256 i = 0; i < oracles.length; i++) {\n (, uint256 timestamp) = oracles[i].getPrice();\n\tif (currentTime - timestamp < STALE_DATA_WINDOW) {\n\t activeOracles[oracleIndex] = address(oracles[i]);\n\t oracleIndex++;\n\t}\n }\n\n address[] memory fixedActiveOracles = new address[](oracleIndex);\n for (uint256 i = 0; i < oracleIndex; i++) {\n fixedActiveOracles[i] = activeOracles[i];\n }\n\n return fixedActiveOracles;\n }\n}\n"
|
||||||
|
},
|
||||||
|
"contracts/utils/StatisticsUtils.sol": {
|
||||||
|
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\nlibrary StatisticsUtils {\n /////////////////\n /// Errors //////\n /////////////////\n\n error EmptyArray();\n\n ///////////////////\n /// Functions /////\n ///////////////////\n\n /**\n * @notice Sorts an array of uint256 values in ascending order using selection sort\n * @dev Uses selection sort algorithm which is not gas-efficient but acceptable for small arrays.\n * This implementation mimics the early MakerDAO Medianizer exactly.\n * Modifies the input array in-place.\n * @param arr The array of uint256 values to sort in ascending order\n */\n function sort(uint256[] memory arr) internal pure {\n uint256 n = arr.length;\n for (uint256 i = 0; i < n; i++) {\n uint256 minIndex = i;\n for (uint256 j = i + 1; j < n; j++) {\n if (arr[j] < arr[minIndex]) {\n minIndex = j;\n }\n }\n if (minIndex != i) {\n (arr[i], arr[minIndex]) = (arr[minIndex], arr[i]);\n }\n }\n }\n\n /**\n * @notice Calculates the median value from a sorted array of uint256 values\n * @dev For arrays with even length, returns the average of the two middle elements.\n * For arrays with odd length, returns the middle element.\n * Assumes the input array is already sorted in ascending order.\n * @param arr The sorted array of uint256 values to calculate median from\n * @return The median value as a uint256\n */\n function getMedian(uint256[] memory arr) internal pure returns (uint256) {\n uint256 length = arr.length;\n if (length == 0) revert EmptyArray();\n if (length % 2 == 0) {\n return (arr[length / 2 - 1] + arr[length / 2]) / 2;\n } else {\n return arr[length / 2];\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"optimizer": {
|
||||||
|
"enabled": true,
|
||||||
|
"runs": 200
|
||||||
|
},
|
||||||
|
"evmVersion": "paris",
|
||||||
|
"outputSelection": {
|
||||||
|
"*": {
|
||||||
|
"*": [
|
||||||
|
"abi",
|
||||||
|
"evm.bytecode",
|
||||||
|
"evm.deployedBytecode",
|
||||||
|
"evm.methodIdentifiers",
|
||||||
|
"metadata",
|
||||||
|
"devdoc",
|
||||||
|
"userdoc",
|
||||||
|
"storageLayout",
|
||||||
|
"evm.gasEstimates"
|
||||||
|
],
|
||||||
|
"": [
|
||||||
|
"ast"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"useLiteralContent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -39,7 +39,7 @@ const config: HardhatUserConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
defaultNetwork: "localhost",
|
defaultNetwork: "sepolia",//"localhost",
|
||||||
namedAccounts: {
|
namedAccounts: {
|
||||||
deployer: {
|
deployer: {
|
||||||
// By default, it will take the first Hardhat account as the deployer
|
// By default, it will take the first Hardhat account as the deployer
|
||||||
@@ -47,10 +47,10 @@ const config: HardhatUserConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
mainnet: {
|
/*mainnet: {
|
||||||
url: "https://mainnet.rpc.buidlguidl.com",
|
url: "https://mainnet.rpc.buidlguidl.com",
|
||||||
accounts: [deployerPrivateKey],
|
accounts: [deployerPrivateKey],
|
||||||
},
|
},*/
|
||||||
sepolia: {
|
sepolia: {
|
||||||
url: `https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`,
|
url: `https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`,
|
||||||
accounts: [deployerPrivateKey],
|
accounts: [deployerPrivateKey],
|
||||||
|
|||||||
@@ -16,13 +16,15 @@ const DAI_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
|
|||||||
const WETH_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
const WETH_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
||||||
const UNISWAP_V2_FACTORY = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f";
|
const UNISWAP_V2_FACTORY = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f";
|
||||||
const mainnet = hardhatConfig.networks.mainnet;
|
const mainnet = hardhatConfig.networks.mainnet;
|
||||||
const MAINNET_RPC = "url" in mainnet ? mainnet.url : "";
|
const MAINNET_RPC = ""; //"url" in mainnet ? mainnet.url : "";
|
||||||
|
|
||||||
export const fetchPriceFromUniswap = async (): Promise<bigint> => {
|
export const fetchPriceFromUniswap = async (): Promise<bigint> => {
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
const cachedPrice = config.PRICE.CACHEDPRICE;
|
const cachedPrice = config.PRICE.CACHEDPRICE;
|
||||||
const timestamp = config.PRICE.TIMESTAMP;
|
const timestamp = config.PRICE.TIMESTAMP;
|
||||||
|
|
||||||
|
return parseEther(cachedPrice.toString());
|
||||||
|
|
||||||
if (Date.now() - timestamp < 1000 * 60 * 60) {
|
if (Date.now() - timestamp < 1000 * 60 * 60) {
|
||||||
return parseEther(cachedPrice.toString());
|
return parseEther(cachedPrice.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"PRICE": {
|
"PRICE": {
|
||||||
"CACHEDPRICE": 4000,
|
"CACHEDPRICE": 2943.585895512704,
|
||||||
"TIMESTAMP": 1761680177006
|
"TIMESTAMP": 1769312154674
|
||||||
},
|
},
|
||||||
"INTERVALS": {
|
"INTERVALS": {
|
||||||
"PRICE_REPORT": 1750,
|
"PRICE_REPORT": 1750,
|
||||||
@@ -13,44 +13,44 @@
|
|||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0
|
||||||
},
|
},
|
||||||
"0x70997970c51812dc3a010c7d01b50e0d17dc79c8": {
|
"0x70997970c51812dc3a010c7d01b50e0d17dc79c8": {
|
||||||
"PROBABILITY_OF_SKIPPING_REPORT": 0,
|
"PROBABILITY_OF_SKIPPING_REPORT": 0.45,
|
||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0.66
|
||||||
},
|
},
|
||||||
"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": {
|
"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": {
|
||||||
"PROBABILITY_OF_SKIPPING_REPORT": 0,
|
"PROBABILITY_OF_SKIPPING_REPORT": 0.56,
|
||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0.23
|
||||||
},
|
},
|
||||||
"0x976ea74026e726554db657fa54763abd0c3a0aa9": {
|
"0x976ea74026e726554db657fa54763abd0c3a0aa9": {
|
||||||
"PROBABILITY_OF_SKIPPING_REPORT": 0,
|
"PROBABILITY_OF_SKIPPING_REPORT": 0.95,
|
||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0.18
|
||||||
},
|
},
|
||||||
"0x90f79bf6eb2c4f870365e785982e1f101e93b906": {
|
"0x90f79bf6eb2c4f870365e785982e1f101e93b906": {
|
||||||
"PROBABILITY_OF_SKIPPING_REPORT": 0,
|
"PROBABILITY_OF_SKIPPING_REPORT": 0.85,
|
||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0.3
|
||||||
},
|
},
|
||||||
"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": {
|
"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": {
|
||||||
"PROBABILITY_OF_SKIPPING_REPORT": 0,
|
"PROBABILITY_OF_SKIPPING_REPORT": 0.34,
|
||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0.25
|
||||||
},
|
},
|
||||||
"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": {
|
"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": {
|
||||||
"PROBABILITY_OF_SKIPPING_REPORT": 0,
|
"PROBABILITY_OF_SKIPPING_REPORT": 0.46,
|
||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0.83
|
||||||
},
|
},
|
||||||
"0xbcd4042de499d14e55001ccbb24a551f3b954096": {
|
"0xbcd4042de499d14e55001ccbb24a551f3b954096": {
|
||||||
"PROBABILITY_OF_SKIPPING_REPORT": 0,
|
"PROBABILITY_OF_SKIPPING_REPORT": 0.69,
|
||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0.61
|
||||||
},
|
},
|
||||||
"0x14dc79964da2c08b23698b3d3cc7ca32193d9955": {
|
"0x14dc79964da2c08b23698b3d3cc7ca32193d9955": {
|
||||||
"PROBABILITY_OF_SKIPPING_REPORT": 0,
|
"PROBABILITY_OF_SKIPPING_REPORT": 0.42,
|
||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0.68
|
||||||
},
|
},
|
||||||
"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": {
|
"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": {
|
||||||
"PROBABILITY_OF_SKIPPING_REPORT": 0,
|
"PROBABILITY_OF_SKIPPING_REPORT": 0.69,
|
||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0
|
||||||
},
|
},
|
||||||
"0xa0ee7a142d267c1f36714e4a8f75612f20a79720": {
|
"0xa0ee7a142d267c1f36714e4a8f75612f20a79720": {
|
||||||
"PROBABILITY_OF_SKIPPING_REPORT": 0,
|
"PROBABILITY_OF_SKIPPING_REPORT": 0.85,
|
||||||
"PRICE_VARIANCE": 0
|
"PRICE_VARIANCE": 0.34
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
1
packages/nextjs/next-env.d.ts
vendored
1
packages/nextjs/next-env.d.ts
vendored
@@ -1,6 +1,5 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
/// <reference path="./.next/types/routes.d.ts" />
|
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const DEFAULT_ALCHEMY_API_KEY = "cR4WnXePioePZ5fFrnSiR";
|
|||||||
|
|
||||||
const scaffoldConfig = {
|
const scaffoldConfig = {
|
||||||
// The networks on which your DApp is live
|
// The networks on which your DApp is live
|
||||||
targetNetworks: [chains.hardhat],
|
targetNetworks: [chains.sepolia], //chains.hardhat],
|
||||||
// The interval at which your front-end polls the RPC servers for new data (it has no effect if you only target the local network (default is 4000))
|
// The interval at which your front-end polls the RPC servers for new data (it has no effect if you only target the local network (default is 4000))
|
||||||
pollingInterval: 30000,
|
pollingInterval: 30000,
|
||||||
// This is ours Alchemy's default API key.
|
// This is ours Alchemy's default API key.
|
||||||
@@ -34,7 +34,7 @@ const scaffoldConfig = {
|
|||||||
// It's recommended to store it in an env variable:
|
// It's recommended to store it in an env variable:
|
||||||
// .env.local for local testing, and in the Vercel/system env config for live apps.
|
// .env.local for local testing, and in the Vercel/system env config for live apps.
|
||||||
walletConnectProjectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID || "3a8170812b534d0ff9d794f19a901d64",
|
walletConnectProjectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID || "3a8170812b534d0ff9d794f19a901d64",
|
||||||
onlyLocalBurnerWallet: true,
|
onlyLocalBurnerWallet: false,
|
||||||
} as const satisfies ScaffoldConfig;
|
} as const satisfies ScaffoldConfig;
|
||||||
|
|
||||||
export default scaffoldConfig;
|
export default scaffoldConfig;
|
||||||
|
|||||||
Reference in New Issue
Block a user