Implement new artifacts format

This commit is contained in:
Leonid Logvinov
2018-04-18 22:22:39 +02:00
parent 69a6166b6a
commit 72b2a1c66f
24 changed files with 314 additions and 272 deletions

View File

@@ -108,8 +108,8 @@ for (const abiFileName of abiFileNames) {
ABI = parsedContent; // ABI file
} else if (!_.isUndefined(parsedContent.abi)) {
ABI = parsedContent.abi; // Truffle artifact
} else if (!_.isUndefined(parsedContent.networks) && !_.isUndefined(parsedContent.networks[args.networkId])) {
ABI = parsedContent.networks[args.networkId].abi; // 0x contracts package artifact
} else if (!_.isUndefined(parsedContent.compilerOutput.abi)) {
ABI = parsedContent.compilerOutput.abi; // 0x artifact
}
if (_.isUndefined(ABI)) {
logUtils.log(`${chalk.red(`ABI not found in ${abiFileName}.`)}`);

View File

@@ -0,0 +1,33 @@
{
"artifactsDir": "../migrations/src/artifacts",
"contractsDir": "src/contracts",
"compilerSettings": {
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
}
},
"contracts": [
"Exchange",
"DummyToken",
"ZRXToken",
"Token",
"WETH9",
"TokenTransferProxy",
"MultiSigWallet",
"MultiSigWalletWithTimeLock",
"MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress",
"MaliciousToken",
"TokenRegistry",
"Arbitrage",
"EtherDelta",
"AccountLevels"
]
}

View File

@@ -16,7 +16,7 @@
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
"run_mocha": "mocha 'lib/test/**/*.js' --timeout 100000 --bail --exit",
"compile:comment": "Yarn workspaces do not link binaries correctly so we need to reference them directly https://github.com/yarnpkg/yarn/issues/3846",
"compile": "node ../deployer/lib/src/cli.js compile --contracts ${npm_package_config_contracts} --contracts-dir src/contracts --artifacts-dir ../migrations/src/artifacts",
"compile": "node ../deployer/lib/src/cli.js compile",
"clean": "shx rm -rf ./lib",
"generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis ${npm_package_config_abis} --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'",
"lint": "tslint --project . 'migrations/**/*.ts' 'test/**/*.ts' 'util/**/*.ts' 'deploy/**/*.ts'",
@@ -26,8 +26,7 @@
"test:circleci": "yarn test:coverage"
},
"config": {
"abis": "../migrations/src/artifacts/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken|Arbitrage|EtherDelta|AccountLevels).json",
"contracts": "Exchange,DummyToken,ZRXToken,Token,WETH9,TokenTransferProxy,MultiSigWallet,MultiSigWalletWithTimeLock,MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress,MaliciousToken,TokenRegistry,Arbitrage,EtherDelta,AccountLevels"
"abis": "../migrations/src/artifacts/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken|Arbitrage|EtherDelta|AccountLevels).json"
},
"repository": {
"type": "git",

View File

@@ -17,7 +17,7 @@ import { chaiSetup } from './utils/chai_setup';
import { deployer } from './utils/deployer';
import { provider, web3Wrapper } from './utils/web3_wrapper';
const MULTI_SIG_ABI = artifacts.MultiSigWalletWithTimeLockArtifact.networks[constants.TESTRPC_NETWORK_ID].abi;
const MULTI_SIG_ABI = artifacts.MultiSigWalletWithTimeLockArtifact.compilerOutput.abi;
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);

View File

@@ -17,10 +17,9 @@ import { ContractName, SubmissionContractEventArgs, TransactionDataParams } from
import { chaiSetup } from './utils/chai_setup';
import { deployer } from './utils/deployer';
import { provider, web3Wrapper } from './utils/web3_wrapper';
const PROXY_ABI = artifacts.TokenTransferProxyArtifact.networks[constants.TESTRPC_NETWORK_ID].abi;
const PROXY_ABI = artifacts.TokenTransferProxyArtifact.compilerOutput.abi;
const MUTISIG_WALLET_WITH_TIME_LOCK_EXCEPT_REMOVE_AUTHORIZED_ADDRESS_ABI =
artifacts.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact.networks[constants.TESTRPC_NETWORK_ID]
.abi;
artifacts.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact.compilerOutput.abi;
chaiSetup.configure();
const expect = chai.expect;

View File

@@ -1,3 +1,5 @@
import { ContractArtifact } from '@0xproject/deployer';
import * as DummyTokenArtifact from '../src/artifacts/DummyToken.json';
import * as ExchangeArtifact from '../src/artifacts/Exchange.json';
import * as MaliciousTokenArtifact from '../src/artifacts/MaliciousToken.json';
@@ -9,17 +11,15 @@ import * as TokenTransferProxyArtifact from '../src/artifacts/TokenTransferProxy
import * as EtherTokenArtifact from '../src/artifacts/WETH9.json';
import * as ZRXArtifact from '../src/artifacts/ZRXToken.json';
import { Artifact } from './types';
export const artifacts = {
ZRXArtifact: (ZRXArtifact as any) as Artifact,
DummyTokenArtifact: (DummyTokenArtifact as any) as Artifact,
TokenArtifact: (TokenArtifact as any) as Artifact,
ExchangeArtifact: (ExchangeArtifact as any) as Artifact,
EtherTokenArtifact: (EtherTokenArtifact as any) as Artifact,
TokenRegistryArtifact: (TokenRegistryArtifact as any) as Artifact,
MaliciousTokenArtifact: (MaliciousTokenArtifact as any) as Artifact,
TokenTransferProxyArtifact: (TokenTransferProxyArtifact as any) as Artifact,
MultiSigWalletWithTimeLockArtifact: (MultiSigWalletWithTimeLockArtifact as any) as Artifact,
MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact: (MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact as any) as Artifact,
ZRXArtifact: (ZRXArtifact as any) as ContractArtifact,
DummyTokenArtifact: (DummyTokenArtifact as any) as ContractArtifact,
TokenArtifact: (TokenArtifact as any) as ContractArtifact,
ExchangeArtifact: (ExchangeArtifact as any) as ContractArtifact,
EtherTokenArtifact: (EtherTokenArtifact as any) as ContractArtifact,
TokenRegistryArtifact: (TokenRegistryArtifact as any) as ContractArtifact,
MaliciousTokenArtifact: (MaliciousTokenArtifact as any) as ContractArtifact,
TokenTransferProxyArtifact: (TokenTransferProxyArtifact as any) as ContractArtifact,
MultiSigWalletWithTimeLockArtifact: (MultiSigWalletWithTimeLockArtifact as any) as ContractArtifact,
MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact: (MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact as any) as ContractArtifact,
};

View File

@@ -100,19 +100,3 @@ export enum ContractName {
EtherDelta = 'EtherDelta',
Arbitrage = 'Arbitrage',
}
export interface Artifact {
contract_name: ContractName;
networks: {
[networkId: number]: {
abi: ContractAbi;
solc_version: string;
keccak256: string;
optimizer_enabled: number;
unlinked_binary: string;
updated_at: number;
address: string;
constructor_args: string;
};
};
}

View File

@@ -13,7 +13,6 @@ import { constants } from './utils/constants';
import { consoleReporter } from './utils/error_reporter';
import { CliOptions, CompilerOptions, DeployerOptions } from './utils/types';
const DEFAULT_OPTIMIZER_ENABLED = false;
const DEFAULT_CONTRACTS_DIR = path.resolve('src/contracts');
const DEFAULT_ARTIFACTS_DIR = path.resolve('src/artifacts');
const DEFAULT_NETWORK_ID = 50;
@@ -28,10 +27,8 @@ const DEFAULT_CONTRACTS_LIST = '*';
async function onCompileCommandAsync(argv: CliOptions): Promise<void> {
const opts: CompilerOptions = {
contractsDir: argv.contractsDir,
networkId: argv.networkId,
optimizerEnabled: argv.shouldOptimize,
artifactsDir: argv.artifactsDir,
specifiedContracts: getContractsSetFromList(argv.contracts),
contracts: argv.contracts === '*' ? argv.contracts : argv.contracts.split(','),
};
await commands.compileAsync(opts);
}
@@ -46,10 +43,8 @@ async function onDeployCommandAsync(argv: CliOptions): Promise<void> {
const networkId = await web3Wrapper.getNetworkIdAsync();
const compilerOpts: CompilerOptions = {
contractsDir: argv.contractsDir,
networkId,
optimizerEnabled: argv.shouldOptimize,
artifactsDir: argv.artifactsDir,
specifiedContracts: getContractsSetFromList(argv.contracts),
contracts: argv.contracts === '*' ? argv.contracts : argv.contracts.split(','),
};
await commands.compileAsync(compilerOpts);
@@ -58,7 +53,7 @@ async function onDeployCommandAsync(argv: CliOptions): Promise<void> {
from: argv.account,
};
const deployerOpts: DeployerOptions = {
artifactsDir: argv.artifactsDir,
artifactsDir: argv.artifactsDir || DEFAULT_ARTIFACTS_DIR,
jsonrpcUrl: argv.jsonrpcUrl,
networkId,
defaults,
@@ -67,27 +62,17 @@ async function onDeployCommandAsync(argv: CliOptions): Promise<void> {
const deployerArgs = deployerArgsString.split(',');
await commands.deployAsync(argv.contract as string, deployerArgs, deployerOpts);
}
/**
* Creates a set of contracts to compile.
* @param contracts Comma separated list of contracts to compile
*/
function getContractsSetFromList(contracts: string): Set<string> {
const specifiedContracts = new Set();
if (contracts === '*') {
return new Set(['*']);
}
const contractsArray = contracts.split(',');
_.forEach(contractsArray, contractName => {
specifiedContracts.add(contractName);
});
return specifiedContracts;
}
/**
* Provides extra required options for 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, exluding .sol extension',
@@ -96,7 +81,35 @@ function deployCommandBuilder(yargsInstance: any) {
type: 'string',
description: 'comma separated list of constructor args to deploy contract with',
})
.demandOption(['contract', 'args'])
.option('jsonrpc-url', {
type: 'string',
default: DEFAULT_JSONRPC_URL,
description: 'url of JSON RPC',
})
.option('account', {
type: 'string',
description: 'account to use for deploying contracts',
})
.option('gas-price', {
type: 'string',
default: DEFAULT_GAS_PRICE,
description: 'gasPrice to be used for transactions',
})
.demandOption(['contract', 'args', 'account'])
.help().argv;
}
/**
* Provides extra required options for 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;
}
@@ -105,44 +118,14 @@ function deployCommandBuilder(yargsInstance: any) {
return yargs
.option('contracts-dir', {
type: 'string',
default: DEFAULT_CONTRACTS_DIR,
description: 'path of contracts directory to compile',
})
.option('network-id', {
type: 'number',
default: DEFAULT_NETWORK_ID,
description: 'mainnet=1, kovan=42, testrpc=50',
})
.option('should-optimize', {
type: 'boolean',
default: DEFAULT_OPTIMIZER_ENABLED,
description: 'enable optimizer',
})
.option('artifacts-dir', {
type: 'string',
default: DEFAULT_ARTIFACTS_DIR,
description: 'path to write contracts artifacts to',
})
.option('jsonrpc-url', {
type: 'string',
default: DEFAULT_JSONRPC_URL,
description: 'url of JSON RPC',
})
.option('gas-price', {
type: 'string',
default: DEFAULT_GAS_PRICE,
description: 'gasPrice to be used for transactions',
})
.option('account', {
type: 'string',
description: 'account to use for deploying contracts',
})
.option('contracts', {
type: 'string',
default: DEFAULT_CONTRACTS_LIST,
description: 'comma separated list of contracts to compile',
})
.command('compile', 'compile contracts', identityCommandBuilder, consoleReporter(onCompileCommandAsync))
.demandCommand(1)
.command('compile', 'compile contracts', compileCommandBuilder, consoleReporter(onCompileCommandAsync))
.command(
'deploy',
'deploy a single contract with provided arguments',

View File

@@ -6,6 +6,7 @@ import {
FSResolver,
NameResolver,
NPMResolver,
RelativeFSResolver,
Resolver,
URLResolver,
} from '@0xproject/sol-resolver';
@@ -38,11 +39,25 @@ import {
ContractNetworks,
ContractSourceData,
ContractSpecificSourceData,
ContractVersionData,
} from './utils/types';
import { utils } from './utils/utils';
const ALL_CONTRACTS_IDENTIFIER = '*';
const SOLC_BIN_DIR = path.join(__dirname, '..', '..', 'solc_bin');
const DEFAULT_CONTRACTS_DIR = path.resolve('contracts');
const DEFAULT_ARTIFACTS_DIR = path.resolve('artifacts');
const DEFAULT_COMPILER_SETTINGS: solc.CompilerSettings = {
optimizer: {
enabled: false,
},
outputSelection: {
'*': {
'*': ['abi', 'evm.bytecode.object'],
},
},
};
const CONFIG_FILE = 'compiler.json';
/**
* The Compiler facilitates compiling Solidity smart contracts and saves the results
@@ -52,26 +67,27 @@ export class Compiler {
private _resolver: Resolver;
private _nameResolver: NameResolver;
private _contractsDir: string;
private _networkId: number;
private _optimizerEnabled: boolean;
private _compilerSettings: solc.CompilerSettings;
private _artifactsDir: string;
private _specifiedContracts: Set<string> = new Set();
private _specifiedContracts: string[] | '*';
/**
* Instantiates a new instance of the Compiler class.
* @param opts Options specifying directories, network, and optimization settings.
* @return An instance of the Compiler class.
*/
constructor(opts: CompilerOptions) {
this._contractsDir = opts.contractsDir;
this._networkId = opts.networkId;
this._optimizerEnabled = opts.optimizerEnabled;
this._artifactsDir = opts.artifactsDir;
this._specifiedContracts = opts.specifiedContracts;
const config: CompilerOptions = fs.existsSync(CONFIG_FILE)
? JSON.parse(fs.readFileSync(CONFIG_FILE).toString())
: {};
this._contractsDir = opts.contractsDir || config.contractsDir || DEFAULT_CONTRACTS_DIR;
this._compilerSettings = opts.compilerSettings || config.compilerSettings || DEFAULT_COMPILER_SETTINGS;
this._artifactsDir = opts.artifactsDir || config.artifactsDir || DEFAULT_ARTIFACTS_DIR;
this._specifiedContracts = opts.contracts || config.contracts || '*';
this._nameResolver = new NameResolver(path.resolve(this._contractsDir));
const resolver = new FallthroughResolver();
resolver.appendResolver(new URLResolver());
const packagePath = path.resolve('');
resolver.appendResolver(new NPMResolver(packagePath));
resolver.appendResolver(new RelativeFSResolver(this._contractsDir));
resolver.appendResolver(new FSResolver());
resolver.appendResolver(this._nameResolver);
this._resolver = resolver;
@@ -83,13 +99,13 @@ export class Compiler {
await createDirIfDoesNotExistAsync(this._artifactsDir);
await createDirIfDoesNotExistAsync(SOLC_BIN_DIR);
let contractNamesToCompile: string[] = [];
if (this._specifiedContracts.has(ALL_CONTRACTS_IDENTIFIER)) {
if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) {
const allContracts = this._nameResolver.getAll();
contractNamesToCompile = _.map(allContracts, contractSource =>
path.basename(contractSource.path, constants.SOLIDITY_FILE_EXTENSION),
);
} else {
contractNamesToCompile = Array.from(this._specifiedContracts.values());
contractNamesToCompile = this._specifiedContracts;
}
for (const contractNameToCompile of contractNamesToCompile) {
await this._compileContractAsync(contractNameToCompile);
@@ -101,17 +117,18 @@ export class Compiler {
*/
private async _compileContractAsync(contractName: string): Promise<void> {
const contractSource = this._resolver.resolve(contractName);
const absoluteContractPath = path.join(this._contractsDir, contractSource.path);
const currentArtifactIfExists = await getContractArtifactIfExistsAsync(this._artifactsDir, contractName);
const sourceTreeHashHex = `0x${this._getSourceTreeHash(contractSource.path).toString('hex')}`;
const sourceTreeHashHex = `0x${this._getSourceTreeHash(absoluteContractPath).toString('hex')}`;
let shouldCompile = false;
if (_.isUndefined(currentArtifactIfExists)) {
shouldCompile = true;
} else {
const currentArtifact = currentArtifactIfExists as ContractArtifact;
shouldCompile =
currentArtifact.networks[this._networkId].optimizer_enabled !== this._optimizerEnabled ||
currentArtifact.networks[this._networkId].source_tree_hash !== sourceTreeHashHex;
currentArtifact.schemaVersion !== '2.0.0' ||
!_.isEqual(currentArtifact.compiler.settings, this._compilerSettings) ||
currentArtifact.sourceTreeHashHex !== sourceTreeHashHex;
}
if (!shouldCompile) {
return;
@@ -139,30 +156,14 @@ export class Compiler {
logUtils.log(`Compiling ${contractName} with Solidity v${solcVersion}...`);
const source = contractSource.source;
const absoluteFilePath = contractSource.path;
const standardInput: solc.StandardInput = {
language: 'Solidity',
sources: {
[absoluteFilePath]: {
urls: [`file://${absoluteFilePath}`],
},
},
settings: {
optimizer: {
enabled: this._optimizerEnabled,
},
outputSelection: {
'*': {
'*': [
'abi',
'evm.bytecode.object',
'evm.bytecode.sourceMap',
'evm.deployedBytecode.object',
'evm.deployedBytecode.sourceMap',
],
},
[contractSource.path]: {
content: contractSource.source,
},
},
settings: this._compilerSettings,
};
const compiled: solc.StandardOutput = JSON.parse(
solcInstance.compileStandardWrapper(JSON.stringify(standardInput), importPath => {
@@ -188,34 +189,28 @@ export class Compiler {
});
}
}
const compiledData = compiled.contracts[absoluteFilePath][contractName];
const compiledData = compiled.contracts[contractSource.path][contractName];
if (_.isUndefined(compiledData)) {
throw new Error(
`Contract ${contractName} not found in ${absoluteFilePath}. Please make sure your contract has the same name as it's file name`,
`Contract ${contractName} not found in ${
contractSource.path
}. Please make sure your contract has the same name as it's file name`,
);
}
const abi: ContractAbi = compiledData.abi;
const bytecode = `0x${compiledData.evm.bytecode.object}`;
const runtimeBytecode = `0x${compiledData.evm.deployedBytecode.object}`;
const sourceMap = compiledData.evm.bytecode.sourceMap;
const sourceMapRuntime = compiledData.evm.deployedBytecode.sourceMap;
const unresolvedSourcePaths = _.keys(compiled.sources);
const sources = _.map(
unresolvedSourcePaths,
unresolvedSourcePath => this._resolver.resolve(unresolvedSourcePath).path,
const sourceCodes = _.mapValues(
compiled.sources,
(_1, sourceFilePath) => this._resolver.resolve(sourceFilePath).source,
);
const updated_at = Date.now();
const contractNetworkData: ContractNetworkData = {
solc_version: solcVersion,
source_tree_hash: sourceTreeHashHex,
optimizer_enabled: this._optimizerEnabled,
abi,
bytecode,
runtime_bytecode: runtimeBytecode,
updated_at,
source_map: sourceMap,
source_map_runtime: sourceMapRuntime,
sources,
const contractVersion: ContractVersionData = {
compilerOutput: compiledData,
sources: compiled.sources,
sourceCodes,
sourceTreeHashHex,
compiler: {
name: 'solc',
version: solcVersion,
settings: this._compilerSettings,
},
};
let newArtifact: ContractArtifact;
@@ -223,17 +218,14 @@ export class Compiler {
const currentArtifact = currentArtifactIfExists as ContractArtifact;
newArtifact = {
...currentArtifact,
networks: {
...currentArtifact.networks,
[this._networkId]: contractNetworkData,
},
...contractVersion,
};
} else {
newArtifact = {
contract_name: contractName,
networks: {
[this._networkId]: contractNetworkData,
},
schemaVersion: '2.0.0',
contractName,
...contractVersion,
networks: {},
};
}

View File

@@ -2,6 +2,7 @@ import { AbiType, ConstructorAbi, ContractAbi, Provider, TxData } from '@0xproje
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';
@@ -28,7 +29,20 @@ export class Deployer {
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.
@@ -58,10 +72,8 @@ export class Deployer {
*/
public async deployAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> {
const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName);
const contractNetworkDataIfExists: ContractNetworkData = this._getContractNetworkDataFromArtifactIfExists(
contractArtifactIfExists,
);
const data = contractNetworkDataIfExists.bytecode;
const compilerOutput = Deployer._getContractCompilerOutputFromArtifactIfExists(contractArtifactIfExists);
const data = compilerOutput.evm.bytecode.object;
const from = await this._getFromAddressAsync();
const gas = await this._getAllowableGasEstimateAsync(data);
const txData = {
@@ -70,7 +82,7 @@ export class Deployer {
data,
gas,
};
const abi = contractNetworkDataIfExists.abi;
const abi = compilerOutput.abi;
const constructorAbi = _.find(abi, { type: AbiType.Constructor }) as ConstructorAbi;
const constructorArgs = _.isUndefined(constructorAbi) ? [] : constructorAbi.inputs;
if (constructorArgs.length !== args.length) {
@@ -138,15 +150,13 @@ export class Deployer {
args: any[],
): Promise<void> {
const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName);
const contractNetworkDataIfExists: ContractNetworkData = this._getContractNetworkDataFromArtifactIfExists(
contractArtifactIfExists,
);
const abi = contractNetworkDataIfExists.abi;
const compilerOutput = Deployer._getContractCompilerOutputFromArtifactIfExists(contractArtifactIfExists);
const abi = compilerOutput.abi;
const encodedConstructorArgs = encoder.encodeConstructorArgsFromAbi(args, abi);
const newContractData = {
...contractNetworkDataIfExists,
const newContractData: ContractNetworkData = {
address: contractAddress,
constructor_args: encodedConstructorArgs,
links: {},
constructorArgs: encodedConstructorArgs,
};
const newArtifact = {
...contractArtifactIfExists,
@@ -173,18 +183,6 @@ export class Deployer {
throw new Error(`Artifact not found for contract: ${contractName} at ${artifactPath}`);
}
}
/**
* Gets data for current networkId stored in artifact.
* @param contractArtifact The contract artifact.
* @return Network specific contract data.
*/
private _getContractNetworkDataFromArtifactIfExists(contractArtifact: ContractArtifact): ContractNetworkData {
const contractNetworkDataIfExists = contractArtifact.networks[this._networkId];
if (_.isUndefined(contractNetworkDataIfExists)) {
throw new Error(`Data not found in artifact for contract: ${contractArtifact.contract_name}`);
}
return contractNetworkDataIfExists;
}
/**
* Gets the address to use for sending a transaction.
* @return The default from address. If not specified, returns the first address accessible by web3.

View File

@@ -1,2 +1,3 @@
export { Deployer } from './deployer';
export { Compiler } from './compiler';
export { ContractArtifact, ContractNetworks } from './utils/types';

View File

@@ -1,4 +1,5 @@
import { ContractAbi, Provider, TxData } from '@0xproject/types';
import * as solc from 'solc';
import * as Web3 from 'web3';
import * as yargs from 'yargs';
@@ -9,28 +10,40 @@ export enum AbiType {
Fallback = 'fallback',
}
export interface ContractArtifact {
contract_name: string;
export interface ContractArtifact extends ContractVersionData {
schemaVersion: '2.0.0';
contractName: string;
networks: ContractNetworks;
}
export interface ContractVersionData {
compiler: {
name: 'solc';
version: string;
settings: solc.CompilerSettings;
};
sources: {
[sourceName: string]: {
id: number;
};
};
sourceCodes: {
[sourceName: string]: string;
};
sourceTreeHashHex: string;
compilerOutput: solc.StandardContractOutput;
}
export interface ContractNetworks {
[key: number]: ContractNetworkData;
[networkId: number]: ContractNetworkData;
}
export interface ContractNetworkData {
solc_version: string;
optimizer_enabled: boolean;
source_tree_hash: string;
abi: ContractAbi;
bytecode: string;
runtime_bytecode: string;
address?: string;
constructor_args?: string;
updated_at: number;
source_map: string;
source_map_runtime: string;
sources: string[];
address: string;
links: {
[linkName: string]: string;
};
constructorArgs: string;
}
export interface SolcErrors {
@@ -42,7 +55,6 @@ export interface CliOptions extends yargs.Arguments {
contractsDir: string;
jsonrpcUrl: string;
networkId: number;
shouldOptimize: boolean;
gasPrice: string;
account?: string;
contract?: string;
@@ -50,11 +62,10 @@ export interface CliOptions extends yargs.Arguments {
}
export interface CompilerOptions {
contractsDir: string;
networkId: number;
optimizerEnabled: boolean;
artifactsDir: string;
specifiedContracts: Set<string>;
contractsDir?: string;
artifactsDir?: string;
compilerSettings?: solc.CompilerSettings;
contracts?: string[] | '*';
}
export interface BaseDeployerOptions {

View File

@@ -18,9 +18,7 @@ describe('#Compiler', function() {
const compilerOpts: CompilerOptions = {
artifactsDir,
contractsDir,
networkId: constants.networkId,
optimizerEnabled: constants.optimizerEnabled,
specifiedContracts: new Set(constants.specifiedContracts),
contracts: constants.contracts,
};
const compiler = new Compiler(compilerOpts);
beforeEach((done: DoneCallback) => {
@@ -38,9 +36,8 @@ describe('#Compiler', function() {
};
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId];
// The last 43 bytes of the binaries are metadata which may not be equivalent
const unlinkedBinaryWithoutMetadata = exchangeContractData.bytecode.slice(0, -86);
const unlinkedBinaryWithoutMetadata = exchangeArtifact.compilerOutput.evm.bytecode.object.slice(0, -86);
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -86);
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
});

View File

@@ -19,9 +19,7 @@ describe('#Deployer', () => {
const compilerOpts: CompilerOptions = {
artifactsDir,
contractsDir,
networkId: constants.networkId,
optimizerEnabled: constants.optimizerEnabled,
specifiedContracts: new Set(constants.specifiedContracts),
contracts: constants.contracts,
};
const compiler = new Compiler(compilerOpts);
const deployerOpts = {
@@ -55,8 +53,7 @@ describe('#Deployer', () => {
const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId];
const exchangeAddress = exchangeContractInstance.address;
expect(exchangeAddress).to.not.equal(undefined);
expect(exchangeContractData.address).to.equal(undefined);
expect(exchangeContractData.constructor_args).to.equal(undefined);
expect(exchangeContractData).to.equal(undefined);
});
});
describe('#deployAndSaveAsync', () => {
@@ -71,7 +68,7 @@ describe('#Deployer', () => {
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.constructor_args);
expect(constructor_args).to.be.equal(exchangeContractData.constructorArgs);
});
});
});

File diff suppressed because one or more lines are too long

View File

@@ -7,5 +7,5 @@ export const constants = {
timeoutMs: 30000,
zrxTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
tokenTransferProxyAddress: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
specifiedContracts: '*',
contracts: '*' as '*',
};

View File

@@ -0,0 +1,15 @@
{
"compilerSettings": {
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
}
}
}

View File

@@ -12,13 +12,13 @@
"build": "tsc",
"test": "run-s build run_mocha",
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
"run_mocha": "mocha lib/test/**/*.js --bail --exit",
"run_mocha": "mocha lib/test/**/*_test.js lib/test/global_hooks.js --bail --exit",
"generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'artifacts/Metacoin.json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers --backend ethers && prettier --write 'src/contract_wrappers/**.ts'",
"coverage:report:text": "istanbul report text",
"coverage:report:html": "istanbul report html && open coverage/index.html",
"coverage:report:lcov": "istanbul report lcov",
"test:circleci": "yarn test:coverage",
"compile": "node ../deployer/lib/src/cli.js compile --contracts Metacoin --contracts-dir contracts --artifacts-dir artifacts"
"compile": "node ../deployer/lib/src/cli.js compile"
},
"author": "",
"license": "Apache-2.0",

View File

@@ -8,25 +8,22 @@ import { ContractData } from './types';
export const collectContractsData = (artifactsPath: string, sourcesPath: string, networkId: number) => {
const artifactsGlob = `${artifactsPath}/**/*.json`;
const artifactFileNames = glob.sync(artifactsGlob, { absolute: true });
const contractsDataIfExists: Array<ContractData | {}> = _.map(artifactFileNames, artifactFileName => {
const contractsData: ContractData[] = [];
_.forEach(artifactFileNames, artifactFileName => {
const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString());
const sources = artifact.networks[networkId].sources;
const contractName = artifact.contract_name;
const sources = _.keys(artifact.sources);
const contractName = artifact.contractName;
// We don't compute coverage for dependencies
const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString());
if (_.isUndefined(artifact.networks[networkId])) {
throw new Error(`No ${contractName} artifacts found for networkId ${networkId}`);
}
const contractData = {
sourceCodes,
sources,
sourceMap: artifact.networks[networkId].source_map,
sourceMapRuntime: artifact.networks[networkId].source_map_runtime,
runtimeBytecode: artifact.networks[networkId].runtime_bytecode,
bytecode: artifact.networks[networkId].bytecode,
bytecode: artifact.compilerOutput.evm.bytecode.object,
sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap,
runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object,
sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap,
};
return contractData;
contractsData.push(contractData);
});
const contractsData = _.filter(contractsDataIfExists, contractData => !_.isEmpty(contractData)) as ContractData[];
return contractsData;
};

View File

@@ -130,7 +130,10 @@ export class CoverageManager {
for (const traceInfo of this._traceInfos) {
if (traceInfo.address !== constants.NEW_CONTRACT) {
// Runtime transaction
const runtimeBytecode = (traceInfo as TraceInfoExistingContract).runtimeBytecode;
let runtimeBytecode = (traceInfo as TraceInfoExistingContract).runtimeBytecode;
if (runtimeBytecode.startsWith('0x')) {
runtimeBytecode = runtimeBytecode.slice(2);
}
const contractData = _.find(this._contractsData, { runtimeBytecode }) as ContractData;
if (_.isUndefined(contractData)) {
throw new Error(`Transaction to an unknown address: ${traceInfo.address}`);
@@ -154,7 +157,10 @@ export class CoverageManager {
}
} else {
// Contract creation transaction
const bytecode = (traceInfo as TraceInfoNewContract).bytecode;
let bytecode = (traceInfo as TraceInfoNewContract).bytecode;
if (bytecode.startsWith('0x')) {
bytecode = bytecode.slice(2);
}
const contractData = _.find(this._contractsData, contractDataCandidate =>
bytecode.startsWith(contractDataCandidate.bytecode),
) as ContractData;

View File

@@ -3,6 +3,7 @@ export { FallthroughResolver } from './resolvers/fallthrough_resolver';
export { URLResolver } from './resolvers/url_resolver';
export { NPMResolver } from './resolvers/npm_resolver';
export { FSResolver } from './resolvers/fs_resolver';
export { RelativeFSResolver } from './resolvers/relative_fs_resolver';
export { NameResolver } from './resolvers/name_resolver';
export { EnumerableResolver } from './resolvers/enumerable_resolver';
export { Resolver } from './resolvers/resolver';

View File

@@ -18,7 +18,7 @@ export class NameResolver extends EnumerableResolver {
const onFile = (filePath: string) => {
const contractName = path.basename(filePath, SOLIDITY_FILE_EXTENSION);
if (contractName === lookupContractName) {
const source = fs.readFileSync(filePath).toString();
const source = fs.readFileSync(path.join(this._contractsDir, filePath)).toString();
contractSource = {
source,
path: filePath,
@@ -35,7 +35,7 @@ export class NameResolver extends EnumerableResolver {
const contractSources: ContractSource[] = [];
const onFile = (filePath: string) => {
const contractName = path.basename(filePath, SOLIDITY_FILE_EXTENSION);
const source = fs.readFileSync(filePath).toString();
const source = fs.readFileSync(path.join(this._contractsDir, filePath)).toString();
const contractSource = {
source,
path: filePath,
@@ -54,9 +54,10 @@ export class NameResolver extends EnumerableResolver {
throw new Error(`No directory found at ${dirPath}`);
}
for (const fileName of dirContents) {
const entryPath = path.join(dirPath, fileName);
const isDirectory = fs.lstatSync(entryPath).isDirectory();
const isComplete = isDirectory ? this._traverseContractsDir(entryPath, onFile) : onFile(entryPath);
const absoluteEntryPath = path.join(dirPath, fileName);
const isDirectory = fs.lstatSync(absoluteEntryPath).isDirectory();
const entryPath = path.relative(this._contractsDir, absoluteEntryPath);
const isComplete = isDirectory ? this._traverseContractsDir(absoluteEntryPath, onFile) : onFile(entryPath);
if (isComplete) {
return isComplete;
}

View File

@@ -0,0 +1,26 @@
import * as fs from 'fs';
import * as path from 'path';
import { ContractSource } from '../types';
import { Resolver } from './resolver';
export class RelativeFSResolver extends Resolver {
private _contractsDir: string;
constructor(contractsDir: string) {
super();
this._contractsDir = contractsDir;
}
// tslint:disable-next-line:prefer-function-over-method
public resolveIfExists(importPath: string): ContractSource | undefined {
const filePath = path.join(this._contractsDir, importPath);
if (fs.existsSync(filePath)) {
const fileContent = fs.readFileSync(filePath).toString();
return {
source: fileContent,
path: importPath,
};
}
return undefined;
}
}

View File

@@ -59,32 +59,33 @@ declare module 'solc' {
| 'evm.gasEstimates'
| 'ewasm.wast'
| 'ewasm.wasm';
export interface CompilerSettings {
remappings?: string[];
optimizer?: {
enabled: boolean;
runs?: number;
};
evmVersion?: 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople';
metadata?: {
useLiteralContent: true;
};
libraries?: {
[fileName: string]: {
[libName: string]: string;
};
};
outputSelection: {
[fileName: string]: {
[contractName: string]: OutputField[];
};
};
}
export interface StandardInput {
language: 'Solidity' | 'serpent' | 'lll' | 'assembly';
sources: {
[fileName: string]: Source;
};
settings: {
remappings?: string[];
optimizer?: {
enabled: boolean;
runs?: number;
};
evmVersion?: 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople';
metadata?: {
useLiteralContent: true;
};
libraries?: {
[fileName: string]: {
[libName: string]: string;
};
};
outputSelection: {
[fileName: string]: {
[contractName: string]: OutputField[];
};
};
};
settings: CompilerSettings;
}
export type ErrorType =
| 'JSONError'
@@ -114,6 +115,19 @@ declare module 'solc' {
formattedMessage?: string;
}
import { ContractAbi } from '@0xproject/types';
export interface StandardContractOutput {
abi: ContractAbi;
evm: {
bytecode: {
object: string;
sourceMap: string;
};
deployedBytecode: {
object: string;
sourceMap: string;
};
};
}
export interface StandardOutput {
errors: Error[];
sources: {
@@ -125,19 +139,7 @@ declare module 'solc' {
};
contracts: {
[fileName: string]: {
[contractName: string]: {
abi: ContractAbi;
evm: {
bytecode: {
object: string;
sourceMap: string;
};
deployedBytecode: {
object: string;
sourceMap: string;
};
};
};
[contractName: string]: StandardContractOutput;
};
};
}