"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const es6_promisify_1 = require("es6-promisify");
const configService_1 = require("./configService");
const iContractWrapperBase_1 = require("./iContractWrapperBase");
const loggingService_1 = require("./loggingService");
const transactionService_1 = require("./transactionService");
const utils_1 = require("./utils");
const utilsInternal_1 = require("./utilsInternal");
/**
 * Abstract base class for all Arc contract wrapper classes.
 *
 * Example of how to define a wrapper:
 *
 * ```
 * import { ContractWrapperBase } from "../contractWrapperBase";
 * import { ContractWrapperFactory } from "../contractWrapperFactory";
 *
 * export class AbsoluteVoteWrapper extends ContractWrapperBase {
 *   [ wrapper properties and methods ]
 * }
 *
 * export const AbsoluteVote = new ContractWrapperFactory(
 *  "AbsoluteVote",
 *  AbsoluteVoteWrapper,
 *  new Web3EventService());
 * ```
 */
class ContractWrapperBase {
    /**
     * ContractWrapperFactory constructs this
     * @param solidityContract The json contract truffle artifact
     * @param web3EventService
     */
    constructor(solidityContract, web3EventService) {
        this.solidityContract = solidityContract;
        this.web3EventService = web3EventService;
    }
    /**
     * The address of the contract
     */
    get address() { return this.contract.address; }
    /**
     * Initialize from a newly-migrated instance.
     * This will migrate a new instance of the contract to the net.
     * @returns this
     */
    hydrateFromNew(...rest) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                // Note that because we are using `.then`, we are returning a true promise
                // rather than the incomplete one returned by truffle.
                this.contract = yield this.solidityContract.new(...rest)
                    .then((contract) => contract, (error) => { throw error; });
                this.hydrated();
            }
            catch (ex) {
                loggingService_1.LoggingService.error(`hydrateFromNew failing: ${ex}`);
                return undefined;
            }
            return this;
        });
    }
    /**
     * Initialize from a given address on the current network.
     * @param address of the deployed contract
     * @returns this or undefined if not found
     */
    hydrateFromAt(address) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                // Note that because we are using `.then`, we are returning a true promise
                // rather than the incomplete one returned by truffle.
                this.contract = yield this.solidityContract.at(address)
                    .then((contract) => contract, (error) => { throw error; });
                this.hydrated();
            }
            catch (ex) {
                loggingService_1.LoggingService.error(`hydrateFromAt failing: ${ex}`);
                return undefined;
            }
            return this;
        });
    }
    /**
     * Given a hash, returns the associated parameters as an object.
     * @param paramsHash
     */
    getParameters(paramsHash) {
        throw new Error("getParameters has not been not implemented by the contract wrapper");
    }
    /**
     * Given a hash, returns the associated parameters as an array, ordered by the order
     * in which the parameters appear in the contract's Parameters struct.
     * @param paramsHash
     */
    getParametersArray(paramsHash) {
        return this.contract.parameters(paramsHash);
    }
    /**
     * Estimate conservatively the amount of gas required to execute the given function with the given parameters.
     * Adds 21000 to the estimate computed by web3.
     *
     * @param func The function
     * @param params The parameters to send to the function
     * @param web3Params The web3 parameters (like "from", for example).  If it contains "gas"
     * then that value is returned, effectively a no-op.
     */
    estimateGas(func, params, web3Params = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (web3Params.gas) {
                return web3Params.gas;
            }
            const currentNetwork = yield utils_1.Utils.getNetworkName();
            const maxGasLimit = yield utilsInternal_1.UtilsInternal.computeMaxGasLimit();
            // note that Ganache is identified specifically as the one instantiated by arc.js (by the networkId)
            if (currentNetwork === "Ganache") {
                return maxGasLimit; // because who cares with ganache and we can't get good estimates from it
            }
            /**
             * Add the input web3 params to the params we pass to estimateGas.
             * Include maxGasLimit just for doing the estimation.
             * I believe estimateGas will return a value will not exceed maxGasLimit.
             * If it returns maxGasLimit then the actual function will probably run out of gas when called for real.
             * TODO:  Throw an exception in that case?
             */
            params = params.concat(Object.assign({ gas: maxGasLimit }, web3Params));
            return Math.max(Math.min((yield func.estimateGas(...params)), maxGasLimit), 21000);
        });
    }
    /**
     * invoked to let base classes know that the `contract` is available.
     */
    /* tslint:disable-next-line:no-empty */
    hydrated() { }
    _setParameters(functionName, txEventContext, ...params) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const parametersHash = yield this.contract.getParametersHash(...params);
            const txResult = yield this.wrapTransactionInvocation(functionName, 
            // typically this is supposed to be an object, but here it is an array
            Object.assign(params, { txEventContext }), this.contract.setParameters, params);
            return new iContractWrapperBase_1.ArcTransactionDataResult(txResult.tx, this.contract, parametersHash);
        });
    }
    _getParametersHash(...params) {
        return this.contract.getParametersHash(...params);
    }
    /**
     * See [Web3EventService.createEventFetcherFactory](Web3EventService.md#createEventFetcherFactory).
     *
     * @type TArgs
     * @param eventName
     */
    createEventFetcherFactory(baseEvent) {
        return this.web3EventService.createEventFetcherFactory(baseEvent);
    }
    validateStandardSchemeParams(params) {
        if (!params.voteParametersHash) {
            throw new Error(`voteParametersHash is not defined`);
        }
        if (!params.votingMachineAddress) {
            throw new Error(`votingMachineAddress is not defined`);
        }
    }
    /**
     * Wrap code that creates a transaction in the given transaction event. This is a helper
     * just for the common case of generating a single transaction.
     * Rethrows exceptions that occur.
     *
     * @param functionName Should look like [contractName].[functionName]
     * @param options Options that will be passed in the event payload, and
     * potentially containing a txEventContext
     * @param generateTx Callback that will  the contract function
     * @param func The contract function
     * @param params The contract function parameters
     * @param web3Params Optional web params, like `from`
     */
    wrapTransactionInvocation(functionName, options, func, params, web3Params) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const payload = transactionService_1.TransactionService.publishKickoffEvent(functionName, options, 1);
            const eventContext = transactionService_1.TransactionService.newTxEventContext(functionName, payload, options);
            try {
                const txHash = yield this.sendTransaction(eventContext, func, params, web3Params);
                transactionService_1.TransactionService.publishTxLifecycleEvents(eventContext, txHash, this.contract);
                return new iContractWrapperBase_1.ArcTransactionResult(txHash, this.contract);
            }
            catch (ex) {
                loggingService_1.LoggingService.error(`ContractWrapperBase.wrapTransactionInvocation: An error occurred calling ${functionName}: ${ex}`);
                throw ex;
            }
        });
    }
    /**
     * Invoke sendTransaction on the function.  Properly publish TxTracking events.
     * Rethrows exceptions that occur.
     *
     * If `ConfigService.get("estimateGas")` and gas was not already supplied,
     * then we estimate gas.
     *
     * @param eventContext The TxTracking context
     * @param func The contract function
     * @param params The contract function parameters
     * @param web3Params Optional web params, like `from`
     */
    sendTransaction(eventContext, func, params, web3Params = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                let error;
                const gasPriceComputed = configService_1.ConfigService.get("gasPriceAdjustment");
                const web3 = yield utils_1.Utils.getWeb3();
                if (gasPriceComputed && !web3Params.gasPrice) {
                    if (gasPriceComputed) {
                        const defaultGasPrice = yield es6_promisify_1.promisify((callback) => { web3.eth.getGasPrice(callback); })();
                        web3Params.gasPrice = yield gasPriceComputed(defaultGasPrice);
                    }
                    loggingService_1.LoggingService.debug(`invoking function with configured gasPrice: ${web3.fromWei(web3Params.gasPrice, "gwei")}`);
                }
                if (configService_1.ConfigService.get("estimateGas") && !web3Params.gas) {
                    yield this.estimateGas(func, params, web3Params)
                        .then((gas) => {
                        // side-effect of altering web3Params allows caller to know what we used
                        Object.assign(web3Params, { gas });
                        loggingService_1.LoggingService.debug(`invoking function with estimated gas: ${gas}`);
                    })
                        .catch((ex) => {
                        loggingService_1.LoggingService.error(`estimateGas failed: ${ex}`);
                        error = ex;
                    });
                }
                else if (web3Params.gas) {
                    // cap any already-given gas limit
                    web3Params.gas = Math.min(web3Params.gas, yield utilsInternal_1.UtilsInternal.computeMaxGasLimit());
                }
                if (error) {
                    // don't attempt sending the tx
                    throw error;
                }
                params = params.concat(web3Params);
                const txHash = yield func.sendTransaction(...params)
                    .then((tx) => tx)
                    /**
                     * Because of truffle's faked Promise implementation, catching here is the only way
                     * to be able to catch it on the outside
                     */
                    .catch((ex) => {
                    error = ex;
                    return null;
                });
                if (error) {
                    throw error;
                }
                return txHash;
            }
            catch (ex) {
                // catch every possible error
                transactionService_1.TransactionService.publishTxFailed(eventContext, transactionService_1.TransactionStage.sent, ex);
                throw ex;
            }
        });
    }
    logContractFunctionCall(functionName, params) {
        loggingService_1.LoggingService.debug(`${functionName}: ${params ? `${loggingService_1.LoggingService.stringifyObject(params)}` : "no parameters"}`);
    }
}
exports.ContractWrapperBase = ContractWrapperBase;
//# sourceMappingURL=contractWrapperBase.js.map