import { Bytes32 , EthAddress , BigNumber , ParamProposalState , ParamProp , PollID , ParamPropChallengeData , UserChallengeData , WrappedPropID , } from "@joincivil/typescript-types" ; import { Observable } from "rxjs" ; import * as Debug from "debug" ; import { CivilErrors , getDefaultFromBlock , pollHelpers } from "@joincivil/utils" ; import { TwoStepEthTransaction } from "../../types" ; import { EthApi , requireAccount } from "@joincivil/ethapi" ; import { BaseWrapper } from "../basewrapper" ; import { CivilParameterizerContract } from "../generated/wrappers/civil_parameterizer" ; import { createTwoStepSimple } from "../utils/contracts" ; import { Voting } from "./voting" ; const debug = Debug ( "civil:tcr" ) ; export const enum Parameters { minDeposit = "minDepost" , pMinDeposit = "pMinDeposit" , applyStageLen = "applyStageLen" , pApplyStageLen = "pApplyStageLen" , commitStageLen = "commitStageLen" , pCommitStageLen = "pCommitStageLen" , revealStageLen = "revealStageLen" , pRevealStageLen = "pRevealStageLen" , dispensationPct = "dispensationPct" , pDispensationPct = "pDispensationPct" , voteQuorum = "voteQuorum" , pVoteQuorum = "pVoteQuorum" , challengeAppealLen = "challengeAppealLen" , challengeAppealCommitLen = "challengeAppealCommitLen" , challengeAppealRevealLen = "challengeAppealRevealLen" } function extractPropID ( e ) { return e . returnValues . propID ; } /**
 * The Parameterizer is where we store and update values associated with "parameters", variables
 * needed for logic of the Registry.
 * Users can propose new values for parameters, as well as challenge and then vote on those proposals
 */ export class Parameterizer extends BaseWrapper < CivilParameterizerContract > { public static async singleton ( ethApi ) { const instance = await CivilParameterizerContract . singletonTrusted ( ethApi ) ; if ( ! instance ) { debug ( "Smart-contract wrapper for Parameterizer returned null, unsupported network" ) ; throw new Error ( CivilErrors . UnsupportedNetwork ) ; } const defaultBlock = getDefaultFromBlock ( await ethApi . network ( ) ) ; return new Parameterizer ( ethApi , instance , await Voting . singleton ( ethApi ) , defaultBlock ) ; } public static async atUntrusted ( web3wrapper , address ) { const instance = CivilParameterizerContract . atUntrusted ( web3wrapper , address ) ; const defaultBlock = getDefaultFromBlock ( await web3wrapper . network ( ) ) ; return new Parameterizer ( web3wrapper , instance , await Voting . singleton ( web3wrapper ) , defaultBlock ) ; } private voting : Voting ; private constructor ( ethApi , instance , voting , defaultBlock ) { super ( ethApi , instance , defaultBlock ) ; this . voting = voting ; this . defaultBlock = defaultBlock ; } /**
     * Returns Voting instance associated with this TCR
     */ public async getVoting ( ) { return Voting . atUntrusted ( this . ethApi , await this . getVotingAddress ( ) ) ; } /**
     * Get address for voting contract used with this TCR
     */ public async getVotingAddress ( ) { return this . instance . voting . callAsync ( ) ; } public async getPropState ( propID ) { if ( ! ( await this . instance . propExists . callAsync ( propID ) ) ) { return ParamProposalState . NOT_FOUND ; } else if ( await this . isPropInUnchallengedApplicationPhase ( propID ) ) { return ParamProposalState . APPLYING ; } else if ( await this . isPropInUnchallengedApplicationUpdatePhase ( propID ) ) { return ParamProposalState . READY_TO_PROCESS ; } else if ( await this . isPropInChallengeCommitPhase ( propID ) ) { return ParamProposalState . CHALLENGED_IN_COMMIT_VOTE_PHASE ; } else if ( await this . isPropInChallengeRevealPhase ( propID ) ) { return ParamProposalState . CHALLENGED_IN_REVEAL_VOTE_PHASE ; } else if ( await this . isPropInChallengeResolvePhase ( propID ) ) { return ParamProposalState . READY_TO_RESOLVE_CHALLENGE ; } else { return ParamProposalState . NOT_FOUND ; } } /**
     * Event Streams
     */ param fromBlock returns public propIDsInApplicationPhase ( fromBlock = this . defaultBlock ) { return this . instance . _ReparameterizationProposalStream ( { } , { fromBlock } ) . map ( extractPropID ) . concatFilter ( async ( propID ) => this . isPropInUnchallengedApplicationPhase ( propID ) ) ; } param fromBlock returns public propIDsInChallengeCommitPhase ( fromBlock = this . defaultBlock ) { return this . instance . _NewChallengeStream ( { } , { fromBlock } ) . map ( extractPropID ) . concatFilter ( async ( propID ) => this . isPropInChallengeCommitPhase ( propID ) ) ; } param fromBlock returns public propIDsInChallengeRevealPhase ( fromBlock = this . defaultBlock ) { return this . instance . _NewChallengeStream ( { } , { fromBlock } ) . map ( extractPropID ) . concatFilter ( async ( propID ) => this . isPropInChallengeRevealPhase ( propID ) ) ; } public propIDsInChallenge ( fromBlock = this . defaultBlock ) { return this . instance . _NewChallengeStream ( { } , { fromBlock } ) . map ( extractPropID ) ; } param fromBlock returns public propIDsToProcess ( fromBlock = this . defaultBlock ) { const applicationsToProcess = this . instance . _ReparameterizationProposalStream ( { } , { fromBlock } ) . map ( extractPropID ) . concatFilter ( async ( propID ) => this . isPropInUnchallengedApplicationUpdatePhase ( propID ) ) ; const challengesToResolve = this . instance . _NewChallengeStream ( { } , { fromBlock } ) . map ( extractPropID ) . concatFilter ( async ( propID ) => this . isPropInChallengeResolvePhase ( propID ) ) ; return Observable . merge ( applicationsToProcess , challengesToResolve ) . distinct ( ) ; } param fromBlock returns public pollIDsForResolvedChallenges ( propID , fromBlock = this . defaultBlock ) { return this . instance . _NewChallengeStream ( { propID } , { fromBlock } ) . concatFilter ( async ( e ) => this . isChallengeResolved ( new BigNumber ( e . returnValues . challengeID ) ) ) . map ( e => new BigNumber ( e . returnValues . challengeID ) ) ; } param fromBlock returns public propIDsForResolvedChallenges ( fromBlock = this . defaultBlock ) { return this . instance . _NewChallengeStream ( { } , { fromBlock } ) . concatFilter ( async ( e ) => this . isChallengeResolved ( new BigNumber ( e . returnValues . challengeID ) ) ) . map ( extractPropID ) ; } public allProposalChallengeIDsEver ( ) { return this . instance . _NewChallengeStream ( { } , { fromBlock : this . defaultBlock } ) . map ( e => { return { propID : extractPropID ( e ) , challengeID : new BigNumber ( e . returnValues . challengeID ) , } ; } ) ; } /**
     * Contract Transactions
     */ param paramName param newValue public async proposeReparameterization ( paramName , newValue ) { return createTwoStepSimple ( this . ethApi , await this . instance . proposeReparameterization . sendTransactionAsync ( paramName , newValue . toString ( ) ) ) ; } param propID public async challengeReparameterization ( propID ) { return createTwoStepSimple ( this . ethApi , await this . instance . challengeReparameterization . sendTransactionAsync ( propID ) ) ; } param propID public async processProposal ( propID ) { return createTwoStepSimple ( this . ethApi , await this . instance . processProposal . sendTransactionAsync ( propID ) ) ; } param challengeID param salt public async claimReward ( challengeID , salt ) { return createTwoStepSimple ( this . ethApi , await this . instance . claimReward . sendTransactionAsync ( challengeID . toString ( ) , salt . toString ( ) ) ) ; } /**
     * Contract Getters
     */ param propID public async getProposal ( propID ) { const [ , challengeID , , name , , , value ] = await this . instance . proposals . callAsync ( propID ) ; return { propID , paramName : name , proposedValue : new BigNumber ( value ) , pollID : new BigNumber ( challengeID ) , } ; } param challengeID param salt param voter public async voterReward ( challengeID , salt , voter ? ) { let who = voter ; if ( ! who ) { who = await requireAccount ( this . ethApi ) . toPromise ( ) ; } return this . instance . voterReward . callAsync ( who , challengeID . toString ( ) , salt . toString ( ) ) . then ( e => new BigNumber ( e ) ) ; } param parameter public async getParameterValue ( parameter ) { return this . instance . get . callAsync ( parameter ) . then ( e => new BigNumber ( e ) ) ; } param parameter public async getChallengeID ( parameter ) { const [ , challengeID ] = await this . instance . proposals . callAsync ( parameter ) ; return new BigNumber ( challengeID ) ; } param challenge public async getChallengeData ( challengeID ) { const [ rewardPool , challenger , resolved , stake , totalTokens ] = await this . instance . challenges . callAsync ( challengeID . toString ( ) ) ; const voting = await this . getVoting ( ) ; const poll = await voting . getPoll ( challengeID ) ; return { rewardPool : new BigNumber ( rewardPool ) , challenger , resolved , stake : new BigNumber ( stake ) , totalTokens : new BigNumber ( totalTokens ) , poll , } ; } param propID public async isPropInUnchallengedApplicationPhase ( propID ) { if ( ! ( await this . instance . propExists . callAsync ( propID ) ) ) { return false ; } const [ appExpiry , challengeID ] = await this . instance . proposals . callAsync ( propID ) ; if ( ! new BigNumber ( challengeID ) . isZero ( ) ) { return false ; } const appExpiryDate = new Date ( new BigNumber ( appExpiry ) . toNumber ( ) * 1000 ) ; if ( appExpiryDate < new Date ( ) ) { return false ; } return true ; } param propID public async isPropInUnchallengedApplicationUpdatePhase ( propID ) { if ( ! ( await this . instance . propExists . callAsync ( propID ) ) ) { return false ; } const [ appExpiry , challengeID ] = await this . instance . proposals . callAsync ( propID ) ; if ( ! new BigNumber ( challengeID ) . isZero ( ) ) { return false ; } const appExpiryDate = new Date ( new BigNumber ( appExpiry ) . toNumber ( ) * 1000 ) ; if ( appExpiryDate > new Date ( ) ) { return false ; } return true ; } param propID public async isPropInChallengeCommitPhase ( propID ) { if ( ! ( await this . instance . propExists . callAsync ( propID ) ) ) { return false ; } const [ , challengeID ] = await this . instance . proposals . callAsync ( propID ) ; if ( new BigNumber ( challengeID ) . isZero ( ) ) { return false ; } const voting = await this . getVoting ( ) ; return voting . isCommitPeriodActive ( new BigNumber ( challengeID ) ) ; } param propID public async isPropInChallengeRevealPhase ( propID ) { if ( ! ( await this . instance . propExists . callAsync ( propID ) ) ) { return false ; } const [ , challengeID ] = await this . instance . proposals . callAsync ( propID ) ; if ( new BigNumber ( challengeID ) . isZero ( ) ) { return false ; } const voting = await this . getVoting ( ) ; return voting . isRevealPeriodActive ( new BigNumber ( challengeID ) ) ; } param propID public async isPropInChallengeResolvePhase ( propID ) { if ( ! ( await this . instance . propExists . callAsync ( propID ) ) ) { return false ; } const [ , challengeID ] = await this . instance . proposals . callAsync ( propID ) ; if ( new BigNumber ( challengeID ) . isZero ( ) ) { return false ; } const voting = await this . getVoting ( ) ; return voting . hasPollEnded ( new BigNumber ( challengeID ) ) ; } param propID public async isChallengeResolved ( pollID ) { const [ , , resolved ] = await this . instance . challenges . callAsync ( pollID . toString ( ) ) ; return resolved ; } param propID public async getPropApplicationExpiry ( propID ) { const expiryTimestamp = await this . getPropApplicationExpiryTimestamp ( propID ) ; return new Date ( expiryTimestamp . toNumber ( ) * 1000 ) ; } param propID throws public async getPropChallengeCommitExpiry ( propID ) { const challengeID = await this . getChallengeID ( propID ) ; if ( challengeID . isZero ( ) ) { throw CivilErrors . NoChallenge ; } const voting = await this . getVoting ( ) ; const poll = await voting . getPoll ( challengeID ) ; const expiryTimestamp = poll . commitEndDate ; return new Date ( expiryTimestamp . toNumber ( ) * 1000 ) ; } param propID throws public async getPropChallengeRevealExpiry ( propID ) { const challengeID = await this . getChallengeID ( propID ) ; if ( challengeID . isZero ( ) ) { throw CivilErrors . NoChallenge ; } const voting = await this . getVoting ( ) ; const poll = await voting . getPoll ( challengeID ) ; const expiryTimestamp = poll . revealEndDate ; return new Date ( expiryTimestamp . toNumber ( ) * 1000 ) ; } param propID public async getPropProcessBy ( propID ) { const [ , , , , , expiryTimestamp ] = await this . instance . proposals . callAsync ( propID ) ; return new Date ( new BigNumber ( expiryTimestamp ) . toNumber ( ) * 1000 ) ; } param propID public async getPropApplicationExpiryTimestamp ( propID ) { const [ appExpiry ] = await this . instance . proposals . callAsync ( propID ) ; return new BigNumber ( appExpiry ) ; } param propID public async getPropName ( propID ) { const [ , , , name ] = await this . instance . proposals . callAsync ( propID ) ; return name ; } param propID public async getPropValue ( propID ) { const [ , , , , , , value ] = await this . instance . proposals . callAsync ( propID ) ; return new BigNumber ( value ) ; } public async getUserProposalChallengeData ( propChallengeID , user ) { let didUserCommit ; let didUserReveal ; let didUserCollect ; let didUserRescue = false ; let didCollectAmount ; let isVoterWinner ; let salt ; let numTokens ; let choice ; let voterReward ; const [ , , resolved ] = await this . instance . challenges . callAsync ( propChallengeID . toString ( ) ) ; const pollData = await this . voting . getPoll ( propChallengeID ) ; let canUserReveal ; let canUserRescue ; let canUserCollect ; if ( user ) { didUserCommit = await this . voting . didCommitVote ( user , propChallengeID ) ; if ( didUserCommit ) { didUserReveal = await this . voting . didRevealVote ( user , propChallengeID ) ; if ( resolved ) { if ( didUserReveal ) { const reveal = await this . voting . getRevealedVoteEvent ( propChallengeID , user ) ; salt = reveal ! . returnValues . salt ; numTokens = reveal ! . returnValues . numTokens ; choice = reveal ! . returnValues . choice ; didUserCollect = await this . instance . tokenClaims . callAsync ( propChallengeID . toString ( ) , user ) ; isVoterWinner = await this . voting . isVoterWinner ( propChallengeID , user ) ; canUserCollect = isVoterWinner && ! didUserCollect ; } else { didUserRescue = ! ( await this . voting . canRescueTokens ( user , propChallengeID ) ) && ! ( await pollHelpers . isInCommitStage ( pollData ) ) && ! ( await pollHelpers . isInRevealStage ( pollData ) ) ; } } else { canUserReveal = ! didUserReveal && ( await pollHelpers . isInRevealStage ( pollData ) ) ; } canUserRescue = ! didUserReveal && ! didUserRescue && ! ( await pollHelpers . isInCommitStage ( pollData ) ) && ! ( await pollHelpers . isInRevealStage ( pollData ) ) ; } } if ( didUserCollect ) { didCollectAmount = await this . getRewardClaimed ( propChallengeID , user ) ; } if ( isVoterWinner && ! didUserCollect ) { voterReward = await this . voterReward ( propChallengeID , new BigNumber ( salt ! ) , user ) ; } return { didUserCommit , didUserReveal , canUserReveal , didUserCollect , canUserCollect , didUserRescue , canUserRescue , didCollectAmount , isVoterWinner , salt : new BigNumber ( salt ! ) , numTokens : new BigNumber ( numTokens as string ) , choice : new BigNumber ( choice as string ) , voterReward , } ; } public async getRewardClaimed ( challengeID , user ) { const reward = await this . instance . _RewardClaimedStream ( { challengeID , voter : user } , { fromBlock : this . defaultBlock } ) . first ( ) . toPromise ( ) ; return new BigNumber ( reward . returnValues . reward ) ; } }