2017-03-29 17:50:05 +00:00
let toposort = require ( 'toposort' ) ;
let async = require ( 'async' ) ;
2018-06-14 13:21:51 +00:00
const cloneDeep = require ( 'clone-deep' ) ;
2016-08-14 12:04:34 +00:00
2017-12-19 19:07:48 +00:00
let utils = require ( '../utils/utils.js' ) ;
2017-02-19 18:17:28 +00:00
2016-09-28 01:04:40 +00:00
// TODO: create a contract object
2017-03-30 11:12:39 +00:00
class ContractsManager {
constructor ( options ) {
2018-05-21 00:46:05 +00:00
const self = this ;
2017-03-30 11:12:39 +00:00
this . contractFiles = options . contractFiles ;
2018-06-14 13:21:51 +00:00
this . contractsConfig = cloneDeep ( options . contractsConfig || { } ) ;
2017-03-30 11:12:39 +00:00
this . contracts = { } ;
this . logger = options . logger ;
this . plugins = options . plugins ;
this . contractDependencies = { } ;
2018-01-13 16:38:10 +00:00
this . gasLimit = options . gasLimit ;
2018-03-11 12:28:03 +00:00
this . deployOnlyOnConfig = false ;
2018-04-27 17:50:57 +00:00
this . events = options . events ;
2018-06-08 11:07:27 +00:00
this . compileError = false ;
2018-04-27 17:50:57 +00:00
2018-05-16 17:56:23 +00:00
self . events . setCommandHandler ( 'contracts:list' , ( cb ) => {
2018-06-08 11:07:27 +00:00
cb ( self . compileError , self . listContracts ( ) ) ;
2018-05-16 16:48:17 +00:00
} ) ;
2018-05-20 16:23:48 +00:00
self . events . setCommandHandler ( "contracts:contract" , ( contractName , cb ) => {
cb ( self . getContract ( contractName ) ) ;
} ) ;
2018-05-21 00:26:15 +00:00
2018-05-30 11:01:22 +00:00
self . events . setCommandHandler ( "contracts:build" , ( configOnly , cb ) => {
self . deployOnlyOnConfig = configOnly ; // temporary, should refactor
2018-06-08 11:07:27 +00:00
self . build ( ( err ) => {
cb ( err ) ;
2018-05-30 11:01:22 +00:00
} ) ;
} ) ;
2018-05-21 00:26:15 +00:00
self . events . on ( "deploy:contract:error" , ( _contract ) => {
self . events . emit ( 'contractsState' , self . contractsState ( ) ) ;
} ) ;
self . events . on ( "deploy:contract:deployed" , ( _contract ) => {
self . events . emit ( 'contractsState' , self . contractsState ( ) ) ;
} ) ;
self . events . on ( "deploy:contract:undeployed" , ( _contract ) => {
self . events . emit ( 'contractsState' , self . contractsState ( ) ) ;
} ) ;
2018-05-30 11:01:22 +00:00
2016-10-31 01:31:29 +00:00
}
2017-03-30 11:12:39 +00:00
build ( done ) {
let self = this ;
2018-06-14 13:21:51 +00:00
self . contracts = { } ;
2017-03-30 11:12:39 +00:00
async . waterfall ( [
2018-06-14 13:21:51 +00:00
function loadContractFiles ( callback ) {
self . events . request ( "config:contractsFiles" , ( contractsFiles ) => {
self . contractsFiles = contractsFiles ;
callback ( ) ;
} ) ;
} ,
function loadContractConfigs ( callback ) {
self . events . request ( "config:contractsConfig" , ( contractsConfig ) => {
self . contractsConfig = cloneDeep ( contractsConfig ) ;
callback ( ) ;
} ) ;
} ,
2017-03-30 11:12:39 +00:00
function compileContracts ( callback ) {
2018-06-08 11:07:27 +00:00
self . events . emit ( "status" , _ _ ( "Compiling..." ) ) ;
2018-06-04 14:45:50 +00:00
if ( process . env . isTest && self . compiledContracts && Object . keys ( self . compiledContracts ) . length ) {
// Only compile once for tests
return callback ( ) ;
}
2018-05-19 03:19:08 +00:00
self . events . request ( "compiler:contracts" , self . contractFiles , function ( err , compiledObject ) {
2017-02-17 12:14:44 +00:00
self . compiledContracts = compiledObject ;
2017-02-28 13:03:03 +00:00
callback ( err ) ;
2017-02-17 12:14:44 +00:00
} ) ;
2017-03-30 11:12:39 +00:00
} ,
function prepareContractsFromConfig ( callback ) {
2018-06-08 11:07:27 +00:00
self . events . emit ( "status" , _ _ ( "Building..." ) ) ;
2017-03-30 11:12:39 +00:00
let className , contract ;
for ( className in self . contractsConfig . contracts ) {
contract = self . contractsConfig . contracts [ className ] ;
2016-09-28 01:04:40 +00:00
2017-03-30 11:12:39 +00:00
contract . className = className ;
contract . args = contract . args || [ ] ;
2016-10-02 15:07:56 +00:00
2017-03-30 11:12:39 +00:00
self . contracts [ className ] = contract ;
}
callback ( ) ;
} ,
2018-06-14 19:22:50 +00:00
function getGasPriceForNetwork ( callback ) {
if ( self . contractsConfig . gasPrice ) {
return callback ( null , self . contractsConfig . gasPrice ) ;
}
self . events . request ( "blockchain:gasPrice" , callback ) ;
} ,
function prepareContractsFromCompilation ( gasPrice , callback ) {
2017-03-30 11:12:39 +00:00
let className , compiledContract , contractConfig , contract ;
for ( className in self . compiledContracts ) {
compiledContract = self . compiledContracts [ className ] ;
contractConfig = self . contractsConfig . contracts [ className ] ;
contract = self . contracts [ className ] || { className : className , args : [ ] } ;
contract . code = compiledContract . code ;
contract . runtimeBytecode = compiledContract . runtimeBytecode ;
contract . realRuntimeBytecode = ( contract . realRuntimeBytecode || contract . runtimeBytecode ) ;
contract . swarmHash = compiledContract . swarmHash ;
contract . gasEstimates = compiledContract . gasEstimates ;
contract . functionHashes = compiledContract . functionHashes ;
contract . abiDefinition = compiledContract . abiDefinition ;
2017-07-16 16:10:17 +00:00
contract . filename = compiledContract . filename ;
2017-03-30 11:12:39 +00:00
contract . gas = ( contractConfig && contractConfig . gas ) || self . contractsConfig . gas || 'auto' ;
2018-06-14 19:22:50 +00:00
contract . gasPrice = contract . gasPrice || gasPrice ;
2017-03-30 11:12:39 +00:00
contract . type = 'file' ;
contract . className = className ;
self . contracts [ className ] = contract ;
}
callback ( ) ;
} ,
2017-12-14 21:16:51 +00:00
function setDeployIntention ( callback ) {
let className , contract ;
for ( className in self . contracts ) {
contract = self . contracts [ className ] ;
contract . deploy = ( contract . deploy === undefined ) || contract . deploy ;
2018-03-11 12:28:03 +00:00
if ( self . deployOnlyOnConfig && ! self . contractsConfig . contracts [ className ] ) {
contract . deploy = false ;
}
2017-12-14 21:16:51 +00:00
if ( contract . code === "" ) {
2018-05-08 21:49:46 +00:00
self . logger . info ( _ _ ( "assuming %s to be an interface" , className ) ) ;
2017-12-14 21:16:51 +00:00
contract . deploy = false ;
}
}
callback ( ) ;
} ,
2017-03-30 11:12:39 +00:00
/*eslint complexity: ["error", 11]*/
function dealWithSpecialConfigs ( callback ) {
let className , contract , parentContractName , parentContract ;
2017-12-19 19:07:48 +00:00
let dictionary = Object . keys ( self . contracts ) ;
2016-09-28 01:04:40 +00:00
2017-03-30 11:12:39 +00:00
for ( className in self . contracts ) {
contract = self . contracts [ className ] ;
2016-10-02 15:07:56 +00:00
2017-03-30 11:12:39 +00:00
if ( contract . instanceOf === undefined ) {
continue ;
}
2016-10-02 15:07:56 +00:00
2017-03-30 11:12:39 +00:00
parentContractName = contract . instanceOf ;
parentContract = self . contracts [ parentContractName ] ;
2016-10-02 15:07:56 +00:00
2017-03-30 11:12:39 +00:00
if ( parentContract === className ) {
2018-05-08 21:49:46 +00:00
self . logger . error ( _ _ ( "%s : instanceOf is set to itself" , className ) ) ;
2017-03-30 11:12:39 +00:00
continue ;
}
2016-10-02 15:07:56 +00:00
2017-03-30 11:12:39 +00:00
if ( parentContract === undefined ) {
2018-05-08 21:49:46 +00:00
self . logger . error ( _ _ ( "{{className}}: couldn't find instanceOf contract {{parentContractName}}" , { className : className , parentContractName : parentContractName } ) ) ;
2017-12-20 14:41:12 +00:00
let suggestion = utils . proposeAlternative ( parentContractName , dictionary , [ className , parentContractName ] ) ;
2017-12-19 19:07:48 +00:00
if ( suggestion ) {
2018-05-08 21:49:46 +00:00
self . logger . warn ( _ _ ( 'did you mean "%s"?' , suggestion ) ) ;
2017-12-19 19:07:48 +00:00
}
2017-03-30 11:12:39 +00:00
continue ;
}
2016-09-28 01:04:40 +00:00
2017-03-30 11:12:39 +00:00
if ( parentContract . args && parentContract . args . length > 0 && ( ( contract . args && contract . args . length === 0 ) || contract . args === undefined ) ) {
contract . args = parentContract . args ;
}
2016-10-02 15:07:56 +00:00
2017-03-30 11:12:39 +00:00
if ( contract . code !== undefined ) {
2018-05-08 21:49:46 +00:00
self . logger . error ( _ _ ( "{{className}} has code associated to it but it's configured as an instanceOf {{parentContractName}}" , { className : className , parentContractName : parentContractName } ) ) ;
2017-03-30 11:12:39 +00:00
}
2016-09-28 01:04:40 +00:00
2017-03-30 11:12:39 +00:00
contract . code = parentContract . code ;
contract . runtimeBytecode = parentContract . runtimeBytecode ;
contract . gasEstimates = parentContract . gasEstimates ;
contract . functionHashes = parentContract . functionHashes ;
contract . abiDefinition = parentContract . abiDefinition ;
2016-10-31 00:48:16 +00:00
2017-03-30 11:12:39 +00:00
contract . gas = contract . gas || parentContract . gas ;
contract . gasPrice = contract . gasPrice || parentContract . gasPrice ;
contract . type = 'instance' ;
2016-09-28 01:04:40 +00:00
2016-10-29 16:02:07 +00:00
}
2017-03-30 11:12:39 +00:00
callback ( ) ;
} ,
function removeContractsWithNoCode ( callback ) {
let className , contract ;
2017-12-19 19:07:48 +00:00
let dictionary = Object . keys ( self . contracts ) ;
2017-03-30 11:12:39 +00:00
for ( className in self . contracts ) {
contract = self . contracts [ className ] ;
if ( contract . code === undefined ) {
2018-05-08 21:49:46 +00:00
self . logger . error ( _ _ ( "%s has no code associated" , className ) ) ;
2017-12-19 19:07:48 +00:00
let suggestion = utils . proposeAlternative ( className , dictionary , [ className ] ) ;
if ( suggestion ) {
2018-05-08 21:49:46 +00:00
self . logger . warn ( _ _ ( 'did you mean "%s"?' , suggestion ) ) ;
2017-12-19 19:07:48 +00:00
}
2017-03-30 11:12:39 +00:00
delete self . contracts [ className ] ;
}
}
self . logger . trace ( self . contracts ) ;
callback ( ) ;
} ,
2018-03-05 01:07:39 +00:00
// TODO: needs refactoring, has gotten too complex
2018-03-04 23:46:12 +00:00
/*eslint complexity: ["error", 16]*/
/*eslint max-depth: ["error", 16]*/
2017-03-30 11:12:39 +00:00
function determineDependencies ( callback ) {
let className , contract ;
for ( className in self . contracts ) {
contract = self . contracts [ className ] ;
2017-07-16 17:31:40 +00:00
// look in code for dependencies
let libMatches = ( contract . code . match ( /\:(.*?)(?=_)/g ) || [ ] ) ;
for ( let match of libMatches ) {
self . contractDependencies [ className ] = self . contractDependencies [ className ] || [ ] ;
self . contractDependencies [ className ] . push ( match . substr ( 1 ) ) ;
}
2017-03-30 11:12:39 +00:00
2017-07-16 17:31:40 +00:00
// look in arguments for dependencies
if ( contract . args === [ ] ) continue ;
2018-03-05 01:07:39 +00:00
let ref ;
if ( Array . isArray ( contract . args ) ) {
ref = contract . args ;
} else {
let keys = Object . keys ( contract . args ) ;
ref = keys . map ( ( k ) => contract . args [ k ] ) . filter ( ( x ) => ! x ) ;
}
2017-03-30 11:12:39 +00:00
for ( let j = 0 ; j < ref . length ; j ++ ) {
let arg = ref [ j ] ;
if ( arg [ 0 ] === "$" ) {
self . contractDependencies [ className ] = self . contractDependencies [ className ] || [ ] ;
self . contractDependencies [ className ] . push ( arg . substr ( 1 ) ) ;
2018-06-13 17:47:11 +00:00
self . checkDependency ( className , arg . substr ( 1 ) ) ;
2017-03-30 11:12:39 +00:00
}
2018-03-04 23:46:12 +00:00
if ( Array . isArray ( arg ) ) {
for ( let sub _arg of arg ) {
if ( sub _arg [ 0 ] === "$" ) {
self . contractDependencies [ className ] = self . contractDependencies [ className ] || [ ] ;
self . contractDependencies [ className ] . push ( sub _arg . substr ( 1 ) ) ;
2018-06-13 17:47:11 +00:00
self . checkDependency ( className , sub _arg . substr ( 1 ) ) ;
2018-03-04 23:46:12 +00:00
}
}
}
2016-10-29 16:02:07 +00:00
}
2017-12-20 19:30:01 +00:00
// look in onDeploy for dependencies
if ( contract . onDeploy === [ ] || contract . onDeploy === undefined ) continue ;
let regex = /\$\w+/g ;
contract . onDeploy . map ( ( cmd ) => {
cmd . replace ( regex , ( match ) => {
self . contractDependencies [ className ] = self . contractDependencies [ className ] || [ ] ;
self . contractDependencies [ className ] . push ( match . substr ( 1 ) ) ;
} ) ;
} ) ;
2016-10-29 16:02:07 +00:00
}
2017-03-30 11:12:39 +00:00
callback ( ) ;
2018-06-20 15:15:47 +00:00
} ,
function setDependencyCount ( callback ) {
let className ;
function getDependencyCount ( contractName , cycleDetector ) {
if ( ! self . contractDependencies [ contractName ] || ! self . contractDependencies [ contractName ] . length ) {
self . contracts [ contractName ] . dependencyCount = 0 ;
return 0 ;
}
if ( self . contracts [ contractName ] . dependencyCount ) {
// Already have that count
return self . contracts [ contractName ] . dependencyCount ;
}
let total = self . contractDependencies [ contractName ] . length ;
self . contractDependencies [ contractName ] . some ( dependencyName => {
if ( cycleDetector . indexOf ( dependencyName ) > - 1 ) {
// We are in a cycle because of the dependency, set both to Infinity
self . contracts [ dependencyName ] . dependencyCount = Infinity ;
total = Infinity ;
return true ;
}
cycleDetector . push ( dependencyName ) ;
total += getDependencyCount ( dependencyName , cycleDetector ) ;
} ) ;
self . contracts [ contractName ] . dependencyCount = total ;
return total ;
}
let cycleDetector ;
for ( className in self . contracts ) {
cycleDetector = [ ] ;
getDependencyCount ( className , cycleDetector ) ;
}
callback ( ) ;
2016-10-29 16:02:07 +00:00
}
2018-06-20 15:15:47 +00:00
] , function ( err ) {
2017-03-30 11:12:39 +00:00
if ( err ) {
2018-06-08 11:07:27 +00:00
self . compileError = true ;
self . events . emit ( "status" , _ _ ( "Compile/Build error" ) ) ;
2018-05-08 21:49:46 +00:00
self . logger . error ( _ _ ( "Error Compiling/Building contracts: " ) + err ) ;
2018-06-15 03:23:31 +00:00
} else {
self . compileError = false ;
2017-03-30 11:12:39 +00:00
}
self . logger . trace ( "finished" . underline ) ;
done ( err , self ) ;
} ) ;
}
2018-06-13 17:47:11 +00:00
checkDependency ( className , dependencyName ) {
if ( ! this . contractDependencies [ className ] ) {
return ;
}
if ( ! this . contracts [ dependencyName ] ) {
this . logger . warn ( _ _ ( '{{className}} has a dependency on {{dependencyName}}' , { className , dependencyName } ) +
_ _ ( ', but it is not present in the contracts' ) ) ;
return ;
}
if ( ! this . contracts [ dependencyName ] . deploy ) {
this . logger . warn ( _ _ ( '{{className}} has a dependency on {{dependencyName}}' , { className , dependencyName } ) +
_ _ ( ', but it is not set to deploy. It could be an interface.' ) ) ;
}
}
2017-03-30 11:12:39 +00:00
getContract ( className ) {
return this . contracts [ className ] ;
2016-09-27 04:55:35 +00:00
}
2017-03-30 11:12:39 +00:00
sortContracts ( contractList ) {
let converted _dependencies = [ ] , i ;
for ( let contract in this . contractDependencies ) {
let dependencies = this . contractDependencies [ contract ] ;
for ( i = 0 ; i < dependencies . length ; i ++ ) {
converted _dependencies . push ( [ contract , dependencies [ i ] ] ) ;
}
}
2016-09-27 04:55:35 +00:00
2017-12-20 19:54:47 +00:00
let orderedDependencies ;
try {
2017-12-28 13:26:02 +00:00
orderedDependencies = toposort ( converted _dependencies . filter ( ( x ) => x [ 0 ] != x [ 1 ] ) ) . reverse ( ) ;
2017-12-20 19:54:47 +00:00
} catch ( e ) {
2018-05-08 21:49:46 +00:00
this . logger . error ( ( _ _ ( "Error: " ) + e . message ) . red ) ;
this . logger . error ( _ _ ( "there are two or more contracts that depend on each other in a cyclic manner" ) . bold . red ) ;
this . logger . error ( _ _ ( "Embark couldn't determine which one to deploy first" ) . red ) ;
2017-12-20 19:58:59 +00:00
throw new Error ( "CyclicDependencyError" ) ;
//process.exit(0);
2017-12-20 19:54:47 +00:00
}
2016-09-27 04:55:35 +00:00
2017-03-30 11:12:39 +00:00
let newList = contractList . sort ( function ( a , b ) {
let order _a = orderedDependencies . indexOf ( a . className ) ;
let order _b = orderedDependencies . indexOf ( b . className ) ;
return order _a - order _b ;
} ) ;
2016-09-27 04:55:35 +00:00
2017-03-30 11:12:39 +00:00
return newList ;
2016-08-14 12:04:34 +00:00
}
2017-03-30 11:12:39 +00:00
// TODO: should be built contracts
listContracts ( ) {
let contracts = [ ] ;
for ( let className in this . contracts ) {
let contract = this . contracts [ className ] ;
contracts . push ( contract ) ;
2016-10-21 03:31:42 +00:00
}
2017-03-30 11:12:39 +00:00
return this . sortContracts ( contracts ) ;
}
2016-10-21 03:31:42 +00:00
2017-03-30 11:12:39 +00:00
contractsState ( ) {
let data = [ ] ;
for ( let className in this . contracts ) {
let contract = this . contracts [ className ] ;
let contractData ;
if ( contract . deploy === false ) {
contractData = [
className . green ,
2018-05-08 21:49:46 +00:00
_ _ ( 'Interface or set to not deploy' ) . green ,
2017-03-30 11:12:39 +00:00
"\t\tn/a" . green
] ;
} else if ( contract . error ) {
contractData = [
className . green ,
2017-12-14 20:03:08 +00:00
( contract . error ) . split ( "\n" ) [ 0 ] . replace ( /Error: /g , '' ) . substring ( 0 , 32 ) . red ,
2017-03-30 11:12:39 +00:00
'\t\tError' . red
] ;
} else {
contractData = [
className . green ,
( contract . deployedAddress || '...' ) . green ,
2018-05-08 21:49:46 +00:00
( ( contract . deployedAddress !== undefined ) ? ( "\t\t" + _ _ ( "Deployed" ) ) . green : ( "\t\t" + _ _ ( "Pending" ) ) . magenta )
2017-03-30 11:12:39 +00:00
] ;
}
data . push ( contractData ) ;
}
return data ;
2016-09-22 22:24:01 +00:00
}
2017-03-30 11:12:39 +00:00
}
2016-09-22 22:24:01 +00:00
2016-08-14 12:04:34 +00:00
module . exports = ContractsManager ;