Skip to content

Commit

Permalink
Merge pull request #191 from VenusProtocol/feat/sfrxeth-oracle
Browse files Browse the repository at this point in the history
[VEN-2551] Get sfrxETH Price from SfrxEthFraxOracle
  • Loading branch information
chechu authored Jun 20, 2024
2 parents 158aa84 + 36f63b5 commit 810129c
Show file tree
Hide file tree
Showing 22 changed files with 5,865 additions and 46 deletions.
Binary file added audits/110_sfrxETHOracle_certik_20240517.pdf
Binary file not shown.
Binary file added audits/111_sfrxETHOracle_quantstamp_20240530.pdf
Binary file not shown.
7 changes: 0 additions & 7 deletions contracts/interfaces/ISfrxETH.sol

This file was deleted.

6 changes: 6 additions & 0 deletions contracts/interfaces/ISfrxEthFraxOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;

interface ISfrxEthFraxOracle {
function getPrices() external view returns (bool _isbadData, uint256 _priceLow, uint256 _priceHigh);
}
103 changes: 91 additions & 12 deletions contracts/oracles/SFrxETHOracle.sol
Original file line number Diff line number Diff line change
@@ -1,29 +1,108 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;

import { ISfrxETH } from "../interfaces/ISfrxETH.sol";
import { CorrelatedTokenOracle } from "./common/CorrelatedTokenOracle.sol";
import { ISfrxEthFraxOracle } from "../interfaces/ISfrxEthFraxOracle.sol";
import { ensureNonzeroAddress, ensureNonzeroValue } from "@venusprotocol/solidity-utilities/contracts/validators.sol";
import { EXP_SCALE } from "@venusprotocol/solidity-utilities/contracts/constants.sol";
import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
import { OracleInterface } from "../interfaces/OracleInterface.sol";

/**
* @title SFrxETHOracle
* @author Venus
* @notice This oracle fetches the price of sfrxETH
*/
contract SFrxETHOracle is CorrelatedTokenOracle {
contract SFrxETHOracle is AccessControlledV8, OracleInterface {
/// @notice Address of SfrxEthFraxOracle
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
ISfrxEthFraxOracle public immutable SFRXETH_FRAX_ORACLE;

/// @notice Address of sfrxETH
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address public immutable SFRXETH;

/// @notice Maximum allowed price difference
uint256 public maxAllowedPriceDifference;

/// @notice Emits when the maximum allowed price difference is updated
event MaxAllowedPriceDifferenceUpdated(uint256 oldMaxAllowedPriceDifference, uint256 newMaxAllowedPriceDifference);

/// @notice Thrown if the price data is invalid
error BadPriceData();

/// @notice Thrown if the price difference exceeds the allowed limit
error PriceDifferenceExceeded();

/// @notice Thrown if the token address is invalid
error InvalidTokenAddress();

/// @notice Constructor for the implementation contract.
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(
address sfrxETH,
address frxETH,
address resilientOracle
) CorrelatedTokenOracle(sfrxETH, frxETH, resilientOracle) {}
/// @custom:error ZeroAddressNotAllowed is thrown when `_sfrxEthFraxOracle` or `_sfrxETH` are the zero address
constructor(address _sfrxEthFraxOracle, address _sfrxETH) {
ensureNonzeroAddress(_sfrxEthFraxOracle);
ensureNonzeroAddress(_sfrxETH);

SFRXETH_FRAX_ORACLE = ISfrxEthFraxOracle(_sfrxEthFraxOracle);
SFRXETH = _sfrxETH;

_disableInitializers();
}

/**
* @notice Sets the contracts required to fetch prices
* @param _accessControlManager Address of the access control manager contract
* @param _maxAllowedPriceDifference Maximum allowed price difference
* @custom:error ZeroValueNotAllowed is thrown if `_maxAllowedPriceDifference` is zero
*/
function initialize(address _accessControlManager, uint256 _maxAllowedPriceDifference) external initializer {
ensureNonzeroValue(_maxAllowedPriceDifference);

__AccessControlled_init(_accessControlManager);
maxAllowedPriceDifference = _maxAllowedPriceDifference;
}

/**
* @notice Sets the maximum allowed price difference
* @param _maxAllowedPriceDifference Maximum allowed price difference
* @custom:error ZeroValueNotAllowed is thrown if `_maxAllowedPriceDifference` is zero
*/
function setMaxAllowedPriceDifference(uint256 _maxAllowedPriceDifference) external {
_checkAccessAllowed("setMaxAllowedPriceDifference(uint256)");
ensureNonzeroValue(_maxAllowedPriceDifference);

emit MaxAllowedPriceDifferenceUpdated(maxAllowedPriceDifference, _maxAllowedPriceDifference);
maxAllowedPriceDifference = _maxAllowedPriceDifference;
}

/**
* @notice Gets the frxETH for 1 sfrxETH
* @return amount Amount of frxETH
* @notice Fetches the USD price of sfrxETH
* @param asset Address of the sfrxETH token
* @return price The price scaled by 1e18
* @custom:error InvalidTokenAddress is thrown when the `asset` is not the sfrxETH token (`SFRXETH`)
* @custom:error BadPriceData is thrown if the `SFRXETH_FRAX_ORACLE` oracle informs it has bad data
* @custom:error ZeroValueNotAllowed is thrown if the prices (low or high, in USD) are zero
* @custom:error PriceDifferenceExceeded is thrown if priceHigh/priceLow is greater than `maxAllowedPriceDifference`
*/
function _getUnderlyingAmount() internal view override returns (uint256) {
return ISfrxETH(CORRELATED_TOKEN).convertToAssets(EXP_SCALE);
function getPrice(address asset) external view returns (uint256) {
if (asset != SFRXETH) revert InvalidTokenAddress();

(bool isBadData, uint256 priceLow, uint256 priceHigh) = SFRXETH_FRAX_ORACLE.getPrices();

if (isBadData) revert BadPriceData();

// calculate price in USD
uint256 priceHighInUSD = (EXP_SCALE ** 2) / priceLow;
uint256 priceLowInUSD = (EXP_SCALE ** 2) / priceHigh;

ensureNonzeroValue(priceHighInUSD);
ensureNonzeroValue(priceLowInUSD);

// validate price difference
uint256 difference = (priceHighInUSD * EXP_SCALE) / priceLowInUSD;
if (difference > maxAllowedPriceDifference) revert PriceDifferenceExceeded();

// calculate and return average price
return (priceHighInUSD + priceLowInUSD) / 2;
}
}
23 changes: 23 additions & 0 deletions contracts/oracles/mocks/MockSFrxEthFraxOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;

import "../../interfaces/ISfrxEthFraxOracle.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MockSfrxEthFraxOracle is ISfrxEthFraxOracle, Ownable {
bool public isBadData;
uint256 public priceLow;
uint256 public priceHigh;

constructor() Ownable() {}

function setPrices(bool _isBadData, uint256 _priceLow, uint256 _priceHigh) external onlyOwner {
isBadData = _isBadData;
priceLow = _priceLow;
priceHigh = _priceHigh;
}

function getPrices() external view override returns (bool, uint256, uint256) {
return (isBadData, priceLow, priceHigh);
}
}
62 changes: 62 additions & 0 deletions deploy/8-deploy-sfrxeth-oracle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { parseUnits } from "ethers/lib/utils";
import { ethers } from "hardhat";
import { DeployFunction } from "hardhat-deploy/dist/types";
import { HardhatRuntimeEnvironment } from "hardhat/types";

import { ADDRESSES } from "../helpers/deploymentConfig";

const func: DeployFunction = async ({ getNamedAccounts, deployments, network }: HardhatRuntimeEnvironment) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();

const proxyOwnerAddress = network.live ? ADDRESSES[network.name].timelock : deployer;

const { sfrxETH, SfrxEthFraxOracle, acm } = ADDRESSES[network.name];
const maxAllowedPriceDifference = parseUnits("1.14", 18);

let SfrxEthFraxOracleAddress = SfrxEthFraxOracle;
if (!SfrxEthFraxOracle) {
await deploy("MockSfrxEthFraxOracle", {
contract: "MockSfrxEthFraxOracle",
from: deployer,
log: true,
autoMine: true,
skipIfAlreadyDeployed: true,
args: [],
});

const mockSfrxEthFraxOracle = await ethers.getContract("MockSfrxEthFraxOracle");
SfrxEthFraxOracleAddress = mockSfrxEthFraxOracle.address;

if ((await mockSfrxEthFraxOracle.owner()) === deployer) {
await mockSfrxEthFraxOracle.transferOwnership(proxyOwnerAddress);
}
}

await deploy("SFrxETHOracle", {
contract: "SFrxETHOracle",
from: deployer,
log: true,
deterministicDeployment: false,
args: [SfrxEthFraxOracleAddress, sfrxETH],
proxy: {
owner: proxyOwnerAddress,
proxyContract: "OptimizedTransparentProxy",
execute: {
methodName: "initialize",
args: [acm, maxAllowedPriceDifference],
},
},
skipIfAlreadyDeployed: true,
});

const sfrxETHOracle = await ethers.getContract("SFrxETHOracle");

if ((await sfrxETHOracle.owner()) === deployer) {
await sfrxETHOracle.transferOwnership(proxyOwnerAddress);
}
};

export default func;
func.tags = ["sFraxETHOracle"];
func.skip = async (hre: HardhatRuntimeEnvironment) => hre.network.name !== "ethereum" && hre.network.name !== "sepolia";
Loading

0 comments on commit 810129c

Please sign in to comment.