diff options
Diffstat (limited to 'packages')
83 files changed, 1250 insertions, 724 deletions
diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json index 9a750a169..8d1dbd67a 100644 --- a/packages/0x.js/CHANGELOG.json +++ b/packages/0x.js/CHANGELOG.json @@ -1,5 +1,15 @@ [ { + "version": "0.35.0", + "changes": [ + { + "note": + "Removed `ZeroExError.TransactionMiningTimeout` and moved it to '@0xproject/web3_wrapper' `Web3WrapperErrors.TransactionMiningTimeout`", + "pr": 485 + } + ] + }, + { "version": "0.34.0", "changes": [ { diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index b27eb188b..a6a9402c6 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -91,7 +91,6 @@ "truffle-hdwallet-provider": "^0.0.3", "tslint": "5.8.0", "typedoc": "0xProject/typedoc", - "types-bn": "^0.0.1", "typescript": "2.7.1", "web3-provider-engine": "^13.0.1", "webpack": "^3.1.0" diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts index b82cc820f..774b9ac12 100644 --- a/packages/0x.js/src/0x.ts +++ b/packages/0x.js/src/0x.ts @@ -58,7 +58,6 @@ export class ZeroEx { */ public proxy: TokenTransferProxyWrapper; private _web3Wrapper: Web3Wrapper; - private _abiDecoder: AbiDecoder; /** * Verifies that the elliptic curve signature `signature` was generated * by signing `data` with the private key corresponding to the `signerAddress` address. @@ -167,21 +166,22 @@ export class ZeroEx { ]); const artifactJSONs = _.values(artifacts); const abiArrays = _.map(artifactJSONs, artifact => artifact.abi); - this._abiDecoder = new AbiDecoder(abiArrays); const defaults = { gasPrice: config.gasPrice, }; this._web3Wrapper = new Web3Wrapper(provider, defaults); + _.forEach(abiArrays, abi => { + this._web3Wrapper.abiDecoder.addABI(abi); + }); this.proxy = new TokenTransferProxyWrapper( this._web3Wrapper, config.networkId, config.tokenTransferProxyContractAddress, ); - this.token = new TokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.proxy); + this.token = new TokenWrapper(this._web3Wrapper, config.networkId, this.proxy); this.exchange = new ExchangeWrapper( this._web3Wrapper, config.networkId, - this._abiDecoder, this.token, config.exchangeContractAddress, config.zrxContractAddress, @@ -191,7 +191,7 @@ export class ZeroEx { config.networkId, config.tokenRegistryContractAddress, ); - this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.token); + this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this.token); } /** * Sets a new web3 provider for 0x.js. Updating the provider will stop all @@ -285,44 +285,12 @@ export class ZeroEx { pollingIntervalMs = 1000, timeoutMs?: number, ): Promise<TransactionReceiptWithDecodedLogs> { - let timeoutExceeded = false; - if (timeoutMs) { - setTimeout(() => (timeoutExceeded = true), timeoutMs); - } - - const txReceiptPromise = new Promise( - (resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => { - const intervalId = intervalUtils.setAsyncExcludingInterval( - async () => { - if (timeoutExceeded) { - intervalUtils.clearAsyncExcludingInterval(intervalId); - return reject(ZeroExError.TransactionMiningTimeout); - } - - const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash); - if (!_.isNull(transactionReceipt)) { - intervalUtils.clearAsyncExcludingInterval(intervalId); - const logsWithDecodedArgs = _.map( - transactionReceipt.logs, - this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder), - ); - const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = { - ...transactionReceipt, - logs: logsWithDecodedArgs, - }; - resolve(transactionReceiptWithDecodedLogArgs); - } - }, - pollingIntervalMs, - (err: Error) => { - intervalUtils.clearAsyncExcludingInterval(intervalId); - reject(err); - }, - ); - }, + const transactionReceiptWithDecodedLogs = await this._web3Wrapper.awaitTransactionMinedAsync( + txHash, + pollingIntervalMs, + timeoutMs, ); - const txReceipt = await txReceiptPromise; - return txReceipt; + return transactionReceiptWithDecodedLogs; } /** * Instantiates and returns a new OrderStateWatcher instance. @@ -331,7 +299,7 @@ export class ZeroEx { * @return An instance of the 0x.js OrderStateWatcher class. */ public createOrderStateWatcher(config?: OrderStateWatcherConfig) { - return new OrderStateWatcher(this._web3Wrapper, this._abiDecoder, this.token, this.exchange, config); + return new OrderStateWatcher(this._web3Wrapper, this.token, this.exchange, config); } /* * HACK: `TokenWrapper` needs a token transfer proxy address. `TokenTransferProxy` address is fetched from diff --git a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts index 6c96428ed..02be2dab3 100644 --- a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts @@ -32,7 +32,6 @@ const CONTRACT_NAME_TO_NOT_FOUND_ERROR: { export class ContractWrapper { protected _web3Wrapper: Web3Wrapper; protected _networkId: number; - private _abiDecoder?: AbiDecoder; private _blockAndLogStreamerIfExists?: BlockAndLogStreamer; private _blockAndLogStreamIntervalIfExists?: NodeJS.Timer; private _filters: { [filterToken: string]: FilterObject }; @@ -41,10 +40,9 @@ export class ContractWrapper { }; private _onLogAddedSubscriptionToken: string | undefined; private _onLogRemovedSubscriptionToken: string | undefined; - constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder?: AbiDecoder) { + constructor(web3Wrapper: Web3Wrapper, networkId: number) { this._web3Wrapper = web3Wrapper; this._networkId = networkId; - this._abiDecoder = abiDecoder; this._filters = {}; this._filterCallbacks = {}; this._blockAndLogStreamerIfExists = undefined; @@ -102,10 +100,10 @@ export class ContractWrapper { protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( log: LogEntry, ): LogWithDecodedArgs<ArgsType> | RawLog { - if (_.isUndefined(this._abiDecoder)) { + if (_.isUndefined(this._web3Wrapper.abiDecoder)) { throw new Error(InternalZeroExError.NoAbiDecoder); } - const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log); + const logWithDecodedArgs = this._web3Wrapper.abiDecoder.tryToDecodeLogOrNoop(log); return logWithDecodedArgs; } protected async _getContractAbiAndAddressFromArtifactsAsync( diff --git a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts index 4a4da116b..f52dba2f1 100644 --- a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts @@ -21,8 +21,8 @@ export class EtherTokenWrapper extends ContractWrapper { [address: string]: EtherTokenContract; } = {}; private _tokenWrapper: TokenWrapper; - constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder: AbiDecoder, tokenWrapper: TokenWrapper) { - super(web3Wrapper, networkId, abiDecoder); + constructor(web3Wrapper: Web3Wrapper, networkId: number, tokenWrapper: TokenWrapper) { + super(web3Wrapper, networkId); this._tokenWrapper = tokenWrapper; } /** diff --git a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts index 6b5a75a9a..53f32f111 100644 --- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts @@ -87,12 +87,11 @@ export class ExchangeWrapper extends ContractWrapper { constructor( web3Wrapper: Web3Wrapper, networkId: number, - abiDecoder: AbiDecoder, tokenWrapper: TokenWrapper, contractAddressIfExists?: string, zrxContractAddressIfExists?: string, ) { - super(web3Wrapper, networkId, abiDecoder); + super(web3Wrapper, networkId); this._tokenWrapper = tokenWrapper; this._orderValidationUtils = new OrderValidationUtils(this); this._contractAddressIfExists = contractAddressIfExists; diff --git a/packages/0x.js/src/contract_wrappers/token_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_wrapper.ts index e350dfb92..5224d451c 100644 --- a/packages/0x.js/src/contract_wrappers/token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_wrapper.ts @@ -22,13 +22,8 @@ export class TokenWrapper extends ContractWrapper { public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; private _tokenContractsByAddress: { [address: string]: TokenContract }; private _tokenTransferProxyWrapper: TokenTransferProxyWrapper; - constructor( - web3Wrapper: Web3Wrapper, - networkId: number, - abiDecoder: AbiDecoder, - tokenTransferProxyWrapper: TokenTransferProxyWrapper, - ) { - super(web3Wrapper, networkId, abiDecoder); + constructor(web3Wrapper: Web3Wrapper, networkId: number, tokenTransferProxyWrapper: TokenTransferProxyWrapper) { + super(web3Wrapper, networkId); this._tokenContractsByAddress = {}; this._tokenTransferProxyWrapper = tokenTransferProxyWrapper; } diff --git a/packages/0x.js/src/globals.d.ts b/packages/0x.js/src/globals.d.ts index 3e8ea21bc..3cd6d91b1 100644 --- a/packages/0x.js/src/globals.d.ts +++ b/packages/0x.js/src/globals.d.ts @@ -1,50 +1,14 @@ declare module 'web3_beta'; -declare module 'chai-bignumber'; -declare module 'dirty-chai'; declare module 'request-promise-native'; -declare module 'web3-provider-engine'; -declare module 'web3-provider-engine/subproviders/rpc'; -declare module 'publish-release'; // semver-sort declarations declare module 'semver-sort' { const desc: (versions: string[]) => string[]; } -// HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion -// interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise -// disallow `namespace`, we disable tslint for the following. -/* tslint:disable */ -declare namespace Chai { - interface Assertion { - bignumber: Assertion; - // HACK: In order to comply with chai-as-promised we make eventually a `PromisedAssertion` not an `Assertion` - eventually: PromisedAssertion; - } -} -/* tslint:enable */ - declare module '*.json' { const json: any; /* tslint:disable */ export default json; /* tslint:enable */ } - -declare module 'ethereumjs-abi' { - const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; -} - -// truffle-hdwallet-provider declarations -declare module 'truffle-hdwallet-provider' { - import { JSONRPCRequestPayload, JSONRPCResponsePayload } from '@0xproject/types'; - import * as Web3 from 'web3'; - class HDWalletProvider implements Web3.Provider { - constructor(mnemonic: string, rpcUrl: string); - public sendAsync( - payload: JSONRPCRequestPayload, - callback: (err: Error, result: JSONRPCResponsePayload) => void, - ): void; - } - export = HDWalletProvider; -} diff --git a/packages/0x.js/src/globalsAugment.d.ts b/packages/0x.js/src/globalsAugment.d.ts deleted file mode 100644 index 3e2f2216b..000000000 --- a/packages/0x.js/src/globalsAugment.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { BigNumber } from '@0xproject/utils'; - -// HACK: This module overrides the Chai namespace so that we can use BigNumber types inside. -// Source: https://github.com/Microsoft/TypeScript/issues/7352#issuecomment-191547232 -declare global { - // HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion - // interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise - // disallow `namespace`, we disable tslint for the following. - /* tslint:disable */ - namespace Chai { - interface NumberComparer { - (value: number | BigNumber, message?: string): Assertion; - } - interface NumericComparison { - greaterThan: NumberComparer; - } - } - /* tslint:enable */ - interface DecodedLogArg { - name: string; - value: string | BigNumber; - } -} diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts index cd6016c5a..0aa0c33bd 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -69,7 +69,6 @@ export class OrderStateWatcher { private _callbackIfExists?: OnOrderStateChangeCallback; private _eventWatcher: EventWatcher; private _web3Wrapper: Web3Wrapper; - private _abiDecoder: AbiDecoder; private _expirationWatcher: ExpirationWatcher; private _orderStateUtils: OrderStateUtils; private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; @@ -78,12 +77,10 @@ export class OrderStateWatcher { private _cleanupJobIntervalIdIfExists?: NodeJS.Timer; constructor( web3Wrapper: Web3Wrapper, - abiDecoder: AbiDecoder, token: TokenWrapper, exchange: ExchangeWrapper, config?: OrderStateWatcherConfig, ) { - this._abiDecoder = abiDecoder; this._web3Wrapper = web3Wrapper; const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs; const stateLayer = @@ -230,7 +227,7 @@ export class OrderStateWatcher { return; } const log = logIfExists as LogEvent; // At this moment we are sure that no error occured and log is defined. - const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop<ContractEventArgs>(log); + const maybeDecodedLog = this._web3Wrapper.abiDecoder.tryToDecodeLogOrNoop<ContractEventArgs>(log); const isLogDecoded = !_.isUndefined(((maybeDecodedLog as any) as LogWithDecodedArgs<ContractEventArgs>).event); if (!isLogDecoded) { return; // noop diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 7f1f140ca..1f128d656 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -38,7 +38,6 @@ export enum ZeroExError { NoNetworkId = 'NO_NETWORK_ID', SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND', SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT', - TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT', } export enum InternalZeroExError { diff --git a/packages/0x.js/tsconfig.json b/packages/0x.js/tsconfig.json index ddf5a910e..e35816553 100644 --- a/packages/0x.js/tsconfig.json +++ b/packages/0x.js/tsconfig.json @@ -1,13 +1,7 @@ { "extends": "../../tsconfig", "compilerOptions": { - "outDir": "lib", - "noImplicitThis": false + "outDir": "lib" }, - "include": [ - "./src/**/*", - "./test/**/*", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts" - ] + "include": ["./src/**/*", "./test/**/*"] } diff --git a/packages/assert/src/globals.d.ts b/packages/assert/src/globals.d.ts index 5476b6bd8..94e63a32d 100644 --- a/packages/assert/src/globals.d.ts +++ b/packages/assert/src/globals.d.ts @@ -1,5 +1,3 @@ -declare module 'dirty-chai'; - declare module '*.json' { const json: any; /* tslint:disable */ diff --git a/packages/base-contract/CHANGELOG.json b/packages/base-contract/CHANGELOG.json index 3147006ea..3b8b22a57 100644 --- a/packages/base-contract/CHANGELOG.json +++ b/packages/base-contract/CHANGELOG.json @@ -1,5 +1,22 @@ [ { + "version": "0.1.0", + "changes": [ + { + "note": "Add tests for traversing ABI tree", + "pr": 485 + }, + { + "note": "Fix ABI tuples traversing", + "pr": 485 + }, + { + "note": "Fix ABI arrays traversing", + "pr": 485 + } + ] + }, + { "timestamp": 1522658513, "version": "0.0.6", "changes": [ diff --git a/packages/base-contract/coverage/.gitkeep b/packages/base-contract/coverage/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/packages/base-contract/coverage/.gitkeep diff --git a/packages/base-contract/package.json b/packages/base-contract/package.json index 9de6c01b6..8c98d1116 100644 --- a/packages/base-contract/package.json +++ b/packages/base-contract/package.json @@ -2,13 +2,18 @@ "name": "@0xproject/base-contract", "version": "0.0.6", "description": "0x Base TS contract", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", "scripts": { "build:watch": "tsc -w", "build": "tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts", "clean": "shx rm -rf lib scripts", - "lint": "tslint --project . 'src/**/*.ts'", + "test": "run-s clean build run_mocha", + "test:circleci": "yarn test:coverage", + "run_mocha": "mocha lib/test/**/*_test.js --bail --exit", + "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", + "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", + "lint": "tslint --project .", "manual:postpublish": "yarn build; node ./scripts/postpublish.js" }, "license": "Apache-2.0", @@ -27,10 +32,13 @@ "copyfiles": "^1.2.0", "npm-run-all": "^4.1.2", "shx": "^0.2.2", + "chai": "^4.0.1", + "mocha": "^4.0.1", "tslint": "5.8.0", "typescript": "2.7.1" }, "dependencies": { + "@0xproject/utils": "^0.4.3", "@0xproject/types": "^0.4.2", "@0xproject/typescript-typings": "^0.0.2", "@0xproject/web3-wrapper": "^0.4.0", diff --git a/packages/base-contract/src/index.ts b/packages/base-contract/src/index.ts index 961da8842..c8cbd7886 100644 --- a/packages/base-contract/src/index.ts +++ b/packages/base-contract/src/index.ts @@ -1,40 +1,31 @@ import { ContractAbi, DataItem, TxData, TxDataPayable } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as ethersContracts from 'ethers-contracts'; import * as _ from 'lodash'; +import { formatABIDataItem } from './utils'; + export class BaseContract { protected _ethersInterface: ethersContracts.Interface; protected _web3Wrapper: Web3Wrapper; public abi: ContractAbi; public address: string; - protected static _transformABIData( + protected static _formatABIDataItemList( abis: DataItem[], values: any[], - transformation: (type: string, value: any) => any, + formatter: (type: string, value: any) => any, ): any { - return _.map(values, (value: any, i: number) => - BaseContract._transformTypedData(abis[i].type, value, transformation), - ); + return _.map(values, (value: any, i: number) => formatABIDataItem(abis[i], value, formatter)); } protected static _lowercaseAddress(type: string, value: string): string { return type === 'address' ? value.toLowerCase() : value; } - protected static _bigNumberToString(type: string, value: string): string { - return _.isObject(value) && (value as any).isBigNumber ? value.toString() : value; + protected static _bigNumberToString(type: string, value: any): any { + return _.isObject(value) && value.isBigNumber ? value.toString() : value; } - private static _transformTypedData( - type: string, - values: any, - transformation: (type: string, value: any) => any, - ): any { - const trailingArrayRegex = /\[\d*\]$/; - if (type.match(trailingArrayRegex)) { - const arrayItemType = type.replace(trailingArrayRegex, ''); - return _.map(values, value => this._transformTypedData(arrayItemType, value, transformation)); - } else { - return transformation(type, values); - } + protected static _bnToBigNumber(type: string, value: any): any { + return _.isObject(value) && value._bn ? new BigNumber(value.toString()) : value; } protected async _applyDefaultsToTxDataAsync<T extends Partial<TxData | TxDataPayable>>( txData: T, diff --git a/packages/base-contract/src/utils.ts b/packages/base-contract/src/utils.ts new file mode 100644 index 000000000..4b86bb1ad --- /dev/null +++ b/packages/base-contract/src/utils.ts @@ -0,0 +1,25 @@ +import { DataItem } from '@0xproject/types'; +import * as _ from 'lodash'; + +// tslint:disable-next-line:completed-docs +export function formatABIDataItem(abi: DataItem, value: any, formatter: (type: string, value: any) => any): any { + const trailingArrayRegex = /\[\d*\]$/; + if (abi.type.match(trailingArrayRegex)) { + const arrayItemType = abi.type.replace(trailingArrayRegex, ''); + return _.map(value, val => { + const arrayItemAbi = { + ...abi, + type: arrayItemType, + }; + return formatABIDataItem(arrayItemAbi, val, formatter); + }); + } else if (abi.type === 'tuple') { + const formattedTuple: { [componentName: string]: DataItem } = {}; + _.forEach(abi.components, componentABI => { + formattedTuple[componentABI.name] = formatABIDataItem(componentABI, value[componentABI.name], formatter); + }); + return formattedTuple; + } else { + return formatter(abi.type, value); + } +} diff --git a/packages/base-contract/test/utils_test.ts b/packages/base-contract/test/utils_test.ts new file mode 100644 index 000000000..c083704f4 --- /dev/null +++ b/packages/base-contract/test/utils_test.ts @@ -0,0 +1,108 @@ +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { formatABIDataItem } from '../src/utils'; + +const { expect } = chai; + +describe('Utils tests', () => { + describe('#formatABIDataItem', () => { + it('correctly handles arrays', () => { + const calls: Array<{ type: string; value: any }> = []; + const abi = { + name: 'values', + type: 'uint256[]', + }; + const val = [1, 2, 3]; + const formatted = formatABIDataItem(abi, val, (type: string, value: any) => { + calls.push({ type, value }); + return value; // no-op + }); + expect(formatted).to.be.deep.equal(val); + expect(calls).to.be.deep.equal([ + { type: 'uint256', value: 1 }, + { type: 'uint256', value: 2 }, + { type: 'uint256', value: 3 }, + ]); + }); + it('correctly handles tuples', () => { + const calls: Array<{ type: string; value: any }> = []; + const abi = { + components: [ + { + name: 'to', + type: 'address', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + name: 'data', + type: 'tuple', + }; + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + const val = { to: ZERO_ADDRESS, amount: new BigNumber(1) }; + const formatted = formatABIDataItem(abi, val, (type: string, value: any) => { + calls.push({ type, value }); + return value; // no-op + }); + expect(formatted).to.be.deep.equal(val); + expect(calls).to.be.deep.equal([ + { + type: 'address', + value: val.to, + }, + { + type: 'uint256', + value: val.amount, + }, + ]); + }); + it('correctly handles nested arrays of tuples', () => { + const calls: Array<{ type: string; value: any }> = []; + const abi = { + components: [ + { + name: 'to', + type: 'address', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + name: 'data', + type: 'tuple[2][]', + }; + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + const val = [ + [{ to: ZERO_ADDRESS, amount: new BigNumber(1) }, { to: ZERO_ADDRESS, amount: new BigNumber(2) }], + ]; + const formatted = formatABIDataItem(abi, val, (type: string, value: any) => { + calls.push({ type, value }); + return value; // no-op + }); + expect(formatted).to.be.deep.equal(val); + expect(calls).to.be.deep.equal([ + { + type: 'address', + value: val[0][0].to, + }, + { + type: 'uint256', + value: val[0][0].amount, + }, + { + type: 'address', + value: val[0][1].to, + }, + { + type: 'uint256', + value: val[0][1].amount, + }, + ]); + }); + }); +}); diff --git a/packages/base-contract/tsconfig.json b/packages/base-contract/tsconfig.json index c56d255d5..8b4cd47a2 100644 --- a/packages/base-contract/tsconfig.json +++ b/packages/base-contract/tsconfig.json @@ -3,5 +3,5 @@ "compilerOptions": { "outDir": "lib" }, - "include": ["./src/**/*"] + "include": ["src/**/*", "test/**/*"] } diff --git a/packages/connect/src/globals.d.ts b/packages/connect/src/globals.d.ts index cb71dcdd1..dd659d094 100644 --- a/packages/connect/src/globals.d.ts +++ b/packages/connect/src/globals.d.ts @@ -1,5 +1,4 @@ declare module 'async-child-process'; -declare module 'dirty-chai'; declare module '*.json' { const value: any; diff --git a/packages/contract_templates/contract.handlebars b/packages/contract_templates/contract.handlebars index 08310c8f2..da2639f78 100644 --- a/packages/contract_templates/contract.handlebars +++ b/packages/contract_templates/contract.handlebars @@ -10,7 +10,6 @@ import { BigNumber, classUtils, promisify } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as ethersContracts from 'ethers-contracts'; import * as _ from 'lodash'; -import * as Web3 from 'web3'; {{#if events}} export type {{contractName}}ContractEventArgs = diff --git a/packages/contract_templates/partials/callAsync.handlebars b/packages/contract_templates/partials/callAsync.handlebars index e3fc2bdf9..8de69203e 100644 --- a/packages/contract_templates/partials/callAsync.handlebars +++ b/packages/contract_templates/partials/callAsync.handlebars @@ -4,9 +4,9 @@ async callAsync( callData: Partial<CallData> = {}, defaultBlock?: BlockParam, ): Promise<{{> return_type outputs=outputs}}> { - const self = this as {{contractName}}Contract; + const self = this as any as {{contractName}}Contract; const inputAbi = (_.find(self.abi, {name: '{{this.name}}'}) as MethodAbi).inputs; - [{{> params inputs=inputs}}] = BaseContract._transformABIData(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self)); + [{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self)); const encodedData = self._ethersInterface.functions.{{this.name}}( {{> params inputs=inputs}} ).data; @@ -19,7 +19,8 @@ async callAsync( const outputAbi = (_.find(self.abi, {name: '{{this.name}}'}) as MethodAbi).outputs; const outputParamsTypes = _.map(outputAbi, 'type'); let resultArray = ethersContracts.Interface.decodeParams(outputParamsTypes, rawCallResult) as any; - resultArray = BaseContract._transformABIData(outputAbi, resultArray, BaseContract._lowercaseAddress.bind(this)); + resultArray = BaseContract._formatABIDataItemList(outputAbi, resultArray, BaseContract._lowercaseAddress.bind(this)); + resultArray = BaseContract._formatABIDataItemList(outputAbi, resultArray, BaseContract._bnToBigNumber.bind(this)); return resultArray{{#singleReturnValue}}[0]{{/singleReturnValue}}; }, {{/hasReturnValue}} diff --git a/packages/contract_templates/partials/tx.handlebars b/packages/contract_templates/partials/tx.handlebars index f76de3a70..41ba6d3f7 100644 --- a/packages/contract_templates/partials/tx.handlebars +++ b/packages/contract_templates/partials/tx.handlebars @@ -8,9 +8,9 @@ public {{this.name}} = { txData: Partial<TxData> = {}, {{/this.payable}} ): Promise<string> { - const self = this as {{contractName}}Contract; + const self = this as any as {{contractName}}Contract; const inputAbi = (_.find(self.abi, {name: '{{this.name}}'}) as MethodAbi).inputs; - [{{> params inputs=inputs}}] = BaseContract._transformABIData(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self)); + [{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self)); const encodedData = self._ethersInterface.functions.{{this.name}}( {{> params inputs=inputs}} ).data @@ -31,9 +31,9 @@ public {{this.name}} = { {{> typed_params inputs=inputs}} txData: Partial<TxData> = {}, ): Promise<number> { - const self = this as {{contractName}}Contract; + const self = this as any as {{contractName}}Contract; const inputAbi = (_.find(self.abi, {name: '{{this.name}}'}) as MethodAbi).inputs; - [{{> params inputs=inputs}}] = BaseContract._transformABIData(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(this)); + [{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(this)); const encodedData = self._ethersInterface.functions.{{this.name}}( {{> params inputs=inputs}} ).data @@ -49,9 +49,9 @@ public {{this.name}} = { getABIEncodedTransactionData( {{> typed_params inputs=inputs}} ): string { - const self = this as {{contractName}}Contract; + const self = this as any as {{contractName}}Contract; const inputAbi = (_.find(self.abi, {name: '{{this.name}}'}) as MethodAbi).inputs; - [{{> params inputs=inputs}}] = BaseContract._transformABIData(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self)); + [{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self)); const abiEncodedTransactionData = self._ethersInterface.functions.{{this.name}}( {{> params inputs=inputs}} ).data diff --git a/packages/contracts/globals.d.ts b/packages/contracts/globals.d.ts index c6597054a..94e63a32d 100644 --- a/packages/contracts/globals.d.ts +++ b/packages/contracts/globals.d.ts @@ -1,35 +1,6 @@ -declare module 'chai-bignumber'; -declare module 'dirty-chai'; - -// HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion -// interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise -// disallow `namespace`, we disable tslint for the following. -/* tslint:disable */ -declare namespace Chai { - interface Assertion { - bignumber: Assertion; - } -} -/* tslint:enable */ - declare module '*.json' { const json: any; /* tslint:disable */ export default json; /* tslint:enable */ } - -declare module 'solc' { - export function compile(sources: any, optimizerEnabled: number, findImports: (importPath: string) => any): any; - export function setupMethods(solcBin: any): any; -} - -declare module 'web3-eth-abi' { - export function encodeParameters(typesArray: string[], parameters: any[]): string; -} - -declare module 'ethereumjs-abi' { - const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; - const soliditySHA256: (argTypes: string[], args: any[]) => Buffer; - const methodID: (name: string, types: string[]) => Buffer; -} diff --git a/packages/contracts/globalsAugment.d.ts b/packages/contracts/globalsAugment.d.ts deleted file mode 100644 index 9b16ce2ad..000000000 --- a/packages/contracts/globalsAugment.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { BigNumber } from '@0xproject/utils'; - -// HACK: This module overrides the Chai namespace so that we can use BigNumber types inside. -// Source: https://github.com/Microsoft/TypeScript/issues/7352#issuecomment-191547232 -declare global { - // HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion - // interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise - // disallow `namespace`, we disable tslint for the following. - /* tslint:disable */ - namespace Chai { - interface NumberComparer { - (value: number | BigNumber, message?: string): Assertion; - } - interface NumericComparison { - greaterThan: NumberComparer; - } - } - /* tslint:enable */ -} diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 9d7a6c87c..8ffe6e992 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -59,8 +59,6 @@ "shx": "^0.2.2", "solc": "^0.4.18", "tslint": "5.8.0", - "types-bn": "^0.0.1", - "types-ethereumjs-util": "0xProject/types-ethereumjs-util", "typescript": "2.7.1", "yargs": "^10.0.3" }, diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json index f32a3682a..717415073 100644 --- a/packages/contracts/tsconfig.json +++ b/packages/contracts/tsconfig.json @@ -4,17 +4,8 @@ "outDir": "lib", "baseUrl": ".", "declaration": false, - "noImplicitThis": false, "allowJs": true }, - "include": [ - "../../node_modules/types-ethereumjs-util/index.d.ts", - "../../node_modules/types-bn/index.d.ts", - "./globals.d.ts", - "./src/**/*", - "./util/**/*", - "./test/**/*", - "./migrations/**/*" - ], + "include": ["./globals.d.ts", "./src/**/*", "./util/**/*", "./test/**/*", "./migrations/**/*"], "exclude": ["./deploy/solc/solc_bin"] } diff --git a/packages/deployer/CHANGELOG.json b/packages/deployer/CHANGELOG.json index c06bbc887..4010250d1 100644 --- a/packages/deployer/CHANGELOG.json +++ b/packages/deployer/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "0.3.5", + "changes": [ + { + "note": "Don't try to write contract artifact if an error occured", + "pr": 485 + } + ] + }, + { "version": "0.3.4", "changes": [ { diff --git a/packages/deployer/solc_bin/.gitkeep b/packages/deployer/solc_bin/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/packages/deployer/solc_bin/.gitkeep diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts index 219a55c32..ba360cb57 100644 --- a/packages/deployer/src/compiler.ts +++ b/packages/deployer/src/compiler.ts @@ -45,7 +45,6 @@ export class Compiler { private _artifactsDir: string; // This get's set in the beggining of `compileAsync` function. It's not called from a constructor, but it's the only public method of that class and could as well be. private _contractSources!: ContractSources; - private _solcErrors: Set<string> = new Set(); private _specifiedContracts: Set<string> = new Set(); private _contractSourceData: ContractSourceData = {}; /** @@ -114,9 +113,6 @@ export class Compiler { for (const fileName of fileNames) { await this._compileContractAsync(fileName); } - this._solcErrors.forEach(errMsg => { - logUtils.log(errMsg); - }); } /** * Compiles contract and saves artifact to artifactsDir. @@ -179,10 +175,23 @@ export class Compiler { ); if (!_.isUndefined(compiled.errors)) { - _.forEach(compiled.errors, errMsg => { - const normalizedErrMsg = getNormalizedErrMsg(errMsg); - this._solcErrors.add(normalizedErrMsg); - }); + const SOLIDITY_WARNING_PREFIX = 'Warning'; + const isError = (errorOrWarning: string) => !errorOrWarning.includes(SOLIDITY_WARNING_PREFIX); + const isWarning = (errorOrWarning: string) => errorOrWarning.includes(SOLIDITY_WARNING_PREFIX); + const errors = _.filter(compiled.errors, isError); + const warnings = _.filter(compiled.errors, isWarning); + if (!_.isEmpty(errors)) { + errors.forEach(errMsg => { + const normalizedErrMsg = getNormalizedErrMsg(errMsg); + logUtils.log(normalizedErrMsg); + }); + process.exit(1); + } else { + warnings.forEach(errMsg => { + const normalizedErrMsg = getNormalizedErrMsg(errMsg); + logUtils.log(normalizedErrMsg); + }); + } } const contractName = path.basename(fileName, constants.SOLIDITY_FILE_EXTENSION); const contractIdentifier = `${fileName}:${contractName}`; diff --git a/packages/deployer/src/globals.d.ts b/packages/deployer/src/globals.d.ts index 5b0d495d5..94e63a32d 100644 --- a/packages/deployer/src/globals.d.ts +++ b/packages/deployer/src/globals.d.ts @@ -1,49 +1,3 @@ -declare module 'dirty-chai'; - -// tslint:disable:completed-docs -declare module 'solc' { - export interface ContractCompilationResult { - srcmap: string; - srcmapRuntime: string; - bytecode: string; - runtimeBytecode: string; - interface: string; - } - export interface CompilationResult { - errors: string[]; - contracts: { - [contractIdentifier: string]: ContractCompilationResult; - }; - sources: { - [sourceName: string]: { - AST: any; - }; - }; - sourceList: string[]; - } - export interface ImportContents { - contents: string; - } - export interface InputSources { - sources: { - [fileName: string]: string; - }; - } - export interface SolcInstance { - compile( - sources: InputSources, - optimizerEnabled: number, - findImports: (importPath: string) => ImportContents, - ): CompilationResult; - } - export function loadRemoteVersion(versionName: string, cb: (err: Error | null, res?: SolcInstance) => void): void; - export function setupMethods(solcBin: any): SolcInstance; -} - -declare module 'web3-eth-abi' { - export function encodeParameters(typesArray: string[], parameters: any[]): string; -} - declare module '*.json' { const json: any; /* tslint:disable */ diff --git a/packages/deployer/test/util/constants.ts b/packages/deployer/test/util/constants.ts index 5d3aab47c..5385b8d17 100644 --- a/packages/deployer/test/util/constants.ts +++ b/packages/deployer/test/util/constants.ts @@ -5,7 +5,7 @@ export const constants = { jsonrpcUrl: 'http://localhost:8545', optimizerEnabled: false, gasPrice: new BigNumber(20000000000), - timeoutMs: 20000, + timeoutMs: 30000, zrxTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498', tokenTransferProxyAddress: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', specifiedContracts: '*', diff --git a/packages/deployer/tsconfig.json b/packages/deployer/tsconfig.json index a4cbc37c5..63cbc75c3 100644 --- a/packages/deployer/tsconfig.json +++ b/packages/deployer/tsconfig.json @@ -4,10 +4,5 @@ "outDir": "lib", "strictFunctionTypes": false }, - "include": [ - "./src/**/*", - "./test/**/*", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts" - ] + "include": ["./src/**/*", "./test/**/*"] } diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index e6dd95bc4..f81c37fc2 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -37,8 +37,6 @@ "nyc": "^11.0.1", "shx": "^0.2.2", "tslint": "5.8.0", - "types-bn": "^0.0.1", - "types-ethereumjs-util": "0xProject/types-ethereumjs-util", "typescript": "2.7.1" }, "dependencies": { diff --git a/packages/dev-utils/src/globals.d.ts b/packages/dev-utils/src/globals.d.ts index 894e56c58..4fee73568 100644 --- a/packages/dev-utils/src/globals.d.ts +++ b/packages/dev-utils/src/globals.d.ts @@ -1,4 +1,3 @@ -declare module 'web3-provider-engine'; declare module 'web3-provider-engine/subproviders/rpc'; declare module '*.json' { diff --git a/packages/dev-utils/tsconfig.json b/packages/dev-utils/tsconfig.json index 7b93af0da..e35816553 100644 --- a/packages/dev-utils/tsconfig.json +++ b/packages/dev-utils/tsconfig.json @@ -3,10 +3,5 @@ "compilerOptions": { "outDir": "lib" }, - "include": [ - "./src/**/*", - "./test/**/*", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts" - ] + "include": ["./src/**/*", "./test/**/*"] } diff --git a/packages/json-schemas/src/globals.d.ts b/packages/json-schemas/src/globals.d.ts index 5476b6bd8..94e63a32d 100644 --- a/packages/json-schemas/src/globals.d.ts +++ b/packages/json-schemas/src/globals.d.ts @@ -1,5 +1,3 @@ -declare module 'dirty-chai'; - declare module '*.json' { const json: any; /* tslint:disable */ diff --git a/packages/metacoin/artifacts/Metacoin.json b/packages/metacoin/artifacts/Metacoin.json new file mode 100644 index 000000000..46c3ee71c --- /dev/null +++ b/packages/metacoin/artifacts/Metacoin.json @@ -0,0 +1,98 @@ +{ + "contract_name": "Metacoin", + "networks": { + "50": { + "solc_version": "0.4.21", + "keccak256": "0x2c3aa2e9dbef58abf57cecc148464d0852a83d7f30bbd2066f2a13b8bd3b1dd0", + "source_tree_hash": "0x2c3aa2e9dbef58abf57cecc148464d0852a83d7f30bbd2066f2a13b8bd3b1dd0", + "optimizer_enabled": false, + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "balances", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "components": [ + { + "name": "to", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transferData", + "type": "tuple" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } + ], + "bytecode": + "0x6060604052341561000f57600080fd5b6127106000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610406806100636000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806327e235e3146100515780632bd14bb914610087575b600080fd5b341561005c57600080fd5b610071600461006c9036906102b9565b6100bd565b60405161007e9190610344565b60405180910390f35b341561009257600080fd5b6100a760046100a29036906102e2565b6100d5565b6040516100b49190610329565b60405180910390f35b60006020528060005260406000206000915090505481565b600081602001516000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561012a5760009050610240565b81602001516000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508160200151600080846000015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816000015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84602001516040516102339190610344565b60405180910390a3600190505b919050565b600061025182356103a2565b905092915050565b60006040828403121561026b57600080fd5b610275604061035f565b9050600061028584828501610245565b6000830152506020610299848285016102a5565b60208301525092915050565b60006102b182356103c2565b905092915050565b6000602082840312156102cb57600080fd5b60006102d984828501610245565b91505092915050565b6000604082840312156102f457600080fd5b600061030284828501610259565b91505092915050565b6103148161038c565b82525050565b61032381610398565b82525050565b600060208201905061033e600083018461030b565b92915050565b6000602082019050610359600083018461031a565b92915050565b6000604051905081810181811067ffffffffffffffff8211171561038257600080fd5b8060405250919050565b60008115159050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60008190509190505600a265627a7a72305820d15828219194e8ddaa624e10f9c8823c05268d79753b4c60ef401fb4fe5f09dc6c6578706572696d656e74616cf50037", + "runtime_bytecode": + "0x60606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806327e235e3146100515780632bd14bb914610087575b600080fd5b341561005c57600080fd5b610071600461006c9036906102b9565b6100bd565b60405161007e9190610344565b60405180910390f35b341561009257600080fd5b6100a760046100a29036906102e2565b6100d5565b6040516100b49190610329565b60405180910390f35b60006020528060005260406000206000915090505481565b600081602001516000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561012a5760009050610240565b81602001516000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508160200151600080846000015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816000015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84602001516040516102339190610344565b60405180910390a3600190505b919050565b600061025182356103a2565b905092915050565b60006040828403121561026b57600080fd5b610275604061035f565b9050600061028584828501610245565b6000830152506020610299848285016102a5565b60208301525092915050565b60006102b182356103c2565b905092915050565b6000602082840312156102cb57600080fd5b60006102d984828501610245565b91505092915050565b6000604082840312156102f457600080fd5b600061030284828501610259565b91505092915050565b6103148161038c565b82525050565b61032381610398565b82525050565b600060208201905061033e600083018461030b565b92915050565b6000602082019050610359600083018461031a565b92915050565b6000604051905081810181811067ffffffffffffffff8211171561038257600080fd5b8060405250919050565b60008115159050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60008190509190505600a265627a7a72305820d15828219194e8ddaa624e10f9c8823c05268d79753b4c60ef401fb4fe5f09dc6c6578706572696d656e74616cf50037", + "updated_at": 1522318279735, + "source_map": "60:662:0:-;;;290:72;;;;;;;;350:5;327:8;:20;336:10;327:20;;;;;;;;;;;;;;;:28;;;;60:662;;;;;;", + "source_map_runtime": + "60:662:0:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;84:41;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;368:352;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;84:41;;;;;;;;;;;;;;;;;:::o;368:352::-;429:12;480;:19;;;457:8;:20;466:10;457:20;;;;;;;;;;;;;;;;:42;453:60;;;508:5;501:12;;;;453:60;547:12;:19;;;523:8;:20;532:10;523:20;;;;;;;;;;;;;;;;:43;;;;;;;;;;;605:12;:19;;;576:8;:25;585:12;:15;;;576:25;;;;;;;;;;;;;;;;:48;;;;;;;;;;;655:12;:15;;;634:58;;643:10;634:58;;;672:12;:19;;;634:58;;;;;;;;;;;;;;;709:4;702:11;;368:352;;;;:::o;5:118:-1:-;;72:46;110:6;97:20;72:46;;;63:55;;57:66;;;;;165:469;;282:4;270:9;265:3;261:19;257:30;254:2;;;300:1;297;290:12;254:2;318:20;333:4;318:20;;;309:29;;386:1;417:49;462:3;453:6;442:9;438:22;417:49;;;411:3;404:5;400:15;393:74;348:130;530:2;563:49;608:3;599:6;588:9;584:22;563:49;;;556:4;549:5;545:16;538:75;488:136;248:386;;;;;641:118;;708:46;746:6;733:20;708:46;;;699:55;;693:66;;;;;766:241;;870:2;858:9;849:7;845:23;841:32;838:2;;;886:1;883;876:12;838:2;921:1;938:53;983:7;974:6;963:9;959:22;938:53;;;928:63;;900:97;832:175;;;;;1014:297;;1146:2;1134:9;1125:7;1121:23;1117:32;1114:2;;;1162:1;1159;1152:12;1114:2;1197:1;1214:81;1287:7;1278:6;1267:9;1263:22;1214:81;;;1204:91;;1176:125;1108:203;;;;;1318:101;1385:28;1407:5;1385:28;;;1380:3;1373:41;1367:52;;;1426:110;1499:31;1524:5;1499:31;;;1494:3;1487:44;1481:55;;;1543:181;;1645:2;1634:9;1630:18;1622:26;;1659:55;1711:1;1700:9;1696:17;1687:6;1659:55;;;1616:108;;;;;1731:193;;1839:2;1828:9;1824:18;1816:26;;1853:61;1911:1;1900:9;1896:17;1887:6;1853:61;;;1810:114;;;;;1931:256;;1993:2;1987:9;1977:19;;2031:4;2023:6;2019:17;2130:6;2118:10;2115:22;2094:18;2082:10;2079:34;2076:62;2073:2;;;2151:1;2148;2141:12;2073:2;2171:10;2167:2;2160:22;1971:216;;;;;2194:92;;2274:5;2267:13;2260:21;2249:32;;2243:43;;;;2293:79;;2362:5;2351:16;;2345:27;;;;2379:128;;2459:42;2452:5;2448:54;2437:65;;2431:76;;;;2514:79;;2583:5;2572:16;;2566:27;;;", + "sources": ["Metacoin.sol"] + } + } +} diff --git a/packages/metacoin/contracts/Metacoin.sol b/packages/metacoin/contracts/Metacoin.sol new file mode 100644 index 000000000..6b6814b21 --- /dev/null +++ b/packages/metacoin/contracts/Metacoin.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.4.21; +pragma experimental ABIEncoderV2; + +contract Metacoin { + mapping (address => uint) public balances; + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + + struct TransferData { + address to; + uint256 amount; + } + + function Metacoin() public { + balances[msg.sender] = 10000; + } + + function transfer(TransferData transferData) public returns (bool success) { + if (balances[msg.sender] < transferData.amount) return false; + balances[msg.sender] -= transferData.amount; + balances[transferData.to] += transferData.amount; + Transfer(msg.sender, transferData.to, transferData.amount); + return true; + } +} diff --git a/packages/metacoin/coverage/.gitkeep b/packages/metacoin/coverage/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/packages/metacoin/coverage/.gitkeep diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json new file mode 100644 index 000000000..9e9ff67ca --- /dev/null +++ b/packages/metacoin/package.json @@ -0,0 +1,50 @@ +{ + "name": "@0xproject/metacoin", + "version": "0.0.1", + "private": true, + "description": "Example solidity project using 0x dev tools", + "scripts": { + "build:watch": "tsc -w", + "lint": "tslint --project .", + "clean": "shx rm -rf lib", + "prebuild": "run-s clean generate_contract_wrappers copy_artifacts", + "copy_artifacts": "copyfiles './artifacts/**/*' './contracts/**/*' ./lib", + "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'", + "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" + }, + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@0xproject/abi-gen": "^0.2.7", + "@0xproject/base-contract": "^0.0.6", + "@0xproject/deployer": "^0.3.3", + "@0xproject/types": "^0.4.1", + "@0xproject/tslint-config": "^0.4.12", + "@0xproject/sol-cov": "^0.0.5", + "@0xproject/subproviders": "^0.8.2", + "@0xproject/web3-wrapper": "^0.4.0", + "@0xproject/utils": "^0.4.3", + "ethers-contracts": "^2.2.1", + "lodash": "^4.17.4", + "web3-provider-engine": "^13.0.1" + }, + "devDependencies": { + "@0xproject/dev-utils": "^0.3.2", + "npm-run-all": "^4.1.2", + "dirty-chai": "^2.0.1", + "shx": "^0.2.2", + "tslint": "5.8.0", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-bignumber": "^2.0.1", + "typescript": "2.7.1" + } +} diff --git a/packages/metacoin/test/global_hooks.ts b/packages/metacoin/test/global_hooks.ts new file mode 100644 index 000000000..509dc6837 --- /dev/null +++ b/packages/metacoin/test/global_hooks.ts @@ -0,0 +1,10 @@ +import { env, EnvVars } from '@0xproject/dev-utils'; + +import { coverage } from './utils/coverage'; + +after('generate coverage report', async () => { + if (env.parseBoolean(EnvVars.SolidityCoverage)) { + const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); + await coverageSubprovider.writeCoverageAsync(); + } +}); diff --git a/packages/metacoin/test/metacoin_test.ts b/packages/metacoin/test/metacoin_test.ts new file mode 100644 index 000000000..f2b396ac2 --- /dev/null +++ b/packages/metacoin/test/metacoin_test.ts @@ -0,0 +1,62 @@ +import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils'; +import { LogWithDecodedArgs } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as chai from 'chai'; + +import { MetacoinContract, TransferContractEventArgs } from '../src/contract_wrappers/metacoin'; + +import { chaiSetup } from './utils/chai_setup'; +import { deployer } from './utils/deployer'; +import { web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const { expect } = chai; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('Metacoin', () => { + let metacoin: MetacoinContract; + const ownerAddress = devConstants.TESTRPC_FIRST_ADDRESS; + const INITIAL_BALANCE = new BigNumber(10000); + before(async () => { + const metacoinInstance = await deployer.deployAsync('Metacoin'); + web3Wrapper.abiDecoder.addABI(metacoinInstance.abi); + metacoin = new MetacoinContract(web3Wrapper, metacoinInstance.abi, metacoinInstance.address); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#constructor', () => { + it(`should initialy give ${INITIAL_BALANCE} tokens to the creator`, async () => { + const balance = await metacoin.balances.callAsync(ownerAddress); + expect(balance).to.be.bignumber.equal(INITIAL_BALANCE); + }); + }); + describe('#transfer', () => { + it(`should successfully transfer tokens`, async () => { + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + const amount = INITIAL_BALANCE.div(2); + const oldBalance = await metacoin.balances.callAsync(ZERO_ADDRESS); + expect(oldBalance).to.be.bignumber.equal(0); + const txHash = await metacoin.transfer.sendTransactionAsync( + { + to: ZERO_ADDRESS, + amount, + }, + { from: devConstants.TESTRPC_FIRST_ADDRESS }, + ); + const txReceipt = await web3Wrapper.awaitTransactionMinedAsync(txHash); + const transferLogs = txReceipt.logs[0] as LogWithDecodedArgs<TransferContractEventArgs>; + expect(transferLogs.args).to.be.deep.equal({ + _to: ZERO_ADDRESS, + _from: devConstants.TESTRPC_FIRST_ADDRESS, + _value: amount, + }); + const newBalance = await metacoin.balances.callAsync(ZERO_ADDRESS); + expect(newBalance).to.be.bignumber.equal(amount); + }); + }); +}); diff --git a/packages/metacoin/test/utils/chai_setup.ts b/packages/metacoin/test/utils/chai_setup.ts new file mode 100644 index 000000000..078edd309 --- /dev/null +++ b/packages/metacoin/test/utils/chai_setup.ts @@ -0,0 +1,13 @@ +import * as chai from 'chai'; +import chaiAsPromised = require('chai-as-promised'); +import ChaiBigNumber = require('chai-bignumber'); +import * as dirtyChai from 'dirty-chai'; + +export const chaiSetup = { + configure() { + chai.config.includeStack = true; + chai.use(ChaiBigNumber()); + chai.use(dirtyChai); + chai.use(chaiAsPromised); + }, +}; diff --git a/packages/metacoin/test/utils/config.ts b/packages/metacoin/test/utils/config.ts new file mode 100644 index 000000000..d3a830754 --- /dev/null +++ b/packages/metacoin/test/utils/config.ts @@ -0,0 +1,9 @@ +import * as path from 'path'; + +export const config = { + networkId: 50, + artifactsDir: path.resolve(__dirname, '../../artifacts'), + contractsDir: path.resolve(__dirname, '../../contracts'), + ganacheLogFile: 'ganache.log', + mnemonic: 'concert load couple harbor equip island argue ramp clarify fence smart topic', +}; diff --git a/packages/metacoin/test/utils/coverage.ts b/packages/metacoin/test/utils/coverage.ts new file mode 100644 index 000000000..6b249384f --- /dev/null +++ b/packages/metacoin/test/utils/coverage.ts @@ -0,0 +1,20 @@ +import { devConstants } from '@0xproject/dev-utils'; +import { CoverageSubprovider } from '@0xproject/sol-cov'; +import * as _ from 'lodash'; + +import { config } from './config'; + +let coverageSubprovider: CoverageSubprovider; + +export const coverage = { + getCoverageSubproviderSingleton(): CoverageSubprovider { + if (_.isUndefined(coverageSubprovider)) { + coverageSubprovider = coverage._getCoverageSubprovider(); + } + return coverageSubprovider; + }, + _getCoverageSubprovider(): CoverageSubprovider { + const defaultFromAddress = devConstants.TESTRPC_FIRST_ADDRESS; + return new CoverageSubprovider(config.artifactsDir, config.contractsDir, config.networkId, defaultFromAddress); + }, +}; diff --git a/packages/metacoin/test/utils/deployer.ts b/packages/metacoin/test/utils/deployer.ts new file mode 100644 index 000000000..7916c8541 --- /dev/null +++ b/packages/metacoin/test/utils/deployer.ts @@ -0,0 +1,17 @@ +import { Deployer } from '@0xproject/deployer'; +import { devConstants } from '@0xproject/dev-utils'; +import * as path from 'path'; + +import { config } from './config'; +import { web3Wrapper } from './web3_wrapper'; + +const deployerOpts = { + web3Provider: web3Wrapper.getProvider(), + artifactsDir: config.artifactsDir, + networkId: config.networkId, + defaults: { + from: devConstants.TESTRPC_FIRST_ADDRESS, + }, +}; + +export const deployer = new Deployer(deployerOpts); diff --git a/packages/metacoin/test/utils/web3_wrapper.ts b/packages/metacoin/test/utils/web3_wrapper.ts new file mode 100644 index 000000000..23bd62b93 --- /dev/null +++ b/packages/metacoin/test/utils/web3_wrapper.ts @@ -0,0 +1,30 @@ +import { env, EnvVars } from '@0xproject/dev-utils'; +import { GanacheSubprovider } from '@0xproject/subproviders'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import ProviderEngine = require('web3-provider-engine'); + +import { config } from './config'; +import { coverage } from './coverage'; + +export const web3Provider = new ProviderEngine(); +const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage); +if (isCoverageEnabled) { + web3Provider.addProvider(coverage.getCoverageSubproviderSingleton()); +} +web3Provider.addProvider( + new GanacheSubprovider({ + logger: { + log: (arg: any) => { + fs.appendFileSync(config.ganacheLogFile, `${arg}\n`); + }, + }, + verbose: env.parseBoolean(EnvVars.SolidityCoverage), + networkId: config.networkId, + mnemonic: config.mnemonic, + }), +); +web3Provider.start(); + +export const web3Wrapper = new Web3Wrapper(web3Provider); diff --git a/packages/metacoin/tsconfig.json b/packages/metacoin/tsconfig.json new file mode 100644 index 000000000..8b4cd47a2 --- /dev/null +++ b/packages/metacoin/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["src/**/*", "test/**/*"] +} diff --git a/packages/metacoin/tslint.json b/packages/metacoin/tslint.json new file mode 100644 index 000000000..ffaefe83a --- /dev/null +++ b/packages/metacoin/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0xproject/tslint-config"] +} diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index c453517a1..e270ff6db 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -13,7 +13,7 @@ "convert_changelogs": "run-s build script:convert_changelogs", "script:deps_versions": "node ./lib/deps_versions.js", "script:publish": "IS_DRY_RUN=true node ./lib/publish.js", - "script:convert_changelogs": "node ./lib/convert_changelogs.js", + "script:convert_changelogs": "node ./lib/convert_changelogs.js" }, "repository": { "type": "git", diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json index 2942a2ac0..fd9ebe905 100644 --- a/packages/sol-cov/package.json +++ b/packages/sol-cov/package.json @@ -51,7 +51,7 @@ "lodash": "^4.17.4", "semaphore-async-await": "^1.5.1", "solidity-coverage": "^0.4.10", - "solidity-parser-antlr": "^0.2.7", + "solidity-parser-antlr": "^0.2.8", "solidity-parser-sc": "^0.4.4" }, "devDependencies": { diff --git a/packages/sol-cov/src/globals.d.ts b/packages/sol-cov/src/globals.d.ts index 0ee0f394c..368e908b4 100644 --- a/packages/sol-cov/src/globals.d.ts +++ b/packages/sol-cov/src/globals.d.ts @@ -1,5 +1,3 @@ -declare module 'dirty-chai'; - // tslint:disable:completed-docs declare module '*.json' { const json: any; diff --git a/packages/sol-cov/tsconfig.json b/packages/sol-cov/tsconfig.json index 7b93af0da..e35816553 100644 --- a/packages/sol-cov/tsconfig.json +++ b/packages/sol-cov/tsconfig.json @@ -3,10 +3,5 @@ "compilerOptions": { "outDir": "lib" }, - "include": [ - "./src/**/*", - "./test/**/*", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts" - ] + "include": ["./src/**/*", "./test/**/*"] } diff --git a/packages/sra-report/src/globals.d.ts b/packages/sra-report/src/globals.d.ts index 817d1534b..03b3205c0 100644 --- a/packages/sra-report/src/globals.d.ts +++ b/packages/sra-report/src/globals.d.ts @@ -1,5 +1,3 @@ -declare module 'dirty-chai'; - declare module 'newman' { export interface NewmanRunSummary { run: NewmanRun; diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index ad7746f5d..4532d3caf 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -70,8 +70,6 @@ "shx": "^0.2.2", "tslint": "5.8.0", "typedoc": "0xProject/typedoc", - "types-bn": "^0.0.1", - "types-ethereumjs-util": "0xProject/types-ethereumjs-util", "typescript": "2.7.1", "webpack": "^3.1.0" }, diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts index ebc4efe86..916fed807 100644 --- a/packages/subproviders/src/globals.d.ts +++ b/packages/subproviders/src/globals.d.ts @@ -1,4 +1,3 @@ -declare module 'dirty-chai'; declare module 'es6-promisify'; // tslint:disable:max-classes-per-file @@ -7,19 +6,6 @@ declare module 'es6-promisify'; // tslint:disable:completed-docs // Ethereumjs-tx declarations -declare module 'ethereumjs-tx' { - class EthereumTx { - public raw: Buffer[]; - public r: Buffer; - public s: Buffer; - public v: Buffer; - public nonce: Buffer; - public serialize(): Buffer; - public getSenderAddress(): Buffer; - constructor(txParams: any); - } - export = EthereumTx; -} // Ledgerco declarations interface ECSignatureString { @@ -84,17 +70,6 @@ declare module 'web3-provider-engine/subproviders/rpc' { } export = RpcSubprovider; } -declare module 'web3-provider-engine' { - class Web3ProviderEngine { - public on(event: string, handler: () => void): void; - public send(payload: any): void; - public sendAsync(payload: any, callback: (error: any, response: any) => void): void; - public addProvider(provider: any): void; - public start(): void; - public stop(): void; - } - export = Web3ProviderEngine; -} declare module 'web3-provider-engine/util/rpc-cache-utils' { class ProviderEngineRpcUtils { public static blockTagForPayload(payload: any): string | null; diff --git a/packages/subproviders/tsconfig.json b/packages/subproviders/tsconfig.json index 7b93af0da..e35816553 100644 --- a/packages/subproviders/tsconfig.json +++ b/packages/subproviders/tsconfig.json @@ -3,10 +3,5 @@ "compilerOptions": { "outDir": "lib" }, - "include": [ - "./src/**/*", - "./test/**/*", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts" - ] + "include": ["./src/**/*", "./test/**/*"] } diff --git a/packages/testnet-faucets/package.json b/packages/testnet-faucets/package.json index 787476a85..967b1cd99 100644 --- a/packages/testnet-faucets/package.json +++ b/packages/testnet-faucets/package.json @@ -39,8 +39,6 @@ "shx": "^0.2.2", "source-map-loader": "^0.1.6", "tslint": "5.8.0", - "types-bn": "^0.0.1", - "types-ethereumjs-util": "0xProject/types-ethereumjs-util", "typescript": "2.7.1", "webpack": "^3.1.0", "webpack-node-externals": "^1.6.0" diff --git a/packages/testnet-faucets/src/ts/global.d.ts b/packages/testnet-faucets/src/ts/global.d.ts index 41a2f3a8a..65e9fb632 100644 --- a/packages/testnet-faucets/src/ts/global.d.ts +++ b/packages/testnet-faucets/src/ts/global.d.ts @@ -9,31 +9,3 @@ declare module '*.json' { export default json; /* tslint:enable */ } - -// Ethereumjs-tx declarations -declare module 'ethereumjs-tx' { - class EthereumTx { - public raw: Buffer[]; - public r: Buffer; - public s: Buffer; - public v: Buffer; - public serialize(): Buffer; - public sign(buffer: Buffer): void; - constructor(txParams: any); - } - export = EthereumTx; -} - -/* tslint:disable */ -declare module 'web3-provider-engine' { - class Web3ProviderEngine { - public on(event: string, handler: () => void): void; - public send(payload: any): void; - public sendAsync(payload: any, callback: (error: any, response: any) => void): void; - public addProvider(provider: any): void; - public start(): void; - public stop(): void; - } - export = Web3ProviderEngine; -} -/* tslint:enable */ diff --git a/packages/testnet-faucets/tsconfig.json b/packages/testnet-faucets/tsconfig.json index b0e7ba00c..d7e89a1e2 100644 --- a/packages/testnet-faucets/tsconfig.json +++ b/packages/testnet-faucets/tsconfig.json @@ -4,9 +4,5 @@ "outDir": "lib", "strictPropertyInitialization": false }, - "include": [ - "./src/ts/**/*", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts" - ] + "include": ["./src/ts/**/*"] } diff --git a/packages/types/CHANGELOG.json b/packages/types/CHANGELOG.json index c70d4cadc..998da0879 100644 --- a/packages/types/CHANGELOG.json +++ b/packages/types/CHANGELOG.json @@ -1,5 +1,9 @@ [ { + "version": "0.5.0", + "changes": [{ "note": "Make `DataItem.components` optional", "pr": 485 }] + }, + { "timestamp": 1522658513, "version": "0.4.2", "changes": [ diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 57b14e230..419611695 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -45,7 +45,7 @@ export interface EventAbi { export interface DataItem { name: string; type: string; - components: DataItem[]; + components?: DataItem[]; } export type OpCode = string; diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index 5ec2db5e0..9f1937b22 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -4,5 +4,5 @@ "typeRoots": ["node_modules/@types"], "outDir": "lib" }, - "include": ["./src/**/*"] + "include": ["src/**/*"] } diff --git a/packages/typescript-typings/types/bn.js/index.d.ts b/packages/typescript-typings/types/bn.js/index.d.ts new file mode 100644 index 000000000..f59b74bad --- /dev/null +++ b/packages/typescript-typings/types/bn.js/index.d.ts @@ -0,0 +1,57 @@ +declare module 'bn.js' { + import { Buffer } from 'buffer'; + + type Endianness = 'le' | 'be'; + + class BN { + constructor(num: number | string | number[] | Buffer, base?: number, endian?: Endianness); + public clone(): BN; + public toString(base?: number, length?: number): string; + public toNumber(): number; + public toJSON(): string; + public toArray(endian?: Endianness, length?: number): number[]; + public toBuffer(endian?: Endianness, length?: number): Buffer; + public bitLength(): number; + public zeroBits(): number; + public byteLength(): number; + public isNeg(): boolean; + public isEven(): boolean; + public isOdd(): boolean; + public isZero(): boolean; + public cmp(b: any): number; + public lt(b: any): boolean; + public lte(b: any): boolean; + public gt(b: any): boolean; + public gte(b: any): boolean; + public eq(b: any): boolean; + public isBN(b: any): boolean; + + public neg(): BN; + public abs(): BN; + public add(b: BN): BN; + public sub(b: BN): BN; + public mul(b: BN): BN; + public sqr(): BN; + public pow(b: BN): BN; + public div(b: BN): BN; + public mod(b: BN): BN; + public divRound(b: BN): BN; + + public or(b: BN): BN; + public and(b: BN): BN; + public xor(b: BN): BN; + public setn(b: number): BN; + public shln(b: number): BN; + public shrn(b: number): BN; + public testn(b: number): boolean; + public maskn(b: number): BN; + public bincn(b: number): BN; + public notn(w: number): BN; + + public gcd(b: BN): BN; + public egcd(b: BN): { a: BN; b: BN; gcd: BN }; + public invm(b: BN): BN; + } + + export = BN; +} diff --git a/packages/typescript-typings/types/chai-bignumber/index.d.ts b/packages/typescript-typings/types/chai-bignumber/index.d.ts new file mode 100644 index 000000000..802b69795 --- /dev/null +++ b/packages/typescript-typings/types/chai-bignumber/index.d.ts @@ -0,0 +1 @@ +declare module 'chai-bignumber'; diff --git a/packages/typescript-typings/types/chai/index.d.ts b/packages/typescript-typings/types/chai/index.d.ts index 8b3e4c079..3bde1f27c 100644 --- a/packages/typescript-typings/types/chai/index.d.ts +++ b/packages/typescript-typings/types/chai/index.d.ts @@ -114,6 +114,9 @@ declare namespace Chai { extensible: Assertion; sealed: Assertion; frozen: Assertion; + bignumber: Assertion; + // HACK: In order to comply with chai-as-promised we make eventually a `PromisedAssertion` not an `Assertion` + eventually: PromisedAssertion; oneOf(list: any[], message?: string): Assertion; } @@ -148,7 +151,7 @@ declare namespace Chai { } interface NumberComparer { - (value: number, message?: string): Assertion; + (value: number | object, message?: string): Assertion; } interface TypeComparison { diff --git a/packages/typescript-typings/types/dirty-chai/index.d.ts b/packages/typescript-typings/types/dirty-chai/index.d.ts new file mode 100644 index 000000000..91ed2021e --- /dev/null +++ b/packages/typescript-typings/types/dirty-chai/index.d.ts @@ -0,0 +1 @@ +declare module 'dirty-chai'; diff --git a/packages/typescript-typings/types/ethereumjs-abi/index.d.ts b/packages/typescript-typings/types/ethereumjs-abi/index.d.ts new file mode 100644 index 000000000..e3d660a4a --- /dev/null +++ b/packages/typescript-typings/types/ethereumjs-abi/index.d.ts @@ -0,0 +1,5 @@ +declare module 'ethereumjs-abi' { + const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; + const soliditySHA256: (argTypes: string[], args: any[]) => Buffer; + const methodID: (name: string, types: string[]) => Buffer; +} diff --git a/packages/typescript-typings/types/ethereumjs-tx/index.d.ts b/packages/typescript-typings/types/ethereumjs-tx/index.d.ts new file mode 100644 index 000000000..1b99d06d9 --- /dev/null +++ b/packages/typescript-typings/types/ethereumjs-tx/index.d.ts @@ -0,0 +1,14 @@ +declare module 'ethereumjs-tx' { + class EthereumTx { + public raw: Buffer[]; + public r: Buffer; + public s: Buffer; + public v: Buffer; + public nonce: Buffer; + public serialize(): Buffer; + public sign(buffer: Buffer): void; + public getSenderAddress(): Buffer; + constructor(txParams: any); + } + export = EthereumTx; +} diff --git a/packages/typescript-typings/types/ethereumjs-util/index.d.ts b/packages/typescript-typings/types/ethereumjs-util/index.d.ts new file mode 100644 index 000000000..fae55c418 --- /dev/null +++ b/packages/typescript-typings/types/ethereumjs-util/index.d.ts @@ -0,0 +1,101 @@ +declare module 'ethereumjs-util' { + import { Buffer } from 'buffer'; + import BN = require('bn.js'); + + interface Signature { + v: number; + r: Buffer; + s: Buffer; + } + + export const MAX_INTEGER: BN; + + export const TWO_POW256: BN; + + export const SHA3_NULL_S: string; + + export const SHA3_NULL: Buffer; + + export const SHA3_RLP_ARRAY_S: string; + + export const SHA3_RLP_ARRAY: Buffer; + + export const SHA3_RLP_S: string; + + export const SHA3_RLP: Buffer; + + export function zeros(bytes: number): Buffer; + + export function setLength(msg: Buffer, length: number, right: boolean): Buffer; + export function setLength(msg: number[], length: number, right: boolean): number[]; + + export function setLengthLeft(msg: Buffer, length: number, right?: boolean): Buffer; + export function setLengthLeft(msg: number[], length: number, right?: boolean): number[]; + + export function setLengthRight(msg: Buffer, length: number): Buffer; + export function setLengthRight(msg: number[], length: number): number[]; + + export function unpad(a: Buffer): Buffer; + export function unpad(a: number[]): number[]; + export function unpad(a: string): string; + + export function toBuffer(v: any): Buffer; + + export function bufferToInt(buf: Buffer): number; + + export function bufferToHex(buf: Buffer): string; + + export function fromSigned(num: Buffer): BN; + + export function toUnsigned(num: BN): Buffer; + + export function sha3(a: Buffer | string | number | number[], bits?: number): Buffer; + + export function sha256(a: Buffer | string | number | number[]): Buffer; + + export function ripemd160(a: Buffer | string | number | number[], padded?: boolean): Buffer; + + export function rlphash(a: Buffer | string | number | number[]): Buffer; + + export function isValidPrivate(privateKey: Buffer): boolean; + + export function isValidPublic(publicKey: Buffer, sanitize?: boolean): boolean; + + export function pubToAddress(publicKey: Buffer, sanitize?: boolean): Buffer; + export function publicToAddress(publicKey: Buffer, sanitize?: boolean): Buffer; + + export function privateToPublic(privateKey: Buffer): Buffer; + + export function importPublic(publicKey: Buffer): Buffer; + + export function ecsign(message: Buffer, privateKey: Buffer): Signature; + + export function hashPersonalMessage(message: Buffer | string): Buffer; + + export function ecrecover(msgHash: Buffer, v: number, r: Buffer, s: Buffer): Buffer; + + export function toRpcSig(v: number, r: Buffer, s: Buffer): string; + + export function fromRpcSig(sig: string): Signature; + + export function privateToAddress(privateKey: Buffer): Buffer; + + export function isValidAddress(address: string): boolean; + + export function toChecksumAddress(address: string): string; + + export function isValidChecksumAddress(address: string): boolean; + + export function generateAddress(from: Buffer | string, nonce: number | string | number[] | Buffer): Buffer; + + export function isPrecompiled(address: Buffer | string): boolean; + + export function addHexPrefix(str: string): string; + + export function stripHexPrefix(str: string): string; + + export function isValidSignature(v: number, r: Buffer | string, s: Buffer | string, homestead?: boolean): boolean; + + export function baToJSON(ba: Buffer): string; + export function baToJSON(ba: any[]): string[]; +} diff --git a/packages/typescript-typings/types/solc/index.d.ts b/packages/typescript-typings/types/solc/index.d.ts new file mode 100644 index 000000000..fcef24b9f --- /dev/null +++ b/packages/typescript-typings/types/solc/index.d.ts @@ -0,0 +1,38 @@ +declare module 'solc' { + export interface ContractCompilationResult { + srcmap: string; + srcmapRuntime: string; + bytecode: string; + runtimeBytecode: string; + interface: string; + } + export interface CompilationResult { + errors: string[]; + contracts: { + [contractIdentifier: string]: ContractCompilationResult; + }; + sources: { + [sourceName: string]: { + AST: any; + }; + }; + sourceList: string[]; + } + export interface ImportContents { + contents: string; + } + export interface InputSources { + sources: { + [fileName: string]: string; + }; + } + export interface SolcInstance { + compile( + sources: InputSources, + optimizerEnabled: number, + findImports: (importPath: string) => ImportContents, + ): CompilationResult; + } + export function loadRemoteVersion(versionName: string, cb: (err: Error | null, res?: SolcInstance) => void): void; + export function setupMethods(solcBin: any): SolcInstance; +} diff --git a/packages/typescript-typings/types/truffle-hdwalet-provider/index.d.ts b/packages/typescript-typings/types/truffle-hdwalet-provider/index.d.ts new file mode 100644 index 000000000..f2b002233 --- /dev/null +++ b/packages/typescript-typings/types/truffle-hdwalet-provider/index.d.ts @@ -0,0 +1,12 @@ +declare module 'truffle-hdwallet-provider' { + import { JSONRPCRequestPayload, JSONRPCResponsePayload } from '@0xproject/types'; + import * as Web3 from 'web3'; + class HDWalletProvider implements Web3.Provider { + constructor(mnemonic: string, rpcUrl: string); + public sendAsync( + payload: JSONRPCRequestPayload, + callback: (err: Error, result: JSONRPCResponsePayload) => void, + ): void; + } + export = HDWalletProvider; +} diff --git a/packages/typescript-typings/types/web3-eth-abi/index.d.ts b/packages/typescript-typings/types/web3-eth-abi/index.d.ts new file mode 100644 index 000000000..5d2f46e04 --- /dev/null +++ b/packages/typescript-typings/types/web3-eth-abi/index.d.ts @@ -0,0 +1,3 @@ +declare module 'web3-eth-abi' { + export function encodeParameters(typesArray: string[], parameters: any[]): string; +} diff --git a/packages/typescript-typings/types/web3-provider-engine/index.d.ts b/packages/typescript-typings/types/web3-provider-engine/index.d.ts new file mode 100644 index 000000000..f30b06873 --- /dev/null +++ b/packages/typescript-typings/types/web3-provider-engine/index.d.ts @@ -0,0 +1,11 @@ +declare module 'web3-provider-engine' { + class Web3ProviderEngine { + public on(event: string, handler: () => void): void; + public send(payload: any): void; + public sendAsync(payload: any, callback: (error: any, response: any) => void): void; + public addProvider(provider: any): void; + public start(): void; + public stop(): void; + } + export = Web3ProviderEngine; +} diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index 887b515d0..7335e3eb7 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,5 +1,9 @@ [ { + "version": "0.5.0", + "changes": [{ "note": "Make `AbiDecoder.addABI` public", "pr": 485 }] + }, + { "timestamp": 1522658513, "version": "0.4.4", "changes": [ diff --git a/packages/utils/src/abi_decoder.ts b/packages/utils/src/abi_decoder.ts index d49906cfb..36c7839cf 100644 --- a/packages/utils/src/abi_decoder.ts +++ b/packages/utils/src/abi_decoder.ts @@ -27,7 +27,7 @@ export class AbiDecoder { return `0x${formatted}`; } constructor(abiArrays: AbiDefinition[][]) { - _.forEach(abiArrays, this._addABI.bind(this)); + _.forEach(abiArrays, this.addABI.bind(this)); } // This method can only decode logs from the 0x & ERC20 smart contracts public tryToDecodeLogOrNoop<ArgsType>(log: LogEntry): LogWithDecodedArgs<ArgsType> | RawLog { @@ -73,7 +73,7 @@ export class AbiDecoder { }; } } - private _addABI(abiArray: AbiDefinition[]): void { + public addABI(abiArray: AbiDefinition[]): void { if (_.isUndefined(abiArray)) { return; } diff --git a/packages/web3-wrapper/CHANGELOG.json b/packages/web3-wrapper/CHANGELOG.json index bcbab3ded..1712c2ca7 100644 --- a/packages/web3-wrapper/CHANGELOG.json +++ b/packages/web3-wrapper/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "0.5.0", + "changes": [ + { + "note": "Add `web3Wrapper.awaitTransactionMinedAsync`", + "pr": 485 + }, + { + "note": + "Add a public field `abiDecoder: AbiDecoder` which allows you to add your ABIs that are later used to decode logs", + "pr": 485 + }, + { + "note": "Export enum `Web3WrapperErrors` with a single value so far: `TransactionMiningTimeout`", + "pr": 485 + } + ] + }, + { "version": "0.4.0", "changes": [ { diff --git a/packages/web3-wrapper/src/index.ts b/packages/web3-wrapper/src/index.ts index 87c69b269..7309e09a8 100644 --- a/packages/web3-wrapper/src/index.ts +++ b/packages/web3-wrapper/src/index.ts @@ -1,326 +1,2 @@ -import { - BlockParam, - BlockWithoutTransactionData, - CallData, - ContractAbi, - FilterObject, - JSONRPCRequestPayload, - JSONRPCResponsePayload, - LogEntry, - RawLogEntry, - TransactionReceipt, - TxData, -} from '@0xproject/types'; -import { BigNumber, promisify } from '@0xproject/utils'; -import * as _ from 'lodash'; -import * as Web3 from 'web3'; - -/** - * A wrapper around the Web3.js 0.x library that provides a consistent, clean promise-based interface. - */ -export class Web3Wrapper { - /** - * Flag to check if this instance is of type Web3Wrapper - */ - public isZeroExWeb3Wrapper = true; - private _web3: Web3; - private _defaults: Partial<TxData>; - private _jsonRpcRequestId: number; - /** - * Instantiates a new Web3Wrapper. - * @param provider The Web3 provider instance you would like the Web3Wrapper to use for interacting with - * the backing Ethereum node. - * @param defaults Override TxData defaults sent with RPC requests to the backing Ethereum node. - * @return An instance of the Web3Wrapper class. - */ - constructor(provider: Web3.Provider, defaults?: Partial<TxData>) { - if (_.isUndefined((provider as any).sendAsync)) { - // Web3@1.0 provider doesn't support synchronous http requests, - // so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x` - // We re-assign the send method so that Web3@1.0 providers work with @0xproject/web3-wrapper - (provider as any).sendAsync = (provider as any).send; - } - this._web3 = new Web3(); - this._web3.setProvider(provider); - this._defaults = defaults || {}; - this._jsonRpcRequestId = 0; - } - /** - * Get the contract defaults set to the Web3Wrapper instance - * @return TxData defaults (e.g gas, gasPrice, nonce, etc...) - */ - public getContractDefaults(): Partial<TxData> { - return this._defaults; - } - /** - * Retrieve the Web3 provider - * @return Web3 provider instance - */ - public getProvider(): Web3.Provider { - return this._web3.currentProvider; - } - /** - * Update the used Web3 provider - * @param provider The new Web3 provider to be set - */ - public setProvider(provider: Web3.Provider) { - this._web3.setProvider(provider); - } - /** - * Check if an address is a valid Ethereum address - * @param address Address to check - * @returns Whether the address is a valid Ethereum address - */ - public isAddress(address: string): boolean { - return this._web3.isAddress(address); - } - /** - * Check whether an address is available through the backing provider. This can be - * useful if you want to know whether a user can sign messages or transactions from - * a given Ethereum address. - * @param senderAddress Address to check availability for - * @returns Whether the address is available through the provider. - */ - public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> { - const addresses = await this.getAvailableAddressesAsync(); - const normalizedAddress = senderAddress.toLowerCase(); - return _.includes(addresses, normalizedAddress); - } - /** - * Fetch the backing Ethereum node's version string (e.g `MetaMask/v4.2.0`) - * @returns Ethereum node's version string - */ - public async getNodeVersionAsync(): Promise<string> { - const nodeVersion = await promisify<string>(this._web3.version.getNode)(); - return nodeVersion; - } - /** - * Fetches the networkId of the backing Ethereum node - * @returns The network id - */ - public async getNetworkIdAsync(): Promise<number> { - const networkIdStr = await promisify<string>(this._web3.version.getNetwork)(); - const networkId = _.parseInt(networkIdStr); - return networkId; - } - /** - * Retrieves the transaction receipt for a given transaction hash - * @param txHash Transaction hash - * @returns The transaction receipt, including it's status (0: failed, 1: succeeded or undefined: not found) - */ - public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> { - const transactionReceipt = await promisify<TransactionReceipt>(this._web3.eth.getTransactionReceipt)(txHash); - if (!_.isNull(transactionReceipt)) { - transactionReceipt.status = this._normalizeTxReceiptStatus(transactionReceipt.status); - } - return transactionReceipt; - } - /** - * Convert an Ether amount from ETH to Wei - * @param ethAmount Amount of Ether to convert to wei - * @returns Amount in wei - */ - public toWei(ethAmount: BigNumber): BigNumber { - const balanceWei = this._web3.toWei(ethAmount, 'ether'); - return balanceWei; - } - /** - * Retrieves an accounts Ether balance in wei - * @param owner Account whose balance you wish to check - * @returns Balance in wei - */ - public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> { - let balanceInWei = await promisify<BigNumber>(this._web3.eth.getBalance)(owner); - // Rewrap in a new BigNumber - balanceInWei = new BigNumber(balanceInWei); - return balanceInWei; - } - /** - * Check if a contract exists at a given address - * @param address Address to which to check - * @returns Whether or not contract code was found at the supplied address - */ - public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { - const code = await promisify<string>(this._web3.eth.getCode)(address); - // Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients - const codeIsEmpty = /^0x0{0,40}$/i.test(code); - return !codeIsEmpty; - } - /** - * Sign a message with a specific address's private key (`eth_sign`) - * @param address Address of signer - * @param message Message to sign - * @returns Signature string (might be VRS or RSV depending on the Signer) - */ - public async signMessageAsync(address: string, message: string): Promise<string> { - const signData = await promisify<string>(this._web3.eth.sign)(address, message); - return signData; - } - /** - * Fetches the latest block number - * @returns Block number - */ - public async getBlockNumberAsync(): Promise<number> { - const blockNumber = await promisify<number>(this._web3.eth.getBlockNumber)(); - return blockNumber; - } - /** - * Fetch a specific Ethereum block - * @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral) - * @returns The requested block without transaction data - */ - public async getBlockAsync(blockParam: string | BlockParam): Promise<BlockWithoutTransactionData> { - const block = await promisify<BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockParam); - return block; - } - /** - * Fetch a block's timestamp - * @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral) - * @returns The block's timestamp - */ - public async getBlockTimestampAsync(blockParam: string | BlockParam): Promise<number> { - const { timestamp } = await this.getBlockAsync(blockParam); - return timestamp; - } - /** - * Retrieve the user addresses available through the backing provider - * @returns Available user addresses - */ - public async getAvailableAddressesAsync(): Promise<string[]> { - const addresses = await promisify<string[]>(this._web3.eth.getAccounts)(); - const normalizedAddresses = _.map(addresses, address => address.toLowerCase()); - return normalizedAddresses; - } - /** - * Take a snapshot of the blockchain state on a TestRPC/Ganache local node - * @returns The snapshot id. This can be used to revert to this snapshot - */ - public async takeSnapshotAsync(): Promise<number> { - const snapshotId = Number(await this._sendRawPayloadAsync<string>({ method: 'evm_snapshot', params: [] })); - return snapshotId; - } - /** - * Revert the blockchain state to a previous snapshot state on TestRPC/Ganache local node - * @param snapshotId snapshot id to revert to - * @returns Whether the revert was successful - */ - public async revertSnapshotAsync(snapshotId: number): Promise<boolean> { - const didRevert = await this._sendRawPayloadAsync<boolean>({ method: 'evm_revert', params: [snapshotId] }); - return didRevert; - } - /** - * Mine a block on a TestRPC/Ganache local node - */ - public async mineBlockAsync(): Promise<void> { - await this._sendRawPayloadAsync<string>({ method: 'evm_mine', params: [] }); - } - /** - * Increase the next blocks timestamp on TestRPC/Ganache local node - * @param timeDelta Amount of time to add in seconds - */ - public async increaseTimeAsync(timeDelta: number): Promise<void> { - await this._sendRawPayloadAsync<string>({ method: 'evm_increaseTime', params: [timeDelta] }); - } - /** - * Retrieve smart contract logs for a given filter - * @param filter Parameters by which to filter which logs to retrieve - * @returns The corresponding log entries - */ - public async getLogsAsync(filter: FilterObject): Promise<LogEntry[]> { - let fromBlock = filter.fromBlock; - if (_.isNumber(fromBlock)) { - fromBlock = this._web3.toHex(fromBlock); - } - let toBlock = filter.toBlock; - if (_.isNumber(toBlock)) { - toBlock = this._web3.toHex(toBlock); - } - const serializedFilter = { - ...filter, - fromBlock, - toBlock, - }; - const payload = { - jsonrpc: '2.0', - id: this._jsonRpcRequestId++, - method: 'eth_getLogs', - params: [serializedFilter], - }; - const rawLogs = await this._sendRawPayloadAsync<RawLogEntry[]>(payload); - const formattedLogs = _.map(rawLogs, this._formatLog.bind(this)); - return formattedLogs; - } - /** - * Get a Web3 contract factory instance for a given ABI - * @param abi Smart contract ABI - * @returns Web3 contract factory which can create Web3 Contract instances from the supplied ABI - */ - public getContractFromAbi(abi: ContractAbi): Web3.Contract<any> { - const web3Contract = this._web3.eth.contract(abi); - return web3Contract; - } - /** - * Calculate the estimated gas cost for a given transaction - * @param txData Transaction data - * @returns Estimated gas cost - */ - public async estimateGasAsync(txData: Partial<TxData>): Promise<number> { - const gas = await promisify<number>(this._web3.eth.estimateGas)(txData); - return gas; - } - /** - * Call a smart contract method at a given block height - * @param callData Call data - * @param defaultBlock Block height at which to make the call. Defaults to `latest` - * @returns The raw call result - */ - public async callAsync(callData: CallData, defaultBlock?: BlockParam): Promise<string> { - const rawCallResult = await promisify<string>(this._web3.eth.call)(callData, defaultBlock); - return rawCallResult; - } - /** - * Send a transaction - * @param txData Transaction data - * @returns Transaction hash - */ - public async sendTransactionAsync(txData: TxData): Promise<string> { - const txHash = await promisify<string>(this._web3.eth.sendTransaction)(txData); - return txHash; - } - private async _sendRawPayloadAsync<A>(payload: Partial<JSONRPCRequestPayload>): Promise<A> { - const sendAsync = this._web3.currentProvider.sendAsync.bind(this._web3.currentProvider); - const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payload); - const result = response.result; - return result; - } - private _normalizeTxReceiptStatus(status: undefined | null | string | 0 | 1): null | 0 | 1 { - // Transaction status might have four values - // undefined - Testrpc and other old clients - // null - New clients on old transactions - // number - Parity - // hex - Geth - if (_.isString(status)) { - return this._web3.toDecimal(status) as 0 | 1; - } else if (_.isUndefined(status)) { - return null; - } else { - return status; - } - } - private _formatLog(rawLog: RawLogEntry): LogEntry { - const formattedLog = { - ...rawLog, - logIndex: this._hexToDecimal(rawLog.logIndex), - blockNumber: this._hexToDecimal(rawLog.blockNumber), - transactionIndex: this._hexToDecimal(rawLog.transactionIndex), - }; - return formattedLog; - } - private _hexToDecimal(hex: string | null): number | null { - if (_.isNull(hex)) { - return null; - } - const decimal = this._web3.toDecimal(hex); - return decimal; - } -} +export { Web3Wrapper } from './web3_wrapper'; +export { Web3WrapperErrors } from './types'; diff --git a/packages/web3-wrapper/src/types.ts b/packages/web3-wrapper/src/types.ts new file mode 100644 index 000000000..79542da10 --- /dev/null +++ b/packages/web3-wrapper/src/types.ts @@ -0,0 +1,3 @@ +export enum Web3WrapperErrors { + TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT', +} diff --git a/packages/web3-wrapper/src/web3_wrapper.ts b/packages/web3-wrapper/src/web3_wrapper.ts new file mode 100644 index 000000000..d75f39ed5 --- /dev/null +++ b/packages/web3-wrapper/src/web3_wrapper.ts @@ -0,0 +1,375 @@ +import { + BlockParam, + BlockWithoutTransactionData, + CallData, + ContractAbi, + FilterObject, + JSONRPCRequestPayload, + JSONRPCResponsePayload, + LogEntry, + RawLogEntry, + TransactionReceipt, + TransactionReceiptWithDecodedLogs, + TxData, +} from '@0xproject/types'; +import { AbiDecoder, BigNumber, intervalUtils, promisify } from '@0xproject/utils'; +import * as _ from 'lodash'; +import * as Web3 from 'web3'; + +import { Web3WrapperErrors } from './types'; + +/** + * A wrapper around the Web3.js 0.x library that provides a consistent, clean promise-based interface. + */ +export class Web3Wrapper { + /** + * Flag to check if this instance is of type Web3Wrapper + */ + public isZeroExWeb3Wrapper = true; + public abiDecoder: AbiDecoder; + private _web3: Web3; + private _defaults: Partial<TxData>; + private _jsonRpcRequestId: number; + /** + * Instantiates a new Web3Wrapper. + * @param provider The Web3 provider instance you would like the Web3Wrapper to use for interacting with + * the backing Ethereum node. + * @param defaults Override TxData defaults sent with RPC requests to the backing Ethereum node. + * @return An instance of the Web3Wrapper class. + */ + constructor(provider: Web3.Provider, defaults?: Partial<TxData>) { + if (_.isUndefined((provider as any).sendAsync)) { + // Web3@1.0 provider doesn't support synchronous http requests, + // so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x` + // We re-assign the send method so that Web3@1.0 providers work with @0xproject/web3-wrapper + (provider as any).sendAsync = (provider as any).send; + } + this.abiDecoder = new AbiDecoder([]); + this._web3 = new Web3(); + this._web3.setProvider(provider); + this._defaults = defaults || {}; + this._jsonRpcRequestId = 0; + } + /** + * Get the contract defaults set to the Web3Wrapper instance + * @return TxData defaults (e.g gas, gasPrice, nonce, etc...) + */ + public getContractDefaults(): Partial<TxData> { + return this._defaults; + } + /** + * Retrieve the Web3 provider + * @return Web3 provider instance + */ + public getProvider(): Web3.Provider { + return this._web3.currentProvider; + } + /** + * Update the used Web3 provider + * @param provider The new Web3 provider to be set + */ + public setProvider(provider: Web3.Provider) { + this._web3.setProvider(provider); + } + /** + * Check if an address is a valid Ethereum address + * @param address Address to check + * @returns Whether the address is a valid Ethereum address + */ + public isAddress(address: string): boolean { + return this._web3.isAddress(address); + } + /** + * Check whether an address is available through the backing provider. This can be + * useful if you want to know whether a user can sign messages or transactions from + * a given Ethereum address. + * @param senderAddress Address to check availability for + * @returns Whether the address is available through the provider. + */ + public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> { + const addresses = await this.getAvailableAddressesAsync(); + const normalizedAddress = senderAddress.toLowerCase(); + return _.includes(addresses, normalizedAddress); + } + /** + * Fetch the backing Ethereum node's version string (e.g `MetaMask/v4.2.0`) + * @returns Ethereum node's version string + */ + public async getNodeVersionAsync(): Promise<string> { + const nodeVersion = await promisify<string>(this._web3.version.getNode)(); + return nodeVersion; + } + /** + * Fetches the networkId of the backing Ethereum node + * @returns The network id + */ + public async getNetworkIdAsync(): Promise<number> { + const networkIdStr = await promisify<string>(this._web3.version.getNetwork)(); + const networkId = _.parseInt(networkIdStr); + return networkId; + } + /** + * Retrieves the transaction receipt for a given transaction hash + * @param txHash Transaction hash + * @returns The transaction receipt, including it's status (0: failed, 1: succeeded or undefined: not found) + */ + public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> { + const transactionReceipt = await promisify<TransactionReceipt>(this._web3.eth.getTransactionReceipt)(txHash); + if (!_.isNull(transactionReceipt)) { + transactionReceipt.status = this._normalizeTxReceiptStatus(transactionReceipt.status); + } + return transactionReceipt; + } + /** + * Convert an Ether amount from ETH to Wei + * @param ethAmount Amount of Ether to convert to wei + * @returns Amount in wei + */ + public toWei(ethAmount: BigNumber): BigNumber { + const balanceWei = this._web3.toWei(ethAmount, 'ether'); + return balanceWei; + } + /** + * Retrieves an accounts Ether balance in wei + * @param owner Account whose balance you wish to check + * @returns Balance in wei + */ + public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> { + let balanceInWei = await promisify<BigNumber>(this._web3.eth.getBalance)(owner); + // Rewrap in a new BigNumber + balanceInWei = new BigNumber(balanceInWei); + return balanceInWei; + } + /** + * Check if a contract exists at a given address + * @param address Address to which to check + * @returns Whether or not contract code was found at the supplied address + */ + public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { + const code = await promisify<string>(this._web3.eth.getCode)(address); + // Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients + const codeIsEmpty = /^0x0{0,40}$/i.test(code); + return !codeIsEmpty; + } + /** + * Sign a message with a specific address's private key (`eth_sign`) + * @param address Address of signer + * @param message Message to sign + * @returns Signature string (might be VRS or RSV depending on the Signer) + */ + public async signMessageAsync(address: string, message: string): Promise<string> { + const signData = await promisify<string>(this._web3.eth.sign)(address, message); + return signData; + } + /** + * Fetches the latest block number + * @returns Block number + */ + public async getBlockNumberAsync(): Promise<number> { + const blockNumber = await promisify<number>(this._web3.eth.getBlockNumber)(); + return blockNumber; + } + /** + * Fetch a specific Ethereum block + * @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral) + * @returns The requested block without transaction data + */ + public async getBlockAsync(blockParam: string | BlockParam): Promise<BlockWithoutTransactionData> { + const block = await promisify<BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockParam); + return block; + } + /** + * Fetch a block's timestamp + * @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral) + * @returns The block's timestamp + */ + public async getBlockTimestampAsync(blockParam: string | BlockParam): Promise<number> { + const { timestamp } = await this.getBlockAsync(blockParam); + return timestamp; + } + /** + * Retrieve the user addresses available through the backing provider + * @returns Available user addresses + */ + public async getAvailableAddressesAsync(): Promise<string[]> { + const addresses = await promisify<string[]>(this._web3.eth.getAccounts)(); + const normalizedAddresses = _.map(addresses, address => address.toLowerCase()); + return normalizedAddresses; + } + /** + * Take a snapshot of the blockchain state on a TestRPC/Ganache local node + * @returns The snapshot id. This can be used to revert to this snapshot + */ + public async takeSnapshotAsync(): Promise<number> { + const snapshotId = Number(await this._sendRawPayloadAsync<string>({ method: 'evm_snapshot', params: [] })); + return snapshotId; + } + /** + * Revert the blockchain state to a previous snapshot state on TestRPC/Ganache local node + * @param snapshotId snapshot id to revert to + * @returns Whether the revert was successful + */ + public async revertSnapshotAsync(snapshotId: number): Promise<boolean> { + const didRevert = await this._sendRawPayloadAsync<boolean>({ method: 'evm_revert', params: [snapshotId] }); + return didRevert; + } + /** + * Mine a block on a TestRPC/Ganache local node + */ + public async mineBlockAsync(): Promise<void> { + await this._sendRawPayloadAsync<string>({ method: 'evm_mine', params: [] }); + } + /** + * Increase the next blocks timestamp on TestRPC/Ganache local node + * @param timeDelta Amount of time to add in seconds + */ + public async increaseTimeAsync(timeDelta: number): Promise<void> { + await this._sendRawPayloadAsync<string>({ method: 'evm_increaseTime', params: [timeDelta] }); + } + /** + * Retrieve smart contract logs for a given filter + * @param filter Parameters by which to filter which logs to retrieve + * @returns The corresponding log entries + */ + public async getLogsAsync(filter: FilterObject): Promise<LogEntry[]> { + let fromBlock = filter.fromBlock; + if (_.isNumber(fromBlock)) { + fromBlock = this._web3.toHex(fromBlock); + } + let toBlock = filter.toBlock; + if (_.isNumber(toBlock)) { + toBlock = this._web3.toHex(toBlock); + } + const serializedFilter = { + ...filter, + fromBlock, + toBlock, + }; + const payload = { + jsonrpc: '2.0', + id: this._jsonRpcRequestId++, + method: 'eth_getLogs', + params: [serializedFilter], + }; + const rawLogs = await this._sendRawPayloadAsync<RawLogEntry[]>(payload); + const formattedLogs = _.map(rawLogs, this._formatLog.bind(this)); + return formattedLogs; + } + /** + * Get a Web3 contract factory instance for a given ABI + * @param abi Smart contract ABI + * @returns Web3 contract factory which can create Web3 Contract instances from the supplied ABI + */ + public getContractFromAbi(abi: ContractAbi): Web3.Contract<any> { + const web3Contract = this._web3.eth.contract(abi); + return web3Contract; + } + /** + * Calculate the estimated gas cost for a given transaction + * @param txData Transaction data + * @returns Estimated gas cost + */ + public async estimateGasAsync(txData: Partial<TxData>): Promise<number> { + const gas = await promisify<number>(this._web3.eth.estimateGas)(txData); + return gas; + } + /** + * Call a smart contract method at a given block height + * @param callData Call data + * @param defaultBlock Block height at which to make the call. Defaults to `latest` + * @returns The raw call result + */ + public async callAsync(callData: CallData, defaultBlock?: BlockParam): Promise<string> { + const rawCallResult = await promisify<string>(this._web3.eth.call)(callData, defaultBlock); + return rawCallResult; + } + /** + * Send a transaction + * @param txData Transaction data + * @returns Transaction hash + */ + public async sendTransactionAsync(txData: TxData): Promise<string> { + const txHash = await promisify<string>(this._web3.eth.sendTransaction)(txData); + return txHash; + } + public async awaitTransactionMinedAsync( + txHash: string, + pollingIntervalMs = 1000, + timeoutMs?: number, + ): Promise<TransactionReceiptWithDecodedLogs> { + let timeoutExceeded = false; + if (timeoutMs) { + setTimeout(() => (timeoutExceeded = true), timeoutMs); + } + + const txReceiptPromise = new Promise( + (resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => { + const intervalId = intervalUtils.setAsyncExcludingInterval( + async () => { + if (timeoutExceeded) { + intervalUtils.clearAsyncExcludingInterval(intervalId); + return reject(Web3WrapperErrors.TransactionMiningTimeout); + } + + const transactionReceipt = await this.getTransactionReceiptAsync(txHash); + if (!_.isNull(transactionReceipt)) { + intervalUtils.clearAsyncExcludingInterval(intervalId); + const logsWithDecodedArgs = _.map( + transactionReceipt.logs, + this.abiDecoder.tryToDecodeLogOrNoop.bind(this.abiDecoder), + ); + const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = { + ...transactionReceipt, + logs: logsWithDecodedArgs, + }; + resolve(transactionReceiptWithDecodedLogArgs); + } + }, + pollingIntervalMs, + (err: Error) => { + intervalUtils.clearAsyncExcludingInterval(intervalId); + reject(err); + }, + ); + }, + ); + const txReceipt = await txReceiptPromise; + return txReceipt; + } + private async _sendRawPayloadAsync<A>(payload: Partial<JSONRPCRequestPayload>): Promise<A> { + const sendAsync = this._web3.currentProvider.sendAsync.bind(this._web3.currentProvider); + const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payload); + const result = response.result; + return result; + } + private _normalizeTxReceiptStatus(status: undefined | null | string | 0 | 1): null | 0 | 1 { + // Transaction status might have four values + // undefined - Testrpc and other old clients + // null - New clients on old transactions + // number - Parity + // hex - Geth + if (_.isString(status)) { + return this._web3.toDecimal(status) as 0 | 1; + } else if (_.isUndefined(status)) { + return null; + } else { + return status; + } + } + private _formatLog(rawLog: RawLogEntry): LogEntry { + const formattedLog = { + ...rawLog, + logIndex: this._hexToDecimal(rawLog.logIndex), + blockNumber: this._hexToDecimal(rawLog.blockNumber), + transactionIndex: this._hexToDecimal(rawLog.transactionIndex), + }; + return formattedLog; + } + private _hexToDecimal(hex: string | null): number | null { + if (_.isNull(hex)) { + return null; + } + const decimal = this._web3.toDecimal(hex); + return decimal; + } +} diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts index 3791b3269..fcf4c2434 100644 --- a/packages/website/ts/globals.d.ts +++ b/packages/website/ts/globals.d.ts @@ -1,7 +1,6 @@ declare module 'react-tooltip'; declare module 'react-router-hash-link'; declare module 'truffle-contract'; -declare module 'ethereumjs-util'; declare module 'keccak'; declare module 'whatwg-fetch'; declare module 'react-html5video'; @@ -9,7 +8,6 @@ declare module 'web3-provider-engine/subproviders/filters'; declare module 'thenby'; declare module 'react-recaptcha'; declare module 'react-document-title'; -declare module 'ethereumjs-tx'; declare module 'react-ga'; declare module '*.json' { @@ -126,17 +124,6 @@ declare module 'web3-provider-engine/subproviders/rpc' { } export = RpcSubprovider; } -declare module 'web3-provider-engine' { - class Web3ProviderEngine { - public on(event: string, handler: () => void): void; - public send(payload: any): void; - public sendAsync(payload: any, callback: (error: any, response: any) => void): void; - public addProvider(provider: any): void; - public start(): void; - public stop(): void; - } - export = Web3ProviderEngine; -} declare interface Artifact { abi: any; |