diff --git a/common/configuration.ts b/common/configuration.ts index febedecbe3..b408263a1a 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -105,6 +105,10 @@ export interface ITokens { steakPYUSD?: string Re7WETH?: string + // Ethena + USDe?: string + sUSDe?: string + // Mountain USDM?: string wUSDM?: string @@ -245,6 +249,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = { bbUSDT: '0x2C25f6C25770fFEC5959D34B94Bf898865e5D6b1', Re7WETH: '0x78Fc2c2eD1A4cDb5402365934aE5648aDAd094d0', sdUSDCUSDCPlus: '0x9bbF31E99F30c38a5003952206C31EEa77540BeF', + USDe: '0x4c9edd5852cd905f086c759e8383e09bff1e68b3', + sUSDe: '0x9D39A5DE30e57443BfF2A8307A4256c8797A3497', }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', @@ -271,6 +277,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH frxETH: '0xc58f3385fbc1c8ad2c0c9a061d7c13b141d7a5df', // frxETH/ETH pyUSD: '0x8f1dF6D7F2db73eECE86a18b4381F4707b918FB1', + USDe: '0xa569d910839Ae8865Da8F8e70FfFb0cBA869F961', }, AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', AAVE_EMISSIONS_MGR: '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5', diff --git a/contracts/plugins/assets/ethena/README.md b/contracts/plugins/assets/ethena/README.md new file mode 100644 index 0000000000..88d4e3f03f --- /dev/null +++ b/contracts/plugins/assets/ethena/README.md @@ -0,0 +1,27 @@ +# Ethena USDe Collateral Plugin + +## Summary + +`USDe` is a synthetic dollar protocol built on Ethereum that derives its value from a delta-neutral basis trade based on funding rates: long ETH-LSD + short ETH. This combined position captures the funding rate on perpetual exchanges, which has been historically positive throughout crypto’s history, which is also used to provision an additional `sUSDe` token. + +This plugin allows `sUSDe` holders to use their tokens as collateral in the Reserve Protocol. + +`sUSDe` is a high-yield (today) ERC4626 vault, most similar to the DAI savings module. The redeemable USDe amount can be obtained by dividing `sUSDe.totalAssets()` by `sUSDe.totalSupply()`. + +`USDe` contract: + +`sUSDe` contract: + +## Implementation + +### Units + +| tok | ref | target | UoA | +| ----- | ---- | ------ | --- | +| sUSDe | USDe | USD | USD | + +### Functions + +#### refPerTok {ref/tok} + +`return shiftl_toFix(erc4626.convertToAssets(oneShare), -refDecimals)` diff --git a/contracts/plugins/assets/ethena/USDeFiatCollateral.sol b/contracts/plugins/assets/ethena/USDeFiatCollateral.sol new file mode 100644 index 0000000000..a419a494c2 --- /dev/null +++ b/contracts/plugins/assets/ethena/USDeFiatCollateral.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { CollateralConfig } from "../AppreciatingFiatCollateral.sol"; +import { ERC4626FiatCollateral } from "../ERC4626FiatCollateral.sol"; + +/** + * @title USDe Fiat Collateral + * @notice Collateral plugin for USDe (Ethena) + * tok = sUSDe (ERC4626 vault) + * ref = USDe + * tar = USD + * UoA = USD + */ + +contract USDeFiatCollateral is ERC4626FiatCollateral { + /// config.erc20 must be sUSDe + /// @param config.chainlinkFeed Feed units: {UoA/ref} + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + constructor(CollateralConfig memory config, uint192 revenueHiding) + ERC4626FiatCollateral(config, revenueHiding) + { + require(config.defaultThreshold != 0, "defaultThreshold zero"); + } +} diff --git a/scripts/addresses/1-tmp-assets-collateral.json b/scripts/addresses/1-tmp-assets-collateral.json index 64e267f254..96b670d82e 100644 --- a/scripts/addresses/1-tmp-assets-collateral.json +++ b/scripts/addresses/1-tmp-assets-collateral.json @@ -58,7 +58,8 @@ "Re7WETH": "0xa0a6C06e45437d4Ae1D778AaeB4605AC2B62A870", "cvxCrvUSDUSDC": "0x9Fc0F31e2D26C437461a9eEBfe858d17e2611Ea5", "cvxCrvUSDUSDT": "0x69c6597690B8Df61D15F201519C03725bdec40c1", - "sfrxETH": "0x4c891fCa6319d492866672E3D2AfdAAA5bDcfF67" + "sfrxETH": "0x4c891fCa6319d492866672E3D2AfdAAA5bDcfF67", + "sUSDe": "0x35081Ca24319835e5f759163F7e75eaB753e0b7E" }, "erc20s": { "stkAAVE": "0x4da27a545c0c5B758a6BA100e3a049001de870f5", @@ -117,6 +118,7 @@ "cvxCrvUSDUSDT": "0x5d1B749bA7f689ef9f260EDC54326C48919cA88b", "sfrxETH": "0xac3E018457B222d93114458476f3E3416Abbe38F", "CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", - "CVX": "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B" + "CVX": "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B", + "sUSDe": "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497" } } \ No newline at end of file diff --git a/scripts/addresses/mainnet-3.4.0/1-tmp-assets-collateral.json b/scripts/addresses/mainnet-3.4.0/1-tmp-assets-collateral.json index 61fc0521a8..9ab05c4160 100644 --- a/scripts/addresses/mainnet-3.4.0/1-tmp-assets-collateral.json +++ b/scripts/addresses/mainnet-3.4.0/1-tmp-assets-collateral.json @@ -58,7 +58,8 @@ "Re7WETH": "0xa0a6C06e45437d4Ae1D778AaeB4605AC2B62A870", "cvxCrvUSDUSDC": "0x9Fc0F31e2D26C437461a9eEBfe858d17e2611Ea5", "cvxCrvUSDUSDT": "0x69c6597690B8Df61D15F201519C03725bdec40c1", - "sfrxETH": "0x4c891fCa6319d492866672E3D2AfdAAA5bDcfF67" + "sfrxETH": "0x4c891fCa6319d492866672E3D2AfdAAA5bDcfF67", + "sUSDe": "0x35081Ca24319835e5f759163F7e75eaB753e0b7E" }, "erc20s": { "stkAAVE": "0x4da27a545c0c5B758a6BA100e3a049001de870f5", @@ -117,6 +118,7 @@ "cvxCrvUSDUSDT": "0x5d1B749bA7f689ef9f260EDC54326C48919cA88b", "sfrxETH": "0xac3E018457B222d93114458476f3E3416Abbe38F", "CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", - "CVX": "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B" + "CVX": "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B", + "sUSDe": "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497" } } diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 65f82bd7c0..5df8f30748 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -83,6 +83,7 @@ async function main() { 'phase2-assets/collaterals/deploy_steakpyusd.ts', 'phase2-assets/collaterals/deploy_bbusdt.ts', 'phase2-assets/collaterals/deploy_re7weth.ts', + 'phase2-assets/collaterals/deploy_USDe.ts', 'phase2-assets/assets/deploy_crv.ts', 'phase2-assets/assets/deploy_cvx.ts' ) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_USDe.ts b/scripts/deployment/phase2-assets/collaterals/deploy_USDe.ts new file mode 100644 index 0000000000..1c84bf726a --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_USDe.ts @@ -0,0 +1,92 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { USDeFiatCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' +import { + DELAY_UNTIL_DEFAULT, + PRICE_TIMEOUT, + ORACLE_TIMEOUT, + ORACLE_ERROR, +} from '../../../../test/plugins/individual-collateral/ethena/constants' + +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 USDe Collateral - sUSDe **************************/ + let collateral: USDeFiatCollateral + + const USDeFiatCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'USDeFiatCollateral' + ) + + collateral = await USDeFiatCollateralFactory.connect(deployer).deploy( + { + priceTimeout: PRICE_TIMEOUT.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDe, + oracleError: ORACLE_ERROR.toString(), + erc20: networkConfig[chainId].tokens.sUSDe, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: ORACLE_TIMEOUT.toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(ORACLE_ERROR).toString(), // ~1.5% + delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), // 72h + }, + fp('1e-6').toString() + ) + + await collateral.deployed() + + console.log( + `Deployed USDe (sUSDe) Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.sUSDe = collateral.address + assetCollDeployments.erc20s.sUSDe = networkConfig[chainId].tokens.sUSDe + 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_USDe.ts b/scripts/verification/collateral-plugins/verify_USDe.ts new file mode 100644 index 0000000000..2692212bb9 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_USDe.ts @@ -0,0 +1,59 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { fp } from '../../../common/numbers' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { + PRICE_TIMEOUT, + ORACLE_ERROR, + ORACLE_TIMEOUT, + DELAY_UNTIL_DEFAULT, +} from '../../../test/plugins/individual-collateral/ethena/constants' + +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 sUSDe COllateral **************************/ + await verifyContract( + chainId, + deployments.collateral.sUSDe, + [ + { + priceTimeout: PRICE_TIMEOUT.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDe, + oracleError: ORACLE_ERROR.toString(), + erc20: networkConfig[chainId].tokens.sUSDe, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: ORACLE_TIMEOUT.toString(), + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(ORACLE_ERROR).toString(), + delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), + }, + fp('1e-6').toString(), + ], + 'contracts/plugins/assets/ethena/USDeFiatCollateral.sol:USDeFiatCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index 811916c60c..c24bf7b29b 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -76,7 +76,8 @@ async function main() { 'collateral-plugins/verify_sfrax.ts', 'collateral-plugins/verify_sfrax_eth.ts', 'collateral-plugins/verify_steakusdc.ts', - 'collateral-plugins/verify_re7weth.ts' + 'collateral-plugins/verify_re7weth.ts', + 'collateral-plugins/verify_USDe.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains diff --git a/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts b/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts new file mode 100644 index 0000000000..a1c1e10fc6 --- /dev/null +++ b/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts @@ -0,0 +1,221 @@ +import collateralTests from '../collateralTests' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' +import { resetFork, mintSUSDe, mintUSDe } 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 { pushOracleForward } from '../../../utils/oracles' +import { bn, fp } from '../../../../common/numbers' +import { ONE_ADDRESS, ZERO_ADDRESS } from '../../../../common/constants' +import { whileImpersonating } from '../../../utils/impersonation' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + USDe, + SUSDe, + USDe_USD_PRICE_FEED, + ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, + MAX_TRADE_VOL, + DELAY_UNTIL_DEFAULT, + DEFAULT_THRESHOLD, +} from './constants' + +/* + Define deployment functions +*/ + +export const defaultUSDeCollateralOpts: CollateralOpts = { + erc20: SUSDe, + targetName: ethers.utils.formatBytes32String('USD'), + rewardERC20: ZERO_ADDRESS, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: USDe_USD_PRICE_FEED, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, // 72 hs + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: fp('0'), +} + +export const deployCollateral = async (opts: CollateralOpts = {}): Promise => { + opts = { ...defaultUSDeCollateralOpts, ...opts } + + const USDeFiatCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'USDeFiatCollateral' + ) + const collateral = await USDeFiatCollateralFactory.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() + + // Push forward feed + await pushOracleForward(opts.chainlinkFeed!) + + // 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 = { ...defaultUSDeCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) + ) + collateralOpts.chainlinkFeed = chainlinkFeed.address + + const sUSDe = (await ethers.getContractAt('IERC20Metadata', SUSDe)) 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, + sUSDe, + rewardToken, + } + } + + return makeCollateralFixtureContext +} + +const mintCollateralTo: MintCollateralFunc = async ( + ctx: CollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintSUSDe(ctx.tok, user, 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 usde = await ethers.getContractAt("IERC20Metadata", USDe) + const currentBal = await usde.balanceOf(ctx.tok.address) + const removeBal = currentBal.mul(pctDecrease).div(100) + await whileImpersonating(ctx.tok.address, async (susdeSigner) => { + await usde.connect(susdeSigner).transfer(ONE_ADDRESS, removeBal) + }) +} + +// prettier-ignore +const increaseRefPerTok = async ( + ctx: CollateralFixtureContext, + pctIncrease: BigNumberish + +) => { + + const usde = await ethers.getContractAt("IERC20Metadata", USDe) + + const currentBal = await usde.balanceOf(ctx.tok.address) + const addBal = currentBal.mul(pctIncrease).div(100) + await mintUSDe(usde, ctx.alice!, addBal, ctx.tok.address) +} + +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, + itChecksTargetPerRefDefaultUp: it, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, + itHasRevenueHiding: it, + collateralName: 'USDe Fiat Collateral', + chainlinkDefaultAnswer, + itIsPricedByPeg: true, + resetFork, +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/ethena/constants.ts b/test/plugins/individual-collateral/ethena/constants.ts new file mode 100644 index 0000000000..20ac7da3e1 --- /dev/null +++ b/test/plugins/individual-collateral/ethena/constants.ts @@ -0,0 +1,18 @@ +import { bn, fp } from '../../../../common/numbers' +import { networkConfig } from '../../../../common/configuration' + +// Mainnet Addresses +export const USDe = networkConfig['31337'].tokens.USDe as string +export const SUSDe = networkConfig['31337'].tokens.sUSDe as string +export const USDe_USD_PRICE_FEED = networkConfig['31337'].chainlinkFeeds.USDe as string +export const USDe_HOLDER = '0x42862F48eAdE25661558AFE0A630b132038553D0' +export const sUSDe_HOLDER = '0x4139cDC6345aFFbaC0692b43bed4D059Df3e6d65' + +export const PRICE_TIMEOUT = bn('604800') // 1 week +export const ORACLE_TIMEOUT = bn(86400) // 24h +export const ORACLE_ERROR = fp('0.005') // 0.5% +export const DEFAULT_THRESHOLD = fp('0.05') // 5% +export const DELAY_UNTIL_DEFAULT = bn(259200) // 72h +export const MAX_TRADE_VOL = bn(1000) + +export const FORK_BLOCK = 19933080 diff --git a/test/plugins/individual-collateral/ethena/helpers.ts b/test/plugins/individual-collateral/ethena/helpers.ts new file mode 100644 index 0000000000..b0e518bba3 --- /dev/null +++ b/test/plugins/individual-collateral/ethena/helpers.ts @@ -0,0 +1,30 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { IERC20Metadata } from '../../../../typechain' +import { whileImpersonating } from '../../../utils/impersonation' +import { BigNumberish } from 'ethers' +import { FORK_BLOCK, sUSDe_HOLDER, USDe_HOLDER } from './constants' +import { getResetFork } from '../helpers' + +export const mintSUSDe = async ( + sUSDe: IERC20Metadata, + account: SignerWithAddress, + amount: BigNumberish, + recipient: string +) => { + await whileImpersonating(sUSDe_HOLDER, async (whale) => { + await sUSDe.connect(whale).transfer(recipient, amount) + }) +} + +export const mintUSDe = async ( + USDe: IERC20Metadata, + account: SignerWithAddress, + amount: BigNumberish, + recipient: string +) => { + await whileImpersonating(USDe_HOLDER, async (whale) => { + await USDe.connect(whale).transfer(recipient, amount) + }) +} + +export const resetFork = getResetFork(FORK_BLOCK)