Skip to content

Commit

Permalink
Added last 24 hour transactions to mBTC-snap
Browse files Browse the repository at this point in the history
  • Loading branch information
naddison36 committed Feb 14, 2021
1 parent 7137bfa commit bcd906e
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 40 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,35 @@ Codebase rules are enforced through a passing [CI](https://circleci.com) (visibl

[1]: https://github.com/nvm-sh/nvm
[2]: https://github.com/trufflesuite/ganache-cli

### Command Line Interface

[Hardhat Tasks](https://hardhat.org/guides/create-task.html) are used for command line interactions with the mStable contracts. The tasks can be found in the [tasks](./tasks) folder.

A separate Hardhat config file [tasks.config.ts](./tasks.config.ts) is used for task config. This inherits from the main Hardhat config file [hardhat.config.ts](./hardhat.config.ts). This avoids circular dependencies when the repository needs to be compiled before the Typechain artifacts have been generated. This means the `--config tasks.config.ts` Hardhat option needs to be used to run the mStable tasks.

Config your network. If you are just using readonly tasks like `mBTC-snap` you don't need to have a signer with Ether in it so the default Hardhat test account is ok to use. For safety, the mainnet config is not committed to the repository to avoid accidentally running tasks against mainnet.

```
mainnet: {
url: process.env.NODE_URL || "",
accounts: {
mnemonic: "test test test test test test test test test test test junk",
},
},
```

**Never commit mainnet private keys, mnemonics or provider URLs to the repository.**

Examples of using the Hardhat tasks

```zsh
# List all Hardhat tasks
hh --config tasks.config.ts

# Set the provider url
export NODE_URL=https://mainnet.infura.io/v3/yourApiKey

# To run the mBTC-snap task against mainnet
hh --config tasks.config.ts --network mainnet mBTC-snap
```
174 changes: 138 additions & 36 deletions tasks/mBTC.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-console */
import { btcBassets, capFactor, contracts, startingCap } from "@utils/btcConstants"
import { fullScale } from "@utils/constants"
import { applyRatio, BN, simpleToExactAmount } from "@utils/math"
import { btcBassets, capFactor, contracts, getBassetFromAddress, startingCap } from "@utils/btcConstants"
import { fullScale, ONE_DAY } from "@utils/constants"
import { applyDecimals, applyRatio, BN, simpleToExactAmount } from "@utils/math"
import { Signer } from "ethers"
import { formatUnits } from "ethers/lib/utils"
import { task } from "hardhat/config"
import { InvariantValidator__factory, Masset, Masset__factory } from "types/generated"

interface TxSummary {
total: BN
fees: BN
}

const getTvlCap = async (signer: Signer): Promise<BN> => {
const validator = await new InvariantValidator__factory(signer).attach(contracts.mainnet.InvariantValidator)
const tvlStartTime = await validator.startTime()
Expand All @@ -18,6 +23,29 @@ const getTvlCap = async (signer: Signer): Promise<BN> => {
return startingCap.add(capFactor.mul(weeksSinceLaunch.pow(2)).div(fullScale.pow(2)))
}

const getBasket = async (mBtc: Masset, signer: Signer) => {
const tvlCap = await getTvlCap(signer)

const bAssets = await mBtc.getBassets()
const values: BN[] = []
let total = BN.from(0)
btcBassets.forEach((bAsset, i) => {
values.push(applyRatio(bAssets[1][i].vaultBalance, bAssets[1][i].ratio))
total = total.add(values[i])
})

console.log("\nmBTC basket")
btcBassets.forEach((bAsset, i) => {
const percentage = values[i].mul(100).div(total)
console.log(`${bAsset.symbol.padEnd(7)} ${formatUnits(values[i]).padEnd(20)} ${percentage.toString().padStart(2)}%`)
})
const surplus = await mBtc.surplus()
console.log(`Surplus ${formatUnits(surplus)}`)
const tvlCapPercentage = total.mul(100).div(tvlCap)
console.log(`Total ${formatUnits(total).padStart(21)}`)
console.log(`TVL cap ${formatUnits(tvlCap).padStart(21)} ${tvlCapPercentage}%`)
}

const getSwapRates = async (mBTC: Masset) => {
console.log("\nSwap rates")
for (const inputToken of btcBassets) {
Expand All @@ -29,17 +57,14 @@ const getSwapRates = async (mBTC: Masset) => {
const inputStr = "0.1"
const input = simpleToExactAmount(inputStr, inputToken.decimals)
const output = await mBTC.getSwapOutput(inputAddress, outputAddress, input)
const scaledInput = BN.from(10)
.pow(18 - inputToken.decimals)
.mul(input)
const scaledOutput = BN.from(10)
.pow(18 - outputToken.decimals)
.mul(output)
const scaledInput = applyDecimals(input, inputToken.decimals)
const scaledOutput = applyDecimals(output, outputToken.decimals)
const percent = scaledOutput.sub(scaledInput).mul(1000).div(scaledInput)
console.log(
`${inputStr} ${inputToken.symbol} -> ${formatUnits(output, outputToken.decimals)}\t${
outputToken.symbol
}\t${percent}bps`,
`${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}`)
Expand All @@ -57,10 +82,99 @@ const getBalances = async (mBTC: Masset) => {
const otherBalances = mBtcBalance.sub(savingBalance).sub(sushiPoolBalance).sub(mStableFundManagerBalance)

console.log("\nmBTC Holders")
console.log(`imBTC ${formatUnits(savingBalance)} ${savingBalance.mul(100).div(mBtcBalance)}%`)
console.log(`Sushi Pool ${formatUnits(sushiPoolBalance)} ${sushiPoolBalance.mul(100).div(mBtcBalance)}%`)
console.log(`mStable Fund Manager ${formatUnits(mStableFundManagerBalance)} ${mStableFundManagerBalance.mul(100).div(mBtcBalance)}%`)
console.log(`Others ${formatUnits(otherBalances)} ${otherBalances.mul(100).div(mBtcBalance)}%`)
console.log(`imBTC ${formatUnits(savingBalance).padEnd(20)} ${savingBalance.mul(100).div(mBtcBalance)}%`)
console.log(`Sushi Pool ${formatUnits(sushiPoolBalance).padEnd(20)} ${sushiPoolBalance.mul(100).div(mBtcBalance)}%`)
console.log(
`mStable Fund Manager ${formatUnits(mStableFundManagerBalance).padEnd(20)} ${mStableFundManagerBalance.mul(100).div(mBtcBalance)}%`,
)
console.log(`Others ${formatUnits(otherBalances).padEnd(20)} ${otherBalances.mul(100).div(mBtcBalance)}%`)
}

const getMints = async (mBTC: Masset, currentBlock: number): Promise<TxSummary> => {
const filter = await mBTC.filters.Minted(null, null, null, null, null)
const logs = await mBTC.queryFilter(filter, currentBlock - ONE_DAY.toNumber())

console.log("\nMints in last 24 hours")
console.log("Block#\t Minter\t\t\t\t\t bAsset Masset Quantity")
let total = BN.from(0)
logs.forEach((log) => {
const inputBasset = getBassetFromAddress(log.args.input)
console.log(`${log.blockNumber} ${log.args.minter} ${inputBasset.symbol.padEnd(6)} ${formatUnits(log.args.mAssetQuantity)}`)
total = total.add(log.args.mAssetQuantity)
})
console.log(`Total ${formatUnits(total)}`)
return {
total,
fees: BN.from(0),
}
}

const getRedemptions = async (mBTC: Masset, currentBlock: number): Promise<TxSummary> => {
const filter = await mBTC.filters.Redeemed(null, null, null, null, null, null)
const logs = await mBTC.queryFilter(filter, currentBlock - ONE_DAY.toNumber())

console.log("\nRedemptions in last 24 hours")
console.log("Block#\t Redeemer\t\t\t\t bAsset Masset Quantity\tFee")
let total = BN.from(0)
let fees = BN.from(0)
logs.forEach((log) => {
const outputBasset = getBassetFromAddress(log.args.output)
console.log(
`${log.blockNumber} ${log.args.redeemer} ${outputBasset.symbol.padEnd(6)} ${formatUnits(log.args.mAssetQuantity)} ${formatUnits(
log.args.scaledFee,
)}`,
)
total = total.add(log.args.mAssetQuantity)
fees = fees.add(log.args.scaledFee)
})
console.log(`Total ${formatUnits(total)}`)

return {
total,
fees,
}
}

const getSwaps = async (mBTC: Masset, currentBlock: number): Promise<TxSummary> => {
const filter = await mBTC.filters.Swapped(null, null, null, null, null, null)
const logs = await mBTC.queryFilter(filter, currentBlock - ONE_DAY.toNumber())

console.log("\nSwaps in last 24 hours")
console.log("Block#\t Swapper\t\t\t\t Input Output Output Quantity\tFee")
// Scaled bAsset quantities
let total = BN.from(0)
let fees = BN.from(0)
logs.forEach((log) => {
const inputBasset = getBassetFromAddress(log.args.input)
const outputBasset = getBassetFromAddress(log.args.output)
console.log(
`${log.blockNumber} ${log.args.swapper} ${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)
})
console.log(`Total ${formatUnits(total)}`)

return {
total,
fees,
}
}

const outputFees = (redeems: TxSummary, swaps: TxSummary) => {
const totalFees = redeems.fees.add(swaps.fees)
const totalTotals = redeems.total.add(swaps.total)
console.log("\nFees in the last 24 hours")
console.log(
`Redeem ${formatUnits(redeems.total).padEnd(22)} ${formatUnits(redeems.fees).padEnd(20)} ${redeems.fees.mul(100).div(totalFees)}%`,
)
console.log(
`Swap ${formatUnits(swaps.total).padEnd(22)} ${formatUnits(swaps.fees).padEnd(20)} ${swaps.fees.mul(100).div(totalFees)}%`,
)
console.log(`Total ${formatUnits(totalTotals).padEnd(22)} ${formatUnits(totalFees).padEnd(20)}`)
}

task("mBTC-snap", "Get the latest data from the mBTC contracts").setAction(async (_, hre) => {
Expand All @@ -73,29 +187,17 @@ task("mBTC-snap", "Get the latest data from the mBTC contracts").setAction(async
}
const mBtc = await new Masset__factory(linkedAddress, signer).attach(contracts.mainnet.mBTC)

const tvlCap = await getTvlCap(signer)

const block = await hre.ethers.provider.getBlockNumber()
console.log(`Latest block ${block}, ${new Date()}`)

const bAssets = await mBtc.getBassets()
const values: BN[] = []
let total = BN.from(0)
btcBassets.forEach((bAsset, i) => {
values.push(applyRatio(bAssets[1][i].vaultBalance, bAssets[1][i].ratio))
total = total.add(values[i])
})
btcBassets.forEach((bAsset, i) => {
const percentage = values[i].mul(100).div(total)
console.log(`${bAsset.symbol}\t${formatUnits(values[i])}\t\t${percentage}%`)
})
const surplus = await mBtc.surplus()
console.log(`Surplus ${formatUnits(surplus)}`)
const tvlCapPercentage = total.mul(100).div(tvlCap)
console.log(`Total ${formatUnits(total)}, tvl cap ${formatUnits(tvlCap)} ${tvlCapPercentage}%`)
const currentBlock = await hre.ethers.provider.getBlockNumber()
console.log(`Latest block ${currentBlock}, ${new Date().toUTCString()}`)

await getBasket(mBtc, signer)
await getBalances(mBtc)
await getSwapRates(mBtc)

await getMints(mBtc, currentBlock)
const redeemSummary = await getRedemptions(mBtc, currentBlock)
const swapSummary = await getSwaps(mBtc, currentBlock)
outputFees(redeemSummary, swapSummary)
})

module.exports = {}
9 changes: 8 additions & 1 deletion test-utils/btcConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const contracts = {
mainnet: {
// BTC tokens
renBTC: "0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D",
sBTC: "0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6",
sBTC: "0xfE18be6b3Bd88A2D2A7f928d00292E7a9963CfC6",
WBTC: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",

// mBTC contracts
Expand All @@ -87,3 +87,10 @@ export const contracts = {
imBTC: "0xBfe31D984d688628d06Ae2Da1D640Cf5D9e242A5",
},
}

export const getBassetFromAddress = (address: string, network = "mainnet"): Bassets => {
const contract = Object.entries(contracts[network]).find((c) => c[1] === address)
if (!contract) return undefined
const symbol = contract[0]
return btcBassets.find((btcBasset) => btcBasset.symbol === symbol)
}
13 changes: 10 additions & 3 deletions test-utils/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ratioScale } from "./constants"

export { BN }

// Converts an unscaled number to scaled number with the specified number of decimals
// eg convert 3 to 3000000000000000000 with 18 decimals
export const simpleToExactAmount = (amount: number | string | BN, decimals: number | BN = 18): BN => {
// Code is largely lifted from the guts of web3 toWei here:
// https://github.com/ethjs/ethjs-unit/blob/master/src/index.js
Expand Down Expand Up @@ -59,6 +61,13 @@ export const simpleToExactAmount = (amount: number | string | BN, decimals: numb
return result
}

// How many mAssets is this bAsset worth using bAsset decimal length
// eg convert 3679485 with 6 decimals (3.679485) to 3679485000000000000 with 18 decimals
export const applyDecimals = (inputQuantity: number | string | BN, decimals = 18): BN =>
BN.from(10)
.pow(18 - decimals)
.mul(inputQuantity)

export const percentToWeight = (percent: number | string | BN): BN => {
return simpleToExactAmount(percent, 16)
}
Expand All @@ -70,9 +79,7 @@ export const applyRatioMassetToBasset = (input: BN, ratio: BN | string): BN => {

// How many mAssets is this bAsset worth
export const applyRatio = (bAssetQ: BN | string | number, ratio: BN | string): BN => {
return BN.from(bAssetQ)
.mul(ratio)
.div(ratioScale)
return BN.from(bAssetQ).mul(ratio).div(ratioScale)
}

// How many mAssets is this bAsset worth
Expand Down

0 comments on commit bcd906e

Please sign in to comment.