Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions contracts/interfaces/IPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ interface IPool {

function setAssetDetails(uint assetId, bool isCoverAsset, bool isAbandoned) external;

function setAssetOracle(address assetAddress, address aggregator, AggregatorType aggregatorType, uint8 assetDecimals) external;

function sendPayout(uint assetIndex, address payable payoutAddress, uint amount, uint depositInETH) external;

function sendEth(address payable payoutAddress, uint amount) external;
Expand Down Expand Up @@ -113,6 +115,7 @@ interface IPool {

// price feed
error AggregatorMustNotBeZeroAddress();
error AggregatorAssetMustNotBeETH();
error IncompatibleAggregatorDecimals(address aggregator, uint expectedDecimals, uint aggregatorDecimals);
error InvalidEthAggregatorType(AggregatorType actual, AggregatorType expected);
error NonPositiveRate(address aggregator, int rate);
Expand Down
4 changes: 4 additions & 0 deletions contracts/mocks/generic/PoolGeneric.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ contract PoolGeneric is IPool {
revert("transferAssetToSwapOperator unsupported");
}

function setAssetOracle(address /* assetAddress */, address /* aggregator */, AggregatorType /* aggregatorType */, uint8 /* assetDecimals */) external virtual {
revert("setAssetOracle unsupported");
}

function sendPayout(uint /* assetIndex */, address payable /* payoutAddress */, uint /* amount */, uint /* depositInETH */) external virtual {
revert("sendPayout unsupported");
}
Expand Down
22 changes: 22 additions & 0 deletions contracts/modules/capital/AggregatorRETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;

interface IRETH {
function getExchangeRate() external view returns (uint256);
}

contract AggregatorRETH {

uint8 public decimals = 18;
IRETH public immutable rETH;

constructor(address _rETH) {
rETH = IRETH(_rETH);
}

function latestAnswer() public view returns (uint256) {
return rETH.getExchangeRate();
}

}
23 changes: 23 additions & 0 deletions contracts/modules/capital/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,29 @@ contract Pool is IPool, ReentrancyGuard, RegistryAware {
return uint(rate) * 1 ether / uint(ethUsdRate);
}

function setAssetOracle(address assetAddress, address aggregator, AggregatorType aggregatorType, uint8 assetDecimals) external onlyContracts(C_GOVERNOR) {
require(assetAddress != address(0), AssetMustNotBeZeroAddress());
require(assetAddress != ETH, AggregatorAssetMustNotBeETH());
require(aggregator != address(0), AggregatorMustNotBeZeroAddress());

// require asset to be registered in the pool (reverts AssetNotFound otherwise)
getAssetId(assetAddress);
uint8 aggregatorDecimals = Aggregator(aggregator).decimals();

if (aggregatorType == AggregatorType.ETH && aggregatorDecimals != 18) {
revert IncompatibleAggregatorDecimals(address(aggregator), 18, aggregatorDecimals);
}
if (aggregatorType == AggregatorType.USD && aggregatorDecimals != 8) {
revert IncompatibleAggregatorDecimals(address(aggregator), 8, aggregatorDecimals);
}

oracles[assetAddress] = Oracle({
aggregator: Aggregator(aggregator),
aggregatorType: aggregatorType,
assetDecimals: assetDecimals
});
}

/* ========== MIGRATION ========== */

function migrate(
Expand Down
140 changes: 140 additions & 0 deletions test/fork/rEthOracle/rETH.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const { ethers, network, nexus, tracer } = require('hardhat');
const { expect } = require('chai');
const { abis, addresses } = require('@nexusmutual/deployments');
const { setBalance, takeSnapshot } = require('@nomicfoundation/hardhat-network-helpers');
const {
revertToSnapshot,
Addresses,
createSafeExecutor,
getFundedSigner,
executeGovernorProposal,
} = require('../utils');
const rETHAbi = require('./rETHAbi.json');

const { deployContract, parseEther, formatEther } = ethers;
const { ContractIndexes } = nexus.constants;

describe('Pool - rETH oracle change', function () {
before(async function () {
// Get or revert snapshot if network is tenderly
if (network.name === 'tenderly') {
const { TENDERLY_SNAPSHOT_ID } = process.env;
if (TENDERLY_SNAPSHOT_ID) {
await revertToSnapshot(TENDERLY_SNAPSHOT_ID);
console.info(`Reverted to snapshot ${TENDERLY_SNAPSHOT_ID}`);
} else {
const { snapshotId } = await takeSnapshot();
console.info('Snapshot ID: ', snapshotId);
}
}

const [deployer] = await ethers.getSigners();
await setBalance(deployer.address, parseEther('1000'));
});

it('load contracts', async function () {
this.registry = await ethers.getContractAt(abis.Registry, addresses.Registry);
this.cover = await ethers.getContractAt(abis.Cover, addresses.Cover);
this.nxm = await ethers.getContractAt(abis.NXMToken, addresses.NXMToken);
this.master = await ethers.getContractAt(abis.NXMaster, addresses.NXMaster);
this.coverNFT = await ethers.getContractAt(abis.CoverNFT, addresses.CoverNFT);
this.coverProducts = await ethers.getContractAt(abis.CoverProducts, addresses.CoverProducts);
this.pool = await ethers.getContractAt(abis.Pool, addresses.Pool);
this.safeTracker = await ethers.getContractAt(abis.SafeTracker, addresses.SafeTracker);
this.assessments = await ethers.getContractAt(abis.Assessments, addresses.Assessments);
this.claims = await ethers.getContractAt(abis.Claims, addresses.Claims);
this.stakingNFT = await ethers.getContractAt(abis.StakingNFT, addresses.StakingNFT);
this.stakingProducts = await ethers.getContractAt(abis.StakingProducts, addresses.StakingProducts);
this.swapOperator = await ethers.getContractAt(abis.SwapOperator, addresses.SwapOperator);
this.tokenController = await ethers.getContractAt(abis.TokenController, addresses.TokenController);
this.individualClaims = await ethers.getContractAt(abis.Claims, addresses.Claims);
this.stakingPoolFactory = await ethers.getContractAt(abis.StakingPoolFactory, addresses.StakingPoolFactory);
this.ramm = await ethers.getContractAt(abis.Ramm, addresses.Ramm);
this.limitOrders = await ethers.getContractAt(abis.LimitOrders, addresses.LimitOrders);
this.governor = await ethers.getContractAt(abis.Governor, addresses.Governor);
this.assessmentsViewer = await ethers.getContractAt(abis.Assessments, addresses.Assessments);
this.coverViewer = await ethers.getContractAt(abis.CoverViewer, addresses.CoverViewer);
this.stakingViewer = await ethers.getContractAt(abis.StakingViewer, addresses.StakingViewer);

// External contracts
this.coverBroker = await ethers.getContractAt(abis.CoverBroker, addresses.CoverBroker);

// Token Mocks
this.weth = await ethers.getContractAt('WETH9', Addresses.WETH_ADDRESS);
this.cbBTC = await ethers.getContractAt('ERC20Mock', Addresses.CBBTC_ADDRESS);
this.dai = await ethers.getContractAt('ERC20Mock', Addresses.DAI_ADDRESS);
this.usdc = await ethers.getContractAt('ERC20Mock', Addresses.USDC_ADDRESS);
this.rEth = await ethers.getContractAt(rETHAbi, Addresses.RETH_ADDRESS);
this.stEth = await ethers.getContractAt('ERC20Mock', Addresses.STETH_ADDRESS);
this.awEth = await ethers.getContractAt('ERC20Mock', Addresses.AWETH_ADDRESS);
this.enzymeShares = await ethers.getContractAt('ERC20Mock', Addresses.ENZYMEV4_VAULT_PROXY_ADDRESS);

// safe executor
this.executeSafeTransaction = await createSafeExecutor(Addresses.ADVISORY_BOARD_MULTISIG);

Object.entries(addresses).forEach(([k, v]) => (tracer.nameTags[v] = `#[${k}]`));
});

it('Impersonate AB members', async function () {
const boardSeats = await this.registry.ADVISORY_BOARD_SEATS();
this.abMembers = [];
for (let i = 1; i <= boardSeats; i++) {
const address = await this.registry.getMemberAddressBySeat(i);
this.abMembers.push(await getFundedSigner(address));
}
});

it('Collect storage data before upgrade', async function () {
this.poolData = {};
this.poolData.assets = await this.pool.getAssets();
this.poolData.mcr = await this.pool.getMCR();
this.poolData.mcrRatio = await this.pool.getMCRRatio();
this.rate = await this.pool.getEthForAsset(this.rEth.target, parseEther('1'));
});

it('Upgrade Pool contracts and change rETH oracle', async function () {
this.rETHAggregator = await deployContract('AggregatorRETH', [this.rEth]);
const pool = await deployContract('Pool', [this.registry.target]);

const transactions = [
{
target: this.registry,
value: 0n,
data: this.registry.interface.encodeFunctionData('upgradeContract', [ContractIndexes.C_POOL, pool.target]),
},
{
target: this.pool,
value: 0n,
data: pool.interface.encodeFunctionData('setAssetOracle', [
this.rEth.target,
this.rETHAggregator.target,
nexus.constants.AggregatorType.ETH,
18,
]),
},
];

await executeGovernorProposal(this.governor, this.abMembers, transactions);

this.pool = await ethers.getContractAt('Pool', pool.target);

const assets = await this.pool.getAssets();
const mcr = await this.pool.getMCR();
const mcrRatio = await this.pool.getMCRRatio();
const rEthOracle = await this.pool.oracles(this.rEth.target);

console.log('===================OLD VALUES=====================');
console.log('mcr', formatEther(this.poolData.mcr));
console.log('mcrRatio', formatEther(this.poolData.mcrRatio));
console.log('rate', formatEther(this.rate));
console.log('===================NEW VALUES=====================');
console.log('mcr', formatEther(mcr));
console.log('mcrRatio', formatEther(mcrRatio));
console.log('rate', formatEther(await this.rEth.getExchangeRate()));

expect(this.poolData.assets).to.be.deep.equal(assets);
expect(rEthOracle.aggregator).to.be.deep.equal(this.rETHAggregator.target);
});

require('../basic-functionality-tests');
});
Loading
Loading