import { EthApi } from "@joincivil/ethapi"; import { CivilErrors, getDefaultFromBlock } from "@joincivil/utils"; import { EthAddress, Param, BigNumber, ParamProposalState } from "@joincivil/typescript-types"; import * as Debug from "debug"; import { Observable } from "rxjs"; import { TwoStepEthTransaction } from "../../types"; import { BaseWrapper } from "../basewrapper"; import { GovernmentContract } from "../generated/wrappers/government"; import { Multisig } from "../multisig/multisig"; import { Voting } from "./voting"; import { createTwoStepSimple } from "../utils/contracts"; const debug = Debug("civil:tcr"); export const enum GovtParameters { requestAppealLen = "requestAppealLen", judgeAppealLen = "judgeAppealLen", appealFee = "appealFee", appealVotePercentage = "appealVotePercentage", govtPCommitStageLen = "govtPCommitStageLen", govtPRevealStageLen = "govtPRevealStageLen", } /** * The Government contract is where parameters related to appeals are stored and where * the controlling entities can update them and update the controlling entities as well */ export class Government extends BaseWrapper { public static async singleton(ethApi: EthApi, multisigAddress: EthAddress): Promise { const instance = await GovernmentContract.singletonTrusted(ethApi); if (!instance) { debug("Smart-contract wrapper for Parameterizer returned null, unsupported network"); throw new Error(CivilErrors.UnsupportedNetwork); } const multisig = Multisig.atUntrusted(ethApi, multisigAddress); const defaultBlock = getDefaultFromBlock(await ethApi.network()); return new Government(ethApi, instance, multisig, defaultBlock); } public static async atUntrusted(web3wrapper: EthApi, address: EthAddress): Promise { const instance = GovernmentContract.atUntrusted(web3wrapper, address); const appellateAddr = await instance.appellate.callAsync(); const multisig = Multisig.atUntrusted(web3wrapper, appellateAddr); const defaultBlock = getDefaultFromBlock(await web3wrapper.network()); return new Government(web3wrapper, instance, multisig, defaultBlock); } private multisig: Multisig; private constructor(ethApi: EthApi, instance: GovernmentContract, multisig: Multisig, defaultBlock: number) { super(ethApi, instance, defaultBlock); this.multisig = multisig; } /** * Returns Voting instance associated with this Government */ public async getVoting(): Promise { return Voting.atUntrusted(this.ethApi, await this.getVotingAddress()); } /** * Get address for voting contract used with this Government */ public async getVotingAddress(): Promise { return this.instance.voting.callAsync(); } /** * Gets an unending stream of parameters being set */ public getParameterSet(fromBlock: number = this.defaultBlock): Observable { return this.instance._ParameterSetStream({}, { fromBlock }).map(e => { return { paramName: e.returnValues.name, value: new BigNumber(e.returnValues.value), }; }); } public async getAppealFee(): Promise { return this.getParameterValue("appealFee"); } /** * Gets the current value of the specified parameter * @param parameter key of parameter to check */ public async getParameterValue(parameter: string): Promise { return this.instance.get.callAsync(parameter).then(d => new BigNumber(d)); } /** * Set value of Government Parameter * @param paramName name of parameter you intend to change * @param newValue value you want parameter to be changed to */ public async set(paramName: GovtParameters | string, newValue: BigNumber): Promise> { const txdata = await this.instance.proposeReparameterization.getRaw(paramName, newValue.toString(), { gas: 0 }); return this.multisig.submitTransaction(this.instance.address, this.ethApi.toBigNumber(0), txdata.data!); } public async processProposal(propID: string): Promise { return createTwoStepSimple(this.ethApi, await this.instance.processProposal.sendTransactionAsync(propID)); } /** * Get the URI of the Civil Constitution */ public async getConstitutionURI(): Promise { return this.instance.constitutionURI.callAsync(); } /** * Get the hash of the Civil Constitution */ public async getConstitutionHash(): Promise { return this.instance.constitutionHash.callAsync(); } public async getAppellate(): Promise { return this.instance.getAppellate.callAsync(); } public async getController(): Promise { return this.instance.getGovernmentController.callAsync(); } /** * An unending stream of the propIDs of all Reparametization proposals currently in * Challenge Commit Phase * @param fromBlock Starting block in history for events. Set to "latest" for only new events. * @returns currently active proposals in Challenge Commit Phase propIDs */ public propIDsInCommitPhase(fromBlock: number = this.defaultBlock): Observable { return this.instance ._GovtReparameterizationProposalStream({}, { fromBlock }) .map(e => e.returnValues.propID) .concatFilter(async propID => this.isPropInCommitPhase(propID)); } /** * An unending stream of the propIDs of all Reparametization proposals currently in * Challenge Reveal Phase * @param fromBlock Starting block in history for events. Set to "latest" for only new events * @returns currently active proposals in Challenge Reveal Phase propIDs */ public propIDsInRevealPhase(fromBlock: number = this.defaultBlock): Observable { return this.instance ._GovtReparameterizationProposalStream({}, { fromBlock }) .map(e => e.returnValues.propID) .concatFilter(async propID => this.isPropInRevealPhase(propID)); } /** * An unending stream of the propIDs of all Reparametization proposals that can be * processed right now. Includes unchallenged applications that have passed their application * expiry time, and proposals with challenges that can be resolved. * @param fromBlock Starting block in history for events. Set to "latest" for only new events * @returns propIDs for proposals that can be updated */ public propIDsToProcess(fromBlock: number = this.defaultBlock): Observable { return this.instance ._GovtReparameterizationProposalStream({}, { fromBlock }) .map(e => e.returnValues.propID) .concatFilter(async propID => this.isPropInResolvePhase(propID)); } public async getPropState(propID: string): Promise { if (!(await this.instance.propExists.callAsync(propID))) { return ParamProposalState.NOT_FOUND; } else if (await this.isPropInCommitPhase(propID)) { return ParamProposalState.CHALLENGED_IN_COMMIT_VOTE_PHASE; } else if (await this.isPropInRevealPhase(propID)) { return ParamProposalState.CHALLENGED_IN_REVEAL_VOTE_PHASE; } else if (await this.isPropInResolvePhase(propID)) { return ParamProposalState.READY_TO_RESOLVE_CHALLENGE; } else { return ParamProposalState.NOT_FOUND; } } /** * Returns whether or not a Proposal is in the Challenge Commit Phase * @param propID ID of prop to check */ public async isPropInCommitPhase(propID: string): Promise { 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)); } /** * Returns whether or not a Proposal is in the Challenge Reveal Phase * @param propID ID of prop to check */ public async isPropInRevealPhase(propID: string): Promise { 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)); } /** * Returns whether or not a Proposal is in the Challenge Resolve Phase * @param propID ID of prop to check */ public async isPropInResolvePhase(propID: string): Promise { 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)); } /** * Gets the current ChallengeID of the specified parameter * @param parameter key of parameter to check */ public async getChallengeID(parameter: string): Promise { const [challengeID] = await this.instance.proposals.callAsync(parameter); return new BigNumber(challengeID); } /** * * @param propID id of proposal to check * @throws {CivilErrors.NoChallenge} */ public async getPropCommitExpiry(propID: string): Promise { 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 id of proposal to check * @throws {CivilErrors.NoChallenge} */ public async getPropRevealExpiry(propID: string): Promise { 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); } /** * Returns the date by which a proposal must be processed. Successful proposals must * be processed within a certain timeframe, to avoid gaming the parameterizer. * @param propID ID of prop to check */ public async getPropProcessBy(propID: string): Promise { const [, , expiryTimestamp] = await this.instance.proposals.callAsync(propID); return new Date(new BigNumber(expiryTimestamp).toNumber() * 1000); } /** * Returns the name of the paramater associated with the given proposal * @param propID ID of prop to check */ public async getPropName(propID: string): Promise { const [, name] = await this.instance.proposals.callAsync(propID); return name; } /** * Returns the value proposed associated with the given proposal * @param propID ID of prop to check */ public async getPropValue(propID: string): Promise { const [, , , value] = await this.instance.proposals.callAsync(propID); return new BigNumber(value); } }