8000 Price feeds for WETH deployment + Bulker changes for OZ audit by kevincheng96 · Pull Request #625 · compound-finance/comet · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Price feeds for WETH deployment + Bulker changes for OZ audit #625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions contracts/Comet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.15;

import "./CometMainInterface.sol";
import "./ERC20.sol";
import "./vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "./IPriceFeed.sol";

/**
* @title Compound's Comet Contract
Expand Down Expand Up @@ -144,7 +144,7 @@ contract Comet is CometMainInterface {
if (config.storeFrontPriceFactor > FACTOR_SCALE) revert BadDiscount();
if (config.assetConfigs.length > MAX_ASSETS) revert TooManyAssets();
if (config.baseMinForRewards == 0) revert BadMinimum();
if (AggregatorV3Interface(config.baseTokenPriceFeed).decimals() != PRICE_FEED_DECIMALS) revert BadDecimals();
if (IPriceFeed(config.baseTokenPriceFeed).decimals() != PRICE_FEED_DECIMALS) revert BadDecimals();

// Copy configuration
unchecked {
Expand Down Expand Up @@ -240,7 +240,7 @@ contract Comet is CometMainInterface {
}

// Sanity check price feed and asset decimals
if (AggregatorV3Interface(priceFeed).decimals() != PRICE_FEED_DECIMALS) revert BadDecimals();
if (IPriceFeed(priceFeed).decimals() != PRICE_FEED_DECIMALS) revert BadDecimals();
if (ERC20(asset).decimals() != decimals_) revert BadDecimals();

// Ensure collateral factors are within range
Expand Down Expand Up @@ -471,7 +471,7 @@ contract Comet is CometMainInterface {
* @return The price, scaled by `PRICE_SCALE`
*/
function getPrice(address priceFeed) override public view returns (uint256) {
(, int price, , , ) = AggregatorV3Interface(priceFeed).latestRoundData();
(, int price, , , ) = IPriceFeed(priceFeed).latestRoundData();
if (price <= 0) revert BadPrice();
return uint256(price);
}
Expand Down
1 change: 0 additions & 1 deletion contracts/CometCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity 0.8.15;
import "./CometConfiguration.sol";
import "./CometStorage.sol";
import "./CometMath.sol";
import "./vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

abstract contract CometCore is CometConfiguration, CometStorage, CometMath {
struct AssetInfo {
Expand Down
45 changes: 45 additions & 0 deletions contracts/ConstantPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;

import "./IPriceFeed.sol";

contract ConstantPriceFeed is IPriceFeed {
/// @notice Version of the price feed
uint public constant override version = 1;

/// @notice Description of the price feed
string public constant description = "Constant price feed";

/// @notice Number of decimals for returned prices
uint8 public immutable override decimals;

/// @notice The constant price
int public immutable constantPrice;

/**
* @notice Construct a new scaling price feed
* @param decimals_ The number of decimals for the returned prices
**/
constructor(uint8 decimals_, int256 constantPrice_) {
decimals = decimals_;
constantPrice = constantPrice_;
}

/**
* @notice Price for the latest round
* @return roundId Round id from the underlying price feed
* @return answer Latest price for the asset (will always be a constant price)
* @return startedAt Timestamp when the round was started; passed on from underlying price feed
* @return updatedAt Timestamp when the round was last updated; passed on from underlying price feed
* @return answeredInRound Round id in which the answer was computed; passed on from underlying price feed
**/
function latestRoundData() external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
) {
return (0, constantPrice, block.timestamp, block.timestamp, 0);
}
}
14 changes: 14 additions & 0 deletions contracts/IERC20NonStandard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;

/**
* @title IERC20NonStandard
* @dev Version of ERC20 with no return values for `transfer` and `transferFrom`
* See https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
*/
interface IERC20NonStandard {
function approve(address spender, uint256 amount) external;
function transfer(address to, uint256 value) external;
function transferFrom(address from, address to, uint256 value) external;
function balanceOf(address account) external view returns (uint256);
}
25 changes: 25 additions & 0 deletions contracts/IPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;

/**
* @dev Interface for price feeds used by Comet
* Note This is Chainlink's AggregatorV3Interface, but without the `getRoundData` function.
*/
interface IPriceFeed {
function decimals() external view returns (uint8);

function description() external view returns (string memory);

function version() external view returns (uint256);

function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
81 changes: 81 additions & 0 deletions contracts/ScalingPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;

import "./vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "./IPriceFeed.sol";

contract ScalingPriceFeed is IPriceFeed {
/** Custom errors **/
error InvalidInt256();

/// @notice Version of the price feed
uint public constant override version = 1;

/// @notice Description of the price feed
string public description;

/// @notice Number of decimals for returned prices
uint8 public immutable override decimals;

/// @notice Underlying Chainlink price feed where prices are fetched from
address public immutable underlyingPriceFeed;

/// @notice Whether or not the price should be upscaled
bool internal immutable shouldUpscale;

/// @notice The amount to upscale or downscale the price by
int256 internal immutable rescaleFactor;

/**
* @notice Construct a new scaling price feed
* @param underlyingPriceFeed_ The address of the underlying price feed to fetch prices from
* @param decimals_ The number of decimals for the returned prices
**/
constructor(address underlyingPriceFeed_, uint8 decimals_) {
underlyingPriceFeed = underlyingPriceFeed_;
decimals = decimals_;
description = AggregatorV3Interface(underlyingPriceFeed_).description();

uint8 chainlinkPriceFeedDecimals = AggregatorV3Interface(underlyingPriceFeed_).decimals();
// Note: Solidity does not allow setting immutables in if/else statements
shouldUpscale = chainlinkPriceFeedDecimals < decimals_ ? true : false;
rescaleFactor = (shouldUpscale
? signed256(10 ** (decimals_ - chainlinkPriceFeedDecimals))
: signed256(10 ** (chainlinkPriceFeedDecimals - decimals_))
);
}

/**
* @notice Price for the latest round
* @return roundId Round id from the underlying price feed
* @return answer Latest price for the asset in terms of ETH
* @return startedAt Timestamp when the round was started; passed on from underlying price feed
* @return updatedAt Timestamp when the round was last updated; passed on from underlying price feed
* @return answeredInRound Round id in which the answer was computed; passed on from underlying price feed
**/
function latestRoundData() override external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
) {
(uint80 roundId_, int256 price, uint256 startedAt_, uint256 updatedAt_, uint80 answeredInRound_) = AggregatorV3Interface(underlyingPriceFeed).latestRoundData();
return (roundId_, scalePrice(price), startedAt_, updatedAt_, answeredInRound_);
}

function signed256(uint256 n) internal pure returns (int256) {
if (n > uint256(type(int256).max)) revert InvalidInt256();
return int256(n);
}

function scalePrice(int256 price) internal view returns (int256) {
int256 scaledPrice;
if (shouldUpscale) {
scaledPrice = price * rescaleFactor;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we ever going to upscale prices?

It seems like the only use case for this contract is truncating an 18 decimal price feed to 8 decimals.

Would it be simpler/more efficient to have scalePrice always return price / rescaleFactor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah maybe simpler is better. But my goal was to make this flexible in case there was ever a need to scale up. There's not much extra gas costs to it, so it just depends on how much we value the extra flexibility.

Personally, I rather have the flexibility (hardcoding price feed decimals has made the WETH deployment trickier, for example), but not a super strong opinion if we think it's not worth it.

} else {
scaledPrice = price / rescaleFactor;
}
return scaledPrice;
}
}
67 changes: 32 additions & 35 deletions contracts/WstETHPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,45 @@
pragma solidity 0.8.15;

import "./vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "./IPriceFeed.sol";
import "./IWstETH.sol";

interface IWstETH {
function decimals() external view returns (uint8);
function tokensPerStEth() external view returns (uint256);
}

contract WstETHPriceFeed is AggregatorV3Interface {
contract WstETHPriceFeed is IPriceFeed {
/** Custom errors **/
error BadDecimals();
error InvalidInt256();
error NotImplemented();

string public constant override description = "Custom price feed for wstETH / USD";

/// @notice Version of the price feed
uint public constant override version = 1;

/// @notice Scale for returned prices
uint8 public override decimals = 8;
/// @notice Description of the price feed
string public constant override description = "Custom price feed for wstETH / ETH";

/// @notice Number of decimals for returned prices
uint8 public immutable override decimals;

/// @notice Chainlink stETH / ETH price feed
address public immutable stETHtoETHPriceFeed;

/// @notice Chainlink stETH / USD price feed
address public immutable stETHtoUSDPriceFeed;
/// @notice Number of decimals for the stETH / ETH price feed
uint public immutable stETHToETHPriceFeedDecimals;

/// @notice WstETH contract address
address public immutable wstETH;

/// @notice scale for WstETH contract
uint public immutable wstETHScale;
/// @notice Scale for WstETH contract
int public immutable wstETHScale;

constructor(address stETHtoUSDPriceFeed_, address wstETH_) {
stETHtoUSDPriceFeed = stETHtoUSDPriceFeed_;
constructor(address stETHtoETHPriceFeed_, address wstETH_, uint8 decimals_) {
stETHtoETHPriceFeed = stETHtoETHPriceFeed_;
stETHToETHPriceFeedDecimals = AggregatorV3Interface(stETHtoETHPriceFeed_).decimals();
wstETH = wstETH_;
wstETHScale = 10 ** IWstETH(wstETH).decimals();
// Note: Safe to convert directly to an int256 because wstETH.decimals == 18
wstETHScale = int256(10 ** IWstETH(wstETH).decimals());

// Note: stETH / ETH price feed has 18 decimals so `decimals_` should always be less than or equals to that
if (decimals_ > stETHToETHPriceFeedDecimals) revert BadDecimals();
decimals = decimals_;
}

function signed256(uint256 n) internal pure returns (int256) {
Expand All @@ -41,20 +49,7 @@ contract WstETHPriceFeed is AggregatorV3Interface {
}

/**
* @notice Unimplemented function required to fulfill AggregatorV3Interface; always reverts
**/
function getRoundData(uint80 _roundId) override external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
) {
revert NotImplemented();
}

/**
* @notice WstETH Price for the latest round
* @notice WstETH price for the latest round
* @return roundId Round id from the stETH price feed
* @return answer Latest price for wstETH / USD
* @return startedAt Timestamp when the round was started; passed on from stETH price feed
Expand All @@ -68,9 +63,11 @@ contract W 6824 stETHPriceFeed is AggregatorV3Interface {
uint256 updatedAt,
uint80 answeredInRound
) {
(uint80 roundId, int256 stETHPrice, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) = AggregatorV3Interface(stETHtoUSDPriceFeed).latestRoundData();
(uint80 roundId_, int256 stETHPrice, uint256 startedAt_, uint256 updatedAt_, uint80 answeredInRound_) = AggregatorV3Interface(stETHtoETHPriceFeed).latestRoundData();
uint256 tokensPerStEth = IWstETH(wstETH).tokensPerStEth();
int256 price = stETHPrice * int256(wstETHScale) / signed256(tokensPerStEth);
return (roundId, price, startedAt, updatedAt, answeredInRound);
int256 price = stETHPrice * wstETHScale / signed256(tokensPerStEth);
// Note: Assumes the stETH price feed has an equal or larger amount of decimals than this price feed
int256 scaledPrice = price / int256(10 ** (stETHToETHPriceFeedDecimals - decimals));
return (roundId_, scaledPrice, startedAt_, updatedAt_, answeredInRound_);
}
}
Loading
0