Skip to content

Commit

Permalink
feat: Create PriceFeedQuoted contract
Browse files Browse the repository at this point in the history
  • Loading branch information
rbajollari committed Aug 28, 2024
1 parent 17e698e commit ea78d14
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 0 deletions.
34 changes: 34 additions & 0 deletions contracts/pricefeedquoted/CloneFactory.sol
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);
}
}
137 changes: 137 additions & 0 deletions contracts/pricefeedquoted/PriceFeedQuoted.sol
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
);
}
}

0 comments on commit ea78d14

Please sign in to comment.