Skip to content

Commit

Permalink
feat: adds task revenue-split-buy-back (#355)
Browse files Browse the repository at this point in the history
adds hardhat task revenue-split-buy-back
  • Loading branch information
doncesarts committed May 3, 2022
1 parent 9fde20e commit 2271bbe
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 4 deletions.
42 changes: 38 additions & 4 deletions tasks/emissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import {
IERC20__factory,
L2EmissionsController__factory,
RevenueBuyBack__factory,
RevenueSplitBuyBack__factory,
RevenueForwarder__factory,
VotiumBribeForwarder__factory,
SavingsManager__factory,
} from "types/generated"
import { ONE_HOUR } from "@utils/constants"
import { simpleToExactAmount } from "@utils/math"
import { logTxDetails, logger, mUSD, mBTC } from "./utils"
import { getSigner } from "./utils/signerFactory"
import { getChain, resolveAddress } from "./utils/networkAddressFactory"
import { getBalancerPolygonReport } from "./utils/emission-disperse-bal"
import { sendPrivateTransaction } from "./utils/flashbots"

import { splitBuyBackRewards } from "./utils/emissions-split-buy-back"
const log = logger("emission")

subtask("emission-calc", "Calculate the weekly emissions")
Expand Down Expand Up @@ -163,15 +165,47 @@ subtask("revenue-buy-back", "Buy back MTA from mUSD and mBTC gov fees")
task("revenue-buy-back").setAction(async (_, __, runSuper) => {
await runSuper()
})
subtask("revenue-split-buy-back", "Buy back MTA from mUSD and mBTC gov fees")
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const signer = await getSigner(hre, taskArgs.speed)
const chain = getChain(hre)
const revenueSplitBuyBackAddress = resolveAddress("RevenueSplitBuyBack", chain)
const revenueSplitBuyBack = RevenueSplitBuyBack__factory.connect(revenueSplitBuyBackAddress, signer)
const musd = {
address: resolveAddress("mUSD", chain),
bAssetMinSlippage: 2, // 2%
rewardMinSlippage: 1, // 1%
mAssetMinBalance: simpleToExactAmount(1000), // 1k USD
}

const mbtc = {
address: resolveAddress("mBTC", chain),
bAssetMinSlippage: 5, // 5%
rewardMinSlippage: 3, // 3%
mAssetMinBalance: simpleToExactAmount(10, 14), // 10 wBTC
}
const mAssets = [musd, mbtc]
const request = {
mAssets,
revenueSplitBuyBack,
swapFees: [3000, 3000],
blockNumber: "latest",
}
const tx = await splitBuyBackRewards(signer, request)
await logTxDetails(tx, `buy back MTA from gov fees`)
})
task("revenue-split-buy-back").setAction(async (_, __, runSuper) => {
await runSuper()
})
subtask("revenue-donate-rewards", "Donate purchased MTA to the staking dials in the Emissions Controller")
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const signer = await getSigner(hre, taskArgs.speed)
const chain = getChain(hre)

const revenueBuyBackAddress = resolveAddress("RevenueBuyBack", chain)
const revenueBuyBack = RevenueBuyBack__factory.connect(revenueBuyBackAddress, signer)
const revenueBuyBackAddress = resolveAddress("RevenueSplitBuyBack", chain)
const revenueBuyBack = RevenueSplitBuyBack__factory.connect(revenueBuyBackAddress, signer)

const tx = await revenueBuyBack.donateRewards()
await logTxDetails(tx, `donate purchased MTA to Emissions Controller`)
Expand Down Expand Up @@ -252,7 +286,7 @@ task("emissions-process", "Weekly mainnet emissions process")
// Dial 15 (Votium) is skipped for now
if (proposal === undefined) {
await hre.run("votium-forward", { speed, proposal })
}
}
// Distributes to dial 16
await hre.run("emission-dist", { speed, dialIds: "16" })

Expand Down
178 changes: 178 additions & 0 deletions tasks/utils/emissions-split-buy-back.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/* eslint-disable no-await-in-loop */
import { Signer } from "@ethersproject/abstract-signer"
import { ContractTransaction } from "ethers"
import { BN, simpleToExactAmount } from "@utils/math"
import { RevenueSplitBuyBack, ERC20__factory } from "types/generated"
import { EncodedPaths, encodeUniswapPath, getWETHPath, quoteSwap } from "@utils/peripheral/uniswap"

export interface MAssetSwap {
address: string
bAssetMinSlippage: number
rewardMinSlippage: number
mAssetMinBalance: number | BN
}

export interface MainParams {
revenueSplitBuyBack: RevenueSplitBuyBack
mAssets: MAssetSwap[]
swapFees: number[]
blockNumber: number | string
}
export interface BuyBackRewardsParams extends MainParams {
minBassetsAmounts: BN[]
minRewardsAmounts: BN[]
uniswapPaths: EncodedPaths[]
}

/**
* Calculate the minBassetsAmounts, minRewardsAmounts and uniswapPaths to execute on the buyback rewards.
*
* @param {Signer} signer
* @param {MainParams} params
* - mAssets: Addresses of mAssets that are to be sold for rewards. eg mUSD and mBTC.
* - revenueSplitBuyBackAddress: The address of the revenue split buy back contract.;
* @return {Promise<BuyBackRewardsParams>}
* - minBassetsAmounts Minimum amount of bAsset tokens to receive for each redeem of mAssets.
* The amount uses the decimal places of the bAsset.
* Example 1: Redeeming 10,000 mUSD with a min 2% slippage to USDC which has 6 decimal places
* minBassetsAmounts = 10,000 mAssets * slippage 0.98 * USDC decimals 1e6 =
* 1e4 * 0.98 * 1e6 = 1e10 * 0.98 = 98e8
*
* Example 2: Redeeming 1 mBTC with a min 5% slippage to WBTC which has 8 decimal places
* minBassetsAmounts = 1 mAsset * slippage 0.95 * WBTC decimals 1e8 =
* 0.95 * 1e8 = 95e6
*
* - minRewardsAmounts Minimum amount of reward tokens received from the sale of bAssets.
* The amount uses the decimal places of the rewards token.
* Example 1: Swapping 10,000 USDC with a min 1% slippage to MTA which has 18 decimal places
* minRewardsAmounts = 10,000 USDC * slippage 0.99 * MTA decimals 1e18 * MTA/USD rate 1.2
* = 1e4 * 0.99 * 1e18 * 1.2 = 1e22 * 0.99 = 99e20
*
* Example 1: Swapping 1 WBTC with a min 3% slippage to MTA which has 18 decimal places
* minRewardsAmounts = 1 WBTC * slippage 0.97 * MTA decimals 1e18 * MTA/BTC rate 0.00001
* = 1 * 0.97 * 1e18 * 0.00001 = 0.97 * 1e13 = 97e11
*
* - uniswapPaths The Uniswap V3 bytes encoded paths.
*/
export const calculateBuyBackRewardsQuote = async (signer: Signer, params: MainParams): Promise<BuyBackRewardsParams> => {
const { revenueSplitBuyBack, mAssets, swapFees, blockNumber } = params
const mAssetsToBuyBack: MAssetSwap[] = []
const minBassetsAmounts: BN[] = []
const minRewardsAmounts: BN[] = []
const uniswapPaths: EncodedPaths[] = []

const configScale: BN = await revenueSplitBuyBack.CONFIG_SCALE()
const treasuryFee: BN = await revenueSplitBuyBack.treasuryFee()
const rewardsToken = await revenueSplitBuyBack.REWARDS_TOKEN()

const rewardsTokenContract = ERC20__factory.connect(rewardsToken, signer)
const rTokenDecimals = await rewardsTokenContract.decimals()
const rTokenSymbol = await rewardsTokenContract.symbol()

for (let i = 0; i < mAssets.length; i = 1 + 1) {
const mAsset = mAssets[i]
const bAsset: string = await revenueSplitBuyBack.bassets(mAsset.address)
const mAssetContract = ERC20__factory.connect(mAsset.address, signer)
const bAssetContract = ERC20__factory.connect(bAsset, signer)

const mAssetBalance: BN = await mAssetContract.balanceOf(revenueSplitBuyBack.address)
const mAssetSymbol: string = await mAssetContract.symbol()

const bAssetDecimals = await bAssetContract.decimals()
const bAssetSymbol: string = await bAssetContract.symbol()
// console.log(`ts: ${mAssetSymbol} balance: ${mAssetBalance.toString()}`);

// Validate if the mAsset balance is grater than the minimum balance to buy back, default is zero.
if (mAssetBalance.gt(mAsset.mAssetMinBalance)) {
// mAssetAmount = 10000e18 * (1e18 - 0.4e18 / 1e18) = 6000e18
const mAssetAmount = mAssetBalance.mul(configScale.sub(treasuryFee)).div(configScale)

// calculate minBassetsAmounts
const bAssetSlippage = 100 - mAsset.bAssetMinSlippage
// mAssetAmount = 6000e18 * (98/100)/1e18 * 1e6 = 5880e6 (USDC)
const minBassetsAmount = mAssetAmount
.mul(bAssetSlippage)
.div(100)
.div(simpleToExactAmount(1))
.mul(simpleToExactAmount(1, bAssetDecimals))
minBassetsAmounts.push(minBassetsAmount)
mAssetsToBuyBack.push(mAsset)

// console for debugging purposes, do not delete

console.table({
mAssetSymbol,
mAssetBalance: mAssetBalance.toString(),
configScale: configScale.toString(),
treasuryFee: treasuryFee.toString(),
bAssetSlippage: bAssetSlippage.toString(),
bAssetDecimals: bAssetDecimals.toString(),
mAssetAmount: mAssetAmount.toString(),
minBassetsAmount: minBassetsAmount.toString(),
})

// 2 ============ minRewardsAmount ============//

const fromToken = { address: bAsset, decimals: bAssetDecimals }
const toToken = { address: rewardsToken, decimals: rTokenDecimals }

// eslint-disable-next-line no-await-in-loop
const { outAmount, exchangeRate } = await quoteSwap(
signer,
fromToken,
toToken,
minBassetsAmount,
blockNumber,
undefined,
swapFees,
)
const rewardSlippage = 100 - mAsset.rewardMinSlippage
// minRewardsAmount = 5880e6 * (98/100) /1e6 * 1e18 = 5880e6 (USDC)
// quote out amount of reward tokens with its decimals.
const minRewardsAmount = outAmount.mul(rewardSlippage).div(100)

minRewardsAmounts.push(minRewardsAmount)

// console for debugging purposes, do not delete
console.table({
bAssetSymbol,
rTokenSymbol,
rewardSlippage: rewardSlippage.toString(),
mAssetAmount: mAssetAmount.toString(),
minBassetsAmount: minBassetsAmount.toString(),
minRewardsAmount: minRewardsAmount.toString(),
outAmount: outAmount.toString(),
exchangeRate: exchangeRate.toString(),
bAssetDecimals: bAssetDecimals.toString(),
rTokenDecimals: rTokenDecimals.toString(),
})

// 3 ============ Uniswap path ============//
const uniswapPath = encodeUniswapPath(getWETHPath(bAsset, rewardsToken), [3000, 3000])
uniswapPaths.push(uniswapPath)
console.log(`ts: swap ${bAssetSymbol} to ${rTokenSymbol}, encodeUniswapPath: ${uniswapPath.encoded.toString()}`)
}
}
return { ...params, mAssets: mAssetsToBuyBack, minBassetsAmounts, minRewardsAmounts, uniswapPaths }
}
/**
* Execute the buyback rewards of different mAssets.
*
* @param {Signer} signer
* @param {MainParams} params
* @return {*} {Promise<ContractTransaction>}
*/
export const splitBuyBackRewards = async (signer: Signer, params: MainParams): Promise<ContractTransaction> => {
const buyBackRewardsParams = await calculateBuyBackRewardsQuote(signer, params)
const { mAssets, minBassetsAmounts, minRewardsAmounts, uniswapPaths, revenueSplitBuyBack } = buyBackRewardsParams
const mAssetAddress = mAssets.map((m) => m.address)
const uniswapPathsEncoded = uniswapPaths.map((u) => u.encoded)
console.log(`ts: buyBackRewards
mAssetAddress ${mAssetAddress}
minBassetsAmounts ${minBassetsAmounts.toString()}
minRewardsAmounts ${minRewardsAmounts.toString()}
uniswapPathsEncoded ${uniswapPathsEncoded}
`)
return revenueSplitBuyBack.buyBackRewards(mAssetAddress, minBassetsAmounts, minRewardsAmounts, uniswapPathsEncoded)
}
44 changes: 44 additions & 0 deletions test-utils/peripheral/uniswap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import { Signer } from "ethers"
import { IUniswapV3Quoter, IUniswapV3Quoter__factory } from "types/generated"
import { BN, simpleToExactAmount } from "../math"

const uniswapEthToken = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
const uniswapQuoterV3Address = "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6"

export interface EncodedPaths {
encoded: string
encodedReversed: string
}
export interface SwapQuote {
outAmount: BN
exchangeRate: BN
}
export interface Token {
address: string
decimals: number
}
export const encodeUniswapPath = (tokenAddresses: string[], fees: number[]): EncodedPaths => {
const FEE_SIZE = 3

Expand Down Expand Up @@ -32,3 +47,32 @@ export const encodeUniswapPath = (tokenAddresses: string[], fees: number[]): Enc
encodedReversed: encodedReversed.toLowerCase(),
}
}
export const getWETHPath = (fromPath: string, toPath: string): Array<string> => [fromPath, uniswapEthToken, toPath]
// It makes it easier to mock this function.
export const quoteExactInput = (
quoter: IUniswapV3Quoter,
encodedPath: string,
amount: BN,
blockNumber: number | string = "latest",
): Promise<BN> => quoter.callStatic.quoteExactInput(encodedPath, amount, { blockTag: blockNumber })

export const quoteSwap = async (
signer: Signer,
from: Token,
to: Token,
inAmount: BN,
blockNumber: number | string,
path?: string[],
fees = [3000, 3000],
): Promise<SwapQuote> => {
// Get quote value from UniswapV3
const uniswapPath = path || getWETHPath(from.address, to.address)
// console.log("ts: quoteSwap uniswapPath", uniswapPath)
// Use Uniswap V3
const encodedPath = encodeUniswapPath(uniswapPath, fees)
const quoter = IUniswapV3Quoter__factory.connect(uniswapQuoterV3Address, signer)
const outAmount = await quoteExactInput(quoter, encodedPath.encoded, inAmount, blockNumber)
const exchangeRate = outAmount.div(simpleToExactAmount(1, to.decimals)).mul(simpleToExactAmount(1, from.decimals)).div(inAmount)
// Exchange rate is not precise enough, better to relay on the output amount.
return { outAmount, exchangeRate }
}

0 comments on commit 2271bbe

Please sign in to comment.