import { BigNumber , ethers } from 'ethers' ; import { waitUntilFree , waitUntilUsed } from 'tcp-port-used' ; import ganache from 'ganache-core' ; import { TEST_ACCOUNTS } from '../constants' ; import { Account } from '../types' ; import { logger } from './logger' ; import { SHOW_VERBOSE_GANACHE_OUTPUT } from './config' ; function findClosingPosition ( data ) { let closePos = 0 ; let depth = 1 ; while ( depth > 0 ) { const char = data [ ++ closePos ] ; if ( char === '{' ) depth ++ ; else if ( char === '}' ) depth -- ; else if ( closePos === data . length - 1 ) return - 1 ; } return ++ closePos ; } function clean ( newData ) { newData = newData . replace ( />/g , '' ) ; newData = newData . replace ( /</g , '' ) ; return newData ; } function extractLogsFromVerboseGanacheOutput ( buffer , newData = '' ) { logger . trace ( { buffer , newData } , 'Extracting logs' ) ; buffer = buffer . concat ( clean ( newData ) ) ; const logLineStart = buffer . indexOf ( '{' ) ; let statement = '' ; if ( logLineStart === - 1 ) { statement = buffer ; buffer = '' ; } else if ( logLineStart > 0 ) { statement = buffer . slice ( 0 , logLineStart ) ; buffer = buffer . slice ( logLineStart ) ; } statement = statement . trim ( ) ; if ( statement ) { logger . info ( { statement } , 'NON-JSON-RPC log line:' ) ; } if ( buffer . length === 0 ) { return '' ; } if ( buffer . indexOf ( '}' ) !== - 1 && buffer . indexOf ( '}' ) < logLineStart ) { logger . error ( { buffer , logLineStart } , 'Unexpected buffer' ) ; return '' ; } const logLineEnd = findClosingPosition ( buffer ) ; if ( logLineEnd === - 1 ) { logger . trace ( { buffer } , 'No end in sight...' ) ; return buffer ; } const logLine = JSON . parse ( buffer . slice ( 0 , logLineEnd ) ) ; if ( 'method' in logLine ) { logger . info ( logLine , `> REQUEST: ${ logLine . method } - ${ logLine . id }` ) ; } else if ( 'error' in logLine ) { logger . info ( logLine , `< ERROR: request failed ${ logLine . id }` ) ; } else if ( 'result' in logLine ) { logger . info ( logLine , `< RESULT: request succeeded ${ logLine . id }` ) ; } else { logger . error ( logLine , 'Log line is not a JSON-RPC message' ) ; } return extractLogsFromVerboseGanacheOutput ( buffer . slice ( logLineEnd ) ) ; } export class GanacheServer { provider : ethers . providers . JsonRpcProvider ; fundedPrivateKey : string ; server : any ; private buffer = '' ; constructor ( public readonly port = 8545 , public readonly chainId = 9001 , accounts = TEST_ACCOUNTS , public readonly timeout = 10000 , gasLimit = 1000000000 , gasPrice = 1 ) { logger . info ( `Starting ganache on port ${ this . port } with network ID ${ this . chainId }` ) ; this . fundedPrivateKey = accounts [ 0 ] . privateKey ; const oneMillion = ethers . utils . parseEther ( '1000000' ) . toHexString ( ) ; const serverOptions = { network_id : this . chainId , accounts : accounts . map ( a => ( { balance : oneMillion , secretKey : a . privateKey } ) ) , gasLimit , gasPrice : BigNumber . from ( gasPrice ) . toHexString ( ) , verbose : SHOW_VERBOSE_GANACHE_OUTPUT , logger : { log : x => { SHOW_VERBOSE_GANACHE_OUTPUT ? extractLogsFromVerboseGanacheOutput ( this . buffer , x ) : logger . info ( x ) ; } } } ; this . server = ganache . server ( { ... serverOptions , _chainId : this . chainId , _chainIdRpc : this . chainId } as any ) ; this . server . listen ( this . port , ( ) => { } ) ; this . provider = new ethers . providers . JsonRpcProvider ( `http://localhost:${ this . port }` ) ; } static async connect ( port ) { const provider = new ethers . providers . JsonRpcProvider ( `http://localhost:${ port }` ) ; try { await provider . getBlockNumber ( ) ; return new GanacheServer ( port ) ; } catch ( e ) { return Promise . reject ( `No ganache server to connect to locally on port ${ port }` ) ; } } async ready ( ) { await waitUntilUsed ( this . port , 500 , this . timeout ) ; } async close ( ) { this . server . close ( ) ; await waitUntilFree ( this . port , 500 , this . timeout ) ; } onClose ( listener ) { this . server . on ( 'close' , listener ) ; } }