Skip to content
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

Convex crvUSD/USDC #1082

Merged
merged 12 commits into from
Feb 26, 2024
56 changes: 34 additions & 22 deletions test/plugins/individual-collateral/curve/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,69 @@ import { networkConfig } from '../../../../common/configuration'
// Mainnet Addresses

// DAI
export const DAI_USD_FEED = networkConfig['1'].chainlinkFeeds.DAI as string
export const DAI_USD_FEED = networkConfig['1'].chainlinkFeeds.DAI!
export const DAI_ORACLE_TIMEOUT = bn('86400')
export const DAI_ORACLE_ERROR = fp('0.0025')

// USDC
export const USDC_USD_FEED = networkConfig['1'].chainlinkFeeds.USDC as string
export const USDC_USD_FEED = networkConfig['1'].chainlinkFeeds.USDC!
export const USDC_ORACLE_TIMEOUT = bn('86400')
export const USDC_ORACLE_ERROR = fp('0.0025')

// USDT
export const USDT_USD_FEED = networkConfig['1'].chainlinkFeeds.USDT as string
export const USDT_USD_FEED = networkConfig['1'].chainlinkFeeds.USDT!
export const USDT_ORACLE_TIMEOUT = bn('86400')
export const USDT_ORACLE_ERROR = fp('0.0025')

// SUSD
export const SUSD_USD_FEED = networkConfig['1'].chainlinkFeeds.sUSD as string
export const SUSD_USD_FEED = networkConfig['1'].chainlinkFeeds.sUSD!
export const SUSD_ORACLE_TIMEOUT = bn('86400')
export const SUSD_ORACLE_ERROR = fp('0.0025')

// FRAX
export const FRAX_USD_FEED = networkConfig['1'].chainlinkFeeds.FRAX as string
export const FRAX_USD_FEED = networkConfig['1'].chainlinkFeeds.FRAX!
export const FRAX_ORACLE_TIMEOUT = bn('3600')
export const FRAX_ORACLE_ERROR = fp('0.01')

// WBTC
export const WBTC_BTC_FEED = networkConfig['1'].chainlinkFeeds.WBTC as string
export const BTC_USD_FEED = networkConfig['1'].chainlinkFeeds.BTC as string
export const WBTC_BTC_FEED = networkConfig['1'].chainlinkFeeds.WBTC!
export const BTC_USD_FEED = networkConfig['1'].chainlinkFeeds.BTC!
export const WBTC_ORACLE_TIMEOUT = bn('86400')
export const BTC_ORACLE_TIMEOUT = bn('3600')
export const WBTC_BTC_ORACLE_ERROR = fp('0.02')
export const BTC_USD_ORACLE_ERROR = fp('0.005')

// WETH
export const WETH_USD_FEED = networkConfig['1'].chainlinkFeeds.ETH as string
export const WETH_USD_FEED = networkConfig['1'].chainlinkFeeds.ETH!
export const WETH_ORACLE_TIMEOUT = bn('86400')
export const WETH_ORACLE_ERROR = fp('0.005')

// MIM
export const MIM_USD_FEED = networkConfig['1'].chainlinkFeeds.MIM as string
export const MIM_USD_FEED = networkConfig['1'].chainlinkFeeds.MIM!
export const MIM_ORACLE_TIMEOUT = bn('86400')
export const MIM_ORACLE_ERROR = fp('0.005') // 0.5%
export const MIM_DEFAULT_THRESHOLD = fp('0.055') // 5.5%

// crvUSD
export const crvUSD_USD_FEED = networkConfig['1'].chainlinkFeeds.crvUSD!
export const crvUSD_ORACLE_TIMEOUT = bn('86400')
export const crvUSD_ORACLE_ERROR = fp('0.005')

// Tokens
export const DAI = networkConfig['1'].tokens.DAI as string
export const USDC = networkConfig['1'].tokens.USDC as string
export const USDT = networkConfig['1'].tokens.USDT as string
export const SUSD = networkConfig['1'].tokens.sUSD as string
export const FRAX = networkConfig['1'].tokens.FRAX as string
export const MIM = networkConfig['1'].tokens.MIM as string
export const eUSD = networkConfig['1'].tokens.eUSD as string
export const WETH = networkConfig['1'].tokens.WETH as string
export const WBTC = networkConfig['1'].tokens.WBTC as string

export const RSR = networkConfig['1'].tokens.RSR as string
export const CRV = networkConfig['1'].tokens.CRV as string
export const CVX = networkConfig['1'].tokens.CVX as string
export const DAI = networkConfig['1'].tokens.DAI!
export const USDC = networkConfig['1'].tokens.USDC!
export const USDT = networkConfig['1'].tokens.USDT!
export const SUSD = networkConfig['1'].tokens.sUSD!
export const FRAX = networkConfig['1'].tokens.FRAX!
export const MIM = networkConfig['1'].tokens.MIM!
export const eUSD = networkConfig['1'].tokens.eUSD!
export const WETH = networkConfig['1'].tokens.WETH!
export const WBTC = networkConfig['1'].tokens.WBTC!
export const crvUSD = networkConfig['1'].tokens.crvUSD!

export const RSR = networkConfig['1'].tokens.RSR!
export const CRV = networkConfig['1'].tokens.CRV!
export const CVX = networkConfig['1'].tokens.CVX!

// 3pool - USDC, USDT, DAI
export const THREE_POOL = '0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7'
Expand Down Expand Up @@ -100,6 +106,12 @@ export const MIM_THREE_POOL_POOL_ID = 40
export const MIM_THREE_POOL_HOLDER = '0x66C90baCE2B68955C875FdA89Ba2c5A94e672440'
export const MIM_THREE_POOL_GAUGE = '0xd8b712d29381748db89c36bca0138d7c75866ddf'

// crvUSD/USDC
export const crvUSD_USDC = '0x4dece678ceceb27446b35c672dc7d61f30bad69e'
export const crvUSD_USDC_POOL_ID = 182
export const crvUSD_USDC_HOLDER = '0x95f00391cB5EebCd190EB58728B4CE23DbFa6ac1'
export const crvUSD_USDC_GAUGE = '0x95f00391cB5EebCd190EB58728B4CE23DbFa6ac1'

// Curve-specific
export const CURVE_MINTER = '0xd061d61a4d941c39e5453435b6345dc261c2fce0'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import collateralTests from '../collateralTests'
import {
CurveCollateralFixtureContext,
CurveCollateralOpts,
MintCurveCollateralFunc,
} from '../pluginTestTypes'
import { mintWPool } from './helpers'
import { ethers } from 'hardhat'
import { ContractFactory, BigNumberish } from 'ethers'
import {
CurvePoolMock,
ERC20Mock,
MockV3Aggregator,
MockV3Aggregator__factory,
TestICollateral,
} from '../../../../../typechain'
import { bn } from '../../../../../common/numbers'
import { ZERO_ADDRESS } from '../../../../../common/constants'
import { expect } from 'chai'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import {
PRICE_TIMEOUT,
CRV,
CVX,
USDC_USD_FEED,
USDC_ORACLE_TIMEOUT,
USDC_ORACLE_ERROR,
MAX_TRADE_VOL,
DEFAULT_THRESHOLD,
DELAY_UNTIL_DEFAULT,
CurvePoolType,
crvUSD_USD_FEED,
crvUSD_USDC,
crvUSD_ORACLE_TIMEOUT,
crvUSD_ORACLE_ERROR,
crvUSD_USDC_HOLDER,
crvUSD_USDC_POOL_ID,
USDC,
crvUSD,
} from '../constants'
import { getResetFork } from '../../helpers'

type Fixture<T> = () => Promise<T>

export const defaultCvxStableCollateralOpts: CurveCollateralOpts = {
erc20: ZERO_ADDRESS,
targetName: ethers.utils.formatBytes32String('USD'),
priceTimeout: PRICE_TIMEOUT,
chainlinkFeed: crvUSD_USD_FEED, // unused but cannot be zero
oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts
oracleError: bn('1'), // unused but cannot be zero
maxTradeVolume: MAX_TRADE_VOL,
defaultThreshold: DEFAULT_THRESHOLD,
delayUntilDefault: DELAY_UNTIL_DEFAULT,
revenueHiding: bn('0'),
nTokens: 2,
curvePool: crvUSD_USDC,
lpToken: crvUSD_USDC,
poolType: CurvePoolType.Plain,
feeds: [[USDC_USD_FEED], [crvUSD_USD_FEED]],
oracleTimeouts: [[USDC_ORACLE_TIMEOUT], [crvUSD_ORACLE_TIMEOUT]],
oracleErrors: [[USDC_ORACLE_ERROR], [crvUSD_ORACLE_ERROR]],
}

export const deployCollateral = async (
opts: CurveCollateralOpts = {}
): Promise<[TestICollateral, CurveCollateralOpts]> => {
if (!opts.erc20 && !opts.feeds) {
const MockV3AggregatorFactory = <MockV3Aggregator__factory>(
await ethers.getContractFactory('MockV3Aggregator')
)

// Substitute feeds
const usdcFeed = <MockV3Aggregator>await MockV3AggregatorFactory.deploy(8, bn('1e8'))
const crvUSDFeed = <MockV3Aggregator>await MockV3AggregatorFactory.deploy(8, bn('1e8'))

const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper')
const wrapper = await wrapperFactory.deploy()
await wrapper.initialize(crvUSD_USDC_POOL_ID)

opts.feeds = [[usdcFeed.address], [crvUSDFeed.address]]
opts.erc20 = wrapper.address
}

opts = { ...defaultCvxStableCollateralOpts, ...opts }

const CvxStableCollateralFactory: ContractFactory = await ethers.getContractFactory(
'CurveStableCollateral'
)

const collateral = <TestICollateral>await CvxStableCollateralFactory.deploy(
{
erc20: opts.erc20,
targetName: opts.targetName,
priceTimeout: opts.priceTimeout,
chainlinkFeed: opts.chainlinkFeed,
oracleError: opts.oracleError,
oracleTimeout: opts.oracleTimeout,
maxTradeVolume: opts.maxTradeVolume,
defaultThreshold: opts.defaultThreshold,
delayUntilDefault: opts.delayUntilDefault,
},
opts.revenueHiding,
{
nTokens: opts.nTokens,
curvePool: opts.curvePool,
poolType: opts.poolType,
feeds: opts.feeds,
oracleTimeouts: opts.oracleTimeouts,
oracleErrors: opts.oracleErrors,
lpToken: opts.lpToken,
}
)
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]
}

const makeCollateralFixtureContext = (
alice: SignerWithAddress,
opts: CurveCollateralOpts = {}
): Fixture<CurveCollateralFixtureContext> => {
const collateralOpts = { ...defaultCvxStableCollateralOpts, ...opts }

const makeCollateralFixtureContext = async () => {
const MockV3AggregatorFactory = <MockV3Aggregator__factory>(
await ethers.getContractFactory('MockV3Aggregator')
)

// Substitute feeds
const usdcFeed = <MockV3Aggregator>await MockV3AggregatorFactory.deploy(8, bn('1e8'))
const crvUSDFeed = <MockV3Aggregator>await MockV3AggregatorFactory.deploy(8, bn('1e8'))
collateralOpts.feeds = [[usdcFeed.address], [crvUSDFeed.address]]

// Use mock curvePool seeded with initial balances
const CurvePoolMockFactory = await ethers.getContractFactory('CurvePoolMock')
const realCurvePool = <CurvePoolMock>await ethers.getContractAt('CurvePoolMock', crvUSD_USDC)
const curvePool = <CurvePoolMock>(
await CurvePoolMockFactory.deploy(
[await realCurvePool.balances(0), await realCurvePool.balances(1)],
[await realCurvePool.coins(0), await realCurvePool.coins(1)]
)
)
await curvePool.setVirtualPrice(await realCurvePool.get_virtual_price())

// Deploy Wrapper
const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper')
const wrapper = await wrapperFactory.deploy()
await wrapper.initialize(crvUSD_USDC_POOL_ID)

collateralOpts.erc20 = wrapper.address
collateralOpts.curvePool = curvePool.address
const collateral = <TestICollateral>((await deployCollateral(collateralOpts))[0] as unknown)
const cvx = <ERC20Mock>await ethers.getContractAt('ERC20Mock', CVX)
const crv = <ERC20Mock>await ethers.getContractAt('ERC20Mock', CRV)

return {
alice,
collateral,
curvePool: curvePool,
wrapper: wrapper,
rewardTokens: [cvx, crv],
poolTokens: [
await ethers.getContractAt('ERC20Mock', USDC),
await ethers.getContractAt('ERC20Mock', crvUSD),
],
feeds: [usdcFeed, crvUSDFeed],
}
}

return makeCollateralFixtureContext
}

/*
Define helper functions
*/

const mintCollateralTo: MintCurveCollateralFunc<CurveCollateralFixtureContext> = async (
ctx: CurveCollateralFixtureContext,
amount: BigNumberish,
user: SignerWithAddress,
recipient: string
) => {
await mintWPool(ctx, amount, user, recipient, crvUSD_USDC_HOLDER)
}

/*
Define collateral-specific tests
*/

// eslint-disable-next-line @typescript-eslint/no-empty-function
const collateralSpecificConstructorTests = () => {}

// eslint-disable-next-line @typescript-eslint/no-empty-function
const collateralSpecificStatusTests = () => {}

/*
Run the test suite
*/

const opts = {
deployCollateral,
collateralSpecificConstructorTests,
collateralSpecificStatusTests,
makeCollateralFixtureContext,
mintCollateralTo,
itChecksTargetPerRefDefault: it,
itChecksTargetPerRefDefaultUp: it,
itChecksRefPerTokDefault: it,
itHasRevenueHiding: it,
itClaimsRewards: it,
isMetapool: false,
resetFork: getResetFork(19287000),
collateralName: 'CurveStableCollateral - ConvexStakingWrapper (crvUSD/USDC)',
}

collateralTests(opts)
4 changes: 2 additions & 2 deletions test/plugins/individual-collateral/curve/cvx/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ export const mintWPool = async (
await lpToken.connect(signer).transfer(user.address, amount)
})

await lpToken.connect(user).approve(ctx.wrapper.address, amount)
await ctx.wrapper.connect(user).deposit(amount, recipient)
await lpToken.connect(user).approve(cvxWrapper.address, amount)
await cvxWrapper.connect(user).deposit(amount, recipient)
}

export const resetFork = getResetFork(FORK_BLOCK)
Expand Down
Loading