-
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: Inception LRT Price Feed (#58)
- Loading branch information
1 parent
7464a84
commit 7dbb63c
Showing
9 changed files
with
383 additions
and
1 deletion.
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,7 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
interface IVault { | ||
/// @dev returns the total deposited into asset strategy | ||
function getTotalDeposited() 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,48 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "./InceptionPriceFeed.sol"; | ||
import "@openzeppelin/contracts/proxy/Clones.sol"; | ||
|
||
/// @title Factory for creating InceptionPriceFeed contract clones. | ||
/// @notice This contract will create a InceptionPriceFeed clone and map its address to the clone creator. | ||
/// @dev Cloning is done with OpenZeppelin's Clones contract. | ||
contract CloneFactory { | ||
event InceptionPriceFeedCloneCreated( | ||
address _inceptionPriceFeedCloneAddress | ||
); | ||
|
||
mapping (address => address) public InceptionPriceFeedCloneAddresses; | ||
address public implementationAddress; | ||
|
||
/// @param _implementationAddress Address of implementation contract to be cloned. | ||
constructor(address _implementationAddress) { | ||
implementationAddress = _implementationAddress; | ||
} | ||
|
||
/// @notice Create clone of InceptionPriceFeed contract and initialize it. | ||
/// @dev Clone method returns address of created clone. | ||
/// @param _vault Address of Inception vault contract. | ||
/// @param _inceptionLRT Address of Inception LRT token. | ||
/// @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 createInceptionPriceFeed( | ||
address _vault, | ||
address _inceptionLRT, | ||
uint8 _priceFeedDecimals, | ||
string calldata _priceFeedBase, | ||
string calldata _priceFeedQuote | ||
) external { | ||
address inceptionPriceFeedCloneAddress = Clones.clone(implementationAddress); | ||
InceptionPriceFeed(inceptionPriceFeedCloneAddress).initialize( | ||
_vault, | ||
_inceptionLRT, | ||
_priceFeedDecimals, | ||
_priceFeedBase, | ||
_priceFeedQuote | ||
); | ||
InceptionPriceFeedCloneAddresses[msg.sender] = inceptionPriceFeedCloneAddress; | ||
emit InceptionPriceFeedCloneCreated(inceptionPriceFeedCloneAddress); | ||
} | ||
} |
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,138 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; | ||
import "../inception/interfaces/IVault.sol"; | ||
|
||
/// @title Contract for retreiving a Inception LRT's exchange rate value with chainlink's AggregatorV3Interface | ||
/// implemented. | ||
/// @author Ojo Network (https://docs.ojo.network/) | ||
contract InceptionPriceFeed is Initializable, AggregatorV3Interface { | ||
uint8 private priceFeedDecimals; | ||
|
||
string private priceFeedBase; | ||
|
||
string private priceFeedQuote; | ||
|
||
address public vault; | ||
|
||
address public inceptionLRT; | ||
|
||
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 _vault Address of Inception vault contract. | ||
/// @param _inceptionLRT Address of Inception LRT token. | ||
/// @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 _vault, | ||
address _inceptionLRT, | ||
uint8 _priceFeedDecimals, | ||
string calldata _priceFeedBase, | ||
string calldata _priceFeedQuote | ||
) external initializer { | ||
vault = _vault; | ||
inceptionLRT = _inceptionLRT; | ||
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 Inception LRT Vault 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 Inception LRT Vault. | ||
/// @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(); | ||
|
||
IVault vault_ = IVault(vault); | ||
ERC20 inceptionLRT_ = ERC20(inceptionLRT); | ||
|
||
answer = 0; | ||
|
||
if (inceptionLRT_.totalSupply() != 0) { | ||
answer = int256(vault_.getTotalDeposited()) * 1e18 / int256(inceptionLRT_.totalSupply()); | ||
} | ||
|
||
// 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,62 @@ | ||
import { Wallet, ethers } from "ethers"; | ||
import CloneFactory from '../artifacts/contracts/inceptionpricefeed/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 inceptionPriceFeedDecimals = process.env.PRICE_FEED_DECIMALS as any; | ||
const inceptionPriceFeeds = JSON.parse(process.env.INCEPTION_PRICE_FEEDS!); | ||
const inceptionVaults = JSON.parse(process.env.INCEPTION_VAULTS!); | ||
const inceptionLRTAddresses = JSON.parse(process.env.INCEPTION_LRT_ADDRESS!); | ||
|
||
if (inceptionPriceFeeds.length !== inceptionVaults.length || inceptionPriceFeeds.length !== inceptionLRTAddresses.length) { | ||
throw new Error('unequal amount of inceptionVaults associated with inceptionPriceFeeds'); | ||
} | ||
|
||
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 cloneFactoryInceptionContract = new ethers.Contract(chain.cloneFactoryInception, CloneFactory.abi, wallet) | ||
let i = 0 | ||
for (const inceptionPriceFeed of inceptionPriceFeeds) { | ||
console.log(`Deploying ${inceptionPriceFeed} price feed on ${chain.name}`); | ||
try { | ||
const [baseAsset, quoteAsset] = inceptionPriceFeed.split('/'); | ||
|
||
console.log("baseAsset", baseAsset) | ||
console.log("quoteAsset", quoteAsset) | ||
const tx = await cloneFactoryInceptionContract.createInceptionPriceFeed(inceptionVaults[i], inceptionLRTAddresses[i], inceptionPriceFeedDecimals, 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 ${inceptionPriceFeed} 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 InceptionCloneFactory from '../artifacts/contracts/inceptionpricefeed/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 inceptionCloneFactoryFactory = new ethers.ContractFactory(InceptionCloneFactory.abi, InceptionCloneFactory.bytecode, wallet) | ||
const inceptionCloneFactory = await inceptionCloneFactoryFactory.deploy(chain.inceptionPriceFeedImplementation) | ||
console.log(`${chain.name}, address: ${await inceptionCloneFactory.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 InceptionPriceFeed from '../artifacts/contracts/inceptionpricefeed/InceptionPriceFeed.sol/InceptionPriceFeed.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 inceptionPriceFeedFactory = new ethers.ContractFactory(InceptionPriceFeed.abi, InceptionPriceFeed.bytecode, wallet) | ||
const inceptionPriceFeed = await inceptionPriceFeedFactory.deploy() | ||
console.log(`${chain.name}, address: ${await inceptionPriceFeed.getAddress()}`); | ||
} | ||
} | ||
} | ||
|
||
main().catch((error) => { | ||
console.error(error); | ||
process.exitCode = 1; | ||
}); |
Oops, something went wrong.