From 6292c0bbf225029d768a52991e6a179f87ad946b Mon Sep 17 00:00:00 2001 From: Ezequiel Gorbatik Date: Tue, 8 Oct 2024 12:06:24 -0300 Subject: [PATCH 1/4] nars - snars - collateral plugin --- common/configuration.ts | 7 + contracts/plugins/assets/num/README.md | 32 +++ .../assets/num/SnARSFiatCollateral.sol | 21 ++ .../phase2-assets/collaterals/deploy_nARS.ts | 91 ++++++++ .../phase2-assets/collaterals/deploy_snARS.ts | 83 +++++++ .../collateral-plugins/verify_snARS.ts | 53 +++++ .../individual-collateral/collateralTests.ts | 27 +++ .../num/SNARSFiatCollateral.test.ts | 205 ++++++++++++++++++ .../individual-collateral/num/constants.ts | 15 ++ .../individual-collateral/num/helpers.ts | 55 +++++ 10 files changed, 589 insertions(+) create mode 100644 contracts/plugins/assets/num/README.md create mode 100644 contracts/plugins/assets/num/SnARSFiatCollateral.sol create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_snARS.ts create mode 100644 scripts/verification/collateral-plugins/verify_snARS.ts create mode 100644 test/plugins/individual-collateral/num/SNARSFiatCollateral.test.ts create mode 100644 test/plugins/individual-collateral/num/constants.ts create mode 100644 test/plugins/individual-collateral/num/helpers.ts diff --git a/common/configuration.ts b/common/configuration.ts index d5164fa638..5031350d33 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -115,6 +115,10 @@ export interface ITokens { // Mountain USDM?: string wUSDM?: string + + // NUM + snARS?: string + nARS?: string } export type ITokensKeys = Array @@ -513,6 +517,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDbC: '0x4c80e24119cfb836cdf0a6b53dc23f04f7e652ca', wstETH: '0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452', STG: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df', + snARS: '0xC1F4C75e8925A67BE4F35D6b1c044B5ea8849a58', + nARS: '0x5e40f26E89213660514c51Fb61b2d357DBf63C85', }, chainlinkFeeds: { DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr @@ -529,6 +535,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHETH: '0xf586d0728a47229e747d824a939000Cf21dEF5A0', // 0.5%, 24h ETHUSD: '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // 0.15%, 20min wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h + nARS: '0xC3a426Ef79fd60A4cA785FC04a2C3cB09d2FEeae', }, GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', diff --git a/contracts/plugins/assets/num/README.md b/contracts/plugins/assets/num/README.md new file mode 100644 index 0000000000..1078c9133d --- /dev/null +++ b/contracts/plugins/assets/num/README.md @@ -0,0 +1,32 @@ +# Collateral Plugins for nARS and snARS + +## Summary + +This plugin allows `nARS` and `snARS` holders to use their tokens as collateral in the Reserve Protocol. + +As described in the [Num Site](https://new.num.finance/) nTokens are ERC20 tokens, tracking the value of an underlying financial asset. +Each nToken issued by Num Finance is fully collateralized by an asset in the traditional market. This means that for every nToken in circulation, there is a real-world asset backing it, ensuring the token's value and stability. + +In this particular case we're incorporating through this plugin 2 nTokens. +- `nARS` is a stablecoin pegged to the `Argentine Peso (ARS)`. +- `snARS` is the staked version of `nARS`. When users stake their `nARS`, they receive `snARS` in return, which grants them certain benefits in the form of yield or Numun Rewards. + +Staking of `nARS` is possible at: https://numun.fi/ +Official num website: https://num.finance/ +nStables documentation: https://docs.nstables.fi/ + +## Implementation + +### Units + +| tok | ref | target | UoA | +| ---- | ---- | ------ | --- | +| nARS | ARS | ARS | USD | + +| tok | ref | target | UoA | +| ---- | ---- | ------ | --- | +| sNARS | ARS | ARS | USD | + +### claimRewards() + +There are no rewards to claim diff --git a/contracts/plugins/assets/num/SnARSFiatCollateral.sol b/contracts/plugins/assets/num/SnARSFiatCollateral.sol new file mode 100644 index 0000000000..33fcc1a9fd --- /dev/null +++ b/contracts/plugins/assets/num/SnARSFiatCollateral.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { CollateralConfig } from "../AppreciatingFiatCollateral.sol"; +import { ERC4626FiatCollateral } from "../ERC4626FiatCollateral.sol"; + +/** + * @title SnARSFiatCollateral + * @notice Collateral plugin for a Num vault with fiat collateral + * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == {UoA} + */ +contract SnARSFiatCollateral is ERC4626FiatCollateral { + /// config.erc20 must be a Num ERC4626 vault + /// @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/deployment/phase2-assets/collaterals/deploy_nARS.ts b/scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts new file mode 100644 index 0000000000..820262cce4 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts @@ -0,0 +1,91 @@ +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 { NARSCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' +import { + DELAY_UNTIL_DEFAULT, + PRICE_TIMEOUT, + ORACLE_TIMEOUT, + ORACLE_ERROR, +} from '../../../../test/plugins/individual-collateral/num/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 NARS Collateral - ARS **************************/ + + const NARSFiatCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'NARSCollateral' + ) + + const collateral = await NARSFiatCollateralFactory.connect(deployer).deploy( + { + priceTimeout: PRICE_TIMEOUT.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.nARS, + oracleError: ORACLE_ERROR.toString(), + erc20: networkConfig[chainId].tokens.nARS, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: ORACLE_TIMEOUT.toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('ARS'), + defaultThreshold: fp('0.01').add(ORACLE_ERROR).toString(), // ~1% + delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), // 72h + }, + fp('1e-6').toString() + ) + + await collateral.deployed() + + console.log( + `Deployed NARS (ARS) Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.nARS = collateral.address + assetCollDeployments.erc20s.nARS = networkConfig[chainId].tokens.nARS + 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/deployment/phase2-assets/collaterals/deploy_snARS.ts b/scripts/deployment/phase2-assets/collaterals/deploy_snARS.ts new file mode 100644 index 0000000000..4a502602c2 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_snARS.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 } 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 snARS Collateral - snARS **************************/ + + const nuARSCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'FiatCollateral' + ) + + const collateral = await nuARSCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.snARS, + oracleError: fp('0.01').toString(), // 1% + erc20: networkConfig[chainId].tokens.snARS, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '3600', // 1 hr + targetName: hre.ethers.utils.formatBytes32String('ARS'), + defaultThreshold: fp('0.02').toString(), // 2% = 1% oracleError + 1% buffer + delayUntilDefault: bn('86400').toString(), // 24h + }, + '0' // revenueHiding = 0 + ) + await collateral.deployed() + + console.log(`Deployed snARS to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.snARS = collateral.address + assetCollDeployments.erc20s.snARS = networkConfig[chainId].tokens.snARS + 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_snARS.ts b/scripts/verification/collateral-plugins/verify_snARS.ts new file mode 100644 index 0000000000..1f587bbe66 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_snARS.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, 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 snARS **************************/ + await verifyContract( + chainId, + deployments.collateral.snARS, + [ + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.snARS, + oracleError: fp('0.01').toString(), // 1% + erc20: networkConfig[chainId].tokens.snARS, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '3600', // 1 hr + targetName: hre.ethers.utils.formatBytes32String('ARS'), + defaultThreshold: fp('0.02').toString(), // 2% + delayUntilDefault: bn('86400').toString(), // 24h + }, + '0', // revenueHiding = 0 + ], + 'contracts/plugins/assets/num/SnARSFiatCollateral.sol:SnARSFiatCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 5a1cc4692a..d07ce7a2b8 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -58,6 +58,7 @@ import { } from '../../../typechain' import snapshotGasCost from '../../utils/snapshotGasCost' import { IMPLEMENTATION, Implementation, ORACLE_ERROR, PRICE_TIMEOUT } from '../../fixtures' +import { NUM_HOLDER } from './num/constants' const getDescribeFork = (targetNetwork = 'mainnet') => { return useEnv('FORK') && useEnv('FORK_NETWORK') === targetNetwork ? describe : describe.skip @@ -1003,6 +1004,32 @@ export default function fn( targetUnitOracle.address, ORACLE_TIMEOUT ) + } else if (target == ethers.utils.formatBytes32String('ARS')) { + // ARS + const erc20 = await ethers.getContractAt( + 'IERC20Metadata', + onBase ? networkConfig[chainId].tokens.snARS! : networkConfig[chainId].tokens.snARS! + ) + const whale = NUM_HOLDER + await whileImpersonating(whale, async (signer) => { + await erc20 + .connect(signer) + .transfer(addr1.address, await erc20.balanceOf(signer.address)) + }) + const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'FiatCollateral' + ) + return await FiatCollateralFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: chainlinkFeed.address, + oracleError: ORACLE_ERROR, + oracleTimeout: ORACLE_TIMEOUT, + maxTradeVolume: MAX_UINT192, + erc20: erc20.address, + targetName: ethers.utils.formatBytes32String('ARS'), + defaultThreshold: fp('0.01'), // 1% + delayUntilDefault: bn('86400'), // 24h, + }) } else { throw new Error(`Unknown target: ${target}`) } diff --git a/test/plugins/individual-collateral/num/SNARSFiatCollateral.test.ts b/test/plugins/individual-collateral/num/SNARSFiatCollateral.test.ts new file mode 100644 index 0000000000..b4fd4ef548 --- /dev/null +++ b/test/plugins/individual-collateral/num/SNARSFiatCollateral.test.ts @@ -0,0 +1,205 @@ +import { networkConfig } from '#/common/configuration' +import { bn, fp } from '#/common/numbers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { MockV3Aggregator } from '@typechain/MockV3Aggregator' +import { TestICollateral } from '@typechain/TestICollateral' +import { MockV3Aggregator__factory } from '@typechain/index' +import { expect } from 'chai' +import { BigNumber, BigNumberish, ContractFactory } from 'ethers' +import { ethers } from 'hardhat' +import collateralTests from '../collateralTests' +import { getResetFork } from '../helpers' +import { CollateralOpts, CollateralFixtureContext } from '../pluginTestTypes' +import { pushOracleForward } from '../../../utils/oracles' +import { MAX_UINT192 } from '#/common/constants' +import { + DELAY_UNTIL_DEFAULT, + FORK_BLOCK, + ORACLE_TIMEOUT, + ORACLE_ERROR, + PRICE_TIMEOUT, +} from './constants' +import { mintCollateralTo } from './helpers' + +interface MAFiatCollateralOpts extends CollateralOpts { + defaultPrice?: BigNumberish + defaultRefPerTok?: BigNumberish +} + +const makeFiatCollateralTestSuite = ( + collateralName: string, + defaultCollateralOpts: MAFiatCollateralOpts +) => { + const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise => { + opts = { ...defaultCollateralOpts, ...opts } + const NumCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'SnARSFiatCollateral' + ) + + const collateral = await NumCollateralFactory.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 chainlink feed + await pushOracleForward(opts.chainlinkFeed!) + + await expect(collateral.refresh()) + + return collateral + } + + type Fixture = () => Promise + + const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + inOpts: MAFiatCollateralOpts = {} + ): Fixture => { + const makeCollateralFixtureContext = async () => { + const opts = { ...defaultCollateralOpts, ...inOpts } + + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) + ) + opts.chainlinkFeed = chainlinkFeed.address + + // Hack: use wrapped vault by default unless the maxTradeVolume is infinite, in which + // case the mock would break things. Care! Fragile! + if (!opts.maxTradeVolume || !MAX_UINT192.eq(opts.maxTradeVolume)) { + const mockNumFactory = await ethers.getContractFactory('MockNum4626') + const mockERC4626 = await mockNumFactory.deploy(opts.erc20!) + opts.erc20 = mockERC4626.address + } + const collateral = await deployCollateral({ ...opts }) + const tok = await ethers.getContractAt('IERC20Metadata', await collateral.erc20()) + + return { + alice, + collateral, + chainlinkFeed, + tok, + } as CollateralFixtureContext + } + + return makeCollateralFixtureContext + } + + 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) + } + + const reduceRefPerTok = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const mockERC4626 = await ethers.getContractAt('MockNum4626', ctx.tok.address) + await mockERC4626.applyMultiple(bn('100').sub(pctDecrease).mul(fp('1')).div(100)) + } + + const increaseRefPerTok = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const mockERC4626 = await ethers.getContractAt('MockNum4626', ctx.tok.address) + await mockERC4626.applyMultiple(bn('100').add(pctIncrease).mul(fp('1')).div(100)) + } + + 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 () => {} + + 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, + resetFork: getResetFork(FORK_BLOCK), + collateralName, + chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, + itIsPricedByPeg: true, + toleranceDivisor: bn('1e7'), // 1 part in 1 billion + targetNetwork: 'base', + } + + collateralTests(opts) +} + +const makeOpts = ( + vault: string, + chainlinkFeed: string, + oracleTimeout: BigNumber, + oracleError: BigNumber +): MAFiatCollateralOpts => { + return { + targetName: ethers.utils.formatBytes32String('ARS'), + priceTimeout: PRICE_TIMEOUT, + oracleTimeout: oracleTimeout, + oracleError: oracleError, + defaultThreshold: oracleError.add(fp('0.01')), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + maxTradeVolume: fp('1e6'), + revenueHiding: fp('0'), + defaultPrice: bn('1e8'), + defaultRefPerTok: fp('1'), + erc20: vault, + chainlinkFeed, + } +} + +/* + Run the test suite +*/ +const { tokens, chainlinkFeeds } = networkConfig[8453] +makeFiatCollateralTestSuite( + 'FiatCollateral - snARS', + makeOpts(tokens.snARS!, chainlinkFeeds.snARS!, ORACLE_TIMEOUT, ORACLE_ERROR) +) diff --git a/test/plugins/individual-collateral/num/constants.ts b/test/plugins/individual-collateral/num/constants.ts new file mode 100644 index 0000000000..5c35cc99b6 --- /dev/null +++ b/test/plugins/individual-collateral/num/constants.ts @@ -0,0 +1,15 @@ +import { bn, fp } from '../../../../common/numbers' +import { networkConfig } from '../../../../common/configuration' + +// oracle settings +export const ORACLE_FEED = networkConfig['8453'].chainlinkFeeds.nARS! +export const ORACLE_TIMEOUT = bn('86400') +export const ORACLE_ERROR = fp('0.0025') + +// general +export const PRICE_TIMEOUT = bn(604800) // 1 week +export const DELAY_UNTIL_DEFAULT = bn(86400) + +// tests +export const FORK_BLOCK = 20493295 +export const NUM_HOLDER = '0xF3F1a405bc844FB3322587a305B1a8b2EC916536' diff --git a/test/plugins/individual-collateral/num/helpers.ts b/test/plugins/individual-collateral/num/helpers.ts new file mode 100644 index 0000000000..6e45e56a8a --- /dev/null +++ b/test/plugins/individual-collateral/num/helpers.ts @@ -0,0 +1,55 @@ +import { whileImpersonating } from '#/utils/impersonation' +import { BigNumberish } from 'ethers' +import { FORK_BLOCK, NUM_HOLDER } from './constants' +import { getResetFork } from '../helpers' +import { CollateralFixtureContext, MintCollateralFunc } from '../pluginTestTypes' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import hre, { ethers } from 'hardhat' + +/** + * Mint collateral to a recipient using a whale. + * @param ctx The CollateralFixtureContext object. + * @param amount The amount of collateral to mint. + * @param _ The signer with address (not used in this function). + * @param recipient The address of the recipient of the minted collateral. + */ +export const mintCollateralTo: MintCollateralFunc = async ( + ctx: CollateralFixtureContext, + amount: BigNumberish, + _: SignerWithAddress, + recipient: string +) => { + const tok = await ethers.getContractAt('MockNum4626', ctx.tok.address) + + // It can be a MockMetaMorpho4626 or the real ERC4626 + try { + // treat it as a wrapper to begin + const actual = await tok.actual() + const underlying = await ethers.getContractAt('IERC20Metadata', actual) + + // Transfer the underlying (real) ERC4626; wrapper is pass-through + await whileImpersonating(hre, NUM_HOLDER, async (whaleSigner) => { + await underlying.connect(whaleSigner).transfer(recipient, amount) + }) + } catch (e) { + // if we error out, then it's not the wrapper we're dealing with + await whileImpersonating(hre, NUM_HOLDER, async (whaleSigner) => { + await ctx.tok.connect(whaleSigner).transfer(recipient, amount) + }) + } +} + +export const mintNARSTo: MintCollateralFunc = async ( + ctx: CollateralFixtureContext, + amount: BigNumberish, + _: SignerWithAddress, + recipient: string +) => { + // treat it as a wrapper to begin + const underlying = await ethers.getContractAt('IERC20Metadata', ctx.tok.address) + await whileImpersonating(hre, NUM_HOLDER, async (whaleSigner) => { + await underlying.connect(whaleSigner).transfer(recipient, amount) + }) +} + +export const resetFork = getResetFork(FORK_BLOCK) From 34bec49eb159d8a386a45cea25007616eb089683 Mon Sep 17 00:00:00 2001 From: Ezequiel Gorbatik Date: Tue, 15 Oct 2024 08:04:37 -0300 Subject: [PATCH 2/4] fix: config, SnARSFiatCollateral, Deploy SNARS, Tests --- common/configuration.ts | 2 +- .../assets/num/SnARSFiatCollateral.sol | 2 +- .../phase2-assets/collaterals/deploy_nARS.ts | 91 ------------------- .../phase2-assets/collaterals/deploy_snARS.ts | 10 +- .../individual-collateral/collateralTests.ts | 26 ------ .../num/SNARSFiatCollateral.test.ts | 2 +- .../individual-collateral/num/constants.ts | 4 +- 7 files changed, 10 insertions(+), 127 deletions(-) delete mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts diff --git a/common/configuration.ts b/common/configuration.ts index 5031350d33..3bcca6e622 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -535,7 +535,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHETH: '0xf586d0728a47229e747d824a939000Cf21dEF5A0', // 0.5%, 24h ETHUSD: '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // 0.15%, 20min wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h - nARS: '0xC3a426Ef79fd60A4cA785FC04a2C3cB09d2FEeae', + nARS: '0xC3a426Ef79fd60A4cA785FC04a2C3cB09d2FEeae', // 0.5%, 15min }, GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', diff --git a/contracts/plugins/assets/num/SnARSFiatCollateral.sol b/contracts/plugins/assets/num/SnARSFiatCollateral.sol index 33fcc1a9fd..f5f6454a29 100644 --- a/contracts/plugins/assets/num/SnARSFiatCollateral.sol +++ b/contracts/plugins/assets/num/SnARSFiatCollateral.sol @@ -7,7 +7,7 @@ import { ERC4626FiatCollateral } from "../ERC4626FiatCollateral.sol"; /** * @title SnARSFiatCollateral * @notice Collateral plugin for a Num vault with fiat collateral - * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == {UoA} + * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == ARS, {UoA} == USD */ contract SnARSFiatCollateral is ERC4626FiatCollateral { /// config.erc20 must be a Num ERC4626 vault diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts b/scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts deleted file mode 100644 index 820262cce4..0000000000 --- a/scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts +++ /dev/null @@ -1,91 +0,0 @@ -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 { NARSCollateral } from '../../../../typechain' -import { ContractFactory } from 'ethers' -import { - DELAY_UNTIL_DEFAULT, - PRICE_TIMEOUT, - ORACLE_TIMEOUT, - ORACLE_ERROR, -} from '../../../../test/plugins/individual-collateral/num/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 NARS Collateral - ARS **************************/ - - const NARSFiatCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( - 'NARSCollateral' - ) - - const collateral = await NARSFiatCollateralFactory.connect(deployer).deploy( - { - priceTimeout: PRICE_TIMEOUT.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.nARS, - oracleError: ORACLE_ERROR.toString(), - erc20: networkConfig[chainId].tokens.nARS, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: ORACLE_TIMEOUT.toString(), // 24 hr - targetName: hre.ethers.utils.formatBytes32String('ARS'), - defaultThreshold: fp('0.01').add(ORACLE_ERROR).toString(), // ~1% - delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), // 72h - }, - fp('1e-6').toString() - ) - - await collateral.deployed() - - console.log( - `Deployed NARS (ARS) Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` - ) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.nARS = collateral.address - assetCollDeployments.erc20s.nARS = networkConfig[chainId].tokens.nARS - 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/deployment/phase2-assets/collaterals/deploy_snARS.ts b/scripts/deployment/phase2-assets/collaterals/deploy_snARS.ts index 4a502602c2..9c778de818 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_snARS.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_snARS.ts @@ -13,7 +13,7 @@ import { fileExists, } from '../../common' import { priceTimeout } from '../../utils' -import { SFraxCollateral } from '../../../../typechain' +import { SnARSFiatCollateral } from '../../../../typechain' import { ContractFactory } from 'ethers' async function main() { @@ -46,16 +46,16 @@ async function main() { 'FiatCollateral' ) - const collateral = await nuARSCollateralFactory.connect(deployer).deploy( + const collateral = await nuARSCollateralFactory.connect(deployer).deploy( { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.snARS, - oracleError: fp('0.01').toString(), // 1% + oracleError: fp('0.005').toString(), // 0.5% erc20: networkConfig[chainId].tokens.snARS, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: '900', // 15min targetName: hre.ethers.utils.formatBytes32String('ARS'), - defaultThreshold: fp('0.02').toString(), // 2% = 1% oracleError + 1% buffer + defaultThreshold: fp('0.015').toString(), // 2% = 0.5% oracleError + 1% buffer delayUntilDefault: bn('86400').toString(), // 24h }, '0' // revenueHiding = 0 diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index d07ce7a2b8..0920c3a56a 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -1004,32 +1004,6 @@ export default function fn( targetUnitOracle.address, ORACLE_TIMEOUT ) - } else if (target == ethers.utils.formatBytes32String('ARS')) { - // ARS - const erc20 = await ethers.getContractAt( - 'IERC20Metadata', - onBase ? networkConfig[chainId].tokens.snARS! : networkConfig[chainId].tokens.snARS! - ) - const whale = NUM_HOLDER - await whileImpersonating(whale, async (signer) => { - await erc20 - .connect(signer) - .transfer(addr1.address, await erc20.balanceOf(signer.address)) - }) - const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory( - 'FiatCollateral' - ) - return await FiatCollateralFactory.deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - oracleTimeout: ORACLE_TIMEOUT, - maxTradeVolume: MAX_UINT192, - erc20: erc20.address, - targetName: ethers.utils.formatBytes32String('ARS'), - defaultThreshold: fp('0.01'), // 1% - delayUntilDefault: bn('86400'), // 24h, - }) } else { throw new Error(`Unknown target: ${target}`) } diff --git a/test/plugins/individual-collateral/num/SNARSFiatCollateral.test.ts b/test/plugins/individual-collateral/num/SNARSFiatCollateral.test.ts index b4fd4ef548..fd2d68ad3c 100644 --- a/test/plugins/individual-collateral/num/SNARSFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/num/SNARSFiatCollateral.test.ts @@ -166,7 +166,7 @@ const makeFiatCollateralTestSuite = ( collateralName, chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, itIsPricedByPeg: true, - toleranceDivisor: bn('1e7'), // 1 part in 1 billion + toleranceDivisor: bn('1e7'), // 1 part in 10 million targetNetwork: 'base', } diff --git a/test/plugins/individual-collateral/num/constants.ts b/test/plugins/individual-collateral/num/constants.ts index 5c35cc99b6..2f58aab1a4 100644 --- a/test/plugins/individual-collateral/num/constants.ts +++ b/test/plugins/individual-collateral/num/constants.ts @@ -3,8 +3,8 @@ import { networkConfig } from '../../../../common/configuration' // oracle settings export const ORACLE_FEED = networkConfig['8453'].chainlinkFeeds.nARS! -export const ORACLE_TIMEOUT = bn('86400') -export const ORACLE_ERROR = fp('0.0025') +export const ORACLE_TIMEOUT = bn('900') +export const ORACLE_ERROR = fp('0.005') // general export const PRICE_TIMEOUT = bn(604800) // 1 week From 8e5470b00d40119aad28c81a3b79eb25939b74fa Mon Sep 17 00:00:00 2001 From: Ezequiel Gorbatik Date: Tue, 15 Oct 2024 12:46:40 -0300 Subject: [PATCH 3/4] feature: Deploy FIAT Collateral --- .../phase2-assets/2_deploy_collateral.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/scripts/deployment/phase2-assets/2_deploy_collateral.ts b/scripts/deployment/phase2-assets/2_deploy_collateral.ts index a886658cb7..2b50fe94e1 100644 --- a/scripts/deployment/phase2-assets/2_deploy_collateral.ts +++ b/scripts/deployment/phase2-assets/2_deploy_collateral.ts @@ -204,6 +204,30 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) } + /******** Deploy Fiat Collateral - nARS **************************/ + if (networkConfig[chainId].tokens.nARS && networkConfig[chainId].chainlinkFeeds.nARS) { + const { collateral: narsCollateral } = await hre.run('deploy-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.nARS, + oracleError: fp('0.005').toString(), // 0.5% + tokenAddress: networkConfig[chainId].tokens.nARS, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '900', + targetName: hre.ethers.utils.formatBytes32String('ARS'), + defaultThreshold: fp('0.01').toString(), // 1.5% + delayUntilDefault: bn('86400').toString(), // 24h + }) + collateral = await ethers.getContractAt('ICollateral', narsCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.nARS = narsCollateral + assetCollDeployments.erc20s.nARS = networkConfig[chainId].tokens.nARS + deployedCollateral.push(narsCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } + /*** AAVE V2 not available in Base or Arbitrum L2s */ if (!baseL2Chains.includes(hre.network.name) && !arbitrumL2Chains.includes(hre.network.name)) { /******** Deploy AToken Fiat Collateral - aDAI **************************/ From 2259af3c43a903c77f1130ddec121d638422ac2d Mon Sep 17 00:00:00 2001 From: Ezequiel Gorbatik Date: Wed, 30 Oct 2024 12:02:56 -0300 Subject: [PATCH 4/4] fix: Readme, SNARSFiatCollateral, Deploy NARS, SNARS, collateral tests --- contracts/plugins/assets/num/README.md | 15 ++-- .../assets/num/SnARSFiatCollateral.sol | 5 +- .../phase2-assets/2_deploy_collateral.ts | 24 ------ .../phase2-assets/collaterals/deploy_nARS.ts | 81 +++++++++++++++++++ .../phase2-assets/collaterals/deploy_snARS.ts | 2 +- .../individual-collateral/collateralTests.ts | 28 +++++++ 6 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts diff --git a/contracts/plugins/assets/num/README.md b/contracts/plugins/assets/num/README.md index 1078c9133d..4750be93bb 100644 --- a/contracts/plugins/assets/num/README.md +++ b/contracts/plugins/assets/num/README.md @@ -7,8 +7,9 @@ This plugin allows `nARS` and `snARS` holders to use their tokens as collateral As described in the [Num Site](https://new.num.finance/) nTokens are ERC20 tokens, tracking the value of an underlying financial asset. Each nToken issued by Num Finance is fully collateralized by an asset in the traditional market. This means that for every nToken in circulation, there is a real-world asset backing it, ensuring the token's value and stability. -In this particular case we're incorporating through this plugin 2 nTokens. -- `nARS` is a stablecoin pegged to the `Argentine Peso (ARS)`. +In this particular case we're incorporating through this plugin 2 nTokens. + +- `nARS` is a stablecoin pegged to the `Argentine Peso (ARS)`. - `snARS` is the staked version of `nARS`. When users stake their `nARS`, they receive `snARS` in return, which grants them certain benefits in the form of yield or Numun Rewards. Staking of `nARS` is possible at: https://numun.fi/ @@ -19,13 +20,13 @@ nStables documentation: https://docs.nstables.fi/ ### Units -| tok | ref | target | UoA | -| ---- | ---- | ------ | --- | -| nARS | ARS | ARS | USD | +| tok | ref | target | UoA | +| ---- | --- | ------ | --- | +| nARS | ARS | ARS | USD | | tok | ref | target | UoA | -| ---- | ---- | ------ | --- | -| sNARS | ARS | ARS | USD | +| ----- | ---- | ------ | --- | +| sNARS | nARS | ARS | USD | ### claimRewards() diff --git a/contracts/plugins/assets/num/SnARSFiatCollateral.sol b/contracts/plugins/assets/num/SnARSFiatCollateral.sol index f5f6454a29..d63a6e8045 100644 --- a/contracts/plugins/assets/num/SnARSFiatCollateral.sol +++ b/contracts/plugins/assets/num/SnARSFiatCollateral.sol @@ -7,7 +7,10 @@ import { ERC4626FiatCollateral } from "../ERC4626FiatCollateral.sol"; /** * @title SnARSFiatCollateral * @notice Collateral plugin for a Num vault with fiat collateral - * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == ARS, {UoA} == USD + * tok = sNARS + * ref = nARS + * tar = ARS + * UoA = USD */ contract SnARSFiatCollateral is ERC4626FiatCollateral { /// config.erc20 must be a Num ERC4626 vault diff --git a/scripts/deployment/phase2-assets/2_deploy_collateral.ts b/scripts/deployment/phase2-assets/2_deploy_collateral.ts index 2b50fe94e1..a886658cb7 100644 --- a/scripts/deployment/phase2-assets/2_deploy_collateral.ts +++ b/scripts/deployment/phase2-assets/2_deploy_collateral.ts @@ -204,30 +204,6 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) } - /******** Deploy Fiat Collateral - nARS **************************/ - if (networkConfig[chainId].tokens.nARS && networkConfig[chainId].chainlinkFeeds.nARS) { - const { collateral: narsCollateral } = await hre.run('deploy-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.nARS, - oracleError: fp('0.005').toString(), // 0.5% - tokenAddress: networkConfig[chainId].tokens.nARS, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '900', - targetName: hre.ethers.utils.formatBytes32String('ARS'), - defaultThreshold: fp('0.01').toString(), // 1.5% - delayUntilDefault: bn('86400').toString(), // 24h - }) - collateral = await ethers.getContractAt('ICollateral', narsCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.nARS = narsCollateral - assetCollDeployments.erc20s.nARS = networkConfig[chainId].tokens.nARS - deployedCollateral.push(narsCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - } - /*** AAVE V2 not available in Base or Arbitrum L2s */ if (!baseL2Chains.includes(hre.network.name) && !arbitrumL2Chains.includes(hre.network.name)) { /******** Deploy AToken Fiat Collateral - aDAI **************************/ diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts b/scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts new file mode 100644 index 0000000000..96d899e46e --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_nARS.ts @@ -0,0 +1,81 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { ICollateral } from '@typechain/ICollateral' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, 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 } from '../../../deployment/utils' + +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}`) + } + + // Only exists on Base chain + if (!baseL2Chains.includes(hre.network.name)) { + throw new Error(`Invalid network ${hre.network.name} - only available on Base chain`) + } + + // 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[] = [] + + let collateral: ICollateral + + /******** Deploy NARS Collateral - ARS **************************/ + + const { collateral: narsCollateral } = await hre.run('deploy-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.nARS, + oracleError: fp('0.005').toString(), // 0.5% + tokenAddress: networkConfig[chainId].tokens.nARS, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '900', + targetName: hre.ethers.utils.formatBytes32String('ARS'), + defaultThreshold: fp('0.015').toString(), // 1.5% + delayUntilDefault: bn('86400').toString(), // 24h + }) + + collateral = await ethers.getContractAt('ICollateral', narsCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.nARS = narsCollateral + assetCollDeployments.erc20s.nARS = networkConfig[chainId].tokens.nARS + deployedCollateral.push(narsCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed nARS asset to ${hre.network.name} (${chainId}): + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_snARS.ts b/scripts/deployment/phase2-assets/collaterals/deploy_snARS.ts index 9c778de818..93a2cc8019 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_snARS.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_snARS.ts @@ -55,7 +55,7 @@ async function main() { maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: '900', // 15min targetName: hre.ethers.utils.formatBytes32String('ARS'), - defaultThreshold: fp('0.015').toString(), // 2% = 0.5% oracleError + 1% buffer + defaultThreshold: fp('0.015').toString(), // 1.5% = 0.5% oracleError + 1% buffer delayUntilDefault: bn('86400').toString(), // 24h }, '0' // revenueHiding = 0 diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 0920c3a56a..4057a3bb44 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -1004,6 +1004,34 @@ export default function fn( targetUnitOracle.address, ORACLE_TIMEOUT ) + } else if (target == ethers.utils.formatBytes32String('ARS')) { + // ARS + if (!onBase) throw new Error('nARS/snARS only available on base') + + const erc20 = await ethers.getContractAt( + 'IERC20Metadata', + onBase ? networkConfig[chainId].tokens.snARS! : networkConfig[chainId].tokens.snARS! + ) + const whale = NUM_HOLDER + await whileImpersonating(whale, async (signer) => { + await erc20 + .connect(signer) + .transfer(addr1.address, await erc20.balanceOf(signer.address)) + }) + const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'FiatCollateral' + ) + return await FiatCollateralFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: chainlinkFeed.address, + oracleError: ORACLE_ERROR, + oracleTimeout: ORACLE_TIMEOUT, + maxTradeVolume: MAX_UINT192, + erc20: erc20.address, + targetName: ethers.utils.formatBytes32String('ARS'), + defaultThreshold: fp('0.015'), // 1.5% + delayUntilDefault: bn('86400'), // 24h, + }) } else { throw new Error(`Unknown target: ${target}`) }