-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Create PriceFeedQuoted contract
- Loading branch information
1 parent
17e698e
commit ea78d14
Showing
2 changed files
with
171 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "./PriceFeedQuoted.sol"; | ||
import "@openzeppelin/contracts/proxy/Clones.sol"; | ||
|
||
/// @title Factory for creating PriceFeedQuoted contract clones. | ||
/// @notice This contract will create a PriceFeedQuoted clone and map its address to the clone creator. | ||
/// @dev Cloning is done with OpenZeppelin's Clones contract. | ||
contract CloneFactory { | ||
event PriceFeedQuotedCloneCreated( | ||
address _priceFeedCloneAddress | ||
); | ||
|
||
mapping (address => address) public PriceFeedQuotedCloneAddresses; | ||
address public implementationAddress; | ||
|
||
/// @param _implementationAddress Address of implementation contract to be cloned. | ||
constructor(address _implementationAddress) { | ||
implementationAddress = _implementationAddress; | ||
} | ||
|
||
/// @notice Create clone of PriceFeedQuoted contract and initialize it. | ||
/// @dev Clone method returns address of created clone. | ||
/// @param _priceFeedDecimals Amount of decimals a PriceFeed is denominiated in. | ||
/// @param _priceFeedBase Base asset of PriceFeed, should be set to asset symbol ticker. | ||
/// @param _priceFeedQuote Quote asset of PriceFeed, should be set to asset symbol ticker. | ||
function createPriceFeedQuoted(uint8 _priceFeedDecimals, string calldata _priceFeedBase, string calldata _priceFeedQuote) external { | ||
address priceFeedCloneAddress = Clones.clone(implementationAddress); | ||
PriceFeedQuoted(priceFeedCloneAddress).initialize(_priceFeedDecimals, _priceFeedBase, _priceFeedQuote); | ||
PriceFeedQuotedCloneAddresses[msg.sender] = priceFeedCloneAddress; | ||
emit PriceFeedQuotedCloneCreated(priceFeedCloneAddress); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; | ||
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; | ||
import "../IOjo.sol"; | ||
import "../OjoTypes.sol"; | ||
|
||
/// @title Contract for calling Ojo's oracle contract with chainlink's AggregatorV3Interface implemented. | ||
/// @author Ojo Network (https://docs.ojo.network/) | ||
contract PriceFeedQuoted is Initializable, AggregatorV3Interface { | ||
uint8 private priceFeedDecimals; | ||
|
||
string private priceFeedBase; | ||
|
||
string private priceFeedQuote; | ||
|
||
IOjo public immutable ojo; | ||
|
||
uint80 constant DEFAULT_ROUND = 1; | ||
|
||
uint256 constant DEFAULT_VERSION = 1; | ||
|
||
uint256 internal constant INT256_MAX = uint256(type(int256).max); | ||
|
||
error GetRoundDataCanBeOnlyCalledWithLatestRound(uint80 requestedRoundId); | ||
|
||
error UnsafeUintToIntConversion(uint256 value); | ||
|
||
constructor(address ojo_) { | ||
ojo = IOjo(ojo_); | ||
} | ||
|
||
/// @notice Initialize clone of this contract. | ||
/// @dev This function is used in place of a constructor in proxy contracts. | ||
/// @param _priceFeedDecimals Amount of decimals a PriceFeed is denominiated in. | ||
/// @param _priceFeedBase Base asset of PriceFeed. | ||
/// @param _priceFeedQuote Quote asset of PriceFeed. | ||
function initialize(uint8 _priceFeedDecimals, string calldata _priceFeedBase, string calldata _priceFeedQuote) | ||
external | ||
initializer { | ||
priceFeedDecimals = _priceFeedDecimals; | ||
_priceFeedBase = _priceFeedBase; | ||
_priceFeedQuote = _priceFeedQuote; | ||
} | ||
|
||
/// @notice Amount of decimals price is denominated in. | ||
function decimals() external view returns (uint8) { | ||
return priceFeedDecimals; | ||
} | ||
|
||
/// @notice Asset pair that this proxy is tracking. | ||
function description() external view returns (string memory) { | ||
return string(abi.encodePacked(priceFeedBase, "/", priceFeedQuote)); | ||
} | ||
|
||
/// @notice Version always returns 1. | ||
function version() external view returns (uint256) { | ||
return DEFAULT_VERSION; | ||
} | ||
|
||
/// @dev Latest round always returns 1 since this contract does not support rounds. | ||
function latestRound() public pure returns (uint80) { | ||
return DEFAULT_ROUND; | ||
} | ||
|
||
/// @notice Fetches price data from Ojo contract from a specified round. | ||
/// @dev Even though rounds are not utilized in this contract getRoundData is implemented for contracts | ||
/// that still rely on it. Function will revert if specified round is not the latest round. | ||
/// @return roundId Round ID of price data, this is always set to 1. | ||
/// @return answer Price in USD of asset this contract is tracking. | ||
/// @return startedAt Timestamp relating to price update. | ||
/// @return updatedAt Timestamp relating to price update. | ||
/// @return answeredInRound Equal to round ID. | ||
function getRoundData(uint80 _roundId) | ||
external | ||
view | ||
returns ( | ||
uint80 roundId, | ||
int256 answer, | ||
uint256 startedAt, | ||
uint256 updatedAt, | ||
uint80 answeredInRound | ||
) { | ||
if (_roundId != latestRound()) { | ||
revert GetRoundDataCanBeOnlyCalledWithLatestRound(_roundId); | ||
} | ||
return latestRoundData(); | ||
} | ||
|
||
/// @notice Fetches latest price data from Ojo contract. | ||
/// @return roundId Round ID of price data, this is always set to 1. | ||
/// @return answer Price of priceFeedBase quoted by priceFeedQuote. | ||
/// @return startedAt Timestamp relating to price update. | ||
/// @return updatedAt Timestamp relating to price update. | ||
/// @return answeredInRound Equal to round ID. | ||
function latestRoundData() | ||
public | ||
view | ||
returns ( | ||
uint80 roundId, | ||
int256 answer, | ||
uint256 startedAt, | ||
uint256 updatedAt, | ||
uint80 answeredInRound | ||
) { | ||
roundId = latestRound(); | ||
bytes32 baseAssetName = bytes32(bytes(priceFeedBase)); | ||
bytes32 quoteAssetName = bytes32(bytes(priceFeedQuote)); | ||
|
||
OjoTypes.PriceData memory basePriceData = ojo.getPriceData(baseAssetName); | ||
OjoTypes.PriceData memory quotePriceData = ojo.getPriceData(quoteAssetName); | ||
|
||
require(quotePriceData.price > 0, "PriceFeed: Quote asset price is zero"); | ||
|
||
if (basePriceData.price > INT256_MAX || quotePriceData.price > INT256_MAX) { | ||
revert UnsafeUintToIntConversion(basePriceData.price > INT256_MAX ? basePriceData.price : quotePriceData.price); | ||
} | ||
|
||
answer = int256(basePriceData.price) * 1e18 / int256(quotePriceData.price); | ||
|
||
// These values are equal after chainlink’s OCR update | ||
startedAt = basePriceData.resolveTime < quotePriceData.resolveTime ? basePriceData.resolveTime : quotePriceData.resolveTime; | ||
updatedAt = startedAt; | ||
|
||
// roundId is always equal to answeredInRound | ||
answeredInRound = roundId; | ||
|
||
return ( | ||
roundId, | ||
answer, | ||
startedAt, | ||
updatedAt, | ||
answeredInRound | ||
); | ||
} | ||
} |