diff --git a/hardhat-fork.config.ts b/hardhat-fork.config.ts index 952103ae..da0722a0 100644 --- a/hardhat-fork.config.ts +++ b/hardhat-fork.config.ts @@ -9,7 +9,7 @@ export default { blockGasLimit: 9000000, forking: { url: process.env.NODE_URL || "", - blockNumber: 12043106, + blockNumber: 12094381, }, }, }, diff --git a/package.json b/package.json index 99562bb7..3bd2dd9d 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "coverage": "yarn hardhat compile --force && node --max_old_space_size=6144 node_modules/.bin/hardhat coverage --temp 'build/contracts' --testfiles 'test/**/*.spec.ts' --show-stack-traces", "convertTestFiles": "cd test-utils/validator-data; ts-node ./convertCsvTestFiles.ts", "task": "yarn compile && yarn hardhat --config tasks.config.ts", + "task-fork": "yarn compile && yarn hardhat --config tasks-fork.config.ts", "test": "yarn hardhat test", "test:long": "LONG_TESTS=true yarn hardhat test", "test-fork": "yarn hardhat --config hardhat-fork.config.ts test ./test-fork/**/*.spec.ts", diff --git a/tasks-fork.config.ts b/tasks-fork.config.ts new file mode 100644 index 00000000..d7e398d3 --- /dev/null +++ b/tasks-fork.config.ts @@ -0,0 +1,8 @@ +import config from "./hardhat-fork.config" + +import "./tasks/deployMbtc" +import "./tasks/mBTC" +import "./tasks/deployMV3" +import "./tasks/mUSD" + +export default config diff --git a/tasks.config.ts b/tasks.config.ts index e744f0b1..802c0a9c 100644 --- a/tasks.config.ts +++ b/tasks.config.ts @@ -1,6 +1,8 @@ -import { hardhatConfig } from "./hardhat.config" +import config from "./hardhat.config" + import "./tasks/deployMbtc" import "./tasks/mBTC" import "./tasks/deployMV3" +import "./tasks/mUSD" -export default hardhatConfig +export default config diff --git a/tasks/mBTC.ts b/tasks/mBTC.ts index 047d8eb1..f8ed1c1f 100644 --- a/tasks/mBTC.ts +++ b/tasks/mBTC.ts @@ -25,11 +25,7 @@ interface Balances { const getTvlCap = async (signer: Signer): Promise => { const validator = await new ValidatorWithTVLCap__factory(signer).attach(contracts.mainnet.InvariantValidator) const tvlStartTime = await validator.startTime() - const weeksSinceLaunch = BN.from(Date.now()) - .div(1000) - .sub(tvlStartTime) - .mul(fullScale) - .div(604800) + const weeksSinceLaunch = BN.from(Date.now()).div(1000).sub(tvlStartTime).mul(fullScale).div(604800) // // e.g. 1e19 + (15e18 * 2.04e36) = 1e19 + 3.06e55 // // startingCap + (capFactor * weeksSinceLaunch**2 / 1e36); return startingCap.add(capFactor.mul(weeksSinceLaunch.pow(2)).div(fullScale.pow(2))) @@ -73,10 +69,7 @@ const getSwapRates = async (mBTC: Masset) => { const output = await mBTC.getSwapOutput(inputAddress, outputAddress, input) const scaledInput = applyDecimals(input, inputToken.decimals) const scaledOutput = applyDecimals(output, outputToken.decimals) - const percent = scaledOutput - .sub(scaledInput) - .mul(10000) - .div(scaledInput) + const percent = scaledOutput.sub(scaledInput).mul(10000).div(scaledInput) console.log( `${inputStr} ${inputToken.symbol.padEnd(6)} -> ${outputToken.symbol.padEnd(6)} ${formatUnits( output, @@ -96,10 +89,7 @@ const getBalances = async (mBTC: Masset): Promise => { const savingBalance = await mBTC.balanceOf(contracts.mainnet.imBTC) const sushiPoolBalance = await mBTC.balanceOf(contracts.mainnet.sushiPool) const mStableFundManagerBalance = await mBTC.balanceOf(contracts.mainnet.fundManager) - const otherBalances = mBtcBalance - .sub(savingBalance) - .sub(sushiPoolBalance) - .sub(mStableFundManagerBalance) + const otherBalances = mBtcBalance.sub(savingBalance).sub(sushiPoolBalance).sub(mStableFundManagerBalance) console.log("\nmBTC Holders") console.log(`imBTC ${formatUnits(savingBalance).padEnd(20)} ${savingBalance.mul(100).div(mBtcBalance)}%`) @@ -260,11 +250,11 @@ const outputFees = ( currentTime: Date, ) => { const totalFees = redeems.fees.add(multiRedeems.fees).add(swaps.fees) - const totalTransactions = mints.total - .add(multiMints.total) - .add(redeems.total) - .add(multiRedeems.total) - .add(swaps.total) + if (totalFees.eq(0)) { + console.log(`\nNo fees since ${startTime.toUTCString()}`) + return + } + const totalTransactions = mints.total.add(multiMints.total).add(redeems.total).add(multiRedeems.total).add(swaps.total) const totalFeeTransactions = redeems.total.add(multiRedeems.total).add(swaps.total) console.log(`\nFees since ${startTime.toUTCString()}`) console.log(" mBTC Volume\t Fees\t\t Fee %") @@ -291,11 +281,7 @@ const outputFees = ( ) const periodSeconds = BN.from(currentTime.valueOf() - startTime.valueOf()).div(1000) const liquidityUtilization = totalFeeTransactions.mul(100).div(balances.total) - const totalApy = totalFees - .mul(100) - .mul(ONE_YEAR) - .div(balances.save) - .div(periodSeconds) + const totalApy = totalFees.mul(100).mul(ONE_YEAR).div(balances.save).div(periodSeconds) console.log(`Total Txs ${formatUnits(totalTransactions).padEnd(22)}`) console.log(`Savings ${formatUnits(balances.save).padEnd(22)} ${formatUnits(totalFees).padEnd(20)} APY ${totalApy}%`) console.log( diff --git a/tasks/mUSD.ts b/tasks/mUSD.ts new file mode 100644 index 00000000..084fe764 --- /dev/null +++ b/tasks/mUSD.ts @@ -0,0 +1,431 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-console */ +import "ts-node/register" +import "tsconfig-paths/register" +import { task, types } from "hardhat/config" +import { ContractFactory, Signer } from "ethers" +import { formatUnits } from "ethers/lib/utils" + +import { Masset } from "types/generated" +import { BN, simpleToExactAmount, applyDecimals } from "@utils/math" +import { BassetStatus } from "@utils/mstable-objects" +import { MassetLibraryAddresses, Masset__factory } from "types/generated/factories/Masset__factory" +import { ONE_YEAR } from "@utils/constants" +import * as MassetV2 from "../test-fork/mUSD/MassetV2.json" + +// Mainnet contract addresses +const mUsdAddress = "0xe2f2a5C287993345a840Db3B0845fbC70f5935a5" +const validatorAddress = "0xCa480D596e6717C95a62a4DC1bD4fbD7b7E7d705" + +const config = { + a: 135, + limits: { + min: simpleToExactAmount(5, 16), + max: simpleToExactAmount(65, 16), + }, +} + +interface TxSummary { + count: number + total: BN + fees: BN +} +interface Token { + symbol: string + address: string + integrator: string + decimals: number + vaultBalance: BN + ratio: BN +} + +interface Balances { + total: BN + save: BN + earn: BN +} + +const sUSD: Token = { + symbol: "sUSD", + address: "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51", + integrator: "0xb9b0cfa90436c3fcbf8d8eb6ed8d0c2e3da47ca9", + decimals: 18, + vaultBalance: BN.from("10725219000000000000000000"), + ratio: BN.from("100000000"), +} +const USDC: Token = { + symbol: "USDC", + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + integrator: "0xD55684f4369040C12262949Ff78299f2BC9dB735", + decimals: 6, + vaultBalance: BN.from("10725219000000"), + ratio: BN.from("100000000000000000000"), +} +const USDT: Token = { + symbol: "USDT", + address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + integrator: "0xf617346A0FB6320e9E578E0C9B2A4588283D9d39", + decimals: 6, + vaultBalance: BN.from("10725219000000"), + ratio: BN.from("100000000000000000000"), +} +const DAI: Token = { + symbol: "DAI", + address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", + integrator: "0xD55684f4369040C12262949Ff78299f2BC9dB735", + decimals: 18, + vaultBalance: BN.from("10725219000000000000000000"), + ratio: BN.from("100000000"), +} + +const mUSDBassets: Token[] = [sUSD, USDC, DAI, USDT] + +// Test mUSD token storage variables +const snapTokenStorage = async (token: Masset) => { + console.log("Symbol: ", (await token.symbol()).toString(), "mUSD") + console.log("Name: ", (await token.name()).toString(), "mStable USD") + console.log("Decimals: ", (await token.decimals()).toString(), 18) + console.log("UserBal: ", (await token.balanceOf("0x5C80E54f903458edD0723e268377f5768C7869d7")).toString(), "6971708003000000000000") + console.log("Supply: ", (await token.totalSupply()).toString(), simpleToExactAmount(43000000).toString()) +} + +// Test the existing Masset V2 storage variables +const snapConfig = async (token: Masset) => { + console.log("SwapFee: ", (await token.swapFee()).toString(), simpleToExactAmount(6, 14).toString()) + console.log("RedemptionFee: ", (await token.redemptionFee()).toString(), simpleToExactAmount(3, 14).toString()) + console.log("CacheSize: ", (await token.cacheSize()).toString(), simpleToExactAmount(3, 16).toString()) + console.log("Surplus: ", (await token.surplus()).toString()) +} + +// Test the new Masset V3 storage variables +const snapMasset = async (mUsd: Masset, validator: string) => { + console.log("ForgeValidator: ", (await mUsd.forgeValidator()).toString(), validator) + console.log("MaxBassets: ", (await mUsd.maxBassets()).toString(), 10) + + // bAsset personal data + const bAssets = await mUsd.getBassets() + mUSDBassets.forEach(async (token, i) => { + console.log(`Addr${i}`, bAssets.personal[i].addr.toString(), token.address) + console.log(`Integ${i}`, bAssets.personal[i].integrator.toString(), token.integrator) + console.log(`TxFee${i}`, bAssets.personal[i].hasTxFee.toString(), "false") + console.log(`Status${i}`, bAssets.personal[i].status.toString(), BassetStatus.Normal) + console.log(`Ratio${i}`, bAssets.data[i].ratio.toString(), simpleToExactAmount(1, 8 + (18 - token.decimals)).toString()) + console.log(`Vault${i}`, bAssets.data[i].vaultBalance.toString(), token.vaultBalance.toString()) + console.log(await mUsd.bAssetIndexes(token.address), i) + const bAsset = await mUsd.getBasset(token.address) + console.log("Sanity check: ", bAsset[0][0], token.address) + }) + + // Get basket state + const basketState = await mUsd.basket() + console.log("UndergoingRecol: ", basketState.undergoingRecol, "true") + console.log("Failed: ", basketState.failed, "false") + + const invariantConfig = await mUsd.getConfig() + console.log("A: ", invariantConfig.a.toString(), config.a * 100) + console.log("Min: ", invariantConfig.limits.min.toString(), config.limits.min.toString()) + console.log("Max: ", invariantConfig.limits.max.toString(), config.limits.max.toString()) +} + +const getSwapRates = async (masset: Masset) => { + console.log("\nSwap rates") + for (const inputToken of mUSDBassets) { + for (const outputToken of mUSDBassets) { + if (inputToken.symbol !== outputToken.symbol) { + const inputAddress = inputToken.address + const outputAddress = outputToken.address + try { + const inputStr = "1000" + const input = simpleToExactAmount(inputStr, inputToken.decimals) + const output = await masset.getSwapOutput(inputAddress, outputAddress, input) + const scaledInput = applyDecimals(input, inputToken.decimals) + const scaledOutput = applyDecimals(output, outputToken.decimals) + const percent = scaledOutput.sub(scaledInput).mul(10000).div(scaledInput) + console.log( + `${inputStr} ${inputToken.symbol.padEnd(6)} -> ${outputToken.symbol.padEnd(6)} ${formatUnits( + output, + outputToken.decimals, + ).padEnd(21)} ${percent.toString().padStart(4)}bps`, + ) + } catch (err) { + console.error(`${inputToken.symbol} -> ${outputToken.symbol} ${err.message}`) + } + } + } + } +} + +const getBalances = async (masset: Masset): Promise => { + const mUsdBalance = await masset.totalSupply() + const savingBalance = await masset.balanceOf("0x30647a72dc82d7fbb1123ea74716ab8a317eac19") + const curveMusdBalance = await masset.balanceOf("0x8474ddbe98f5aa3179b3b3f5942d724afcdec9f6") + const mStableDAOBalance = await masset.balanceOf("0x3dd46846eed8D147841AE162C8425c08BD8E1b41") + const balancerETHmUSD5050Balance = await masset.balanceOf("0xe036cce08cf4e23d33bc6b18e53caf532afa8513") + const otherBalances = mUsdBalance.sub(savingBalance).sub(curveMusdBalance).sub(mStableDAOBalance).sub(balancerETHmUSD5050Balance) + + console.log("\nmUSD Holders") + console.log(`imUSD ${formatUnits(savingBalance).padEnd(20)} ${savingBalance.mul(100).div(mUsdBalance)}%`) + console.log(`Curve mUSD ${formatUnits(curveMusdBalance).padEnd(20)} ${curveMusdBalance.mul(100).div(mUsdBalance)}%`) + console.log(`mStable DAO ${formatUnits(mStableDAOBalance).padEnd(20)} ${mStableDAOBalance.mul(100).div(mUsdBalance)}%`) + console.log( + `Balancer ETH/mUSD 50/50 #2 ${formatUnits(balancerETHmUSD5050Balance).padEnd(20)} ${balancerETHmUSD5050Balance + .mul(100) + .div(mUsdBalance)}%`, + ) + console.log(`Others ${formatUnits(otherBalances).padEnd(20)} ${otherBalances.mul(100).div(mUsdBalance)}%`) + + const surplus = await masset.surplus() + console.log(`Surplus ${formatUnits(surplus)}`) + console.log(`Total ${formatUnits(mUsdBalance)}`) + + return { + total: mUsdBalance, + save: savingBalance, + earn: curveMusdBalance, + } +} + +// Deploys Migrator, pulls Manager address and deploys mUSD implementation +const getMusd = async (deployer: Signer): Promise => { + const linkedAddress: MassetLibraryAddresses = { + __$1a38b0db2bd175b310a9a3f8697d44eb75$__: "0x1E91F826fa8aA4fa4D3F595898AF3A64dd188848", // Masset Manager + } + const mUsdV3Factory = new Masset__factory(linkedAddress, deployer) + return mUsdV3Factory.attach(mUsdAddress) +} + +const getMints = async (mBTC: Masset, fromBlock: number, startTime: Date): Promise => { + const filter = await mBTC.filters.Minted(null, null, null, null, null) + const logs = await mBTC.queryFilter(filter, fromBlock) + + console.log(`\nMints since block ${fromBlock} at ${startTime.toUTCString()}`) + console.log("Block#\t Tx hash\t\t\t\t\t\t\t bAsset Masset Quantity") + let total = BN.from(0) + let count = 0 + logs.forEach((log) => { + const inputBasset = mUSDBassets.find((b) => b.address === log.args.input) + console.log(`${log.blockNumber} ${log.transactionHash} ${inputBasset.symbol.padEnd(6)} ${formatUnits(log.args.mAssetQuantity)}`) + total = total.add(log.args.mAssetQuantity) + count += 1 + }) + console.log(`Count ${count}, Total ${formatUnits(total)}`) + return { + count, + total, + fees: BN.from(0), + } +} + +const getMultiMints = async (mBTC: Masset, fromBlock: number, startTime: Date): Promise => { + const filter = await mBTC.filters.MintedMulti(null, null, null, null, null) + const logs = await mBTC.queryFilter(filter, fromBlock) + + console.log(`\nMulti Mints since block ${fromBlock} at ${startTime.toUTCString()}`) + console.log("Block#\t Tx hash\t\t\t\t\t\t\t Masset Quantity") + let total = BN.from(0) + let count = 0 + logs.forEach((log) => { + // Ignore nMintMulti events from collectInterest and collectPlatformInterest + if (!log.args.inputs.length) return + const inputBassets = log.args.inputs.map((input) => mUSDBassets.find((b) => b.address === input)) + console.log(`${log.blockNumber} ${log.transactionHash} ${formatUnits(log.args.mAssetQuantity)}`) + inputBassets.forEach((bAsset, i) => { + console.log(` ${bAsset.symbol.padEnd(6)} ${formatUnits(log.args.inputQuantities[i], bAsset.decimals).padEnd(21)}`) + }) + total = total.add(log.args.mAssetQuantity) + count += 1 + }) + console.log(`Count ${count}, Total ${formatUnits(total)}`) + return { + count, + total, + fees: BN.from(0), + } +} + +const getSwaps = async (mBTC: Masset, fromBlock: number, startTime: Date): Promise => { + const filter = await mBTC.filters.Swapped(null, null, null, null, null, null) + const logs = await mBTC.queryFilter(filter, fromBlock) + + console.log(`\nSwaps since block ${fromBlock} at ${startTime.toUTCString()}`) + console.log("Block#\t Tx hash\t\t\t\t\t\t\t Input Output Output Quantity\tFee") + // Scaled bAsset quantities + let total = BN.from(0) + let fees = BN.from(0) + let count = 0 + logs.forEach((log) => { + const inputBasset = mUSDBassets.find((b) => b.address === log.args.input) + const outputBasset = mUSDBassets.find((b) => b.address === log.args.output) + console.log( + `${log.blockNumber} ${log.transactionHash} ${inputBasset.symbol.padEnd(6)} ${outputBasset.symbol.padEnd(6)} ${formatUnits( + log.args.outputAmount, + outputBasset.decimals, + ).padEnd(21)} ${formatUnits(log.args.scaledFee)}`, + ) + total = total.add(applyDecimals(log.args.outputAmount, outputBasset.decimals)) + fees = fees.add(log.args.scaledFee) + count += 1 + }) + console.log(`Count ${count}, Total ${formatUnits(total)}`) + + return { + count, + total, + fees, + } +} + +const getRedemptions = async (mBTC: Masset, fromBlock: number, startTime: Date): Promise => { + const filter = await mBTC.filters.Redeemed(null, null, null, null, null, null) + const logs = await mBTC.queryFilter(filter, fromBlock) + + console.log(`\nRedemptions since block ${fromBlock} at ${startTime.toUTCString()}`) + console.log("Block#\t Tx hash\t\t\t\t\t\t\t bAsset Masset Quantity\tFee") + let total = BN.from(0) + let fees = BN.from(0) + let count = 0 + logs.forEach((log) => { + const outputBasset = mUSDBassets.find((b) => b.address === log.args.output) + console.log( + `${log.blockNumber} ${log.transactionHash} ${outputBasset.symbol.padEnd(6)} ${formatUnits( + log.args.mAssetQuantity, + )} ${formatUnits(log.args.scaledFee)}`, + ) + total = total.add(log.args.mAssetQuantity) + fees = fees.add(log.args.scaledFee) + count += 1 + }) + console.log(`Count ${count}, Total ${formatUnits(total)}`) + + return { + count, + total, + fees, + } +} + +const getMultiRedemptions = async (mBTC: Masset, fromBlock: number, startTime: Date): Promise => { + const filter = await mBTC.filters.RedeemedMulti(null, null, null, null, null, null) + const logs = await mBTC.queryFilter(filter, fromBlock) + + console.log(`\nMulti Redemptions since block ${fromBlock} at ${startTime.toUTCString()}`) + console.log("Block#\t Tx hash\t\t\t\t\t\t\t Masset Quantity\t Fee") + let total = BN.from(0) + let fees = BN.from(0) + let count = 0 + logs.forEach((log) => { + const outputBassets = log.args.outputs.map((output) => mUSDBassets.find((b) => b.address === output)) + console.log(`${log.blockNumber} ${log.transactionHash} ${formatUnits(log.args.mAssetQuantity)} ${formatUnits(log.args.scaledFee)}`) + outputBassets.forEach((bAsset, i) => { + console.log(` ${bAsset.symbol.padEnd(6)} ${formatUnits(log.args.outputQuantity[i], bAsset.decimals).padEnd(21)}`) + }) + total = total.add(log.args.mAssetQuantity) + fees = fees.add(log.args.scaledFee) + count += 1 + }) + console.log(`Count ${count}, Total ${formatUnits(total)}`) + + return { + count, + total, + fees, + } +} + +const outputFees = ( + mints: TxSummary, + multiMints: TxSummary, + swaps: TxSummary, + redeems: TxSummary, + multiRedeems: TxSummary, + balances: Balances, + startTime: Date, + currentTime: Date, +) => { + const totalFees = redeems.fees.add(multiRedeems.fees).add(swaps.fees) + if (totalFees.eq(0)) { + console.log(`\nNo fees since ${startTime.toUTCString()}`) + return + } + const totalTransactions = mints.total.add(multiMints.total).add(redeems.total).add(multiRedeems.total).add(swaps.total) + const totalFeeTransactions = redeems.total.add(multiRedeems.total).add(swaps.total) + console.log(`\nFees since ${startTime.toUTCString()}`) + console.log(" # mUSD Volume\t Fees\t\t Fee %") + console.log( + `Mints ${mints.count.toString().padEnd(2)} ${formatUnits(mints.total).padEnd(22)} ${formatUnits(mints.fees).padEnd( + 20, + )} ${mints.fees.mul(100).div(totalFees)}%`, + ) + console.log( + `Multi Mints ${multiMints.count.toString().padEnd(2)} ${formatUnits(multiMints.total).padEnd(22)} ${formatUnits( + multiMints.fees, + ).padEnd(20)} ${multiMints.fees.mul(100).div(totalFees)}%`, + ) + console.log( + `Redeems ${redeems.count.toString().padEnd(2)} ${formatUnits(redeems.total).padEnd(22)} ${formatUnits(redeems.fees).padEnd( + 20, + )} ${redeems.fees.mul(100).div(totalFees)}%`, + ) + console.log( + `Multi Redeems ${multiRedeems.count.toString().padEnd(2)} ${formatUnits(multiRedeems.total).padEnd(22)} ${formatUnits( + multiRedeems.fees, + ).padEnd(20)} ${multiRedeems.fees.mul(100).div(totalFees)}%`, + ) + console.log( + `Swaps ${swaps.count.toString().padEnd(2)} ${formatUnits(swaps.total).padEnd(22)} ${formatUnits(swaps.fees).padEnd( + 20, + )} ${swaps.fees.mul(100).div(totalFees)}%`, + ) + const periodSeconds = BN.from(currentTime.valueOf() - startTime.valueOf()).div(1000) + const liquidityUtilization = totalFeeTransactions.mul(100).div(balances.total) + const totalApy = totalFees.mul(100).mul(ONE_YEAR).div(balances.save).div(periodSeconds) + console.log(`Total Txs ${formatUnits(totalTransactions).padEnd(22)}`) + console.log(`Savings ${formatUnits(balances.save).padEnd(22)} ${formatUnits(totalFees).padEnd(20)} APY ${totalApy}%`) + console.log( + `${liquidityUtilization}% liquidity utilization (${formatUnits(totalFeeTransactions)} of ${formatUnits(balances.total)} mUSD)`, + ) +} + +task("mUSD-snapv2", "Snaps mUSD's V2 storage").setAction(async (hre) => { + const { ethers } = hre + console.log(`Current block number ${await ethers.provider.getBlockNumber()}`) + const [signer] = await ethers.getSigners() + + const mUsdV2Factory = new ContractFactory(MassetV2.abi, MassetV2.bytecode, signer) + const mUSD = mUsdV2Factory.attach(mUsdAddress) as Masset + + await snapTokenStorage(mUSD) + await snapConfig(mUSD) + await getBalances(mUSD) +}) + +task("mUSD-snap", "Snaps mUSD") + .addOptionalParam("from", "Block to query transaction events from. (default: deployment block)", 12094461, types.int) + .setAction(async (taskArgs, hre) => { + const { ethers } = hre + const [signer] = await ethers.getSigners() + + const mUSD = await getMusd(signer) + + const currentBlock = await hre.ethers.provider.getBlockNumber() + const currentTime = new Date() + const fromBlock = taskArgs.from + console.log(`Latest block ${currentBlock}, ${currentTime.toUTCString()}`) + const startBlock = await hre.ethers.provider.getBlock(fromBlock) + const startTime = new Date(startBlock.timestamp * 1000) + + const balances = await getBalances(mUSD) + + await getSwapRates(mUSD) + + const mintSummary = await getMints(mUSD, fromBlock, startTime) + const mintMultiSummary = await getMultiMints(mUSD, fromBlock, startTime) + const swapSummary = await getSwaps(mUSD, fromBlock, startTime) + const redeemSummary = await getRedemptions(mUSD, fromBlock, startTime) + const redeemMultiSummary = await getMultiRedemptions(mUSD, fromBlock, startTime) + + outputFees(mintSummary, mintMultiSummary, swapSummary, redeemSummary, redeemMultiSummary, balances, startTime, currentTime) + }) + +module.exports = {} diff --git a/test-fork/mUSD/mUSD-migrate.spec.ts b/test-fork/mUSD/mUSD-migrate.spec.ts index b67a9b84..00cabed1 100644 --- a/test-fork/mUSD/mUSD-migrate.spec.ts +++ b/test-fork/mUSD/mUSD-migrate.spec.ts @@ -7,8 +7,8 @@ import { expect } from "chai" import { Contract, ContractFactory, Signer } from "ethers" import { ethers, network } from "hardhat" -import { ONE_WEEK, ONE_DAY, ZERO_ADDRESS, DEAD_ADDRESS } from "@utils/constants" -import { applyDecimals, applyRatio, applyRatioMassetToBasset, BN, simpleToExactAmount } from "@utils/math" +import { ONE_WEEK, DEAD_ADDRESS } from "@utils/constants" +import { applyDecimals, applyRatioMassetToBasset, BN, simpleToExactAmount } from "@utils/math" import { DelayedProxyAdmin, DelayedProxyAdmin__factory, @@ -16,12 +16,11 @@ import { Masset, MusdV3, MusdV3__factory, - AaveV2Integration__factory, AaveV2Integration, } from "types/generated" import { MusdV3LibraryAddresses } from "types/generated/factories/MusdV3__factory" -import { increaseTime } from "@utils/time" import { BassetStatus } from "@utils/mstable-objects" +import { assertBNClosePercent } from "@utils/assertions" // eslint-disable-next-line @typescript-eslint/no-var-requires import { formatUnits } from "ethers/lib/utils" @@ -44,11 +43,6 @@ const nexusAddress = "0xAFcE80b19A8cE13DEc0739a1aaB7A028d6845Eb3" const delayedProxyAdminAddress = "0x5C8eb57b44C1c6391fC7a8A0cf44d26896f92386" const governorAddress = "0xF6FF1F7FCEB2cE6d26687EaaB5988b445d0b94a2" -// Aave V2 -const aaveAddress = "0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5" - -const forkBlockNumber = 12043106 - const defaultConfig = { a: 135, limits: { @@ -70,17 +64,17 @@ interface Token { const sUSD: Token = { symbol: "sUSD", address: "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51", - integrator: "0xf617346A0FB6320e9E578E0C9B2A4588283D9d39", // Aave vault + integrator: "0xB9b0cfa90436C3FcBf8d8eb6Ed8d0c2e3da47CA9", // Aave vault decimals: 18, - vaultBalance: BN.from("80910135777356730215"), - whaleAddress: "0x8cA24021E3Ee3B5c241BBfcee0712554D7Dc38a1", + vaultBalance: BN.from("90000000000000000000"), + whaleAddress: "0x49BE88F0fcC3A8393a59d3688480d7D253C37D2A", } const USDC: Token = { symbol: "USDC", address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", integrator: "0xD55684f4369040C12262949Ff78299f2BC9dB735", // Compound Vault decimals: 6, - vaultBalance: BN.from("190649757940"), + vaultBalance: BN.from("10725219000000"), whaleAddress: "0xf977814e90da44bfa03b6295a0616a897441acec", // Binance } const TUSD: Token = { @@ -88,7 +82,7 @@ const TUSD: Token = { address: "0x0000000000085d4780B73119b644AE5ecd22b376", integrator: "0xf617346A0FB6320e9E578E0C9B2A4588283D9d39", // Aave vault decimals: 18, - vaultBalance: BN.from("20372453144590237158484978"), + vaultBalance: BN.from("10725219000000000000000000"), whaleAddress: "0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be", // Binance } const USDT: Token = { @@ -96,7 +90,7 @@ const USDT: Token = { address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", integrator: "0xf617346A0FB6320e9E578E0C9B2A4588283D9d39", // Aave vault decimals: 6, - vaultBalance: BN.from("24761709994543"), + vaultBalance: BN.from("10725219000000"), whaleAddress: "0xf977814e90da44bfa03b6295a0616a897441acec", // Binance } const DAI: Token = { @@ -104,7 +98,7 @@ const DAI: Token = { address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", integrator: "0xD55684f4369040C12262949Ff78299f2BC9dB735", // Compound Vault decimals: 18, - vaultBalance: BN.from(0), + vaultBalance: BN.from("10725219000000000000000000"), whaleAddress: "0xbe0eb53f46cd790cd13851d5eff43d12404d33e8", } @@ -225,25 +219,22 @@ const getMusdv3 = async (deployer: Signer): Promise => { } // Test mUSD token storage variables -const validateTokenStorage = async (token: MusdV3 | Masset | Contract, overrideSupply = "45324535157903774527261941") => { +const validateTokenStorage = async (token: MusdV3 | Masset | Contract, overrideSupply = "42495582693426128850845316") => { expect(await token.symbol(), "symbol").to.eq("mUSD") expect(await token.name(), "name").to.eq("mStable USD") expect(await token.decimals(), "decimals").to.eq(18) // some mUSD token holder - expect(await token.balanceOf("0x5C80E54f903458edD0723e268377f5768C7869d7"), `mUSD balance at block ${forkBlockNumber}`).to.eq( - "6971708003000000000000", - ) - // For block number 11880000 - expect(await token.totalSupply(), `totalSupply at block ${forkBlockNumber}`).to.eq(overrideSupply) + expect(await token.balanceOf("0x5C80E54f903458edD0723e268377f5768C7869d7"), `mUSD balance`).to.eq("6971708003000000000000") + assertBNClosePercent(await token.totalSupply(), BN.from(overrideSupply), "0.1") } // Test the existing Masset V2 storage variables -const validateUnchangedMassetStorage = async (mUsd: MusdV3 | Masset | Contract, overrideSurplus = "358648087000000000001") => { +const validateUnchangedMassetStorage = async (mUsd: MusdV3 | Masset | Contract, overrideSurplus = "1") => { expect(await mUsd.swapFee(), "swap fee").to.eq(simpleToExactAmount(6, 14)) expect(await mUsd.redemptionFee(), "redemption fee").to.eq(simpleToExactAmount(3, 14)) expect(await mUsd.cacheSize(), "cache size").to.eq(simpleToExactAmount(3, 16)) - expect(await mUsd.surplus(), `surplus at block ${forkBlockNumber}`).to.eq(overrideSurplus) - expect(await mUsd.nexus(), `nexus at block ${forkBlockNumber}`).to.eq(nexusAddress) + expect(await mUsd.surplus(), "surplus").to.eq(overrideSurplus) + expect(await mUsd.nexus(), "nexus").to.eq(nexusAddress) } // Check that the bAsset data is what we expect @@ -260,7 +251,7 @@ const validateBasset = (bAssets, i: number, expectToken: Token, expectVaultBalan } // Test the new Masset V3 storage variables -const validateNewMassetStorage = async (mUsd: MusdV3 | Masset, validator: string, newPlatform: string, expectVaultBalances?: BN[]) => { +const validateNewMassetStorage = async (mUsd: MusdV3 | Masset, validator: string, expectVaultBalances?: BN[]) => { expect(await mUsd.forgeValidator(), "forge validator").to.eq(validator) expect(await mUsd.maxBassets(), "maxBassets").to.eq(10) @@ -268,9 +259,6 @@ const validateNewMassetStorage = async (mUsd: MusdV3 | Masset, validator: string const bAssets = await mUsd.getBassets() await Promise.all( finalBassets.map(async (token, i) => { - if (token.symbol === "sUSD") { - token.integrator = newPlatform - } validateBasset(bAssets, i, token, expectVaultBalances) expect(await mUsd.bAssetIndexes(token.address)).eq(i) const bAsset = await mUsd.getBasset(token.address) @@ -292,38 +280,19 @@ const validateNewMassetStorage = async (mUsd: MusdV3 | Masset, validator: string } // Swaps two tokens in an attempted rebalance -const balanceBasset = async ( - mUsdV2: Contract, - scaledVaultBalances: BN[], - scaledTargetBalance: BN, - inputToken: Token, - outputToken: Token, -): Promise => { +const balanceBasset = async (mUsdV2: Contract, inputToken: Token, inputQuantity: BN, outputToken: Token): Promise => { const { whaleAddress } = inputToken const signer = await impersonate(whaleAddress) - // scaled target weight - input scaled balance - const inputDiffToTarget = scaledTargetBalance.sub(scaledVaultBalances[inputToken.index]) - // output scaled balance - scaled target weight - // If output is TUSD then simply set the baseline to 0 - scaledTargetBalance = outputToken.symbol === "TUSD" ? BN.from(0) : scaledTargetBalance - const outputDiffToTarget = scaledVaultBalances[outputToken.index].sub(scaledTargetBalance) - const minBassetAmount = inputDiffToTarget.lt(outputDiffToTarget) ? inputDiffToTarget : outputDiffToTarget - if (minBassetAmount.lt(0)) return - const bAssetAmount = applyRatioMassetToBasset(minBassetAmount, BN.from(10).pow(26 - inputToken.decimals)) // Check the whale has enough input tokens const inputTokenContract = new ERC20__factory(signer).attach(inputToken.address) const whaleBalance = await inputTokenContract.balanceOf(whaleAddress) - expect(whaleBalance, `Whale ${inputToken.symbol} balance`).to.gte(bAssetAmount) + expect(whaleBalance, `Whale ${inputToken.symbol} balance`).to.gte(inputQuantity) // whale approves input tokens - await inputTokenContract.approve(mUsdV2.address, whaleAddress) - - const tx = mUsdV2.connect(signer).swap(inputToken.address, outputToken.address, bAssetAmount, whaleAddress) + await inputTokenContract.approve(mUsdV2.address, inputQuantity) + const tx = mUsdV2.connect(signer).swap(inputToken.address, outputToken.address, inputQuantity, whaleAddress) await expect(tx).to.emit(mUsdV2, "Swapped") - scaledVaultBalances[inputToken.index] = scaledVaultBalances[inputToken.index].add(minBassetAmount) - // this is not 100% accurate as the outputs are less fees but it's close enough for testing - scaledVaultBalances[outputToken.index] = scaledVaultBalances[outputToken.index].sub(minBassetAmount) } /** @@ -418,20 +387,6 @@ describe("mUSD V2.0 to V3.0", () => { it("gets deployed mUSD impl", async () => { mUsdV3 = await getMusdv3(deployer) }) - it("checks the proposed upgrade data", async () => { - const data = await mUsdV3.impl.interface.encodeFunctionData("upgrade", [validatorAddress, defaultConfig]) - - const request = await delayedProxyAdmin.requests(mUsdProxyAddress) - expect(request.data).eq(data) - expect(request.implementation).eq(mUsdV3.impl.address) - }) - it("checks nexus address on deployed mUSD", async () => { - const assignedNexus = await mUsdV3.impl.nexus() - expect(assignedNexus).eq(nexusAddress) - }) - it("delays 6 days", async () => { - await increaseTime(ONE_DAY.mul(6).toNumber()) - }) }) /* @@ -448,105 +403,15 @@ describe("mUSD V2.0 to V3.0", () => { * ii) Mostly on 2.5 execution * iii) Final state pre-upgrade (everything in place & paused) */ - describe("STEP 2 - Achieve equilibrium weights & prep", () => { + describe.skip("STEP 2 - Achieve equilibrium weights & prep", () => { const scaledVaultBalances: BN[] = [] let scaledTargetBalance: BN before(async () => { const basketManagerFactory = new ContractFactory(BasketManagerV2Abi, BasketManagerV2Bytecode, governor) basketManager = basketManagerFactory.attach(basketManagerAddress) }) - it("migrates sUSD to AaveV2", async () => { - aaveV2 = await new AaveV2Integration__factory(deployer).deploy( - nexusAddress, - mUsdProxyAddress, - aaveAddress, - basketManagerAddress, - ) - await aaveV2.deployTransaction.wait() - await aaveV2.connect(governor).setPTokenAddress(sUSD.address, "0x6c5024cd4f8a59110119c56f8933403a539555eb") - await aaveV2.connect(governor).setPTokenAddress(USDT.address, "0x3ed3b47dd13ec9a98b44e6204a523e766b225811") - await basketManager.migrateBassets([sUSD.address], aaveV2.address) - }) - it("adds DAI to the basket", async () => { - // 2. Add DAI to basket - await basketManager.addBasset(DAI.address, "0xD55684f4369040C12262949Ff78299f2BC9dB735", false) - }) - it("should get bAssets to check current weights", async () => { - const { bAssets } = await basketManager.getBassets() - let scaledTotalVaultBalance = BN.from(0) - intermediaryBassets.forEach((token, i) => { - const scaledVaultBalance = applyDecimals(bAssets[i].vaultBalance, token.decimals) - scaledVaultBalances[i] = scaledVaultBalance - scaledTotalVaultBalance = scaledTotalVaultBalance.add(scaledVaultBalance) - expect(bAssets[i].vaultBalance).to.eq(token.vaultBalance) - }) - scaledTargetBalance = scaledTotalVaultBalance.div(4) - expect(scaledVaultBalances[0].mul(10000).div(scaledTotalVaultBalance)).to.eq(0) - expect(scaledVaultBalances[1].mul(10000).div(scaledTotalVaultBalance)).to.eq(42) - expect(scaledVaultBalances[2].mul(10000).div(scaledTotalVaultBalance)).to.eq(4494) - expect(scaledVaultBalances[3].mul(10000).div(scaledTotalVaultBalance)).to.eq(5463) - expect(scaledVaultBalances[4].mul(10000).div(scaledTotalVaultBalance)).to.eq(0) - }) - it("should update max weights to 25.01%", async () => { - await basketManager.setBasketWeights( - intermediaryBassets.map((token) => token.address), - [ - simpleToExactAmount(2501, 14), - simpleToExactAmount(2501, 14), - 0, - simpleToExactAmount(2501, 14), - simpleToExactAmount(2501, 14), - ], // 25.01% where 100% = 1e18 - ) - }) - it("collects interest one last time", async () => { - await savingsManager.collectAndDistributeInterest(mUsdV2.address) - }) - // Step 1. Swap DAI in for TUSD - // Step 2. Swap sUSD in for else - // Check: Taking DAI or sUSD out of the basket is not possible once in - it("should swap DAI for TUSD to balance DAI", async () => { - await balanceBasset(mUsdV2, scaledVaultBalances, scaledTargetBalance, intermediaryBassets[4], intermediaryBassets[2]) - }) - it("should not be possible to take out the DAI, aside from adding sUSD", async () => { - // mint not possible with others - // await expect(mUsdV2.mint(intermediaryBassets[1].address, simpleToExactAmount(1))).to.be.revertedWith("Pausable: paused") - // redeem into DAI not possible - // swap into DAI not possible - }) - it("should swap sUSD for TUSD to balance TUSD", async () => { - const whale = await impersonate(currentBassets[2].whaleAddress) - const inputTokenContract = new ERC20__factory(whale).attach(currentBassets[2].address) - await inputTokenContract.transfer("0x3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3", simpleToExactAmount(15000000, 18)) - await balanceBasset(mUsdV2, scaledVaultBalances, scaledTargetBalance, currentBassets[0], currentBassets[2]) - }) - it("should swap sUSD for USDT to balance sUSD", async () => { - await balanceBasset(mUsdV2, scaledVaultBalances, scaledTargetBalance, currentBassets[0], currentBassets[3]) - }) - it("should swap USDC for USDT to balance both USDC and USDT", async () => { - await balanceBasset(mUsdV2, scaledVaultBalances, scaledTargetBalance, currentBassets[1], currentBassets[3]) - }) - it("should not be possible to take out the sUSD", async () => { - // mint not possible with others - // redeem into sUSD not possible - // swap into sUSD not possible - }) - it("pauses BasketManager and SavingsManager", async () => { - // do the pause - await basketManager.pause() - await savingsManager.pause() - // check pause - expect(await basketManager.paused()).eq(true) - expect(await savingsManager.paused()).eq(true) - // check that nothing can be called - await expect(mUsdV2.mint(intermediaryBassets[0].address, simpleToExactAmount(1))).to.be.revertedWith("Pausable: paused") - await expect(savingsManager.collectAndStreamInterest(mUsdV2.address)).to.be.revertedWith("Pausable: paused") - }) - it("removes TUSD from the basket", async () => { - await basketManager.removeBasset(TUSD.address) - }) it("should have valid storage before upgrade", async () => { - await validateTokenStorage(mUsdV2, "45324893805990774527261941") + await validateTokenStorage(mUsdV2) await validateUnchangedMassetStorage(mUsdV2, "1") // bAsset personal data // Get new vault balances after the bAssets have been balanced @@ -571,18 +436,19 @@ describe("mUSD V2.0 to V3.0", () => { * v) Do some admin operations */ describe("STEP 3 - Upgrading mUSD", () => { - before("elapse the time", async () => { - await increaseTime(ONE_DAY.mul(2)) - }) describe("accept proposal and verify storage", () => { - it("Should upgrade balanced mUSD", async () => { + // TODO - exec IRL then remove + it.skip("Should upgrade balanced mUSD", async () => { // Approve and execute call to upgradeToAndCall on mUSD proxy which then calls migrate on the new mUSD V3 implementation - await delayedProxyAdmin.acceptUpgradeRequest(mUsdProxyAddress) - + const tx = await delayedProxyAdmin.acceptUpgradeRequest(mUsdProxyAddress) + const receipt = await tx.wait() + console.log(`acceptUpgradeRequest gas used ${receipt.gasUsed}`) + }) + it("Should have proper storage", async () => { // validate after the upgrade - await validateTokenStorage(mUsdV3.proxy, "45324893805990774527261941") + await validateTokenStorage(mUsdV3.proxy) await validateUnchangedMassetStorage(mUsdV3.proxy, "1") - await validateNewMassetStorage(mUsdV3.proxy, validatorAddress, aaveV2.address, balancedVaultBalances) + await validateNewMassetStorage(mUsdV3.proxy, validatorAddress, balancedVaultBalances) }) it("blocks mint/swap/redeem", async () => { // mint/swap = Unhealthy @@ -766,9 +632,6 @@ describe("mUSD V2.0 to V3.0", () => { it("should collect interest after upgrade", async () => { await savingsManager.collectAndDistributeInterest(mUsdV3.proxy.address) }) - it("should stream interest after upgrade", async () => { - await savingsManager.collectAndStreamInterest(mUsdV3.proxy.address) - }) }) }) }) diff --git a/test-utils/assertions.ts b/test-utils/assertions.ts index 6a87f329..989318b2 100644 --- a/test-utils/assertions.ts +++ b/test-utils/assertions.ts @@ -36,7 +36,6 @@ export const assertBNClosePercent = (a: BN, b: BN, variance: string | number = " .mul(2) .mul(fullScale) .div(a.add(b)) - console.log(a.toString(), b.toString()) assert.ok( diff.lte(varianceBN), `Numbers exceed ${variance}% diff (Delta between a and b is ${diff.toString()}%, but variance was only ${varianceBN.toString()})`, diff --git a/tsconfig.json b/tsconfig.json index 85c662fb..b5bf6e78 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,5 +28,5 @@ "./test-fork/**/*.ts", "./test-utils/**/*.ts" ], - "files": ["./hardhat.config.ts", "./hardhat-fork.config.ts", "./tasks.config.ts"] + "files": ["./hardhat.config.ts", "./hardhat-fork.config.ts", "./tasks.config.ts", "./tasks-fork.config.ts"] } \ No newline at end of file