import { BigNumber } from "bignumber.js" ; import { promisify } from "es6-promisify" ; import { Address , Hash } from "./commonTypes" ; import { ConfigService } from "./configService" ; import { ControllerService } from "./controllerService" ; import { ArcTransactionDataResult , ArcTransactionResult , GasPriceAdjustor , IContractWrapper , IContractWrapperFactory , StandardSchemeParams } from "./iContractWrapperBase" ; import { LoggingService } from "./loggingService" ; import { TransactionService , TransactionStage , TxEventContext , TxGeneratingFunctionOptions } from "./transactionService" ; import { Utils } from "./utils" ; import { UtilsInternal } from "./utilsInternal" ; import { EventFetcherFactory , Web3EventService } from "./web3EventService" ; /**
 * 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());
 * ```
 */ export abstract class ContractWrapperBase implements IContractWrapper { /**
     * The wrapper factor class providing static methods `at(someAddress)`, `new()` and `deployed()`.
     */ public abstract factory : IContractWrapperFactory < any > ; /**
     * The name of the contract.
     */ public abstract name : string ; /**
     * A more friendly name for the contract.
     */ public abstract friendlyName : string ; /**
     * The address of the contract
     */ public get address ( ) : Address { return this . contract . address ; } /**
     * The underlying truffle contract object.  Use this to access
     * parts of the contract that aren't accessible via the wrapper.
     */ public contract : any ; param solidityContract param web3EventService constructor ( private solidityContract , protected web3EventService ) { } returns public async hydrateFromNew ( ... rest ) { try { this . contract = await this . solidityContract . new ( ... rest ) . then ( ( contract ) => contract , ( error ) => { throw error ; } ) ; this . hydrated ( ) ; } catch ( ex ) { LoggingService . error ( `hydrateFromNew failing: ${ ex }` ) ; return undefined ; } return this ; } param address returns public async hydrateFromAt ( address ) { try { this . contract = await this . solidityContract . at ( address ) . then ( ( contract ) => contract , ( error ) => { throw error ; } ) ; this . hydrated ( ) ; } catch ( ex ) { LoggingService . error ( `hydrateFromAt failing: ${ ex }` ) ; return undefined ; } return this ; } param paramsHash public getParameters ( paramsHash ) { throw new Error ( "getParameters has not been not implemented by the contract wrapper" ) ; } param paramsHash public getParametersArray ( paramsHash ) { return this . contract . parameters ( paramsHash ) ; } param func param params param web3Params public async estimateGas ( func , params , web3Params = { } ) { if ( web3Params . gas ) { return web3Params . gas ; } const currentNetwork = await Utils . getNetworkName ( ) ; const maxGasLimit = await UtilsInternal . computeMaxGasLimit ( ) ; if ( currentNetwork === "Ganache" ) { return maxGasLimit ; } /**
         * 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 ( ( await func . estimateGas ( ... params ) ) , maxGasLimit ) , 21000 ) ; } /**
     * invoked to let base classes know that the `contract` is available.
     */ protected hydrated ( ) { } protected async _setParameters ( functionName , txEventContext , ... params ) { const parametersHash = await this . contract . getParametersHash ( ... params ) ; const txResult = await this . wrapTransactionInvocation ( functionName , Object . assign ( params , { txEventContext } ) , this . contract . setParameters , params ) ; return new ArcTransactionDataResult < Hash > ( txResult . tx , this . contract , parametersHash ) ; } protected _getParametersHash ( ... params ) { return this . contract . getParametersHash ( ... params ) ; } type TArgs param eventName protected createEventFetcherFactory < TArgs > ( baseEvent ) { return this . web3EventService . createEventFetcherFactory ( baseEvent ) ; } protected validateStandardSchemeParams ( params ) { if ( ! params . voteParametersHash ) { throw new Error ( `voteParametersHash is not defined` ) ; } if ( ! params . votingMachineAddress ) { throw new Error ( `votingMachineAddress is not defined` ) ; } } param functionName param options param generateTx param func param params param web3Params protected async wrapTransactionInvocation ( functionName , options , func , params , web3Params ? ) { const payload = TransactionService . publishKickoffEvent ( functionName , options , 1 ) ; const eventContext = TransactionService . newTxEventContext ( functionName , payload , options ) ; try { const txHash = await this . sendTransaction ( eventContext , func , params , web3Params ) ; TransactionService . publishTxLifecycleEvents ( eventContext , txHash , this . contract ) ; return new ArcTransactionResult ( txHash , this . contract ) ; } catch ( ex ) { LoggingService . error ( `ContractWrapperBase.wrapTransactionInvocation: An error occurred calling ${ functionName }: ${ ex }` ) ; throw ex ; } } param eventContext param func param params param web3Params protected async sendTransaction ( eventContext , func , params , web3Params = { } ) { try { let error ; const gasPriceComputed = ConfigService . get ( "gasPriceAdjustment" ) as GasPriceAdjustor ; const web3 = await Utils . getWeb3 ( ) ; if ( gasPriceComputed && ! web3Params . gasPrice ) { if ( gasPriceComputed ) { const defaultGasPrice = await promisify ( ( callback ) : void => { web3 . eth . getGasPrice ( callback ) ; } ) ( ) as BigNumber ; web3Params . gasPrice = await gasPriceComputed ( defaultGasPrice ) ; } LoggingService . debug ( `invoking function with configured gasPrice: ${ web3 . fromWei ( web3Params . gasPrice , "gwei" ) }` ) ; } if ( ConfigService . get ( "estimateGas" ) && ! web3Params . gas ) { await this . estimateGas ( func , params , web3Params ) . then ( ( gas ) => { Object . assign ( web3Params , { gas } ) ; LoggingService . debug ( `invoking function with estimated gas: ${ gas }` ) ; } ) . catch ( ( ex ) => { LoggingService . error ( `estimateGas failed: ${ ex }` ) ; error = ex ; } ) ; } else if ( web3Params . gas ) { web3Params . gas = Math . min ( web3Params . gas , await UtilsInternal . computeMaxGasLimit ( ) ) ; } if ( error ) { throw error ; } params = params . concat ( web3Params ) ; const txHash = await func . sendTransaction ( ... params ) . then ( ( tx ) => tx ) . catch ( ( ex ) => { error = ex ; return null ; } ) ; if ( error ) { throw error ; } return txHash ; } catch ( ex ) { TransactionService . publishTxFailed ( eventContext , TransactionStage . sent , ex ) ; throw ex ; } } protected logContractFunctionCall ( functionName , params ? ) { LoggingService . debug ( `${ functionName }: ${ params ? `${ LoggingService . stringifyObject ( params ) }` : "no parameters" }` ) ; } } export type TruffleContractFunction = ( args ? : Array < any > ) => Promise < Hash > ; export interface ITruffleContractFunction extends TruffleContractFunction { sendTransaction : ( args ? : Array < any > ) => Promise < Hash > ; estimateGas : ( args ? : Array < any > ) => number ; }