Skip to content

Commit

Permalink
Added feature to set a fixed price for assets and added deprecated ma…
Browse files Browse the repository at this point in the history
…rket configs
  • Loading branch information
amit-momin committed May 2, 2024
1 parent e3aa50e commit b2b78e4
Show file tree
Hide file tree
Showing 4 changed files with 380 additions and 40 deletions.
49 changes: 49 additions & 0 deletions configuration/parameters-price-oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export type TokenConfig = {
underlyingAssetDecimals: string;
// Address to Chainlink feed used for asset price
priceFeed: string;
// Fixed price for asset
fixedPrice: string;
};

const parameters: TokenConfig[] = [
Expand All @@ -13,96 +15,143 @@ const parameters: TokenConfig[] = [
cToken: "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5",
underlyingAssetDecimals: "18",
priceFeed: "0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419",
fixedPrice: "0",
},
{
// "NAME": "DAI",
cToken: "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643",
underlyingAssetDecimals: "18",
priceFeed: "0xaed0c38402a5d19df6e4c03f4e2dced6e29c1ee9",
fixedPrice: "0",
},
{
// "NAME": "USDC",
cToken: "0x39AA39c021dfbaE8faC545936693aC917d5E7563",
underlyingAssetDecimals: "6",
priceFeed: "0x8fffffd4afb6115b954bd326cbe7b4ba576818f6",
fixedPrice: "0",
},
{
// "NAME": "USDT",
cToken: "0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9",
underlyingAssetDecimals: "6",
priceFeed: "0x3e7d1eab13ad0104d2750b8863b489d65364e32d",
fixedPrice: "0",
},
{
// "NAME": "WBTCv2",
cToken: "0xccf4429db6322d5c611ee964527d42e5d685dd6a",
underlyingAssetDecimals: "8",
priceFeed: "0x45939657d1CA34A8FA39A924B71D28Fe8431e581", // Custom Compound Feed
fixedPrice: "0",
},
{
// "NAME": "WBTC",
cToken: "0xC11b1268C1A384e55C48c2391d8d480264A3A7F4",
underlyingAssetDecimals: "8",
priceFeed: "0x45939657d1CA34A8FA39A924B71D28Fe8431e581", // Custom Compound Feed
fixedPrice: "0",
},
{
// "NAME": "BAT",
cToken: "0x6C8c6b02E7b2BE14d4fA6022Dfd6d75921D90E4E",
underlyingAssetDecimals: "18",
priceFeed: "0x9441D7556e7820B5ca42082cfa99487D56AcA958",
fixedPrice: "0",
},
{
// "NAME": "ZRX",
cToken: "0xB3319f5D18Bc0D84dD1b4825Dcde5d5f7266d407",
underlyingAssetDecimals: "18",
priceFeed: "0x2885d15b8af22648b98b122b22fdf4d2a56c6023",
fixedPrice: "0",
},
{
// "NAME": "UNI",
cToken: "0x35A18000230DA775CAc24873d00Ff85BccdeD550",
underlyingAssetDecimals: "18",
priceFeed: "0x553303d460ee0afb37edff9be42922d8ff63220e",
fixedPrice: "0",
},
{
// "NAME": "COMP",
cToken: "0x70e36f6BF80a52b3B46b3aF8e106CC0ed743E8e4",
underlyingAssetDecimals: "18",
priceFeed: "0xdbd020caef83efd542f4de03e3cf0c28a4428bd5",
fixedPrice: "0",
},
{
// "NAME": "LINK",
cToken: "0xFAce851a4921ce59e912d19329929CE6da6EB0c7",
underlyingAssetDecimals: "18",
priceFeed: "0x2c1d072e956affc0d435cb7ac38ef18d24d9127c",
fixedPrice: "0",
},
{
// "NAME": "TUSD",
cToken: "0x12392F67bdf24faE0AF363c24aC620a2f67DAd86",
underlyingAssetDecimals: "18",
priceFeed: "0xec746ecf986e2927abd291a2a1716c940100f8ba",
fixedPrice: "0",
},
{
// "NAME": "AAVE",
cToken: "0xe65cdB6479BaC1e22340E4E755fAE7E509EcD06c",
underlyingAssetDecimals: "18",
priceFeed: "0x547a514d5e3769680ce22b2361c10ea13619e8a9",
fixedPrice: "0",
},
{
// "NAME": "SUSHI",
cToken: "0x4B0181102A0112A2ef11AbEE5563bb4a3176c9d7",
underlyingAssetDecimals: "18",
priceFeed: "0xcc70f09a6cc17553b2e31954cd36e4a2d89501f7",
fixedPrice: "0",
},
{
// "NAME": "MKR",
cToken: "0x95b4eF2869eBD94BEb4eEE400a99824BF5DC325b",
underlyingAssetDecimals: "18",
priceFeed: "0xec1d1b3b0443256cc3860e24a46f108e699484aa",
fixedPrice: "0",
},
{
// "NAME": "YFI",
cToken: "0x80a2AE356fc9ef4305676f7a3E2Ed04e12C33946",
underlyingAssetDecimals: "18",
priceFeed: "0xa027702dbb89fbd58938e4324ac03b58d812b0e1",
fixedPrice: "0",
},
{
// "NAME": "USDP",
cToken: "0x041171993284df560249B57358F931D9eB7b925D",
underlyingAssetDecimals: "18",
priceFeed: "0x09023c0da49aaf8fc3fa3adf34c6a7016d38d5e3",
fixedPrice: "0",
},
{
// "NAME": "REP",
// Deprecated market
cToken: "0x158079Ee67Fce2f58472A96584A73C7Ab9AC95c1",
underlyingAssetDecimals: "18",
priceFeed: "0x0000000000000000000000000000000000000000",
fixedPrice: "6433680000000000000",
},
{
// "NAME": "FEI",
// Deprecated market
cToken: "0x7713DD9Ca933848F6819F38B8352D9A15EA73F67",
underlyingAssetDecimals: "18",
priceFeed: "0x0000000000000000000000000000000000000000",
fixedPrice: "1001094000000000000",
},
{
// "NAME": "SAI",
// Deprecated market
cToken: "0xF5DCe57282A584D2746FaF1593d3121Fcac444dC",
underlyingAssetDecimals: "18",
priceFeed: "0x0000000000000000000000000000000000000000",
fixedPrice: "16616092000000000000",
},
];

Expand Down
118 changes: 93 additions & 25 deletions contracts/PriceOracle/PriceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,28 @@ import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/
contract PriceOracle is Ownable2Step {

/// @dev Configuration used to return the USD price for the associated cToken asset and base unit needed for formatting
/// @dev Fixed price is returned for assets that do not have a price feed. Expected to already be formatted.
struct TokenConfig {
// Decimals of the underlying asset (e.g. 18 for ETH)
// Decimals of the underlying asset (e.g. 18 for ETH). Needed for formatting when using a price feed. Ignored when using fixed price.
uint8 underlyingAssetDecimals;
// Address of the feed used to retrieve the asset's price
address priceFeed;
// Fixed price for asset that does not have a price feed
uint256 fixedPrice;
}

/// @dev Type used to load the contract with configs during deployment
/// There should be 1 LoadConfig object for each supported asset, passed in the constructor.
/// @dev There should be 1 LoadConfig object for each supported asset, passed in the constructor.
/// @dev Price feed and fixed price are mutually exclusive. Only one or the other should be set.
struct LoadConfig {
// Decimals of the underlying asset (e.g. 18 for ETH)
uint8 underlyingAssetDecimals;
// Address of the Compound Token
address cToken;
// Address of the feed used to retrieve the asset's price
address priceFeed;
// Fixed price for asset that does not have a price feed
uint256 fixedPrice;
}

/// @dev Mapping of cToken address to TokenConfig used to maintain the supported assets
Expand All @@ -32,19 +38,29 @@ contract PriceOracle is Ownable2Step {
/// @param cToken cToken address that the config was added for
/// @param underlyingAssetDecimals Decimals of the underlying asset
/// @param priceFeed Address of the feed used to retrieve the asset's price
event PriceOracleAssetAdded(address indexed cToken, uint8 underlyingAssetDecimals, address priceFeed);
/// @param fixedPrice The fixed price returned when a price feed does not exist for an asset
event PriceOracleAssetAdded(address indexed cToken, uint8 underlyingAssetDecimals, address priceFeed, uint256 fixedPrice);

/// @notice The event emitted when the price feed for an existing config is updated
/// @param cToken cToken address that the config was updated for
/// @param oldPriceFeed The existing price feed address configured in the token config
/// @param newPriceFeed The new price feed address the token config is being updated to
event PriceOracleAssetPriceFeedUpdated(address indexed cToken, address oldPriceFeed, address newPriceFeed);
/// @param oldFixedPrice The fixed price previously set in the config. Price feed updates clear the fixed price if not 0 already.
event PriceOracleAssetPriceFeedUpdated(address indexed cToken, address oldPriceFeed, address newPriceFeed, uint256 oldFixedPrice);

/// @notice The event emitted when the fixed price for an existing config is updated
/// @param cToken cToken address that the config was updated for
/// @param oldFixedPrice The existing fixed price set in the token config
/// @param newFixedPrice The new fixed price set in the token config
/// @param oldPriceFeed The price feed previously set in the config. Fixed price updates clear the price feed if 0 not already.
event PriceOracleAssetFixedPriceUpdated(address indexed cToken, uint256 oldFixedPrice, uint256 newFixedPrice, address oldPriceFeed);

/// @notice The event emitted when an asset is removed to the mapping
/// @param cToken cToken address that the config was removed for
/// @param underlyingAssetDecimals Decimals of the underlying asset in the removed config.
/// @param priceFeed Address price feed set in the removed config
event PriceOracleAssetRemoved(address indexed cToken, uint8 underlyingAssetDecimals, address priceFeed);
/// @param priceFeed Price feed address set in the removed config
/// @param fixedPrice The fixed price set in the removed config
event PriceOracleAssetRemoved(address indexed cToken, uint8 underlyingAssetDecimals, address priceFeed, uint256 fixedPrice);

/// @notice The max decimals value allowed for price feed
uint8 internal constant MAX_DECIMALS = 72;
Expand All @@ -61,9 +77,19 @@ contract PriceOracle is Ownable2Step {
/// @notice Sum of price feed's decimals and underlyingAssetDecimals is greater than MAX_DECIMALS
error FormattingDecimalsTooHigh(uint16 decimals);

/// @notice Price feed missing or duplicated
/// @param priceFeed Price feed address provided
error InvalidPriceFeed(address priceFeed);
/// @notice Price feed missing
error MissingPriceFeed();

/// @notice Fixed price missing
error MissingFixedPrice();

/// @notice Both price feed and fixed price missing
error MissingPriceConfigs();

/// @notice Price feed and fixed price are both set
/// @param priceFeed Price feed provided
/// @param fixedPrice Fixed price provided
error InvalidPriceConfigs(address priceFeed, uint256 fixedPrice);

/// @notice Config already exists
/// @param cToken cToken address provided
Expand All @@ -73,12 +99,18 @@ contract PriceOracle is Ownable2Step {
/// @param cToken cToken address provided
error ConfigNotFound(address cToken);

/// @notice Same price feed as the existing one was provided when updating the price feed config
/// @notice Same price feed as the existing one was provided when updating the config
/// @param cToken cToken address that the price feed update is for
/// @param existingPriceFeed Price feed address set in the existing config
/// @param newPriceFeed Price feed address provided to update to
error UnchangedPriceFeed(address cToken, address existingPriceFeed, address newPriceFeed);

/// @notice Same fixed price as the existing one was provided when updating the config
/// @param cToken cToken address that the fixed price update is for
/// @param existingFixedPrice The fixed price set in the existing config
/// @param newFixedPrice The fixed price provided to update to
error UnchangedFixedPrice(address cToken, uint256 existingFixedPrice, uint256 newFixedPrice);

/**
* @notice Construct a Price Oracle contract for a set of token configurations
* @param configs The token configurations that define which price feed and base unit to use for each asset
Expand Down Expand Up @@ -107,7 +139,10 @@ contract PriceOracle is Ownable2Step {
returns (uint256)
{
TokenConfig memory config = tokenConfigs[cToken];
if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);
// Check if config exists for cToken
if (config.underlyingAssetDecimals == 0) revert ConfigNotFound(cToken);
// Return fixed price if set
if (config.fixedPrice != 0) return config.fixedPrice;
// Initialize the aggregator to read the price from
AggregatorV3Interface priceFeed = AggregatorV3Interface(config.priceFeed);
// Retrieve decimals from feed for formatting
Expand Down Expand Up @@ -145,7 +180,7 @@ contract PriceOracle is Ownable2Step {
function getConfig(address cToken) external view returns (TokenConfig memory) {
TokenConfig memory config = tokenConfigs[cToken];
// Check if config exists for cToken
if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);
if (config.underlyingAssetDecimals == 0) revert ConfigNotFound(cToken);
return config;
}

Expand All @@ -155,30 +190,59 @@ contract PriceOracle is Ownable2Step {
*/
function addConfig(LoadConfig memory config) public onlyOwner {
_validateTokenConfig(config);
TokenConfig memory tokenConfig = TokenConfig(config.underlyingAssetDecimals, config.priceFeed);
TokenConfig memory tokenConfig = TokenConfig(config.underlyingAssetDecimals, config.priceFeed, config.fixedPrice);
tokenConfigs[config.cToken] = tokenConfig;
emit PriceOracleAssetAdded(config.cToken, config.underlyingAssetDecimals, config.priceFeed);
emit PriceOracleAssetAdded(config.cToken, config.underlyingAssetDecimals, config.priceFeed, config.fixedPrice);
}

/**
* @notice Updates the price feed in the token config for a particular cToken
* @notice Updates the price feed in the token config for a particular cToken and sets fixed price to 0 if it is not already.
* @param cToken The cToken address that the config needs to be updated for
* @param priceFeed The address of the new price feed the config needs to be updated to
*/
function updateConfigPriceFeed(address cToken, address priceFeed) external onlyOwner {
TokenConfig memory config = tokenConfigs[cToken];
// Check if config exists for cToken
if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);
if (config.underlyingAssetDecimals == 0) revert ConfigNotFound(cToken);
// Validate price feed
if (priceFeed == address(0)) revert InvalidPriceFeed(priceFeed);
if (priceFeed == address(0)) revert MissingPriceFeed();
// Check if existing price feed is the same as the new one sent
if (config.priceFeed == priceFeed) revert UnchangedPriceFeed(cToken, config.priceFeed, priceFeed);
// Validate the decimals for the price feed since it could differ from the previous one
_validateDecimals(priceFeed, config.underlyingAssetDecimals);

address existingPriceFeed = config.priceFeed;
tokenConfigs[cToken].priceFeed = priceFeed;
emit PriceOracleAssetPriceFeedUpdated(cToken, existingPriceFeed, priceFeed);
uint256 existingFixedPrice = config.fixedPrice;
TokenConfig storage storageConfig = tokenConfigs[cToken];
storageConfig.priceFeed = priceFeed;
if (config.fixedPrice != 0) {
storageConfig.fixedPrice = 0;
}
emit PriceOracleAssetPriceFeedUpdated(cToken, existingPriceFeed, priceFeed, existingFixedPrice);
}

/**
* @notice Updates the fixed price in the token config for a particular cToken and sets price feed to 0 if it is not already.
* @param cToken The cToken address that the config needs to be updated for
* @param fixedPrice The fixed price to be returned for the asset. Expected to be formatted.
*/
function updateConfigFixedPrice(address cToken, uint256 fixedPrice) external onlyOwner {
TokenConfig memory config = tokenConfigs[cToken];
// Check if config exists for cToken
if (config.underlyingAssetDecimals == 0) revert ConfigNotFound(cToken);
// Validate price feed
if (fixedPrice == 0) revert MissingFixedPrice();
// Check if existing price feed is the same as the new one sent
if (config.fixedPrice == fixedPrice) revert UnchangedFixedPrice(cToken, config.fixedPrice, fixedPrice);

uint256 existingFixedPrice = config.fixedPrice;
address existingPriceFeed = config.priceFeed;
TokenConfig storage storageConfig = tokenConfigs[cToken];
storageConfig.fixedPrice = fixedPrice;
if (config.priceFeed != address(0)) {
storageConfig.priceFeed = address(0);
}
emit PriceOracleAssetFixedPriceUpdated(cToken, existingFixedPrice, fixedPrice, existingPriceFeed);
}

/**
Expand All @@ -188,10 +252,9 @@ contract PriceOracle is Ownable2Step {
function removeConfig(address cToken) external onlyOwner {
TokenConfig memory config = tokenConfigs[cToken];
// Check if config exists for cToken
if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);

if (config.underlyingAssetDecimals == 0) revert ConfigNotFound(cToken);
delete tokenConfigs[cToken];
emit PriceOracleAssetRemoved(cToken, config.underlyingAssetDecimals, config.priceFeed);
emit PriceOracleAssetRemoved(cToken, config.underlyingAssetDecimals, config.priceFeed, config.fixedPrice);
}

/**
Expand All @@ -201,10 +264,15 @@ contract PriceOracle is Ownable2Step {
*/
function _validateTokenConfig(LoadConfig memory config) internal view {
if (config.cToken == address(0)) revert MissingCTokenAddress();
if (config.priceFeed == address(0)) revert InvalidPriceFeed(config.priceFeed);
// Check if both price feed and fixed price are empty
if (config.priceFeed == address(0) && config.fixedPrice == 0) revert MissingPriceConfigs();
// Check if both price feed and fixed price are set
if (config.priceFeed != address(0) && config.fixedPrice != 0) revert InvalidPriceConfigs(config.priceFeed, config.fixedPrice);
// Check if duplicate configs were submitted for the same cToken
if (tokenConfigs[config.cToken].priceFeed != address(0)) revert DuplicateConfig(config.cToken);
_validateDecimals(config.priceFeed, config.underlyingAssetDecimals);
if (tokenConfigs[config.cToken].underlyingAssetDecimals != 0) revert DuplicateConfig(config.cToken);
if (config.priceFeed != address(0)) {
_validateDecimals(config.priceFeed, config.underlyingAssetDecimals);
}
}

/**
Expand Down

0 comments on commit b2b78e4

Please sign in to comment.