mirror of
https://github.com/luxfi/uma.git
synced 2026-10-30 15:14:44 +00:00
139 lines
6.8 KiB
Solidity
139 lines
6.8 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.0;
|
|
|
|
import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseChildTunnel.sol";
|
|
import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol";
|
|
import "../data-verification-mechanism/interfaces/RegistryInterface.sol";
|
|
import "./OracleBaseTunnel.sol";
|
|
import "../common/implementation/AncillaryData.sol";
|
|
import "../common/implementation/Lockable.sol";
|
|
|
|
/**
|
|
* @title Adapter deployed on sidechain to give financial contracts the ability to trigger cross-chain price requests to
|
|
* the mainnet DVM. Also has the ability to receive published prices from mainnet. This contract can be treated as the
|
|
* "DVM" for this network, because a calling contract can request and access a resolved price request from this
|
|
* contract.
|
|
* @dev The intended client of this contract is an OptimisticOracle on sidechain that needs price
|
|
* resolution secured by the DVM on mainnet.
|
|
*/
|
|
contract OracleChildTunnel is OracleBaseTunnel, OracleAncillaryInterface, FxBaseChildTunnel, Lockable {
|
|
constructor(address _fxChild, address _finderAddress)
|
|
OracleBaseTunnel(_finderAddress)
|
|
FxBaseChildTunnel(_fxChild)
|
|
{}
|
|
|
|
// This assumes that the local network has a Registry that resembles the mainnet registry.
|
|
modifier onlyRegisteredContract() {
|
|
RegistryInterface registry = RegistryInterface(finder.getImplementationAddress(OracleInterfaces.Registry));
|
|
require(registry.isContractRegistered(msg.sender), "Caller must be registered");
|
|
_;
|
|
}
|
|
|
|
/**
|
|
* @notice This should be called to bridge a price request to mainnet.
|
|
* @dev Can be called only by a registered contract that is allowed to make DVM price requests. Will mark this
|
|
* price request as Requested, and therefore able to receive the price resolution data from mainnet. Emits a message
|
|
* that will be included in regular checkpoint of all sidechain transactions to mainnet.
|
|
* @param identifier Identifier of price request.
|
|
* @param time Timestamp of price request.
|
|
* @param ancillaryData extra data of price request.
|
|
*/
|
|
function requestPrice(
|
|
bytes32 identifier,
|
|
uint256 time,
|
|
bytes memory ancillaryData
|
|
) public override nonReentrant() onlyRegisteredContract() {
|
|
// This implementation allows duplicate price requests to emit duplicate MessageSent events via
|
|
// _sendMessageToRoot. The DVM will not have a problem handling duplicate requests (it will just ignore them).
|
|
// This is potentially a fallback in case the checkpointing to mainnet is missing the `requestPrice` transaction
|
|
// for some reason. There is little risk in duplicating MessageSent emissions because the sidechain bridge
|
|
// does not impose any rate-limiting and this method is only callable by registered callers.
|
|
_requestPrice(identifier, time, _stampAncillaryData(ancillaryData, msg.sender));
|
|
_sendMessageToRoot(abi.encode(identifier, time, _stampAncillaryData(ancillaryData, msg.sender)));
|
|
}
|
|
|
|
/**
|
|
* @notice Resolves a price request.
|
|
* @dev The data will be received automatically from the state receiver when the state is synced between Ethereum
|
|
* and Polygon. This will revert if the Root chain sender is not the `fxRootTunnel` contract.
|
|
* @param sender The sender of `data` from the Root chain.
|
|
* @param data ABI encoded params with which to call `_publishPrice`.
|
|
*/
|
|
function _processMessageFromRoot(
|
|
uint256, /* stateId */
|
|
address sender,
|
|
bytes memory data
|
|
) internal override validateSender(sender) {
|
|
(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) =
|
|
abi.decode(data, (bytes32, uint256, bytes, int256));
|
|
_publishPrice(identifier, time, ancillaryData, price);
|
|
}
|
|
|
|
/**
|
|
* @notice Returns whether a price has resolved for the request.
|
|
* @param identifier Identifier of price request.
|
|
* @param time Timestamp of price request
|
|
* @param ancillaryData extra data of price request.
|
|
* @return True if a price is available, False otherwise. If true, then getPrice will succeed for the request.
|
|
*/
|
|
function hasPrice(
|
|
bytes32 identifier,
|
|
uint256 time,
|
|
bytes memory ancillaryData
|
|
) public view override nonReentrantView() onlyRegisteredContract() returns (bool) {
|
|
bytes32 priceRequestId = _encodePriceRequest(identifier, time, _stampAncillaryData(ancillaryData, msg.sender));
|
|
return prices[priceRequestId].state == RequestState.Resolved;
|
|
}
|
|
|
|
/**
|
|
* @notice Returns resolved price for the request.
|
|
* @dev Reverts if price is not available.
|
|
* @param identifier Identifier of price request.
|
|
* @param time Timestamp of price request
|
|
* @param ancillaryData extra data of price request.
|
|
* @return int256 Price, or reverts if no resolved price for any reason.
|
|
*/
|
|
function getPrice(
|
|
bytes32 identifier,
|
|
uint256 time,
|
|
bytes memory ancillaryData
|
|
) public view override nonReentrantView() onlyRegisteredContract() returns (int256) {
|
|
bytes32 priceRequestId = _encodePriceRequest(identifier, time, _stampAncillaryData(ancillaryData, msg.sender));
|
|
Price storage lookup = prices[priceRequestId];
|
|
require(lookup.state == RequestState.Resolved, "Price has not been resolved");
|
|
return lookup.price;
|
|
}
|
|
|
|
/**
|
|
* @notice Generates stamped ancillary data in the format that it would be used in the case of a price request.
|
|
* @param ancillaryData ancillary data of the price being requested.
|
|
* @param requester sender of the initial price request.
|
|
* @return the stamped ancillary bytes.
|
|
*/
|
|
function stampAncillaryData(bytes memory ancillaryData, address requester)
|
|
public
|
|
view
|
|
nonReentrantView()
|
|
returns (bytes memory)
|
|
{
|
|
return _stampAncillaryData(ancillaryData, requester);
|
|
}
|
|
|
|
/**
|
|
* @dev We don't handle specifically the case where `ancillaryData` is not already readily translatable in utf8.
|
|
* For those cases, we assume that the client will be able to strip out the utf8-translatable part of the
|
|
* ancillary data that this contract stamps.
|
|
*/
|
|
function _stampAncillaryData(bytes memory ancillaryData, address requester) internal view returns (bytes memory) {
|
|
// Price requests that originate from this method, on Polygon, will ultimately be submitted to the DVM on
|
|
// Ethereum via the OracleRootTunnel. Therefore this contract should stamp its requester's address in the
|
|
// ancillary data so voters can conveniently track the requests path to the DVM.
|
|
return
|
|
AncillaryData.appendKeyValueUint(
|
|
AncillaryData.appendKeyValueAddress(ancillaryData, "childRequester", requester),
|
|
"childChainId",
|
|
block.chainid
|
|
);
|
|
}
|
|
}
|