"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const bignumber_js_1 = require("bignumber.js");
const ethereumjs = require("ethereumjs-abi");
const avatarService_1 = require("../avatarService");
const configService_1 = require("../configService");
const contractWrapperFactory_1 = require("../contractWrapperFactory");
const iContractWrapperBase_1 = require("../iContractWrapperBase");
const proposalService_1 = require("../proposalService");
const transactionService_1 = require("../transactionService");
const utils_1 = require("../utils");
const web3EventService_1 = require("../web3EventService");
const es6_promisify_1 = require("es6-promisify");
const loggingService_1 = require("../loggingService");
const utilsInternal_1 = require("../utilsInternal");
const erc20_1 = require("./erc20");
const intVoteInterface_1 = require("./intVoteInterface");
class GenesisProtocolWrapper extends intVoteInterface_1.IntVoteInterfaceWrapper {
    constructor() {
        super(...arguments);
        this.name = "GenesisProtocol";
        this.friendlyName = "Genesis Protocol";
        this.factory = exports.GenesisProtocolFactory;
    }
    /* tslint:enable:max-line-length */
    /**
     * Stake some tokens on the final outcome matching this vote.
     *
     * A transfer of tokens from the staker to this GenesisProtocol scheme
     * is automatically approved and executed on the token with which
     * this GenesisProtocol scheme was deployed.
     *
     * @param {StakeConfig} options
     * @returns Promise<ArcTransactionResult>
     */
    stake(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            yield this._validateVote(options.vote, options.proposalId);
            const amount = new bignumber_js_1.BigNumber(options.amount);
            if (amount.lte(0)) {
                throw new Error("amount must be > 0");
            }
            const autoApproveTransfer = configService_1.ConfigService.get("autoApproveTokenTransfers");
            const functionName = "GenesisProtocol.stake";
            const payload = transactionService_1.TransactionService.publishKickoffEvent(functionName, options, 1 + (autoApproveTransfer ? 1 : 0));
            const eventContext = transactionService_1.TransactionService.newTxEventContext(functionName, payload, options);
            /**
             * approve immediate transfer of staked tokens to this scheme
             */
            if (autoApproveTransfer) {
                const stakingToken = yield this.getStakingToken();
                const result = yield stakingToken.approve({
                    amount,
                    spender: this.address,
                    txEventContext: eventContext,
                });
                yield result.watchForTxMined();
            }
            this.logContractFunctionCall("GenesisProtocol.stake", options);
            const tx = yield this.sendTransaction(eventContext, this.contract.stake, [options.proposalId, options.vote, amount]);
            if (tx) {
                transactionService_1.TransactionService.publishTxLifecycleEvents(eventContext, tx, this.contract);
            }
            return new iContractWrapperBase_1.ArcTransactionResult(tx, this.contract);
        });
    }
    /**
     * Preapprove the transfer of stakingTokens from the default account to this GenesisProtocol contract,
     * and then stake, all in a single transaction.
     * @param options
     */
    stakeWithApproval(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            yield this._validateVote(options.vote, options.proposalId);
            const amount = new bignumber_js_1.BigNumber(options.amount);
            if (amount.lte(0)) {
                throw new Error("amount must be > 0");
            }
            const stakingTokenAddress = yield this.getStakingTokenAddress();
            const stakingToken = (yield (yield utils_1.Utils.requireContract("ERC827Token")).at(stakingTokenAddress));
            if (!stakingToken) {
                throw new Error("GenesisProtocol.stakeWithApproval: token must implement ERC827Token");
            }
            const staker = yield utils_1.Utils.getDefaultAccount();
            const nonce = utilsInternal_1.UtilsInternal.getRandomNumber();
            const web3 = yield utils_1.Utils.getWeb3();
            let signature;
            let signatureType;
            if (web3.currentProvider.isMetaMask) {
                const msgParams = [
                    {
                        // Any string label you want
                        name: "GenesisProtocolAddress",
                        // Any valid solidity type
                        type: "address",
                        // The value to sign
                        value: this.address,
                    },
                    {
                        name: "ProposalId",
                        type: "bytes32",
                        value: options.proposalId,
                    },
                    {
                        name: "Vote",
                        type: "uint",
                        value: options.vote,
                    },
                    {
                        name: "AmountToStake",
                        type: "uint",
                        value: amount.toString(10),
                    },
                    {
                        name: "Nonce",
                        type: "uint",
                        value: nonce,
                    },
                ];
                const result = yield es6_promisify_1.promisify((callback) => web3.currentProvider.sendAsync({
                    from: staker,
                    method: "eth_signTypedData",
                    params: [msgParams, staker],
                }, callback))();
                if (result.error) {
                    throw new Error(`stakeWithApproval: ${result.error.message}`);
                }
                signature = result.result;
                signatureType = 2;
            }
            else {
                const textMsg = "0x" + ethereumjs.soliditySHA3(["address", "bytes32", "uint", "uint", "uint"], [this.address, options.proposalId, options.vote, amount.toString(10), nonce]).toString("hex");
                signature = yield es6_promisify_1.promisify((callback) => web3.eth.sign(staker, textMsg, callback))();
                signatureType = 1;
            }
            const extraData = yield this.contract.stakeWithSignature.request(options.proposalId, options.vote, amount.toString(10), nonce, signatureType, signature);
            this.logContractFunctionCall("GenesisProtocol.stakeWithApproval", options);
            /**
             * We are not using DaoTokenWrapper here because we can't be sure the stakingToken is one.
             * We only know it is ERC827Token, and have retrieved it above as such.
             */
            return this.wrapTransactionInvocation("GenesisProtocol.stakeWithApproval", options, stakingToken.approveAndCall, [this.address, amount.toString(10), extraData.params[0].data], { from: staker });
        });
    }
    /**
     * Redeem any tokens and reputation, excluding bounty, that are due the beneficiary from the outcome of the proposal.
     * @param {RedeemConfig} options
     * @returns Promise<ArcTransactionResult>
     */
    redeem(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.beneficiaryAddress) {
                throw new Error("beneficiaryAddress is not defined");
            }
            const proposalState = yield this.getState({ proposalId: options.proposalId });
            if ((proposalState !== ProposalState.Executed) &&
                (proposalState !== ProposalState.Closed)) {
                /* tslint:disable-next-line:max-line-length */
                throw new Error(`cannot redeem unless proposal state is either executed or closed. Current state: ${ProposalState[proposalState]}`);
            }
            this.logContractFunctionCall("GenesisProtocol.redeem", options);
            return this.wrapTransactionInvocation("GenesisProtocol.redeem", options, this.contract.redeem, [options.proposalId, options.beneficiaryAddress]);
        });
    }
    /**
     * Redeem any token bounty that are due the beneficiary from the outcome of the proposal.
     * @param {RedeemConfig} options
     * @returns Promise<ArcTransactionResult>
     */
    redeemDaoBounty(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.beneficiaryAddress) {
                throw new Error("beneficiaryAddress is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.redeemDaoBounty", options);
            const proposalState = yield this.getState({ proposalId: options.proposalId });
            if ((proposalState !== ProposalState.Executed) &&
                (proposalState !== ProposalState.Closed)) {
                throw new Error("cannot redeem bounty unless proposal state is either executed or closed");
            }
            return this.wrapTransactionInvocation("GenesisProtocol.redeemDaoBounty", options, this.contract.redeemDaoBounty, [options.proposalId, options.beneficiaryAddress]);
        });
    }
    /**
     * Return whether a proposal should be shifted to the boosted phase.
     * @param {ShouldBoostConfig} options
     * @returns Promise<boolean>
     */
    shouldBoost(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.shouldBoost", options);
            return this.contract.shouldBoost(options.proposalId);
        });
    }
    /**
     * Return the current proposal score.
     * @param {GetScoreConfig} options
     * @returns Promise<BigNumber>
     */
    getScore(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.score", options);
            // TODO:  convert to a number?
            return this.contract.score(options.proposalId);
        });
    }
    /**
     * Return the threshold that is required for a proposal to be shifted into the boosted state.
     * The computation depends on the current number of boosted proposals that were created for
     * this voting machine by the scheme at `schemeInfo.address` for the given `avatarAddress`
     * (`avatarAddress` is the `organizationAddress` given to the `propose` method),
     * and the GenesisProtocol parameters `thresholdConstA` and `thresholdConstB` which
     * are registered on behalf of the given scheme with an avatar's Controller,
     * keyed by the given `avatarAddress`.
     *
     * Thus `avatarAddress` must be an avatar address and this function will not work if
     * you pass for `avatarAddress` any other contract address than an avatar, even though
     * GenesisProtocol is capable of using those non-avatar contract addresses.
     * In those cases, when the organization is not an avatar, you must use `getThresholdFromProposal`
     * to obtain the threshold.
     *
     * Note that there will be a separately-scoped threshold for proposals created for this
     * voting machine by any other scheme than the one given.
     *
     * Note that for Arc.js schemes you can just pass an Arc.js contract wrapper for `schemeInfo`.
     */
    getThresholdForSchemeAndCreator(schemeInfo, avatarAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!schemeInfo) {
                throw new Error("schemeAddress is not defined");
            }
            if (!avatarAddress) {
                throw new Error("avatarAddress is not defined");
            }
            const organizationId = this.organizationIdFromProposalCreator(schemeInfo.address, avatarAddress);
            const votingMachineInfo = yield schemeInfo.getSchemeParameters(avatarAddress);
            if (!votingMachineInfo) {
                throw new Error("votingMachineInfo is null, avatarAddress may not be an avatar");
            }
            if (votingMachineInfo.votingMachineAddress !== this.address) {
                throw new Error("the scheme is not using this voting machine");
            }
            const votingMachineParamsHash = votingMachineInfo.voteParametersHash;
            if (utilsInternal_1.UtilsInternal.isNullHash(votingMachineParamsHash)) {
                throw new Error("avatarAddress does not yield a parameters hash, may not be an avatar");
            }
            this.logContractFunctionCall("GenesisProtocol.threshold", { address: schemeInfo.address, avatarAddress, votingMachineParamsHash, organizationId });
            return this.contract.threshold(votingMachineParamsHash, organizationId);
        });
    }
    /**
     * Return the threshold that is required for a proposal to be shifted into the boosted state.
     * The computation depends on the current number of boosted proposals that were created for
     * this voting machine by the scheme that created the given proposal,
     * and the GenesisProtocol parameters `thresholdConstA` and `thresholdConstB` which
     * are stored with the proposal.
     *
     * Note that there will be a separately-scoped threshold for proposals created for this
     * voting machine by any other scheme than the one that created the given proposal.
     */
    getThresholdFromProposal(proposalId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!proposalId) {
                throw new Error("proposalId is not defined");
            }
            const proposal = yield this.getProposal(proposalId);
            this.logContractFunctionCall("GenesisProtocol.threshold", { proposalId, parametersHash: proposal.paramsHash, organizationId: proposal.organizationId });
            return this.contract.threshold(proposal.paramsHash, proposal.organizationId);
        });
    }
    /**
     * Returns a promise of the number of boosted proposals by the address of the scheme
     * that created the proposal and the "creator address" which is typically the avatar for proposals
     * created by the Arc universal schemes.
     * The count does not include those that have expired but have not yet
     * been executed to update their status.
     */
    getBoostedProposalsCount(schemeAddress, creatorAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!schemeAddress) {
                throw new Error("schemeAddress is not defined");
            }
            if (!creatorAddress) {
                throw new Error("creatorAddress is not defined");
            }
            const organizationId = this.organizationIdFromProposalCreator(schemeAddress, creatorAddress);
            this.logContractFunctionCall("GenesisProtocol.getBoostedProposalsCount", { schemeAddress, creatorAddress, organizationId });
            return this.contract.getBoostedProposalsCount(organizationId);
        });
    }
    /**
     * Return the current balances on this GenesisProtocol's staking and the given avatar's native tokens.
     * This can be useful, for example, if you want to know in advance whether the avatar has enough funds
     * at the moment to payout rewards to stakers and voters.
     * It also returns the respective tokens' truffle contracts.
     * @param options
     */
    getTokenBalances(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.avatarAddress) {
                throw new Error("avatarAddress is not defined");
            }
            const stakingToken = yield this.getStakingToken();
            const stakingTokenBalance = yield stakingToken.getBalanceOf(options.avatarAddress);
            const avatarService = new avatarService_1.AvatarService(options.avatarAddress);
            const nativeToken = yield avatarService.getNativeToken();
            const nativeTokenBalance = yield nativeToken.getBalanceOf(options.avatarAddress);
            return {
                nativeToken,
                nativeTokenBalance,
                stakingToken,
                stakingTokenBalance,
            };
        });
    }
    /**
     * Return the number of possible choices when voting for the proposal.
     * @param {GetNumberOfChoicesConfig} options
     * @returns Promise<number>
     */
    getNumberOfChoices(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.getNumberOfChoices", options);
            const numOfChoices = yield this.contract.getNumberOfChoices(options.proposalId);
            return numOfChoices.toNumber();
        });
    }
    /**
     * Return the vote and the amount of reputation of the voter committed to this proposal
     * @param {GetVoterInfoResult} options
     * @returns Promise<GetVoterInfoResult>
     */
    getVoterInfo(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.voter) {
                throw new Error("voter is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.voteInfo", options);
            const result = yield this.contract.voteInfo(options.proposalId, options.voter);
            return {
                reputation: result[1],
                vote: result[0].toNumber(),
            };
        });
    }
    /**
     * Returns the reputation currently voted on the given choice.
     * @param {GetVoteStatusConfig} options
     * @returns Promise<BigNumber>
     */
    getVoteStatus(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            yield this._validateVote(options.vote, options.proposalId);
            this.logContractFunctionCall("GenesisProtocol.voteStatus", options);
            /**
             * an array of number counts for each vote choice
             */
            return this.contract.voteStatus(options.proposalId, options.vote);
        });
    }
    /**
     * Return the preBoosted votes, amount staked per vote, total staked and
     * total staked net voters' take for a given proposal
     * @param {GetProposalStatusConfig} options
     * @returns Promise<GetProposalStatusResult>
     */
    getProposalStatus(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.proposalStatus", options);
            const result = yield this.contract.proposalStatus(options.proposalId);
            return {
                preBoostedVotesNo: result[1],
                preBoostedVotesYes: result[0],
                stakesNo: result[5],
                stakesYes: result[4],
                totalStaked: result[3],
                totalStakerStakes: result[2],
            };
        });
    }
    /**
     * For proposals created by Arc universal schemes, returns a promise of the address of the avatar
     * on whose behalf the proposal was created.  For proposals created by other schemes, the value
     * is defined by those contracts, but is expected to be an address.
     * @returns Promise<Address>
     */
    getProposalCreator(proposalId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!proposalId) {
                throw new Error("proposalId is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.getProposalOrganization", proposalId);
            const organizationId = yield this.getProposalOrganizationId(proposalId);
            if (!organizationId) {
                throw new Error("the proposal is not found");
            }
            return this.contract.organizations(organizationId);
        });
    }
    /**
     * Return the vote and stake amount for a given proposal and staker.
     * @param {GetStakerInfoConfig} options
     * @returns Promise<GetStakerInfoResult>
     */
    getStakerInfo(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.staker) {
                throw new Error("staker is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.getStaker", options);
            const result = yield this.contract.getStaker(options.proposalId, options.staker);
            return {
                stake: result[1],
                vote: result[0].toNumber(),
            };
        });
    }
    /**
     * Return the winningVote for a given proposal.
     * @param {GetWinningVoteConfig} options
     * @returns Promise<number>
     */
    getWinningVote(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.winningVote", options);
            const winningVote = yield this.contract.winningVote(options.proposalId);
            return winningVote.toNumber();
        });
    }
    /**
     * Return the current state of a given proposal.
     * @param {GetStateConfig} options
     * @returns Promise<number>
     */
    getState(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.state", options);
            const state = yield this.contract.state(options.proposalId);
            return state.toNumber();
        });
    }
    /**
     * EntityFetcherFactory for votable GenesisProtocolProposal.
     * @param avatarAddress
     */
    get VotableGenesisProtocolProposals() {
        const proposalService = new proposalService_1.ProposalService(this.web3EventService);
        return proposalService.getProposalEvents({
            proposalsEventFetcher: this.NewProposal,
            transformEventCallback: (event) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                return this.getProposal(event.args._proposalId);
            }),
            votableOnly: true,
            votingMachine: this,
        });
    }
    /**
     * Cancel the given proposal
     * @param options
     */
    cancelProposal(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            throw new Error("GenesisProtocol does not support cancelProposal");
        });
    }
    cancelVote(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            throw new Error("GenesisProtocol does not support cancelVote");
        });
    }
    /**
     * EntityFetcherFactory for executed ExecutedGenesisProposal.
     * The Arc GenesisProtocol contract retains the original proposal struct after execution.
     * @param avatarAddress
     */
    get ExecutedProposals() {
        return this.web3EventService
            .createEntityFetcherFactory(this.ExecuteProposal, (event) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const proposal = yield this.getProposal(event.args._proposalId);
            return Object.assign(proposal, {
                decision: event.args._decision.toNumber(),
                executionState: yield this.getProposalExecutionState(proposal.proposalId),
                totalReputation: event.args._totalReputation,
            });
        }));
    }
    /**
     * Returns a promise of the execution state of the given proposal.  The result is
     * ExecutionState.None if the proposal has not been executed or is not found.
     * @param proposalId
     */
    getProposalExecutionState(proposalId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const fetcher = this.GPExecuteProposal({ _proposalId: proposalId }, { fromBlock: 0 });
            const events = yield fetcher.get();
            return events.length ? events[0].args._executionState.toNumber() : ExecutionState.None;
        });
    }
    /**
     * Returns promise of a `GenesisProtocolProposal` for the given proposal id.
     * @param avatarAddress
     * @param proposalId
     */
    getProposal(proposalId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const proposalParams = yield this.contract.proposals(proposalId);
            const creatorAddress = yield this.getProposalCreator(proposalId);
            return this.convertProposalPropsArrayToObject(proposalParams, proposalId, creatorAddress);
        });
    }
    getParametersHash(params) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            params = Object.assign({}, yield exports.GetDefaultGenesisProtocolParameters(), params);
            return this._getParametersHash([
                params.preBoostedVoteRequiredPercentage || 0,
                params.preBoostedVotePeriodLimit,
                params.boostedVotePeriodLimit,
                params.thresholdConstA || 0,
                params.thresholdConstB || 0,
                params.minimumStakingFee || 0,
                params.quietEndingPeriod,
                params.proposingRepRewardConstA || 0,
                params.proposingRepRewardConstB || 0,
                params.stakerFeeRatioForVoters || 0,
                params.votersReputationLossRatio || 0,
                params.votersGainRepRatioFromLostRep || 0,
                params.daoBountyConst || 0,
                params.daoBountyLimit || 0,
            ], params.voteOnBehalf);
        });
    }
    /**
     * Set the contract parameters.
     * @param {GenesisProtocolParams} params
     * @returns parameters hash
     */
    setParameters(params) {
        const _super = name => super[name];
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            params = Object.assign({}, yield exports.GetDefaultGenesisProtocolParameters(), params);
            // in Wei
            const maxEthValue = new bignumber_js_1.BigNumber(10).pow(26);
            const minimumStakingFee = new bignumber_js_1.BigNumber(params.minimumStakingFee);
            if (minimumStakingFee.lt(0)) {
                throw new Error("minimumStakingFee must be greater than or equal to 0");
            }
            if (minimumStakingFee.gt(maxEthValue)) {
                throw new Error(`minimumStakingFee must be less than ${maxEthValue}`);
            }
            const proposingRepRewardConstA = params.proposingRepRewardConstA || 0;
            if ((proposingRepRewardConstA < 0) || (proposingRepRewardConstA > 100000000)) {
                throw new Error("proposingRepRewardConstA must be greater than or equal to 0 and less than or equal to 100000000");
            }
            const proposingRepRewardConstB = params.proposingRepRewardConstB || 0;
            if ((proposingRepRewardConstB < 0) || (proposingRepRewardConstB > 100000000)) {
                throw new Error("proposingRepRewardConstB must be greater than or equal to 0 and less than or equal to 100000000");
            }
            const thresholdConstA = new bignumber_js_1.BigNumber(params.thresholdConstA);
            if (thresholdConstA.lt(0)) {
                throw new Error("thresholdConstA must be greater than or equal to 0");
            }
            if (thresholdConstA.gt(maxEthValue)) {
                throw new Error(`thresholdConstA must be less than ${maxEthValue}`);
            }
            const thresholdConstB = params.thresholdConstB || 0;
            if ((thresholdConstB <= 0) || (thresholdConstB > 100000000)) {
                throw new Error("thresholdConstB must be greater than 0 and less than or equal to 100000000");
            }
            const preBoostedVoteRequiredPercentage = params.preBoostedVoteRequiredPercentage || 0;
            if ((preBoostedVoteRequiredPercentage <= 0) || (preBoostedVoteRequiredPercentage > 100)) {
                throw new Error("preBoostedVoteRequiredPercentage must be greater than 0 and less than or equal to 100");
            }
            const stakerFeeRatioForVoters = params.stakerFeeRatioForVoters || 0;
            if ((stakerFeeRatioForVoters < 0) || (stakerFeeRatioForVoters > 100)) {
                throw new Error("stakerFeeRatioForVoters must be greater than or equal to 0 and less than or equal to 100");
            }
            const votersGainRepRatioFromLostRep = params.votersGainRepRatioFromLostRep || 0;
            if ((votersGainRepRatioFromLostRep < 0) || (votersGainRepRatioFromLostRep > 100)) {
                throw new Error("votersGainRepRatioFromLostRep must be greater than or equal to 0 and less than or equal to 100");
            }
            const votersReputationLossRatio = params.votersReputationLossRatio || 0;
            if ((votersReputationLossRatio < 0) || (votersReputationLossRatio > 100)) {
                throw new Error("votersReputationLossRatio must be greater than or equal to  0 and less than or equal to 100");
            }
            const daoBountyConst = params.daoBountyConst || 0;
            if ((daoBountyConst <= stakerFeeRatioForVoters) || (daoBountyConst >= stakerFeeRatioForVoters * 2)) {
                throw new Error("daoBountyConst must be greater than stakerFeeRatioForVoters and less than 2*stakerFeeRatioForVoters");
            }
            const daoBountyLimit = new bignumber_js_1.BigNumber(params.daoBountyLimit);
            if (daoBountyLimit.lt(0)) {
                throw new Error("daoBountyLimit must be greater than or equal to 0");
            }
            return _super("_setParameters").call(this, "GenesisProtocol.setParameters", params.txEventContext, [
                preBoostedVoteRequiredPercentage,
                params.preBoostedVotePeriodLimit,
                params.boostedVotePeriodLimit,
                thresholdConstA,
                thresholdConstB,
                minimumStakingFee,
                params.quietEndingPeriod,
                proposingRepRewardConstA,
                proposingRepRewardConstB,
                stakerFeeRatioForVoters,
                votersReputationLossRatio,
                votersGainRepRatioFromLostRep,
                daoBountyConst,
                daoBountyLimit,
            ], params.voteOnBehalf);
        });
    }
    getParameters(paramsHash) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const paramsArray = yield this.getParametersArray(paramsHash);
            const params = {
                boostedVotePeriodLimit: paramsArray[2].toNumber(),
                minimumStakingFee: paramsArray[5],
                preBoostedVotePeriodLimit: paramsArray[1].toNumber(),
                preBoostedVoteRequiredPercentage: paramsArray[0].toNumber(),
                proposingRepRewardConstA: paramsArray[7].toNumber(),
                proposingRepRewardConstB: paramsArray[8].toNumber(),
                quietEndingPeriod: paramsArray[6].toNumber(),
                stakerFeeRatioForVoters: paramsArray[9].toNumber(),
                thresholdConstA: paramsArray[3],
                thresholdConstB: paramsArray[4].toNumber(),
                voteOnBehalf: paramsArray[12],
                votersGainRepRatioFromLostRep: paramsArray[11].toNumber(),
                votersReputationLossRatio: paramsArray[10].toNumber(),
            };
            const bountyParamsArray = yield this.contract.getDaoBountyParams(paramsHash);
            params.daoBountyConst = bountyParamsArray[0].toNumber();
            params.daoBountyLimit = bountyParamsArray[1];
            return params;
        });
    }
    /**
     * Returns promise of the staking token as Erc20Wrapper.
     * @returns Promise<Erc20Wrapper>
     */
    getStakingToken() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const tokenAddress = yield this.getStakingTokenAddress();
            // Erc20Wrapper includes `approve`, which is required for staking
            return erc20_1.Erc20Factory.at(tokenAddress);
        });
    }
    /**
     * Returns promise of the staking token address.
     * @returns Promise<Address>
     */
    getStakingTokenAddress() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.logContractFunctionCall("GenesisProtocol.stakingToken");
            return yield this.contract.stakingToken();
        });
    }
    propose(options) {
        const _super = name => super[name];
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const functionName = "GenesisProtocol.propose";
            const payload = transactionService_1.TransactionService.publishKickoffEvent(functionName, options, 1);
            const eventContext = transactionService_1.TransactionService.newTxEventContext(functionName, payload, options);
            return _super("propose").call(this, Object.assign(options, { txEventContext: eventContext }));
        });
    }
    vote(options) {
        const _super = name => super[name];
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const functionName = "GenesisProtocol.vote";
            const payload = transactionService_1.TransactionService.publishKickoffEvent(functionName, options, 1);
            const eventContext = transactionService_1.TransactionService.newTxEventContext(functionName, payload, options);
            return _super("vote").call(this, Object.assign(options, { txEventContext: eventContext }));
        });
    }
    voteWithSpecifiedAmounts(options) {
        const _super = name => super[name];
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const functionName = "GenesisProtocol.voteWithSpecifiedAmounts";
            const payload = transactionService_1.TransactionService.publishKickoffEvent(functionName, options, 1);
            const eventContext = transactionService_1.TransactionService.newTxEventContext(functionName, payload, options);
            return _super("voteWithSpecifiedAmounts").call(this, Object.assign(options, { txEventContext: eventContext }));
        });
    }
    execute(options) {
        const _super = name => super[name];
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const functionName = "GenesisProtocol.execute";
            const payload = transactionService_1.TransactionService.publishKickoffEvent(functionName, options, 1);
            const eventContext = transactionService_1.TransactionService.newTxEventContext(functionName, payload, options);
            return _super("execute").call(this, Object.assign(options, { txEventContext: eventContext }));
        });
    }
    hydrated() {
        super.hydrated();
        /* tslint:disable:max-line-length */
        this.GPExecuteProposal = this.createEventFetcherFactory(this.contract.GPExecuteProposal);
        this.Stake = this.createEventFetcherFactory(this.contract.Stake);
        this.Redeem = this.createEventFetcherFactory(this.contract.Redeem);
        this.RedeemReputation = this.createEventFetcherFactory(this.contract.RedeemReputation);
        this.RedeemDaoBounty = this.createEventFetcherFactory(this.contract.RedeemDaoBounty);
        /* tslint:enable:max-line-length */
    }
    /**
     * Returns a promise of the `organizationId`of the proposal.  This id is unique to the
     * contract that created the proposal and, in the case of Arc schemes, the avatar in which
     * the scheme is registered.  The value is included in `GenesisProtocolProposal`
     * and is returned by all of the voting machine events.  So you could use this value when filtering
     * events from a single avatar and contract registered with the avatar.
     */
    getProposalOrganizationId(proposalId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!proposalId) {
                throw new Error("proposalId is not defined");
            }
            this.logContractFunctionCall("GenesisProtocol.getProposalOrganization", proposalId);
            return this.contract.getProposalOrganization(proposalId);
        });
    }
    convertProposalPropsArrayToObject(proposalArray, proposalId, creatorAddress) {
        return {
            boostedPhaseTime: proposalArray[5].toNumber(),
            creatorAddress,
            currentBoostedVotePeriodLimit: proposalArray[9].toNumber(),
            daoBountyRemain: proposalArray[11],
            numOfChoices: proposalArray[2].toNumber(),
            organizationId: proposalArray[0],
            paramsHash: proposalArray[10],
            proposalId,
            proposer: proposalArray[8],
            state: proposalArray[6].toNumber(),
            submittedTime: proposalArray[4].toNumber(),
            votersStakes: proposalArray[3],
            winningVote: proposalArray[7].toNumber(),
        };
    }
}
exports.GenesisProtocolWrapper = GenesisProtocolWrapper;
/**
 * defined just to add good type checking
 */
class GenesisProtocolFactoryType extends contractWrapperFactory_1.ContractWrapperFactory {
    /**
     * Migrate a new instance of GenesisProtocol.
     * @param stakingTokenAddress The token that will be used when staking.  Typically
     * is the token of the DAO that is going to use this GenesisProtocol.
     */
    new(stakingTokenAddress) {
        const _super = name => super[name];
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            /**
             * We always have to estimate gas here, regardless of the "estimateGas" config setting,
             * because truffle's default gas limit does not suffice
             */
            const estimate = yield _super("estimateConstructorGas").call(this, stakingTokenAddress);
            loggingService_1.LoggingService.debug(`Instantiating GenesisProtocol with gas: ${estimate}`);
            return _super("new").call(this, stakingTokenAddress, { gas: estimate });
        });
    }
}
exports.GenesisProtocolFactoryType = GenesisProtocolFactoryType;
exports.GenesisProtocolFactory = new GenesisProtocolFactoryType("GenesisProtocol", GenesisProtocolWrapper, new web3EventService_1.Web3EventService());
var ExecutionState;
(function (ExecutionState) {
    ExecutionState[ExecutionState["None"] = 0] = "None";
    ExecutionState[ExecutionState["PreBoostedTimeOut"] = 1] = "PreBoostedTimeOut";
    ExecutionState[ExecutionState["PreBoostedBarCrossed"] = 2] = "PreBoostedBarCrossed";
    ExecutionState[ExecutionState["BoostedTimeOut"] = 3] = "BoostedTimeOut";
    ExecutionState[ExecutionState["BoostedBarCrossed"] = 4] = "BoostedBarCrossed";
})(ExecutionState = exports.ExecutionState || (exports.ExecutionState = {}));
var ProposalState;
(function (ProposalState) {
    ProposalState[ProposalState["None"] = 0] = "None";
    ProposalState[ProposalState["Closed"] = 1] = "Closed";
    ProposalState[ProposalState["Executed"] = 2] = "Executed";
    ProposalState[ProposalState["PreBoosted"] = 3] = "PreBoosted";
    ProposalState[ProposalState["Boosted"] = 4] = "Boosted";
    ProposalState[ProposalState["QuietEndingPeriod"] = 5] = "QuietEndingPeriod";
})(ProposalState = exports.ProposalState || (exports.ProposalState = {}));
exports.GetDefaultGenesisProtocolParameters = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
    const web3 = yield utils_1.Utils.getWeb3();
    return {
        boostedVotePeriodLimit: 259200,
        daoBountyConst: 75,
        daoBountyLimit: web3.toWei(100),
        minimumStakingFee: web3.toWei(0),
        preBoostedVotePeriodLimit: 1814400,
        preBoostedVoteRequiredPercentage: 50,
        proposingRepRewardConstA: 5,
        proposingRepRewardConstB: 5,
        quietEndingPeriod: 86400,
        stakerFeeRatioForVoters: 50,
        thresholdConstA: web3.toWei(7),
        thresholdConstB: 3,
        voteOnBehalf: utils_1.Utils.NULL_ADDRESS,
        votersGainRepRatioFromLostRep: 80,
        votersReputationLossRatio: 1,
    };
});
//# sourceMappingURL=genesisProtocol.js.map