import { BN , sum , percentToWeight } from "@utils/math" ; import { task , types } from "hardhat/config" ; import "ts-node/register" ; import "tsconfig-paths/register" ; import { EmissionsController__factory , IERC20__factory } from "types/generated" ; import { MTA , usdFormatter } from "./utils" ; import { getChain , getChainAddress } from "./utils/networkAddressFactory" ; import { getSigner } from "./utils/signerFactory" ; import { getBlock } from "./utils/snap-utils" ; interface DialData { disabled : boolean ; notify : boolean ; cap : number ; balance : BN ; recipient : string ; } interface DialDetails { dialId : number ; voteWeight : BN ; distributed : BN ; donated : BN ; rewards : BN ; } interface DialsSnap { nextEpoch : number ; distributionAmounts : BN [ ] ; totalDialVotes : BN ; dialsData : DialData [ ] ; dialsDetails : DialDetails [ ] ; totalDistributed : BN ; totalDonated : BN ; totalRewards : BN ; emissionsControllerBalance : BN ; csv : boolean ; } dev param { Array < BN > } latestDialVotes param { Array < DialData > } dialsData param { BN } emissionForEpoch return { Object } const calculateRewards = ( latestDialVotes , dialsData , emissionForEpoch ) => { const dialLen = latestDialVotes . length ; const dials = [ ... dialsData ] ; const dialVotes = dialsData . map ( ( dial , i ) => ( dial . disabled ? BN . from ( 0 ) : latestDialVotes [ i ] ) ) ; const totalDialVotes = dialVotes . reduce ( sum ) ; const distributionAmounts = Array ( dialLen ) . fill ( BN . from ( 0 ) ) ; let postCappedVotes = totalDialVotes ; let postCappedEmission = emissionForEpoch ; for ( let k = 0 ; k < dialLen ; k += 1 ) { const dialData = dialsData [ k ] ; if ( dialData . cap > 0 && ! dialData . disabled ) { const maxVotes = BN . from ( dialData . cap ) . mul ( totalDialVotes ) . div ( 100 ) ; if ( dialVotes [ k ] . gt ( maxVotes ) ) { distributionAmounts [ k ] = BN . from ( dialData . cap ) . mul ( emissionForEpoch ) . div ( 100 ) ; dials [ k ] = { ... dials [ k ] , balance : dials [ k ] . balance . add ( distributionAmounts [ k ] ) } ; postCappedVotes = postCappedVotes . sub ( dialVotes [ k ] ) ; postCappedEmission = postCappedEmission . sub ( distributionAmounts [ k ] ) ; dialVotes [ k ] = BN . from ( 0 ) ; } } } for ( let l = 0 ; l < dialLen ; l += 1 ) { if ( ! dialVotes [ l ] . eq ( BN . from ( 0 ) ) && ! postCappedVotes . eq ( BN . from ( 0 ) ) ) { distributionAmounts [ l ] = dialVotes [ l ] . mul ( postCappedEmission ) . div ( postCappedVotes ) ; dials [ l ] = { ... dials [ l ] , balance : dials [ l ] . balance . add ( distributionAmounts [ l ] ) } ; } } return { distributionAmounts , totalDialVotes , dialsData : dials } ; } ; const dialNames = [ "MTA Staking Contract" , "BPT Staking Contract" , "mUSD Vault" , "mBTC Vault" , "GUSD FP Vault" , "BUSD FP Vault" , "alUSD FP Vault" , "RAI FP Vault" , "FEI FP Vault" , "HBTC FP Vault" , "tBTCv2 FP Vault" , "Polygon mUSD Vault" , "Polygon FRAX Farm" , "Polygon Balancer Pool" , "Treasury DAO" , "Votium" , "Visor Finance" , ] ; const dialsDetailsToString = ( dialsDetails ) => dialsDetails . map ( ( dd , i ) => `  ${ dd . dialId . toString ( ) . padStart ( 2 ) }\t${ dialNames [ i ] . padStart ( 21 ) }\t${ usdFormatter ( dd . voteWeight , 18 , 5 , 2 ) }\t${ usdFormatter ( dd . distributed ) }\t${ usdFormatter ( dd . donated ) }\t ${ usdFormatter ( dd . rewards ) }` ) . join ( "\n" ) ; const dialsDetailsToCsv = ( dialsDetails ) => dialsDetails . map ( ( dd , i ) => `${ dd . dialId . toString ( ) . padStart ( 2 ) }, ${ dialNames [ i ] . padStart ( 21 ) }, ${ usdFormatter ( dd . voteWeight , 18 , 5 , 2 ) }, ${ usdFormatter ( dd . distributed ) }, ${ usdFormatter ( dd . donated ) }, ${ usdFormatter ( dd . rewards ) }` ) . join ( "\n" ) ; const outputDialsSnap = ( dialsSnap ) => { if ( ! dialsSnap . csv ) { console . log ( `\nEmissions Controller Dials Snap at epoch ${ dialsSnap . nextEpoch }` ) ; console . log ( `  ID\t\t\tName\tPercent\t   Distributed\t\tDonated\t\t Total` ) ; console . log ( dialsDetailsToString ( dialsSnap . dialsDetails ) ) ; console . log ( `Totals\t\t\t\t\t${ usdFormatter ( dialsSnap . totalDistributed ) }\t${ usdFormatter ( dialsSnap . totalDonated ) }\t ${ usdFormatter ( dialsSnap . totalRewards ) }` ) ; console . log ( "MTA in Emissions Controller" , usdFormatter ( dialsSnap . emissionsControllerBalance ) ) ; } else { console . log ( `ID, Name, Percent, Distributed, Donated, Total` ) ; console . log ( dialsDetailsToCsv ( dialsSnap . dialsDetails ) ) ; } } ; /**
 *
 *  1.- For each dial in the Emissions Controller
 *      1.1- Get the weighted votes as a percentage of the total weighted votes across all dials -  from voteHistory
 *      1.2- Calculate distributed MTA rewards for the next run factoring in disabled dials and reward caps for the staking contracts - topLineEmission
 *      1.3- Get the current donated MTA rewards - balance  from DialData
 *      1.4- Total rewards = distributed + donated rewards
 *   2.- Total MTA rewards to be distributed across all dials - basically the sum
 *   3.- Total MTA rewards currently donated across all dials - the sum of balance of each dial
 *   4.- Total MTA rewards across all dials = distributed + donated
 *   5.- Get MTA balance in the Emissions Controller  - // (REWARD_TOKEN.balanceOf(emissionsController))
 */ task ( "dials-snap" , "Snaps Emissions Controller's dials" ) . addOptionalParam ( "csv" , "Output in comma separated values" , false , types . boolean ) . addOptionalParam ( "block" , "Block number. (default: current block)" , 0 , types . int ) . setAction ( async ( _taskArgs , hre ) => { const signer = await getSigner ( hre ) ; const chain = getChain ( hre ) ; const block = await getBlock ( hre . ethers , _taskArgs . block ) ; const emissionsControllerAddress = getChainAddress ( "EmissionsController" , chain ) ; const emissionsController = EmissionsController__factory . connect ( emissionsControllerAddress , signer ) ; const mtaToken = IERC20__factory . connect ( MTA . address , signer ) ; const [ , lastEpoch ] = await emissionsController . epochs ( { blockTag : block . blockNumber , } ) ; const nextEpoch = lastEpoch + 1 ; const dialsDetails = [ ] ; const totalDistributed = await emissionsController . topLineEmission ( nextEpoch , { blockTag : block . blockNumber , } ) ; let totalDonated = BN . from ( 0 ) ; let totalRewards = BN . from ( 0 ) ; const emissionsControllerBalance = await mtaToken . balanceOf ( emissionsController . address , { blockTag : block . blockNumber , } ) ; const latestDialVotes = await emissionsController . getDialVotes ( { blockTag : block . blockNumber , } ) ; const dialsData = [ ] ; for ( let i = 0 ; i < latestDialVotes . length ; i += 1 ) { dialsData . push ( await emissionsController . dials ( i , { blockTag : block . blockNumber , } ) ) ; } const totalDialVotes = dialsData . filter ( ( dial ) => ! dial . disabled ) . map ( ( _dial , i ) => latestDialVotes [ i ] ) . reduce ( sum ) ; const calculatedRewards = calculateRewards ( latestDialVotes , dialsData , totalDistributed ) ; latestDialVotes . forEach ( async ( vote , dialId ) => { const dialData = dialsData [ dialId ] ; const adjustedVote = BN . from ( dialData . disabled ? 0 : vote ) ; const voteWeight = percentToWeight ( totalDialVotes . eq ( 0 ) ? BN . from ( 0 ) : adjustedVote . mul ( 10000 ) . div ( totalDialVotes ) ) ; const distributed = calculatedRewards . distributionAmounts [ dialId ] ; const donated = dialData . balance ; const rewards = donated . add ( distributed ) ; totalDonated = totalDonated . add ( donated ) ; totalRewards = totalRewards . add ( rewards ) ; dialsDetails . push ( { dialId , voteWeight , distributed , donated , rewards } ) ; } ) ; outputDialsSnap ( { nextEpoch , dialsDetails , totalDistributed , totalDonated , totalRewards , emissionsControllerBalance , ... calculatedRewards , csv : _taskArgs . csv , } ) ; } ) ; module . exports = { } ;