-
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: YieldNest price feed contract (#56)
- Loading branch information
1 parent
41f6c74
commit 1c617cf
Showing
9 changed files
with
396 additions
and
23 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
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,6 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
interface IynViewer { | ||
function getRate() external view returns (uint256); | ||
} |
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,44 @@ | ||
pragma solidity ^0.8.20; | ||
|
||
import "./ynPriceFeed.sol"; | ||
import "@openzeppelin/contracts/proxy/Clones.sol"; | ||
|
||
/// @title Factory for creating ynPriceFeed contract clones. | ||
/// @notice This contract will create a ynPriceFeed clone and map its address to the clone creator. | ||
/// @dev Cloning is done with OpenZeppelin's Clones contract. | ||
contract CloneFactory { | ||
event ynPriceFeedCloneCreated( | ||
address _ynPriceFeedCloneAddress | ||
); | ||
|
||
mapping (address => address) public ynPriceFeedCloneAddresses; | ||
address public implementationAddress; | ||
|
||
/// @param _implementationAddress Address of implementation contract to be cloned. | ||
constructor(address _implementationAddress) { | ||
implementationAddress = _implementationAddress; | ||
} | ||
|
||
/// @notice Create clone of ynPriceFeed contract and initialize it. | ||
/// @dev Clone method returns address of created clone. | ||
/// @param _ynViewer Address of ynViewer contract. | ||
/// @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 createYnPriceFeed( | ||
address _ynViewer, | ||
uint8 _priceFeedDecimals, | ||
string calldata _priceFeedBase, | ||
string calldata _priceFeedQuote | ||
) external { | ||
address ynPriceFeedCloneAddress = Clones.clone(implementationAddress); | ||
ynPriceFeed(ynPriceFeedCloneAddress).initialize( | ||
_ynViewer, | ||
_priceFeedDecimals, | ||
_priceFeedBase, | ||
_priceFeedQuote | ||
); | ||
ynPriceFeedCloneAddresses[msg.sender] = ynPriceFeedCloneAddress; | ||
emit ynPriceFeedCloneCreated(ynPriceFeedCloneAddress); | ||
} | ||
} |
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,126 @@ | ||
// 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 "../yn/interfaces/IynViewer.sol"; | ||
|
||
/// @title Contract for retreiving a YieldNest LRT's exchange rate value with chainlink's AggregatorV3Interface | ||
/// implemented. | ||
/// @author Ojo Network (https://docs.ojo.network/) | ||
contract ynPriceFeed is Initializable, AggregatorV3Interface { | ||
uint8 private priceFeedDecimals; | ||
|
||
string private priceFeedBase; | ||
|
||
string private priceFeedQuote; | ||
|
||
address public ynViewer; | ||
|
||
uint80 constant DEFAULT_ROUND = 1; | ||
|
||
uint256 constant DEFAULT_VERSION = 1; | ||
|
||
uint256 internal constant INT256_MAX = uint256(type(int256).max); | ||
|
||
error GetRoundDataCanBeOnlyCalledWithLatestRound(uint80 requestedRoundId); | ||
|
||
/// @notice Initialize clone of this contract. | ||
/// @dev This function is used in place of a constructor in proxy contracts. | ||
/// @param _ynViewer Address of ynViewer contract. | ||
/// @param _priceFeedDecimals Amount of decimals a PriceFeed is denominiated in. | ||
/// @param _priceFeedBase Base asset of PriceFeed. | ||
/// @param _priceFeedQuote Quote asset of PriceFeed. | ||
function initialize( | ||
address _ynViewer, | ||
uint8 _priceFeedDecimals, | ||
string calldata _priceFeedBase, | ||
string calldata _priceFeedQuote | ||
) external initializer { | ||
ynViewer = _ynViewer; | ||
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 Calculates exchange rate from the ynViewer 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 Calculates exchange rate from the ynViewer 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(); | ||
|
||
IynViewer ynViewer_ = IynViewer(ynViewer); | ||
answer = int256(ynViewer_.getRate()); | ||
|
||
// These values are equal after chainlink’s OCR update | ||
startedAt = block.timestamp; | ||
updatedAt = startedAt; | ||
|
||
// roundId is always equal to answeredInRound | ||
answeredInRound = roundId; | ||
|
||
return ( | ||
roundId, | ||
answer, | ||
startedAt, | ||
updatedAt, | ||
answeredInRound | ||
); | ||
} | ||
} |
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
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,61 @@ | ||
import { Wallet, ethers } from "ethers"; | ||
import CloneFactory from '../artifacts/contracts/ynpricefeed/CloneFactory.sol/CloneFactory.json'; | ||
import testnet_chains from '../testnet_chains.json'; | ||
import mainnet_chains from '../mainnet_chains.json'; | ||
|
||
async function main() { | ||
const evmChains = JSON.parse(process.env.EVM_CHAINS!); | ||
const ynPriceFeedDecimals = process.env.PRICE_FEED_DECIMALS as any; | ||
const ynPriceFeeds = JSON.parse(process.env.YN_PRICE_FEEDS!); | ||
const ynViewers = JSON.parse(process.env.YN_VIEWERS!); | ||
|
||
if (ynPriceFeeds.length !== ynViewers.length) { | ||
throw new Error('unequal amount of ynViewers associated with ynPriceFeeds'); | ||
} | ||
|
||
const privateKey = process.env.PRIVATE_KEY; | ||
|
||
if (!privateKey) { | ||
throw new Error('Invalid private key. Make sure the PRIVATE_KEY environment variable is set.'); | ||
} | ||
|
||
const mainnet = process.env.MAINNET as string | ||
let chains = testnet_chains.map((chain) => ({ ...chain })); | ||
if (mainnet === "TRUE") { | ||
chains = mainnet_chains.map((chain) => ({ ...chain })); | ||
} | ||
|
||
for (const chain of chains) { | ||
if (evmChains.includes(chain.name)) { | ||
const provider = new ethers.JsonRpcProvider(chain.rpc) | ||
const wallet = new Wallet(privateKey, provider); | ||
const balance = await provider.getBalance(wallet.address) | ||
console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); | ||
|
||
const cloneFactoryYnContract = new ethers.Contract(chain.cloneFactoryYn, CloneFactory.abi, wallet) | ||
let i = 0 | ||
for (const ynPriceFeed of ynPriceFeeds) { | ||
console.log(`Deploying ${ynPriceFeed} price feed on ${chain.name}`); | ||
try { | ||
const [baseAsset, quoteAsset] = ynPriceFeed.split('/'); | ||
|
||
console.log("baseAsset", baseAsset) | ||
console.log("quoteAsset", quoteAsset) | ||
const tx = await cloneFactoryYnContract.createYnPriceFeed(ynViewers[i], ynPriceFeedDecimals, baseAsset, quoteAsset); | ||
console.log(`Transaction sent: ${tx.hash}`); | ||
|
||
const receipt = await tx.wait(); | ||
console.log(`Transaction mined: ${receipt.transactionHash}`); | ||
} catch (error) { | ||
console.error(`Failed to deploy ${ynPriceFeed} on ${chain.name}:`, error); | ||
} | ||
i += 1 | ||
} | ||
} | ||
} | ||
} | ||
|
||
main().catch((error) => { | ||
console.error(error); | ||
process.exitCode = 1; | ||
}); |
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,38 @@ | ||
import { Wallet, ethers } from "ethers"; | ||
import CloneFactoryQuoted from '../artifacts/contracts/ynpricefeed/CloneFactory.sol/CloneFactory.json'; | ||
import testnet_chains from '../testnet_chains.json'; | ||
import mainnet_chains from '../mainnet_chains.json'; | ||
|
||
async function main () { | ||
const evmChains = JSON.parse(process.env.EVM_CHAINS!); | ||
|
||
const privateKey = process.env.PRIVATE_KEY; | ||
|
||
if (!privateKey) { | ||
throw new Error('Invalid private key. Make sure the PRIVATE_KEY environment variable is set.'); | ||
} | ||
|
||
const mainnet = process.env.MAINNET as string | ||
let chains = testnet_chains.map((chain) => ({ ...chain })); | ||
if (mainnet === "TRUE") { | ||
chains = mainnet_chains.map((chain) => ({ ...chain })); | ||
} | ||
|
||
for (const chain of chains) { | ||
if (evmChains.includes(chain.name)) { | ||
const provider = new ethers.JsonRpcProvider(chain.rpc) | ||
const wallet = new Wallet(privateKey, provider); | ||
const balance = await provider.getBalance(wallet.address) | ||
console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); | ||
|
||
const cloneFactoryQuotedFactory = new ethers.ContractFactory(CloneFactoryQuoted.abi, CloneFactoryQuoted.bytecode, wallet) | ||
const cloneFactoryQuoted = await cloneFactoryQuotedFactory.deploy(chain.ynPriceFeedImplementation) | ||
console.log(`${chain.name}, address: ${await cloneFactoryQuoted.getAddress()}`); | ||
} | ||
} | ||
} | ||
|
||
main().catch((error) => { | ||
console.error(error); | ||
process.exitCode = 1; | ||
}); |
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,38 @@ | ||
import { Wallet, ethers } from "ethers"; | ||
import ynPriceFeed from '../artifacts/contracts/ynpricefeed/ynPriceFeed.sol/ynPriceFeed.json'; | ||
import testnet_chains from '../testnet_chains.json'; | ||
import mainnet_chains from '../mainnet_chains.json'; | ||
|
||
async function main() { | ||
const evmChains = JSON.parse(process.env.EVM_CHAINS!); | ||
|
||
const privateKey = process.env.PRIVATE_KEY; | ||
|
||
if (!privateKey) { | ||
throw new Error('Invalid private key. Make sure the PRIVATE_KEY environment variable is set.'); | ||
} | ||
|
||
const mainnet = process.env.MAINNET as string | ||
let chains = testnet_chains.map((chain) => ({ ...chain })); | ||
if (mainnet === "TRUE") { | ||
chains = mainnet_chains.map((chain) => ({ ...chain })); | ||
} | ||
|
||
for (const chain of chains) { | ||
if (evmChains.includes(chain.name)) { | ||
const provider = new ethers.JsonRpcProvider(chain.rpc) | ||
const wallet = new Wallet(privateKey, provider); | ||
const balance = await provider.getBalance(wallet.address) | ||
console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); | ||
|
||
const ynPriceFeedFactory = new ethers.ContractFactory(ynPriceFeed.abi, ynPriceFeed.bytecode, wallet) | ||
const ynPriceFeedImplementation = await ynPriceFeedFactory.deploy() | ||
console.log(`${chain.name}, address: ${await ynPriceFeedImplementation.getAddress()}`); | ||
} | ||
} | ||
} | ||
|
||
main().catch((error) => { | ||
console.error(error); | ||
process.exitCode = 1; | ||
}); |
Oops, something went wrong.