import fs from "fs-extra" ; import { CheckResult , Receipt } from "../../core" ; import { Provider } from "../../core/provider" ; import { getNormalizedContractFilePath } from "../../utils/contractSourceDir" ; import { getTempFilePath } from "../../utils/fsUtil" ; import { executeCommand } from "../../utils/processUtil" ; export class ExecutionError extends Error { readonly code : number ; readonly commandOutput : string ; readonly errorOutput : string ; constructor ( message , code , commandOutput , errorOutput ) { super ( message ) ; this . message = message ; this . name = this . constructor . name ; this . code = code ; this . commandOutput = commandOutput ; this . errorOutput = errorOutput ; } } export class NativeClarityBinProvider implements Provider { param dbFilePath static async create ( allocations , dbFilePath , clarityBinPath ) { const executor = new NativeClarityBinProvider ( allocations , dbFilePath , clarityBinPath ) ; await executor . initialize ( ) ; return executor ; } param allocations param clarityBinPath static async createEphemeral ( allocations , clarityBinPath ) { const tempDbPath = getTempFilePath ( "blockstack-local-{uniqueID}.db" ) ; const instance = await this . create ( allocations , tempDbPath , clarityBinPath ) ; instance . closeActions . push ( ( ) => { try { fs . unlinkSync ( instance . dbFilePath ) ; } catch ( error ) { } } ) ; return instance ; } public readonly allocations : InitialAllocation [ ] ; public readonly dbFilePath : string ; readonly clarityBinPath : string ; private closeActions : ( ( ( ) => Promise < any > ) | ( ( ) => any ) ) [ ] = [ ] ; constructor ( allocations , dbFilePath , clarityBinPath ) { this . allocations = allocations ; this . dbFilePath = dbFilePath ; this . clarityBinPath = clarityBinPath ; } param args async runCommand ( args , opts ? ) { const result = await executeCommand ( this . clarityBinPath , [ ... args ] , { stdin : opts && opts . stdin , } ) ; result . stdout = result . stdout . replace ( /\r\n|\r|\n/ , "\n" ) . replace ( /\r\n|\r|\n$/ , "" ) ; result . stderr = result . stderr . replace ( /\r\n|\r|\n/g , "\n" ) . replace ( /\r\n|\r|\n$/ , "" ) ; return result ; } async initialize ( ) { const result = await this . runCommand ( [ "initialize" , "-" , this . dbFilePath ] , { stdin : JSON . stringify ( this . allocations ) , } ) ; if ( result . exitCode !== 0 ) { throw new ExecutionError ( `Initialize failed with bad exit code ${ result . exitCode }: ${ result . stderr }` , result . exitCode , result . stdout , result . stderr ) ; } if ( ! result . stdout . includes ( "Database created." ) ) { throw new ExecutionError ( `Initialize failed with bad output: ${ result . stdout }` , result . exitCode , result . stdout , result . stderr ) ; } } async checkContract ( contractFilePath ) { const filePath = getNormalizedContractFilePath ( contractFilePath ) ; const result = await this . runCommand ( [ "check" , filePath , this . dbFilePath , "--output_analysis" ] ) ; if ( result . exitCode !== 0 ) { return { success : false , error : result . stdout + "\n" + result . stderr , } ; } else { const contractInterface = JSON . parse ( result . stdout ) ; return { success : true , result : contractInterface , } ; } } async launchContract ( contractName , contractFilePath ) { const filePath = getNormalizedContractFilePath ( contractFilePath ) ; const result = await this . runCommand ( [ "launch" , contractName , filePath , this . dbFilePath ] ) ; if ( result . exitCode !== 0 ) { throw new ExecutionError ( `Launch contract failed with bad exit code ${ result . exitCode }: ${ result . stderr }` , result . exitCode , result . stdout , result . stderr ) ; } if ( ! result . stdout . includes ( "Contract initialized!" ) ) { throw new ExecutionError ( `Launch contract failed with bad output: ${ result . stdout }` , result . exitCode , result . stdout , result . stderr ) ; } return { success : true , debugOutput : result . stderr , } ; } async execute ( contractName , functionName , senderAddress , ... args ) { const result = await this . runCommand ( [ "execute" , this . dbFilePath , contractName , functionName , senderAddress , ... args , ] ) ; if ( result . exitCode !== 0 ) { throw new ExecutionError ( `Execute expression on contract failed with bad exit code ${ result . exitCode }: ${ result . stderr }` , result . exitCode , result . stdout , result . stderr ) ; } const executed = result . stdout . startsWith ( "Transaction executed and committed." ) ; const didReturnErr = result . stdout . includes ( " Returned: (err" ) ; if ( ! executed || didReturnErr ) { throw new ExecutionError ( `Execute expression on contract failed with bad output: ${ result . stdout }` , result . exitCode , result . stdout , result . stderr ) ; } return { success : true , result : result . stdout , debugOutput : result . stderr , } ; } async evalRaw ( evalStatement ) { const result = await this . runCommand ( [ "eval_raw" , this . dbFilePath ] , { stdin : evalStatement , } ) ; if ( result . exitCode !== 0 ) { throw new ExecutionError ( `Eval raw expression failed with bad exit code ${ result . exitCode }: ${ result . stderr }` , result . exitCode , result . stdout , result . stderr ) ; } const successPrefix = result . stdout . match ( /(Program executed successfully! Output: (\r\n|\r|\n))/ ) ; if ( ! successPrefix || successPrefix . length < 1 ) { throw new ExecutionError ( `Eval raw expression failed with bad output: ${ result . stdout }` , result . exitCode , result . stdout , result . stderr ) ; } const outputResult = result . stdout . substr ( successPrefix [ 0 ] . length ) ; return { success : true , result : outputResult , debugOutput : result . stderr , } ; } async eval ( contractName , evalStatement , includeDebugOutput ? , atChaintip = true ) { const result = await this . runCommand ( [ `eval${ atChaintip ? "_at_chaintip" : "" }` , contractName , this . dbFilePath ] , { stdin : evalStatement , } ) ; if ( result . exitCode !== 0 ) { throw new ExecutionError ( `Eval expression on contract failed with bad exit code ${ result . exitCode }: ${ result . stderr }` , result . exitCode , result . stdout , result . stderr ) ; } const successPrefix = result . stdout . match ( /(Program executed successfully! Output: (\r\n|\r|\n))/ ) ; if ( ! successPrefix || successPrefix . length < 1 ) { throw new ExecutionError ( `Eval expression on contract failed with bad output: ${ result . stdout }` , result . exitCode , result . stdout , result . stderr ) ; } const outputResult = result . stdout . substr ( successPrefix [ 0 ] . length ) ; if ( includeDebugOutput ) { return { success : true , result : outputResult , debugOutput : result . stderr , } ; } else { return { success : true , result : outputResult , } ; } } async mineBlock ( time ? ) { const args = [ "mine_block" ] ; const timeArg = time || Math . round ( Date . now ( ) / 1000 ) ; args . push ( timeArg . toString ( ) ) ; args . push ( this . dbFilePath ) ; const result = await this . runCommand ( args ) ; if ( result . exitCode !== 0 ) { throw new ExecutionError ( `Mine block failed with bad exit code ${ result . exitCode }: ${ result . stderr }` , result . exitCode , result . stdout , result . stderr ) ; } if ( result . stdout !== "Simulated block mine!" ) { throw new ExecutionError ( `Mine block failed with bad output: ${ result . stdout }` , result . exitCode , result . stdout , result . stderr ) ; } } async mineBlocks ( count ) { const result = await this . runCommand ( [ "mine_blocks" , `--data=${ this . dbFilePath }` , `--count=${ count . toString ( ) }` , ] ) ; if ( result . exitCode !== 0 ) { throw new ExecutionError ( `Mine blocks failed with bad exit code ${ result . exitCode }: ${ result . stderr }` , result . exitCode , result . stdout , result . stderr ) ; } if ( result . stdout !== "Simulated block mine!" ) { throw new ExecutionError ( `Mine blocks failed with bad output: ${ result . stdout }` , result . exitCode , result . stdout , result . stderr ) ; } } async getBlockHeight ( ) { const result = await this . runCommand ( [ "get_block_height" , this . dbFilePath ] ) ; if ( result . exitCode !== 0 ) { throw new ExecutionError ( `Get block height failed with bad exit code ${ result . exitCode }: ${ result . stderr }` , result . exitCode , result . stdout , result . stderr ) ; } const successPrefix = result . stdout . match ( /(Simulated block height: (\r\n|\r|\n))/ ) ; if ( ! successPrefix || successPrefix . length < 1 ) { throw new ExecutionError ( `Get block height failed with bad output: ${ result . stdout }` , result . exitCode , result . stdout , result . stderr ) ; } const outputResult = result . stdout . substr ( successPrefix [ 0 ] . length ) ; const heightInt = BigInt ( outputResult ) ; return heightInt ; } async close ( ) { for ( const closeAction of this . closeActions ) { await Promise . resolve ( closeAction ( ) ) ; } } } export interface InitialAllocation { principal : string ; amount : number ; }