import { subscription } from '@aragon/connect-core' ; import { BigNumber , providers as ethersProviders } from 'ethers' ; import { Address , SubscriptionCallback , SubscriptionResult } from '@aragon/connect-types' ; import ERC20 from './ERC20' ; import Setting from './Setting' ; import CastVote from './CastVote' ; import ArbitratorFee from './ArbitratorFee' ; import CollateralRequirement from './CollateralRequirement' ; import { IDisputableVotingConnector , VoteData } from '../types' ; import { bn , formatBn , PCT_BASE , PCT_DECIMALS , currentTimestampEvm , } from '../helpers' ; export default class Vote { #ethersProvider : ethersProviders . Provider ; #connector : IDisputableVotingConnector ; readonly id : string ; readonly votingId : string ; readonly voteId : string ; readonly creator : string ; readonly originalCreator : string ; readonly duration : string ; readonly context : string ; readonly voteStatus : string ; readonly actionId : string ; readonly challengeId : string ; readonly challenger : string ; readonly challengeEndDate : string ; readonly disputeId : string ; readonly settingId : string ; readonly startDate : string ; readonly totalPower : string ; readonly snapshotBlock : string ; readonly yeas : string ; readonly nays : string ; readonly pausedAt : string ; readonly pauseDuration : string ; readonly quietEndingExtension : string ; readonly quietEndingExtensionDuration : string ; readonly quietEndingSnapshotSupport : string ; readonly script : string ; readonly settledAt : string ; readonly disputedAt : string ; readonly executedAt : string ; readonly isAccepted : boolean ; readonly tokenId : string ; readonly tokenSymbol : string ; readonly tokenDecimals : string ; readonly settlementOffer : string | null ; readonly collateralRequirementId : string ; readonly collateralTokenId : string ; readonly collateralTokenSymbol : string ; readonly collateralTokenDecimals : string ; readonly submitterArbitratorFeeId : string ; readonly challengerArbitratorFeeId : string ; constructor ( data , connector , ethersProvider ) { this . #ethersProvider = ethersProvider ; this . #connector = connector ; this . id = data . id ; this . votingId = data . votingId ; this . voteId = data . voteId ; this . duration = data . duration ; this . quietEndingExtension = data . quietEndingExtension ; this . creator = data . creator ; this . originalCreator = data . originalCreator ; this . context = data . context ; this . voteStatus = data . status ; this . actionId = data . actionId ; this . challengeId = data . challengeId ; this . challenger = data . challenger ; this . challengeEndDate = data . challengeEndDate ; this . disputeId = data . disputeId ; this . settingId = data . settingId ; this . startDate = data . startDate ; this . totalPower = data . totalPower ; this . snapshotBlock = data . snapshotBlock ; this . yeas = data . yeas ; this . nays = data . nays ; this . pausedAt = data . pausedAt ; this . pauseDuration = data . pauseDuration ; this . quietEndingExtensionDuration = data . quietEndingExtensionDuration ; this . quietEndingSnapshotSupport = data . quietEndingSnapshotSupport ; this . script = data . script ; this . settledAt = data . settledAt ; this . disputedAt = data . disputedAt ; this . executedAt = data . executedAt ; this . isAccepted = data . isAccepted ; this . tokenId = data . tokenId ; this . tokenSymbol = data . tokenSymbol ; this . tokenDecimals = data . tokenDecimals ; this . settlementOffer = data . settlementOffer ; this . collateralRequirementId = data . collateralRequirementId ; this . collateralTokenId = data . collateralTokenId ; this . collateralTokenSymbol = data . collateralTokenSymbol ; this . collateralTokenDecimals = data . collateralTokenDecimals ; this . submitterArbitratorFeeId = data . submitterArbitratorFeeId ; this . challengerArbitratorFeeId = data . challengerArbitratorFeeId ; } get hasEnded ( ) : boolean { const currentTimestamp = currentTimestampEvm ( ) ; return this . voteStatus === 'Cancelled' || this . voteStatus === 'Settled' || ( this . voteStatus === 'Challenged' && currentTimestamp . gte ( this . challengeEndDate ) ) || ( this . voteStatus !== 'Challenged' && this . voteStatus !== 'Disputed' && currentTimestamp . gte ( this . endDate ) ) ; } get endDate ( ) : string { const baseVoteEndDate = bn ( this . startDate ) . add ( bn ( this . duration ) ) ; const endDateAfterPause = baseVoteEndDate . add ( bn ( this . pauseDuration ) ) ; const lastComputedEndDate = endDateAfterPause . add ( bn ( this . quietEndingExtensionDuration ) ) ; const currentTimestamp = currentTimestampEvm ( ) ; if ( currentTimestamp . lt ( lastComputedEndDate ) || ! this . wasFlipped ) { return lastComputedEndDate . toString ( ) ; } return lastComputedEndDate . add ( bn ( this . quietEndingExtension ) ) . toString ( ) ; } get currentQuietEndingExtensionDuration ( ) : string { const actualEndDate = bn ( this . endDate ) ; const baseVoteEndDate = bn ( this . startDate ) . add ( bn ( this . duration ) ) ; const endDateAfterPause = baseVoteEndDate . add ( bn ( this . pauseDuration ) ) ; return actualEndDate . sub ( endDateAfterPause ) . toString ( ) ; } get yeasPct ( ) : string { return this . _votingPowerPct ( this . yeas ) ; } get naysPct ( ) : string { return this . _votingPowerPct ( this . nays ) ; } get formattedYeas ( ) : string { return formatBn ( this . yeas , this . tokenDecimals ) ; } get formattedYeasPct ( ) : string { return formatBn ( this . yeasPct , PCT_DECIMALS ) ; } get formattedNays ( ) : string { return formatBn ( this . nays , this . tokenDecimals ) ; } get formattedNaysPct ( ) : string { return formatBn ( this . naysPct , PCT_DECIMALS ) ; } get formattedTotalPower ( ) : string { return formatBn ( this . totalPower , this . tokenDecimals ) ; } get status ( ) : string { if ( this . hasEnded ) { if ( this . voteStatus === 'Scheduled' ) { return this . isAccepted ? 'Accepted' : 'Rejected' ; } else if ( this . voteStatus === 'Challenged' ) { return 'Settled' ; } } return this . voteStatus ; } get wasFlipped ( ) : boolean { if ( this . quietEndingSnapshotSupport == 'Absent' ) { return false ; } const wasInitiallyAccepted = this . quietEndingSnapshotSupport == 'Yea' ; const currentExtensions = bn ( this . quietEndingExtensionDuration ) . div ( bn ( this . quietEndingExtension ) ) ; const wasAcceptedBeforeLastFlip = wasInitiallyAccepted == ( currentExtensions . mod ( bn ( '2' ) ) . eq ( bn ( '0' ) ) ) ; return wasAcceptedBeforeLastFlip != this . isAccepted ; } async hasEndedExecutionDelay ( ) { const setting = await this . setting ( ) ; const currentTimestamp = currentTimestampEvm ( ) ; const executionDelayEndDate = bn ( this . endDate ) . add ( setting . executionDelay ) ; return currentTimestamp . gte ( executionDelayEndDate ) ; } async canVote ( voterAddress ) { return ! this . hasEnded && this . voteStatus === 'Scheduled' && ! ( await this . hasVoted ( voterAddress ) ) && ( await this . votingPower ( voterAddress ) ) . gt ( bn ( 0 ) ) ; } async canExecute ( ) { return this . isAccepted && this . voteStatus === 'Scheduled' && ( await this . hasEndedExecutionDelay ( ) ) ; } async votingPower ( voterAddress ) { const token = await this . token ( ) ; return token . balanceAt ( voterAddress , this . snapshotBlock ) ; } async formattedVotingPower ( voterAddress ) { const token = await this . token ( ) ; const balance = await token . balanceAt ( voterAddress , this . snapshotBlock ) ; return formatBn ( balance , token . decimals ) ; } async token ( ) { return this . #connector . ERC20 ( this . tokenId ) ; } async hasVoted ( voterAddress ) { const castVote = await this . castVote ( voterAddress ) ; return castVote !== null ; } onHasVoted ( voterAddress , callback ? ) { return subscription < any > ( ( error , castVote ) => { callback ?. ( error , error ? undefined : ( castVote !== null ) as boolean ) ; } , ( callback ) => this . #connector . onCastVote ( this . castVoteId ( voterAddress ) , callback ) ) ; } castVoteId ( voterAddress ) { return `${ this . id }-cast-${ voterAddress . toLowerCase ( ) }` ; } async castVote ( voterAddress ) { return this . #connector . castVote ( this . castVoteId ( voterAddress ) ) ; } onCastVote ( voterAddress , callback ? ) { return subscription < CastVote | null > ( callback , ( callback ) => this . #connector . onCastVote ( this . castVoteId ( voterAddress ) , callback ) ) ; } async castVotes ( { first = 1000 , skip = 0 } = { } ) { return this . #connector . castVotes ( this . id , first , skip ) ; } onCastVotes ( { first = 1000 , skip = 0 } = { } , callback ? ) { return subscription < CastVote [ ] > ( callback , ( callback ) => this . #connector . onCastVotes ( this . id , first , skip , callback ) ) ; } async collateralRequirement ( ) { return this . #connector . collateralRequirement ( this . collateralRequirementId ) ; } onCollateralRequirement ( callback ? ) { return subscription < CollateralRequirement > ( callback , ( callback ) => this . #connector . onCollateralRequirement ( this . collateralRequirementId , callback ) ) ; } async submitterArbitratorFee ( ) { return this . #connector . arbitratorFee ( this . submitterArbitratorFeeId || '' ) ; } onSubmitterArbitratorFee ( callback ? ) { return subscription < ArbitratorFee | null > ( callback , ( callback ) => this . #connector . onArbitratorFee ( this . submitterArbitratorFeeId || '' , callback ) ) ; } async challengerArbitratorFee ( ) { return this . #connector . arbitratorFee ( this . challengerArbitratorFeeId || '' ) ; } onChallengerArbitratorFee ( callback ? ) { return subscription < ArbitratorFee | null > ( callback , ( callback ) => this . #connector . onArbitratorFee ( this . challengerArbitratorFeeId || '' , callback ) ) ; } async setting ( ) { return this . #connector . setting ( this . settingId ) ; } onSetting ( callback ? ) { return subscription < Setting > ( callback , ( callback ) => this . #connector . onSetting ( this . settingId , callback ) ) ; } async formattedSettlementOffer ( ) { if ( ! this . settlementOffer ) { return null ; } return formatBn ( this . settlementOffer , this . collateralTokenDecimals ) ; } _votingPowerPct ( num ) { const totalPower = bn ( this . totalPower ) ; return bn ( num ) . mul ( PCT_BASE ) . div ( totalPower ) . toString ( ) ; } }