diff options
Diffstat (limited to 'packages/deployer')
-rw-r--r-- | packages/deployer/src/cli.ts | 126 | ||||
-rw-r--r-- | packages/deployer/src/commands.ts | 14 | ||||
-rw-r--r-- | packages/deployer/src/deployer.ts | 223 | ||||
-rw-r--r-- | packages/deployer/src/index.ts | 1 | ||||
-rw-r--r-- | packages/deployer/src/utils/contract.ts | 80 | ||||
-rw-r--r-- | packages/deployer/src/utils/error_reporter.ts | 18 | ||||
-rw-r--r-- | packages/deployer/src/utils/types.ts | 27 | ||||
-rw-r--r-- | packages/deployer/test/deployer_test.ts | 74 |
8 files changed, 15 insertions, 548 deletions
diff --git a/packages/deployer/src/cli.ts b/packages/deployer/src/cli.ts index 8c89cf382..2412b8d34 100644 --- a/packages/deployer/src/cli.ts +++ b/packages/deployer/src/cli.ts @@ -8,130 +8,34 @@ import * as path from 'path'; import * as Web3 from 'web3'; import * as yargs from 'yargs'; -import { commands } from './commands'; +import { Compiler } from './compiler'; import { constants } from './utils/constants'; -import { consoleReporter } from './utils/error_reporter'; -import { CliOptions, CompilerOptions, DeployerOptions } from './utils/types'; +import { CompilerOptions } from './utils/types'; -const DEFAULT_CONTRACTS_DIR = path.resolve('src/contracts'); -const DEFAULT_ARTIFACTS_DIR = path.resolve('src/artifacts'); -const DEFAULT_NETWORK_ID = 50; -const DEFAULT_JSONRPC_URL = 'http://localhost:8545'; -const DEFAULT_GAS_PRICE = (10 ** 9 * 2).toString(); const DEFAULT_CONTRACTS_LIST = '*'; const SEPARATOR = ','; -/** - * Compiles all contracts with options passed in through CLI. - * @param argv Instance of process.argv provided by yargs. - */ -async function onCompileCommandAsync(argv: CliOptions): Promise<void> { - const opts: CompilerOptions = { - contractsDir: argv.contractsDir, - artifactsDir: argv.artifactsDir, - contracts: argv.contracts === DEFAULT_CONTRACTS_LIST ? DEFAULT_CONTRACTS_LIST : argv.contracts.split(SEPARATOR), - }; - await commands.compileAsync(opts); -} -/** - * Deploys a single contract with provided name and args. - * @param argv Instance of process.argv provided by yargs. - */ -async function onDeployCommandAsync(argv: CliOptions): Promise<void> { - const url = argv.jsonrpcUrl; - const provider = new Web3.providers.HttpProvider(url); - const web3Wrapper = new Web3Wrapper(provider); - const networkId = await web3Wrapper.getNetworkIdAsync(); - const compilerOpts: CompilerOptions = { - contractsDir: argv.contractsDir, - artifactsDir: argv.artifactsDir, - contracts: argv.contracts === DEFAULT_CONTRACTS_LIST ? DEFAULT_CONTRACTS_LIST : argv.contracts.split(SEPARATOR), - }; - await commands.compileAsync(compilerOpts); - - const defaults = { - gasPrice: new BigNumber(argv.gasPrice), - from: argv.account, - }; - const deployerOpts: DeployerOptions = { - artifactsDir: argv.artifactsDir || DEFAULT_ARTIFACTS_DIR, - jsonrpcUrl: argv.jsonrpcUrl, - networkId, - defaults, - }; - const deployerArgsString = argv.constructorArgs as string; - const deployerArgs = deployerArgsString.split(SEPARATOR); - await commands.deployAsync(argv.contract as string, deployerArgs, deployerOpts); -} -/** - * Adds additional required options for when the user is calling the deploy command. - * @param yargsInstance yargs instance provided in builder function callback. - */ -function deployCommandBuilder(yargsInstance: any) { - return yargsInstance - .option('network-id', { - type: 'number', - default: DEFAULT_NETWORK_ID, - description: 'mainnet=1, kovan=42, testrpc=50', - }) - .option('contract', { - type: 'string', - description: 'name of contract to deploy, excluding .sol extension', - }) - .option('constructor-args', { - type: 'string', - description: 'comma separated list of constructor args to deploy contract with', - }) - .option('jsonrpc-url', { - type: 'string', - default: DEFAULT_JSONRPC_URL, - description: 'url of JSON RPC', - }) - .option('account', { +(async () => { + const argv = yargs + .option('contracts-dir', { type: 'string', - description: 'account to use for deploying contracts', + description: 'path of contracts directory to compile', }) - .option('gas-price', { + .option('artifacts-dir', { type: 'string', - default: DEFAULT_GAS_PRICE, - description: 'gasPrice to be used for transactions', + description: 'path to write contracts artifacts to', }) - .demandOption(['contract', 'args', 'account']) - .help().argv; -} - -/** - * Adds additional required options for when the user is calling the compile command. - * @param yargsInstance yargs instance provided in builder function callback. - */ -function compileCommandBuilder(yargsInstance: any) { - return yargsInstance .option('contracts', { type: 'string', default: DEFAULT_CONTRACTS_LIST, description: 'comma separated list of contracts to compile', }) .help().argv; -} - -(() => { - const identityCommandBuilder = _.identity; - return yargs - .option('contracts-dir', { - type: 'string', - description: 'path of contracts directory to compile', - }) - .option('artifacts-dir', { - type: 'string', - description: 'path to write contracts artifacts to', - }) - .demandCommand(1) - .command('compile', 'compile contracts', compileCommandBuilder, consoleReporter(onCompileCommandAsync)) - .command( - 'deploy', - 'deploy a single contract with provided arguments', - deployCommandBuilder, - consoleReporter(onDeployCommandAsync), - ) - .help().argv; + const opts: CompilerOptions = { + contractsDir: argv.contractsDir, + artifactsDir: argv.artifactsDir, + contracts: argv.contracts === DEFAULT_CONTRACTS_LIST ? DEFAULT_CONTRACTS_LIST : argv.contracts.split(SEPARATOR), + }; + const compiler = new Compiler(opts); + await compiler.compileAsync(); })(); diff --git a/packages/deployer/src/commands.ts b/packages/deployer/src/commands.ts deleted file mode 100644 index 8e544a60b..000000000 --- a/packages/deployer/src/commands.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Compiler } from './compiler'; -import { Deployer } from './deployer'; -import { CompilerOptions, DeployerOptions } from './utils/types'; - -export const commands = { - async compileAsync(opts: CompilerOptions): Promise<void> { - const compiler = new Compiler(opts); - await compiler.compileAsync(); - }, - async deployAsync(contractName: string, args: any[], opts: DeployerOptions): Promise<void> { - const deployer = new Deployer(opts); - await deployer.deployAndSaveAsync(contractName, args); - }, -}; diff --git a/packages/deployer/src/deployer.ts b/packages/deployer/src/deployer.ts deleted file mode 100644 index c8c3a9a06..000000000 --- a/packages/deployer/src/deployer.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { AbiType, ConstructorAbi, ContractAbi, Provider, TxData } from '@0xproject/types'; -import { logUtils } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; -import * as solc from 'solc'; -import * as Web3 from 'web3'; - -import { Contract } from './utils/contract'; -import { encoder } from './utils/encoder'; -import { fsWrapper } from './utils/fs_wrapper'; -import { - ContractArtifact, - ContractNetworkData, - DeployerOptions, - ProviderDeployerOptions, - UrlDeployerOptions, -} from './utils/types'; -import { utils } from './utils/utils'; - -// Gas added to gas estimate to make sure there is sufficient gas for deployment. -const EXTRA_GAS = 200000; - -/** - * The Deployer facilitates deploying Solidity smart contracts to the blockchain. - * It can be used to build custom migration scripts. - */ -export class Deployer { - public web3Wrapper: Web3Wrapper; - private _artifactsDir: string; - private _networkId: number; - private _defaults: Partial<TxData>; - /** - * Gets data for current version stored in artifact. - * @param contractArtifact The contract artifact. - * @return Version specific contract data. - */ - private static _getContractCompilerOutputFromArtifactIfExists( - contractArtifact: ContractArtifact, - ): solc.StandardContractOutput { - const compilerOutputIfExists = contractArtifact.compilerOutput; - if (_.isUndefined(compilerOutputIfExists)) { - throw new Error(`Compiler output not found in artifact for contract: ${contractArtifact.contractName}`); - } - return compilerOutputIfExists; - } - /** - * Instantiate a new instance of the Deployer class. - * @param opts Deployer options, including either an RPC url or Provider instance. - * @returns A Deployer instance - */ - constructor(opts: DeployerOptions) { - this._artifactsDir = opts.artifactsDir; - this._networkId = opts.networkId; - this._defaults = opts.defaults; - let provider: Provider; - if (_.isUndefined((opts as ProviderDeployerOptions).provider)) { - const jsonrpcUrl = (opts as UrlDeployerOptions).jsonrpcUrl; - if (_.isUndefined(jsonrpcUrl)) { - throw new Error(`Deployer options don't contain provider nor jsonrpcUrl. Please pass one of them`); - } - provider = new Web3.providers.HttpProvider(jsonrpcUrl); - } else { - provider = (opts as ProviderDeployerOptions).provider; - } - this.web3Wrapper = new Web3Wrapper(provider, this._defaults); - } - /** - * Loads a contract's corresponding artifacts and deploys it with the supplied constructor arguments. - * @param contractName Name of the contract to deploy. Must match name of an artifact in supplied artifacts directory. - * @param args Array of contract constructor arguments. - * @return Deployed contract instance. - */ - public async deployAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> { - const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName); - const compilerOutput = Deployer._getContractCompilerOutputFromArtifactIfExists(contractArtifactIfExists); - const data = compilerOutput.evm.bytecode.object; - const from = await this._getFromAddressAsync(); - const gas = await this._getAllowableGasEstimateAsync(data); - const txData = { - gasPrice: this._defaults.gasPrice, - from, - data, - gas, - }; - if (_.isUndefined(compilerOutput.abi)) { - throw new Error(`ABI not found in ${contractName} artifacts`); - } - const abi = compilerOutput.abi; - const constructorAbi = _.find(abi, { type: AbiType.Constructor }) as ConstructorAbi; - const constructorArgs = _.isUndefined(constructorAbi) ? [] : constructorAbi.inputs; - if (constructorArgs.length !== args.length) { - const constructorSignature = `constructor(${_.map(constructorArgs, arg => `${arg.type} ${arg.name}`).join( - ', ', - )})`; - throw new Error( - `${contractName} expects ${constructorArgs.length} constructor params: ${constructorSignature}. Got ${ - args.length - }`, - ); - } - const web3ContractInstance = await this._deployFromAbiAsync(abi, args, txData); - logUtils.log(`${contractName}.sol successfully deployed at ${web3ContractInstance.address}`); - const contractInstance = new Contract(web3ContractInstance, this._defaults); - return contractInstance; - } - /** - * Loads a contract's artifact, deploys it with supplied constructor arguments, and saves the updated data - * back to the artifact file. - * @param contractName Name of the contract to deploy. Must match name of an artifact in artifacts directory. - * @param args Array of contract constructor arguments. - * @return Deployed contract instance. - */ - public async deployAndSaveAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> { - const contractInstance = await this.deployAsync(contractName, args); - await this._saveContractDataToArtifactAsync(contractName, contractInstance.address, args); - return contractInstance; - } - /** - * Deploys a contract given its ABI, arguments, and transaction data. - * @param abi ABI of contract to deploy. - * @param args Constructor arguments to use in deployment. - * @param txData Tx options used for deployment. - * @return Promise that resolves to a web3 contract instance. - */ - private async _deployFromAbiAsync(abi: ContractAbi, args: any[], txData: TxData): Promise<any> { - const contract: Web3.Contract<Web3.ContractInstance> = this.web3Wrapper.getContractFromAbi(abi); - const deployPromise = new Promise((resolve, reject) => { - /** - * Contract is inferred as 'any' because TypeScript - * is not able to read 'new' from the Contract interface - */ - (contract as any).new(...args, txData, (err: Error, res: any): any => { - if (err) { - reject(err); - } else if (_.isUndefined(res.address) && !_.isUndefined(res.transactionHash)) { - logUtils.log(`transactionHash: ${res.transactionHash}`); - } else { - resolve(res); - } - }); - }); - return deployPromise; - } - /** - * Updates a contract artifact's address and encoded constructor arguments. - * @param contractName Name of contract. Must match an existing artifact. - * @param contractAddress Contract address to save to artifact. - * @param args Contract constructor arguments that will be encoded and saved to artifact. - */ - private async _saveContractDataToArtifactAsync( - contractName: string, - contractAddress: string, - args: any[], - ): Promise<void> { - const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName); - const compilerOutput = Deployer._getContractCompilerOutputFromArtifactIfExists(contractArtifactIfExists); - if (_.isUndefined(compilerOutput.abi)) { - throw new Error(`ABI not found in ${contractName} artifacts`); - } - const abi = compilerOutput.abi; - const encodedConstructorArgs = encoder.encodeConstructorArgsFromAbi(args, abi); - const newContractData: ContractNetworkData = { - address: contractAddress, - links: {}, - constructorArgs: encodedConstructorArgs, - }; - const newArtifact = { - ...contractArtifactIfExists, - networks: { - ...contractArtifactIfExists.networks, - [this._networkId]: newContractData, - }, - }; - const artifactString = utils.stringifyWithFormatting(newArtifact); - const artifactPath = `${this._artifactsDir}/${contractName}.json`; - await fsWrapper.writeFileAsync(artifactPath, artifactString); - } - /** - * Loads a contract artifact, if it exists. - * @param contractName Name of the contract, without the extension. - * @return The contract artifact. - */ - private _loadContractArtifactIfExists(contractName: string): ContractArtifact { - const artifactPath = `${this._artifactsDir}/${contractName}.json`; - try { - const contractArtifact: ContractArtifact = require(artifactPath); - return contractArtifact; - } catch (err) { - throw new Error(`Artifact not found for contract: ${contractName} at ${artifactPath}`); - } - } - /** - * Gets the address to use for sending a transaction. - * @return The default from address. If not specified, returns the first address accessible by web3. - */ - private async _getFromAddressAsync(): Promise<string> { - let from: string; - if (_.isUndefined(this._defaults.from)) { - const accounts = await this.web3Wrapper.getAvailableAddressesAsync(); - from = accounts[0]; - } else { - from = this._defaults.from; - } - return from; - } - /** - * Estimates the gas required for a transaction. - * If gas would be over the block gas limit, the max allowable gas is returned instead. - * @param data Bytecode to estimate gas for. - * @return Gas estimate for transaction data. - */ - private async _getAllowableGasEstimateAsync(data: string): Promise<number> { - const block = await this.web3Wrapper.getBlockAsync('latest'); - let gas: number; - try { - const gasEstimate: number = await this.web3Wrapper.estimateGasAsync({ data }); - gas = Math.min(gasEstimate + EXTRA_GAS, block.gasLimit); - } catch (err) { - gas = block.gasLimit; - } - return gas; - } -} diff --git a/packages/deployer/src/index.ts b/packages/deployer/src/index.ts index 31a75677b..4b4c51de2 100644 --- a/packages/deployer/src/index.ts +++ b/packages/deployer/src/index.ts @@ -1,3 +1,2 @@ -export { Deployer } from './deployer'; export { Compiler } from './compiler'; export { ContractArtifact, ContractNetworks } from './utils/types'; diff --git a/packages/deployer/src/utils/contract.ts b/packages/deployer/src/utils/contract.ts deleted file mode 100644 index e8dd5218a..000000000 --- a/packages/deployer/src/utils/contract.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { schemas, SchemaValidator } from '@0xproject/json-schemas'; -import { AbiType, ContractAbi, EventAbi, FunctionAbi, MethodAbi, TxData } from '@0xproject/types'; -import { promisify } from '@0xproject/utils'; -import * as _ from 'lodash'; -import * as Web3 from 'web3'; - -export class Contract implements Web3.ContractInstance { - public address: string; - public abi: ContractAbi; - private _contract: Web3.ContractInstance; - private _defaults: Partial<TxData>; - private _validator: SchemaValidator; - // This class instance is going to be populated with functions and events depending on the ABI - // and we don't know their types in advance - [name: string]: any; - constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<TxData>) { - this._contract = web3ContractInstance; - this.address = web3ContractInstance.address; - this.abi = web3ContractInstance.abi; - this._defaults = defaults; - this._populateEvents(); - this._populateFunctions(); - this._validator = new SchemaValidator(); - } - private _populateFunctions(): void { - const functionsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Function) as FunctionAbi[]; - _.forEach(functionsAbi, (functionAbi: MethodAbi) => { - if (functionAbi.constant) { - const cbStyleCallFunction = this._contract[functionAbi.name].call; - this[functionAbi.name] = promisify(cbStyleCallFunction, this._contract); - this[functionAbi.name].call = promisify(cbStyleCallFunction, this._contract); - } else { - const cbStyleFunction = this._contract[functionAbi.name]; - const cbStyleCallFunction = this._contract[functionAbi.name].call; - const cbStyleEstimateGasFunction = this._contract[functionAbi.name].estimateGas; - this[functionAbi.name] = this._promisifyWithDefaultParams(cbStyleFunction); - this[functionAbi.name].estimateGasAsync = promisify(cbStyleEstimateGasFunction); - this[functionAbi.name].sendTransactionAsync = this._promisifyWithDefaultParams(cbStyleFunction); - this[functionAbi.name].call = promisify(cbStyleCallFunction, this._contract); - } - }); - } - private _populateEvents(): void { - const eventsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Event) as EventAbi[]; - _.forEach(eventsAbi, (eventAbi: EventAbi) => { - this[eventAbi.name] = this._contract[eventAbi.name]; - }); - } - private _promisifyWithDefaultParams(fn: (...args: any[]) => void): (...args: any[]) => Promise<any> { - const promisifiedWithDefaultParams = async (...args: any[]) => { - const promise = new Promise((resolve, reject) => { - const lastArg = args[args.length - 1]; - let txData: Partial<TxData> = {}; - if (this._isTxData(lastArg)) { - txData = args.pop(); - } - txData = { - ...this._defaults, - ...txData, - }; - const callback = (err: Error, data: any) => { - if (_.isNull(err)) { - resolve(data); - } else { - reject(err); - } - }; - args.push(txData); - args.push(callback); - fn.apply(this._contract, args); - }); - return promise; - }; - return promisifiedWithDefaultParams; - } - private _isTxData(lastArg: any): boolean { - const isValid = this._validator.isValid(lastArg, schemas.txDataSchema); - return isValid; - } -} diff --git a/packages/deployer/src/utils/error_reporter.ts b/packages/deployer/src/utils/error_reporter.ts deleted file mode 100644 index 4e73307f0..000000000 --- a/packages/deployer/src/utils/error_reporter.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { logUtils } from '@0xproject/utils'; - -/** - * Makes an async function no-throw printing errors to the console - * @param asyncFn async function to wrap - * @return Wrapped version of the passed function - */ -export function consoleReporter<T>(asyncFn: (arg: T) => Promise<void>): (arg: T) => Promise<void> { - const noThrowFnAsync = async (arg: T) => { - try { - const result = await asyncFn(arg); - return result; - } catch (err) { - logUtils.log(`${err}`); - } - }; - return noThrowFnAsync; -} diff --git a/packages/deployer/src/utils/types.ts b/packages/deployer/src/utils/types.ts index 19c27df67..b12a11b79 100644 --- a/packages/deployer/src/utils/types.ts +++ b/packages/deployer/src/utils/types.ts @@ -50,17 +50,6 @@ export interface SolcErrors { [key: string]: boolean; } -export interface CliOptions extends yargs.Arguments { - artifactsDir: string; - contractsDir: string; - jsonrpcUrl: string; - networkId: number; - gasPrice: string; - account?: string; - contract?: string; - args?: string; -} - export interface CompilerOptions { contractsDir?: string; artifactsDir?: string; @@ -68,22 +57,6 @@ export interface CompilerOptions { contracts?: string[] | '*'; } -export interface BaseDeployerOptions { - artifactsDir: string; - networkId: number; - defaults: Partial<TxData>; -} - -export interface ProviderDeployerOptions extends BaseDeployerOptions { - provider: Provider; -} - -export interface UrlDeployerOptions extends BaseDeployerOptions { - jsonrpcUrl: string; -} - -export type DeployerOptions = UrlDeployerOptions | ProviderDeployerOptions; - export interface ContractSourceData { [contractName: string]: ContractSpecificSourceData; } diff --git a/packages/deployer/test/deployer_test.ts b/packages/deployer/test/deployer_test.ts deleted file mode 100644 index a8abc6454..000000000 --- a/packages/deployer/test/deployer_test.ts +++ /dev/null @@ -1,74 +0,0 @@ -import * as chai from 'chai'; -import 'mocha'; - -import { Compiler } from '../src/compiler'; -import { Deployer } from '../src/deployer'; -import { fsWrapper } from '../src/utils/fs_wrapper'; -import { CompilerOptions, ContractArtifact, ContractNetworkData, DoneCallback } from '../src/utils/types'; - -import { constructor_args, exchange_binary } from './fixtures/exchange_bin'; -import { constants } from './util/constants'; -import { provider } from './util/provider'; - -const expect = chai.expect; - -describe('#Deployer', () => { - const artifactsDir = `${__dirname}/fixtures/artifacts`; - const contractsDir = `${__dirname}/fixtures/contracts`; - const exchangeArtifactPath = `${artifactsDir}/Exchange.json`; - const compilerOpts: CompilerOptions = { - artifactsDir, - contractsDir, - contracts: constants.contracts, - }; - const compiler = new Compiler(compilerOpts); - const deployerOpts = { - artifactsDir, - networkId: constants.networkId, - provider, - defaults: { - gasPrice: constants.gasPrice, - }, - }; - const deployer = new Deployer(deployerOpts); - beforeEach(function(done: DoneCallback) { - this.timeout(constants.timeoutMs); - (async () => { - if (fsWrapper.doesPathExistSync(exchangeArtifactPath)) { - await fsWrapper.removeFileAsync(exchangeArtifactPath); - } - await compiler.compileAsync(); - done(); - })().catch(done); - }); - describe('#deployAsync', () => { - it('should deploy the Exchange contract without updating the Exchange artifact', async () => { - const exchangeConstructorArgs = [constants.zrxTokenAddress, constants.tokenTransferProxyAddress]; - const exchangeContractInstance = await deployer.deployAsync('Exchange', exchangeConstructorArgs); - const opts = { - encoding: 'utf8', - }; - const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts); - const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString); - const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId]; - const exchangeAddress = exchangeContractInstance.address; - expect(exchangeAddress).to.not.equal(undefined); - expect(exchangeContractData).to.equal(undefined); - }); - }); - describe('#deployAndSaveAsync', () => { - it('should save the correct contract address and constructor arguments to the Exchange artifact', async () => { - const exchangeConstructorArgs = [constants.zrxTokenAddress, constants.tokenTransferProxyAddress]; - const exchangeContractInstance = await deployer.deployAndSaveAsync('Exchange', exchangeConstructorArgs); - const opts = { - encoding: 'utf8', - }; - const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts); - const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString); - const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId]; - const exchangeAddress = exchangeContractInstance.address; - expect(exchangeAddress).to.be.equal(exchangeContractData.address); - expect(constructor_args).to.be.equal(exchangeContractData.constructorArgs); - }); - }); -}); |