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 ;
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
2018-08-14 17:36:08 +00:00
determineArguments ( suppliedArgs , contract , accounts , 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-08-14 17:36:08 +00:00
function parseArg ( arg , cb ) {
const match = arg . match ( /\$accounts\[([0-9]+)]/ ) ;
if ( match ) {
if ( ! accounts [ match [ 1 ] ] ) {
2018-09-12 13:00:20 +00:00
return cb ( _ _ ( 'No corresponding account at index %d' , match [ 1 ] ) ) ;
2018-08-14 17:36:08 +00:00
}
return cb ( null , accounts [ match [ 1 ] ] ) ;
}
let contractName = arg . substr ( 1 ) ;
self . events . request ( 'contracts:contract' , contractName , ( referedContract ) => {
2018-09-18 20:30:32 +00:00
// Because we're referring to a contract that is not being deployed (ie. an interface),
// we still need to provide a valid address so that the ABI checker won't fail.
2018-09-18 20:36:21 +00:00
cb ( null , ( referedContract . deployedAddress || '0x0000000000000000000000000000000000000000' ) ) ;
2018-08-14 17:36:08 +00:00
} ) ;
}
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-08-14 17:36:08 +00:00
parseArg ( arg , nextEachCb ) ;
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-08-14 17:36:08 +00:00
parseArg ( sub _arg , nextSubEachCb ) ;
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 ;
2018-08-14 17:36:08 +00:00
let accounts = [ ] ;
let deploymentAccount ;
2017-03-30 11:12:39 +00:00
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-07-27 20:03:15 +00:00
function requestBlockchainConnector ( callback ) {
self . events . request ( "blockchain:object" , ( blockchain ) => {
self . blockchain = blockchain ;
callback ( ) ;
} ) ;
} ,
2018-08-14 17:36:08 +00:00
// TODO: can potentially go to a beforeDeploy plugin
function getAccounts ( next ) {
deploymentAccount = self . blockchain . defaultAccount ( ) ;
self . blockchain . getAccounts ( function ( err , _accounts ) {
if ( err ) {
return next ( new Error ( err ) ) ;
}
accounts = _accounts ;
// applying deployer account configuration, if any
if ( typeof contract . fromIndex === 'number' ) {
deploymentAccount = accounts [ contract . fromIndex ] ;
if ( deploymentAccount === undefined ) {
return next ( _ _ ( "error deploying" ) + " " + contract . className + ": " + _ _ ( "no account found at index" ) + " " + contract . fromIndex + _ _ ( " check the config" ) ) ;
}
}
if ( typeof contract . from === 'string' && typeof contract . fromIndex !== 'undefined' ) {
self . logger . warn ( _ _ ( 'Both "from" and "fromIndex" are defined for contract' ) + ' "' + contract . className + '". ' + _ _ ( 'Using "from" as deployer account.' ) ) ;
}
if ( typeof contract . from === 'string' ) {
deploymentAccount = contract . from ;
}
deploymentAccount = deploymentAccount || accounts [ 0 ] ;
contract . deploymentAccount = deploymentAccount ;
next ( ) ;
} ) ;
} ,
2018-05-21 16:29:18 +00:00
function _determineArguments ( next ) {
2018-08-14 17:36:08 +00:00
self . determineArguments ( params || contract . args , contract , accounts , ( err , realArgs ) => {
2018-05-31 14:02:57 +00:00
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 ;
2018-08-29 10:23:24 +00:00
self . logFunction ( contract ) ( contract . className . bold . cyan + _ _ ( " already deployed at " ) . green + contract . address . bold . cyan ) ;
2018-05-21 00:59:55 +00:00
self . events . emit ( "deploy:contract:deployed" , contract ) ;
return next ( ) ;
}
2016-10-02 21:57:33 +00:00
2018-08-20 21:25:30 +00:00
self . plugins . emitAndRunActionsForEvent ( 'deploy:contract:shouldDeploy' , { contract : contract , shouldDeploy : true } , function ( _err , params ) {
let trackedContract = params . contract ;
if ( ! params . shouldDeploy ) {
2018-09-15 16:20:20 +00:00
return self . willNotDeployContract ( contract , trackedContract , next ) ;
2018-08-20 21:25:30 +00:00
}
if ( ! trackedContract . address ) {
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-09-15 16:20:20 +00:00
willNotDeployContract ( contract , trackedContract , callback ) {
contract . deploy = false ;
this . events . emit ( "deploy:contract:undeployed" , contract ) ;
callback ( ) ;
}
2018-01-05 20:10:47 +00:00
contractAlreadyDeployed ( contract , trackedContract , callback ) {
const self = this ;
2018-08-29 10:23:24 +00:00
this . logFunction ( contract ) ( 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-08-15 10:03:50 +00:00
self . events . request ( 'runcode:eval' , contractCode , ( ) => { } , true ) ;
2018-05-23 15:16:13 +00:00
return callback ( ) ;
} ) ;
2018-01-05 20:10:47 +00:00
}
2018-08-29 10:23:24 +00:00
logFunction ( contract ) {
return contract . silent ? this . logger . trace . bind ( this . logger ) : this . logger . info . bind ( this . logger ) ;
}
2018-05-22 19:37:04 +00:00
deployContract ( contract , callback ) {
2017-03-30 11:12:39 +00:00
let self = this ;
2018-05-22 19:37:04 +00:00
let contractParams = ( contract . realArgs || contract . args ) . slice ( ) ;
2018-01-18 19:41:33 +00:00
let deployObject ;
async . waterfall ( [
function doLinking ( next ) {
2018-08-03 19:52:34 +00:00
let contractCode = contract . code ;
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 16:36:54 +00:00
if ( contractCode . indexOf ( linkReference . substr ( 0 , 38 ) ) < 0 ) { // substr to simulate the cut that solc does
2018-05-21 00:46:05 +00:00
continue ;
}
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-08-03 19:52:34 +00:00
// saving code changes back to the contract object
2018-05-21 00:46:05 +00:00
contract . code = contractCode ;
2018-08-07 19:26:39 +00:00
self . events . request ( 'contracts:setBytecode' , contract . className , contractCode ) ;
2018-05-21 00:46:05 +00:00
next ( ) ;
} ) ;
2018-01-18 19:41:33 +00:00
} ,
function applyBeforeDeploy ( next ) {
2018-08-24 14:31:40 +00:00
self . plugins . emitAndRunActionsForEvent ( 'deploy:contract:beforeDeploy' , { contract : contract } , ( _params ) => {
2018-08-20 21:25:30 +00:00
next ( ) ;
} ) ;
2018-01-18 19:41:33 +00:00
} ,
2018-07-17 12:10:22 +00:00
function getGasPriceForNetwork ( next ) {
2018-09-03 08:48:44 +00:00
self . events . request ( "blockchain:gasPrice" , ( err , gasPrice ) => {
if ( err ) {
return next ( new Error ( _ _ ( "could not get the gas price" ) ) ) ;
}
2018-07-17 12:10:22 +00:00
contract . gasPrice = contract . gasPrice || gasPrice ;
next ( ) ;
} ) ;
} ,
2018-01-18 19:41:33 +00:00
function createDeployObject ( next ) {
2018-08-03 19:52:34 +00:00
let contractCode = contract . code ;
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-09-21 21:52:32 +00:00
return next ( new Error ( _ _ ( "attempted to deploy %s without specifying parameters" , contract . className ) ) + ". " + _ _ ( "check if there are any params defined for this contract in this environment in the contracts configuration file" ) ) ;
2018-01-17 23:04:19 +00:00
}
2018-08-30 13:53:04 +00:00
return next ( new Error ( e ) ) ;
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-09-26 22:45:06 +00:00
let increase _per = 1 + ( Math . random ( ) / 10.0 )
contract . gas = Math . floor ( gasValue * increase _per ) ;
2018-01-19 18:57:35 +00:00
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 ;
2018-08-29 10:23:24 +00:00
self . logFunction ( contract ) ( _ _ ( "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-08-07 19:30:02 +00:00
2018-05-19 00:26:21 +00:00
self . blockchain . deployContractFromObject ( deployObject , {
2018-08-03 19:52:34 +00:00
from : contract . deploymentAccount ,
2018-01-18 19:41:33 +00:00
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-09-25 13:59:08 +00:00
if ( error . message && error . message . indexOf ( 'replacement transaction underpriced' ) !== - 1 ) {
2018-09-22 13:21:30 +00:00
self . logger . warn ( "replacement transaction underpriced: This warning typically means a transaction exactly like this one is still pending on the blockchain" ) ;
2018-09-21 21:52:32 +00:00
}
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-08-29 10:23:24 +00:00
self . logFunction ( contract ) ( 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-08-15 10:03:50 +00:00
self . events . request ( 'runcode:eval' , contractCode , ( ) => { } , true ) ;
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 ;