import { Address } from '@celo/connect' ; import { GroupVoterReward , VoterReward } from '@celo/contractkit/lib/wrappers/Election' ; import { AccountSlashed } from '@celo/contractkit/lib/wrappers/LockedGold' ; import { Validator , ValidatorReward } from '@celo/contractkit/lib/wrappers/Validators' ; import { eqAddress } from '@celo/utils/lib/address' ; import { flags } from '@oclif/command' ; import BigNumber from 'bignumber.js' ; import { cli } from 'cli-ux' ; import { BaseCommand } from '../../base' ; import { newCheckBuilder } from '../../utils/checks' ; import { Flags } from '../../utils/command' ; interface ExplainedVoterReward extends VoterReward { validators : Validator [ ] ; } interface ExplainedGroupVoterReward extends GroupVoterReward { validators : Validator [ ] ; } export default class Show extends BaseCommand { static description = 'Show rewards information about a voter, registered Validator, or Validator Group' ; static flags = { ... BaseCommand . flags , estimate : flags . boolean ( { description : 'Estimate voter rewards from current votes' } ) , voter : Flags . address ( { description : 'Voter to show rewards for' } ) , validator : Flags . address ( { description : 'Validator to show rewards for' } ) , group : Flags . address ( { description : 'Validator Group to show rewards for' } ) , slashing : flags . boolean ( { description : 'Show rewards for slashing' , default : true } ) , epochs : flags . integer ( { default : 1 , description : 'Show results for the last N epochs' , } ) , ... ( cli . table . flags ( ) as object ) , } ; static args = [ ] ; static examples = [ 'show --address 0x5409ed021d9299bf6814279a6a1411a7e866a631' ] ; async run ( ) { const res = this . parse ( Show ) ; const filter = Boolean ( res . flags . voter ) || Boolean ( res . flags . validator ) || Boolean ( res . flags . group ) ; const election = await this . kit . contracts . getElection ( ) ; const validators = await this . kit . contracts . getValidators ( ) ; const lockedGold = await this . kit . contracts . getLockedGold ( ) ; const currentEpoch = ( await validators . getEpochNumber ( ) ) . toNumber ( ) ; const checkBuilder = newCheckBuilder ( this ) ; const epochs = Math . max ( 1 , res . flags . epochs || 1 ) ; if ( res . flags . validator ) { if ( res . flags . voter || res . flags . group ) { throw Error ( 'Cannot select --validator with --voter or --group' ) ; } checkBuilder . isValidator ( res . flags . validator ) ; } if ( res . flags . group ) { checkBuilder . isValidatorGroup ( res . flags . group ) ; } if ( res . flags . voter ) { checkBuilder . isAccount ( res . flags . voter ) ; } await checkBuilder . runChecks ( ) ; let voterRewards = [ ] ; let groupVoterRewards = [ ] ; let validatorRewards = [ ] ; let validatorGroupRewards = [ ] ; let accountsSlashed = [ ] ; cli . action . start ( `Calculating rewards` ) ; for ( let epochNumber = Math . max ( 0 , currentEpoch - epochs ) ; epochNumber < currentEpoch ; epochNumber ++ ) { if ( ! filter || res . flags . voter ) { const electedValidators = await election . getElectedValidators ( epochNumber ) ; if ( ! filter ) { const epochGroupVoterRewards = await election . getGroupVoterRewards ( epochNumber ) ; groupVoterRewards = groupVoterRewards . concat ( epochGroupVoterRewards . map ( ( e ) : ExplainedGroupVoterReward => ( { ... e , validators : filterValidatorsByGroup ( electedValidators , e . group . address ) , } ) ) ) ; } else if ( res . flags . voter ) { const address = res . flags . voter ; try { const epochVoterRewards = await election . getVoterRewards ( address , epochNumber , res . flags . estimate ? await election . getVoterShare ( address ) : undefined ) ; voterRewards = voterRewards . concat ( epochVoterRewards . map ( ( e ) : ExplainedVoterReward => ( { ... e , validators : filterValidatorsByGroup ( electedValidators , e . group . address ) , } ) ) ) ; } catch ( error ) { if ( error . message . includes ( 'missing trie node' ) ) { throw new Error ( 'Exact voter information is avaiable only for 1024 blocks after each epoch.\n' + 'Supply --estimate to estimate rewards based on current votes, or use an archive node.' ) ; } else { throw error ; } } } } if ( ! filter || res . flags . validator || res . flags . group ) { const epochValidatorRewards = await validators . getValidatorRewards ( epochNumber ) ; if ( ! filter || res . flags . validator ) { const address = res . flags . validator ; validatorRewards = validatorRewards . concat ( address ? epochValidatorRewards . filter ( ( e ) => eqAddress ( e . validator . address , address ) ) : epochValidatorRewards ) ; } if ( ! filter || res . flags . group ) { const address = res . flags . group ; validatorGroupRewards = validatorGroupRewards . concat ( address ? epochValidatorRewards . filter ( ( e ) => eqAddress ( e . group . address , address ) ) : epochValidatorRewards ) ; } } if ( res . flags . slashing ) { const epochAccountsSlashed = await lockedGold . getAccountsSlashed ( epochNumber ) ; const address = res . flags . voter || res . flags . validator || res . flags . group ; accountsSlashed = accountsSlashed . concat ( address ? filterAccountsSlashed ( epochAccountsSlashed , address ) : epochAccountsSlashed ) ; } } if ( res . flags . slashing ) { const epochAccountsSlashed = await lockedGold . getAccountsSlashed ( currentEpoch ) ; const address = res . flags . voter || res . flags . validator || res . flags . group ; accountsSlashed = accountsSlashed . concat ( address ? filterAccountsSlashed ( epochAccountsSlashed , address ) : epochAccountsSlashed ) ; } cli . action . stop ( ) ; if ( voterRewards . length > 0 ) { console . info ( '' ) ; console . info ( 'Voter rewards:' ) ; cli . table ( voterRewards , { address : { } , addressPayment : { get : ( e ) => e . addressPayment . toFixed ( 0 ) } , group : { get : ( e ) => e . group . address } , averageValidatorScore : { get : ( e ) => averageValidatorScore ( e . validators ) . toFixed ( ) } , epochNumber : { } , } , res . flags ) ; } else if ( groupVoterRewards . length > 0 ) { console . info ( '' ) ; console . info ( 'Group voter rewards:' ) ; cli . table ( groupVoterRewards , { groupName : { get : ( e ) => e . group . name } , group : { get : ( e ) => e . group . address } , groupVoterPayment : { get : ( e ) => e . groupVoterPayment . toFixed ( 0 ) } , averageValidatorScore : { get : ( e ) => averageValidatorScore ( e . validators ) . toFixed ( ) } , epochNumber : { } , } , res . flags ) ; } if ( validatorRewards . length > 0 ) { console . info ( '' ) ; console . info ( 'Validator rewards:' ) ; cli . table ( validatorRewards , { validatorName : { get : ( e ) => e . validator . name } , validator : { get : ( e ) => e . validator . address } , validatorPayment : { get : ( e ) => e . validatorPayment . toFixed ( 0 ) } , validatorScore : { get : ( e ) => e . validator . score . toFixed ( ) } , group : { get : ( e ) => e . group . address } , epochNumber : { } , } , res . flags ) ; } if ( validatorGroupRewards . length > 0 ) { console . info ( '' ) ; console . info ( 'Validator Group rewards:' ) ; cli . table ( validatorGroupRewards , { groupName : { get : ( e ) => e . group . name } , group : { get : ( e ) => e . group . address } , groupPayment : { get : ( e ) => e . groupPayment . toFixed ( 0 ) } , validator : { get : ( e ) => e . validator . address } , validatorScore : { get : ( e ) => e . validator . score . toFixed ( ) } , epochNumber : { } , } , res . flags ) ; } if ( accountsSlashed . length > 0 ) { console . info ( '' ) ; console . info ( 'Slashing penalties and rewards:' ) ; cli . table ( accountsSlashed , { slashed : { } , penalty : { get : ( e ) => e . penalty . toFixed ( 0 ) } , reporter : { } , reward : { get : ( e ) => e . reward . toFixed ( 0 ) } , epochNumber : { } , } , res . flags ) ; } if ( voterRewards . length === 0 && groupVoterRewards . length === 0 && validatorRewards . length === 0 && validatorGroupRewards . length === 0 && accountsSlashed . length === 0 ) { console . info ( 'No rewards.' ) ; } } } function filterValidatorsByGroup ( validators , group ) { return validators . filter ( ( v ) => eqAddress ( v . affiliation || '' , group ) ) ; } function averageValidatorScore ( validators ) { return validators . reduce ( ( sumScore , v ) => sumScore . plus ( v . score ) , new BigNumber ( 0 ) ) . dividedBy ( validators . length || 1 ) ; } function filterAccountsSlashed ( accountsSlashed , address ) { return accountsSlashed . filter ( ( e ) => eqAddress ( e . slashed , address ) || eqAddress ( e . reporter , address ) ) ; }