From 4bd8b712d6f55b8a012ffd0f4451c598b4739b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 25 Oct 2024 23:48:27 +0200 Subject: [PATCH 1/6] [WIP] feat: add collateral for OETH L2 Base. --- .../assets/origin/OETHCollateralL2Base.sol | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 contracts/plugins/assets/origin/OETHCollateralL2Base.sol diff --git a/contracts/plugins/assets/origin/OETHCollateralL2Base.sol b/contracts/plugins/assets/origin/OETHCollateralL2Base.sol new file mode 100644 index 000000000..903123462 --- /dev/null +++ b/contracts/plugins/assets/origin/OETHCollateralL2Base.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/utils/math/Math.sol"; +import "../../../libraries/Fixed.sol"; +import "../AppreciatingFiatCollateral.sol"; +import "../OracleLib.sol"; + +interface IWSuperOETHb { + function convertToAssets(uint256 amount) external view returns (uint256); +} + +interface IMorphoChainlinkOracleV2 { + function price() external view returns (uint256); +} + +/** + * @title Origin Staked ETH Collateral for Base L2 + * @notice Collateral plugin for Origin OETH, + * tok = wsuperOETHb (wrapped superOETHb) + * ref = superOETHb (pegged to ETH 1:1) + * tar = ETH + * UoA = USD + */ +contract OETHCollateralL2Base is AppreciatingFiatCollateral { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + IMorphoChainlinkOracleV2 public immutable targetPerTokChainlinkFeed; // {tar/token} + + AggregatorV3Interface public immutable uoaPerTargetChainlinkFeed; // {UoA/tar} + uint48 public immutable uoaPerTargetChainlinkTimeout; // {s} + + /// @param config.chainlinkFeed - ignored + /// @param config.oracleTimeout - ignored + /// @param config.oracleError {1} Should be the oracle error for UoA/tok + constructor( + CollateralConfig memory config, + uint192 revenueHiding, + IMorphoChainlinkOracleV2 _targetPerTokChainlinkFeed, + AggregatorV3Interface _uoaPerTargetChainlinkFeed, + uint48 _uoaPerTargetChainlinkTimeout + ) AppreciatingFiatCollateral(config, revenueHiding) { + require(config.defaultThreshold != 0, "defaultThreshold zero"); + + require(address(_targetPerTokChainlinkFeed) != address(0), "targetPerTokFeed missing"); + require(address(_uoaPerTargetChainlinkFeed) != address(0), "uoaPerTargetFeed missing"); + + targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed; + + uoaPerTargetChainlinkFeed = _uoaPerTargetChainlinkFeed; + uoaPerTargetChainlinkTimeout = _uoaPerTargetChainlinkTimeout; + + maxOracleTimeout = uint48(Math.max(maxOracleTimeout, _uoaPerTargetChainlinkTimeout)); + } + + /// Can revert, used by other contract functions in order to catch errors + /// @return low {UoA/tok} The low price estimate + /// @return high {UoA/tok} The high price estimate + /// @return pegPrice {target/ref} The actual price observed in the peg + function tryPrice() external view override returns (uint192 low, uint192 high, uint192 pegPrice) { + // {tar/tok} + // {ETH/wsuperOETHb} + uint192 targetPerTok = _safeWrap(targetPerTokChainlinkFeed.price()); + + // {UoA/tar} + // {USD/ETH} + uint192 uoaPerTar = uoaPerTargetChainlinkFeed.price(uoaPerTargetChainlinkTimeout); + + // {UoA/tok} = {UoA/tar} * {tar/tok} + // USD/wsuperOETHb = USD/ETH * ETH/wsuperOETHb + uint192 p = uoaPerTar.mul(targetPerTok); + uint192 err = p.mul(oracleError, CEIL); + + high = p + err; + low = p - err; + // assert(low <= high); obviously true just by inspection + + // {tar/ref} = {tar/tok} / {ref/tok} Get current market peg + // ETH/superOETHb = ETH/wsuperOETHb / superOETHb/wsuperOETHb + pegPrice = targetPerTok.div(underlyingRefPerTok()); + } + + /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens + /// {superOETHb/wsuperOETHb} + function underlyingRefPerTok() public view override returns (uint192) { + return _safeWrap(IWSuperOETHb(address(erc20)).convertToAssets(FIX_ONE)); + } +} From 08c1b89714dfcc5b41288a8f5679d8be4255fbac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 25 Oct 2024 23:53:49 +0200 Subject: [PATCH 2/6] lint --- .../plugins/assets/origin/OETHCollateralL2Base.sol | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/contracts/plugins/assets/origin/OETHCollateralL2Base.sol b/contracts/plugins/assets/origin/OETHCollateralL2Base.sol index 903123462..7407058d4 100644 --- a/contracts/plugins/assets/origin/OETHCollateralL2Base.sol +++ b/contracts/plugins/assets/origin/OETHCollateralL2Base.sol @@ -58,7 +58,16 @@ contract OETHCollateralL2Base is AppreciatingFiatCollateral { /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate /// @return pegPrice {target/ref} The actual price observed in the peg - function tryPrice() external view override returns (uint192 low, uint192 high, uint192 pegPrice) { + function tryPrice() + external + view + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { // {tar/tok} // {ETH/wsuperOETHb} uint192 targetPerTok = _safeWrap(targetPerTokChainlinkFeed.price()); From 9aa77c7011910f0372347da6faa1b4f39c55b68e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 29 Oct 2024 18:12:49 +0100 Subject: [PATCH 3/6] feat: add WSuperOETHb interface --- .../assets/origin/OETHCollateralL2Base.sol | 56 +++++++++++++++---- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/contracts/plugins/assets/origin/OETHCollateralL2Base.sol b/contracts/plugins/assets/origin/OETHCollateralL2Base.sol index 7407058d4..15a9f5205 100644 --- a/contracts/plugins/assets/origin/OETHCollateralL2Base.sol +++ b/contracts/plugins/assets/origin/OETHCollateralL2Base.sol @@ -7,7 +7,48 @@ import "../AppreciatingFiatCollateral.sol"; import "../OracleLib.sol"; interface IWSuperOETHb { - function convertToAssets(uint256 amount) external view returns (uint256); + event Approval(address indexed owner, address indexed spender, uint256 value); + event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); + event GovernorshipTransferred(address indexed previousGovernor, address indexed newGovernor); + event PendingGovernorshipTransfer(address indexed previousGovernor, address indexed newGovernor); + event Transfer(address indexed from, address indexed to, uint256 value); + event Withdraw( + address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares + ); + + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function asset() external view returns (address); + function balanceOf(address account) external view returns (uint256); + function claimGovernance() external; + function convertToAssets(uint256 shares) external view returns (uint256 assets); + function convertToShares(uint256 assets) external view returns (uint256 shares); + function decimals() external view returns (uint8); + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); + function deposit(uint256 assets, address receiver) external returns (uint256); + function governor() external view returns (address); + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + function initialize() external; + function isGovernor() external view returns (bool); + function maxDeposit(address) external view returns (uint256); + function maxMint(address) external view returns (uint256); + function maxRedeem(address owner) external view returns (uint256); + function maxWithdraw(address owner) external view returns (uint256); + function mint(uint256 shares, address receiver) external returns (uint256); + function name() external view returns (string memory); + function previewDeposit(uint256 assets) external view returns (uint256); + function previewMint(uint256 shares) external view returns (uint256); + function previewRedeem(uint256 shares) external view returns (uint256); + function previewWithdraw(uint256 assets) external view returns (uint256); + function redeem(uint256 shares, address receiver, address owner) external returns (uint256); + function symbol() external view returns (string memory); + function totalAssets() external view returns (uint256); + function totalSupply() external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + function transferGovernance(address _newGovernor) external; + function transferToken(address asset_, uint256 amount_) external; + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256); } interface IMorphoChainlinkOracleV2 { @@ -58,19 +99,10 @@ contract OETHCollateralL2Base is AppreciatingFiatCollateral { /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate /// @return pegPrice {target/ref} The actual price observed in the peg - function tryPrice() - external - view - override - returns ( - uint192 low, - uint192 high, - uint192 pegPrice - ) - { + function tryPrice() external view override returns (uint192 low, uint192 high, uint192 pegPrice) { // {tar/tok} // {ETH/wsuperOETHb} - uint192 targetPerTok = _safeWrap(targetPerTokChainlinkFeed.price()); + uint192 targetPerTok = _safeWrap(targetPerTokChainlinkFeed.price()) / 1e18; // {UoA/tar} // {USD/ETH} From 920b82b57865a170992d0b280e7ad07fb7efe4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 29 Oct 2024 18:13:07 +0100 Subject: [PATCH 4/6] feat: add tests for OETHL2 --- common/configuration.ts | 3 + .../origin/OETHCollateralL2Base.test.ts | 258 ++++++++++++++++++ .../individual-collateral/origin/constants.ts | 31 +++ .../individual-collateral/origin/helpers.ts | 20 ++ 4 files changed, 312 insertions(+) create mode 100644 test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts create mode 100644 test/plugins/individual-collateral/origin/constants.ts create mode 100644 test/plugins/individual-collateral/origin/helpers.ts diff --git a/common/configuration.ts b/common/configuration.ts index d5164fa63..d431b5c3b 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -86,6 +86,7 @@ export interface ITokens { wsgUSDbC?: string yvCurveUSDPcrvUSD?: string yvCurveUSDCcrvUSD?: string + wsuperOETHb?: string pyUSD?: string aEthPyUSD?: string @@ -513,6 +514,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDbC: '0x4c80e24119cfb836cdf0a6b53dc23f04f7e652ca', wstETH: '0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452', STG: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df', + wsuperOETHb: '0x7FcD174E80f264448ebeE8c88a7C4476AAF58Ea6' }, chainlinkFeeds: { DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr @@ -529,6 +531,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHETH: '0xf586d0728a47229e747d824a939000Cf21dEF5A0', // 0.5%, 24h ETHUSD: '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // 0.15%, 20min wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h + wsuperOETHb: '0x28C964c985fe84736fAdc7Cf0bBd58B54bc7CF93' }, GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', diff --git a/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts b/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts new file mode 100644 index 000000000..62330b864 --- /dev/null +++ b/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts @@ -0,0 +1,258 @@ +import collateralTests from '../collateralTests' +import { setStorageAt, getStorageAt } from '@nomicfoundation/hardhat-network-helpers' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' +import { mintWSUPEROETHB } from './helpers' +import { expect } from 'chai' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumber, BigNumberish } from 'ethers' +import { + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, + IWSuperOETHb, +} from '../../../../typechain' +import { pushOracleForward } from '../../../utils/oracles' +import { bn, fp } from '../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../common/constants' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + FORK_BLOCK_BASE, + BASE_PRICE_FEEDS, + BASE_FEEDS_TIMEOUT, + BASE_ORACLE_ERROR, + BASE_WSUPEROETHB, + BASE_WSUPEROETHB_WHALE, +} from './constants' +import { getResetFork } from '../helpers' + +/* + Define interfaces +*/ +interface WSUPEROETHBCollateralFixtureContext extends CollateralFixtureContext { + wsuperoethb: IWSuperOETHb + targetPerRefChainlinkFeed: MockV3Aggregator + uoaPerTargetChainlinkFeed: MockV3Aggregator +} + +/* + Define deployment functions +*/ + +interface WSUPEROETHBCollateralOpts extends CollateralOpts { + targetPerTokChainlinkFeed?: string + uoaPerTargetChainlinkFeed?: string + uoaPerTargetChainlinkTimeout?: BigNumberish +} + +export const defaultWSUPEROETHBCollateralOpts: WSUPEROETHBCollateralOpts = { + erc20: BASE_WSUPEROETHB, + targetName: ethers.utils.formatBytes32String('ETH'), + rewardERC20: ZERO_ADDRESS, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: BASE_PRICE_FEEDS.wsuperOETHb_ETH, // ignored + oracleTimeout: '1000', // ignored + oracleError: BASE_ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + targetPerTokChainlinkFeed: BASE_PRICE_FEEDS.wsuperOETHb_ETH, + uoaPerTargetChainlinkFeed: BASE_PRICE_FEEDS.ETH_USD, + uoaPerTargetChainlinkTimeout: BASE_FEEDS_TIMEOUT.ETH_USD, + revenueHiding: fp('1e-4'), +} + +export const deployCollateral = async ( + opts: WSUPEROETHBCollateralOpts = {} +): Promise => { + opts = { ...defaultWSUPEROETHBCollateralOpts, ...opts } + + const WSuperOETHbCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'OETHCollateralL2Base' + ) + + const collateral = await WSuperOETHbCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + rewardERC20: opts.rewardERC20, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + opts.targetPerTokChainlinkFeed, + opts.chainlinkFeed ?? opts.uoaPerTargetChainlinkFeed, + opts.uoaPerTargetChainlinkTimeout, + { gasLimit: 2000000000 } + ) + + // Push forward chainlink feed + await pushOracleForward(opts.uoaPerTargetChainlinkFeed!) + + await collateral.deployed() + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return collateral +} + +const defaultAnswers = { + targetPerRefChainlinkFeed: bn('1e18'), + uoaPerTargetChainlinkFeed: bn('2000e8'), + refPerTokenChainlinkFeed: bn('1.1e18'), +} + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultWSUPEROETHBCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const targetPerRefChainlinkFeed = await MockV3AggregatorFactory.deploy( + 18, + defaultAnswers.targetPerRefChainlinkFeed + ) + const uoaPerTargetChainlinkFeed = await MockV3AggregatorFactory.deploy(8, defaultAnswers.uoaPerTargetChainlinkFeed) + + collateralOpts.chainlinkFeed = uoaPerTargetChainlinkFeed.address + collateralOpts.uoaPerTargetChainlinkFeed = uoaPerTargetChainlinkFeed.address + + const wsuperOETHb = (await ethers.getContractAt('IWSuperOETHb', BASE_WSUPEROETHB)) as IWSuperOETHb + const rewardToken = (await ethers.getContractAt('ERC20Mock', ZERO_ADDRESS)) as ERC20Mock + const collateral = await deployCollateral(collateralOpts) + + return { + alice, + collateral, + wsuperoethb: wsuperOETHb, + tok: wsuperOETHb, + rewardToken, + chainlinkFeed: uoaPerTargetChainlinkFeed, + targetPerRefChainlinkFeed: targetPerRefChainlinkFeed, + uoaPerTargetChainlinkFeed, + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCollateralFunc = async ( + ctx: WSUPEROETHBCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWSUPEROETHB(ctx.wsuperoethb, user, amount, recipient, BASE_WSUPEROETHB_WHALE) +} + +const reduceTargetPerRef = async ( + ctx: WSUPEROETHBCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} + +const increaseTargetPerRef = async ( + ctx: WSUPEROETHBCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} + +const reduceRefPerTok = async (ctx: WSUPEROETHBCollateralFixtureContext, pctDecrease: BigNumberish) => { + const slot = 2 + const storedTotalSupply = BigNumber.from(await getStorageAt(ctx.tok.address, slot)) + const newStoredTotalAssets = storedTotalSupply.add(storedTotalSupply.mul(pctDecrease).div(100)) + await setStorageAt(ctx.tok.address, slot, newStoredTotalAssets) +} + +const increaseRefPerTok = async ( + ctx: WSUPEROETHBCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + const slot = 2 + const storedTotalSupply = BigNumber.from(await getStorageAt(ctx.tok.address, slot)) + const newStoredTotalAssets = storedTotalSupply.sub(storedTotalSupply.mul(pctIncrease).div(100)) + await setStorageAt(ctx.tok.address, slot, newStoredTotalAssets) +} + +const getExpectedPrice = async (ctx: WSUPEROETHBCollateralFixtureContext): Promise => { + const uoaPerTargetChainlinkFeedAnswer = await ctx.uoaPerTargetChainlinkFeed.latestAnswer() + const uoaPerTargetChainlinkFeedDecimals = await ctx.uoaPerTargetChainlinkFeed.decimals() + + const refPerTok = await ctx.collateral.refPerTok() + + const result = uoaPerTargetChainlinkFeedAnswer + .mul(bn(10).pow(18 - uoaPerTargetChainlinkFeedDecimals)) + .mul(refPerTok) + .div(fp('1')) + + return result +} + +const collateralSpecificConstructorTests = () => { } + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => { } + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const beforeEachRewardsTest = async () => { } + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefaultUp: it.skip, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK_BASE), + collateralName: 'OETHCollateralL2Base', + chainlinkDefaultAnswer: defaultAnswers.uoaPerTargetChainlinkFeed, + itIsPricedByPeg: true, + itHasOracleRefPerTok: true, + targetNetwork: 'base', + toleranceDivisor: bn('1e2'), +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/origin/constants.ts b/test/plugins/individual-collateral/origin/constants.ts new file mode 100644 index 000000000..bdeb9a1f2 --- /dev/null +++ b/test/plugins/individual-collateral/origin/constants.ts @@ -0,0 +1,31 @@ +import { bn, fp } from '../../../../common/numbers' +import { networkConfig } from '../../../../common/configuration' +import { combinedError } from '../../../../scripts/deployment/utils' + +// Mainnet Addresses + +// Base Addresses +export const BASE_WSUPEROETHB = networkConfig['8453'].tokens.wsuperOETHb as string +export const BASE_WSUPEROETHB_WHALE = '0x190e5C6AabB2BeC4eB0B9b2274e9b62cdaEDF356' // Silo +export const FORK_BLOCK_BASE = 21698000 +export const BASE_PRICE_FEEDS = { + // traditional finance notation, opposite of our unit system + wsuperOETHb_ETH: networkConfig['8453'].chainlinkFeeds.wsuperOETHb, // {ETH/wsuperOETHb} + ETH_USD: networkConfig['8453'].chainlinkFeeds.ETHUSD, // {USD/ETH} +} +export const BASE_FEEDS_TIMEOUT = { + wsuperOETHb_ETH: bn(86400), + ETH_USD: bn(1200), +} +export const BASE_ORACLE_ERROR = combinedError( + fp('0.0015'), + combinedError(fp('0.005'), fp('0.005')) +) + +// Data +export const PRICE_TIMEOUT = bn('604800') // 1 week +export const ORACLE_TIMEOUT = bn(86400) // 24 hours in seconds +export const ORACLE_ERROR = fp('0.005') +export const DEFAULT_THRESHOLD = bn(5).mul(bn(10).pow(16)) // 0.05 +export const DELAY_UNTIL_DEFAULT = bn(86400) +export const MAX_TRADE_VOL = bn(1000) diff --git a/test/plugins/individual-collateral/origin/helpers.ts b/test/plugins/individual-collateral/origin/helpers.ts new file mode 100644 index 000000000..4f573f153 --- /dev/null +++ b/test/plugins/individual-collateral/origin/helpers.ts @@ -0,0 +1,20 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { IERC20 } from '../../../../typechain' +import { whileImpersonating } from '../../../utils/impersonation' +import { BigNumberish } from 'ethers' +import { FORK_BLOCK_BASE, BASE_WSUPEROETHB_WHALE } from './constants' +import { getResetFork } from '../helpers' + +export const mintWSUPEROETHB = async ( + wsuperoethb: IERC20, + account: SignerWithAddress, + amount: BigNumberish, + recipient: string, + whale: string = BASE_WSUPEROETHB_WHALE +) => { + await whileImpersonating(whale, async (wsuperoethbWhale) => { + await wsuperoethb.connect(wsuperoethbWhale).transfer(recipient, amount) + }) +} + +export const resetFork = getResetFork(FORK_BLOCK_BASE) From d79e19801d4c7b053e54087f4e509ac3dd2468cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 29 Oct 2024 18:16:14 +0100 Subject: [PATCH 5/6] lint and prettier --- .../assets/origin/OETHCollateralL2Base.sol | 72 +++- .../origin/OETHCollateralL2Base.test.ts | 347 +++++++++--------- 2 files changed, 244 insertions(+), 175 deletions(-) diff --git a/contracts/plugins/assets/origin/OETHCollateralL2Base.sol b/contracts/plugins/assets/origin/OETHCollateralL2Base.sol index 15a9f5205..d597b2b09 100644 --- a/contracts/plugins/assets/origin/OETHCollateralL2Base.sol +++ b/contracts/plugins/assets/origin/OETHCollateralL2Base.sol @@ -10,45 +10,96 @@ interface IWSuperOETHb { event Approval(address indexed owner, address indexed spender, uint256 value); event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); event GovernorshipTransferred(address indexed previousGovernor, address indexed newGovernor); - event PendingGovernorshipTransfer(address indexed previousGovernor, address indexed newGovernor); + event PendingGovernorshipTransfer( + address indexed previousGovernor, + address indexed newGovernor + ); event Transfer(address indexed from, address indexed to, uint256 value); event Withdraw( - address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares + address indexed caller, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares ); function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function asset() external view returns (address); + function balanceOf(address account) external view returns (uint256); + function claimGovernance() external; + function convertToAssets(uint256 shares) external view returns (uint256 assets); + function convertToShares(uint256 assets) external view returns (uint256 shares); + function decimals() external view returns (uint8); + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); + function deposit(uint256 assets, address receiver) external returns (uint256); + function governor() external view returns (address); + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + function initialize() external; + function isGovernor() external view returns (bool); + function maxDeposit(address) external view returns (uint256); + function maxMint(address) external view returns (uint256); + function maxRedeem(address owner) external view returns (uint256); + function maxWithdraw(address owner) external view returns (uint256); + function mint(uint256 shares, address receiver) external returns (uint256); + function name() external view returns (string memory); + function previewDeposit(uint256 assets) external view returns (uint256); + function previewMint(uint256 shares) external view returns (uint256); + function previewRedeem(uint256 shares) external view returns (uint256); + function previewWithdraw(uint256 assets) external view returns (uint256); - function redeem(uint256 shares, address receiver, address owner) external returns (uint256); + + function redeem( + uint256 shares, + address receiver, + address owner + ) external returns (uint256); + function symbol() external view returns (string memory); + function totalAssets() external view returns (uint256); + function totalSupply() external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + function transferGovernance(address _newGovernor) external; + function transferToken(address asset_, uint256 amount_) external; - function withdraw(uint256 assets, address receiver, address owner) external returns (uint256); + + function withdraw( + uint256 assets, + address receiver, + address owner + ) external returns (uint256); } interface IMorphoChainlinkOracleV2 { @@ -99,7 +150,16 @@ contract OETHCollateralL2Base is AppreciatingFiatCollateral { /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate /// @return pegPrice {target/ref} The actual price observed in the peg - function tryPrice() external view override returns (uint192 low, uint192 high, uint192 pegPrice) { + function tryPrice() + external + view + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { // {tar/tok} // {ETH/wsuperOETHb} uint192 targetPerTok = _safeWrap(targetPerTokChainlinkFeed.price()) / 1e18; diff --git a/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts b/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts index 62330b864..ed216d64e 100644 --- a/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts +++ b/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts @@ -6,27 +6,27 @@ import { expect } from 'chai' import { ethers } from 'hardhat' import { ContractFactory, BigNumber, BigNumberish } from 'ethers' import { - ERC20Mock, - MockV3Aggregator, - MockV3Aggregator__factory, - TestICollateral, - IWSuperOETHb, + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, + IWSuperOETHb, } from '../../../../typechain' import { pushOracleForward } from '../../../utils/oracles' import { bn, fp } from '../../../../common/numbers' import { ZERO_ADDRESS } from '../../../../common/constants' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { - PRICE_TIMEOUT, - MAX_TRADE_VOL, - DEFAULT_THRESHOLD, - DELAY_UNTIL_DEFAULT, - FORK_BLOCK_BASE, - BASE_PRICE_FEEDS, - BASE_FEEDS_TIMEOUT, - BASE_ORACLE_ERROR, - BASE_WSUPEROETHB, - BASE_WSUPEROETHB_WHALE, + PRICE_TIMEOUT, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + FORK_BLOCK_BASE, + BASE_PRICE_FEEDS, + BASE_FEEDS_TIMEOUT, + BASE_ORACLE_ERROR, + BASE_WSUPEROETHB, + BASE_WSUPEROETHB_WHALE, } from './constants' import { getResetFork } from '../helpers' @@ -34,9 +34,9 @@ import { getResetFork } from '../helpers' Define interfaces */ interface WSUPEROETHBCollateralFixtureContext extends CollateralFixtureContext { - wsuperoethb: IWSuperOETHb - targetPerRefChainlinkFeed: MockV3Aggregator - uoaPerTargetChainlinkFeed: MockV3Aggregator + wsuperoethb: IWSuperOETHb + targetPerRefChainlinkFeed: MockV3Aggregator + uoaPerTargetChainlinkFeed: MockV3Aggregator } /* @@ -44,113 +44,119 @@ interface WSUPEROETHBCollateralFixtureContext extends CollateralFixtureContext { */ interface WSUPEROETHBCollateralOpts extends CollateralOpts { - targetPerTokChainlinkFeed?: string - uoaPerTargetChainlinkFeed?: string - uoaPerTargetChainlinkTimeout?: BigNumberish + targetPerTokChainlinkFeed?: string + uoaPerTargetChainlinkFeed?: string + uoaPerTargetChainlinkTimeout?: BigNumberish } export const defaultWSUPEROETHBCollateralOpts: WSUPEROETHBCollateralOpts = { - erc20: BASE_WSUPEROETHB, - targetName: ethers.utils.formatBytes32String('ETH'), - rewardERC20: ZERO_ADDRESS, - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: BASE_PRICE_FEEDS.wsuperOETHb_ETH, // ignored - oracleTimeout: '1000', // ignored - oracleError: BASE_ORACLE_ERROR, - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - targetPerTokChainlinkFeed: BASE_PRICE_FEEDS.wsuperOETHb_ETH, - uoaPerTargetChainlinkFeed: BASE_PRICE_FEEDS.ETH_USD, - uoaPerTargetChainlinkTimeout: BASE_FEEDS_TIMEOUT.ETH_USD, - revenueHiding: fp('1e-4'), + erc20: BASE_WSUPEROETHB, + targetName: ethers.utils.formatBytes32String('ETH'), + rewardERC20: ZERO_ADDRESS, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: BASE_PRICE_FEEDS.wsuperOETHb_ETH, // ignored + oracleTimeout: '1000', // ignored + oracleError: BASE_ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + targetPerTokChainlinkFeed: BASE_PRICE_FEEDS.wsuperOETHb_ETH, + uoaPerTargetChainlinkFeed: BASE_PRICE_FEEDS.ETH_USD, + uoaPerTargetChainlinkTimeout: BASE_FEEDS_TIMEOUT.ETH_USD, + revenueHiding: fp('1e-4'), } export const deployCollateral = async ( - opts: WSUPEROETHBCollateralOpts = {} + opts: WSUPEROETHBCollateralOpts = {} ): Promise => { - opts = { ...defaultWSUPEROETHBCollateralOpts, ...opts } - - const WSuperOETHbCollateralFactory: ContractFactory = await ethers.getContractFactory( - 'OETHCollateralL2Base' - ) - - const collateral = await WSuperOETHbCollateralFactory.deploy( - { - erc20: opts.erc20, - targetName: opts.targetName, - rewardERC20: opts.rewardERC20, - priceTimeout: opts.priceTimeout, - chainlinkFeed: opts.chainlinkFeed, - oracleError: opts.oracleError, - oracleTimeout: opts.oracleTimeout, - maxTradeVolume: opts.maxTradeVolume, - defaultThreshold: opts.defaultThreshold, - delayUntilDefault: opts.delayUntilDefault, - }, - opts.revenueHiding, - opts.targetPerTokChainlinkFeed, - opts.chainlinkFeed ?? opts.uoaPerTargetChainlinkFeed, - opts.uoaPerTargetChainlinkTimeout, - { gasLimit: 2000000000 } - ) - - // Push forward chainlink feed - await pushOracleForward(opts.uoaPerTargetChainlinkFeed!) - - await collateral.deployed() - // sometimes we are trying to test a negative test case and we want this to fail silently - // fortunately this syntax fails silently because our tools are terrible - await expect(collateral.refresh()) - - return collateral + opts = { ...defaultWSUPEROETHBCollateralOpts, ...opts } + + const WSuperOETHbCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'OETHCollateralL2Base' + ) + + const collateral = await WSuperOETHbCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + rewardERC20: opts.rewardERC20, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + opts.targetPerTokChainlinkFeed, + opts.chainlinkFeed ?? opts.uoaPerTargetChainlinkFeed, + opts.uoaPerTargetChainlinkTimeout, + { gasLimit: 2000000000 } + ) + + // Push forward chainlink feed + await pushOracleForward(opts.uoaPerTargetChainlinkFeed!) + + await collateral.deployed() + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return collateral } const defaultAnswers = { - targetPerRefChainlinkFeed: bn('1e18'), - uoaPerTargetChainlinkFeed: bn('2000e8'), - refPerTokenChainlinkFeed: bn('1.1e18'), + targetPerRefChainlinkFeed: bn('1e18'), + uoaPerTargetChainlinkFeed: bn('2000e8'), + refPerTokenChainlinkFeed: bn('1.1e18'), } type Fixture = () => Promise const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - opts: CollateralOpts = {} + alice: SignerWithAddress, + opts: CollateralOpts = {} ): Fixture => { - const collateralOpts = { ...defaultWSUPEROETHBCollateralOpts, ...opts } - - const makeCollateralFixtureContext = async () => { - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - const targetPerRefChainlinkFeed = await MockV3AggregatorFactory.deploy( - 18, - defaultAnswers.targetPerRefChainlinkFeed - ) - const uoaPerTargetChainlinkFeed = await MockV3AggregatorFactory.deploy(8, defaultAnswers.uoaPerTargetChainlinkFeed) - - collateralOpts.chainlinkFeed = uoaPerTargetChainlinkFeed.address - collateralOpts.uoaPerTargetChainlinkFeed = uoaPerTargetChainlinkFeed.address - - const wsuperOETHb = (await ethers.getContractAt('IWSuperOETHb', BASE_WSUPEROETHB)) as IWSuperOETHb - const rewardToken = (await ethers.getContractAt('ERC20Mock', ZERO_ADDRESS)) as ERC20Mock - const collateral = await deployCollateral(collateralOpts) - - return { - alice, - collateral, - wsuperoethb: wsuperOETHb, - tok: wsuperOETHb, - rewardToken, - chainlinkFeed: uoaPerTargetChainlinkFeed, - targetPerRefChainlinkFeed: targetPerRefChainlinkFeed, - uoaPerTargetChainlinkFeed, - } + const collateralOpts = { ...defaultWSUPEROETHBCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const targetPerRefChainlinkFeed = await MockV3AggregatorFactory.deploy( + 18, + defaultAnswers.targetPerRefChainlinkFeed + ) + const uoaPerTargetChainlinkFeed = await MockV3AggregatorFactory.deploy( + 8, + defaultAnswers.uoaPerTargetChainlinkFeed + ) + + collateralOpts.chainlinkFeed = uoaPerTargetChainlinkFeed.address + collateralOpts.uoaPerTargetChainlinkFeed = uoaPerTargetChainlinkFeed.address + + const wsuperOETHb = (await ethers.getContractAt( + 'IWSuperOETHb', + BASE_WSUPEROETHB + )) as IWSuperOETHb + const rewardToken = (await ethers.getContractAt('ERC20Mock', ZERO_ADDRESS)) as ERC20Mock + const collateral = await deployCollateral(collateralOpts) + + return { + alice, + collateral, + wsuperoethb: wsuperOETHb, + tok: wsuperOETHb, + rewardToken, + chainlinkFeed: uoaPerTargetChainlinkFeed, + targetPerRefChainlinkFeed: targetPerRefChainlinkFeed, + uoaPerTargetChainlinkFeed, } + } - return makeCollateralFixtureContext + return makeCollateralFixtureContext } /* @@ -158,101 +164,104 @@ const makeCollateralFixtureContext = ( */ const mintCollateralTo: MintCollateralFunc = async ( - ctx: WSUPEROETHBCollateralFixtureContext, - amount: BigNumberish, - user: SignerWithAddress, - recipient: string + ctx: WSUPEROETHBCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string ) => { - await mintWSUPEROETHB(ctx.wsuperoethb, user, amount, recipient, BASE_WSUPEROETHB_WHALE) + await mintWSUPEROETHB(ctx.wsuperoethb, user, amount, recipient, BASE_WSUPEROETHB_WHALE) } const reduceTargetPerRef = async ( - ctx: WSUPEROETHBCollateralFixtureContext, - pctDecrease: BigNumberish + ctx: WSUPEROETHBCollateralFixtureContext, + pctDecrease: BigNumberish ) => { - const lastRound = await ctx.chainlinkFeed.latestRoundData() - const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) - await ctx.chainlinkFeed.updateAnswer(nextAnswer) + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) } const increaseTargetPerRef = async ( - ctx: WSUPEROETHBCollateralFixtureContext, - pctIncrease: BigNumberish + ctx: WSUPEROETHBCollateralFixtureContext, + pctIncrease: BigNumberish ) => { - const lastRound = await ctx.chainlinkFeed.latestRoundData() - const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) - await ctx.chainlinkFeed.updateAnswer(nextAnswer) + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) } -const reduceRefPerTok = async (ctx: WSUPEROETHBCollateralFixtureContext, pctDecrease: BigNumberish) => { - const slot = 2 - const storedTotalSupply = BigNumber.from(await getStorageAt(ctx.tok.address, slot)) - const newStoredTotalAssets = storedTotalSupply.add(storedTotalSupply.mul(pctDecrease).div(100)) - await setStorageAt(ctx.tok.address, slot, newStoredTotalAssets) +const reduceRefPerTok = async ( + ctx: WSUPEROETHBCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + const slot = 2 + const storedTotalSupply = BigNumber.from(await getStorageAt(ctx.tok.address, slot)) + const newStoredTotalAssets = storedTotalSupply.add(storedTotalSupply.mul(pctDecrease).div(100)) + await setStorageAt(ctx.tok.address, slot, newStoredTotalAssets) } const increaseRefPerTok = async ( - ctx: WSUPEROETHBCollateralFixtureContext, - pctIncrease: BigNumberish + ctx: WSUPEROETHBCollateralFixtureContext, + pctIncrease: BigNumberish ) => { - const slot = 2 - const storedTotalSupply = BigNumber.from(await getStorageAt(ctx.tok.address, slot)) - const newStoredTotalAssets = storedTotalSupply.sub(storedTotalSupply.mul(pctIncrease).div(100)) - await setStorageAt(ctx.tok.address, slot, newStoredTotalAssets) + const slot = 2 + const storedTotalSupply = BigNumber.from(await getStorageAt(ctx.tok.address, slot)) + const newStoredTotalAssets = storedTotalSupply.sub(storedTotalSupply.mul(pctIncrease).div(100)) + await setStorageAt(ctx.tok.address, slot, newStoredTotalAssets) } const getExpectedPrice = async (ctx: WSUPEROETHBCollateralFixtureContext): Promise => { - const uoaPerTargetChainlinkFeedAnswer = await ctx.uoaPerTargetChainlinkFeed.latestAnswer() - const uoaPerTargetChainlinkFeedDecimals = await ctx.uoaPerTargetChainlinkFeed.decimals() + const uoaPerTargetChainlinkFeedAnswer = await ctx.uoaPerTargetChainlinkFeed.latestAnswer() + const uoaPerTargetChainlinkFeedDecimals = await ctx.uoaPerTargetChainlinkFeed.decimals() - const refPerTok = await ctx.collateral.refPerTok() + const refPerTok = await ctx.collateral.refPerTok() - const result = uoaPerTargetChainlinkFeedAnswer - .mul(bn(10).pow(18 - uoaPerTargetChainlinkFeedDecimals)) - .mul(refPerTok) - .div(fp('1')) + const result = uoaPerTargetChainlinkFeedAnswer + .mul(bn(10).pow(18 - uoaPerTargetChainlinkFeedDecimals)) + .mul(refPerTok) + .div(fp('1')) - return result + return result } -const collateralSpecificConstructorTests = () => { } +const collateralSpecificConstructorTests = () => {} // eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificStatusTests = () => { } +const collateralSpecificStatusTests = () => {} // eslint-disable-next-line @typescript-eslint/no-empty-function -const beforeEachRewardsTest = async () => { } +const beforeEachRewardsTest = async () => {} /* Run the test suite */ const opts = { - deployCollateral, - collateralSpecificConstructorTests, - collateralSpecificStatusTests, - beforeEachRewardsTest, - makeCollateralFixtureContext, - mintCollateralTo, - reduceTargetPerRef, - increaseTargetPerRef, - reduceRefPerTok, - increaseRefPerTok, - getExpectedPrice, - itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, - itChecksTargetPerRefDefaultUp: it.skip, - itChecksRefPerTokDefault: it, - itChecksPriceChanges: it, - itChecksNonZeroDefaultThreshold: it, - itHasRevenueHiding: it, - resetFork: getResetFork(FORK_BLOCK_BASE), - collateralName: 'OETHCollateralL2Base', - chainlinkDefaultAnswer: defaultAnswers.uoaPerTargetChainlinkFeed, - itIsPricedByPeg: true, - itHasOracleRefPerTok: true, - targetNetwork: 'base', - toleranceDivisor: bn('1e2'), + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefaultUp: it.skip, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK_BASE), + collateralName: 'OETHCollateralL2Base', + chainlinkDefaultAnswer: defaultAnswers.uoaPerTargetChainlinkFeed, + itIsPricedByPeg: true, + itHasOracleRefPerTok: true, + targetNetwork: 'base', + toleranceDivisor: bn('1e2'), } collateralTests(opts) From b3835f6019608a257caaac72f35226162a0bacdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 29 Oct 2024 18:18:50 +0100 Subject: [PATCH 6/6] fix empy function --- .../individual-collateral/origin/OETHCollateralL2Base.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts b/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts index ed216d64e..e183da143 100644 --- a/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts +++ b/test/plugins/individual-collateral/origin/OETHCollateralL2Base.test.ts @@ -224,6 +224,7 @@ const getExpectedPrice = async (ctx: WSUPEROETHBCollateralFixtureContext): Promi return result } +// eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificConstructorTests = () => {} // eslint-disable-next-line @typescript-eslint/no-empty-function