From e4701bdb08353ce13cac3fd85f7e63d658b02bcc Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 7 Nov 2023 18:20:51 -0500 Subject: [PATCH 1/4] add sFrax collateral impl + tests --- common/configuration.ts | 15 +- contracts/plugins/assets/frax/IFrax.sol | 9 + contracts/plugins/assets/frax/README.md | 33 +++ .../plugins/assets/frax/SFraxCollateral.sol | 38 ++++ .../frax/SFraxCollateralTestSuite.test.ts | 206 ++++++++++++++++++ .../individual-collateral/frax/constants.ts | 17 ++ .../individual-collateral/frax/helpers.ts | 13 ++ 7 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 contracts/plugins/assets/frax/IFrax.sol create mode 100644 contracts/plugins/assets/frax/README.md create mode 100644 contracts/plugins/assets/frax/SFraxCollateral.sol create mode 100644 test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/frax/constants.ts create mode 100644 test/plugins/individual-collateral/frax/helpers.ts diff --git a/common/configuration.ts b/common/configuration.ts index 2276396baf..2cc32f07f0 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -58,6 +58,7 @@ export interface ITokens { cUSDCv3?: string cUSDbCv3?: string ONDO?: string + sFRAX?: string sDAI?: string cbETH?: string STG?: string @@ -177,6 +178,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { rETH: '0xae78736Cd615f374D3085123A210448E74Fc6393', cUSDCv3: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3', + sFRAX: '0xA663B02CF0a4b149d2aD41910CB81e23e1c41c32', sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea', cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', STG: '0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6', @@ -228,7 +230,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { COMET_EXT: '0x285617313887d43256F852cAE0Ee4de4b68D45B0', AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb', - STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b' + STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b', }, '1': { name: 'mainnet', @@ -279,6 +281,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { rETH: '0xae78736Cd615f374D3085123A210448E74Fc6393', cUSDCv3: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3', + sFRAX: '0xA663B02CF0a4b149d2aD41910CB81e23e1c41c32', sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea', cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', STG: '0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6', @@ -327,7 +330,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { COMET_EXT: '0x285617313887d43256F852cAE0Ee4de4b68D45B0', AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb', - STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b' + STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b', }, '3': { name: 'tenderly', @@ -426,7 +429,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { COMET_EXT: '0x285617313887d43256F852cAE0Ee4de4b68D45B0', AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb', - STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b' + STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b', }, '5': { name: 'goerli', @@ -530,7 +533,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { aWETHv3: '0xD4a0e0b9149BCee3C920d2E00b5dE09138fd8bb7', acbETHv3: '0xcf3D55c10DB69f28fD1A75Bd73f3D8A2d9c595ad', sUSDbC: '0x4c80e24119cfb836cdf0a6b53dc23f04f7e652ca', - STG: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df' + STG: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df', }, chainlinkFeeds: { DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr @@ -543,7 +546,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { RSR: '0xAa98aE504658766Dfe11F31c5D95a0bdcABDe0b1', // 2%, 24hr wstETHstETHexr: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24hr cbETHETHexr: '0x868a501e68F3D1E89CfC0D22F6b22E8dabce5F04', // 0.5%, 24hr - STG: '0x63Af8341b62E683B87bB540896bF283D96B4D385' + STG: '0x63Af8341b62E683B87bB540896bF283D96B4D385', }, GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', @@ -552,7 +555,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { COMET_EXT: '0x2F9E3953b2Ef89fA265f2a32ed9F80D00229125B', AAVE_V3_POOL: '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5', AAVE_V3_INCENTIVES_CONTROLLER: '0xf9cc4F0D883F1a1eb2c253bdb46c254Ca51E1F44', - STARGATE_STAKING_CONTRACT: '0x06Eb48763f117c7Be887296CDcdfad2E4092739C' + STARGATE_STAKING_CONTRACT: '0x06Eb48763f117c7Be887296CDcdfad2E4092739C', }, } diff --git a/contracts/plugins/assets/frax/IFrax.sol b/contracts/plugins/assets/frax/IFrax.sol new file mode 100644 index 0000000000..616a83af83 --- /dev/null +++ b/contracts/plugins/assets/frax/IFrax.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +// This interface only used in tests +interface IFrax is IERC20Metadata { + function pool_burn_from(address, uint256) external; // only-callable by frax pools +} diff --git a/contracts/plugins/assets/frax/README.md b/contracts/plugins/assets/frax/README.md new file mode 100644 index 0000000000..12c973fdb2 --- /dev/null +++ b/contracts/plugins/assets/frax/README.md @@ -0,0 +1,33 @@ +# Staked FRAX (sFRAX) Collateral Plugin + +## Summary + +This plugin allows `sFRAX` holders to use their tokens as collateral in the Reserve Protocol. + +sFRAX is a non-upgradeable ERC4626 vault that earns the user the right to an increasing quantity of FRAX over time. The income stream is administered through a timelock + multisig. The only control the timelock has over the vault is the ability to change the rate of interest accrual. At all times `sFRAX` can be redeemed for a prorata portion of the held `FRAX`. + +The timelock + multisig targets a rate of appreciation for `sFRAX` equal to the IORB, or the FED's **interest rate on reserve balances**. In the background, an AMO puts FRAX to work in defi in order to try to cover as much of the interest as possible. Any interest that is not found defi is covered by FXS. If the frax protocol were unable to make good on the targeted IORB rate, they would either have to drop the `sFRAX` yield or risk de-pegging FRAX, which would begin to be become undercollateralized. + +Since it is ERC4626, the redeemable FRAX amount can be gotten by dividing `sFRAX.totalAssets()` by `sFRAX.totalSupply()`. + +No function needs be called in order to update `refPerTok()`. `totalAssets()` is already a function of the block timestamp and increases as time passes. + +We can use 0 revenue hiding since the vault correctly rounds defensively in favor of `sFRAX` holders during deposit/withdrawal (thx t11s). + +No rewards other than the ever-increasing exchange rate. + +`sFRAX` contract: + +## Implementation + +### Units + +| tok | ref | target | UoA | +| ----- | ---- | ------ | --- | +| sFRAX | FRAX | USD | USD | + +### Functions + +#### refPerTok {ref/tok} + +`return divuu(IStakedFrax(address(erc20)).totalAssets(), IStakedFrax(address(erc20)).totalSupply());` diff --git a/contracts/plugins/assets/frax/SFraxCollateral.sol b/contracts/plugins/assets/frax/SFraxCollateral.sol new file mode 100644 index 0000000000..90b7331815 --- /dev/null +++ b/contracts/plugins/assets/frax/SFraxCollateral.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../../../libraries/Fixed.sol"; +import "../../../vendor/oz/IERC4626.sol"; +import "../AppreciatingFiatCollateral.sol"; + +interface IStakedFrax is IERC4626 { + function syncRewardsAndDistribution() external; +} + +/** + * @title sFRAX Collateral + * @notice Collateral plugin for staked FRAX (sFRAX) + * tok = sFRAX (ERC4626 vault) + * ref = FRAX + * tar = USD + * UoA = USD + */ +contract SFraxCollateral is AppreciatingFiatCollateral { + // solhint-disable no-empty-blocks + + /// @param config.chainlinkFeed {UoA/ref} price of DAI in USD terms + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + {} + + // solhint-enable no-empty-blocks + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function _underlyingRefPerTok() internal view override returns (uint192) { + return + divuu( + IStakedFrax(address(erc20)).totalAssets(), + IStakedFrax(address(erc20)).totalSupply() + ); + } +} diff --git a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts new file mode 100644 index 0000000000..cf4038f9aa --- /dev/null +++ b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts @@ -0,0 +1,206 @@ +import collateralTests from '../collateralTests' +import { setStorageAt, getStorageAt } from '@nomicfoundation/hardhat-network-helpers' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' +import { resetFork, mintSFrax } from './helpers' +import { ethers } from 'hardhat' +import { expect } from 'chai' +import { ContractFactory, BigNumberish, BigNumber } from 'ethers' +import { + ERC20Mock, + IERC20Metadata, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../typechain' +import { bn, fp } from '../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../common/constants' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + SFRAX, + ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + FRAX_USD_PRICE_FEED, +} from './constants' + +/* + Define deployment functions +*/ + +export const defaultSFraxCollateralOpts: CollateralOpts = { + erc20: SFRAX, + targetName: ethers.utils.formatBytes32String('USD'), + rewardERC20: ZERO_ADDRESS, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: FRAX_USD_PRICE_FEED, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: fp('0'), +} + +export const deployCollateral = async (opts: CollateralOpts = {}): Promise => { + opts = { ...defaultSFraxCollateralOpts, ...opts } + + const SFraxCollateralFactory: ContractFactory = await ethers.getContractFactory('SFraxCollateral') + const collateral = await SFraxCollateralFactory.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, + { gasLimit: 2000000000 } + ) + 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 chainlinkDefaultAnswer = bn('1e8') + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultSFraxCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) + ) + collateralOpts.chainlinkFeed = chainlinkFeed.address + + const sfrax = (await ethers.getContractAt('IERC20Metadata', SFRAX)) as IERC20Metadata + const rewardToken = (await ethers.getContractAt('ERC20Mock', ZERO_ADDRESS)) as ERC20Mock + const collateral = await deployCollateral(collateralOpts) + const tok = await ethers.getContractAt('IERC20Metadata', await collateral.erc20()) + + return { + alice, + collateral, + chainlinkFeed, + tok, + sfrax, + rewardToken, + } + } + + return makeCollateralFixtureContext +} + +const mintCollateralTo: MintCollateralFunc = async ( + ctx: CollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintSFrax(ctx.tok, amount, recipient) +} + +const reduceTargetPerRef = async (ctx: CollateralFixtureContext, 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: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} + +// prettier-ignore +const reduceRefPerTok = async ( + ctx: CollateralFixtureContext, + pctDecrease: BigNumberish +) => { + const storedTotalAssets = BigNumber.from(await getStorageAt(ctx.tok.address, 9)) + const newStoredTotalAssets = storedTotalAssets.sub(storedTotalAssets.mul(pctDecrease).div(100)) + await setStorageAt(ctx.tok.address, 9, newStoredTotalAssets) +} + +// prettier-ignore +const increaseRefPerTok = async ( + ctx: CollateralFixtureContext, + pctIncrease: BigNumberish + +) => { + const storedTotalAssets = BigNumber.from(await getStorageAt(ctx.tok.address, 9)) + const newStoredTotalAssets = storedTotalAssets.add(storedTotalAssets.mul(pctIncrease).div(100)) + await setStorageAt(ctx.tok.address, 9, newStoredTotalAssets) +} + +const getExpectedPrice = async (ctx: CollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const refPerTok = await ctx.collateral.refPerTok() + return clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(refPerTok) + .div(fp('1')) +} + +/* + 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 = () => {} + +// 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, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it.skip, + resetFork, + collateralName: 'SFraxCollateral', + chainlinkDefaultAnswer, + itIsPricedByPeg: true, + toleranceDivisor: bn('1e8'), // 1-part in 100 million +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/frax/constants.ts b/test/plugins/individual-collateral/frax/constants.ts new file mode 100644 index 0000000000..a331f9492b --- /dev/null +++ b/test/plugins/individual-collateral/frax/constants.ts @@ -0,0 +1,17 @@ +import { bn, fp } from '../../../../common/numbers' +import { networkConfig } from '../../../../common/configuration' + +// Mainnet Addresses +export const FRAX_USD_PRICE_FEED = networkConfig['31337'].chainlinkFeeds.FRAX as string +export const FRAX = networkConfig['31337'].tokens.FRAX as string +export const SFRAX = networkConfig['31337'].tokens.sFRAX as string +export const SFRAX_HOLDER = '0xC38744840abCe123608B6f79a8Ac7bAE2153194e' + +export const PRICE_TIMEOUT = bn('604800') // 1 week +export const ORACLE_TIMEOUT = bn(3600) // 1 hour in seconds +export const ORACLE_ERROR = fp('0.01') // 1% +export const DEFAULT_THRESHOLD = fp('0.02') +export const DELAY_UNTIL_DEFAULT = bn(86400) +export const MAX_TRADE_VOL = bn(1000) + +export const FORK_BLOCK = 18522901 diff --git a/test/plugins/individual-collateral/frax/helpers.ts b/test/plugins/individual-collateral/frax/helpers.ts new file mode 100644 index 0000000000..34664e3da9 --- /dev/null +++ b/test/plugins/individual-collateral/frax/helpers.ts @@ -0,0 +1,13 @@ +import { IERC20Metadata } from '../../../../typechain' +import { whileImpersonating } from '../../../utils/impersonation' +import { BigNumberish } from 'ethers' +import { FORK_BLOCK, SFRAX_HOLDER } from './constants' +import { getResetFork } from '../helpers' + +export const mintSFrax = async (sFrax: IERC20Metadata, amount: BigNumberish, recipient: string) => { + await whileImpersonating(SFRAX_HOLDER, async (whale) => { + await sFrax.connect(whale).transfer(recipient, amount) + }) +} + +export const resetFork = getResetFork(FORK_BLOCK) From d3456731d3a38d315d054692185f4273598f2beb Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 7 Nov 2023 18:22:08 -0500 Subject: [PATCH 2/4] remove unused interface --- contracts/plugins/assets/frax/IFrax.sol | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 contracts/plugins/assets/frax/IFrax.sol diff --git a/contracts/plugins/assets/frax/IFrax.sol b/contracts/plugins/assets/frax/IFrax.sol deleted file mode 100644 index 616a83af83..0000000000 --- a/contracts/plugins/assets/frax/IFrax.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.19; - -import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; - -// This interface only used in tests -interface IFrax is IERC20Metadata { - function pool_burn_from(address, uint256) external; // only-callable by frax pools -} From 3c6eef15a8b5981c3981c930f617d2cf59a0c8c9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 7 Nov 2023 18:30:26 -0500 Subject: [PATCH 3/4] deployment + verification scripts --- scripts/deploy.ts | 7 +- .../phase2-assets/collaterals/deploy_sfrax.ts | 83 +++++++++++++++++++ .../collateral-plugins/verify_sfrax.ts | 53 ++++++++++++ scripts/verify_etherscan.ts | 5 +- 4 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_sfrax.ts create mode 100644 scripts/verification/collateral-plugins/verify_sfrax.ts diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 37166ba82a..eb9d82f713 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -26,7 +26,7 @@ async function main() { // See `confirm.ts` for part 2 // Phase 1- Implementations - let scripts = [ + const scripts = [ 'phase1-common/0_setup_deployments.ts', 'phase1-common/1_deploy_libraries.ts', 'phase1-common/2_deploy_implementations.ts', @@ -61,7 +61,8 @@ async function main() { 'phase2-assets/collaterals/deploy_dsr_sdai.ts', 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', 'phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts', - 'phase2-assets/collaterals/deploy_aave_v3_usdc.ts' + 'phase2-assets/collaterals/deploy_aave_v3_usdc.ts', + 'phase2-assets/collaterals/deploy_sfrax.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains @@ -73,7 +74,7 @@ async function main() { 'phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts', 'phase2-assets/collaterals/deploy_aave_v3_usdbc.ts', 'phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts', - 'phase2-assets/assets/deploy_stg.ts', + 'phase2-assets/assets/deploy_stg.ts' ) } diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_sfrax.ts b/scripts/deployment/phase2-assets/collaterals/deploy_sfrax.ts new file mode 100644 index 0000000000..1510377f20 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_sfrax.ts @@ -0,0 +1,83 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { priceTimeout, oracleTimeout } from '../../utils' +import { SFraxCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy SFRAX Collateral - sFRAX **************************/ + + const SFraxCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'SFraxCollateral' + ) + + const collateral = await SFraxCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.FRAX, + oracleError: fp('0.01').toString(), // 1% + erc20: networkConfig[chainId].tokens.sFRAX, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.02').toString(), // 2% = 1% oracleError + 1% buffer + delayUntilDefault: bn('86400').toString(), // 24h + }, + '0' // revenueHiding = 0 + ) + await collateral.deployed() + + console.log(`Deployed sFRAX to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.sFRAX = collateral.address + assetCollDeployments.erc20s.sFRAX = networkConfig[chainId].tokens.sFRAX + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_sfrax.ts b/scripts/verification/collateral-plugins/verify_sfrax.ts new file mode 100644 index 0000000000..5e4be4fc45 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_sfrax.ts @@ -0,0 +1,53 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { fp, bn } from '../../../common/numbers' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { priceTimeout, oracleTimeout, verifyContract } from '../../deployment/utils' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify sFRAX **************************/ + await verifyContract( + chainId, + deployments.collateral.sFRAX, + [ + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.FRAX, + oracleError: fp('0.01').toString(), // 1% + erc20: networkConfig[chainId].tokens.sFRAX, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.02').toString(), // 2% + delayUntilDefault: bn('86400').toString(), // 24h + }, + '0', // revenueHiding = 0 + ], + 'contracts/plugins/assets/frax/SFraxCollateral.sol:SFraxCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index f9b9d28265..a0a69c2281 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -36,7 +36,7 @@ async function main() { // even if some portions have already been verified // Phase 1- Common - let scripts = [ + const scripts = [ '0_verify_libraries.ts', '1_verify_implementations.ts', '2_verify_rsrAsset.ts', @@ -61,7 +61,8 @@ async function main() { 'collateral-plugins/verify_cbeth.ts', 'collateral-plugins/verify_sdai.ts', 'collateral-plugins/verify_morpho.ts', - 'collateral-plugins/verify_aave_v3_usdc.ts' + 'collateral-plugins/verify_aave_v3_usdc.ts', + 'collateral-plugins/verify_sfrax.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains From a637a7ae7eb40a660227fc5e1acc0a600a3e1ded Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 7 Nov 2023 19:16:38 -0500 Subject: [PATCH 4/4] update fork block --- .github/workflows/tests.yml | 1 - test/integration/fork-block-numbers.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 62e19d8665..8c1d16e8da 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,6 @@ jobs: - run: yarn devchain & env: MAINNET_RPC_URL: https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 - FORK_BLOCK: 18329921 FORK_NETWORK: mainnet - run: yarn deploy:run --network localhost env: diff --git a/test/integration/fork-block-numbers.ts b/test/integration/fork-block-numbers.ts index cf5b9d0336..c575f48e3d 100644 --- a/test/integration/fork-block-numbers.ts +++ b/test/integration/fork-block-numbers.ts @@ -5,7 +5,7 @@ const forkBlockNumber = { 'mainnet-deployment': 15690042, // Ethereum 'flux-finance': 16836855, // Ethereum 'mainnet-2.0': 17522362, // Ethereum - default: 16934828, // Ethereum + default: 18522901, // Ethereum } export default forkBlockNumber