Skip to content

Commit

Permalink
feat: add cbBTC cover asset support 2.10.0 (#1277)
Browse files Browse the repository at this point in the history
  • Loading branch information
shark0der authored Nov 11, 2024
2 parents d3078df + 46df658 commit 0e9eac6
Show file tree
Hide file tree
Showing 35 changed files with 933 additions and 222 deletions.
18 changes: 17 additions & 1 deletion contracts/interfaces/IPriceFeedOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,32 @@ interface Aggregator {

interface IPriceFeedOracle {

struct OracleAsset {
enum AggregatorType { ETH, USD }

struct AssetInfo {
Aggregator aggregator;
AggregatorType aggregatorType;
uint8 decimals;
}

function ETH() external view returns (address);
function assets(address) external view returns (Aggregator, uint8);
function assetsMap(address) external view returns (Aggregator, AggregatorType, uint8);

function getAssetToEthRate(address asset) external view returns (uint);
function getAssetForEth(address asset, uint ethIn) external view returns (uint);
function getEthForAsset(address asset, uint amount) external view returns (uint);

/* ========== ERRORS ========== */

error EmptyAssetAddresses();
error ArgumentLengthMismatch(uint assetAddressesLength, uint aggregatorsLength, uint typesLength, uint decimalsLength);
error ZeroAddress(string parameter);
error ZeroDecimals(address asset);
error IncompatibleAggregatorDecimals(address aggregator, uint8 aggregatorDecimals, uint8 expectedDecimals);
error UnknownAggregatorType(uint8 aggregatorType);
error EthUsdAggregatorNotSet();
error InvalidEthAggregatorType(AggregatorType actual, AggregatorType expected);
error UnknownAsset(address asset);
error NonPositiveRate(address aggregator, int rate);
}
2 changes: 1 addition & 1 deletion contracts/mocks/common/ChainlinkAggregatorMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity ^0.8.18;
contract ChainlinkAggregatorMock {

uint public latestAnswer;
uint public decimals;
uint public decimals = 18;

function setDecimals(uint _decimals) public {
decimals = _decimals;
Expand Down
7 changes: 6 additions & 1 deletion contracts/mocks/common/PriceFeedOracleMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "../../interfaces/IPriceFeedOracle.sol";
contract PriceFeedOracleMock is IPriceFeedOracle {

address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
mapping(address => OracleAsset) public assets;
mapping(address => AssetInfo) public assetsMap;

uint public ethRate;

Expand All @@ -26,4 +26,9 @@ contract PriceFeedOracleMock is IPriceFeedOracle {
function getEthForAsset(address, uint amount) external view returns (uint) {
return amount / ethRate;
}

function assets(address assetAddress) external view returns (Aggregator, uint8) {
AssetInfo memory asset = assetsMap[assetAddress];
return (asset.aggregator, asset.decimals);
}
}
4 changes: 4 additions & 0 deletions contracts/mocks/generic/PriceFeedOracleGeneric.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ contract PriceFeedOracleGeneric is IPriceFeedOracle {
revert("Unsupported");
}

function assetsMap(address) external virtual view returns (Aggregator, AggregatorType, uint8) {
revert("Unsupported");
}

function getAssetToEthRate(address) external virtual view returns (uint) {
revert("Unsupported");
}
Expand Down
144 changes: 100 additions & 44 deletions contracts/modules/capital/PriceFeedOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,93 +4,149 @@ pragma solidity ^0.8.18;

import "../../interfaces/IPriceFeedOracle.sol";


contract PriceFeedOracle is IPriceFeedOracle {

mapping(address => OracleAsset) public assets;
mapping(address => AssetInfo) public assetsMap;

address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public immutable safeTracker;

constructor(
address[] memory _assetAddresses,
address[] memory _assetAggregators,
AggregatorType[] memory _aggregatorTypes,
uint8[] memory _assetDecimals,
address _safeTracker
) {
require(
_assetAddresses.length == _assetAggregators.length && _assetAggregators.length == _assetDecimals.length,
"PriceFeedOracle: different args length"
);
require(_safeTracker != address(0), "PriceFeedOracle: safeTracker cannot be zero address");
if (_assetAddresses.length == 0) {
revert EmptyAssetAddresses();
}
if (
_assetAddresses.length != _assetAggregators.length ||
_assetAggregators.length != _aggregatorTypes.length ||
_aggregatorTypes.length != _assetDecimals.length
) {
revert ArgumentLengthMismatch(
_assetAddresses.length,
_assetAggregators.length,
_aggregatorTypes.length,
_assetDecimals.length
);
}
if (_safeTracker == address(0)) {
revert ZeroAddress("safeTracker");
}

safeTracker = _safeTracker;
assets[_safeTracker] = OracleAsset(Aggregator(_safeTracker), 18);
assetsMap[_safeTracker] = AssetInfo(Aggregator(_safeTracker), AggregatorType.ETH, 18);

for (uint i = 0; i < _assetAddresses.length; i++) {
assets[_assetAddresses[i]] = OracleAsset(Aggregator(_assetAggregators[i]), _assetDecimals[i]);
if (_assetAddresses[i] == address(0)) {
revert ZeroAddress("assetAddress");
}
if (_assetAggregators[i] == address(0)) {
revert ZeroAddress("aggregator");
}
if (_assetDecimals[i] == 0) {
revert ZeroDecimals(_assetAddresses[i]);
}

Aggregator aggregator = Aggregator(_assetAggregators[i]);
uint8 aggregatorDecimals = aggregator.decimals();

if (_aggregatorTypes[i] == AggregatorType.ETH && aggregatorDecimals != 18) {
revert IncompatibleAggregatorDecimals(_assetAggregators[i], aggregatorDecimals, 18);
}
if (_aggregatorTypes[i] == AggregatorType.USD && aggregatorDecimals != 8) {
revert IncompatibleAggregatorDecimals(_assetAggregators[i], aggregatorDecimals, 8);
}

assetsMap[_assetAddresses[i]] = AssetInfo(aggregator, _aggregatorTypes[i], _assetDecimals[i]);
}

// Require ETH-USD asset
AssetInfo memory ethAsset = assetsMap[ETH];
if (address(ethAsset.aggregator) == address(0)) {
revert EthUsdAggregatorNotSet();
}
if (ethAsset.aggregatorType != AggregatorType.USD) {
revert InvalidEthAggregatorType(ethAsset.aggregatorType, AggregatorType.USD);
}
}

/**
* @dev Returns the amount of ether in wei that are equivalent to 1 unit (10 ** decimals) of asset
* @param assetAddress address of asset
* @return price in ether
*/
/// @notice Returns the amount of ether in wei that are equivalent to 1 unit (10 ** decimals) of asset
/// @param assetAddress address of asset
/// @return price in ether
function getAssetToEthRate(address assetAddress) public view returns (uint) {
if (assetAddress == ETH || assetAddress == safeTracker) {
return 1 ether;
}

OracleAsset memory asset = assets[assetAddress];
return _getAssetToEthRate(asset.aggregator);
AssetInfo memory asset = assetsMap[assetAddress];
return _getAssetToEthRate(asset.aggregator, asset.aggregatorType);
}

/**
* @dev Returns the amount of currency that is equivalent to ethIn amount of ether.
* @param assetAddress address of asset
* @param ethIn amount of ether to be converted to the asset
* @return asset amount
*/
/// @notice Returns the amount of currency that is equivalent to ethIn amount of ether.
/// @param assetAddress address of asset
/// @param ethIn amount of ether to be converted to the asset
/// @return asset amount
function getAssetForEth(address assetAddress, uint ethIn) external view returns (uint) {
if (assetAddress == ETH || assetAddress == safeTracker) {
return ethIn;
}

OracleAsset memory asset = assets[assetAddress];
uint price = _getAssetToEthRate(asset.aggregator);
AssetInfo memory asset = assetsMap[assetAddress];
uint price = _getAssetToEthRate(asset.aggregator, asset.aggregatorType);

return ethIn * (10**uint(asset.decimals)) / price;
return ethIn * (10 ** uint(asset.decimals)) / price;
}

/**
* @dev Returns the amount of eth that is equivalent to a given asset and amount
* @param assetAddress address of asset
* @param amount amount of asset
* @return amount of ether
*/
/// @notice Returns the amount of eth that is equivalent to a given asset and amount
/// @param assetAddress address of asset
/// @param amount amount of asset
/// @return amount of ether
function getEthForAsset(address assetAddress, uint amount) external view returns (uint) {
if (assetAddress == ETH || assetAddress == safeTracker) {
return amount;
}

OracleAsset memory asset = assets[assetAddress];
uint price = _getAssetToEthRate(asset.aggregator);
AssetInfo memory asset = assetsMap[assetAddress];
uint price = _getAssetToEthRate(asset.aggregator, asset.aggregatorType);

return amount * (price) / 10**uint(asset.decimals);
return amount * (price) / 10 ** uint(asset.decimals);
}

/**
* @dev Returns the amount of ether in wei that are equivalent to 1 unit (10 ** decimals) of asset
* @param aggregator The asset aggregator
* @return price in ether
*/
function _getAssetToEthRate(Aggregator aggregator) internal view returns (uint) {
require(address(aggregator) != address(0), "PriceFeedOracle: Unknown asset");
// TODO: consider checking the latest timestamp and revert if it's *very* old
/// @notice Returns the amount of ether in wei that are equivalent to 1 unit (10 ** decimals) of asset
/// @param aggregator The asset aggregator
/// @param aggregatorType The asset aggregator type (i.e ETH, USD)
/// @return price in ether
function _getAssetToEthRate(Aggregator aggregator, AggregatorType aggregatorType) internal view returns (uint) {
// NOTE: Current implementation relies on off-chain staleness checks, consider adding on-chain staleness check?
int rate = aggregator.latestAnswer();
require(rate > 0, "PriceFeedOracle: Rate must be > 0");
if (rate <= 0) {
revert NonPositiveRate(address(aggregator), rate);
}

if (aggregatorType == AggregatorType.ETH) {
return uint(rate);
}

// AggregatorType.USD - convert the USD rate to its equivalent ETH rate using the ETH-USD exchange rate
AssetInfo memory ethAsset = assetsMap[ETH];

int ethUsdRate = ethAsset.aggregator.latestAnswer();
if (ethUsdRate <= 0) {
revert NonPositiveRate(ETH, ethUsdRate);
}

return (uint(rate) * 1e18) / uint(ethUsdRate);
}

return uint(rate);
/// @notice Retrieves the aggregator and decimals for a specific asset
/// @param assetAddress address of the asset
/// @return Aggregator instance and decimals of the asset
function assets(address assetAddress) external view returns (Aggregator, uint8) {
AssetInfo memory asset = assetsMap[assetAddress];
return (asset.aggregator, asset.decimals);
}
}
4 changes: 2 additions & 2 deletions deployments/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion deployments/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nexusmutual/deployments",
"version": "2.9.0",
"version": "2.10.0",
"description": "Nexus Mutual deployed contract addresses and abis",
"typings": "./dist/index.d.ts",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion deployments/src/addresses.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"NXMaster": "0x01BFd82675DBCc7762C84019cA518e701C0cD07e",
"NexusViewer": "0xcafea70070104A17f10a1E61d6BaBE30D64B7Ec2",
"Pool": "0xcafeaf6eA90CB931ae43a8Cf4B25a73a24cF6158",
"PriceFeedOracle": "0xcafea210B662b19bbd1692873A46be324a482672",
"PriceFeedOracle": "0xcafea905B417AC7778843aaE1A0b3848CA97a592",
"ProposalCategory": "0x888eA6Ab349c854936b98586CE6a17E98BF254b2",
"Ramm": "0xcafea54f03E1Cc036653444e581A10a43B2487CD",
"SafeTracker": "0xcafeaB8B01C74c2239eA9b2B0F6aB2dD409c6c13",
Expand Down
6 changes: 6 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ const ContractCode = {
CoverProducts: 'CP',
};

const AggregatorType = {
ETH: 0,
USD: 1,
};

module.exports = {
Assets,
CoverStatus,
Expand All @@ -171,4 +176,5 @@ module.exports = {
NXMasterOwnerParamType,
PoolAsset,
ContractCode,
AggregatorType,
};
19 changes: 9 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nexusmutual",
"version": "2.9.0",
"version": "2.10.0",
"description": "NexusMutual smart contracts",
"repository": {
"type": "git",
Expand All @@ -20,7 +20,7 @@
},
"homepage": "https://github.com/NexusMutual/smart-contracts",
"dependencies": {
"@nexusmutual/deployments": "^2.8.0",
"@nexusmutual/deployments": "^2.10.0",
"@nexusmutual/ethers-v5-aws-kms-signer": "^0.0.1",
"@nomicfoundation/hardhat-network-helpers": "^1.0.8",
"@openzeppelin/contracts-v4": "npm:@openzeppelin/contracts@^4.7.3",
Expand Down
Loading

0 comments on commit 0e9eac6

Please sign in to comment.