import * as fs from "fs-extra" ; import { HardhatRuntimeEnvironment } from "hardhat/types" ; import { sep } from "path" ; import { DefaultChainId , NetworkMap , PluginName } from "./index" ; import { TenderlyService } from "./tenderly/TenderlyService" ; import { ContractByName , Metadata , TenderlyArtifact , TenderlyContract , TenderlyContractUploadRequest } from "./tenderly/types" ; import { resolveDependencies } from "./util" ; export class Tenderly { public env : HardhatRuntimeEnvironment ; constructor ( bre ) { this . env = bre ; } public async verify ( ... contracts ) { const flatContracts = contracts . reduce ( ( accumulator , value ) => accumulator . concat ( value ) , [ ] ) ; const requestData = await this . filterContracts ( flatContracts ) ; if ( requestData == null ) { console . log ( "Verification failed" ) ; return ; } try { await TenderlyService . verifyContracts ( requestData ) ; } catch ( err ) { if ( err instanceof Error ) { console . log ( err . message ) ; } } } public async push ( ... contracts ) { const flatContracts = contracts . reduce ( ( accumulator , value ) => accumulator . concat ( value ) , [ ] ) ; const requestData = await this . filterContracts ( flatContracts ) ; if ( this . env . config . tenderly . project === undefined ) { console . log ( `Error in ${ PluginName }: Please provide the project field in the tenderly object in hardhat.config.js` ) ; return ; } if ( this . env . config . tenderly . username === undefined ) { console . log ( `Error in ${ PluginName }: Please provide the username field in the tenderly object in hardhat.config.js` ) ; return ; } if ( requestData == null ) { console . log ( "Push failed" ) ; return ; } try { await TenderlyService . pushContracts ( requestData , this . env . config . tenderly . project , this . env . config . tenderly . username ) ; } catch ( err ) { if ( err instanceof Error ) { console . log ( err . message ) ; } } } public async persistArtifacts ( ... contracts ) { if ( contracts . length === 0 ) { return ; } const sourcePaths = await this . env . run ( "compile:solidity:get-source-paths" ) ; const sourceNames = await this . env . run ( "compile:solidity:get-source-names" , { sourcePaths } ) ; const data = await this . env . run ( "compile:solidity:get-dependency-graph" , { sourceNames } ) ; let contract ; data . _resolvedFiles . forEach ( ( resolvedFile , _ ) => { const sourcePath = resolvedFile . sourceName ; const name = sourcePath . split ( "/" ) . slice ( - 1 ) [ 0 ] . split ( "." ) [ 0 ] ; for ( contract of contracts ) { if ( contract . name === name ) { const network = this . env . hardhatArguments . network !== "hardhat" ? this . env . hardhatArguments . network || contract . network : contract . network ; if ( network === undefined ) { console . log ( `Error in ${ PluginName }: Please provide a network via the hardhat --network argument or directly in the contract` ) ; continue ; } let chainID = NetworkMap [ network ! . toLowerCase ( ) ] ; if ( this . env . config . networks [ network ! ] . chainId !== undefined ) { chainID = this . env . config . networks [ network ! ] . chainId ! . toString ( ) ; } if ( chainID == undefined ) { chainID = DefaultChainId ; } const destPath = `deployments${ sep }${ network ! . toLowerCase ( ) }_${ chainID }${ sep }` ; const contractDataPath = `${ this . env . config . paths . artifacts }${ sep }${ sourcePath }${ sep }${ name }.json` ; const contractData = JSON . parse ( fs . readFileSync ( contractDataPath ) . toString ( ) ) ; const metadata = { compiler : { version : this . env . config . solidity . compilers [ 0 ] . version } , sources : { [ sourcePath ] : { content : resolvedFile . content . rawContent } } } ; const visited = { } ; resolveDependencies ( data , sourcePath , metadata , visited ) ; const artifact = { metadata : JSON . stringify ( metadata ) , address : contract . address , bytecode : contractData . bytecode , deployedBytecode : contractData . deployedBytecode , abi : contractData . abi } ; fs . outputFileSync ( `${ destPath }${ name }.json` , JSON . stringify ( artifact ) ) ; } } } ) ; } private async filterContracts ( flatContracts ) { let contract ; const requestData = await this . getContractData ( flatContracts ) ; for ( contract of flatContracts ) { const network = this . env . hardhatArguments . network !== "hardhat" ? this . env . hardhatArguments . network || contract . network : contract . network ; if ( network === undefined ) { console . log ( `Error in ${ PluginName }: Please provide a network via the hardhat --network argument or directly in the contract` ) ; return null ; } const index = requestData . contracts . findIndex ( requestContract => requestContract . contractName === contract . name ) ; if ( index === - 1 ) { continue ; } let chainID = NetworkMap [ network ! . toLowerCase ( ) ] ; if ( this . env . config . networks [ network ! ] . chainId !== undefined ) { chainID = this . env . config . networks [ network ! ] . chainId ! . toString ( ) ; } if ( chainID === undefined ) { console . log ( `Error in ${ PluginName }: Couldn't identify network. Please provide a chainID in the network config object` ) ; return null ; } requestData . contracts [ index ] . networks = { [ chainID ] : { address : contract . address } } ; } return requestData ; } private async getContracts ( flatContracts ) { const sourcePaths = await this . env . run ( "compile:solidity:get-source-paths" ) ; const sourceNames = await this . env . run ( "compile:solidity:get-source-names" , { sourcePaths } ) ; const data = await this . env . run ( "compile:solidity:get-dependency-graph" , { sourceNames } ) ; let contract ; const requestContracts = [ ] ; const metadata = { compiler : { version : this . env . config . solidity . compilers [ 0 ] . version } , sources : { } } ; data . _resolvedFiles . forEach ( ( resolvedFile , _ ) => { const sourcePath = resolvedFile . sourceName ; const name = sourcePath . split ( "/" ) . slice ( - 1 ) [ 0 ] . split ( "." ) [ 0 ] ; for ( contract of flatContracts ) { if ( contract . name !== name ) { continue ; } metadata . sources [ sourcePath ] = { content : resolvedFile . content . rawContent } ; const visited = { } ; resolveDependencies ( data , sourcePath , metadata , visited ) ; } } ) ; for ( const [ key , value ] of Object . entries ( metadata . sources ) ) { const name = key . split ( "/" ) . slice ( - 1 ) [ 0 ] . split ( "." ) [ 0 ] ; const contractToPush = { contractName : name , source : value . content , sourcePath : key , networks : { } , compiler : { name : "solc" , version : this . env . config . solidity ?. compilers [ 0 ] . version ! } } ; requestContracts . push ( contractToPush ) ; } return requestContracts ; } private async getContractData ( flatContracts ) { const config = this . env . config ; const contracts = await this . getContracts ( flatContracts ) ; const solcConfig = { compiler_version : config . solidity . compilers [ 0 ] . version , optimizations_used : config . solidity . compilers [ 0 ] . settings . optimizer . enabled , optimizations_count : config . solidity . compilers [ 0 ] . settings . optimizer . runs } ; return { contracts , config : solcConfig } ; } }