2017-03-29 17:50:05 +00:00
let async = require ( 'async' ) ;
2018-01-05 20:10:47 +00:00
//require("../utils/debug_util.js")(__filename, async);
2018-07-27 17:09:07 +00:00
let utils = require ( '../../utils/utils.js' ) ;
2017-02-19 18:17:28 +00:00
2018-05-29 21:23:29 +00:00
class ContractDeployer {
2017-03-30 11:12:39 +00:00
constructor ( options ) {
2018-05-29 21:23:29 +00:00
const self = this ;
2018-05-18 22:31:47 +00:00
this . blockchain = options . blockchain ;
2017-03-30 11:12:39 +00:00
this . logger = options . logger ;
2018-02-27 20:40:05 +00:00
this . events = options . events ;
2018-01-17 23:04:19 +00:00
this . plugins = options . plugins ;
2018-05-29 21:23:29 +00:00
self . events . setCommandHandler ( 'deploy:contract' , ( contract , cb ) => {
self . checkAndDeployContract ( contract , null , cb ) ;
} ) ;
2018-01-05 20:10:47 +00:00
}
2017-03-30 11:12:39 +00:00
2018-05-21 16:29:18 +00:00
// TODO: determining the arguments could also be in a module since it's not
// part of ta 'normal' contract deployment
determineArguments ( suppliedArgs , contract , callback ) {
2018-05-21 17:11:13 +00:00
const self = this ;
2017-03-30 11:12:39 +00:00
2018-03-05 01:07:39 +00:00
let args = suppliedArgs ;
if ( ! Array . isArray ( args ) ) {
args = [ ] ;
let abi = contract . abiDefinition . find ( ( abi ) => abi . type === 'constructor' ) ;
for ( let input of abi . inputs ) {
let inputValue = suppliedArgs [ input . name ] ;
if ( ! inputValue ) {
2018-05-08 21:49:46 +00:00
this . logger . error ( _ _ ( "{{inputName}} has not been defined for {{className}} constructor" , { inputName : input . name , className : contract . className } ) ) ;
2018-03-05 01:07:39 +00:00
}
args . push ( inputValue || "" ) ;
}
}
2018-05-22 20:16:52 +00:00
async . map ( args , ( arg , nextEachCb ) => {
2017-03-30 11:12:39 +00:00
if ( arg [ 0 ] === "$" ) {
2018-05-21 17:30:45 +00:00
let contractName = arg . substr ( 1 ) ;
2018-05-21 17:11:13 +00:00
self . events . request ( 'contracts:contract' , contractName , ( referedContract ) => {
2018-05-22 20:16:52 +00:00
nextEachCb ( null , referedContract . deployedAddress ) ;
2018-05-21 17:11:13 +00:00
} ) ;
2018-03-04 23:46:12 +00:00
} else if ( Array . isArray ( arg ) ) {
2018-05-22 20:16:52 +00:00
async . map ( arg , ( sub _arg , nextSubEachCb ) => {
2018-03-04 23:46:12 +00:00
if ( sub _arg [ 0 ] === "$" ) {
2018-05-21 17:30:45 +00:00
let contractName = sub _arg . substr ( 1 ) ;
self . events . request ( 'contracts:contract' , contractName , ( referedContract ) => {
2018-05-22 20:16:52 +00:00
nextSubEachCb ( null , referedContract . deployedAddress ) ;
2018-05-21 17:30:45 +00:00
} ) ;
2018-03-04 23:46:12 +00:00
} else {
2018-05-22 20:16:52 +00:00
nextSubEachCb ( null , sub _arg ) ;
2018-03-04 23:46:12 +00:00
}
2018-05-22 20:16:52 +00:00
} , ( err , subRealArgs ) => {
nextEachCb ( null , subRealArgs ) ;
2018-05-21 17:30:45 +00:00
} ) ;
2017-03-30 11:12:39 +00:00
} else {
2018-05-22 20:16:52 +00:00
nextEachCb ( null , arg ) ;
2017-03-30 11:12:39 +00:00
}
2018-05-31 14:02:57 +00:00
} , callback ) ;
2016-09-28 01:13:54 +00:00
}
2017-03-30 11:12:39 +00:00
checkAndDeployContract ( contract , params , callback ) {
let self = this ;
contract . error = false ;
if ( contract . deploy === false ) {
2018-05-21 00:26:15 +00:00
self . events . emit ( "deploy:contract:undeployed" , contract ) ;
2017-03-30 11:12:39 +00:00
return callback ( ) ;
}
2016-09-25 01:10:47 +00:00
2018-05-21 00:59:55 +00:00
async . waterfall ( [
2018-05-21 16:29:18 +00:00
function _determineArguments ( next ) {
2018-05-31 14:02:57 +00:00
self . determineArguments ( params || contract . args , contract , ( err , realArgs ) => {
if ( err ) {
return next ( err ) ;
}
2018-05-21 16:29:18 +00:00
contract . realArgs = realArgs ;
next ( ) ;
} ) ;
2018-05-21 00:59:55 +00:00
} ,
function deployIt ( next ) {
if ( contract . address !== undefined ) {
try {
utils . toChecksumAddress ( contract . address ) ;
} catch ( e ) {
self . logger . error ( _ _ ( "error deploying %s" , contract . className ) ) ;
self . logger . error ( e . message ) ;
contract . error = e . message ;
self . events . emit ( "deploy:contract:error" , contract ) ;
return next ( e . message ) ;
}
contract . deployedAddress = contract . address ;
self . logger . info ( contract . className . bold . cyan + _ _ ( " already deployed at " ) . green + contract . address . bold . cyan ) ;
self . events . emit ( "deploy:contract:deployed" , contract ) ;
return next ( ) ;
}
2016-10-02 21:57:33 +00:00
2018-06-01 17:44:49 +00:00
// TODO find a better way to do that
if ( process . env . isTest ) {
2018-06-01 17:53:09 +00:00
return self . deployContract ( contract , next ) ;
2018-06-01 17:44:49 +00:00
}
2018-05-21 00:59:55 +00:00
// TODO: this should be a plugin API instead, if not existing, it should by default deploy the contract
self . events . request ( "deploy:contract:shouldDeploy" , contract , function ( trackedContract ) {
if ( ! trackedContract ) {
2018-05-22 19:37:04 +00:00
return self . deployContract ( contract , next ) ;
2018-05-21 00:59:55 +00:00
}
2018-05-19 02:40:47 +00:00
2018-05-21 00:59:55 +00:00
self . blockchain . getCode ( trackedContract . address , function ( _getCodeErr , codeInChain ) {
if ( codeInChain !== "0x" ) {
self . contractAlreadyDeployed ( contract , trackedContract , next ) ;
} else {
2018-05-22 19:37:04 +00:00
self . deployContract ( contract , next ) ;
2018-05-21 00:59:55 +00:00
}
} ) ;
} ) ;
}
] , callback ) ;
2018-01-05 20:10:47 +00:00
}
2017-02-18 21:53:49 +00:00
2018-01-05 20:10:47 +00:00
contractAlreadyDeployed ( contract , trackedContract , callback ) {
const self = this ;
2018-05-08 21:49:46 +00:00
self . logger . info ( contract . className . bold . cyan + _ _ ( " already deployed at " ) . green + trackedContract . address . bold . cyan ) ;
2018-01-05 20:10:47 +00:00
contract . deployedAddress = trackedContract . address ;
2018-05-20 23:59:35 +00:00
self . events . emit ( "deploy:contract:deployed" , contract ) ;
2018-01-05 20:10:47 +00:00
2018-05-23 15:16:13 +00:00
// TODO: can be moved into a afterDeploy event
// just need to figure out the gasLimit coupling issue
2018-05-29 21:23:29 +00:00
self . events . request ( 'code-generator:contract:vanilla' , contract , contract . _gasLimit , ( contractCode ) => {
2018-05-23 15:16:56 +00:00
self . events . request ( 'runcode:eval' , contractCode ) ;
2018-05-23 15:16:13 +00:00
return callback ( ) ;
} ) ;
2018-01-05 20:10:47 +00:00
}
2018-05-22 19:37:04 +00:00
deployContract ( contract , callback ) {
2017-03-30 11:12:39 +00:00
let self = this ;
2018-01-18 19:41:33 +00:00
let accounts = [ ] ;
2018-05-22 19:37:04 +00:00
let contractParams = ( contract . realArgs || contract . args ) . slice ( ) ;
2018-01-18 19:41:33 +00:00
let contractCode = contract . code ;
2018-05-18 22:31:47 +00:00
let deploymentAccount = self . blockchain . defaultAccount ( ) ;
2018-01-18 19:41:33 +00:00
let deployObject ;
async . waterfall ( [
2018-05-20 23:29:26 +00:00
// TODO: can potentially go to a beforeDeploy plugin
2018-01-18 19:41:33 +00:00
function getAccounts ( next ) {
2018-05-18 22:31:47 +00:00
self . blockchain . getAccounts ( function ( err , _accounts ) {
2018-01-18 19:41:33 +00:00
if ( err ) {
2018-01-18 19:46:53 +00:00
return next ( new Error ( err ) ) ;
2018-01-18 19:41:33 +00:00
}
accounts = _accounts ;
2018-01-20 00:56:05 +00:00
// applying deployer account configuration, if any
2018-06-20 18:06:15 +00:00
if ( typeof contract . fromIndex === 'number' ) {
2018-01-20 00:56:05 +00:00
deploymentAccount = accounts [ contract . fromIndex ] ;
2018-01-20 01:38:28 +00:00
if ( deploymentAccount === undefined ) {
2018-05-08 21:49:46 +00:00
return next ( _ _ ( "error deploying" ) + " " + contract . className + ": " + _ _ ( "no account found at index" ) + " " + contract . fromIndex + _ _ ( " check the config" ) ) ;
2018-01-20 01:38:28 +00:00
}
2018-01-20 00:56:05 +00:00
}
2018-06-20 18:06:15 +00:00
if ( typeof contract . from === 'string' && typeof contract . fromIndex !== 'undefined' ) {
2018-05-08 21:49:46 +00:00
self . logger . warn ( _ _ ( 'Both "from" and "fromIndex" are defined for contract' ) + ' "' + contract . className + '". ' + _ _ ( 'Using "from" as deployer account.' ) ) ;
2018-01-20 00:56:05 +00:00
}
2018-06-20 18:06:15 +00:00
if ( typeof contract . from === 'string' ) {
2018-01-20 00:56:05 +00:00
deploymentAccount = contract . from ;
}
2018-01-18 19:41:33 +00:00
deploymentAccount = deploymentAccount || accounts [ 0 ] ;
next ( ) ;
} ) ;
} ,
function doLinking ( next ) {
2018-06-08 11:07:27 +00:00
self . events . request ( 'contracts:list' , ( _err , contracts ) => {
2018-05-21 00:46:05 +00:00
for ( let contractObj of contracts ) {
let filename = contractObj . filename ;
let deployedAddress = contractObj . deployedAddress ;
if ( deployedAddress ) {
deployedAddress = deployedAddress . substr ( 2 ) ;
}
let linkReference = '__' + filename + ":" + contractObj . className ;
2018-07-12 15:18:03 +00:00
if ( contractCode . indexOf ( linkReference . substr ( 0 , 38 ) ) < 0 ) { // substr to simulate the cut that solc does
continue ;
}
2018-05-21 00:46:05 +00:00
if ( linkReference . length > 40 ) {
return next ( new Error ( _ _ ( "{{linkReference}} is too long, try reducing the path of the contract ({{filename}}) and/or its name {{contractName}}" , { linkReference : linkReference , filename : filename , contractName : contractObj . className } ) ) ) ;
}
let toReplace = linkReference + "_" . repeat ( 40 - linkReference . length ) ;
if ( deployedAddress === undefined ) {
let libraryName = contractObj . className ;
return next ( new Error ( _ _ ( "{{contractName}} needs {{libraryName}} but an address was not found, did you deploy it or configured an address?" , { contractName : contract . className , libraryName : libraryName } ) ) ) ;
}
contractCode = contractCode . replace ( new RegExp ( toReplace , "g" ) , deployedAddress ) ;
2018-01-18 19:41:33 +00:00
}
2018-05-21 00:46:05 +00:00
// saving code changes back to contract object
contract . code = contractCode ;
next ( ) ;
} ) ;
2018-01-18 19:41:33 +00:00
} ,
function applyBeforeDeploy ( next ) {
2018-05-30 12:00:31 +00:00
self . plugins . emitAndRunActionsForEvent ( 'deploy:contract:beforeDeploy' , { contract : contract } , next ) ;
2018-01-18 19:41:33 +00:00
} ,
function createDeployObject ( next ) {
2018-05-18 23:00:36 +00:00
let contractObject = self . blockchain . ContractObject ( { abi : contract . abiDefinition } ) ;
2018-01-18 19:41:33 +00:00
try {
2018-04-13 19:48:19 +00:00
const dataCode = contractCode . startsWith ( '0x' ) ? contractCode : "0x" + contractCode ;
2018-05-19 00:26:21 +00:00
deployObject = self . blockchain . deployContractObject ( contractObject , { arguments : contractParams , data : dataCode } ) ;
2018-01-18 19:41:33 +00:00
} catch ( e ) {
2018-03-02 22:48:30 +00:00
if ( e . message . indexOf ( 'Invalid number of parameters for "undefined"' ) >= 0 ) {
2018-05-08 21:49:46 +00:00
return next ( new Error ( _ _ ( "attempted to deploy %s without specifying parameters" , contract . className ) ) ) ;
2018-01-18 19:41:33 +00:00
} else {
return next ( new Error ( e ) ) ;
2018-01-17 23:04:19 +00:00
}
2018-01-05 20:10:47 +00:00
}
2018-01-18 19:41:33 +00:00
next ( ) ;
} ,
2018-01-19 18:57:35 +00:00
function estimateCorrectGas ( next ) {
if ( contract . gas === 'auto' ) {
2018-06-14 13:21:51 +00:00
return self . blockchain . estimateDeployContractGas ( deployObject , ( err , gasValue ) => {
if ( err ) {
return next ( err ) ;
}
2018-01-19 18:57:35 +00:00
contract . gas = gasValue ;
next ( ) ;
2018-06-14 13:21:51 +00:00
} ) ;
2018-01-19 18:57:35 +00:00
}
next ( ) ;
} ,
2018-01-18 19:41:33 +00:00
function deployTheContract ( next ) {
2018-07-25 12:27:15 +00:00
let estimatedCost = contract . gas * contract . gasPrice ;
self . logger . info ( _ _ ( "deploying" ) + " " + contract . className . bold . cyan + " " + _ _ ( "with" ) . green + " " + contract . gas + " " + _ _ ( "gas at the price of" ) . green + " " + contract . gasPrice + " " + _ _ ( "Wei, estimated cost:" ) . green + " " + estimatedCost + " Wei" . green ) ;
2018-01-18 19:41:33 +00:00
2018-05-19 00:26:21 +00:00
self . blockchain . deployContractFromObject ( deployObject , {
2018-01-18 19:41:33 +00:00
from : deploymentAccount ,
gas : contract . gas ,
gasPrice : contract . gasPrice
2018-05-19 00:26:21 +00:00
} , function ( error , receipt ) {
if ( error ) {
2018-05-22 19:37:04 +00:00
contract . error = error . message ;
self . events . emit ( "deploy:contract:error" , contract ) ;
2018-05-19 00:26:21 +00:00
return next ( new Error ( "error deploying =" + contract . className + "= due to error: " + error . message ) ) ;
2018-01-18 19:41:33 +00:00
}
2018-07-25 12:27:15 +00:00
self . logger . info ( contract . className . bold . cyan + " " + _ _ ( "deployed at" ) . green + " " + receipt . contractAddress . bold . cyan + " " + _ _ ( "using" ) . green + " " + receipt . gasUsed + " " + _ _ ( "gas" ) . green ) ;
2018-05-19 00:26:21 +00:00
contract . deployedAddress = receipt . contractAddress ;
contract . transactionHash = receipt . transactionHash ;
2018-06-28 18:01:44 +00:00
receipt . className = contract . className ;
2018-05-22 19:37:04 +00:00
self . events . emit ( "deploy:contract:receipt" , receipt ) ;
self . events . emit ( "deploy:contract:deployed" , contract ) ;
// TODO: can be moved into a afterDeploy event
// just need to figure out the gasLimit coupling issue
2018-05-29 21:23:29 +00:00
self . events . request ( 'code-generator:contract:vanilla' , contract , contract . _gasLimit , ( contractCode ) => {
2018-05-22 19:37:04 +00:00
self . events . request ( 'runcode:eval' , contractCode ) ;
2018-05-28 23:40:55 +00:00
self . plugins . runActionsForEvent ( 'deploy:contract:deployed' , { contract : contract } , ( ) => {
2018-05-22 19:37:04 +00:00
return next ( null , receipt ) ;
} ) ;
} ) ;
2018-01-18 19:41:33 +00:00
} ) ;
}
] , callback ) ;
2017-03-30 11:12:39 +00:00
}
}
2016-08-14 12:04:34 +00:00
2018-05-29 21:23:29 +00:00
module . exports = ContractDeployer ;