mirror of
https://github.com/luxfi/uma.git
synced 2026-10-30 15:14:44 +00:00
143 lines
5.6 KiB
Solidity
143 lines
5.6 KiB
Solidity
// SPDX-License-Identifier: AGPL-3.0-only
|
|
pragma solidity ^0.8.0;
|
|
|
|
import "../../common/implementation/Testable.sol";
|
|
import "../interfaces/OracleInterface.sol";
|
|
import "../interfaces/IdentifierWhitelistInterface.sol";
|
|
import "../interfaces/FinderInterface.sol";
|
|
import "../implementation/Constants.sol";
|
|
|
|
// A mock oracle used for testing.
|
|
contract MockOracle is OracleInterface, Testable {
|
|
// Represents an available price. Have to keep a separate bool to allow for price=0.
|
|
struct Price {
|
|
bool isAvailable;
|
|
int256 price;
|
|
// Time the verified price became available.
|
|
uint256 verifiedTime;
|
|
}
|
|
|
|
// The two structs below are used in an array and mapping to keep track of prices that have been requested but are
|
|
// not yet available.
|
|
struct QueryIndex {
|
|
bool isValid;
|
|
uint256 index;
|
|
}
|
|
|
|
// Represents a (identifier, time) point that has been queried.
|
|
struct QueryPoint {
|
|
bytes32 identifier;
|
|
uint256 time;
|
|
}
|
|
|
|
// Reference to the Finder.
|
|
FinderInterface private finder;
|
|
|
|
// Maps request IDs to their resolved Price structs.
|
|
mapping(bytes32 => Price) private verifiedPrices;
|
|
|
|
// Maps request IDs to their pending QueryIndex structs.
|
|
mapping(bytes32 => QueryIndex) private queryIndices;
|
|
|
|
// Array of pending QueryPoint structs.
|
|
QueryPoint[] private requestedPrices;
|
|
|
|
event PriceRequestAdded(
|
|
address indexed requester,
|
|
bytes32 indexed identifier,
|
|
uint256 time,
|
|
bytes32 indexed requestId
|
|
);
|
|
event PushedPrice(
|
|
address indexed pusher,
|
|
bytes32 indexed identifier,
|
|
uint256 time,
|
|
int256 price,
|
|
bytes32 indexed requestId
|
|
);
|
|
|
|
constructor(address _finderAddress, address _timerAddress) Testable(_timerAddress) {
|
|
finder = FinderInterface(_finderAddress);
|
|
}
|
|
|
|
// Enqueues a request (if a request isn't already present) for the given (identifier, time) pair.
|
|
|
|
function requestPrice(bytes32 identifier, uint256 time) public override {
|
|
require(_getIdentifierWhitelist().isIdentifierSupported(identifier));
|
|
bytes32 requestId = _encodePriceRequest(identifier, time);
|
|
Price storage lookup = verifiedPrices[requestId];
|
|
if (!lookup.isAvailable && !queryIndices[requestId].isValid) {
|
|
// New query, enqueue it for review.
|
|
queryIndices[requestId] = QueryIndex(true, requestedPrices.length);
|
|
requestedPrices.push(QueryPoint(identifier, time));
|
|
emit PriceRequestAdded(msg.sender, identifier, time, requestId);
|
|
}
|
|
}
|
|
|
|
// Pushes the verified price for a requested query.
|
|
function pushPrice(
|
|
bytes32 identifier,
|
|
uint256 time,
|
|
int256 price
|
|
) public {
|
|
bytes32 requestId = _encodePriceRequest(identifier, time);
|
|
verifiedPrices[requestId] = Price(true, price, getCurrentTime());
|
|
|
|
QueryIndex storage queryIndex = queryIndices[requestId];
|
|
require(queryIndex.isValid, "Can't push prices that haven't been requested");
|
|
// Delete from the array. Instead of shifting the queries over, replace the contents of `indexToReplace` with
|
|
// the contents of the last index (unless it is the last index).
|
|
uint256 indexToReplace = queryIndex.index;
|
|
delete queryIndices[requestId];
|
|
uint256 lastIndex = requestedPrices.length - 1;
|
|
if (lastIndex != indexToReplace) {
|
|
QueryPoint storage queryToCopy = requestedPrices[lastIndex];
|
|
queryIndices[_encodePriceRequest(queryToCopy.identifier, queryToCopy.time)].index = indexToReplace;
|
|
requestedPrices[indexToReplace] = queryToCopy;
|
|
}
|
|
requestedPrices.pop();
|
|
|
|
emit PushedPrice(msg.sender, identifier, time, price, requestId);
|
|
}
|
|
|
|
// Wrapper function to push the verified price by request ID.
|
|
function pushPriceByRequestId(bytes32 requestId, int256 price) external {
|
|
QueryPoint memory queryPoint = getRequestParameters(requestId);
|
|
pushPrice(queryPoint.identifier, queryPoint.time, price);
|
|
}
|
|
|
|
// Checks whether a price has been resolved.
|
|
function hasPrice(bytes32 identifier, uint256 time) public view override returns (bool) {
|
|
Price storage lookup = verifiedPrices[_encodePriceRequest(identifier, time)];
|
|
return lookup.isAvailable;
|
|
}
|
|
|
|
// Gets a price that has already been resolved.
|
|
function getPrice(bytes32 identifier, uint256 time) public view override returns (int256) {
|
|
Price storage lookup = verifiedPrices[_encodePriceRequest(identifier, time)];
|
|
require(lookup.isAvailable);
|
|
return lookup.price;
|
|
}
|
|
|
|
// Gets the queries that still need verified prices.
|
|
function getPendingQueries() external view returns (QueryPoint[] memory) {
|
|
return requestedPrices;
|
|
}
|
|
|
|
// Gets the request parameters by request ID.
|
|
function getRequestParameters(bytes32 requestId) public view returns (QueryPoint memory) {
|
|
QueryIndex storage queryIndex = queryIndices[requestId];
|
|
require(queryIndex.isValid, "Request ID not found");
|
|
return requestedPrices[queryIndex.index];
|
|
}
|
|
|
|
function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface supportedIdentifiers) {
|
|
return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist));
|
|
}
|
|
|
|
// Returns an encoded bytes32 representing a price request ID. Used when storing/referencing price requests.
|
|
function _encodePriceRequest(bytes32 identifier, uint256 time) internal pure returns (bytes32) {
|
|
return keccak256(abi.encode(identifier, time));
|
|
}
|
|
}
|