@mstable/protocol
Version:
mStable Contracts
666 lines • 37.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAlcxTokens = exports.getAaveTokens = exports.getCompTokens = exports.quoteSwap = exports.getCollectedInterest = exports.getLiquidatorInterest = exports.outputFees = exports.calcApy = exports.getMultiRedemptions = exports.getRedemptions = exports.getSwaps = exports.getMultiMints = exports.getMints = exports.getBalances = exports.getBasket = exports.snapSave = exports.snapConfig = exports.getBlockRange = exports.getBlock = exports.isMusdLegacy = exports.isMusdEth = exports.isFeederPool = void 0;
const constants_1 = require("@utils/constants");
const math_1 = require("@utils/math");
const utils_1 = require("ethers/lib/utils");
const generated_1 = require("types/generated");
const uniswap_1 = require("@utils/peripheral/uniswap");
const AaveStakedTokenV2__factory_1 = require("types/generated/factories/AaveStakedTokenV2__factory");
const Comptroller__factory_1 = require("types/generated/factories/Comptroller__factory");
const quantity_formatters_1 = require("./quantity-formatters");
const tokens_1 = require("./tokens");
const networkAddressFactory_1 = require("./networkAddressFactory");
const comptrollerAddress = "0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B";
const uniswapEthToken = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
// Only the FeederPool has the redeemProportionately function
function isFeederPool(asset) {
return asset.redeemProportionately !== undefined;
}
exports.isFeederPool = isFeederPool;
// Only the mUSD deployed to Ethereum mainnet has the surplus function
function isMusdEth(asset) {
return asset.surplus !== undefined;
}
exports.isMusdEth = isMusdEth;
// mUSD before upgrade to the MusdV3 contract 0x15B2838Cd28cc353Afbe59385db3F366D8945AEe at block 12094376
// mUSD implementations are
// Initialized at block 10148035 to 0xB83A5a51df21321b365c918832E7E8f5DE686f7E
// Upgraded at block 10463013 to 0xE4c5b1765BF420016027177289908C5A3Ea7668E
// Upgraded at block 11516027 to 0xE0d0D052d5B1082E52C6b8422Acd23415c3DF1c4
// Upgraded at block 12094376 to 0x15B2838Cd28cc353Afbe59385db3F366D8945AEe
function isMusdLegacy(asset) {
return asset.getBasketManager !== undefined;
}
exports.isMusdLegacy = isMusdLegacy;
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const getBlock = async (ethers, _blockNumber) => {
const blockNumber = _blockNumber || (await ethers.provider.getBlockNumber());
const toBlock = await ethers.provider.getBlock(blockNumber);
const blockTime = new Date(toBlock.timestamp * 1000);
return {
blockNumber,
blockTime,
};
};
exports.getBlock = getBlock;
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const getBlockRange = async (ethers, fromBlockNumber, _toBlockNumber) => {
const toBlockNumber = _toBlockNumber || (await ethers.provider.getBlockNumber());
// const toBlock = await ethers.provider.getBlock(toBlockNumber)
// const endTime = new Date(toBlock.timestamp * 1000)
const toBlock = await exports.getBlock(ethers, _toBlockNumber);
const fromBlock = await exports.getBlock(ethers, fromBlockNumber);
console.log(`Between blocks ${fromBlock.blockNumber} and ${toBlockNumber}. ${fromBlock.blockTime} and ${toBlock.blockTime}`);
return {
fromBlock,
toBlock,
};
};
exports.getBlockRange = getBlockRange;
const snapConfig = async (asset, toBlock) => {
let ampData;
if (isMusdLegacy(asset))
return;
if (isMusdEth(asset)) {
ampData = await asset.ampData();
}
else {
const fpData = await asset.data();
ampData = fpData.ampData;
}
const conf = await asset.getConfig({
blockTag: toBlock,
});
console.log(`\nAmplification coefficient (A): ${utils_1.formatUnits(conf.a, 2)}`);
const startDate = new Date(ampData.rampStartTime.toNumber() * 1000);
const endDate = new Date(ampData.rampEndTime.toNumber() * 1000);
if (startDate.valueOf() !== endDate.valueOf()) {
console.log(`Ramp A: initial ${utils_1.formatUnits(ampData.initialA, 2)}; target ${utils_1.formatUnits(ampData.targetA, 2)}`);
console.log(`Ramp A: start ${startDate}; end ${endDate}`);
}
console.log(`Weights: min ${utils_1.formatUnits(conf.limits.min, 16)}% max ${utils_1.formatUnits(conf.limits.max, 16)}%`);
};
exports.snapConfig = snapConfig;
const snapSave = async (symbol, signer, chain, toBlock) => {
const savingContractAddress = networkAddressFactory_1.resolveAddress(symbol, chain, "savings");
const savingsContract = new generated_1.SavingsContract__factory(signer).attach(savingContractAddress);
const exchangeRate = await savingsContract.exchangeRate({
blockTag: toBlock,
});
console.log(`\nSave rate ${utils_1.formatUnits(exchangeRate)}`);
};
exports.snapSave = snapSave;
const getTvlCap = async (signer, tvlConfig, toBlock) => {
const validator = await new generated_1.ValidatorWithTVLCap__factory(signer).attach(tvlConfig.invariantValidatorAddress);
const tvlStartTime = await validator.startTime({
blockTag: toBlock,
});
const weeksSinceLaunch = math_1.BN.from(Date.now()).div(1000).sub(tvlStartTime).mul(constants_1.fullScale).div(604800);
// // e.g. 1e19 + (15e18 * 2.04e36) = 1e19 + 3.06e55
// // startingCap + (capFactor * weeksSinceLaunch**2 / 1e36);
return tvlConfig.startingCap.add(tvlConfig.capFactor.mul(weeksSinceLaunch.pow(2)).div(constants_1.fullScale.pow(2)));
};
const getBasket = async (asset, bAssetSymbols, mAssetName = "mBTC", quantityFormatter, toBlock, tvlConfig, exposedLogic) => {
if (isMusdLegacy(asset))
return;
const bAssets = await asset.getBassets({
blockTag: toBlock,
});
const bAssetTotals = [];
let bAssetsTotal = math_1.BN.from(0);
bAssetSymbols.forEach((_, i) => {
let scaledBassetQuantity;
if (isMusdEth(asset)) {
scaledBassetQuantity = math_1.applyRatio(bAssets[1][i].vaultBalance, bAssets[1][i].ratio);
}
else if (isFeederPool(asset)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
scaledBassetQuantity = math_1.applyRatio(bAssets.vaultData[i].vaultBalance, bAssets.vaultData[i].ratio);
}
else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
scaledBassetQuantity = math_1.applyRatio(bAssets.bData[i].vaultBalance, bAssets.bData[i].ratio);
}
bAssetTotals.push(scaledBassetQuantity);
bAssetsTotal = bAssetsTotal.add(scaledBassetQuantity);
});
console.log(`\n${mAssetName} basket`);
bAssetSymbols.forEach((symbol, i) => {
const percentage = bAssetTotals[i].mul(100).div(bAssetsTotal);
console.log(` ${symbol.padEnd(7)} ${quantityFormatter(bAssetTotals[i]).padEnd(20)} ${percentage.toString().padStart(2)}%`);
});
let mAssetSurplus = math_1.BN.from(0);
if (isMusdEth(asset)) {
mAssetSurplus = await asset.surplus({
blockTag: toBlock,
});
}
else if (!isFeederPool(asset)) {
mAssetSurplus = (await asset.data({
blockTag: toBlock,
})).surplus;
}
const mAssetSupply = await asset.totalSupply({
blockTag: toBlock,
});
console.log(`Surplus ${utils_1.formatUnits(mAssetSurplus)}`);
console.log(`${mAssetName} ${quantityFormatter(mAssetSupply)}`);
const mAssetTotal = mAssetSupply.add(mAssetSurplus);
if (exposedLogic) {
const config = {
supply: mAssetSupply,
...(await asset.getConfig({
blockTag: toBlock,
})),
recolFee: 0,
};
const k = await exposedLogic.getK(bAssets[1], config);
console.log(`Total (K) ${utils_1.formatUnits(k)}`);
// Sum of base assets less mAsset total supply less mAsset surplus
const bAssetMassetDiff = k.sub(mAssetTotal);
const bAssetMassetDiffBasisPoints = bAssetMassetDiff.mul(10000).div(mAssetTotal);
console.log(`Total ${mAssetName} ${utils_1.formatUnits(mAssetTotal)} (${utils_1.formatUnits(bAssetMassetDiff)} ${bAssetMassetDiffBasisPoints}bps over-collateralised)`);
}
if (tvlConfig) {
const tvlCap = await getTvlCap(asset.signer, tvlConfig, toBlock);
const tvlCapPercentage = bAssetsTotal.mul(100).div(tvlCap);
console.log(`TVL cap ${quantityFormatter(tvlCap)} ${tvlCapPercentage}%`);
}
};
exports.getBasket = getBasket;
const getBalances = async (mAsset, accounts, quantityFormatter, toBlock) => {
if (isMusdLegacy(mAsset)) {
return {
total: math_1.BN.from(0),
save: math_1.BN.from(0),
earn: math_1.BN.from(0),
};
}
const mAssetBalance = await mAsset.totalSupply({
blockTag: toBlock,
});
console.log("\nHolders");
let balanceSum = math_1.BN.from(0);
const balances = [];
for (const account of accounts) {
const balance = await mAsset.balanceOf(account.address, {
blockTag: toBlock,
});
console.log(`${account.name.padEnd(26)} ${quantityFormatter(balance)} ${balance.mul(100).div(mAssetBalance)}%`);
balanceSum = balanceSum.add(balance);
balances.push(balance);
}
const otherBalances = mAssetBalance.sub(balanceSum);
console.log(`${"Other".padEnd(26)} ${quantityFormatter(otherBalances)} ${otherBalances.mul(100).div(mAssetBalance)}%`);
const surplus = isMusdEth(mAsset)
? await mAsset.surplus({
blockTag: toBlock,
})
: (await mAsset.data({
blockTag: toBlock,
})).surplus;
console.log(`Surplus ${quantityFormatter(surplus)}`);
console.log(`Total ${quantityFormatter(mAssetBalance)}`);
return {
total: mAssetBalance,
save: balances[0],
earn: balances[1],
};
};
exports.getBalances = getBalances;
const getMints = async (bAssets, mAsset, fromBlock, toBlock, quantityFormatter) => {
const filter = mAsset.filters.Minted(null, null, null, null, null);
const logs = await mAsset.queryFilter(filter, fromBlock, toBlock);
console.log("\nMints");
console.log("Block#\t Tx hash\t\t\t\t\t\t\t bAsset Quantity");
let total = math_1.BN.from(0);
let count = 0;
logs.forEach((log) => {
const inputBasset = bAssets.find((b) => b.address === log.args.input);
if (!inputBasset) {
throw Error(`Failed to find bAsset with address ${log.args.input}`);
}
// mAssetQuantity is for Masset. output is for FeederPool
const quantity = log.args.mAssetQuantity || log.args.output;
console.log(`${log.blockNumber} ${log.transactionHash} ${inputBasset.symbol.padEnd(4)} ${quantityFormatter(quantity)}`);
total = total.add(quantity);
count += 1;
});
console.log(`Count ${count}, Total ${quantityFormatter(total)}`);
return {
count,
total,
fees: math_1.BN.from(0),
};
};
exports.getMints = getMints;
const getMultiMints = async (bAssets, mAsset, fromBlock, toBlock, quantityFormatter) => {
const filter = mAsset.filters.MintedMulti(null, null, null, null, null);
const logs = await mAsset.queryFilter(filter, fromBlock, toBlock);
console.log("\nMulti Mints");
console.log("Block#\t Tx hash\t\t\t\t\t\t\t\t Quantity");
let total = math_1.BN.from(0);
let count = 0;
logs.forEach((log) => {
// Ignore MintMulti events from collectInterest and collectPlatformInterest
if (!log.args.inputs.length)
return;
const inputBassets = log.args.inputs.map((input) => bAssets.find((b) => b.address === input));
// mAssetQuantity is for Masset. output is for FeederPool
const quantity = log.args.mAssetQuantity || log.args.output;
console.log(`${log.blockNumber} ${log.transactionHash} ${quantityFormatter(quantity)}`);
inputBassets.forEach((bAsset, i) => {
console.log(` ${bAsset.symbol.padEnd(4)} ${quantityFormatter(log.args.inputQuantities[i], bAsset.decimals)}`);
});
total = total.add(quantity);
count += 1;
});
console.log(`Count ${count}, Total ${quantityFormatter(total)}`);
return {
count,
total,
fees: math_1.BN.from(0),
};
};
exports.getMultiMints = getMultiMints;
const getSwaps = async (bAssets, mAsset, fromBlock, toBlock, quantityFormatter) => {
const filter = mAsset.filters.Swapped(null, null, null, null, null, null);
const logs = await mAsset.queryFilter(filter, fromBlock, toBlock);
console.log("\nSwaps");
console.log("Block#\t Tx hash\t\t\t\t\t\t\t Input Output Quantity Fee");
// Scaled bAsset quantities
let total = math_1.BN.from(0);
let fees = math_1.BN.from(0);
let count = 0;
logs.forEach((log) => {
const inputBasset = bAssets.find((b) => b.address === log.args.input);
const outputBasset = bAssets.find((b) => b.address === log.args.output);
const fee = log.args.scaledFee || log.args.fee;
console.log(`${log.blockNumber} ${log.transactionHash} ${inputBasset.symbol.padEnd(4)} ${outputBasset.symbol.padEnd(4)} ${quantityFormatter(log.args.outputAmount, outputBasset.decimals)} ${quantityFormatter(fee, 18, 8)}`);
total = total.add(math_1.applyDecimals(log.args.outputAmount, outputBasset.decimals));
fees = fees.add(fee);
count += 1;
});
console.log(`Count ${count}, Total ${quantityFormatter(total)}`);
return {
count,
total,
fees,
};
};
exports.getSwaps = getSwaps;
const getRedemptions = async (bAssets, mAsset, fromBlock, toBlock, quantityFormatter) => {
const filter = mAsset.filters.Redeemed(null, null, null, null, null, null);
const logs = await mAsset.queryFilter(filter, fromBlock, toBlock);
console.log("\nRedemptions");
console.log("Block#\t Tx hash\t\t\t\t\t\t\t bAsset Quantity Fee");
let total = math_1.BN.from(0);
let fees = math_1.BN.from(0);
let count = 0;
logs.forEach((log) => {
const outputBasset = bAssets.find((b) => b.address === log.args.output);
console.log(`${log.blockNumber} ${log.transactionHash} ${outputBasset.symbol.padEnd(4)} ${quantityFormatter(log.args.mAssetQuantity)} ${quantityFormatter(log.args.scaledFee, 18, 8)}`);
total = total.add(log.args.mAssetQuantity);
fees = fees.add(log.args.scaledFee);
count += 1;
});
console.log(`Count ${count}, Total ${quantityFormatter(total)}`);
return {
count,
total,
fees,
};
};
exports.getRedemptions = getRedemptions;
const getMultiRedemptions = async (bAssets, mAsset, fromBlock, toBlock, quantityFormatter) => {
if (isMusdLegacy(mAsset)) {
return {
count: 0,
total: math_1.BN.from(0),
fees: math_1.BN.from(0),
};
}
const filter = mAsset.filters.RedeemedMulti(null, null, null, null, null, null);
const logs = await mAsset.queryFilter(filter, fromBlock, toBlock);
console.log("\nMulti Redemptions");
console.log("Block#\t Tx hash\t\t\t\t\t\t\t\t Quantity Fee");
let total = math_1.BN.from(0);
let fees = math_1.BN.from(0);
let count = 0;
logs.forEach((log) => {
const outputBassets = log.args.outputs.map((output) => bAssets.find((b) => b.address === output));
console.log(`${log.blockNumber} ${log.transactionHash} ${quantityFormatter(log.args.mAssetQuantity)} ${quantityFormatter(log.args.scaledFee, 18, 8)}`);
outputBassets.forEach((bAsset, i) => {
console.log(` ${bAsset.symbol.padEnd(4)} ${quantityFormatter(log.args.outputQuantity[i], bAsset.decimals)}`);
});
total = total.add(log.args.mAssetQuantity);
fees = fees.add(log.args.scaledFee);
count += 1;
});
console.log(`Count ${count}, Total ${quantityFormatter(total)}`);
return {
count,
total,
fees,
};
};
exports.getMultiRedemptions = getMultiRedemptions;
// Returns the APY in basis points which is the percentage to 2 decimal places
const calcApy = (startTime, endTime, quantity, saveBalance) => {
const periodSeconds = math_1.BN.from(endTime.valueOf() - startTime.valueOf()).div(1000);
return quantity.mul(10000).mul(constants_1.ONE_YEAR).div(saveBalance).div(periodSeconds);
};
exports.calcApy = calcApy;
const outputFees = (mints, multiMints, swaps, redeems, multiRedeems, balances, startTime, endTime, quantityFormatter) => {
const totalFees = redeems.fees.add(multiRedeems.fees).add(swaps.fees);
if (totalFees.eq(0)) {
console.log(`\nNo fees since ${startTime}`);
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}`);
console.log(" # Volume Fees %");
console.log(`Mints ${mints.count.toString().padEnd(2)} ${quantityFormatter(mints.total)} ${quantityFormatter(mints.fees, 18, 9)} ${mints.fees.mul(100).div(totalFees).toString().padStart(3)}%`);
console.log(`Multi Mints ${multiMints.count.toString().padEnd(2)} ${quantityFormatter(multiMints.total)} ${quantityFormatter(multiMints.fees, 18, 9)} ${multiMints.fees.mul(100).div(totalFees).toString().padStart(3)}%`);
console.log(`Redeems ${redeems.count.toString().padEnd(2)} ${quantityFormatter(redeems.total)} ${quantityFormatter(redeems.fees, 18, 9)} ${redeems.fees.mul(100).div(totalFees).toString().padStart(3)}%`);
console.log(`Multi Redeems ${multiRedeems.count.toString().padEnd(2)} ${quantityFormatter(multiRedeems.total)} ${quantityFormatter(multiRedeems.fees, 18, 9)} ${multiRedeems.fees.mul(100).div(totalFees).toString().padStart(3)}%`);
console.log(`Swaps ${swaps.count.toString().padEnd(2)} ${quantityFormatter(swaps.total)} ${quantityFormatter(swaps.fees, 18, 9)} ${swaps.fees.mul(100).div(totalFees).toString().padStart(3)}%`);
const totalApy = exports.calcApy(startTime, endTime, totalFees, balances.save);
const liquidityUtilization = totalFeeTransactions.mul(100).div(balances.total);
console.log(`Total Txs ${quantityFormatter(totalTransactions)}`);
console.log(`Savings ${quantityFormatter(balances.save)} ${quantityFormatter(totalFees, 18, 9)} APY ${utils_1.formatUnits(totalApy, 2)}%`);
console.log(`${liquidityUtilization}% liquidity utilization (${quantityFormatter(totalFeeTransactions)} of ${quantityFormatter(balances.total)} mAssets)`);
};
exports.outputFees = outputFees;
const getLiquidatorInterest = async (mAsset, savingsManager, fromBlock, toBlock, quantityFormatter) => {
const filter = savingsManager.filters.LiquidatorDeposited(mAsset.address, null);
const logs = await savingsManager.queryFilter(filter, fromBlock.blockNumber, toBlock.blockNumber);
let total = math_1.BN.from(0);
let count = 0;
logs.forEach((log) => {
console.log(`${log.blockNumber} ${log.transactionHash} ${quantityFormatter(log.args.amount)}`);
count += 1;
total = total.add(log.args.amount);
});
return { total, count };
};
exports.getLiquidatorInterest = getLiquidatorInterest;
const getCollectedInterest = async (bAssets, mAsset, savingsManager, fromBlock, toBlock, quantityFormatter, savingsBalance) => {
// Get MintedMulti events where the mAsset is the minter
const filter = mAsset.filters.MintedMulti(mAsset.address, null, null, null, null);
const logs = await mAsset.queryFilter(filter, fromBlock.blockNumber, toBlock.blockNumber);
console.log(`\nCollected Interest between ${fromBlock.blockTime} and ${toBlock.blockTime}`);
console.log("Block#\t Tx hash\t\t\t\t\t\t\t\t Quantity");
let total = math_1.BN.from(0);
let tradingFees = math_1.BN.from(0);
let countTradingFees = 0;
const platformFees = bAssets.map(() => math_1.BN.from(0));
let totalPlatformInterest = math_1.BN.from(0);
let countPlatformInterest = 0;
let count = 0;
logs.forEach((log) => {
// Ignore MintMulti events not from collectInterest and collectPlatformInterest
if (log.args.inputs.length)
return;
// Calculate the quantity of interest collected
// For mAssets:
// - Trading fees = mAssetQuantity
// - Platform fees = mAssetQuantity
// For Feeder Pools:
// - Trading fees = log.args.output
// - Platform fees = sum of the input quantities as log.args.output is 0
let quantity = math_1.BN.from(0);
if (log.args.mAssetQuantity !== undefined) {
quantity = log.args.mAssetQuantity;
}
else if (log.args.output && log.args.output.gt(0)) {
quantity = log.args.output;
}
else {
quantity = log.args.inputQuantities.reduce((sum, input, i) => {
const scaledFee = math_1.applyDecimals(input, bAssets[i].decimals);
platformFees[i] = platformFees[i].add(scaledFee);
return sum.add(scaledFee);
}, math_1.BN.from(0));
}
console.log(`${log.blockNumber} ${log.transactionHash} ${quantityFormatter(quantity)}`);
if (log.args.inputQuantities.length) {
countPlatformInterest += 1;
totalPlatformInterest = totalPlatformInterest.add(quantity);
log.args.inputQuantities.forEach((inputQuantity, i) => {
var _a;
console.log(` ${bAssets[i].symbol.padEnd(4)} ${quantityFormatter(inputQuantity, ((_a = bAssets[i]) === null || _a === void 0 ? void 0 : _a.decimals) || 18)}`);
});
}
else {
countTradingFees += 1;
tradingFees = tradingFees.add(quantity);
}
total = total.add(quantity);
count += 1;
});
const { total: liquidatorInterest, count: countLiquidator } = await exports.getLiquidatorInterest(mAsset, savingsManager, fromBlock, toBlock, quantityFormatter);
total = total.add(liquidatorInterest);
if (total.eq(0)) {
console.log("No interest was collected");
return {
count,
total,
fees: math_1.BN.from(0),
};
}
const tradingFeesApy = exports.calcApy(fromBlock.blockTime, toBlock.blockTime, tradingFees, savingsBalance);
console.log(`Trading fees ${quantityFormatter(tradingFees)} ${utils_1.formatUnits(tradingFees.mul(10000).div(total), 2)}% ${utils_1.formatUnits(tradingFeesApy, 2)}APY`);
const totalPlatformApy = exports.calcApy(fromBlock.blockTime, toBlock.blockTime, totalPlatformInterest, savingsBalance);
console.log(`Platform interest ${quantityFormatter(totalPlatformInterest)} ${utils_1.formatUnits(totalPlatformInterest.mul(10000).div(total), 2)}% ${utils_1.formatUnits(totalPlatformApy, 2)}APY`);
// Avoid div by 0
totalPlatformInterest = totalPlatformInterest.gt(0) ? totalPlatformInterest : math_1.BN.from(1);
bAssets.forEach((bAsset, i) => {
const platformFeeApy = exports.calcApy(fromBlock.blockTime, toBlock.blockTime, platformFees[i], savingsBalance);
console.log(` ${bAsset.symbol.padEnd(4)} ${quantityFormatter(platformFees[i])} ${utils_1.formatUnits(platformFees[i].mul(10000).div(totalPlatformInterest), 2)}% ${utils_1.formatUnits(platformFeeApy, 2)}APY`);
});
const totalLiquidatorApy = exports.calcApy(fromBlock.blockTime, toBlock.blockTime, liquidatorInterest, savingsBalance);
console.log(`Platform rewards ${quantityFormatter(liquidatorInterest)} ${utils_1.formatUnits(liquidatorInterest.mul(10000).div(total), 2)}% ${utils_1.formatUnits(totalLiquidatorApy, 2)}APY`);
const totalApy = exports.calcApy(fromBlock.blockTime, toBlock.blockTime, total, savingsBalance);
console.log(`Total interest ${quantityFormatter(total)} ${utils_1.formatUnits(totalApy, 2)}APY`);
console.log(`Interest collections: ${countTradingFees} trading fee, ${countPlatformInterest} platform interest, ${countLiquidator} liquidator`);
return {
count,
total,
fees: math_1.BN.from(0),
};
};
exports.getCollectedInterest = getCollectedInterest;
const quoteSwap = async (signer, from, to, inAmount, toBlock, path, fees = [3000, 3000]) => {
// Get USDC value from Uniswap
const uniswapPath = path || [from.address, uniswapEthToken, to.address];
let outAmount;
if (toBlock.blockNumber > 12364832) {
// Use Uniswap V3
const encodedPath = uniswap_1.encodeUniswapPath(uniswapPath, fees);
const quoter = generated_1.IUniswapV3Quoter__factory.connect("0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6", signer);
outAmount = await quoter.callStatic.quoteExactInput(encodedPath.encoded, inAmount, { blockTag: toBlock.blockNumber });
}
else {
// Use Uniswap v2
const router = generated_1.IUniswapV2Router02__factory.connect("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", signer);
const output = await router.getAmountsOut(inAmount, uniswapPath, { blockTag: toBlock.blockNumber });
outAmount = output[2];
}
// exchange rate = out amount / 10**(out decimals) / in amount * (10**to decimals)
const exchangeRate = outAmount.div(math_1.simpleToExactAmount(1, to.decimals)).mul(math_1.simpleToExactAmount(1, from.decimals)).div(inAmount);
return { outAmount, exchangeRate };
};
exports.quoteSwap = quoteSwap;
const getCompTokens = async (signer, toBlock, quantityFormatter = quantity_formatters_1.usdFormatter, chain = tokens_1.Chain.mainnet) => {
const comptroller = Comptroller__factory_1.Comptroller__factory.connect(comptrollerAddress, signer);
const compToken = generated_1.ERC20__factory.connect(tokens_1.COMP.address, signer);
let totalComp = math_1.BN.from(0);
console.log(`\nCOMP accrued`);
// Get COMP that can be claimed
const compAccrued = await comptroller.compAccrued(tokens_1.USDC.integrator, { blockTag: toBlock.blockNumber });
totalComp = totalComp.add(compAccrued);
console.log(`USDC ${quantityFormatter(compAccrued)}`);
// Get COMP in mUSD integration
const compIntegrationBal = await compToken.balanceOf(tokens_1.USDC.integrator, { blockTag: toBlock.blockNumber });
totalComp = totalComp.add(compIntegrationBal);
console.log(`Integration ${quantityFormatter(compIntegrationBal)}`);
// Get COMP in mUSD liquidator
const liquidatorAddress = networkAddressFactory_1.getChainAddress("Liquidator", chain);
const compLiquidatorBal = await compToken.balanceOf(liquidatorAddress, { blockTag: toBlock.blockNumber });
totalComp = totalComp.add(compLiquidatorBal);
console.log(`Liquidator ${quantityFormatter(compLiquidatorBal)}`);
const compUsdc = totalComp.gt(0)
? await exports.quoteSwap(signer, tokens_1.COMP, tokens_1.USDC, totalComp, toBlock)
: { outAmount: math_1.BN.from(0), exchangeRate: math_1.BN.from(0) };
console.log(`Total ${quantityFormatter(totalComp)} ${quantityFormatter(compUsdc.outAmount, tokens_1.USDC.decimals)} USDC`);
console.log(`COMP/USDC exchange rate: ${compUsdc.exchangeRate}`);
const liquidator = await generated_1.Liquidator__factory.connect(liquidatorAddress, signer);
const liqData = await liquidator.liquidations(tokens_1.USDC.integrator);
console.log(`Min COMP/USDC rate ${utils_1.formatUnits(liqData.minReturn, tokens_1.USDC.decimals)}`);
const nextRunFromTimestamp = liqData.lastTriggered.add(constants_1.ONE_WEEK);
console.log(`Next run ${new Date(nextRunFromTimestamp.toNumber() * 1000)}`);
};
exports.getCompTokens = getCompTokens;
const getAaveTokens = async (signer, toBlock, quantityFormatter = quantity_formatters_1.usdFormatter, chain = tokens_1.Chain.mainnet) => {
const stkAaveToken = AaveStakedTokenV2__factory_1.AaveStakedTokenV2__factory.connect(tokens_1.stkAAVE.address, signer);
const aaveToken = generated_1.ERC20__factory.connect(tokens_1.AAVE.address, signer);
const aaveIncentivesAddress = networkAddressFactory_1.getChainAddress("AaveIncentivesController", chain);
const aaveIncentives = generated_1.IAaveIncentivesController__factory.connect(aaveIncentivesAddress, signer);
const liquidatorAddress = networkAddressFactory_1.getChainAddress("Liquidator", chain);
const liquidator = await generated_1.Liquidator__factory.connect(liquidatorAddress, signer);
let totalStkAaveAndAave = math_1.BN.from(0);
if (toBlock.blockNumber <= 12319489) {
console.log(`\nbefore stkAAVE`);
return;
}
// Get accrued stkAave for each integration contract that is still to be claimed from the controller
console.log(`\nstkAAVE accrued and unclaimed`);
let totalUnclaimed = math_1.BN.from(0);
const integrationTokens = [[tokens_1.DAI, tokens_1.USDT, tokens_1.sUSD], [tokens_1.GUSD], [tokens_1.BUSD], [tokens_1.RAI], [tokens_1.FEI], [tokens_1.WBTC]];
for (const bAssets of integrationTokens) {
const bAssetSymbols = bAssets.reduce((symbols, token) => `${symbols}${token.symbol} `, "");
const aTokens = bAssets.map((t) => t.liquidityProvider);
const accruedBal = await aaveIncentives.getRewardsBalance(aTokens, bAssets[0].integrator, {
blockTag: toBlock.blockNumber,
});
totalUnclaimed = totalUnclaimed.add(accruedBal);
console.log(`${bAssetSymbols.padEnd(16)} ${quantityFormatter(accruedBal)}`);
}
console.log(`Total ${quantityFormatter(totalUnclaimed)}`);
totalStkAaveAndAave = totalStkAaveAndAave.add(totalUnclaimed);
// Get stkAAVE balances in liquidators
console.log(`\nstkAAVE claimed by integrations`);
let totalClaimedstkAave = math_1.BN.from(0);
for (const bAssets of integrationTokens) {
const bAssetSymbols = bAssets.reduce((symbols, token) => `${symbols}${token.symbol} `, "");
const integrationData = await liquidator.liquidations(bAssets[0].integrator, { blockTag: toBlock.blockNumber });
totalClaimedstkAave = totalClaimedstkAave.add(integrationData.aaveBalance);
console.log(`${bAssetSymbols.padEnd(16)} ${quantityFormatter(integrationData.aaveBalance)}`);
}
console.log(`Total ${quantityFormatter(totalClaimedstkAave, 18, 6)}`);
const liquidatorTotalBalance = await liquidator.totalAaveBalance({ blockTag: toBlock.blockNumber });
console.log(`Total Aave Balance ${quantityFormatter(liquidatorTotalBalance, 18, 6)}`);
// Get stkAave and AAVE in liquidity manager
const liquidatorStkAaveBal = await stkAaveToken.balanceOf(liquidatorAddress, { blockTag: toBlock.blockNumber });
const liquidatorAaveBal = await aaveToken.balanceOf(liquidatorAddress, { blockTag: toBlock.blockNumber });
console.log(`\nLiquidator actual stkAAVE ${quantityFormatter(liquidatorStkAaveBal)}`);
console.log(`Liquidator actual AAVE ${quantityFormatter(liquidatorAaveBal)}`);
totalStkAaveAndAave = totalStkAaveAndAave.add(liquidatorStkAaveBal);
totalStkAaveAndAave = totalStkAaveAndAave.add(liquidatorAaveBal);
let aaveUsdc;
if (liquidatorStkAaveBal.gt(0)) {
aaveUsdc = await exports.quoteSwap(signer, tokens_1.AAVE, tokens_1.USDC, liquidatorStkAaveBal, toBlock);
console.log(`\nLiquidator ${quantityFormatter(liquidatorStkAaveBal)} ${quantityFormatter(aaveUsdc.outAmount, tokens_1.USDC.decimals)} USDC`);
}
else {
const reasonableAaveAmount = math_1.simpleToExactAmount(25);
aaveUsdc = await exports.quoteSwap(signer, tokens_1.AAVE, tokens_1.USDC, reasonableAaveAmount, toBlock);
console.log(`\nLiquidator ${quantityFormatter(liquidatorStkAaveBal)}`);
}
const totalUSDC = totalStkAaveAndAave.mul(aaveUsdc.exchangeRate).div(math_1.simpleToExactAmount(1, tokens_1.AAVE.decimals - tokens_1.USDC.decimals));
console.log(`Total ${quantityFormatter(totalStkAaveAndAave)} ${quantityFormatter(totalUSDC, tokens_1.USDC.decimals)} USDC`);
console.log(`AAVE/USDC exchange rate: ${aaveUsdc.exchangeRate}`);
// Get AAVE/USDC exchange rate
const musdLiqData = await liquidator.liquidations(tokens_1.USDT.integrator, { blockTag: toBlock.blockNumber });
console.log(`Min AAVE/USDC rate ${utils_1.formatUnits(musdLiqData.minReturn, tokens_1.USDC.decimals)}`);
// Get AAVE/GUSD exchange rate
const gusdLiqData = await liquidator.liquidations(tokens_1.GUSD.integrator, { blockTag: toBlock.blockNumber });
console.log(`Min AAVE/GUSD rate ${utils_1.formatUnits(gusdLiqData.minReturn, tokens_1.GUSD.decimals)}`);
// Get AAVE/BTC exchange rate
const btcLiqData = await liquidator.liquidations(tokens_1.WBTC.integrator, { blockTag: toBlock.blockNumber });
console.log(`Min AAVE/WBTC rate ${utils_1.formatUnits(btcLiqData.minReturn, tokens_1.WBTC.decimals)}`);
// Get next unlock window
const cooldownStart = await stkAaveToken.stakersCooldowns(liquidatorAddress, { blockTag: toBlock.blockNumber });
const cooldownEnd = cooldownStart.add(constants_1.ONE_DAY.mul(10));
const colldownEndDate = new Date(cooldownEnd.toNumber() * 1000);
console.log(`next stkAAVE unlock ${colldownEndDate} (${cooldownEnd})`);
// Get unclaimed rewards
const integrations = [
{
desc: "DAI, USDT & sUSD",
integrator: tokens_1.DAI.integrator,
},
{
desc: "GUSD",
integrator: tokens_1.GUSD.integrator,
},
{
desc: "WBTC",
integrator: tokens_1.WBTC.integrator,
},
];
console.log("\nstkAAVE unclaimed (no accrued)");
const unclaimedRewardsPromises = integrations.map((i) => aaveIncentives.getUserUnclaimedRewards(i.integrator, { blockTag: toBlock.blockNumber }));
const unclaimedRewards = await Promise.all(unclaimedRewardsPromises);
let totalUnclaimedRewards = math_1.BN.from(0);
integrations.forEach((integration, i) => {
console.log(`${integration.desc.padEnd(10)}${quantityFormatter(unclaimedRewards[i])}`);
totalUnclaimedRewards = totalUnclaimedRewards.add(unclaimedRewards[i]);
});
console.log(`Total ${quantityFormatter(totalUnclaimedRewards)}`);
// Get accrued stkAave for the old Aave V2 integration contract,
// which is still used by sUSD. No longer used by DAI and USDT
console.log(`\nstkAAVE unclaimable (integration contract can not claim)`);
const unclaimableRewards = await aaveIncentives.getUserUnclaimedRewards(tokens_1.sUSD.integrator, {
blockTag: toBlock.blockNumber,
});
console.log(`Old Aave V2 ${quantityFormatter(unclaimableRewards)}`);
};
exports.getAaveTokens = getAaveTokens;
const getAlcxTokens = async (signer, toBlock, quantityFormatter = quantity_formatters_1.usdFormatter, chain = tokens_1.Chain.mainnet) => {
const poolId = 0;
const alchemixStakingPoolsAddress = networkAddressFactory_1.getChainAddress("AlchemixStakingPool", chain);
const alchemixStakingPools = generated_1.IAlchemixStakingPools__factory.connect(alchemixStakingPoolsAddress, signer);
const alcxToken = generated_1.ERC20__factory.connect(tokens_1.ALCX.address, signer);
let totalComp = math_1.BN.from(0);
console.log(`\nALCX accrued`);
// Get ALCX that can be claimed
const alcxAccrued = await alchemixStakingPools.getStakeTotalUnclaimed(tokens_1.alUSD.integrator, poolId, { blockTag: toBlock.blockNumber });
totalComp = totalComp.add(alcxAccrued);
console.log(`alUSD ${quantityFormatter(alcxAccrued)}`);
// Get ALCX in Alchemix integration
const alchemixIntegrationBal = await alcxToken.balanceOf(tokens_1.alUSD.integrator, { blockTag: toBlock.blockNumber });
totalComp = totalComp.add(alchemixIntegrationBal);
console.log(`Integration ${quantityFormatter(alchemixIntegrationBal)}`);
// Get ALCX in Liquidator
const liquidatorAddress = networkAddressFactory_1.getChainAddress("Liquidator", chain);
const compLiquidatorBal = await alcxToken.balanceOf(liquidatorAddress, { blockTag: toBlock.blockNumber });
totalComp = totalComp.add(compLiquidatorBal);
console.log(`Liquidator ${quantityFormatter(compLiquidatorBal)}`);
try {
const alcxUsdc = await exports.quoteSwap(signer, tokens_1.ALCX, tokens_1.alUSD, totalComp, toBlock, [tokens_1.ALCX.address, uniswapEthToken, tokens_1.DAI.address, tokens_1.alUSD.address], [10000, 3000, 500]);
console.log(`Total ${quantityFormatter(totalComp)} ${quantityFormatter(alcxUsdc.outAmount)} alUSD`);
console.log(`ALCX/USDC exchange rate: ${alcxUsdc.exchangeRate}`);
}
catch (err) {
console.log(`Total ${quantityFormatter(totalComp)}`);
console.error(`Failed to get ALCX to alUSD rate.`);
}
};
exports.getAlcxTokens = getAlcxTokens;
//# sourceMappingURL=snap-utils.js.map