Skip to content
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
5 changes: 4 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ jobs:
TS_NODE_SKIP_IGNORE: true
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet
FORK_BLOCK: latest
PROTO_IMPL: 1
FORK: 1

Expand Down Expand Up @@ -111,6 +112,7 @@ jobs:
TS_NODE_SKIP_IGNORE: true
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet
FORK_BLOCK: latest
PROTO_IMPL: 1
FORK: 1

Expand Down Expand Up @@ -138,6 +140,7 @@ jobs:
TS_NODE_SKIP_IGNORE: true
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet
FORK_BLOCK: latest
PROTO_IMPL: 1
FORK: 1

Expand Down Expand Up @@ -165,7 +168,7 @@ jobs:
TS_NODE_SKIP_IGNORE: true
BASE_RPC_URL: https://base-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_BASE_KEY }}
FORK_NETWORK: base
FORK_BLOCK: 4446300
FORK_BLOCK: latest
FORK: 1
PROTO_IMPL: 1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,30 @@ pragma solidity 0.8.19;

import "./CurveStableCollateral.sol";

// No usage currently, solely for inheriting from

/**
* @title CurveAppreciatingRTokenFiatCollateral
* This plugin contract is intended for use with a CurveLP token for a pool between a
* USD reference token and an RToken that is appreciating relative to it.
* Works for both CurveGaugeWrapper and ConvexStakingWrapper.
* This plugin contract is intended for use with a v7.0.0 CurveStableSwapNG pool/token
Comment thread
julianmrodri marked this conversation as resolved.
* with an internal exchange rate accounting method for appreciation of one side of the pool.
* Works for both CurveGaugeWrapper and ConvexStakingWrapper.
*
* Warning: Defaults after haircut! After the RToken accepts a devaluation this collateral
* plugin will default and the collateral will be removed from the basket.
*
* LP Token should be worth 2x the reference token at deployment
* LP Token should be worth 1x the reference token at deployment, not 2x like many CryptoSwaps.
*
* tok = ConvexStakingWrapper(volatileCryptoPool)
* tok = ConvexStakingWrapper(stableSwapNGPool)
* ref = USDC
* tar = USD
* UoA = USD
*
* @notice Curve pools with native ETH or ERC777 should be avoided,
* see docs/collateral.md for information
* see docs/collateral.md for information.
* However there are reentrancy checks based on the `totalSupply()` function of the pool.
*
* Important: Make sure the Curve pool is compatible with the plugin! This includes pool
* invariants AND security assumptions like reentrancy vectors. Compare to existing deployments!
*/
contract CurveAppreciatingRTokenFiatCollateral is CurveStableCollateral {
using OracleLib for AggregatorV3Interface;
Expand All @@ -30,26 +36,42 @@ contract CurveAppreciatingRTokenFiatCollateral is CurveStableCollateral {
IAssetRegistry internal immutable pairedAssetRegistry; // AssetRegistry of paired RToken
IBasketHandler internal immutable pairedBasketHandler; // BasketHandler of paired RToken

uint256 public immutable pairedRTokenRefreshInterval; // {s}

/// @dev config Unused members: chainlinkFeed, oracleError, oracleTimeout
/// @dev config.erc20 should be a CurveGaugeWrapper or ConvexStakingWrapper
/// @param pairedRTokenRefreshInterval_ {s} Refresh interval of the inner RToken
constructor(
CollateralConfig memory config,
uint192 revenueHiding,
PTConfiguration memory ptConfig
PTConfiguration memory ptConfig,
uint256 pairedRTokenRefreshInterval_
) CurveStableCollateral(config, revenueHiding, ptConfig) {
rToken = IRToken(address(token0));
IMain main = rToken.main();
pairedAssetRegistry = main.assetRegistry();
pairedBasketHandler = main.basketHandler();

pairedRTokenRefreshInterval = pairedRTokenRefreshInterval_;
}

/// Should not revert
/// Refresh exchange rates and update default status.
/// Have to override to add custom default checks
function refresh() public virtual override {
// solhint-disable-next-line no-empty-blocks
try pairedAssetRegistry.refresh() {} catch {
// must allow failure since cannot brick refresh()
// gas-cheap re-entrancy check, since CurveStableSwapNG v7.0.0
// example: https://etherscan.io/token/0x2c683fad51da2cd17793219cc86439c1875c353e
IERC20(address(curvePool)).totalSupply();

// refresh paired (inner) RToken lazily
if (
pairedRTokenRefreshInterval == 0 ||
pairedAssetRegistry.lastRefresh() + pairedRTokenRefreshInterval <= block.timestamp
) {
// solhint-disable-next-line no-empty-blocks
try pairedAssetRegistry.refresh() {} catch {
// must allow failure since cannot brick refresh()
}
}

CollateralStatus oldStatus = status();
Expand Down Expand Up @@ -126,23 +148,15 @@ contract CurveAppreciatingRTokenFiatCollateral is CurveStableCollateral {
}
}

/// @dev Not up-only! The RToken can devalue its exchange rate peg
/// @dev Assumption: The RToken BU is intended to equal the reference token in value
/// @return {ref/tok} Quantity of whole reference units per whole collateral tokens
function underlyingRefPerTok() public view virtual override returns (uint192) {
// {ref/tok} = quantity of the reference unit token in the pool per LP token

// {lpToken@t=0/lpToken}
uint192 virtualPrice = _safeWrap(curvePool.get_virtual_price());
// this is missing the fact that the RToken has also appreciated in this time

// {BU/rTok}
uint192 rTokenRate = divuu(rToken.basketsNeeded(), rToken.totalSupply());
// not worth the gas to protect against div-by-zero

// {ref/tok} = {ref/lpToken} = {lpToken@t=0/lpToken} * {1} * 2{ref/lpToken@t=0}
return virtualPrice.mul(rTokenRate.sqrt()).mulu(2); // LP token worth twice as much
}
// inherits from CurveStableCollateral, implementation kept as documentation
//
// /// @dev Not up-only! The RToken can devalue its exchange rate peg
// /// @dev Assumption: The RToken BU is intended to equal the reference token in value
// /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens
// function underlyingRefPerTok() public view virtual override returns (uint192) {
// // pool already incorporates all prior RToken appreciation into the virtual price
// return _safeWrap(curvePool.get_virtual_price());
// }

/// @dev Warning: Can revert
/// @dev Only works when the RToken is the 0th index token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@ import "./CurveAppreciatingRTokenFiatCollateral.sol";
* Warning: Defaults after haircut! After the RToken accepts a devaluation this collateral
* plugin will default and the collateral will be removed from the basket.
*
* LP Token should be worth 2x the reference token at deployment
* LP Token should be worth 1x the reference token at deployment, not 2x like many CryptoSwaps.
*
* tok = ConvexStakingWrapper(volatileCryptoPool)
* tok = ConvexStakingWrapper(stableSwapNGPool)
* ref = WETH
* tar = ETH
* UoA = USD
*
* @notice This Curve Pool contains WETH, which can be used to intercept execution by providing
* `use_eth=true` to remove_liquidity()/remove_liquidity_one_coin(). It is guarded against
* by the recommended method of calling `claim_admin_fees()`.
* Important: Make sure the Curve pool is compatible with the plugin! This includes pool
* invariants AND security assumptions like reentrancy vectors. Compare to existing deployments!
*/
contract CurveAppreciatingRTokenSelfReferentialCollateral is CurveAppreciatingRTokenFiatCollateral {
using OracleLib for AggregatorV3Interface;
Expand All @@ -31,18 +30,20 @@ contract CurveAppreciatingRTokenSelfReferentialCollateral is CurveAppreciatingRT

/// @dev config Unused members: chainlinkFeed, oracleError, oracleTimeout
/// @dev config.erc20 should be a CurveGaugeWrapper or ConvexStakingWrapper
/// @param pairedRTokenRefreshInterval_ {s} Refresh interval of the inner RToken
constructor(
CollateralConfig memory config,
uint192 revenueHiding,
PTConfiguration memory ptConfig
) CurveAppreciatingRTokenFiatCollateral(config, revenueHiding, ptConfig) {}

/// Should not revert (unless CurvePool is re-entrant!)
/// Refresh exchange rates and update default status.
function refresh() public virtual override {
curvePool.claim_admin_fees(); // revert if curve pool is re-entrant
super.refresh();
}
PTConfiguration memory ptConfig,
uint256 pairedRTokenRefreshInterval_
)
CurveAppreciatingRTokenFiatCollateral(
config,
revenueHiding,
ptConfig,
pairedRTokenRefreshInterval_
)
{}

// === Internal ===

Expand Down
3 changes: 3 additions & 0 deletions contracts/plugins/assets/curve/CurveRecursiveCollateral.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import "../OracleLib.sol";
* ref = coins(0) in the pool
* tar = USD
* UoA = USD
*
* Important: Make sure the Curve pool is compatible with the plugin! This includes pool
* invariants AND security assumptions like reentrancy vectors. Compare to existing deployments!
*/
contract CurveRecursiveCollateral is CurveStableCollateral {
using OracleLib for AggregatorV3Interface;
Expand Down
3 changes: 3 additions & 0 deletions contracts/plugins/assets/curve/CurveStableCollateral.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ IERC20 constant CVX = IERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B);
*
* @notice Curve pools with native ETH or ERC777 should be avoided,
* see docs/collateral.md for information
*
* Important: Make sure the Curve pool is compatible with the plugin! This includes pool
* invariants AND security assumptions like reentrancy vectors. Compare to existing deployments!
*/
contract CurveStableCollateral is AppreciatingFiatCollateral, PoolTokens {
using OracleLib for AggregatorV3Interface;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ interface ICurveMetaPool is ICurvePool, IERC20Metadata {
*
* @notice Curve pools with native ETH or ERC777 should be avoided,
* see docs/collateral.md for information
*
* Important: Make sure the Curve pool is compatible with the plugin! This includes pool
* invariants AND security assumptions like reentrancy vectors. Compare to existing deployments!
*/
contract CurveStableMetapoolCollateral is CurveStableCollateral {
using OracleLib for AggregatorV3Interface;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import "./CurveStableMetapoolCollateral.sol";
*
* @notice Curve pools with native ETH or ERC777 should be avoided,
* see docs/collateral.md for information
*
* Important: Make sure the Curve pool is compatible with the plugin! This includes pool
* invariants AND security assumptions like reentrancy vectors. Compare to existing deployments!
*/
contract CurveStableRTokenMetapoolCollateral is CurveStableMetapoolCollateral {
using FixLib for uint192;
Expand Down
3 changes: 3 additions & 0 deletions contracts/plugins/assets/curve/L2ConvexStableCollateral.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ interface IConvexRewardPool is IERC20Metadata {
*
* @notice Pools with native ETH or ERC777 should be avoided,
* see docs/collateral.md for information
*
* Important: Make sure the Curve pool is compatible with the plugin! This includes pool
* invariants AND security assumptions like reentrancy vectors. Compare to existing deployments!
*/
contract L2ConvexStableCollateral is CurveStableCollateral {
using OracleLib for AggregatorV3Interface;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ interface IStakeDAOGauge is IERC20Metadata {
* ref = USDC
* tar = USD
* UoA = USD
*
* Important: Make sure the Curve pool is compatible with the plugin! This includes pool
* invariants AND security assumptions like reentrancy vectors. Compare to existing deployments!
*/
contract StakeDAORecursiveCollateral is CurveRecursiveCollateral {
using OracleLib for AggregatorV3Interface;
Expand Down
4 changes: 4 additions & 0 deletions contracts/plugins/mocks/CurveMetapoolMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ contract CurveMetapoolMock is CurvePoolMock, ERC20Mock {
CurvePoolMock(intialBalances, _coins)
ERC20Mock("Mock CurveMetaPool", "Mock CMP")
{}

function totalSupply() public view override(CurvePoolMock, ERC20) returns (uint256) {
return ERC20.totalSupply();
}
}
4 changes: 4 additions & 0 deletions contracts/plugins/mocks/CurvePoolMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ contract CurvePoolMock is ICurvePool {
uint256,
uint256
) external {}

function totalSupply() public view virtual returns (uint256) {
return 0;
}
}

interface ICurvePoolVariantInt {
Expand Down
16 changes: 11 additions & 5 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,18 @@ const config: HardhatUserConfig = {
defaultNetwork: 'hardhat',
networks: {
hardhat: {
// network for tests/in-process stuff
forking: useEnv('FORK')
? {
url: forkRpcs[useEnv('FORK_NETWORK', 'mainnet') as Network],
blockNumber: Number(useEnv(`FORK_BLOCK`, forkBlockNumber['default'].toString())),
}
? (() => {
const forkBlock = useEnv(`FORK_BLOCK`, forkBlockNumber['default'].toString())
const forking: { url: string; blockNumber?: number } = {
url: forkRpcs[useEnv('FORK_NETWORK', 'mainnet') as Network],
}
// Only add blockNumber if it's not "latest"
if (forkBlock !== 'latest') {
forking.blockNumber = Number(forkBlock)
}
return forking
})()
: undefined,
gas: 0x1ffffffff,
blockGasLimit: 0x1fffffffffffff,
Expand Down
6 changes: 3 additions & 3 deletions scripts/deployment/phase2-assets/2_deploy_collateral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ async function main() {
oracleError: fp('0.01').toString(), // 1%
tokenAddress: networkConfig[chainId].tokens.USDP,
maxTradeVolume: fp('1e6').toString(), // $1m,
oracleTimeout: '3600', // 1 hr
oracleTimeout: '86400', // 24 hr
targetName: hre.ethers.utils.formatBytes32String('USD'),
defaultThreshold: fp('0.02').toString(), // 2%
delayUntilDefault: bn('86400').toString(), // 24h
Expand Down Expand Up @@ -409,7 +409,7 @@ async function main() {
oracleError: fp('0.01').toString(), // 1%
staticAToken: ausdpStaticToken.address,
maxTradeVolume: fp('1e6').toString(), // $1m,
oracleTimeout: '3600', // 1 hr
oracleTimeout: '86400', // 24 hr
targetName: hre.ethers.utils.formatBytes32String('USD'),
defaultThreshold: fp('0.02').toString(), // 2%
delayUntilDefault: bn('86400').toString(), // 24h
Expand Down Expand Up @@ -508,7 +508,7 @@ async function main() {
oracleError: fp('0.01').toString(), // 1%
cToken: networkConfig[chainId].tokens.cUSDP,
maxTradeVolume: fp('1e6').toString(), // $1m,
oracleTimeout: '3600', // 1 hr
oracleTimeout: '86400', // 24 hr
targetName: hre.ethers.utils.formatBytes32String('USD'),
defaultThreshold: fp('0.02').toString(), // 2%
delayUntilDefault: bn('86400').toString(), // 24h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ async function main() {

/******** Deploy Convex Appreciating RToken Collateral for ETH+/ETH **************************/

const CurveStableCollateralFactory = await hre.ethers.getContractFactory(
'CurveAppreciatingRTokenSelfReferentialCollateral'
)
const ConvexStakingWrapperFactory = await hre.ethers.getContractFactory('ConvexStakingWrapper')

const CurveAppreciatingRTokenSelfReferentialCollateralFactory =
await hre.ethers.getContractFactory('CurveAppreciatingRTokenSelfReferentialCollateral')

const wPool = await ConvexStakingWrapperFactory.deploy()
await wPool.deployed()
await (await wPool.initialize(ETHPLUS_BP_POOL_ID)).wait()
Expand All @@ -70,7 +70,7 @@ async function main() {
)

const collateral = <CurveAppreciatingRTokenSelfReferentialCollateral>(
await CurveStableCollateralFactory.connect(deployer).deploy(
await CurveAppreciatingRTokenSelfReferentialCollateralFactory.connect(deployer).deploy(
{
erc20: wPool.address,
targetName: hre.ethers.utils.formatBytes32String('ETH'),
Expand All @@ -91,7 +91,8 @@ async function main() {
oracleTimeouts: [[bn('1')], [WETH_ORACLE_TIMEOUT]],
oracleErrors: [[bn('1')], [WETH_ORACLE_ERROR]],
lpToken: ETHPLUS_BP_TOKEN,
}
},
'86400' // refresh ETH+ every 24h
)
)
await collateral.deployed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ async function main() {
[networkConfig[chainId].chainlinkFeeds.USDP],
[networkConfig[chainId].chainlinkFeeds.crvUSD],
],
oracleTimeouts: [['3600'], ['86400']],
oracleTimeouts: [['86400'], ['86400']],
oracleErrors: [[fp('0.01').toString()], [fp('0.005').toString()]],
lpToken: YVUSDP_LP_TOKEN,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ async function main() {
'contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper'
)

/******** Verify eUSD/fraxBP plugin **************************/
/******** Verify ETH+/ETH plugin **************************/
await verifyContract(
chainId,
deployments.collateral.cvxETHPlusETH,
Expand All @@ -78,6 +78,7 @@ async function main() {
oracleErrors: [[bn('1')], [WETH_ORACLE_ERROR]],
lpToken: ETHPLUS_BP_TOKEN,
},
'86400', // refresh ETH+ every 24h
],
'contracts/plugins/assets/curve/CurveAppreciatingRTokenSelfReferentialCollateral.sol:CurveAppreciatingRTokenSelfReferentialCollateral'
)
Expand Down
Loading
Loading