Skip to content

Commit

Permalink
Removed cToken field from token config
Browse files Browse the repository at this point in the history
  • Loading branch information
amit-momin committed Mar 22, 2024
1 parent d15c5fb commit 6a53315
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 32 deletions.
43 changes: 26 additions & 17 deletions contracts/PriceOracle/PriceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,24 @@ 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
/// There should be 1 TokenConfig object for each supported asset, passed in the constructor.
struct TokenConfig {
// 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;
}

/// @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.
struct LoadConfig {
// Decimals of the underlying asset (e.g. 18 for ETH)
uint8 underlyingAssetDecimals;
// Address of the feed used to retrieve the asset's price
address priceFeed;
// Address of the Compound Token
address cToken;
}

/// @dev Mapping of cToken address to TokenConfig used to maintain the supported assets
mapping (address => TokenConfig) tokenConfigs;

Expand Down Expand Up @@ -75,11 +83,12 @@ contract PriceOracle is Ownable2Step {
* @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
*/
constructor(TokenConfig[] memory configs) {
constructor(LoadConfig[] memory configs) {
// Populate token config mapping
for (uint i = 0; i < configs.length; i++) {
TokenConfig memory config = configs[i];
addConfig(config);
LoadConfig memory loadConfig = configs[i];
TokenConfig memory tokenConfig = TokenConfig(loadConfig.underlyingAssetDecimals, loadConfig.priceFeed);
addConfig(loadConfig.cToken, tokenConfig);
}
}

Expand All @@ -99,7 +108,7 @@ contract PriceOracle is Ownable2Step {
returns (uint256)
{
TokenConfig memory config = tokenConfigs[cToken];
if (config.cToken == address(0)) revert ConfigNotFound(cToken);
if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);
// Initialize the aggregator to read the price from
AggregatorV3Interface priceFeed = AggregatorV3Interface(config.priceFeed);
// Retrieve decimals from feed for formatting
Expand Down Expand Up @@ -137,18 +146,18 @@ 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.cToken == address(0)) revert ConfigNotFound(cToken);
if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);
return config;
}

/**
* @notice Adds a new token config to enable the contract to provide prices for a new asset
* @param config Token config struct that contains the info for a new asset configuration
*/
function addConfig(TokenConfig memory config) public onlyOwner {
_validateTokenConfig(config);
tokenConfigs[config.cToken] = config;
emit PriceOracleAssetAdded(config.cToken, config.underlyingAssetDecimals, config.priceFeed);
function addConfig(address cToken, TokenConfig memory config) public onlyOwner {
_validateTokenConfig(cToken, config);
tokenConfigs[cToken] = config;
emit PriceOracleAssetAdded(cToken, config.underlyingAssetDecimals, config.priceFeed);
}

/**
Expand All @@ -159,7 +168,7 @@ contract PriceOracle is Ownable2Step {
function updateConfigPriceFeed(address cToken, address priceFeed) external onlyOwner {
TokenConfig storage config = tokenConfigs[cToken];
// Check if config exists for cToken
if (config.cToken == address(0)) revert ConfigNotFound(cToken);
if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);
// Validate price feed
if (priceFeed == address(0)) revert InvalidPriceFeed(priceFeed);
// Check if existing price feed is the same as the new one sent
Expand All @@ -179,7 +188,7 @@ contract PriceOracle is Ownable2Step {
function removeConfig(address cToken) external onlyOwner {
TokenConfig memory config = tokenConfigs[cToken];
// Check if config exists for cToken
if (config.cToken == address(0)) revert ConfigNotFound(cToken);
if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);

delete tokenConfigs[cToken];
emit PriceOracleAssetRemoved(cToken, config.underlyingAssetDecimals, config.priceFeed);
Expand All @@ -190,11 +199,11 @@ contract PriceOracle is Ownable2Step {
* @dev All fields are required
* @param config TokenConfig struct that needs to be validated
*/
function _validateTokenConfig(TokenConfig memory config) internal view {
if (config.cToken == address(0)) revert MissingCTokenAddress();
function _validateTokenConfig(address cToken, TokenConfig memory config) internal view {
if (cToken == address(0)) revert MissingCTokenAddress();
if (config.priceFeed == address(0)) revert InvalidPriceFeed(config.priceFeed);
// Check if duplicate configs were submitted for the same cToken
if (tokenConfigs[config.cToken].cToken != address(0)) revert DuplicateConfig(config.cToken);
if (tokenConfigs[cToken].priceFeed != address(0)) revert DuplicateConfig(cToken);
_validateDecimals(config.priceFeed, config.underlyingAssetDecimals);
}

Expand Down
26 changes: 13 additions & 13 deletions test/PriceOracle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ describe("PriceOracle", () => {
underlyingAssetDecimals: "18",
priceFeed: mockedEthAggregator.address,
};
expect(await priceOracle.addConfig(newConfig))
expect(await priceOracle.addConfig(newConfig.cToken, newConfig))
.to.emit(priceOracle, "PriceOracleAssetAdded")
.withArgs(
newConfig.cToken,
Expand All @@ -270,9 +270,9 @@ describe("PriceOracle", () => {
priceFeed: "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5",
};

await expect(priceOracle.addConfig(dupeConfig)).to.be.revertedWith(
"DuplicateConfig"
);
await expect(
priceOracle.addConfig(dupeConfig.cToken, dupeConfig)
).to.be.revertedWith("DuplicateConfig");
});
it("should revert for 0 underlyingAssetDecimals in config", async () => {
const invalidConfig: TokenConfig = {
Expand All @@ -281,9 +281,9 @@ describe("PriceOracle", () => {
priceFeed: "0x09023c0da49aaf8fc3fa3adf34c6a7016d38d5e3",
};

await expect(priceOracle.addConfig(invalidConfig)).to.be.revertedWith(
"InvalidUnderlyingAssetDecimals"
);
await expect(
priceOracle.addConfig(invalidConfig.cToken, invalidConfig)
).to.be.revertedWith("InvalidUnderlyingAssetDecimals");
});
it("should revert for underlyingAssetDecimals too high in config", async () => {
const mockedEthAggregator = await deployMockContract(
Expand All @@ -297,9 +297,9 @@ describe("PriceOracle", () => {
priceFeed: mockedEthAggregator.address,
};

await expect(priceOracle.addConfig(invalidConfig)).to.be.revertedWith(
"FormattingDecimalsTooHigh"
);
await expect(
priceOracle.addConfig(invalidConfig.cToken, invalidConfig)
).to.be.revertedWith("FormattingDecimalsTooHigh");
});
it("should revert for feed decimals too high", async () => {
const mockedEthAggregator = await deployMockContract(
Expand All @@ -313,9 +313,9 @@ describe("PriceOracle", () => {
priceFeed: mockedEthAggregator.address,
};

await expect(priceOracle.addConfig(invalidConfig)).to.be.revertedWith(
"FormattingDecimalsTooHigh"
);
await expect(
priceOracle.addConfig(invalidConfig.cToken, invalidConfig)
).to.be.revertedWith("FormattingDecimalsTooHigh");
});
});
describe("updateConfigPriceFeed", () => {
Expand Down
2 changes: 0 additions & 2 deletions test/PriceOracleConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,13 @@ describe("PriceOracle", () => {
);
const config1 = configs[0];
const returnedConfig1 = await priceOracle.getConfig(config1.cToken);
expect(returnedConfig1.cToken).to.equal(config1.cToken);
expect(returnedConfig1.underlyingAssetDecimals).to.equal(
Number(config1.underlyingAssetDecimals)
);
expect(returnedConfig1.priceFeed).to.equal(config1.priceFeed);

const config2 = configs[1];
const returnedConfig2 = await priceOracle.getConfig(config2.cToken);
expect(returnedConfig2.cToken).to.equal(config2.cToken);
expect(returnedConfig2.underlyingAssetDecimals).to.equal(
Number(config2.underlyingAssetDecimals)
);
Expand Down

0 comments on commit 6a53315

Please sign in to comment.