├── .editorconfig ├── .gitattributes ├── .gitignore ├── .npmignore ├── .npmignore.swp ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── bin └── ethdeploy.js ├── dist ├── ethdeploy.js ├── ethdeploy.js.map └── ethdeploy.min.js ├── ethdeploy-logo.png ├── example ├── .gitignore ├── contracts │ └── SimpleStore.sol ├── environments.json ├── environments.json_backup_2017-05-15T19:14:14.867Z ├── environments.json_backup_2017-10-22T22:29:29.858Z ├── environments.json_backup_2017-10-22T22:30:12.659Z ├── ethdeploy.testrpc.config.js ├── index.js └── package.json ├── internals └── webpack │ └── webpack.config.js ├── package.json └── src ├── index.js ├── lib ├── index.js └── tests │ └── test.index.js ├── loaders ├── environment │ ├── index.js │ └── package.json ├── raw-environment │ ├── index.js │ └── package.json ├── raw-solc │ ├── index.js │ └── package.json ├── solc-json │ ├── index.js │ └── package.json └── solc │ ├── index.js │ └── package.json ├── plugins ├── index.js └── tests │ └── test.index.js ├── tests ├── configs │ ├── ethdeploy.config.minimum.testnet.js │ ├── ethdeploy.config.minimumNoDefinedPlugins.testnet.js │ ├── ethdeploy.config.minimumNoOutput.testnet.js │ ├── ethdeploy.config.minimumNoPlugins.testnet.js │ ├── ethdeploy.config.noloaders.testnet.js │ ├── ethdeploy.config.noplugin.testnet.js │ ├── ethdeploy.config.nopreloaders.testnet.js │ ├── ethdeploy.config.notxobject.testnet.js │ ├── ethdeploy.config.rawenv.testnet.js │ ├── ethdeploy.config.rawsolc.testnet.js │ ├── ethdeploy.config.solcLoader.testnet.js │ ├── ethdeploy.config.stringEntry.testnet.js │ ├── ethdeploy.config.testnet.js │ └── ethdeploy.config.zeroloaders.testnet.js ├── contracts │ ├── SimpleStore.sol │ └── test.SimpleStore.sol └── test.index.js └── utils ├── index.js └── tests ├── test.index.js └── testSources ├── someDir ├── AnotherDir │ └── something.json ├── anotherFile └── someDeeperDir │ └── AnotherDeeperDir │ └── anotherFile ├── someFile.json └── someFile.s /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = false 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text eol=lf 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # server config 51 | .htaccess text 52 | 53 | # git config 54 | .gitattributes text 55 | .gitignore text 56 | .gitconfig text 57 | 58 | # code analysis config 59 | .jshintrc text 60 | .jscsrc text 61 | .jshintignore text 62 | .csslintrc text 63 | 64 | # misc config 65 | *.yaml text 66 | *.yml text 67 | .editorconfig text 68 | 69 | # build config 70 | *.npmignore text 71 | *.bowerrc text 72 | 73 | # Heroku 74 | Procfile text 75 | .slugignore text 76 | 77 | # Documentation 78 | *.md text 79 | LICENSE text 80 | AUTHORS text 81 | 82 | 83 | # 84 | ## These files are binary and should be left untouched 85 | # 86 | 87 | # (binary is a macro for -text -diff) 88 | *.png binary 89 | *.jpg binary 90 | *.jpeg binary 91 | *.gif binary 92 | *.ico binary 93 | *.mov binary 94 | *.mp4 binary 95 | *.mp3 binary 96 | *.flv binary 97 | *.fla binary 98 | *.swf binary 99 | *.gz binary 100 | *.zip binary 101 | *.7z binary 102 | *.ttf binary 103 | *.eot binary 104 | *.woff binary 105 | *.pyc binary 106 | *.pdf binary 107 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check auto-generated stuff into git 2 | node_modules 3 | package-lock.json 4 | coverage 5 | 6 | lib 7 | !src/lib/ 8 | !src/lib 9 | !src/* 10 | 11 | # Cruft 12 | .DS_Store 13 | npm-debug.log 14 | 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example 2 | -------------------------------------------------------------------------------- /.npmignore.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentCicero/ethdeploy/acb1212e1942ce604eb82e34ce2f8c9d6b20a0a6/.npmignore.swp -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | language: node_js 3 | node_js: 4 | - "6" 5 | compiler: 6 | - gcc 7 | - clang 8 | install: 9 | env: 10 | - CXX=g++-4.8 11 | addons: 12 | apt: 13 | sources: 14 | - ubuntu-toolchain-r-test 15 | packages: 16 | - gcc-4.8 17 | - g++-4.8 18 | - clang 19 | after_success: npm run coveralls 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 -- main export, tests 2 | 3 | 1. Basic testing 4 | 2. Basic docs 5 | 3. License 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting the project maintainer at nick.dodson@consensys.net. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 46 | version 1.3.0, available at 47 | [http://contributor-covenant.org/version/1/3/0/][version] 48 | 49 | [homepage]: http://contributor-covenant.org 50 | [version]: http://contributor-covenant.org/version/1/3/0/ 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016 Nick Dodson. nickdodson.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## ethdeploy | webpack for smart-contracts ;) 4 | 5 | A first pass at a highly configurable contract staging and deployment utility. 6 | 7 | Made with ❤︎ by Nick Dodson. If you're using this tool, we'd love to hear from you! 8 | 9 | ## Features 10 | - Highly unopinionated 11 | - Just deployment, that's it! (does not compile or tests contracts, but plugins though ;=D) 12 | - Composable, to be integrated into other things like `webpack` loaders, the `cli` or any other frameworks 13 | - Extremely configurable (deploy contracts with different settings to multiple environments in different ways) 14 | - Extensible, deployment staging can happen in any environment to any environment 15 | - Lightly abstracted, promisified but mostly unopinionated deployment scripting (maybe no promises in the future though) 16 | - Lightweight, does not include a ton of dependencies 17 | - Simple and robust, intakes data/config >> outputs result data 18 | - Is meant to aid in contract deployment, not dictate entire application design 19 | 20 | ## About 21 | 22 | Deploy your Ethereum smart-contracts to multiple environments, with a range of configurations, using lightly abstracted promisified deployment staging modules. The end result is an environments object, that contains all configured contract information (e.g. address, receipt, gas, abi, etc.) for each selected environment and their contracts. 23 | 24 | Once central purpose of this module is to deploy contracts which need to be deployed, and skip the deployment of contracts which have already been deployed with the same inputs and bytecode. 25 | 26 | Note, this module is highly experimental and requires more testing and research before being using on the main network. Use at your own risk. 27 | 28 | ## Installation 29 | 30 | ``` 31 | npm install --save ethdeploy 32 | ``` 33 | 34 | ## Example 35 | 36 | Checkout the ethdeploy [example](/example/index.js) provided. This will launch a testrpc server (a dummy ethereum api) in the background, and then deploy a bunch of contracts. 37 | 38 | ``` 39 | npm install 40 | cd example 41 | npm install 42 | npm start 43 | ``` 44 | 45 | ## CLI 46 | 47 | The CLI allows you to deploy an ethdeploy module from the command line. You can use this globally or loally. 48 | 49 | ``` 50 | ethdeploy ./ethdeploy.testnet.js 51 | 52 | // or locally as: 53 | 54 | node ./node_modules/ethdeploy/bin/ethdeploy.js ./ethdeploy.testnet.js 55 | #NOTE: currently it is necessary to first 56 | (cd node_modules/ethdeploy/ && npm install) 57 | ``` 58 | 59 | ## Example Deployment Module 60 | 61 | Here we have an example `ethdeploy` deployment configuration module. 62 | 63 | ```js 64 | const HttpProvider = require('ethjs-provider-http'); 65 | 66 | module.exports = (options) => ({ // eslint-disable-line 67 | entry: [ 68 | './environments.json', 69 | './src/tests/contracts', 70 | ], 71 | output: { 72 | path: './', 73 | filename: 'environments.json', 74 | safe: true, 75 | }, 76 | module: { 77 | environment: { 78 | name: 'localtestnet', 79 | provider: new HttpProvider('http://localhost:8545'), 80 | defaultTxObject: { 81 | from: 1, 82 | gas: 3000001, 83 | }, 84 | }, 85 | preLoaders: [ 86 | { test: /\.(json)$/, loader: 'ethdeploy-environment-loader' }, 87 | ], 88 | loaders: [ 89 | { test: /\.(sol)$/, loader: 'ethdeploy-solc-loader', optimize: 1 }, 90 | ], 91 | deployment: (deploy, contracts, done) => { 92 | deploy(contracts.SimpleStore, { from: 0 }).then(() => { 93 | done(); 94 | }); 95 | }, 96 | }, 97 | plugins: [ 98 | new options.plugins.JSONFilter(), 99 | new options.plugins.JSONMinifier(), 100 | ], 101 | }); 102 | ``` 103 | 104 | Here we have a simple configuraton loading in previous contract builds, and new contract data. The deployment module just deploys the single contract and fires the `done` method, which will then output the result in an `environments.json` file as specified by the `config` output. The `solc` loader loads new contract data from `.sol` files, while the `environments` loader processes data from `.json` files. The default environment states that account `1` should be used to deploy contracts, while in deployment, the developer state they want the `SimpleStore` contract to be deployed from account `0`. 105 | 106 | This module will produce JSON like this: 107 | 108 | ```js 109 | { 110 | "localtestnet": { 111 | "SimpleStore": { 112 | "bytecode": "0x...", 113 | "interface": "[{....}]", 114 | "address": "0x3a70a6765746af3bfa974fff9d753d4b6c56b333", 115 | "inputs": [], 116 | "transactionObject": { 117 | "from": "0x7f3e74e3dbb4091973ea1b449692c504c35ef768", 118 | "gas": 3000001 119 | } 120 | } 121 | } 122 | } 123 | ``` 124 | 125 | ## Config Module Description 126 | 127 | Ethdeploy modules are `Object`s or `Funtion`s, much like webpack modules. You specify a single deployment environment per file. This includes the inputs, loaders, environment, deployment schedule and output. This follows in some way the webpack data processing flow, from entry, to output. 128 | 129 | ```js 130 | module.exports = { 131 | entry: [], // entry data, usually paths to files or directors 132 | output: {}, // output specifications, output file name etc 133 | module: {}, // the deployment configuration, environment, deployment schedule 134 | plugins: {}, // plugins that format output data, do special things 135 | }; 136 | ``` 137 | 138 | ### Entry 139 | 140 | The module entry is usually file or directory paths, but can actually be any kind of object. If you would like to use your own custom object, you can specify your own `module.sourceMapper` which will help produce a sourcemap of the entry for loaders to intake. 141 | 142 | ### Output 143 | 144 | The output specifies information about the output file or object. Usually things like the output filename. 145 | 146 | ### Module 147 | 148 | The module is where you specify all your deployment environment and schedule. This is where the action happens for `ethdeploy`. 149 | 150 | ### Plugins 151 | 152 | Plugins help format the output which is usually JSON. It processes output data into a format that you want. 153 | 154 | ## Data Processing Flow 155 | 156 | `ethdeploy` is designed to help load and deploy your Ethereum contracts, then output the data (in a file or object). The data process flow is at first glass complicated, but is designed for complete configuratbility of contract deployment of Ethereum contracts. Here is the `ethdeploy` data processing flow. In simple terms, `ethdeploy` intakes the configuration module, and should output a single data output/file. 157 | 158 | Stages of processing: 159 | 160 | 1. [source mapping of entry] : source map all entry data into a single output source map object 161 | 2. [environment configuration] : configure environment (load accounts, set gas and defaults) 162 | 3. [pre loader processing] : load all pre configured data such as previous builds/deployments 163 | 4. [loader processing] : load all new data, like new contract bytecode or interfaces 164 | 5. [deployment module processing] : run the module.deployment method, which will trigger the deployment process 165 | 6. [output plugin processing] : run the specified output plugins if any 166 | 7. [final data write/output] : write the final output object/data 167 | 168 | ## `ethdeploy` module 169 | 170 | The `ethdeploy` module can be required and used in normal nodejs javascript contexts. `ethdeploy` should also be able to be used client-side in the browser, although this has not beed tested yet. Here is the `ethdeploy` using in a nodejs context. The module simply intakes the config file and returns a standard callback result. 171 | 172 | ```js 173 | const ethdeploy = require('ethdeploy'); 174 | const deploymentConfig = require('ethdeploy.testrpc.config.js'); 175 | 176 | ethdeploy(deploymentConfig, (err, result) => { 177 | console.log(err, result); 178 | }); 179 | ``` 180 | 181 | ## Loaders 182 | 183 | Ethdeploy has a simple loader API that allows you to build or plugin existing loaders. There are two kinds of loaders, `preLoaders` and `loaders`. PreLoaders are for pre data change loading, such as loading in previous environments or deployment data. The loaders are for loading in new contract data like new build data from a recent solc build. The loader loads in a sourceMapped data object from the module sourcemapper, then spits out a environment JSON structure object. 184 | 185 | Exmaple loader: 186 | 187 | ```js 188 | /** 189 | * Loads an environments.json file, produced by ethdeploy 190 | * 191 | * @method loader 192 | * @param {Object} sourceMap the file source map 193 | * @param {Object} loaderConfig the config for the specified loader 194 | * @param {Object} environment the loaded environment object 195 | * @return {Object} contracts the output contracts object 196 | */ 197 | module.exports = function loader(sourceMap, loaderConfig, environment) { // eslint-disable-line 198 | // loader code 199 | } 200 | ``` 201 | 202 | ### Available Loaders: 203 | 204 | Here are some available loaders. The `environment` and `solc` loaders are most likely the ones you would use the most (i.e. loading your previous deployments and your new contract builds). 205 | 206 | - `ethdeploy-environment-loader`: loads standard ethdeploy environment files (for loading previous deployments) 207 | - `ethdeploy-solc-loader`: compiles `.sol` Ethereum contracts for ethdeploy (for loading solc contract data) 208 | - `ethdeploy-solc-json-loader`: loads and processes solc-json files (the output from solc as a JSON) 209 | 210 | ### Loader Config 211 | 212 | Ethdeploy loaders, much like webpack loaders, use regex to test if the sourcemap presented should be laoded by the required loader module. There are three regex properties you can use to select the correct files for your loader. 213 | 214 | - `test`: regex test must be positive to include in loader 215 | - `include`: regex test must be positive or null to include in loader 216 | - `exclude`: if specified, file path must be negative against this test to include in loader 217 | 218 | Sometimes you want specific files to be excluded from specific loaders, like tests in final build and deployment. The `exclude` test is good for this, it allows you to do things like exclude Solidity test files from final build deployment. 219 | 220 | ## Plugins 221 | 222 | Plugins help format the output data, they simple intake the data string (usually a JSON string) and format that data however the developer wants. The most used plugin is the JSON minifier plugin which just minifies the outputted JSON information. There is a default set of plugins which are fed in through the ethdeploy method options input. See the `example` for more details. 223 | 224 | Here is the JSON Minifier plugin: 225 | 226 | ```js 227 | /** 228 | * Minifies JSON output 229 | * 230 | * @method JSONMinifier 231 | * @param {String} output the final build file produced by ethdeploy 232 | * @return {String} parsedOutput parsed output 233 | */ 234 | function JSONMinifier() { 235 | const self = this; 236 | self.process = ({ output, baseContracts, contracts, environment }) => JSON.stringify(JSON.parse(output)); 237 | } 238 | ``` 239 | 240 | ### Available Plugins 241 | 242 | Here are some available plugins for `ethdeploy`. The main one will most likely be the `JSONMinifier` plugin, used to minify output JSON. Note, these plugins come with ethdeploy and are fed in through the options object of your deployment module (if you use a type `Function` module). 243 | 244 | - `JSONMinifier`: minifies output JSON from ethdeploy 245 | - `JSONExpander`: expands output JSON from ethdeploy 246 | - `JSONFilter`: filters the JSON output to `address`, `bytecode`, `interface`, `transactionObject` and `inputs` properties. 247 | - `IncludeContracts` includes selected contracts from the build process and includes them in a special `contracts` environment 248 | 249 | #### IncludeContracts Plugin 250 | 251 | This is an overview of the important IncludeContracts plugin. This plugin is used to include certain contracts that you may not need to deploy, but do need to include the interface or bytecode of for your dApp. 252 | 253 | Example in Use: 254 | 255 | ```js 256 | 257 | plugins: 258 | new options.plugins.IncludeContracts(['SimpleStoreInterface', 'Token', 'Proxy']), 259 | 260 | new options.plugins.JSONFilter(), 261 | new options.plugins.JSONMinifier(), 262 | ], 263 | ``` 264 | 265 | Here we see the IncludeContracts plugin including the interface to the `SimpleStore` contract and the `Token` interface and the `Proxy` contract. This will then produce an output object like this: 266 | 267 | ```js 268 | { 269 | "ropsten": { 270 | "SimpleStore": { 271 | "bytecode": "0x...", 272 | "interface": "[{....}]", 273 | "address": "0x3a70a6765746af3bfa974fff9d753d4b6c56b333", 274 | "inputs": [], 275 | "transactionObject": { 276 | "from": "0x7f3e74e3dbb4091973ea1b449692c504c35ef768", 277 | "gas": 3000001 278 | } 279 | } 280 | }, 281 | "contracts": { 282 | "SimpleStoreInterface": { 283 | "bytecode": "0x...", 284 | "interface": "[{....}]", 285 | }, 286 | "Token": { 287 | "bytecode": "0x...", 288 | "interface": "[{....}]", 289 | }, 290 | "Proxy": { 291 | "bytecode": "0x...", 292 | "interface": "[{....}]", 293 | } 294 | } 295 | } 296 | ``` 297 | 298 | Now with this input, you can take the interface data into your dApp. Note, this will override the contracts environment everytime. 299 | 300 | ## Deployment Modules 301 | 302 | The `ethdeploy` config allows you to specify your deployment in the `module` object. Within this module are a few critical configuration requirements. In your module you must specify a `environment`, and `deployment` function. Loaders should also be used to load in the entry data into the deployment module. The loaders will intake data loaded in from the source mapping stage, and output the final data to the `contracts` object used in `module.deployment`. The `deploy` method intakes the formatted contract data, compares it with what was loaded at the preLoaded stage, if there are different properties like `bytecode` or new contracts, it will deploy those, otherwise it will skip and return the exiting contract instance. Once the deployment module is done, the `done` method should be fired, to trigger the output processing. 303 | 304 | Example: 305 | 306 | ```js 307 | module: { 308 | environment: { 309 | name: 'localtestnet', 310 | provider: new HttpProvider('http://localhost:8545'), 311 | defaultTxObject: { 312 | from: 1, 313 | gas: 3000001, 314 | }, 315 | }, 316 | preLoaders: [ 317 | { test: /\.(json)$/, loader: 'ethdeploy-environment-loader' }, 318 | ], 319 | loaders: [ 320 | { test: /\.(sol)$/, loader: 'ethdeploy-solc-loader', optimize: 1 }, 321 | ], 322 | deployment: (deploy, contracts, done) => { 323 | deploy(contracts.SimpleStore, 'constructor argument 1', 'argument 2...', { from: 0 }).then(() => done()); 324 | }, 325 | }, 326 | ``` 327 | 328 | ### environment 329 | 330 | The environment specifies your deployment env. provider, name and default transaction object. 331 | 332 | ### preLoaders 333 | 334 | The pre-loaders load all previous deployment information, such as previous contract builds or deployoments. 335 | 336 | ### loaders 337 | 338 | The loaders load all current contract builds, such as new solc contracts or new contract bytecode. 339 | 340 | ### deployment 341 | 342 | The deployment module is where the contract deployment schedule is specified. This is where the main action happens for contract deployment. The `deploy` method is used to deploy pre-formatted `contracts` data. Once the process is completed, the `done` method should be fired to complete the process. 343 | 344 | ## Deloyment Scheduling 345 | 346 | Ethdeploy allows you to specify your own complex deployment schedule. The inputs provided to the deployment property are `deploy`, `contracts`, `done` and `environment`. The `deploy` method is used to deploy the contract object. The `contracts` object is fed in by the loaders, and is used by the `deploy` method to deploy the contracts. The `done` method should be fired at the end of deployment to stop the deployment process and begin the outputting process. The `environment` object is used for including environmental information into your schedule like accounts, balances and other things of this sort. 347 | 348 | Basic Example: 349 | 350 | ```js 351 | deployment: (deploy, contracts, done) => { 352 | deploy(contracts.SimpleStore).then(() => done()); 353 | }, 354 | ``` 355 | 356 | Here we have a very basic deployment schedule. This will deploy contract SimpleStore with the `deploy` method. Then once the contract is deployed, the `done` method is fired. 357 | 358 | 359 | Complex Example: 360 | 361 | ```js 362 | deployment: (deploy, contracts, done) => { 363 | deploy(contracts.SimpleStore, 45, 'My Simple Store', { from: 0 }) 364 | .then((contractInstance) => deploy(contracts.StandardToken, contractInstance.address)) 365 | .then(done); 366 | }, 367 | ``` 368 | 369 | Here we have a more complex example. The first contract `SimpleStore` is being deployed with two constructor arguments, (1) the value `45` and (2) the String `'My Simple Store'`. Contract `SimpleStore` is being deployed from account `0`, as specified by the transaction object `{ from: 0 }`. Then once `SimpleStore` is deployed, the StandardToken contract is being deployed with a single constructor argument, the address of the newly deployed `SimpleStore` contract instance. Once the `StandardToken` contract is deployed, the `done` method is fired to end the deployment schedule. 370 | 371 | This is a more complex deployment example, where one contract relies on the others address for deployment. Also note that if SimpleStore had beed deployed previously for example, the outputted environment was loaded back into `ethdeploy`, it would not be redeployed if all inputs are the same. The inputs to re-interate are: `address`, `transactionObject`, `bytecode`, `inputs` and `interface`. If any of these values had changed, then the `SimpleStore` contract would re-deploy, otherwise the contractInstance retured is simply that of the previously deployed contract. 372 | 373 | ## Environments Object (the output object) 374 | 375 | `ethdeploy` will output your contracts either as an object within execution or to a file system, if used in the CLI. The final object output follows a very simple organizational pattern. Namely, envrionment, then contracts. A single environments output can contain multiple environments and multiple deployments of different contracts within each environment. 376 | 377 | Example output: 378 | 379 | ```js 380 | { 381 | "ropsten": { 382 | "SimpleStore": { 383 | "address": "0x..", 384 | "bytecode": "0x..", 385 | "interface": "[{}]", 386 | }, 387 | "SimpleStoreFactory": { 388 | "address": "0x..", 389 | "bytecode": "0x..", 390 | "interface": "[{}]", 391 | } 392 | } 393 | } 394 | ``` 395 | 396 | Above, we see that there is the environment ropsten and the subsequent contracts deployed to environment `ropsten`. From this object, you would either include or `require` the object into your dApp, where it can be used by the front end. 397 | 398 | The common properties used by `ethdeploy` in the outputted environments JSON are: 399 | 400 | 1. `address` {String} the address of the deployed contract instance 401 | 2. `transactionObject` {Object} the transaction object used to deploy that contract instance 402 | 3. `bytecode` {String} the Ethereum virtual machine code (bytecode) of that contract instance 403 | 4. `inputs` {Array} the inputs used to deploy that contract (with numbers formatted as hex base 16) 404 | 5. `interface` {String} the interface of the contract, specified as a JSON string 405 | 406 | These properties are both outputed by ethdeploy and looked at by the default deployment method `deploy` when the deployment schedule is being used. 407 | 408 | Other additional properties are: 409 | 410 | 1. `receipt` {Object} the transaction receipt data 411 | 2. `assembly` {Object} the contract assembly code 412 | 413 | ### License 414 | 415 | This module is under The MIT License. Please see the `LICENCE` file for more details. 416 | -------------------------------------------------------------------------------- /bin/ethdeploy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const meow = require('meow'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const ethdeploy = require('../src/index.js'); 7 | const log = require('../src/utils/index.js').log; 8 | 9 | function noop2Callback(v, d, cb) { 10 | cb(null, null); 11 | } 12 | 13 | function renameIfExsits(renamePath, renamePathOutput, cb) { 14 | if (fs.existsSync(renamePath)) { 15 | return fs.rename(renamePath, renamePathOutput, cb); 16 | } 17 | 18 | cb(null, null); 19 | return null; 20 | } 21 | 22 | // handle cli 23 | const cli = meow(` 24 | Usage 25 | $ ethdeploy 26 | Options 27 | --help the help CLI 28 | --version, -v the package verson number 29 | Example 30 | $ ethdeploy ./ethdeploy.config.testnet.js 31 | `, { 32 | alias: {}, 33 | }); 34 | 35 | if (typeof cli.input[0] === 'undefined') { 36 | cli.showHelp(); 37 | } else { 38 | const configPath = path.resolve(cli.input[0]); 39 | const configObject = require(configPath); // eslint-disable-line 40 | 41 | ethdeploy(configObject, (deployError, deployResult) => { 42 | if (deployError) { 43 | log('Deployment error', deployError); 44 | process.exit(1); 45 | } 46 | 47 | // config from result 48 | const config = deployResult.config; 49 | 50 | // if config output file is specified 51 | if (typeof config.output === 'object') { 52 | const outputString = deployResult.output; 53 | const outputFile = path.resolve(config.output.path, config.output.filename); 54 | const outputSafe = config.output.safe || false; 55 | const outputFileSafe = `${outputFile}_backup_${(new Date()).toISOString()}`; 56 | const renameMethod = outputSafe ? renameIfExsits : noop2Callback; 57 | 58 | // backup output from previous builds 59 | renameMethod(outputFile, outputFileSafe, renameError => { 60 | if (renameError) { 61 | log(`while writting safe output backup file: ${renameError}`); 62 | process.exit(1); 63 | } 64 | 65 | fs.writeFile(outputFile, outputString, (writeFileError) => { 66 | if (writeFileError) { 67 | log(`while writting output file to ${outputFile}: ${writeFileError}`); 68 | process.exit(1); 69 | } 70 | 71 | log(`Deployment file written to: ${outputFile}`); 72 | }); 73 | }); 74 | } 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /ethdeploy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentCicero/ethdeploy/acb1212e1942ce604eb82e34ce2f8c9d6b20a0a6/ethdeploy-logo.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /example/contracts/SimpleStore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract SimpleStore { 4 | uint256 public store; 5 | 6 | function SimpleStore(uint256 _initialValue) public { 7 | store = _initialValue; 8 | } 9 | 10 | function setStore(uint256 _value) public { 11 | store = _value; 12 | } 13 | 14 | function getValue() public constant returns (uint256) { 15 | return store; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/environments.json: -------------------------------------------------------------------------------- 1 | { 2 | "testrpc": { 3 | "contracts/SimpleStore.sol:SimpleStore": { 4 | "bytecode": "6060604052341561000f57600080fd5b60405160208061014b833981016040528080519060200190919050508060008190555050610109806100426000396000f3006060604052600436106053576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063209652551460585780637f626f1a14607e578063975057e714609e575b600080fd5b3415606257600080fd5b606860c4565b6040518082815260200191505060405180910390f35b3415608857600080fd5b609c600480803590602001909190505060cd565b005b341560a857600080fd5b60ae60d7565b6040518082815260200191505060405180910390f35b60008054905090565b8060008190555050565b600054815600a165627a7a72305820848c6b285029df686c3ea20ad4e3351824524f5fde4b95dc5257b0c3c8c80e5a0029", 5 | "interface": "[{\"constant\":true,\"inputs\":[],\"name\":\"getValue\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"setStore\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"store\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_initialValue\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]", 6 | "address": "0x3f7db06cf4e3b31b104c3aff154adfbcbf0a07fa", 7 | "inputs": [ 8 | 458977, 9 | { 10 | "from": "0x491e14be6fc0d633c2f8f20da25fe1962dba4ff3" 11 | } 12 | ], 13 | "transactionObject": { 14 | "from": "0x491e14be6fc0d633c2f8f20da25fe1962dba4ff3", 15 | "gas": 3000001 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /example/environments.json_backup_2017-05-15T19:14:14.867Z: -------------------------------------------------------------------------------- 1 | { 2 | "testrpc": { 3 | "SimpleStore": { 4 | "bytecode": "606060405234610000576040516020806100fd833981016040528080519060200190919050505b806000819055505b505b60c08061003d6000396000f360606040526000357c0100000000000000000000000000000000000000000000000000000000900480632096525514604a5780637f626f1a14606a578063975057e7146084575b6000565b34600057605460a4565b6040518082815260200191505060405180910390f35b346000576082600480803590602001909190505060af565b005b34600057608e60ba565b6040518082815260200191505060405180910390f35b600060005490505b90565b806000819055505b50565b6000548156", 5 | "interface": "[{\"constant\":true,\"inputs\":[],\"name\":\"getValue\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"setStore\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"store\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"inputs\":[{\"name\":\"_initialValue\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"constructor\"}]", 6 | "address": "0xb48cb3dc0f55ffb06a4810fdb1142cd2ae3a61a7", 7 | "inputs": [ 8 | 458977, 9 | { 10 | "from": "0xc9dd9662133b86564046506ce9dc7ade1898e1a6" 11 | } 12 | ], 13 | "transactionObject": { 14 | "from": "0xc9dd9662133b86564046506ce9dc7ade1898e1a6", 15 | "gas": 3000001 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/environments.json_backup_2017-10-22T22:29:29.858Z: -------------------------------------------------------------------------------- 1 | { 2 | "testrpc": { 3 | "SimpleStore": { 4 | "bytecode": "606060405234610000576040516020806100fd833981016040528080519060200190919050505b806000819055505b505b60c08061003d6000396000f360606040526000357c0100000000000000000000000000000000000000000000000000000000900480632096525514604a5780637f626f1a14606a578063975057e7146084575b6000565b34600057605460a4565b6040518082815260200191505060405180910390f35b346000576082600480803590602001909190505060af565b005b34600057608e60ba565b6040518082815260200191505060405180910390f35b600060005490505b90565b806000819055505b50565b6000548156", 5 | "interface": "[{\"constant\":true,\"inputs\":[],\"name\":\"getValue\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"setStore\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"store\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"inputs\":[{\"name\":\"_initialValue\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"constructor\"}]", 6 | "address": "0xf5fcf5c4301a8d4a78127ffdd455f224812436cb", 7 | "inputs": [ 8 | 458977, 9 | { 10 | "from": "0x94eec4dd61d256c7ff79f07798b8ed6188e72ccf" 11 | } 12 | ], 13 | "transactionObject": { 14 | "from": "0x94eec4dd61d256c7ff79f07798b8ed6188e72ccf", 15 | "gas": 3000001 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /example/environments.json_backup_2017-10-22T22:30:12.659Z: -------------------------------------------------------------------------------- 1 | { 2 | "testrpc": { 3 | "contracts/SimpleStore.sol:SimpleStore": { 4 | "bytecode": "6060604052341561000f57600080fd5b60405160208061014b833981016040528080519060200190919050508060008190555050610109806100426000396000f3006060604052600436106053576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063209652551460585780637f626f1a14607e578063975057e714609e575b600080fd5b3415606257600080fd5b606860c4565b6040518082815260200191505060405180910390f35b3415608857600080fd5b609c600480803590602001909190505060cd565b005b341560a857600080fd5b60ae60d7565b6040518082815260200191505060405180910390f35b60008054905090565b8060008190555050565b600054815600a165627a7a72305820848c6b285029df686c3ea20ad4e3351824524f5fde4b95dc5257b0c3c8c80e5a0029", 5 | "interface": "[{\"constant\":true,\"inputs\":[],\"name\":\"getValue\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"setStore\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"store\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_initialValue\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]", 6 | "address": "0x0603a73643c3dfeb7dc0bd37555cea9eb8edb6f7", 7 | "inputs": [ 8 | 458977, 9 | { 10 | "from": "0x5039d118b33d197451d794ccd0376281caeccc20" 11 | } 12 | ], 13 | "transactionObject": { 14 | "from": "0x5039d118b33d197451d794ccd0376281caeccc20", 15 | "gas": 3000001 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /example/ethdeploy.testrpc.config.js: -------------------------------------------------------------------------------- 1 | const TestRPC = require('ethereumjs-testrpc'); // eslint-disable-line 2 | 3 | module.exports = (options) => ({ // eslint-disable-line 4 | entry: [ 5 | './environments.json', 6 | './contracts', 7 | ], 8 | output: { 9 | path: './', 10 | filename: 'environments.json', 11 | safe: true, 12 | }, 13 | module: { 14 | environment: { 15 | name: 'testrpc', 16 | provider: TestRPC.provider(), 17 | defaultTxObject: { 18 | from: 1, 19 | gas: 3000001, 20 | }, 21 | }, 22 | preLoaders: [ 23 | { test: /\.(json)$/, loader: 'ethdeploy-environment-loader', build: true }, 24 | ], 25 | loaders: [ 26 | { test: /\.(sol)$/, loader: 'ethdeploy-solc-loader' }, 27 | ], 28 | deployment: (deploy, contracts, done) => { 29 | deploy(contracts['contracts/SimpleStore.sol:SimpleStore'], 458977, { from: 0 }).then(() => { 30 | done(); 31 | }); 32 | }, 33 | }, 34 | plugins: [ 35 | new options.plugins.JSONFilter(), 36 | new options.plugins.JSONExpander(), 37 | ], 38 | }); 39 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | const contracts = require('./environments.json'); // eslint-disable-line 2 | 3 | console.log('This is being logged from the index.js', contracts); // eslint-disable-line 4 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethdeploy-example", 3 | "version": "1.0.0", 4 | "description": "A simple ethdeploy example configuration ", 5 | "main": "index.js", 6 | "scripts": { 7 | "deploy": "node ../bin/ethdeploy.js ./ethdeploy.testrpc.config.js", 8 | "start": "npm run deploy && node ./index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "private": true, 12 | "keywords": [ 13 | "ethdeploy", 14 | "example" 15 | ], 16 | "author": "Nick Dodson", 17 | "license": "MIT", 18 | "dependencies": { 19 | "ethdeploy-solc-loader": "file:../src/loaders/solc", 20 | "ethdeploy-environment-loader": "file:../src/loaders/environment", 21 | "ethdeploy": "file:../", 22 | "ethereumjs-testrpc": "*" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internals/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); // eslint-disable-line 2 | 3 | var env = process.env.NODE_ENV; // eslint-disable-line 4 | var filename = 'ethdeploy'; // eslint-disable-line 5 | var library = 'ethdeploy'; // eslint-disable-line 6 | var config = { // eslint-disable-line 7 | entry: [ 8 | './lib/index.js', 9 | ], 10 | module: { 11 | loaders: [ 12 | { 13 | test: /\.js$/, 14 | loaders: ['babel-loader'], 15 | exclude: /node_modules/, 16 | }, 17 | { 18 | test: /\.json$/, 19 | loader: 'json', 20 | }, 21 | ], 22 | }, 23 | node: { 24 | fs: 'empty', 25 | }, 26 | devtool: 'cheap-module-source-map', 27 | output: { 28 | path: 'dist', 29 | filename: filename + '.js', // eslint-disable-line 30 | library: library, // eslint-disable-line 31 | libraryTarget: 'umd', 32 | umdNamedDefine: true, 33 | }, 34 | plugins: [ 35 | new webpack.BannerPlugin({ banner: ' /* eslint-disable */ ', raw: true, entryOnly: true }), 36 | new webpack.optimize.OccurrenceOrderPlugin(), 37 | new webpack.DefinePlugin({ 38 | 'process.env.NODE_ENV': JSON.stringify(env), 39 | }), 40 | ], 41 | }; 42 | 43 | 44 | if (env === 'production') { 45 | config.output.filename = filename + '.min.js'; // eslint-disable-line 46 | config.plugins 47 | .push(new webpack.optimize.UglifyJsPlugin({ 48 | compressor: { 49 | pure_getters: true, 50 | unsafe: true, 51 | unsafe_comps: true, 52 | warnings: false, 53 | screw_ie8: false, 54 | }, 55 | mangle: { 56 | screw_ie8: false, 57 | }, 58 | output: { 59 | screw_ie8: false, 60 | }, 61 | })); 62 | config.plugins.push(new webpack.optimize.DedupePlugin()); 63 | } 64 | 65 | 66 | module.exports = config; 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethdeploy", 3 | "version": "1.0.8", 4 | "description": "A highly configurable contract deployment and staging utiltiy.", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "dist", 8 | "internals", 9 | "bin", 10 | "lib", 11 | "src" 12 | ], 13 | "bin": { 14 | "ethdeploy": "./bin/ethdeploy.js" 15 | }, 16 | "scripts": { 17 | "start": "npm test", 18 | "release": "npmpub", 19 | "pretest": "npm run lint", 20 | "prepublish": "npm run test", 21 | "prebuild": "npm run build:clean && npm run test", 22 | "build:clean": "npm run test:clean && rimraf ./dist", 23 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib --copy-files", 24 | "build:umd": "cross-env BABEL_ENV=commonjs NODE_ENV=development webpack --config ./internals/webpack/webpack.config.js --progress", 25 | "build:umd:min": "cross-env BABEL_ENV=commonjs NODE_ENV=production webpack --config ./internals/webpack/webpack.config.js --progress --profile", 26 | "build:umd:stats": "cross-env BABEL_ENV=commonjs NODE_ENV=production webpack --config ./internals/webpack/webpack.config.js --progress --profile --json > dist/stats.json", 27 | "build": "npm run build:commonjs && npm run test:lib && npm run build:umd && npm run build:umd:min", 28 | "testrpc": "testrpc", 29 | "lint": "npm run lint:js", 30 | "lint:eslint": "eslint --ignore-path .gitignore --ignore-pattern **/**.min.js", 31 | "lint:js": "npm run lint:eslint -- . ", 32 | "lint:staged": "lint-staged", 33 | "test:clean": "rimraf ./coverage", 34 | "test": "mocha ./src/**/test.*.js ./src/**/*/test.*.js -R spec --timeout 2000000", 35 | "test:lib": "mocha ./lib/tests/**/*.js -R spec --timeout 2000000", 36 | "test:utils": "mocha ./src/utils/tests/**/*.js -R spec --timeout 2000000", 37 | "test:plugins": "mocha ./src/plugins/tests/**/*.js -R spec --timeout 2000000", 38 | "test-travis": "node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- ./src/**/test.*.js ./src/**/*/test.*.js -R spec --timeout 2000000", 39 | "coveralls": "npm run test-travis && cat ./coverage/lcov.info | coveralls" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "git+ssh://git@github.com/SilentCicero/ethdeploy.git" 44 | }, 45 | "babel": { 46 | "plugins": [ 47 | [ 48 | "transform-es2015-template-literals", 49 | { 50 | "loose": true 51 | } 52 | ], 53 | "transform-es2015-literals", 54 | "transform-es2015-function-name", 55 | "transform-es2015-arrow-functions", 56 | "transform-es2015-block-scoped-functions", 57 | [ 58 | "transform-es2015-classes", 59 | { 60 | "loose": true 61 | } 62 | ], 63 | "transform-es2015-object-super", 64 | "transform-es2015-shorthand-properties", 65 | [ 66 | "transform-es2015-computed-properties", 67 | { 68 | "loose": true 69 | } 70 | ], 71 | [ 72 | "transform-es2015-for-of", 73 | { 74 | "loose": true 75 | } 76 | ], 77 | "transform-es2015-sticky-regex", 78 | "transform-es2015-unicode-regex", 79 | "check-es2015-constants", 80 | [ 81 | "transform-es2015-spread", 82 | { 83 | "loose": true 84 | } 85 | ], 86 | "transform-es2015-parameters", 87 | [ 88 | "transform-es2015-destructuring", 89 | { 90 | "loose": true 91 | } 92 | ], 93 | "transform-es2015-block-scoping", 94 | "transform-object-rest-spread", 95 | "transform-es3-member-expression-literals", 96 | "transform-es3-property-literals" 97 | ], 98 | "env": { 99 | "commonjs": { 100 | "plugins": [ 101 | [ 102 | "transform-es2015-modules-commonjs", 103 | { 104 | "loose": true 105 | } 106 | ] 107 | ] 108 | } 109 | } 110 | }, 111 | "dependencies": { 112 | "clone-deep": "^0.2.4", 113 | "deep-assign": "2.0.0", 114 | "deep-equal": "1.0.1", 115 | "ethjs-contract": "0.1.7", 116 | "ethjs-query": "0.2.6", 117 | "fs": "0.0.1-security", 118 | "meow": "3.7.0", 119 | "node-dir": "0.1.16", 120 | "strip-hex-prefix": "1.0.0" 121 | }, 122 | "devDependencies": { 123 | "ethdeploy-environment-loader": "file:./src/loaders/environment", 124 | "ethdeploy-solc-loader": "file:./src/loaders/solc", 125 | "ethdeploy-raw-solc-loader": "file:./src/loaders/raw-solc", 126 | "babel-cli": "6.18.0", 127 | "babel-core": "6.18.2", 128 | "babel-eslint": "7.1.0", 129 | "babel-loader": "6.2.8", 130 | "babel-plugin-check-es2015-constants": "6.8.0", 131 | "babel-plugin-transform-class-properties": "6.18.0", 132 | "babel-plugin-transform-es2015-arrow-functions": "6.8.0", 133 | "babel-plugin-transform-es2015-block-scoped-functions": "6.8.0", 134 | "babel-plugin-transform-es2015-block-scoping": "6.18.0", 135 | "babel-plugin-transform-es2015-classes": "6.18.0", 136 | "babel-plugin-transform-es2015-computed-properties": "6.8.0", 137 | "babel-plugin-transform-es2015-destructuring": "6.19.0", 138 | "babel-plugin-transform-es2015-for-of": "6.18.0", 139 | "babel-plugin-transform-es2015-function-name": "6.9.0", 140 | "babel-plugin-transform-es2015-literals": "6.8.0", 141 | "babel-plugin-transform-es2015-modules-commonjs": "6.18.0", 142 | "babel-plugin-transform-es2015-object-super": "6.8.0", 143 | "babel-plugin-transform-es2015-parameters": "6.18.0", 144 | "babel-plugin-transform-es2015-shorthand-properties": "6.18.0", 145 | "babel-plugin-transform-es2015-spread": "6.8.0", 146 | "babel-plugin-transform-es2015-sticky-regex": "6.8.0", 147 | "babel-plugin-transform-es2015-template-literals": "6.8.0", 148 | "babel-plugin-transform-es2015-unicode-regex": "6.11.0", 149 | "babel-plugin-transform-es3-member-expression-literals": "6.5.0", 150 | "babel-plugin-transform-es3-property-literals": "6.5.0", 151 | "babel-plugin-transform-object-rest-spread": "6.19.0", 152 | "babel-plugin-transform-runtime": "^6.15.0", 153 | "babel-polyfill": "6.16.0", 154 | "babel-register": "6.18.0", 155 | "bignumber.js": "^3.0.1", 156 | "bn.js": "^4.11.6", 157 | "chai": "3.5.0", 158 | "check-es3-syntax-cli": "0.1.3", 159 | "coveralls": "2.11.9", 160 | "cross-env": "1.0.7", 161 | "eslint": "2.10.1", 162 | "eslint-config-airbnb": "9.0.1", 163 | "eslint-import-resolver-webpack": "0.2.4", 164 | "eslint-plugin-import": "1.8.0", 165 | "eslint-plugin-jsx-a11y": "1.2.0", 166 | "eslint-plugin-react": "5.1.1", 167 | "ethereumjs-testrpc": "3.0.2", 168 | "ethjs-provider-signer": "^0.1.0", 169 | "ethjs-signer": "^0.1.0", 170 | "eventsource-polyfill": "0.9.6", 171 | "istanbul": "0.4.5", 172 | "json-loader": "0.5.4", 173 | "lint-staged": "1.0.1", 174 | "mocha": "3.1.2", 175 | "pre-commit": "1.1.3", 176 | "rimraf": "2.3.4", 177 | "solc": "^0.4.6", 178 | "webpack": "2.1.0-beta.15" 179 | }, 180 | "engines": { 181 | "npm": ">=3", 182 | "node": ">=6.5.0" 183 | }, 184 | "keywords": [ 185 | "ethereum", 186 | "events", 187 | "rpc" 188 | ], 189 | "author": "Nick Dodson ", 190 | "license": "MIT", 191 | "bugs": { 192 | "url": "https://github.com/SilentCicero/ethdeploy/issues" 193 | }, 194 | "homepage": "https://github.com/SilentCicero/ethdeploy#readme", 195 | "lint-staged": { 196 | "lint:eslint": "*.js" 197 | }, 198 | "eslintConfig": { 199 | "parser": "babel-eslint", 200 | "extends": "airbnb", 201 | "env": { 202 | "node": true, 203 | "mocha": true, 204 | "es6": true 205 | }, 206 | "parserOptions": { 207 | "ecmaVersion": 6, 208 | "sourceType": "module" 209 | }, 210 | "rules": { 211 | "import/no-unresolved": 2, 212 | "comma-dangle": [ 213 | 2, 214 | "always-multiline" 215 | ], 216 | "indent": [ 217 | 2, 218 | 2, 219 | { 220 | "SwitchCase": 1 221 | } 222 | ], 223 | "no-console": 1, 224 | "max-len": 0, 225 | "prefer-template": 2, 226 | "no-use-before-define": 0, 227 | "newline-per-chained-call": 0, 228 | "arrow-body-style": [ 229 | 2, 230 | "as-needed" 231 | ] 232 | } 233 | }, 234 | "pre-commit": "test" 235 | } 236 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils/index.js'); 2 | const lib = require('./lib/index.js'); 3 | const cloneDeep = require('clone-deep'); 4 | const deployPlugins = require('./plugins/index.js'); 5 | const bnToString = utils.bnToString; 6 | const error = utils.error; 7 | const configError = lib.configError; 8 | const entrySourceMap = lib.entrySourceMap; 9 | const loadEnvironment = lib.loadEnvironment; 10 | const loadContracts = lib.loadContracts; 11 | const buildDeployer = lib.buildDeployMethod; 12 | const processOutput = lib.processOutput; 13 | const transformContracts = lib.transformContracts; 14 | 15 | 16 | /** 17 | * Intakes config object, deploys contracts, outputs result as string for file writting. 18 | * 19 | * @method ethdeploy 20 | * @param {Object|Function} config the ethdeploy config object or method 21 | * @param {Function} callbackInput the final callback that returns the output 22 | * @callback {Object} outputObject returns the final config object, and contracts output 23 | */ 24 | module.exports = function ethdeploy(config, callbackInput) { // eslint-disable-line 25 | // this is the initial deployed contracts store 26 | var deployedContracts = {}; // eslint-disable-line 27 | const callback = callbackInput || function cb() {}; 28 | 29 | if (typeof config !== 'function' && typeof config !== 'object') { 30 | return callback(error('config input param must be a type Object or Function.'), null); 31 | } 32 | 33 | // build config object with options, plugins 34 | const configObject = (typeof config !== 'function') ? config : config({ plugins: deployPlugins }); 35 | 36 | // validate the config object, if error, stop process 37 | if (configError(configObject) !== null) { 38 | return callback(error(configError(configObject)), null); 39 | } 40 | 41 | // stage loaders, deployer, environment transform, plugins, entry, env, deployment 42 | const buildDeployMethod = configObject.deployer || buildDeployer; 43 | const buildEnvironment = configObject.environmentLoader || loadEnvironment; 44 | const entry = configObject.entry; 45 | const modulePreLoaders = configObject.module.preLoaders || []; 46 | const moduleLoaders = configObject.module.loaders || []; 47 | const moduleEnvironment = configObject.module.environment; 48 | const moduleDeloyment = configObject.module.deployment; 49 | const plugins = configObject.plugins || []; 50 | const loadEntry = configObject.sourceMapper || entrySourceMap; 51 | 52 | // build report method 53 | const reportMethod = (name, data, address, inputs, transactionObject, receipt) => { 54 | deployedContracts = Object.assign({}, cloneDeep(deployedContracts), bnToString({ 55 | [name]: Object.assign({}, cloneDeep(deployedContracts[name] || {}), cloneDeep(data), { name, address, inputs, transactionObject, receipt }), 56 | }, 16, true)); 57 | }; 58 | 59 | // build sourcemap from entry 60 | loadEntry(entry, (sourceMapError, sourceMap) => { // eslint-disable-line 61 | if (sourceMapError !== null) { return callback(error(sourceMapError), null); } 62 | 63 | // transform environment 64 | buildEnvironment(moduleEnvironment, (envTransformError, environment) => { // eslint-disable-line 65 | if (envTransformError !== null) { return callback(error(envTransformError, null)); } 66 | 67 | // load and process contracts from sourcemap, base contracts layer 68 | loadContracts(modulePreLoaders, {}, sourceMap, environment, (preLoaderError, baseContracts) => { // eslint-disable-line 69 | if (preLoaderError !== null) { return callback(error(preLoaderError), null); } 70 | 71 | // load and process contracts from sourcemap 72 | loadContracts(moduleLoaders, baseContracts, sourceMap, environment, (loaderError, contracts) => { // eslint-disable-line 73 | if (loaderError !== null) { return callback(error(loaderError), null); } 74 | 75 | // scoped base contracts 76 | const scopedBaseContracts = transformContracts(baseContracts, environment.name); 77 | 78 | // scope the contracts only to the environment being deployed 79 | const scopedContracts = transformContracts(contracts, environment.name); 80 | 81 | // build done method 82 | const doneMethod = () => { 83 | const finalOutput = Object.assign({}, cloneDeep(baseContracts), { 84 | [environment.name]: cloneDeep(deployedContracts), 85 | }); 86 | 87 | // final output processing with plugins 88 | processOutput(plugins, finalOutput, configObject, scopedBaseContracts, scopedContracts, environment, (pluginError, outputString) => { 89 | if (pluginError) { 90 | callback(pluginError, null); 91 | } else { 92 | callback(null, { config: configObject, output: outputString }); 93 | } 94 | }); 95 | 96 | utils.log('Deployment module completed!'); 97 | }; 98 | 99 | // build deploy method 100 | const deployMethod = buildDeployMethod(scopedBaseContracts, environment, reportMethod); 101 | 102 | // run the deployment module 103 | moduleDeloyment(deployMethod, scopedContracts, doneMethod, environment); 104 | }); 105 | }); 106 | }); 107 | }); 108 | }; 109 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | const deepAssign = require('deep-assign'); 2 | const deepEqual = require('deep-equal'); 3 | const Eth = require('ethjs-query'); 4 | const EthUtils = require('ethjs-util'); 5 | const EthContract = require('ethjs-contract'); 6 | const stripHexPrefix = require('strip-hex-prefix'); 7 | const cloneDeep = require('clone-deep'); 8 | const utils = require('../utils/index.js'); 9 | const bnToString = utils.bnToString; 10 | const error = utils.error; 11 | const filterSourceMap = utils.filterSourceMap; 12 | const deployContract = utils.deployContract; 13 | const getInputSources = utils.getInputSources; 14 | 15 | /** 16 | * Transform default tx object with accounts (mainly account 0 => accounts[0]) 17 | * 18 | * @method transformTxObject 19 | * @param {Object} txObject the input default tx object 20 | * @param {Array} accounts the accounts from Ethereum RPC 21 | * @return {Object} output the transformed tx object 22 | */ 23 | function transformTxObject(txObject, accounts) { 24 | // if no tx object, bypass 25 | if (typeof txObject !== 'object') { return txObject; } 26 | 27 | const from = typeof txObject.from === 'number' ? accounts[txObject.from] : txObject.from; 28 | 29 | return Object.assign({}, cloneDeep(txObject), { from }); 30 | } 31 | 32 | /** 33 | * Load the environment, get accounts, gas limits, balances etc. 34 | * 35 | * @method loadEnvironment 36 | * @param {Object} environment the environment object specified in the config.js 37 | * @param {Function} callback the callback to return the environment 38 | * @callback {Object} output the transformed environment object 39 | */ 40 | function loadEnvironment(environment, callback) { 41 | const errorMsgBase = 'while transforming environment, '; 42 | 43 | var transformedEnvironment = cloneDeep(environment); // eslint-disable-line 44 | 45 | const query = new Eth(transformedEnvironment.provider); 46 | query.net_version((versionError, result) => { // eslint-disable-line 47 | if (versionError) { return callback(error(`${errorMsgBase}error attempting to connect to node environment '${transformedEnvironment.name}': ${versionError}`), null); } 48 | 49 | query.accounts((accountsError, accounts) => { // eslint-disable-line 50 | if (accountsError !== null) { return callback(error(`${errorMsgBase}error while getting accounts for deployment: ${accountsError}`), null); } 51 | 52 | callback(accountsError, Object.assign({}, cloneDeep(transformedEnvironment), { 53 | accounts, 54 | defaultTxObject: transformTxObject(environment.defaultTxObject, accounts), 55 | })); 56 | }); 57 | }); 58 | } 59 | 60 | /** 61 | * Prepair the contracts for deployment, scope contracts array, add name 62 | * 63 | * @method transformContracts 64 | * @param {Object} contracts the environment object specified in the config.js 65 | * @param {String} environmentName the callback to return the environment 66 | * @return {Object} output the scoped contracts object, ready for deployment method 67 | */ 68 | function transformContracts(contracts, environmentName) { 69 | const scopedContracts = Object.assign({}, cloneDeep(contracts[environmentName] || {})); 70 | 71 | // add name property to all contracts for deployment identification 72 | Object.keys(scopedContracts).forEach((contractName) => { 73 | scopedContracts[contractName].name = contractName; 74 | }); 75 | 76 | // return new scroped contracts 77 | return scopedContracts; 78 | } 79 | 80 | /** 81 | * Validate the config, after the method has been called 82 | * 83 | * @method configError 84 | * @param {Object} config the environment object specified in the config.js 85 | * @return {Object|Null} output the config error, if any 86 | */ 87 | function configError(config) { 88 | if (typeof config !== 'object') { return `the config method must return a config object, got type ${typeof config}`; } 89 | if (typeof config.entry === 'undefined') { return `No defined entry! 'config.entry' must be defined, got type ${typeof config.entry}.`; } 90 | if (typeof config.module !== 'object') { return `No defined deployment module! 'config.module' must be an object, got type ${typeof config.module}`; } 91 | if (typeof config.module.deployment !== 'function') { return `No defined deployment function! 'config.module.deployment' must be type Function (i.e. 'module.deployment = (deploy, contracts, done){}' ), got ${typeof config.module.deployment}`; } 92 | if (typeof config.module.environment !== 'object') { return `No defined module environment object! 'config.module.environment' must be type Object, got ${typeof config.module.environment}`; } 93 | 94 | const environment = config.module.environment; 95 | if (typeof environment.provider !== 'object') { return `No defined provider object! 'config.module.environment' must have a defined 'provider' object, got ${typeof environment.provider}`; } 96 | if (typeof environment.name !== 'string') { return `No defined environment name! 'config.module.environment.name' must be type String, got ${typeof environment.name}`; } 97 | 98 | return null; 99 | } 100 | 101 | /** 102 | * Require the loader 103 | * 104 | * @method requireLoader 105 | * @param {Object} loaderConfig the loader config 106 | * @return {Object} loader the required loader 107 | */ 108 | function requireLoader(loaderConfig) { 109 | const errMsgBase = `while requiring loader ${JSON.stringify(loaderConfig)} config,`; 110 | if (typeof loaderConfig !== 'object') { throw error(`${errMsgBase}, config must be object, got ${typeof loaderConfig}`); } 111 | if (typeof loaderConfig.loader !== 'string') { throw error(`${errMsgBase}, config.loader must be String, got ${JSON.stringify(loaderConfig.loader)}`); } 112 | 113 | return require(loaderConfig.loader); // eslint-disable-line 114 | } 115 | 116 | /** 117 | * Load contracts from the sourcemap, start from base 118 | * 119 | * @method loadContracts 120 | * @param {Array} loaders the array of loaders 121 | * @param {Object} base the base contracts object from which to assign to 122 | * @param {Object} sourceMap the sourcemap from load time 123 | * @param {Object} environment the environments object 124 | * @param {Function} callback the method callback that returns the contracts 125 | * @callback {Object} contracts the loaded contracts 126 | */ 127 | function loadContracts(loaders, base, sourceMap, environment, callback) { // eslint-disable-line 128 | var outputContracts = cloneDeep(base); // eslint-disable-line 129 | const errMsgBase = 'while processing entry data, '; 130 | if (!Array.isArray(loaders)) { return callback(error(`${errMsgBase}loaders must be type Array, got ${typeof loaders}`)); } 131 | 132 | // process loaders 133 | try { 134 | loaders.forEach((loaderConfig) => { 135 | // require the loader for use 136 | const loader = requireLoader(loaderConfig); 137 | 138 | // filtered sourcemap based on regex/include 139 | const filteredSourceMap = filterSourceMap(loaderConfig.test, 140 | loaderConfig.include, 141 | sourceMap, 142 | loaderConfig.exclude); 143 | 144 | // get loaded contracts 145 | const loadedEnvironment = loader(cloneDeep(filteredSourceMap), loaderConfig, environment); 146 | 147 | // output the new contracts 148 | outputContracts = Object.assign({}, cloneDeep(outputContracts), cloneDeep(loadedEnvironment)); 149 | }); 150 | 151 | // transform final contracts output 152 | callback(null, outputContracts); 153 | } catch (loaderError) { 154 | callback(error(`${errMsgBase}loader error: ${loaderError}`)); 155 | } 156 | } 157 | 158 | /** 159 | * Process the final load output into string output for file creation. 160 | * 161 | * @method processOutput 162 | * @param {Array} plugins the array of plugins, if any 163 | * @param {Object} outputObject the deplyed contracts 164 | * @param {Object} configObject the config js object 165 | * @param {Function} callback the method callback that returns the final string output 166 | * @callback {String} outputString the loaded contracts 167 | */ 168 | function processOutput(plugins, outputObject, configObject, baseContracts, contracts, environment, callback) { // eslint-disable-line 169 | // the final string to be outputed 170 | let outputString = JSON.stringify(outputObject, null, 2); // eslint-disable-line 171 | 172 | // the err msg base 173 | const errMsgBase = 'while processing output with plugins, '; 174 | if (!Array.isArray(plugins)) { return callback(error(`${errMsgBase}plugins must be type Array, got ${typeof plugins}`)); } 175 | 176 | // process deployers 177 | try { 178 | plugins.forEach((plugin) => { 179 | // process deployer method 180 | outputString = plugin.process({ output: outputString, config: configObject, baseContracts, contracts, environment }); 181 | }); 182 | 183 | // return final output string 184 | callback(null, outputString); 185 | } catch (deployerError) { 186 | callback(error(`${errMsgBase}plugins error: ${deployerError}`)); 187 | } 188 | } 189 | 190 | /** 191 | * Determine if the contract has already been deployed. 192 | * 193 | * @method contractIsDeployed 194 | * @param {Object} baseContract the base contracts on which to deploy new ones 195 | * @param {Object} stagedContract the transformed environment 196 | * @return {Boolean} isDeployed has the contract already been deployed 197 | */ 198 | function contractIsDeployed(baseContract, stagedContract) { 199 | // if bytecode and inputs match, then skip with instance 200 | if (deepEqual(typeof baseContract.address, 'string') 201 | && deepEqual(baseContract.transactionObject, stagedContract.transactionObject) 202 | && deepEqual(`0x${stripHexPrefix(baseContract.bytecode)}`, `0x${stripHexPrefix(stagedContract.bytecode)}`) 203 | && deepEqual(baseContract.inputs, stagedContract.inputs)) { 204 | return true; 205 | } 206 | 207 | return false; 208 | } 209 | 210 | function isDefined(value) { 211 | return typeof value !== 'undefined'; 212 | } 213 | 214 | /** 215 | * Is the value a transaction object. 216 | * 217 | * @method isTransactionObject 218 | * @param {Optional} value the potential tx object 219 | * @return {Boolean} isTransactionObject is the object a tx object 220 | */ 221 | function isTransactionObject(value) { 222 | if (typeof value !== 'object') { return false; } 223 | const keys = Object.keys(value); 224 | 225 | if (keys.length > 5) { return false; } 226 | if (keys.length === 0) { return true; } 227 | 228 | if (keys.length > 0 && isDefined(value.from) || isDefined(value.to) || isDefined(value.data) || isDefined(value.gas) || isDefined(value.gasPrice)) { 229 | return true; 230 | } 231 | 232 | return false; 233 | } 234 | 235 | /** 236 | * Basic deployer, if not deployed, deploy, else, skip and return instance 237 | * 238 | * @method buildDeployMethod 239 | * @param {Array} baseContracts the base contracts on which to compare to see if already deployed 240 | * @param {Object} transformedEnvironment the transformed environment 241 | * @param {Object} report the reporter method to report newly deployed contracts 242 | * @callback {Function} deploy the deply method used in module.deployment 243 | */ 244 | function buildDeployMethod(baseContracts, transformedEnvironment, report) { 245 | return (...args) => { 246 | let transactionObject = {}; 247 | const defaultTxObject = transformedEnvironment.defaultTxObject || {}; 248 | const contractData = args[0]; 249 | 250 | 251 | if (typeof contractData !== 'object') { 252 | const noContractError = 'A contract you are trying to deploy does not exist in your contracts object. Please check your entry, loaders and contracts object.'; 253 | 254 | return Promise.reject(error(noContractError)); 255 | } 256 | 257 | const baseContract = baseContracts[contractData.name] || {}; 258 | const contractNewArguments = args.slice(1); 259 | const contractInputs = bnToString(Array.prototype.slice.call(contractNewArguments)); 260 | const contractBytecode = `0x${stripHexPrefix(contractData.bytecode)}`; 261 | const contractABI = JSON.parse(contractData.interface); 262 | const eth = new Eth(transformedEnvironment.provider); 263 | const contract = new EthContract(eth); 264 | const contractFactory = contract(contractABI, contractBytecode, defaultTxObject); 265 | 266 | // trim callback from inputs, not args 267 | // custom tx object not handled yet..... 268 | if (typeof contractInputs[contractInputs.length - 1] === 'function') { 269 | contractInputs.pop(); 270 | } 271 | 272 | // if there is a tx object provided for just this contractInputs 273 | // then get tx object, assign over default and use as the latest tx object 274 | if (isTransactionObject(contractInputs[contractInputs.length - 1])) { 275 | const transformedTransactionObject = transformTxObject(contractInputs[contractInputs.length - 1], transformedEnvironment.accounts); 276 | contractInputs[contractInputs.length - 1] = transformedTransactionObject; 277 | transactionObject = Object.assign({}, cloneDeep(defaultTxObject), cloneDeep(transformedTransactionObject)); 278 | } else { 279 | transactionObject = Object.assign({}, cloneDeep(defaultTxObject)); 280 | } 281 | 282 | // check contract has transaction object, either default or specified 283 | if (!EthUtils.isHexString(transactionObject.from, 20)) { 284 | const invalidFromAccount = `Attempting to deploy contract '${contractData.name}' with an invalid 'from' account specified. The 'from' account must be a valid 20 byte hex prefixed Ethereum address, got value '${transactionObject.from}'. Please specify a defaultTxObject in the module.environment.defaultTxObject (i.e. 'defaultTxObject: { from: 0 }') object or in the in the deploy method.`; 285 | 286 | return Promise.reject(error(invalidFromAccount)); 287 | } 288 | 289 | // check if contract is already deployed, if so, return instance 290 | return new Promise((resolve, reject) => { 291 | const resolveAndReport = (contractInstance) => { 292 | // report the contract 293 | report(contractData.name, 294 | contractData, 295 | contractInstance.address, 296 | contractInputs, 297 | transactionObject, 298 | (contractInstance.receipt || baseContract.receipt)); 299 | 300 | // resolve deployment 301 | resolve(contractInstance); 302 | }; 303 | 304 | // if the contract is deployed, resolve with base base contract, else deploy 305 | if (contractIsDeployed(baseContract, { 306 | transactionObject, 307 | bytecode: contractBytecode, 308 | inputs: contractInputs, 309 | })) { 310 | resolveAndReport(contractFactory.at(baseContract.address)); 311 | } else { 312 | deployContract(eth, contractFactory, contractInputs, (deployError, instance) => { 313 | if (deployError) { 314 | console.log(error(`while deploying contract '${contractData.name}': `, JSON.stringify(deployError.value, null, 2))); // eslint-disable-line 315 | reject(deployError); 316 | } else { 317 | resolveAndReport(instance); 318 | } 319 | }); 320 | } 321 | }); 322 | }; 323 | } 324 | 325 | /** 326 | * Get source map for a single config object entry path. 327 | * 328 | * @method singleEntrySourceMap 329 | * @param {String} entryItem the entry item string, generally a file or dir path 330 | * @param {Array} entryData the entry data 331 | * @param {Object} sourceMap the source map 332 | * @callback {Function} callback the callback 333 | */ 334 | function singleEntrySourceMap(entryItem, entryData, sourceMap, callback) { // eslint-disable-line 335 | if (typeof entryItem !== 'string') { return callback(null, sourceMap); } 336 | 337 | // get input sources for this entry 338 | getInputSources(entryItem, (inputSourceError, inputSourceMap) => { // eslint-disable-line 339 | if (inputSourceError) { return callback(inputSourceError, null); } 340 | 341 | // get source data 342 | const sourceData = deepAssign({}, sourceMap, inputSourceMap); 343 | 344 | // get next entry item 345 | const nextEntryItem = entryData[entryData.indexOf(entryItem) + 1]; 346 | 347 | // recursively go through tree 348 | singleEntrySourceMap(nextEntryItem, entryData, sourceData, callback); 349 | }); 350 | } 351 | 352 | /** 353 | * Build complete file source map of all entry items from the config.entry. 354 | * 355 | * @method entrySourceMap 356 | * @param {Array|String} entry the entry object 357 | * @param {Function} callback the callback that will return the source map 358 | * @callback {Object} sourceMap the source map object with all files/dirs in entry 359 | */ 360 | function entrySourceMap(entry, callback) { 361 | const entryData = typeof entry === 'string' ? [entry] : entry; 362 | 363 | // get source map 364 | singleEntrySourceMap(entryData[0], entryData, {}, callback); 365 | } 366 | 367 | // critical concept methods for ethdeploy 368 | module.exports = { 369 | transformTxObject, 370 | processOutput, 371 | buildDeployMethod, 372 | loadContracts, 373 | requireLoader, 374 | configError, 375 | transformContracts, 376 | loadEnvironment, 377 | singleEntrySourceMap, 378 | entrySourceMap, 379 | }; 380 | -------------------------------------------------------------------------------- /src/lib/tests/test.index.js: -------------------------------------------------------------------------------- 1 | const lib = require('../index.js'); 2 | const assert = require('chai').assert; 3 | 4 | /* 5 | SMALL: 6 | transformTxObject, 7 | requireLoader, 8 | configError, 9 | transformContracts, 10 | loadEnvironment, 11 | 12 | LARGE: 13 | processOutput, 14 | buildDeployMethod, 15 | loadContracts, 16 | 17 | MEDIUM: 18 | singleEntrySourceMap, 19 | entrySourceMap, 20 | */ 21 | 22 | describe('lib', () => { 23 | // smaller methods first 24 | 25 | describe('transformTxObject', () => { 26 | it('should function properly', () => { 27 | assert.equal(typeof lib.transformTxObject, 'function'); 28 | }); 29 | }); 30 | 31 | describe('requireLoader', () => { 32 | it('should function properly', () => { 33 | assert.equal(typeof lib.requireLoader, 'function'); 34 | }); 35 | }); 36 | 37 | describe('configError', () => { 38 | it('should function properly', () => { 39 | assert.equal(typeof lib.configError, 'function'); 40 | }); 41 | }); 42 | 43 | describe('loadEnvironment', () => { 44 | it('should function properly', () => { 45 | assert.equal(typeof lib.loadEnvironment, 'function'); 46 | }); 47 | }); 48 | 49 | describe('transformContracts', () => { 50 | it('should function properly', () => { 51 | assert.equal(typeof lib.transformContracts, 'function'); 52 | }); 53 | }); 54 | 55 | // medium size methods 56 | 57 | describe('singleEntrySourceMap', () => { 58 | it('should function properly', () => { 59 | assert.equal(typeof lib.singleEntrySourceMap, 'function'); 60 | }); 61 | }); 62 | 63 | describe('entrySourceMap', () => { 64 | it('should function properly', () => { 65 | assert.equal(typeof lib.entrySourceMap, 'function'); 66 | }); 67 | }); 68 | 69 | // larger methods second 70 | 71 | describe('processOutput', () => { 72 | it('should function properly', () => { 73 | assert.equal(typeof lib.processOutput, 'function'); 74 | }); 75 | }); 76 | 77 | describe('buildDeployMethod', () => { 78 | it('should function properly', () => { 79 | assert.equal(typeof lib.buildDeployMethod, 'function'); 80 | }); 81 | }); 82 | 83 | describe('loadContracts', () => { 84 | it('should function properly', () => { 85 | assert.equal(typeof lib.loadContracts, 'function'); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /src/loaders/environment/index.js: -------------------------------------------------------------------------------- 1 | const deepAssign = require('deep-assign'); 2 | 3 | /** 4 | * Loads an environments.json file, produced by ethdeploy 5 | * 6 | * @method loader 7 | * @param {Object} sourceMap the file source map 8 | * @param {Object} loaderConfig the config for the specified loader 9 | * @param {Object} environment the loaded environment object 10 | * @return {Object} contracts the output contracts 11 | */ 12 | module.exports = function loader(sourceMap, loaderConfig, environment) { // eslint-disable-line 13 | let outputObject = Object.assign({}); 14 | 15 | Object.keys(sourceMap).forEach((fileName) => { 16 | try { 17 | const source = (sourceMap[fileName] === '' && loaderConfig.build) ? '{}' : sourceMap[fileName]; 18 | const testJSON = JSON.parse(source); 19 | 20 | // if not an object 21 | if (typeof testJSON !== 'object') { throw new Error(`the file '${fileName}' did not result in a type Object json result.`); } 22 | 23 | outputObject = deepAssign({}, outputObject, testJSON); 24 | } catch (jsonError) { 25 | throw new Error(`while loading environment JSON from file '${fileName}', JSON error: ${JSON.stringify(jsonError)}`); 26 | } 27 | }); 28 | 29 | return outputObject; 30 | }; 31 | -------------------------------------------------------------------------------- /src/loaders/environment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethdeploy-environment-loader", 3 | "version": "1.0.0", 4 | "description": "A solc loader for ethdeploy.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/SilentCicero/ethdeploy.git" 12 | }, 13 | "keywords": [ 14 | "environment", 15 | "loader", 16 | "ethdeploy", 17 | "ethereum", 18 | "environment" 19 | ], 20 | "dependencies": { 21 | "deep-assign": "*" 22 | }, 23 | "author": "Nick Dodson", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/SilentCicero/ethdeploy-solc-loader/issues" 27 | }, 28 | "homepage": "https://github.com/SilentCicero/ethdeploy-solc-loader#readme" 29 | } 30 | -------------------------------------------------------------------------------- /src/loaders/raw-environment/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Here the sourcemap is the environments JSON, so we can just copy and return. 3 | * 4 | * @method loader 5 | * @param {Object} sourceMap the file source map 6 | * @param {Object} loaderConfig the config for the specified loader 7 | * @param {Object} environment the loaded environment object 8 | * @return {Object} contracts the output contracts 9 | */ 10 | module.exports = function loader(sourceMap, loaderConfig, environment) { // eslint-disable-line 11 | return Object.assign({}, sourceMap); 12 | }; 13 | -------------------------------------------------------------------------------- /src/loaders/raw-environment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethdeploy-raw-environment-loader", 3 | "version": "1.0.0", 4 | "description": "A raw environment loader for ethdeploy.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/SilentCicero/ethdeploy.git" 12 | }, 13 | "keywords": [ 14 | "solc", 15 | "loader", 16 | "ethdeploy", 17 | "ethereum", 18 | "solidity" 19 | ], 20 | "author": "Nick Dodson", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/SilentCicero/ethdeploy-raw-solc-loader/issues" 24 | }, 25 | "homepage": "https://github.com/SilentCicero/ethdeploy-raw-solc-loader#readme" 26 | } 27 | -------------------------------------------------------------------------------- /src/loaders/raw-solc/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Here the sourcemap is the contracts JSON, so we can just copy, name and return. 3 | * 4 | * @method loader 5 | * @param {Object} sourceMap the file source map 6 | * @param {Object} loaderConfig the config for the specified loader 7 | * @param {Object} environment the loaded environment object 8 | * @return {Object} contracts the output contracts 9 | */ 10 | module.exports = function loader(sourceMap, loaderConfig, environment) { // eslint-disable-line 11 | return Object.assign({}, { [environment.name]: sourceMap }); 12 | }; 13 | -------------------------------------------------------------------------------- /src/loaders/raw-solc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethdeploy-raw-solc-loader", 3 | "version": "1.0.0", 4 | "description": "A raw solc loader for ethdeploy.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/SilentCicero/ethdeploy.git" 12 | }, 13 | "keywords": [ 14 | "solc", 15 | "loader", 16 | "ethdeploy", 17 | "ethereum", 18 | "solidity" 19 | ], 20 | "author": "Nick Dodson", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/SilentCicero/ethdeploy-raw-solc-loader/issues" 24 | }, 25 | "homepage": "https://github.com/SilentCicero/ethdeploy-raw-solc-loader#readme" 26 | } 27 | -------------------------------------------------------------------------------- /src/loaders/solc-json/index.js: -------------------------------------------------------------------------------- 1 | // like solc output 2 | function solcOutputLike(obj) { 3 | let isSolcLike = false; 4 | 5 | Object.keys(obj).forEach((key) => { 6 | if (!isSolcLike && typeof obj[key] === 'object') { 7 | isSolcLike = (typeof obj[key].bytecode === 'string' 8 | && typeof obj[key].interface === 'string'); 9 | } 10 | }); 11 | 12 | return isSolcLike; 13 | } 14 | 15 | /** 16 | * Loads JSON produced by solc, formats it for environment style 17 | * 18 | * @method loader 19 | * @param {Object} sourceMap the file source map 20 | * @param {Object} loaderConfig the config for the specified loader 21 | * @param {Object} environment the loaded environment object 22 | * @return {Object} contracts the output contracts 23 | */ 24 | module.exports = function loader(sourceMap, loaderConfig, environment) { 25 | const solcContracts = {}; 26 | 27 | sourceMap.forEach((fileName) => { 28 | try { 29 | const testJSON = JSON.parse(sourceMap[fileName]); 30 | 31 | // if not an object 32 | if (typeof testJSON !== 'object') { throw new Error(`the file '${fileName}' did not result in a type Object json result.`); } 33 | 34 | // if the file looks like a solc output file 35 | if (solcOutputLike(testJSON)) { 36 | solcContracts[fileName] = testJSON; 37 | } 38 | } catch (jsonError) { 39 | throw new Error(`[solc-json] while loading solc JSON, error: ${JSON.stringify(jsonError)}`); 40 | } 41 | }); 42 | 43 | return { [environment.name]: solcContracts }; 44 | }; 45 | -------------------------------------------------------------------------------- /src/loaders/solc-json/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethdeploy-solc-json-loader", 3 | "version": "1.0.0", 4 | "description": "A solc json loader for ethdeploy.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/SilentCicero/ethdeploy.git" 12 | }, 13 | "keywords": [ 14 | "solc", 15 | "loader", 16 | "ethdeploy", 17 | "ethereum", 18 | "solidity" 19 | ], 20 | "author": "Nick Dodson", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/SilentCicero/ethdeploy-solc-loader/issues" 24 | }, 25 | "homepage": "https://github.com/SilentCicero/ethdeploy-solc-loader#readme" 26 | } 27 | -------------------------------------------------------------------------------- /src/loaders/solc/index.js: -------------------------------------------------------------------------------- 1 | const solc = require('solc'); 2 | 3 | // detect error type from error messages 4 | function errortype(message) { 5 | return (String(message).match(/^(.*:[0-9]*:[0-9]* )?Warning: /) ? 'warning' : 'error'); 6 | } 7 | 8 | // remove warnings from errors 9 | function filterErrorWarnings(errors) { 10 | return (errors || []).filter(error => errortype(error) === 'error'); 11 | } 12 | 13 | /** 14 | * Compiles solidity files in sourcemap, returns contracts. 15 | * 16 | * @method loader 17 | * @param {Object} sourceMap the file source map 18 | * @param {Object} loaderConfig the config for the specified loader 19 | * @param {Object} environment the loaded environment object 20 | * @return {Object} contracts the output contracts 21 | */ 22 | module.exports = function solcLoader(sourceMap, loaderConfig, environment) { 23 | const adjustBase = loaderConfig.base; 24 | const filterWarnings = loaderConfig.filterWarnings; 25 | const filterFilenames = loaderConfig.filterFilenames; 26 | const adjustedSourceMap = {}; 27 | 28 | if (adjustBase) { 29 | Object.keys(sourceMap).forEach(filePath => { 30 | adjustedSourceMap[filePath.replace(adjustBase, '').replace(/\/\//g, '').trim()] = sourceMap[filePath]; 31 | }); 32 | } 33 | 34 | const output = solc.compile({ sources: (adjustBase ? adjustedSourceMap : sourceMap) }, (loaderConfig.optimize || 0)); 35 | const errors = (filterWarnings ? filterErrorWarnings(output.errors) : output.errors) || []; 36 | const outputContracts = Object.assign({}, output.contracts); 37 | 38 | if (errors.length > 0) { 39 | throw new Error(`[solc-loader] while compiling contracts, errors: ${JSON.stringify((output.errors || []), null, 2)}`); 40 | } 41 | 42 | if (filterFilenames) { 43 | Object.keys(outputContracts) 44 | .forEach(sourceKey => { 45 | const savedSource = Object.assign({}, outputContracts[sourceKey]); 46 | delete outputContracts[sourceKey]; 47 | 48 | const filteredName = String(sourceKey).split(':').pop(); 49 | outputContracts[filteredName] = savedSource; 50 | }); 51 | } 52 | 53 | return { [environment.name]: outputContracts }; 54 | }; 55 | -------------------------------------------------------------------------------- /src/loaders/solc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethdeploy-solc-loader", 3 | "version": "1.0.6", 4 | "description": "A solc loader for ethdeploy.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/SilentCicero/ethdeploy.git" 12 | }, 13 | "keywords": [ 14 | "solc", 15 | "loader", 16 | "ethdeploy", 17 | "ethereum", 18 | "solidity" 19 | ], 20 | "author": "Nick Dodson", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/SilentCicero/ethdeploy-solc-loader/issues" 24 | }, 25 | "peerDependencies": { 26 | "solc": "*" 27 | }, 28 | "homepage": "https://github.com/SilentCicero/ethdeploy-solc-loader#readme" 29 | } 30 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minifies JSON output 3 | * 4 | * @method JSONMinifier 5 | * @param {String} output the final build file produced by ethdeploy 6 | * @return {String} parsedOutput parsed output 7 | */ 8 | function JSONMinifier() { 9 | const self = this; 10 | self.process = ({ output }) => JSON.stringify(JSON.parse(output)); 11 | } 12 | 13 | /** 14 | * Expands JSON output 15 | * 16 | * @method JSONMinifier 17 | * @param {String} output the final build file produced by ethdeploy with indents 18 | * @return {String} parsedOutput parsed output 19 | */ 20 | function JSONExpander() { 21 | const self = this; 22 | self.process = ({ output }) => JSON.stringify(JSON.parse(output), null, 2); 23 | } 24 | 25 | // a basic util filter function 26 | function keyFilter(obj, predicate) { 27 | var result = {}, key; // eslint-disable-line 28 | 29 | for (key in obj) { // eslint-disable-line 30 | if (obj[key] && predicate(key)) { 31 | result[key] = obj[key]; 32 | } 33 | } 34 | 35 | return result; 36 | } 37 | 38 | /** 39 | * This wil include contracts data in the environments output 40 | * 41 | * @method IncludeContracts 42 | * @param {Array} contractsSelector contracts to include in a contracts property 43 | * @param {Array} propertyFilter if fiter is not truthy, then ignore filtering 44 | * @param {String} environmentName environment name is usually contracts 45 | * @return {String} parsedOutput parsed output 46 | */ 47 | function IncludeContracts( 48 | contractsSelector, 49 | propertyFilter = ['interface', 'bytecode'], 50 | environmentName = 'contracts') { 51 | const self = this; 52 | self.process = ({ output, contracts }) => { 53 | // parse output 54 | const parsedOutput = JSON.parse(output); 55 | 56 | // add contracts environment 57 | parsedOutput[environmentName] = Object.assign({}); 58 | 59 | // go through selected contracts 60 | contractsSelector.forEach((contractName) => { 61 | // if the contracts object has the selectedcontract 62 | // include it 63 | if ((contracts || {})[contractName]) { 64 | const contractObject = Object.assign({}, contracts[contractName]); 65 | 66 | // include contract data in environments output with property filter 67 | parsedOutput[environmentName][contractName] = propertyFilter ? keyFilter(contractObject, (key => propertyFilter.indexOf(key) !== -1)) : contractObject; 68 | } 69 | }); 70 | 71 | return JSON.stringify(parsedOutput); 72 | }; 73 | } 74 | 75 | /** 76 | * JSONFilter 77 | * 78 | * @method JSONFilter 79 | * @param {String} output the final build file produced by ethdeploy 80 | * @return {String} parsedOutput parsed output 81 | */ 82 | function JSONFilter(contractProperties = ['address', 'interface', 'bytecode', 'transactionObject', 'inputs']) { 83 | const self = this; 84 | self.process = ({ output }) => { 85 | const jsonObject = JSON.parse(output); 86 | const outputObject = Object.assign({}); 87 | 88 | Object.keys(jsonObject).forEach((environmentName) => { 89 | outputObject[environmentName] = Object.assign({}); 90 | 91 | Object.keys(jsonObject[environmentName]).forEach((contractName) => { 92 | outputObject[environmentName][contractName] = Object.assign({}); 93 | 94 | Object.keys(jsonObject[environmentName][contractName]).forEach((contactProperty) => { 95 | if (contractProperties.indexOf(contactProperty) !== -1) { 96 | outputObject[environmentName][contractName][contactProperty] = jsonObject[environmentName][contractName][contactProperty]; 97 | } 98 | }); 99 | }); 100 | }); 101 | 102 | return JSON.stringify(outputObject); 103 | }; 104 | } 105 | 106 | // export plugins 107 | module.exports = { 108 | JSONMinifier, 109 | JSONExpander, 110 | IncludeContracts, 111 | JSONFilter, 112 | }; 113 | -------------------------------------------------------------------------------- /src/plugins/tests/test.index.js: -------------------------------------------------------------------------------- 1 | const plugins = require('../index.js'); 2 | const assert = require('chai').assert; 3 | 4 | describe('plugins', () => { 5 | describe('JSONMinifier', () => { 6 | it('should function properly', () => { 7 | assert.equal(typeof plugins.JSONMinifier, 'function'); 8 | const data = { 9 | output: '{}', 10 | }; 11 | const result = (new plugins.JSONMinifier()).process(data); 12 | const expected = '{}'; 13 | assert.deepEqual(result, expected); 14 | }); 15 | }); 16 | 17 | describe('JSONExpander', () => { 18 | it('should function properly', () => { 19 | assert.equal(typeof plugins.JSONExpander, 'function'); 20 | const data = { 21 | output: '{"a":"b"}', 22 | }; 23 | const expected = 24 | `{ 25 | "a": "b" 26 | }`; 27 | const result = (new plugins.JSONExpander()).process(data); 28 | assert.deepEqual(result, expected); 29 | }); 30 | }); 31 | 32 | describe('IncludeContracts', () => { 33 | it('should function properly', () => { 34 | assert.equal(typeof plugins.IncludeContracts, 'function'); 35 | const data = { 36 | output: '{}', 37 | contracts: { 38 | SimpleStoreInterface: { 39 | bytecode: '0x...', 40 | interface: '[{....}]', 41 | }, 42 | Token: { 43 | bytecode: '0x...', 44 | interface: '[{....}]', 45 | }, 46 | Proxy: { 47 | bytecode: '0x...', 48 | interface: '[{....}]', 49 | }, 50 | }, 51 | }; 52 | const result = (new plugins.IncludeContracts(['SimpleStoreInterface', 'Token', 'Proxy'])).process(data); 53 | const expected = '{"contracts":{"SimpleStoreInterface":{"bytecode":"0x...","interface":"[{....}]"},"Token":{"bytecode":"0x...","interface":"[{....}]"},"Proxy":{"bytecode":"0x...","interface":"[{....}]"}}}'; 54 | assert.deepEqual(result, expected); 55 | }); 56 | }); 57 | 58 | 59 | describe('JSONFilter', () => { 60 | it('should function properly', () => { 61 | assert.equal(typeof plugins.JSONFilter, 'function'); 62 | const unfilteredObjectString = 63 | ` 64 | { 65 | "ropsten": { 66 | "SimpleStore": { 67 | "bytecode": "0x...", 68 | "interface": "[{}]", 69 | "address": "0x3a70a6765746af3bfa974fff9d753d4b6c56b333", 70 | "inputs": [], 71 | "transactionObject": { 72 | "from": "0x7f3e74e3dbb4091973ea1b449692c504c35ef768", 73 | "gas": 3000001 74 | }, 75 | "WILL_BE_FILTERED": "OUT" 76 | } 77 | } 78 | } 79 | `; 80 | const data = { 81 | output: unfilteredObjectString, 82 | }; 83 | const result = (new plugins.JSONFilter()).process(data); 84 | const expected = '{"ropsten":{"SimpleStore":{"bytecode":"0x...","interface":"[{}]","address":"0x3a70a6765746af3bfa974fff9d753d4b6c56b333","inputs":[],"transactionObject":{"from":"0x7f3e74e3dbb4091973ea1b449692c504c35ef768","gas":3000001}}}}'; 85 | assert.deepEqual(result, expected); 86 | }); 87 | }); 88 | }); 89 | 90 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.minimum.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = (options) => ({ 5 | entry: [], 6 | output: { 7 | path: './', 8 | filename: 'environments.json', 9 | }, 10 | module: { 11 | environment: { 12 | name: 'ropsten', 13 | provider: new SignerProvider('https://ropsten.infura.io', { 14 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 15 | signTransaction: (rawTx, cb) => { 16 | cb(null, sign(rawTx, '0x..privateKey...')); 17 | }, 18 | }), 19 | }, 20 | deployment: (deploy, contracts, done) => { 21 | done(); 22 | }, 23 | }, 24 | plugins: [ 25 | new options.plugins.JSONMinifier(), 26 | ], 27 | }); 28 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.minimumNoDefinedPlugins.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = (options) => ({ 5 | entry: [], 6 | output: { 7 | path: './', 8 | filename: 'environments.json', 9 | }, 10 | module: { 11 | environment: { 12 | name: 'ropsten', 13 | provider: new SignerProvider('https://ropsten.infura.io', { 14 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 15 | signTransaction: (rawTx, cb) => { 16 | cb(null, sign(rawTx, '0x..privateKey...')); 17 | }, 18 | }), 19 | }, 20 | deployment: (deploy, contracts, done) => { 21 | done(); 22 | }, 23 | }, 24 | plugins: [ 25 | new options.plugins.JSONMinifier(), 26 | ], 27 | }); 28 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.minimumNoOutput.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = () => ({ 5 | entry: [], 6 | module: { 7 | environment: { 8 | name: 'ropsten', 9 | provider: new SignerProvider('https://ropsten.infura.io', { 10 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 11 | signTransaction: (rawTx, cb) => { 12 | cb(null, sign(rawTx, '0x..privateKey...')); 13 | }, 14 | }), 15 | }, 16 | deployment: (deploy, contracts, done) => { 17 | done(); 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.minimumNoPlugins.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = () => ({ 5 | entry: [], 6 | output: { 7 | path: './', 8 | filename: 'environments.json', 9 | }, 10 | module: { 11 | environment: { 12 | name: 'ropsten', 13 | provider: new SignerProvider('https://ropsten.infura.io', { 14 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 15 | signTransaction: (rawTx, cb) => { 16 | cb(null, sign(rawTx, '0x..privateKey...')); 17 | }, 18 | }), 19 | }, 20 | deployment: (deploy, contracts, done) => { 21 | done(); 22 | }, 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.noloaders.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = (options) => ({ 5 | entry: [ 6 | 'environments.json', 7 | 'contracts', 8 | ], 9 | output: { 10 | path: './', 11 | filename: 'environments.json', 12 | }, 13 | module: { 14 | preLoaders: [ 15 | { test: /\.(json)$/, loader: '../loaders/solc-json.js' }, 16 | ], 17 | environment: { 18 | name: 'ropsten', 19 | provider: new SignerProvider('http://localhost:8545', { 20 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 21 | signTransaction: (rawTx, cb) => { 22 | cb(null, sign(rawTx, '0x..privateKey...')); 23 | }, 24 | }), 25 | defaultTxObject: { 26 | from: 0, 27 | gas: 3000000, 28 | }, 29 | }, 30 | deployment: (deploy, contracts, done) => { 31 | deploy(contracts.SimpleStore).then((simpleStoreInstance) => { 32 | console.log(simpleStoreInstance); // eslint-disable-line 33 | 34 | done(); 35 | }); 36 | }, 37 | }, 38 | plugins: [ 39 | new options.plugins.JSONMinifier(), 40 | ], 41 | }); 42 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.noplugin.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = () => ({ 5 | entry: [ 6 | 'environments.json', 7 | 'contracts', 8 | ], 9 | output: { 10 | path: './', 11 | filename: 'environments.json', 12 | }, 13 | module: { 14 | preLoaders: [ 15 | { test: /\.(json)$/, loader: '../loaders/environment.js', build: true, include: /(environments)/ }, 16 | ], 17 | loaders: [ 18 | { test: /\.(sol)$/, loader: '../loaders/solc.js', optimize: 1 }, 19 | { test: /\.(json)$/, loader: '../loaders/solc-json.js' }, 20 | ], 21 | environment: { 22 | name: 'ropsten', 23 | provider: new SignerProvider('http://localhost:8545', { 24 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 25 | signTransaction: (rawTx, cb) => { 26 | cb(null, sign(rawTx, '0x..privateKey...')); 27 | }, 28 | }), 29 | defaultTxObject: { 30 | from: 0, 31 | gas: 3000000, 32 | }, 33 | }, 34 | deployment: (deploy, contracts, done) => { 35 | deploy(contracts.SimpleStore).then((simpleStoreInstance) => { 36 | console.log(simpleStoreInstance); // eslint-disable-line 37 | 38 | done(); 39 | }); 40 | }, 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.nopreloaders.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = (options) => ({ 5 | entry: [ 6 | 'environments.json', 7 | 'contracts', 8 | ], 9 | output: { 10 | path: './', 11 | filename: 'environments.json', 12 | }, 13 | module: { 14 | loaders: [ 15 | { test: /\.(json)$/, loader: '../loaders/solc-json.js' }, 16 | ], 17 | environment: { 18 | name: 'ropsten', 19 | provider: new SignerProvider('http://localhost:8545', { 20 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 21 | signTransaction: (rawTx, cb) => { 22 | cb(null, sign(rawTx, '0x..privateKey...')); 23 | }, 24 | }), 25 | defaultTxObject: { 26 | from: 0, 27 | gas: 3000000, 28 | }, 29 | }, 30 | deployment: (deploy, contracts, done) => { 31 | deploy(contracts.SimpleStore).then((simpleStoreInstance) => { 32 | console.log(simpleStoreInstance); // eslint-disable-line 33 | 34 | done(); 35 | }); 36 | }, 37 | }, 38 | plugins: [ 39 | new options.plugins.JSONMinifier(), 40 | ], 41 | }); 42 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.notxobject.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = (options) => ({ 5 | entry: [ 6 | 'environments.json', 7 | 'contracts', 8 | ], 9 | output: { 10 | path: './', 11 | filename: 'environments.json', 12 | }, 13 | module: { 14 | preLoaders: [ 15 | { test: /\.(json)$/, loader: '../loaders/environment.js', build: true, include: /(environments)/ }, 16 | ], 17 | loaders: [ 18 | { test: /\.(sol)$/, loader: '../loaders/solc.js', optimize: 1 }, 19 | { test: /\.(json)$/, loader: '../loaders/solc-json.js' }, 20 | ], 21 | environment: { 22 | name: 'ropsten', 23 | provider: new SignerProvider('http://localhost:8545', { 24 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 25 | signTransaction: (rawTx, cb) => { 26 | cb(null, sign(rawTx, '0x..privateKey...')); 27 | }, 28 | }), 29 | }, 30 | deployment: (deploy, contracts, done) => { 31 | deploy(contracts.SimpleStore, { 32 | from: '0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC', 33 | gas: 3000000, 34 | }).then((simpleStoreInstance) => { 35 | console.log(simpleStoreInstance); // eslint-disable-line 36 | 37 | done(); 38 | }); 39 | }, 40 | }, 41 | plugins: [ 42 | new options.plugins.JSONMinifier(), 43 | ], 44 | }); 45 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.rawenv.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = () => ({ 5 | entry: { 6 | ropsten: { 7 | SimpleStore: { 8 | }, 9 | }, 10 | mainnet: { 11 | SomeOtherContract: { 12 | }, 13 | }, 14 | }, 15 | sourceMapper: (entry, cb) => cb(null, entry), 16 | module: { 17 | preLoaders: [ 18 | { loader: '../loaders/raw-environment.js' }, 19 | ], 20 | environment: { 21 | name: 'ropsten', 22 | provider: new SignerProvider('http://localhost:8545', { 23 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 24 | signTransaction: (rawTx, cb) => { 25 | cb(null, sign(rawTx, '0x..privateKey...')); 26 | }, 27 | }), 28 | }, 29 | deployment: (deploy, contracts, done) => { 30 | deploy(contracts.SimpleStore).then((simpleStoreInstance) => { 31 | console.log(simpleStoreInstance); // eslint-disable-line 32 | 33 | done(); 34 | }); 35 | }, 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.rawsolc.testnet.js: -------------------------------------------------------------------------------- 1 | const TestRPC = require('ethereumjs-testrpc'); 2 | 3 | module.exports = () => ({ 4 | entry: { 5 | SimpleStore: { 6 | }, 7 | }, 8 | sourceMapper: (entry, cb) => cb(null, entry), 9 | module: { 10 | preLoaders: [ 11 | { loader: '../loaders/raw-solc-json.js' }, 12 | ], 13 | environment: { 14 | name: 'ropsten', 15 | provider: TestRPC.provider(), 16 | defaultTxObject: { 17 | from: 0, 18 | gas: 3000000, 19 | }, 20 | }, 21 | deployment: (deploy, contracts, done) => { 22 | deploy(contracts.SimpleStore).then((simpleStoreInstance) => { 23 | console.log(simpleStoreInstance); // eslint-disable-line 24 | 25 | done(); 26 | }); 27 | }, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.solcLoader.testnet.js: -------------------------------------------------------------------------------- 1 | const TestRPC = require('ethereumjs-testrpc'); // eslint-disable-line 2 | // const HttpProvider = require('ethjs-provider-http'); 3 | 4 | module.exports = (options) => ({ // eslint-disable-line 5 | entry: [ 6 | './environments.json', 7 | './src/tests/contracts', 8 | ], 9 | output: { 10 | path: './', 11 | filename: 'environments.json', 12 | safe: true, 13 | }, 14 | module: { 15 | environment: { 16 | name: 'testrpc', 17 | provider: TestRPC.provider(), 18 | defaultTxObject: { 19 | from: 1, 20 | gas: 3000001, 21 | }, 22 | }, 23 | preLoaders: [ 24 | { test: /\.(json)$/, loader: 'ethdeploy-environment-loader', build: true }, 25 | ], 26 | loaders: [ 27 | { test: /\.(sol)$/, exclude: /(test\.)/, loader: 'ethdeploy-solc-loader', optimize: 1 }, 28 | ], 29 | deployment: (deploy, contracts, done) => { 30 | deploy(contracts.SimpleStore, { from: 0 }).then(() => { 31 | done(); 32 | }); 33 | }, 34 | }, 35 | plugins: [ 36 | // new options.plugins.JSONMinifier(), 37 | ], 38 | }); 39 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.stringEntry.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = (options) => ({ 5 | entry: 'contracts', 6 | output: { 7 | path: './', 8 | filename: 'environments.json', 9 | }, 10 | module: { 11 | preLoaders: [ 12 | { test: /\.(json)$/, loader: '../loaders/environment.js', build: true, include: /(environments)/ }, 13 | ], 14 | loaders: [ 15 | { test: /\.(sol)$/, loader: '../loaders/solc.js', optimize: 1 }, 16 | ], 17 | environment: { 18 | name: 'ropsten', 19 | provider: new SignerProvider('http://localhost:8545', { 20 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 21 | signTransaction: (rawTx, cb) => { 22 | cb(null, sign(rawTx, '0x..privateKey...')); 23 | }, 24 | }), 25 | defaultTxObject: { 26 | from: 0, 27 | gas: 3000000, 28 | }, 29 | }, 30 | deployment: (deploy, contracts, done) => { 31 | deploy(contracts.SimpleStore).then((simpleStoreInstance) => { 32 | console.log(simpleStoreInstance); // eslint-disable-line 33 | 34 | done(); 35 | }); 36 | }, 37 | }, 38 | plugins: [ 39 | new options.plugins.JSONMinifier(), 40 | ], 41 | }); 42 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = (options) => ({ 5 | entry: [ 6 | 'environments.json', 7 | 'contracts', 8 | ], 9 | output: { 10 | path: './', 11 | filename: 'environments.json', 12 | }, 13 | module: { 14 | preLoaders: [ 15 | { test: /\.(json)$/, loader: '../loaders/environment.js', build: true, include: /(environments)/ }, 16 | ], 17 | loaders: [ 18 | { test: /\.(sol)$/, loader: '../loaders/solc.js', optimize: 1 }, 19 | { test: /\.(json)$/, loader: '../loaders/solc-json.js' }, 20 | ], 21 | environment: { 22 | name: 'ropsten', 23 | provider: new SignerProvider('http://localhost:8545', { 24 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 25 | signTransaction: (rawTx, cb) => { 26 | cb(null, sign(rawTx, '0x..privateKey...')); 27 | }, 28 | }), 29 | defaultTxObject: { 30 | from: 0, 31 | gas: 3000000, 32 | }, 33 | }, 34 | deployment: (deploy, contracts, done) => { 35 | deploy(contracts.SimpleStore).then((simpleStoreInstance) => { 36 | console.log(simpleStoreInstance); // eslint-disable-line 37 | 38 | done(); 39 | }); 40 | }, 41 | }, 42 | plugins: [ 43 | new options.plugins.JSONMinifier(), 44 | ], 45 | }); 46 | -------------------------------------------------------------------------------- /src/tests/configs/ethdeploy.config.zeroloaders.testnet.js: -------------------------------------------------------------------------------- 1 | const sign = require('ethjs-signer').sign; 2 | const SignerProvider = require('ethjs-provider-signer'); 3 | 4 | module.exports = (options) => ({ 5 | entry: [ 6 | 'environments.json', 7 | 'contracts', 8 | ], 9 | output: { 10 | path: './', 11 | filename: 'environments.json', 12 | }, 13 | module: { 14 | environment: { 15 | name: 'ropsten', 16 | provider: new SignerProvider('http://localhost:8545', { 17 | accounts: (cb) => cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']), 18 | signTransaction: (rawTx, cb) => { 19 | cb(null, sign(rawTx, '0x..privateKey...')); 20 | }, 21 | }), 22 | defaultTxObject: { 23 | from: 0, 24 | gas: 3000000, 25 | }, 26 | }, 27 | deployment: (deploy, contracts, done) => { 28 | done(); 29 | }, 30 | }, 31 | plugins: [ 32 | new options.plugins.JSONMinifier(), 33 | ], 34 | }); 35 | -------------------------------------------------------------------------------- /src/tests/contracts/SimpleStore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract SimpleStore { 4 | uint256 public store; 5 | 6 | function setStore(uint256 _value) public { 7 | store = _value; 8 | } 9 | 10 | function getValue() public constant returns (uint256) { 11 | return store; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/tests/contracts/test.SimpleStore.sol: -------------------------------------------------------------------------------- 1 | dsfsdsfd 2 | -------------------------------------------------------------------------------- /src/tests/test.index.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert; 2 | const BN = require('bn.js'); // eslint-disable-line 3 | const BigNumber = require('bignumber.js'); // eslint-disable-line 4 | const ethdeploy = require('../index.js'); // eslint-disable-line 5 | const HttpProvider = require('ethjs-provider-http'); // eslint-disable-line 6 | const TestRPC = require('ethereumjs-testrpc'); 7 | 8 | describe('ethdeploy', () => { 9 | describe('main method', () => { 10 | it('should instantiate properly', () => { 11 | assert.equal(typeof ethdeploy, 'function'); 12 | }); 13 | 14 | it('should handle undefined', (done) => { 15 | ethdeploy(undefined, (err, result) => { 16 | assert.isOk(err); 17 | assert.isNotOk(result); 18 | done(); 19 | }); 20 | }); 21 | 22 | it('should handle empty object', (done) => { 23 | ethdeploy({}, (err, result) => { 24 | assert.isOk(err); 25 | assert.isNotOk(result); 26 | done(); 27 | }); 28 | }); 29 | 30 | it('should handle empty function', (done) => { 31 | ethdeploy(() => {}, (err, result) => { 32 | assert.isOk(err); 33 | assert.isNotOk(result); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('should handle normal config', (done) => { 39 | ethdeploy({ 40 | entry: [], 41 | output: {}, 42 | module: {}, 43 | }, (err, result) => { 44 | assert.isOk(err); 45 | assert.isNotOk(result); 46 | done(); 47 | }); 48 | }); 49 | 50 | it('should handle normal config no provider', (done) => { 51 | ethdeploy({ 52 | entry: [], 53 | output: { 54 | }, 55 | module: { 56 | deployment: () => {}, 57 | }, 58 | }, (err, result) => { 59 | assert.isOk(err); 60 | assert.isNotOk(result); 61 | done(); 62 | }); 63 | }); 64 | 65 | it('should handle normal config with no env', (done) => { 66 | ethdeploy({ 67 | entry: [], 68 | output: { 69 | }, 70 | module: { 71 | environment: {}, 72 | deployment: () => {}, 73 | }, 74 | }, (err, result) => { 75 | assert.isOk(err); 76 | assert.isNotOk(result); 77 | done(); 78 | }); 79 | }); 80 | 81 | it('should handle normal config with provider', (done) => { 82 | ethdeploy({ 83 | entry: [], 84 | output: { 85 | }, 86 | module: { 87 | environment: { 88 | provider: new HttpProvider('http://localhost:3000'), 89 | }, 90 | deployment: () => {}, 91 | }, 92 | }, (err, result) => { 93 | assert.isOk(err); 94 | assert.isNotOk(result); 95 | done(); 96 | }); 97 | }); 98 | 99 | it('should handle normal config with testrpc', (done) => { 100 | ethdeploy({ 101 | entry: [], 102 | output: {}, 103 | module: { 104 | environment: { 105 | name: 'localhost', 106 | provider: TestRPC.provider(), 107 | }, 108 | deployment: (deploy, c, done1) => done(), // eslint-disable-line 109 | }, 110 | }, (err, result) => { 111 | assert.isOk(result); 112 | assert.isNotOk(err); 113 | done(); 114 | }); 115 | }); 116 | 117 | it('should handle normal entry with testrpc', (done) => { 118 | ethdeploy({ 119 | entry: { 120 | SimpleStore: 1, 121 | }, 122 | output: {}, 123 | sourceMapper: (v, cb) => cb(null, v), 124 | module: { 125 | loaders: [ 126 | { loader: 'ethdeploy-raw-solc-loader' }, 127 | ], 128 | environment: { 129 | name: 'localhost', 130 | provider: TestRPC.provider(), 131 | }, 132 | deployment: (deploy, contracts, done1) => { 133 | assert.equal(contracts.SimpleStore, 1); 134 | 135 | done1(); 136 | }, // eslint-disable-line 137 | }, 138 | }, () => done()); 139 | }); 140 | 141 | it('should handle normal entry with testrpc/raw solc', (done) => { 142 | ethdeploy({ 143 | entry: { 144 | SimpleStore: { 145 | bytecode: '', 146 | interface: '', 147 | }, 148 | }, 149 | output: {}, 150 | sourceMapper: (v, cb) => cb(null, v), 151 | module: { 152 | loaders: [ 153 | { loader: 'ethdeploy-raw-solc-loader' }, 154 | ], 155 | environment: { 156 | name: 'localhost', 157 | provider: TestRPC.provider(), 158 | }, 159 | deployment: (deploy, contracts, done1) => { 160 | assert.equal(contracts.SimpleStore, 1); 161 | 162 | done1(); 163 | }, // eslint-disable-line 164 | }, 165 | }, () => done()); 166 | }); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | const dir = require('node-dir'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const ethUtil = require('ethjs-util'); 5 | 6 | /** 7 | * Returns the ISO current date time. 8 | * 9 | * @method isoTime 10 | * @return {String} ISOTime the ISO time string 11 | */ 12 | function isoTime() { 13 | return (new Date()).toISOString(); 14 | } 15 | 16 | /** 17 | * Basic error method for ethdeploy 18 | * 19 | * @method isoTime 20 | * @param {String} msg the error message 21 | * @return {Object} Error the new Error object 22 | */ 23 | function log(...args) { 24 | return console.log(`[ethdeploy ${isoTime()}] `, ...args); // eslint-disable-line 25 | } 26 | 27 | /** 28 | * Basic error method for ethdeploy 29 | * 30 | * @method isoTime 31 | * @param {String} msg the error message 32 | * @return {Object} Error the new Error object 33 | */ 34 | function error(...args) { 35 | return new Error(`[ethdeploy ${isoTime()}] ${args.map((arg) => arg)}`); 36 | } 37 | 38 | // recursively converts all BN or BigNumber instances into string 39 | 40 | /** 41 | * Converts all BigNumber and BN instances to string, even nested deep in Arrays or Objects. 42 | * 43 | * @method bnToString 44 | * @param {Optional} objInput optional input type, bypasses most 45 | * @param {Number} baseInput the base number (usually either 10 or 16). 46 | * @param {Boolean} hexPrefixed hex prefix the output 47 | * @return {Optional} output returns optional type output with converted bn's. 48 | */ 49 | function bnToString(objInput, baseInput, hexPrefixed) { 50 | var obj = objInput; // eslint-disable-line 51 | const base = baseInput || 10; 52 | 53 | // obj is an array 54 | if (typeof obj === 'object' && obj !== null) { 55 | if (Array.isArray(obj)) { 56 | // convert items in array 57 | obj = obj.map((item) => bnToString(item, base, hexPrefixed)); 58 | } else { 59 | if (obj.toString && (obj.lessThan || obj.dividedToIntegerBy || obj.isBN || obj.toTwos)) { 60 | return hexPrefixed ? `0x${ethUtil.padToEven(obj.toString(16))}` : obj.toString(base); 61 | } else { // eslint-disable-line 62 | // recurively converty item 63 | Object.keys(obj).forEach((key) => { 64 | obj[key] = bnToString(obj[key], base, hexPrefixed); 65 | }); 66 | } 67 | } 68 | } 69 | 70 | return obj; 71 | } 72 | 73 | /** 74 | * Filters a given sourcemap by specific test regex's. 75 | * 76 | * @method filterSourceMap 77 | * @param {Regex} testRegex the test regex (only include these files) 78 | * @param {Regex} includeRegex the include test regex (only include these files) 79 | * @param {Object} sourceMap the complete file sourcemap 80 | * @return {Object} filteredSourceMap the filtered sourcemap 81 | */ 82 | /* 83 | function filterSourceMap(testRegex, includeRegex, sourceMap, excludeRegex) { 84 | const outputData = Object.assign({}); 85 | const testTestRegex = testRegex || /./g; 86 | const testIncludeRegex = includeRegex || /./g; 87 | const testExcludeRegex = excludeRegex || null; 88 | 89 | if (typeof testTestRegex !== 'object') { throw error(`while filtering source map, ${JSON.stringify(sourceMap)}, test regex must be type object, got ${typeof testRegex}.`); } 90 | if (typeof testIncludeRegex !== 'object') { throw error(`while filtering source map, ${JSON.stringify(sourceMap)}, include regex must be type object, got ${typeof testIncludeRegex}.`); } 91 | if (typeof testExcludeRegex !== 'object') { throw error(`while filtering source map, ${JSON.stringify(sourceMap)}, exclude regex must be type object, got ${typeof testExcludeRegex}.`); } 92 | 93 | Object.keys(sourceMap).forEach((key) => { 94 | if (testTestRegex.test(key) && testIncludeRegex.test(key) && (testExcludeRegex === null || !testExcludeRegex.test(key))) { 95 | if (sourceMap[key]) { 96 | outputData[key] = sourceMap[key]; 97 | } 98 | } 99 | }); 100 | 101 | return outputData; 102 | }*/ 103 | function filterSourceMap(testRegex, includeRegex, sourceMap, excludeRegex) { 104 | const outputData = Object.assign({}); 105 | const testTestRegex = testRegex || /./g; 106 | const testIncludeRegex = includeRegex || null; 107 | const testExcludeRegex = excludeRegex || null; 108 | 109 | if (typeof testTestRegex !== 'object') { throw error(`while filtering source map, ${JSON.stringify(sourceMap)}, test regex must be type object, got ${typeof testRegex}.`); } 110 | if (typeof testIncludeRegex !== 'object') { throw error(`while filtering source map, ${JSON.stringify(sourceMap)}, include regex must be type object, got ${typeof testIncludeRegex}.`); } 111 | if (typeof testExcludeRegex !== 'object') { throw error(`while filtering source map, ${JSON.stringify(sourceMap)}, exclude regex must be type object, got ${typeof testExcludeRegex}.`); } 112 | 113 | Object.keys(sourceMap).forEach((key) => { 114 | if (testTestRegex.test(key) 115 | && (testIncludeRegex === null || testIncludeRegex.test(key)) 116 | && (testExcludeRegex === null || !testExcludeRegex.test(key))) { 117 | Object.assign(outputData, { [key]: sourceMap[key] }); 118 | } 119 | }); 120 | 121 | return outputData; 122 | } 123 | 124 | 125 | /** 126 | * This will wait for a transaction to present a receipt 127 | * 128 | * @method getTransactionSuccess 129 | * @param {Object} eth the eth query instance 130 | * @param {Object} txHash the transaction hash 131 | * @param {Object} timeout settings 132 | * @param {Function} callback the final callback 133 | * @callback {Object} contractInstance the deployed contract instance with receipt prop 134 | */ 135 | function getTransactionSuccess(eth, txHash, timeout = 800000, callback) { 136 | const cb = callback || function cb() {}; 137 | let count = 0; 138 | return new Promise((resolve, reject) => { 139 | const txInterval = setInterval(() => { 140 | eth.getTransactionReceipt(txHash, (err, result) => { 141 | if (err) { 142 | clearInterval(txInterval); 143 | cb(err, null); 144 | reject(err); 145 | } 146 | 147 | if (!err && result) { 148 | clearInterval(txInterval); 149 | cb(null, result); 150 | resolve(result); 151 | } 152 | }); 153 | 154 | if (count >= timeout) { 155 | clearInterval(txInterval); 156 | const errMessage = `Receipt timeout waiting for tx hash: ${txHash}`; 157 | cb(errMessage, null); 158 | reject(errMessage); 159 | } 160 | count += 7000; 161 | }, 7000); 162 | }); 163 | } 164 | 165 | /** 166 | * Deploy the contract with eth, factory, and args 167 | * 168 | * @method deployContract 169 | * @param {Object} eth the eth query instance 170 | * @param {Object} factory the contract factory 171 | * @param {Array} args the contract constructor arguments 172 | * @param {Function} callback the final callback 173 | * @callback {Object} contractInstance the deployed contract instance with receipt prop 174 | */ 175 | function deployContract(eth, factory, args, callback) { 176 | factory.new.apply(factory, args).then((txHash) => { 177 | getTransactionSuccess(eth, txHash, 8000000, (receiptError, receipt) => { 178 | if (receiptError) { 179 | callback(receiptError, null); 180 | } 181 | 182 | if (receipt) { 183 | const contractInstance = factory.at(receipt.contractAddress); 184 | contractInstance.receipt = receipt; 185 | callback(null, contractInstance); 186 | } 187 | }); 188 | }).catch(callback); 189 | } 190 | 191 | /** 192 | * Get all input source for a specific pathname, used for mapping config entry 193 | * 194 | * @method getInputSources 195 | * @param {String} pathname a path string to a file or directory 196 | * @param {Function} callback the final callback that returns the sources 197 | * @callback {Object} sourceMap returns a source map for this pathname 198 | */ 199 | function getInputSources(pathname, callback) { 200 | let filesRead = 0; 201 | let sources = {}; 202 | 203 | // test file is a file, the last section contains a extention period 204 | if (String(pathname).split('/').pop().indexOf('.') !== -1) { 205 | const searchPathname = pathname.substr(0, 2) === './' ? pathname.slice(2) : pathname; 206 | 207 | fs.readFile(searchPathname, 'utf8', (readFileError, fileData) => { // eslint-disable-line 208 | if (readFileError) { 209 | return callback(error(`while while getting input sources ${readFileError}`)); 210 | } 211 | 212 | callback(null, { [searchPathname]: fileData }); 213 | }); 214 | } else { 215 | // get all file names 216 | dir.files(pathname, (filesError, files) => { // eslint-disable-line 217 | if (filesError) { 218 | return callback(error(`while while getting input sources ${filesError}`)); 219 | } 220 | 221 | // if no files in directory, then fire callback with empty sources 222 | if (files.length === 0) { 223 | callback(null, sources); 224 | } else { 225 | // read all files 226 | dir.readFiles(pathname, (readFilesError, content, filename, next) => { // eslint-disable-line 227 | if (readFilesError) { 228 | return callback(error(`while getting input sources ${readFilesError}`)); 229 | } 230 | 231 | // add input sources to output 232 | sources = Object.assign({}, sources, { 233 | [path.join('./', filename)]: content, 234 | }); 235 | 236 | // increase files readFiles 237 | filesRead += 1; 238 | 239 | // process next file 240 | if (filesRead === files.length) { 241 | callback(null, sources); 242 | } else { 243 | next(); 244 | } 245 | }); 246 | } 247 | }); 248 | } 249 | } 250 | 251 | // the base utilty methods for ethdeploy 252 | module.exports = { 253 | isoTime, 254 | error, 255 | log, 256 | deployContract, 257 | filterSourceMap, 258 | getInputSources, 259 | bnToString, 260 | }; 261 | -------------------------------------------------------------------------------- /src/utils/tests/test.index.js: -------------------------------------------------------------------------------- 1 | const utils = require('../index.js'); 2 | const assert = require('chai').assert; 3 | const BN = require('bn.js'); 4 | const BigNumber = require('bignumber.js'); 5 | 6 | describe('utils', () => { 7 | describe('isoTime', () => { 8 | it('should function properly', () => { 9 | assert.equal(typeof utils.isoTime, 'function'); 10 | assert.equal(typeof utils.isoTime(), 'string'); 11 | }); 12 | }); 13 | 14 | describe('error', () => { 15 | it('should function properly', () => { 16 | assert.equal(typeof utils.error, 'function'); 17 | assert.equal(typeof utils.error(), 'object'); 18 | }); 19 | }); 20 | 21 | describe('deployContract', () => { 22 | it('should function properly', () => { 23 | assert.equal(typeof utils.deployContract, 'function'); 24 | }); 25 | }); 26 | 27 | describe('filterSourceMap', () => { 28 | it('should function properly', () => { 29 | const testMap = { 30 | 'somefile.json': '{}', 31 | '#.': 'ssf', 32 | 'sdkffjsd.js': 'jsdkfk', 33 | 'anotherfile.json': '{}', 34 | 'sdflskjlfsd.sol': 'sdf', 35 | 'sdfk/jsdfjksdf/kjsdfkjsfd.js': 'fssdf', 36 | }; 37 | const JSONTestMap = { 38 | 'anotherfile.json': '{}', 39 | 'somefile.json': '{}', 40 | }; 41 | const anotherFileJSON = { 42 | 'anotherfile.json': '{}', 43 | }; 44 | 45 | assert.equal(typeof utils.filterSourceMap, 'function'); 46 | 47 | assert.deepEqual(utils.filterSourceMap(/\.(json)$/, null, testMap), JSONTestMap); 48 | assert.deepEqual(utils.filterSourceMap(null, /\.(json)$/, testMap), JSONTestMap); 49 | assert.deepEqual(utils.filterSourceMap(null, null, testMap), testMap); 50 | assert.deepEqual(utils.filterSourceMap(null, /anotherfile/, testMap), anotherFileJSON); 51 | assert.throws(() => utils.filterSourceMap(null, 4232, testMap), Error); 52 | assert.throws(() => utils.filterSourceMap(24323424, /anotherfile/, testMap), Error); 53 | }); 54 | }); 55 | 56 | describe('getInputSources', () => { 57 | it('should function properly', (done) => { 58 | assert.equal(typeof utils.getInputSources, 'function'); 59 | 60 | utils.getInputSources('./src/utils/tests/testSources', (err, result) => { 61 | assert.equal(err, null); 62 | assert.deepEqual(result, { 63 | 'src/utils/tests/testSources/someDir/AnotherDir/something.json': '{"yes": 1}\n', 64 | 'src/utils/tests/testSources/someDir/anotherFile': '', 65 | 'src/utils/tests/testSources/someDir/someDeeperDir/AnotherDeeperDir/anotherFile': '', 66 | 'src/utils/tests/testSources/someFile.json': '{}\n', 67 | 'src/utils/tests/testSources/someFile.s': 'const cool = 1;\n\nmodule.exports = cool;\n' }); 68 | done(); 69 | }); 70 | }); 71 | 72 | it('should handle a single file', (done) => { 73 | utils.getInputSources('./src/utils/tests/testSources/someFile.json', (err, result) => { 74 | assert.equal(err, null); 75 | assert.deepEqual(result, { 'src/utils/tests/testSources/someFile.json': '{}\n' }); 76 | done(); 77 | }); 78 | }); 79 | 80 | it('should handle invalid file', (done) => { 81 | utils.getInputSources('src/utils/tests/testSources/someFile.jssdn', (err) => { 82 | assert.equal(typeof err, 'object'); 83 | done(); 84 | }); 85 | }); 86 | 87 | it('should handle invalid file', (done) => { 88 | utils.getInputSources('src/utils/tests/testSousdfeFile', (err) => { 89 | assert.equal(typeof err, 'object'); 90 | done(); 91 | }); 92 | }); 93 | 94 | it('should handle invalid file', (done) => { 95 | utils.getInputSources('src/utils/tests/testSousdfeFile.jssdn', (err) => { 96 | assert.equal(typeof err, 'object'); 97 | done(); 98 | }); 99 | }); 100 | 101 | it('should handle a single file', (done) => { 102 | utils.getInputSources('src/utils/tests/testSources/someFile.json', (err, result) => { 103 | assert.equal(err, null); 104 | assert.deepEqual(result, { 'src/utils/tests/testSources/someFile.json': '{}\n' }); 105 | done(); 106 | }); 107 | }); 108 | }); 109 | 110 | describe('bnToString', () => { 111 | it('should function properly', () => { 112 | assert.equal(typeof utils.bnToString, 'function'); 113 | 114 | const bnToStringTests = [ 115 | { actual: [], expected: [] }, 116 | { actual: 0, expected: 0 }, 117 | { actual: 'something', expected: 'something' }, 118 | { actual: true, expected: true }, 119 | { actual: false, expected: false }, 120 | { actual: { something: true }, expected: { something: true } }, 121 | { actual: { something: 'someString' }, expected: { something: 'someString' } }, 122 | { actual: null, expected: null }, 123 | { actual: undefined, expected: undefined }, 124 | { actual: [22, 333, 'hello', null], expected: [22, 333, 'hello', null] }, 125 | { actual: new BN(1), expected: '1' }, 126 | { actual: new BigNumber(0), expected: '0' }, 127 | { actual: new BN('1000'), expected: '1000' }, 128 | { actual: [23498, 'sdfhjs', null, true, new BN(1), true], expected: [23498, 'sdfhjs', null, true, '1', true] }, 129 | { actual: [23498, new BN(1), null, true, new BN(1), true], expected: [23498, '1', null, true, '1', true] }, 130 | { actual: { cool: 45, something: new BN(1), arr: [23498, new BigNumber(1), null, true, new BN(1), true] }, 131 | expected: { cool: 45, something: '1', arr: [23498, '1', null, true, '1', true] } }, 132 | { actual: { cool: 45, something: new BN(1), arr: [23498, new BN(1), null, true, { another: new BN(1), cool: 222 }, true] }, 133 | expected: { cool: 45, something: '1', arr: [23498, '1', null, true, { another: '1', cool: 222 }, true] } }, 134 | ]; 135 | 136 | bnToStringTests.forEach((testCase) => { 137 | assert.deepEqual(utils.bnToString(testCase.actual), testCase.expected); 138 | }); 139 | }); 140 | 141 | it('should hex prefix properly', () => { 142 | assert.deepEqual(utils.bnToString(new BN('249824987'), 16, true), `0x0${new BN('249824987').toString(16)}`); 143 | }); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /src/utils/tests/testSources/someDir/AnotherDir/something.json: -------------------------------------------------------------------------------- 1 | {"yes": 1} 2 | -------------------------------------------------------------------------------- /src/utils/tests/testSources/someDir/anotherFile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentCicero/ethdeploy/acb1212e1942ce604eb82e34ce2f8c9d6b20a0a6/src/utils/tests/testSources/someDir/anotherFile -------------------------------------------------------------------------------- /src/utils/tests/testSources/someDir/someDeeperDir/AnotherDeeperDir/anotherFile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentCicero/ethdeploy/acb1212e1942ce604eb82e34ce2f8c9d6b20a0a6/src/utils/tests/testSources/someDir/someDeeperDir/AnotherDeeperDir/anotherFile -------------------------------------------------------------------------------- /src/utils/tests/testSources/someFile.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /src/utils/tests/testSources/someFile.s: -------------------------------------------------------------------------------- 1 | const cool = 1; 2 | 3 | module.exports = cool; 4 | --------------------------------------------------------------------------------