diff options
131 files changed, 1367 insertions, 801 deletions
diff --git a/.prettierignore b/.prettierignore index c3738481a..385c15654 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,4 @@ lib -generated .nyc_output /packages/contracts/src/artifacts package.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e8828ff6b..2bb71e855 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,9 +8,9 @@ If you'd like to contribute to 0x protocol, please fork the repo, fix, commit an We encourage a “PR early” approach so create the PR as early as possible even without the fix/feature ready, so that devs and other contributors know you have picked up the issue. These early PRs should indicate an 'in progress' status by adding the '[WIP]' prefix to the PR title. Please make sure your contributions adhere to our coding guidelines: -* Pull requests adding features or refactoring should be opened against the `development` branch -* Pull requests fixing bugs in the latest release version should be opened again the `master` branch -* Write [good commit messages](https://chris.beams.io/posts/git-commit/) +* Pull requests adding features or refactoring should be opened against the `development` branch +* Pull requests fixing bugs in the latest release version should be opened again the `master` branch +* Write [good commit messages](https://chris.beams.io/posts/git-commit/) ### Code quality @@ -31,10 +31,10 @@ We also use [Prettier](https://prettier.io/) to auto-format our code. Be sure to If using the Atom text editor, we recommend you install the following packages: -* [atom-typescript](https://atom.io/packages/atom-typescript) -* [linter-tslint](https://atom.io/packages/linter-tslint) -* [prettier-atom](https://atom.io/packages/prettier-atom) -* [language-ethereum](https://atom.io/packages/language-ethereum) +* [atom-typescript](https://atom.io/packages/atom-typescript) +* [linter-tslint](https://atom.io/packages/linter-tslint) +* [prettier-atom](https://atom.io/packages/prettier-atom) +* [language-ethereum](https://atom.io/packages/language-ethereum) Our CI will also run TSLint and Prettier as a part of the test run when you submit your PR. Make sure that the CI tests pass for your contribution. diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 96eae1691..481b1d536 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -24,9 +24,9 @@ <!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> -* [ ] Bug fix (non-breaking change which fixes an issue) -* [ ] New feature (non-breaking change which adds functionality) -* [ ] Breaking change (fix or feature that would cause existing functionality to change) +* [ ] Bug fix (non-breaking change which fixes an issue) +* [ ] New feature (non-breaking change which adds functionality) +* [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Checklist: @@ -34,9 +34,9 @@ <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> -* [ ] Change requires a change to the documentation. -* [ ] Added tests to cover my changes. -* [ ] Added new entries to the relevant CHANGELOGs. -* [ ] Updated the new versions of the changed packages in the relevant CHANGELOGs. -* [ ] Labeled this PR with the 'WIP' label if it is a work in progress. -* [ ] Labeled this PR with the labels corresponding to the changed package. +* [ ] Change requires a change to the documentation. +* [ ] Added tests to cover my changes. +* [ ] Added new entries to the relevant CHANGELOGs. +* [ ] Updated the new versions of the changed packages in the relevant CHANGELOGs. +* [ ] Labeled this PR with the 'WIP' label if it is a work in progress. +* [ ] Labeled this PR with the labels corresponding to the changed package. @@ -48,10 +48,10 @@ This repository contains all the 0x developer tools written in TypeScript. Our h Dedicated documentation pages: -* [0x.js Library](https://0xproject.com/docs/0xjs) -* [0x Connect](https://0xproject.com/docs/connect) -* [Smart contracts](https://0xproject.com/docs/contracts) -* [Standard Relayer API](https://github.com/0xProject/standard-relayer-api/blob/master/README.md) +* [0x.js Library](https://0xproject.com/docs/0xjs) +* [0x Connect](https://0xproject.com/docs/connect) +* [Smart contracts](https://0xproject.com/docs/contracts) +* [Standard Relayer API](https://github.com/0xProject/standard-relayer-api/blob/master/README.md) ## Contributing diff --git a/package.json b/package.json index 9c3825822..e130cbcc8 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "async-child-process": "^1.1.1", "ethereumjs-testrpc": "^6.0.3", "lerna": "^2.5.1", - "prettier": "1.9.2", + "prettier": "^1.11.0", "publish-release": "0xproject/publish-release", "semver-sort": "^0.0.4" } diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md index 0af474c74..b882a1b31 100644 --- a/packages/0x.js/CHANGELOG.md +++ b/packages/0x.js/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## v0.33.0 - _TBD, 2018_ + + * Validate and lowercase all addresses in public methods (#373) + * Improve validation to force passing contract addresses on private networks (#385) + * Change `LogErrorContractEventArgs.errorId` type from `BigNumber` to `number` (#413) + * Rename all public `_unsubscribeAll` methods to `unsubscribeAll` (#415) + ## v0.32.2 - _February 9, 2018_ * Fix publishing issue where .npmignore was not properly excluding undesired content (#389) diff --git a/packages/0x.js/contract_templates/partials/call.handlebars b/packages/0x.js/contract_templates/partials/call.handlebars deleted file mode 100644 index 0475136f0..000000000 --- a/packages/0x.js/contract_templates/partials/call.handlebars +++ /dev/null @@ -1,15 +0,0 @@ -public {{this.name}} = { - async callAsync( - {{> typed_params inputs=inputs}} - defaultBlock?: Web3.BlockParam, - ): Promise<{{> return_type outputs=outputs}}> { - const self = this as {{contractName}}Contract; - const result = await promisify<{{> return_type outputs=outputs}}>( - self._web3ContractInstance.{{this.name}}.call, - self._web3ContractInstance, - )( - {{> params inputs=inputs}} - ); - return result; - }, -}; diff --git a/packages/0x.js/contract_templates/partials/return_type.handlebars b/packages/0x.js/contract_templates/partials/return_type.handlebars deleted file mode 100644 index 383961a40..000000000 --- a/packages/0x.js/contract_templates/partials/return_type.handlebars +++ /dev/null @@ -1,6 +0,0 @@ -{{#singleReturnValue}} -{{#returnType outputs.0.type}}{{/returnType}} -{{/singleReturnValue}} -{{^singleReturnValue}} -[{{#each outputs}}{{#returnType type}}{{/returnType}}{{#unless @last}}, {{/unless}}{{/each}}] -{{/singleReturnValue}} diff --git a/packages/0x.js/contract_templates/partials/typed_params.handlebars b/packages/0x.js/contract_templates/partials/typed_params.handlebars deleted file mode 100644 index 3ea4b2e95..000000000 --- a/packages/0x.js/contract_templates/partials/typed_params.handlebars +++ /dev/null @@ -1,3 +0,0 @@ -{{#each inputs}} - {{name}}: {{#parameterType type}}{{/parameterType}}, -{{/each}} diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index 4fb0eb6ac..20af430d6 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -17,7 +17,7 @@ "build": "run-p build:umd:prod build:commonjs; exit 0;", "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR", "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", - "generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --template contract_templates/contract.handlebars --partials 'contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated", + "generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'", "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", "test:circleci": "run-s test:coverage report_test_coverage", "test": "run-s clean test:commonjs", @@ -77,17 +77,20 @@ "types-bn": "^0.0.1", "typescript": "2.7.1", "web3-provider-engine": "^13.0.1", + "ethers-typescript-typings": "^0.0.1", "web3-typescript-typings": "^0.9.11", "webpack": "^3.1.0" }, "dependencies": { "@0xproject/assert": "^0.0.20", + "@0xproject/base-contract": "^0.0.1", "@0xproject/json-schemas": "^0.7.12", "@0xproject/types": "^0.2.3", "@0xproject/utils": "^0.3.4", "@0xproject/web3-wrapper": "^0.1.14", "bintrees": "^1.0.2", "bn.js": "^4.11.8", + "ethers-contracts": "^2.2.1", "ethereumjs-abi": "^0.6.4", "ethereumjs-blockstream": "^2.0.6", "ethereumjs-util": "^5.1.1", diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts index 6cfa65cc2..22a5fee10 100644 --- a/packages/0x.js/src/0x.ts +++ b/packages/0x.js/src/0x.ts @@ -13,6 +13,8 @@ import { TokenTransferProxyWrapper } from './contract_wrappers/token_transfer_pr import { TokenWrapper } from './contract_wrappers/token_wrapper'; import { OrderStateWatcher } from './order_watcher/order_state_watcher'; import { zeroExConfigSchema } from './schemas/zero_ex_config_schema'; +import { zeroExPrivateNetworkConfigSchema } from './schemas/zero_ex_private_network_config_schema'; +import { zeroExPublicNetworkConfigSchema } from './schemas/zero_ex_public_network_config_schema'; import { ECSignature, Order, SignedOrder, Web3Provider, ZeroExConfig, ZeroExError } from './types'; import { assert } from './utils/assert'; import { constants } from './utils/constants'; @@ -74,8 +76,9 @@ export class ZeroEx { assert.isHexString('data', data); assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema); assert.isETHAddressHex('signerAddress', signerAddress); + const normalizedSignerAddress = signerAddress.toLowerCase(); - const isValidSignature = signatureUtils.isValidSignature(data, signature, signerAddress); + const isValidSignature = signatureUtils.isValidSignature(data, signature, normalizedSignerAddress); return isValidSignature; } /** @@ -163,7 +166,10 @@ export class ZeroEx { */ constructor(provider: Web3Provider, config: ZeroExConfig) { assert.isWeb3Provider('provider', provider); - assert.doesConformToSchema('config', config, zeroExConfigSchema); + assert.doesConformToSchema('config', config, zeroExConfigSchema, [ + zeroExPrivateNetworkConfigSchema, + zeroExPublicNetworkConfigSchema, + ]); const artifactJSONs = _.values(artifacts); const abiArrays = _.map(artifactJSONs, artifact => artifact.abi); this._abiDecoder = new AbiDecoder(abiArrays); @@ -245,6 +251,7 @@ export class ZeroEx { ): Promise<ECSignature> { assert.isHexString('orderHash', orderHash); await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper); + const normalizedSignerAddress = signerAddress.toLowerCase(); let msgHashHex = orderHash; if (shouldAddPersonalMessagePrefix) { @@ -253,7 +260,7 @@ export class ZeroEx { msgHashHex = ethUtil.bufferToHex(msgHashBuff); } - const signature = await this._web3Wrapper.signTransactionAsync(signerAddress, msgHashHex); + const signature = await this._web3Wrapper.signTransactionAsync(normalizedSignerAddress, msgHashHex); // HACK: There is no consensus on whether the signatureHex string should be formatted as // v + r + s OR r + s + v, and different clients (even different versions of the same client) @@ -262,7 +269,7 @@ export class ZeroEx { const validVParamValues = [27, 28]; const ecSignatureVRS = signatureUtils.parseSignatureHexAsVRS(signature); if (_.includes(validVParamValues, ecSignatureVRS.v)) { - const isValidVRSSignature = ZeroEx.isValidSignature(orderHash, ecSignatureVRS, signerAddress); + const isValidVRSSignature = ZeroEx.isValidSignature(orderHash, ecSignatureVRS, normalizedSignerAddress); if (isValidVRSSignature) { return ecSignatureVRS; } @@ -270,7 +277,7 @@ export class ZeroEx { const ecSignatureRSV = signatureUtils.parseSignatureHexAsRSV(signature); if (_.includes(validVParamValues, ecSignatureRSV.v)) { - const isValidRSVSignature = ZeroEx.isValidSignature(orderHash, ecSignatureRSV, signerAddress); + const isValidRSVSignature = ZeroEx.isValidSignature(orderHash, ecSignatureRSV, normalizedSignerAddress); if (isValidRSVSignature) { return ecSignatureRSV; } diff --git a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts index b313273b5..ad7727de5 100644 --- a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts @@ -108,10 +108,10 @@ export class ContractWrapper { const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log); return logWithDecodedArgs; } - protected async _instantiateContractIfExistsAsync( + protected async _getContractAbiAndAddressFromArtifactsAsync( artifact: Artifact, addressIfExists?: string, - ): Promise<Web3.ContractInstance> { + ): Promise<[Web3.ContractAbi, string]> { let contractAddress: string; if (_.isUndefined(addressIfExists)) { if (_.isUndefined(artifact.networks[this._networkId])) { @@ -125,8 +125,8 @@ export class ContractWrapper { if (!doesContractExist) { throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]); } - const contractInstance = this._web3Wrapper.getContractInstance(artifact.abi, contractAddress); - return contractInstance; + const abiAndAddress: [Web3.ContractAbi, string] = [artifact.abi, contractAddress]; + return abiAndAddress; } protected _getContractAddress(artifact: Artifact, addressIfExists?: string): string { if (_.isUndefined(addressIfExists)) { 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 db7cdee43..42f8213a2 100644 --- a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts @@ -41,15 +41,18 @@ export class EtherTokenWrapper extends ContractWrapper { depositor: string, txOpts: TransactionOpts = {}, ): Promise<string> { + assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); assert.isValidBaseUnitAmount('amountInWei', amountInWei); await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper); + const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); + const normalizedDepositorAddress = depositor.toLowerCase(); - const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor); + const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(normalizedDepositorAddress); assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit); - const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress); + const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress); const txHash = await wethContract.deposit.sendTransactionAsync({ - from: depositor, + from: normalizedDepositorAddress, value: amountInWei, gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, @@ -72,14 +75,20 @@ export class EtherTokenWrapper extends ContractWrapper { txOpts: TransactionOpts = {}, ): Promise<string> { assert.isValidBaseUnitAmount('amountInWei', amountInWei); + assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper); + const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); + const normalizedWithdrawerAddress = withdrawer.toLowerCase(); - const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(etherTokenAddress, withdrawer); + const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync( + normalizedEtherTokenAddress, + normalizedWithdrawerAddress, + ); assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal); - const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress); + const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress); const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, { - from: withdrawer, + from: normalizedWithdrawerAddress, gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, }); @@ -101,11 +110,12 @@ export class EtherTokenWrapper extends ContractWrapper { indexFilterValues: IndexedFilterValues, ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); + const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); const logs = await this._getLogsAsync<ArgsType>( - etherTokenAddress, + normalizedEtherTokenAddress, eventName, blockRange, indexFilterValues, @@ -129,11 +139,12 @@ export class EtherTokenWrapper extends ContractWrapper { callback: EventCallback<ArgsType>, ): string { assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); + const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); assert.isFunction('callback', callback); const subscriptionToken = this._subscribe<ArgsType>( - etherTokenAddress, + normalizedEtherTokenAddress, eventName, indexFilterValues, artifacts.EtherTokenArtifact.abi, @@ -151,7 +162,7 @@ export class EtherTokenWrapper extends ContractWrapper { /** * Cancels all existing subscriptions */ - public _unsubscribeAll(): void { + public unsubscribeAll(): void { super._unsubscribeAll(); } /** @@ -168,7 +179,7 @@ export class EtherTokenWrapper extends ContractWrapper { return contractAddressIfExists; } private _invalidateContractInstance(): void { - this._unsubscribeAll(); + this.unsubscribeAll(); this._etherTokenContractsByAddress = {}; } private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> { @@ -176,11 +187,11 @@ export class EtherTokenWrapper extends ContractWrapper { if (!_.isUndefined(etherTokenContract)) { return etherTokenContract; } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( + const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( artifacts.EtherTokenArtifact, etherTokenAddress, ); - const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); + const contractInstance = new EtherTokenContract(this._web3Wrapper, abi, address); etherTokenContract = contractInstance; this._etherTokenContractsByAddress[etherTokenAddress] = etherTokenContract; return etherTokenContract; diff --git a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts index c82b7ecf5..20b46c6bc 100644 --- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts @@ -108,8 +108,10 @@ export class ExchangeWrapper extends ContractWrapper { const exchangeContract = await this._getExchangeContractAsync(); const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + const txData = {}; let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync( orderHash, + txData, defaultBlock, ); // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber @@ -127,7 +129,8 @@ export class ExchangeWrapper extends ContractWrapper { const exchangeContract = await this._getExchangeContractAsync(); const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, defaultBlock); + const txData = {}; + let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, txData, defaultBlock); // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits); return fillAmountInBaseUnits; @@ -144,7 +147,8 @@ export class ExchangeWrapper extends ContractWrapper { const exchangeContract = await this._getExchangeContractAsync(); const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, defaultBlock); + const txData = {}; + let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, txData, defaultBlock); // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits); return cancelledAmountInBaseUnits; @@ -180,6 +184,7 @@ export class ExchangeWrapper extends ContractWrapper { assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const normalizedTakerAddress = takerAddress.toLowerCase(); const exchangeInstance = await this._getExchangeContractAsync(); const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) @@ -192,7 +197,7 @@ export class ExchangeWrapper extends ContractWrapper { exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, - takerAddress, + normalizedTakerAddress, zrxTokenAddress, ); } @@ -208,7 +213,7 @@ export class ExchangeWrapper extends ContractWrapper { signedOrder.ecSignature.r, signedOrder.ecSignature.s, { - from: takerAddress, + from: normalizedTakerAddress, gas: orderTransactionOpts.gasLimit, gasPrice: orderTransactionOpts.gasPrice, }, @@ -254,6 +259,7 @@ export class ExchangeWrapper extends ContractWrapper { assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const normalizedTakerAddress = takerAddress.toLowerCase(); const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) ? SHOULD_VALIDATE_BY_DEFAULT @@ -267,7 +273,7 @@ export class ExchangeWrapper extends ContractWrapper { exchangeTradeEmulator, signedOrder, fillTakerTokenAmount.minus(filledTakerTokenAmount), - takerAddress, + normalizedTakerAddress, zrxTokenAddress, ); filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount); @@ -301,7 +307,7 @@ export class ExchangeWrapper extends ContractWrapper { rArray, sArray, { - from: takerAddress, + from: normalizedTakerAddress, gas: orderTransactionOpts.gasLimit, gasPrice: orderTransactionOpts.gasPrice, }, @@ -345,6 +351,7 @@ export class ExchangeWrapper extends ContractWrapper { ); assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const normalizedTakerAddress = takerAddress.toLowerCase(); const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) ? SHOULD_VALIDATE_BY_DEFAULT : orderTransactionOpts.shouldValidate; @@ -356,7 +363,7 @@ export class ExchangeWrapper extends ContractWrapper { exchangeTradeEmulator, orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, - takerAddress, + normalizedTakerAddress, zrxTokenAddress, ); } @@ -389,7 +396,7 @@ export class ExchangeWrapper extends ContractWrapper { rArray, sArray, { - from: takerAddress, + from: normalizedTakerAddress, gas: orderTransactionOpts.gasLimit, gasPrice: orderTransactionOpts.gasPrice, }, @@ -417,6 +424,7 @@ export class ExchangeWrapper extends ContractWrapper { assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const normalizedTakerAddress = takerAddress.toLowerCase(); const exchangeInstance = await this._getExchangeContractAsync(); @@ -430,7 +438,7 @@ export class ExchangeWrapper extends ContractWrapper { exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, - takerAddress, + normalizedTakerAddress, zrxTokenAddress, ); } @@ -444,7 +452,7 @@ export class ExchangeWrapper extends ContractWrapper { signedOrder.ecSignature.r, signedOrder.ecSignature.s, { - from: takerAddress, + from: normalizedTakerAddress, gas: orderTransactionOpts.gasLimit, gasPrice: orderTransactionOpts.gasPrice, }, @@ -476,6 +484,7 @@ export class ExchangeWrapper extends ContractWrapper { ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, ); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const normalizedTakerAddress = takerAddress.toLowerCase(); if (_.isEmpty(orderFillRequests)) { throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); } @@ -492,7 +501,7 @@ export class ExchangeWrapper extends ContractWrapper { exchangeTradeEmulator, orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, - takerAddress, + normalizedTakerAddress, zrxTokenAddress, ); } @@ -520,7 +529,7 @@ export class ExchangeWrapper extends ContractWrapper { rParams, sParams, { - from: takerAddress, + from: normalizedTakerAddress, gas: orderTransactionOpts.gasLimit, gasPrice: orderTransactionOpts.gasPrice, }, @@ -544,6 +553,7 @@ export class ExchangeWrapper extends ContractWrapper { assert.doesConformToSchema('order', order, schemas.orderSchema); assert.isValidBaseUnitAmount('takerTokenCancelAmount', cancelTakerTokenAmount); await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper); + const normalizedMakerAddress = order.maker.toLowerCase(); const exchangeInstance = await this._getExchangeContractAsync(); @@ -566,7 +576,7 @@ export class ExchangeWrapper extends ContractWrapper { orderValues, cancelTakerTokenAmount, { - from: order.maker, + from: normalizedMakerAddress, gas: orderTransactionOpts.gasLimit, gasPrice: orderTransactionOpts.gasPrice, }, @@ -603,6 +613,8 @@ export class ExchangeWrapper extends ContractWrapper { assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed); const maker = makers[0]; await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper); + const normalizedMakerAddress = maker.toLowerCase(); + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) ? SHOULD_VALIDATE_BY_DEFAULT : orderTransactionOpts.shouldValidate; @@ -636,7 +648,7 @@ export class ExchangeWrapper extends ContractWrapper { orderValues, cancelTakerTokenAmounts, { - from: maker, + from: normalizedMakerAddress, gas: orderTransactionOpts.gasLimit, gasPrice: orderTransactionOpts.gasPrice, }, @@ -679,7 +691,7 @@ export class ExchangeWrapper extends ContractWrapper { /** * Cancels all existing subscriptions */ - public _unsubscribeAll(): void { + public unsubscribeAll(): void { super._unsubscribeAll(); } /** @@ -757,13 +769,14 @@ export class ExchangeWrapper extends ContractWrapper { assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const normalizedTakerAddress = takerAddress.toLowerCase(); const zrxTokenAddress = this.getZRXTokenAddress(); const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, - takerAddress, + normalizedTakerAddress, zrxTokenAddress, ); } @@ -803,13 +816,14 @@ export class ExchangeWrapper extends ContractWrapper { assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const normalizedTakerAddress = takerAddress.toLowerCase(); const zrxTokenAddress = this.getZRXTokenAddress(); const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, - takerAddress, + normalizedTakerAddress, zrxTokenAddress, ); } @@ -848,7 +862,7 @@ export class ExchangeWrapper extends ContractWrapper { }); if (!_.isUndefined(errLog)) { const logArgs = (errLog as LogWithDecodedArgs<LogErrorContractEventArgs>).args; - const errCode = logArgs.errorId.toNumber(); + const errCode = logArgs.errorId; const errMessage = this._exchangeContractErrCodesToMsg[errCode]; throw new Error(errMessage); } @@ -862,7 +876,7 @@ export class ExchangeWrapper extends ContractWrapper { return contractAddress; } private _invalidateContractInstances(): void { - this._unsubscribeAll(); + this.unsubscribeAll(); delete this._exchangeContractIfExists; } private async _isValidSignatureUsingContractCallAsync( @@ -873,11 +887,12 @@ export class ExchangeWrapper extends ContractWrapper { assert.isHexString('dataHex', dataHex); assert.doesConformToSchema('ecSignature', ecSignature, schemas.ecSignatureSchema); assert.isETHAddressHex('signerAddressHex', signerAddressHex); + const normalizedSignerAddress = signerAddressHex.toLowerCase(); const exchangeInstance = await this._getExchangeContractAsync(); const isValidSignature = await exchangeInstance.isValidSignature.callAsync( - signerAddressHex, + normalizedSignerAddress, dataHex, ecSignature.v, ecSignature.r, @@ -895,11 +910,11 @@ export class ExchangeWrapper extends ContractWrapper { if (!_.isUndefined(this._exchangeContractIfExists)) { return this._exchangeContractIfExists; } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( + const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( artifacts.ExchangeArtifact, this._contractAddressIfExists, ); - const contractInstance = new ExchangeContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); + const contractInstance = new ExchangeContract(this._web3Wrapper, abi, address); this._exchangeContractIfExists = contractInstance; return this._exchangeContractIfExists; } diff --git a/packages/0x.js/src/contract_wrappers/generated/.gitignore b/packages/0x.js/src/contract_wrappers/generated/.gitignore index 834808b48..72e8ffc0d 100644 --- a/packages/0x.js/src/contract_wrappers/generated/.gitignore +++ b/packages/0x.js/src/contract_wrappers/generated/.gitignore @@ -1,6 +1 @@ -dummy_token.ts -ether_token.ts -exchange.ts -token_registry.ts -token_transfer_proxy.ts -token.ts +* diff --git a/packages/0x.js/src/contract_wrappers/generated/base_contract.ts b/packages/0x.js/src/contract_wrappers/generated/base_contract.ts deleted file mode 100644 index d8fac7eea..000000000 --- a/packages/0x.js/src/contract_wrappers/generated/base_contract.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {TxData, TxDataPayable} from '@0xproject/types'; -import * as _ from 'lodash'; -import * as Web3 from 'web3'; - -export class BaseContract { - protected _web3ContractInstance: Web3.ContractInstance; - protected _defaults: Partial<TxData>; - protected async _applyDefaultsToTxDataAsync<T extends TxData|TxDataPayable>( - txData: T, - estimateGasAsync?: (txData: T) => Promise<number>, - ): Promise<TxData> { - // Gas amount sourced with the following priorities: - // 1. Optional param passed in to public method call - // 2. Global config passed in at library instantiation - // 3. Gas estimate calculation + safety margin - const removeUndefinedProperties = _.pickBy; - const txDataWithDefaults = { - ...removeUndefinedProperties(this._defaults), - ...removeUndefinedProperties(txData as any), - // HACK: TS can't prove that T is spreadable. - // Awaiting https://github.com/Microsoft/TypeScript/pull/13288 to be merged - }; - if (_.isUndefined(txDataWithDefaults.gas) && !_.isUndefined(estimateGasAsync)) { - const estimatedGas = await estimateGasAsync(txData); - txDataWithDefaults.gas = estimatedGas; - } - return txDataWithDefaults; - } - constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<TxData>) { - this._web3ContractInstance = web3ContractInstance; - this._defaults = defaults; - } -} diff --git a/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts index f54aaf0f8..e1806c6f2 100644 --- a/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts @@ -23,7 +23,7 @@ export class TokenRegistryWrapper extends ContractWrapper { address: metadata[0], name: metadata[1], symbol: metadata[2], - decimals: metadata[3].toNumber(), + decimals: metadata[3], }; return token; } @@ -50,7 +50,8 @@ export class TokenRegistryWrapper extends ContractWrapper { public async getTokenAddressesAsync(): Promise<string[]> { const tokenRegistryContract = await this._getTokenRegistryContractAsync(); const addresses = await tokenRegistryContract.getTokenAddresses.callAsync(); - return addresses; + const lowerCaseAddresses = _.map(addresses, address => address.toLowerCase()); + return lowerCaseAddresses; } /** * Retrieves a token by address currently listed in the Token Registry smart contract @@ -58,9 +59,10 @@ export class TokenRegistryWrapper extends ContractWrapper { */ public async getTokenIfExistsAsync(address: string): Promise<Token | undefined> { assert.isETHAddressHex('address', address); + const normalizedAddress = address.toLowerCase(); const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address); + const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(normalizedAddress); const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); return token; } @@ -115,14 +117,11 @@ export class TokenRegistryWrapper extends ContractWrapper { if (!_.isUndefined(this._tokenRegistryContractIfExists)) { return this._tokenRegistryContractIfExists; } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( + const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( artifacts.TokenRegistryArtifact, this._contractAddressIfExists, ); - const contractInstance = new TokenRegistryContract( - web3ContractInstance, - this._web3Wrapper.getContractDefaults(), - ); + const contractInstance = new TokenRegistryContract(this._web3Wrapper, abi, address); this._tokenRegistryContractIfExists = contractInstance; return this._tokenRegistryContractIfExists; } diff --git a/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts index f5d9d108a..211c7dfb4 100644 --- a/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts @@ -2,6 +2,7 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import { artifacts } from '../artifacts'; +import { assert } from '../utils/assert'; import { ContractWrapper } from './contract_wrapper'; import { TokenTransferProxyContract } from './generated/token_transfer_proxy'; @@ -22,8 +23,12 @@ export class TokenTransferProxyWrapper extends ContractWrapper { * @return Whether the exchangeContractAddress is authorized. */ public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> { + assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress); + const normalizedExchangeContractAddress = exchangeContractAddress.toLowerCase(); const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); - const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(exchangeContractAddress); + const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync( + normalizedExchangeContractAddress, + ); return isAuthorized; } /** @@ -54,14 +59,11 @@ export class TokenTransferProxyWrapper extends ContractWrapper { if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) { return this._tokenTransferProxyContractIfExists; } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( + const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( artifacts.TokenTransferProxyArtifact, this._contractAddressIfExists, ); - const contractInstance = new TokenTransferProxyContract( - web3ContractInstance, - this._web3Wrapper.getContractDefaults(), - ); + const contractInstance = new TokenTransferProxyContract(this._web3Wrapper, abi, address); this._tokenTransferProxyContractIfExists = contractInstance; return this._tokenTransferProxyContractIfExists; } diff --git a/packages/0x.js/src/contract_wrappers/token_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_wrapper.ts index a018006b8..0f688cb71 100644 --- a/packages/0x.js/src/contract_wrappers/token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_wrapper.ts @@ -46,10 +46,13 @@ export class TokenWrapper extends ContractWrapper { ): Promise<BigNumber> { assert.isETHAddressHex('ownerAddress', ownerAddress); assert.isETHAddressHex('tokenAddress', tokenAddress); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); - const tokenContract = await this._getTokenContractAsync(tokenAddress); + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let balance = await tokenContract.balanceOf.callAsync(ownerAddress, defaultBlock); + const txData = {}; + let balance = await tokenContract.balanceOf.callAsync(normalizedOwnerAddress, txData, defaultBlock); // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber balance = new BigNumber(balance); return balance; @@ -72,14 +75,17 @@ export class TokenWrapper extends ContractWrapper { amountInBaseUnits: BigNumber, txOpts: TransactionOpts = {}, ): Promise<string> { - await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper); assert.isETHAddressHex('spenderAddress', spenderAddress); assert.isETHAddressHex('tokenAddress', tokenAddress); + await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedSpenderAddress = spenderAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const tokenContract = await this._getTokenContractAsync(tokenAddress); - const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, { - from: ownerAddress, + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); + const txHash = await tokenContract.approve.sendTransactionAsync(normalizedSpenderAddress, amountInBaseUnits, { + from: normalizedOwnerAddress, gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, }); @@ -103,10 +109,16 @@ export class TokenWrapper extends ContractWrapper { spenderAddress: string, txOpts: TransactionOpts = {}, ): Promise<string> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isETHAddressHex('spenderAddress', spenderAddress); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); + const normalizedSpenderAddress = spenderAddress.toLowerCase(); const txHash = await this.setAllowanceAsync( - tokenAddress, - ownerAddress, - spenderAddress, + normalizedTokenAddress, + normalizedOwnerAddress, + normalizedSpenderAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, txOpts, ); @@ -128,10 +140,20 @@ export class TokenWrapper extends ContractWrapper { ): Promise<BigNumber> { assert.isETHAddressHex('ownerAddress', ownerAddress); assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isETHAddressHex('spenderAddress', spenderAddress); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); + const normalizedSpenderAddress = spenderAddress.toLowerCase(); - const tokenContract = await this._getTokenContractAsync(tokenAddress); + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let allowanceInBaseUnits = await tokenContract.allowance.callAsync(ownerAddress, spenderAddress, defaultBlock); + const txData = {}; + let allowanceInBaseUnits = await tokenContract.allowance.callAsync( + normalizedOwnerAddress, + normalizedSpenderAddress, + txData, + defaultBlock, + ); // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits); return allowanceInBaseUnits; @@ -149,9 +171,16 @@ export class TokenWrapper extends ContractWrapper { ): Promise<BigNumber> { assert.isETHAddressHex('ownerAddress', ownerAddress); assert.isETHAddressHex('tokenAddress', tokenAddress); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); - const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts); + const allowanceInBaseUnits = await this.getAllowanceAsync( + normalizedTokenAddress, + normalizedOwnerAddress, + proxyAddress, + methodOpts, + ); return allowanceInBaseUnits; } /** @@ -172,12 +201,14 @@ export class TokenWrapper extends ContractWrapper { ): Promise<string> { assert.isETHAddressHex('ownerAddress', ownerAddress); assert.isETHAddressHex('tokenAddress', tokenAddress); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); const txHash = await this.setAllowanceAsync( - tokenAddress, - ownerAddress, + normalizedTokenAddress, + normalizedOwnerAddress, proxyAddress, amountInBaseUnits, txOpts, @@ -200,9 +231,13 @@ export class TokenWrapper extends ContractWrapper { ownerAddress: string, txOpts: TransactionOpts = {}, ): Promise<string> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); const txHash = await this.setProxyAllowanceAsync( - tokenAddress, - ownerAddress, + normalizedTokenAddress, + normalizedOwnerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, txOpts, ); @@ -225,19 +260,22 @@ export class TokenWrapper extends ContractWrapper { txOpts: TransactionOpts = {}, ): Promise<string> { assert.isETHAddressHex('tokenAddress', tokenAddress); - await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper); assert.isETHAddressHex('toAddress', toAddress); + await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedFromAddress = fromAddress.toLowerCase(); + const normalizedToAddress = toAddress.toLowerCase(); assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const tokenContract = await this._getTokenContractAsync(tokenAddress); + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); - const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress); + const fromAddressBalance = await this.getBalanceAsync(normalizedTokenAddress, normalizedFromAddress); if (fromAddressBalance.lessThan(amountInBaseUnits)) { throw new Error(ZeroExError.InsufficientBalanceForTransfer); } - const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, { - from: fromAddress, + const txHash = await tokenContract.transfer.sendTransactionAsync(normalizedToAddress, amountInBaseUnits, { + from: normalizedFromAddress, gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, }); @@ -265,30 +303,38 @@ export class TokenWrapper extends ContractWrapper { amountInBaseUnits: BigNumber, txOpts: TransactionOpts = {}, ): Promise<string> { - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.isETHAddressHex('fromAddress', fromAddress); assert.isETHAddressHex('toAddress', toAddress); + assert.isETHAddressHex('fromAddress', fromAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper); + const normalizedToAddress = toAddress.toLowerCase(); + const normalizedFromAddress = fromAddress.toLowerCase(); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedSenderAddress = senderAddress.toLowerCase(); assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const tokenContract = await this._getTokenContractAsync(tokenAddress); + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); - const fromAddressAllowance = await this.getAllowanceAsync(tokenAddress, fromAddress, senderAddress); + const fromAddressAllowance = await this.getAllowanceAsync( + normalizedTokenAddress, + normalizedFromAddress, + normalizedSenderAddress, + ); if (fromAddressAllowance.lessThan(amountInBaseUnits)) { throw new Error(ZeroExError.InsufficientAllowanceForTransfer); } - const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress); + const fromAddressBalance = await this.getBalanceAsync(normalizedTokenAddress, normalizedFromAddress); if (fromAddressBalance.lessThan(amountInBaseUnits)) { throw new Error(ZeroExError.InsufficientBalanceForTransfer); } const txHash = await tokenContract.transferFrom.sendTransactionAsync( - fromAddress, - toAddress, + normalizedFromAddress, + normalizedToAddress, amountInBaseUnits, { - from: senderAddress, + from: normalizedSenderAddress, gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, }, @@ -311,11 +357,12 @@ export class TokenWrapper extends ContractWrapper { callback: EventCallback<ArgsType>, ): string { assert.isETHAddressHex('tokenAddress', tokenAddress); + const normalizedTokenAddress = tokenAddress.toLowerCase(); assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); assert.isFunction('callback', callback); const subscriptionToken = this._subscribe<ArgsType>( - tokenAddress, + normalizedTokenAddress, eventName, indexFilterValues, artifacts.TokenArtifact.abi, @@ -333,7 +380,7 @@ export class TokenWrapper extends ContractWrapper { /** * Cancels all existing subscriptions */ - public _unsubscribeAll(): void { + public unsubscribeAll(): void { super._unsubscribeAll(); } /** @@ -352,11 +399,12 @@ export class TokenWrapper extends ContractWrapper { indexFilterValues: IndexedFilterValues, ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { assert.isETHAddressHex('tokenAddress', tokenAddress); + const normalizedTokenAddress = tokenAddress.toLowerCase(); assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); const logs = await this._getLogsAsync<ArgsType>( - tokenAddress, + normalizedTokenAddress, eventName, blockRange, indexFilterValues, @@ -365,21 +413,22 @@ export class TokenWrapper extends ContractWrapper { return logs; } private _invalidateContractInstances(): void { - this._unsubscribeAll(); + this.unsubscribeAll(); this._tokenContractsByAddress = {}; } private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> { - let tokenContract = this._tokenContractsByAddress[tokenAddress]; + const normalizedTokenAddress = tokenAddress.toLowerCase(); + let tokenContract = this._tokenContractsByAddress[normalizedTokenAddress]; if (!_.isUndefined(tokenContract)) { return tokenContract; } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( + const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( artifacts.TokenArtifact, - tokenAddress, + normalizedTokenAddress, ); - const contractInstance = new TokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); + const contractInstance = new TokenContract(this._web3Wrapper, abi, address); tokenContract = contractInstance; - this._tokenContractsByAddress[tokenAddress] = tokenContract; + this._tokenContractsByAddress[normalizedTokenAddress] = tokenContract; return tokenContract; } } diff --git a/packages/0x.js/src/schemas/zero_ex_config_schema.ts b/packages/0x.js/src/schemas/zero_ex_config_schema.ts index 546b1c2d0..a9c3c64fc 100644 --- a/packages/0x.js/src/schemas/zero_ex_config_schema.ts +++ b/packages/0x.js/src/schemas/zero_ex_config_schema.ts @@ -1,27 +1,5 @@ export const zeroExConfigSchema = { id: '/ZeroExConfig', - properties: { - networkId: { - type: 'number', - minimum: 0, - }, - gasPrice: { $ref: '/Number' }, - exchangeContractAddress: { $ref: '/Address' }, - tokenRegistryContractAddress: { $ref: '/Address' }, - orderWatcherConfig: { - type: 'object', - properties: { - pollingIntervalMs: { - type: 'number', - minimum: 0, - }, - numConfirmations: { - type: 'number', - minimum: 0, - }, - }, - }, - }, + oneOf: [{ $ref: '/ZeroExPrivateNetworkConfig' }, { $ref: '/ZeroExPublicNetworkConfig' }], type: 'object', - required: ['networkId'], }; diff --git a/packages/0x.js/src/schemas/zero_ex_private_network_config_schema.ts b/packages/0x.js/src/schemas/zero_ex_private_network_config_schema.ts new file mode 100644 index 000000000..f7f649a6d --- /dev/null +++ b/packages/0x.js/src/schemas/zero_ex_private_network_config_schema.ts @@ -0,0 +1,35 @@ +export const zeroExPrivateNetworkConfigSchema = { + id: '/ZeroExPrivateNetworkConfig', + properties: { + networkId: { + type: 'number', + minimum: 1, + }, + gasPrice: { $ref: '/Number' }, + zrxContractAddress: { $ref: '/Address' }, + exchangeContractAddress: { $ref: '/Address' }, + tokenRegistryContractAddress: { $ref: '/Address' }, + tokenTransferProxyContractAddress: { $ref: '/Address' }, + orderWatcherConfig: { + type: 'object', + properties: { + pollingIntervalMs: { + type: 'number', + minimum: 0, + }, + numConfirmations: { + type: 'number', + minimum: 0, + }, + }, + }, + }, + type: 'object', + required: [ + 'networkId', + 'zrxContractAddress', + 'exchangeContractAddress', + 'tokenRegistryContractAddress', + 'tokenTransferProxyContractAddress', + ], +}; diff --git a/packages/0x.js/src/schemas/zero_ex_public_network_config_schema.ts b/packages/0x.js/src/schemas/zero_ex_public_network_config_schema.ts new file mode 100644 index 000000000..9da31481a --- /dev/null +++ b/packages/0x.js/src/schemas/zero_ex_public_network_config_schema.ts @@ -0,0 +1,29 @@ +export const zeroExPublicNetworkConfigSchema = { + id: '/ZeroExPublicNetworkConfig', + properties: { + networkId: { + type: 'number', + enum: [1, 3, 4, 42, 50], + }, + gasPrice: { $ref: '/Number' }, + zrxContractAddress: { $ref: '/Address' }, + exchangeContractAddress: { $ref: '/Address' }, + tokenRegistryContractAddress: { $ref: '/Address' }, + tokenTransferProxyContractAddress: { $ref: '/Address' }, + orderWatcherConfig: { + type: 'object', + properties: { + pollingIntervalMs: { + type: 'number', + minimum: 0, + }, + numConfirmations: { + type: 'number', + minimum: 0, + }, + }, + }, + }, + type: 'object', + required: ['networkId'], +}; diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index ab97f7775..2f17e30c2 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -127,7 +127,7 @@ export interface SignedOrder extends Order { } // [address, name, symbol, decimals, ipfsHash, swarmHash] -export type TokenMetadata = [string, string, string, BigNumber, string, string]; +export type TokenMetadata = [string, string, string, number, string, string]; export interface Token { name: string; @@ -196,7 +196,7 @@ export interface OrderStateWatcherConfig { } /* - * networkId: The id of the underlying ethereum network your provider is connected to. (1-mainnet, 42-kovan, 50-testrpc) + * networkId: The id of the underlying ethereum network your provider is connected to. (1-mainnet, 3-ropsten, 4-rinkeby, 42-kovan, 50-testrpc) * gasPrice: Gas price to use with every transaction * exchangeContractAddress: The address of an exchange contract to use * zrxContractAddress: The address of the ZRX contract to use diff --git a/packages/0x.js/test/ether_token_wrapper_test.ts b/packages/0x.js/test/ether_token_wrapper_test.ts index da49ec467..68f2c6f66 100644 --- a/packages/0x.js/test/ether_token_wrapper_test.ts +++ b/packages/0x.js/test/ether_token_wrapper_test.ts @@ -75,11 +75,14 @@ describe('EtherTokenWrapper', () => { const contractAddressIfExists = zeroEx.etherToken.getContractAddressIfExists(); expect(contractAddressIfExists).to.not.be.undefined(); }); - it('should return undefined if connected to an unknown network', () => { + it('should throw if connected to a private network and contract addresses are not specified', () => { const UNKNOWN_NETWORK_NETWORK_ID = 10; - const unknownNetworkZeroEx = new ZeroEx(web3.currentProvider, { networkId: UNKNOWN_NETWORK_NETWORK_ID }); - const contractAddressIfExists = unknownNetworkZeroEx.etherToken.getContractAddressIfExists(); - expect(contractAddressIfExists).to.be.undefined(); + expect( + () => + new ZeroEx(web3.currentProvider, { + networkId: UNKNOWN_NETWORK_NETWORK_ID, + } as any), + ).to.throw(); }); }); describe('#depositAsync', () => { @@ -155,7 +158,7 @@ describe('EtherTokenWrapper', () => { etherTokenAddress = etherToken.address; }); afterEach(() => { - zeroEx.etherToken._unsubscribeAll(); + zeroEx.etherToken.unsubscribeAll(); }); // Hack: Mocha does not allow a test to be both async and have a `done` callback // Since we need to await the receipt of the event in the `subscribe` callback, diff --git a/packages/0x.js/test/exchange_wrapper_test.ts b/packages/0x.js/test/exchange_wrapper_test.ts index 325426438..688be628f 100644 --- a/packages/0x.js/test/exchange_wrapper_test.ts +++ b/packages/0x.js/test/exchange_wrapper_test.ts @@ -922,7 +922,7 @@ describe('ExchangeWrapper', () => { ); }); afterEach(async () => { - zeroEx.exchange._unsubscribeAll(); + zeroEx.exchange.unsubscribeAll(); }); // Hack: Mocha does not allow a test to be both async and have a `done` callback // Since we need to await the receipt of the event in the `subscribe` callback, diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index b49dee8e5..7f79e3802 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -9,10 +9,10 @@ import * as Web3 from 'web3'; import { ZeroEx } from '../src/0x'; import { ExpirationWatcher } from '../src/order_watcher/expiration_watcher'; import { DoneCallback, Token } from '../src/types'; -import { constants } from '../src/utils/constants'; import { utils } from '../src/utils/utils'; import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; import { FillScenarios } from './utils/fill_scenarios'; import { reportNoErrorCallbackErrors } from './utils/report_callback_errors'; import { TokenUtils } from './utils/token_utils'; diff --git a/packages/0x.js/test/subscription_test.ts b/packages/0x.js/test/subscription_test.ts index 337e2effa..f485bf84b 100644 --- a/packages/0x.js/test/subscription_test.ts +++ b/packages/0x.js/test/subscription_test.ts @@ -49,7 +49,7 @@ describe('SubscriptionTest', () => { tokenAddress = token.address; }); afterEach(() => { - zeroEx.token._unsubscribeAll(); + zeroEx.token.unsubscribeAll(); _.each(stubs, s => s.restore()); stubs = []; }); @@ -76,7 +76,7 @@ describe('SubscriptionTest', () => { const callback = (err: Error | null, logEvent?: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop; zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error('JSON RPC error'))]; - zeroEx.token._unsubscribeAll(); + zeroEx.token.unsubscribeAll(); done(); })().catch(done); }); diff --git a/packages/0x.js/test/token_wrapper_test.ts b/packages/0x.js/test/token_wrapper_test.ts index 34ebe30c2..070d6ec47 100644 --- a/packages/0x.js/test/token_wrapper_test.ts +++ b/packages/0x.js/test/token_wrapper_test.ts @@ -377,7 +377,7 @@ describe('TokenWrapper', () => { tokenAddress = token.address; }); afterEach(() => { - zeroEx.token._unsubscribeAll(); + zeroEx.token.unsubscribeAll(); }); // Hack: Mocha does not allow a test to be both async and have a `done` callback // Since we need to await the receipt of the event in the `subscribe` callback, diff --git a/packages/0x.js/test/utils/fill_scenarios.ts b/packages/0x.js/test/utils/fill_scenarios.ts index 1a61487f4..8b1308298 100644 --- a/packages/0x.js/test/utils/fill_scenarios.ts +++ b/packages/0x.js/test/utils/fill_scenarios.ts @@ -35,12 +35,8 @@ export class FillScenarios { const web3Wrapper = (this._zeroEx as any)._web3Wrapper as Web3Wrapper; for (const token of this._tokens) { if (token.symbol !== 'ZRX' && token.symbol !== 'WETH') { - const contractInstance = web3Wrapper.getContractInstance( - artifacts.DummyTokenArtifact.abi, - token.address, - ); const defaults = {}; - const dummyToken = new DummyTokenContract(contractInstance, defaults); + const dummyToken = new DummyTokenContract(web3Wrapper, artifacts.DummyTokenArtifact.abi, token.address); const tokenSupply = ZeroEx.toBaseUnitAmount(INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS, token.decimals); const txHash = await dummyToken.setBalance.sendTransactionAsync(this._coinbase, tokenSupply, { from: this._coinbase, diff --git a/packages/0x.js/tsconfig.json b/packages/0x.js/tsconfig.json index 117f51e83..a6b5c71c2 100644 --- a/packages/0x.js/tsconfig.json +++ b/packages/0x.js/tsconfig.json @@ -9,6 +9,7 @@ "./test/**/*", "../../node_modules/types-bn/index.d.ts", "../../node_modules/types-ethereumjs-util/index.d.ts", + "../../node_modules/ethers-typescript-typings/index.d.ts", "../../node_modules/web3-typescript-typings/index.d.ts", "../../node_modules/chai-typescript-typings/index.d.ts", "../../node_modules/chai-as-promised-typescript-typings/index.d.ts" diff --git a/packages/abi-gen/CHANGELOG.md b/packages/abi-gen/CHANGELOG.md index c86d6fb55..d9198d3ed 100644 --- a/packages/abi-gen/CHANGELOG.md +++ b/packages/abi-gen/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## v0.2.3 - _TBD, 2018_ + + * Add a `backend` parameter that allows you to specify the Ethereum library you use in your templates (`web3` or `ethers`). Ethers auto-converts small ints to numbers whereas Web3 doesn't. Defaults to `web3` (#413) + * Add support for [tuple types](https://solidity.readthedocs.io/en/develop/abi-spec.html#handling-tuple-types) (#413) + * Add `hasReturnValue` to context data (#413) + ## v0.2.1 - _February 9, 2018_ * Fix publishing issue where .npmignore was not properly excluding undesired content (#389) diff --git a/packages/abi-gen/src/index.ts b/packages/abi-gen/src/index.ts index bc5a974a9..7c29f7d1d 100644 --- a/packages/abi-gen/src/index.ts +++ b/packages/abi-gen/src/index.ts @@ -11,13 +11,14 @@ import * as yargs from 'yargs'; import toSnakeCase = require('to-snake-case'); import * as Web3 from 'web3'; -import { ContextData, ParamKind } from './types'; +import { ContextData, ContractsBackend, ParamKind } from './types'; import { utils } from './utils'; const ABI_TYPE_CONSTRUCTOR = 'constructor'; const ABI_TYPE_METHOD = 'function'; const ABI_TYPE_EVENT = 'event'; const DEFAULT_NETWORK_ID = 50; +const DEFAULT_BACKEND = 'web3'; const args = yargs .option('abis', { @@ -43,6 +44,12 @@ const args = yargs demandOption: true, normalize: true, }) + .option('backend', { + describe: `The backing Ethereum library your app uses. Either 'web3' or 'ethers'. Ethers auto-converts small ints to numbers whereas Web3 doesn't.`, + type: 'string', + choices: [ContractsBackend.Web3, ContractsBackend.Ethers], + default: DEFAULT_BACKEND, + }) .option('network-id', { describe: 'ID of the network where contract ABIs are nested in artifacts', type: 'number', @@ -73,8 +80,8 @@ function writeOutputFile(name: string, renderedTsCode: string): void { utils.log(`Created: ${chalk.bold(filePath)}`); } -Handlebars.registerHelper('parameterType', utils.solTypeToTsType.bind(utils, ParamKind.Input)); -Handlebars.registerHelper('returnType', utils.solTypeToTsType.bind(utils, ParamKind.Output)); +Handlebars.registerHelper('parameterType', utils.solTypeToTsType.bind(utils, ParamKind.Input, args.backend)); +Handlebars.registerHelper('returnType', utils.solTypeToTsType.bind(utils, ParamKind.Output, args.backend)); if (args.partials) { registerPartials(args.partials); @@ -129,6 +136,7 @@ for (const abiFileName of abiFileNames) { const methodData = { ...methodAbi, singleReturnValue: methodAbi.outputs.length === 1, + hasReturnValue: methodAbi.outputs.length !== 0, }; return methodData; }); diff --git a/packages/abi-gen/src/types.ts b/packages/abi-gen/src/types.ts index e82ab824b..deddb1857 100644 --- a/packages/abi-gen/src/types.ts +++ b/packages/abi-gen/src/types.ts @@ -12,8 +12,14 @@ export enum AbiType { Fallback = 'fallback', } +export enum ContractsBackend { + Web3 = 'web3', + Ethers = 'ethers', +} + export interface Method extends Web3.MethodAbi { singleReturnValue: boolean; + hasReturnValue: boolean; } export interface ContextData { diff --git a/packages/abi-gen/src/utils.ts b/packages/abi-gen/src/utils.ts index 14255643a..3e4ff619a 100644 --- a/packages/abi-gen/src/utils.ts +++ b/packages/abi-gen/src/utils.ts @@ -3,17 +3,23 @@ import * as _ from 'lodash'; import * as path from 'path'; import * as Web3 from 'web3'; -import { AbiType, ParamKind } from './types'; +import { AbiType, ContractsBackend, ParamKind } from './types'; export const utils = { - solTypeToTsType(paramKind: ParamKind, solType: string): string { + solTypeToTsType( + paramKind: ParamKind, + backend: ContractsBackend, + solType: string, + components?: Web3.DataItem[], + ): string { const trailingArrayRegex = /\[\d*\]$/; if (solType.match(trailingArrayRegex)) { const arrayItemSolType = solType.replace(trailingArrayRegex, ''); - const arrayItemTsType = utils.solTypeToTsType(paramKind, arrayItemSolType); - const arrayTsType = utils.isUnionType(arrayItemTsType) - ? `Array<${arrayItemTsType}>` - : `${arrayItemTsType}[]`; + const arrayItemTsType = utils.solTypeToTsType(paramKind, backend, arrayItemSolType, components); + const arrayTsType = + utils.isUnionType(arrayItemTsType) || utils.isObjectType(arrayItemTsType) + ? `Array<${arrayItemTsType}>` + : `${arrayItemTsType}[]`; return arrayTsType; } else { const solTypeRegexToTsType = [ @@ -24,25 +30,49 @@ export const utils = { { regex: '^bytes\\d*$', tsType: 'string' }, ]; if (paramKind === ParamKind.Input) { - // web3 allows to pass those an non-bignumbers and that's nice - // but it always returns stuff as BigNumbers + // web3 and ethers allow to pass those as numbers instead of bignumbers solTypeRegexToTsType.unshift({ regex: '^u?int(8|16|32)?$', tsType: 'number|BigNumber', }); } + if (backend === ContractsBackend.Ethers && paramKind === ParamKind.Output) { + // ethers-contracts automatically converts small BigNumbers to numbers + solTypeRegexToTsType.unshift({ + regex: '^u?int(8|16|32|48)?$', + tsType: 'number', + }); + } for (const regexAndTxType of solTypeRegexToTsType) { const { regex, tsType } = regexAndTxType; if (solType.match(regex)) { return tsType; } } + const TUPLE_TYPE_REGEX = '^tuple$'; + if (solType.match(TUPLE_TYPE_REGEX)) { + const componentsType = _.map(components, component => { + const componentValueType = utils.solTypeToTsType( + paramKind, + backend, + component.type, + component.components, + ); + const componentType = `${component.name}: ${componentValueType}`; + return componentType; + }); + const tsType = `{${componentsType}}`; + return tsType; + } throw new Error(`Unknown Solidity type found: ${solType}`); } }, isUnionType(tsType: string): boolean { return tsType === 'number|BigNumber'; }, + isObjectType(tsType: string): boolean { + return /^{.*}$/.test(tsType); + }, log(...args: any[]): void { console.log(...args); // tslint:disable-line:no-console }, diff --git a/packages/assert/CHANGELOG.md b/packages/assert/CHANGELOG.md index 23c2c5e56..6721c4cb6 100644 --- a/packages/assert/CHANGELOG.md +++ b/packages/assert/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## v0.1.0 - _TBD, 2018_ + + * Remove isETHAddressHex checksum address check and assume address will be lowercased (#373) + * Add an optional parameter `subSchemas` to `doesConformToSchema` method (#385) + ## v0.0.18 - _February 9, 2017_ * Fix publishing issue where .npmignore was not properly excluding undesired content (#389) diff --git a/packages/assert/src/index.ts b/packages/assert/src/index.ts index 7ad574ec7..40d083cb6 100644 --- a/packages/assert/src/index.ts +++ b/packages/assert/src/index.ts @@ -33,11 +33,8 @@ export const assert = { ); }, isETHAddressHex(variableName: string, value: string): void { + this.assert(_.isString(value), this.typeAssertionMessage(variableName, 'string', value)); this.assert(addressUtils.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value)); - this.assert( - addressUtils.isAddress(value) && value.toLowerCase() === value, - `Checksummed addresses are not supported. Convert ${variableName} to lower case before passing`, - ); }, doesBelongToStringEnum( variableName: string, @@ -66,8 +63,11 @@ export const assert = { const isWeb3Provider = _.isFunction(value.send) || _.isFunction(value.sendAsync); this.assert(isWeb3Provider, this.typeAssertionMessage(variableName, 'Web3.Provider', value)); }, - doesConformToSchema(variableName: string, value: any, schema: Schema): void { + doesConformToSchema(variableName: string, value: any, schema: Schema, subSchemas?: Schema[]): void { const schemaValidator = new SchemaValidator(); + if (!_.isUndefined(subSchemas)) { + _.map(subSchemas, schemaValidator.addSchema.bind(schemaValidator)); + } const validationResult = schemaValidator.validate(value, schema); const hasValidationErrors = validationResult.errors.length > 0; const msg = `Expected ${variableName} to conform to schema ${schema.id} diff --git a/packages/base-contract/.npmignore b/packages/base-contract/.npmignore new file mode 100644 index 000000000..ad5ffcd56 --- /dev/null +++ b/packages/base-contract/.npmignore @@ -0,0 +1,5 @@ +.* +yarn-error.log +/scripts/ +/src/ +tsconfig.json diff --git a/packages/base-contract/CHANGELOG.md b/packages/base-contract/CHANGELOG.md new file mode 100644 index 000000000..74e983f1c --- /dev/null +++ b/packages/base-contract/CHANGELOG.md @@ -0,0 +1,5 @@ +# CHANGELOG + +## v0.0.1 - _TBD, 2018_ + + * Initial release (#TBD) diff --git a/packages/base-contract/README.md b/packages/base-contract/README.md new file mode 100644 index 000000000..ff0d4d303 --- /dev/null +++ b/packages/base-contract/README.md @@ -0,0 +1,53 @@ +## @0xproject/base-contract + +BaseContract to derive all auto-generated wrappers from + +## Installation + +```bash +yarn add @0xproject/base-contract +``` + +## Usage + +```javascript +import { BaseContract } from '@0xproject/base-contract'; +``` + +## Contributing + +We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +```bash +yarn build +``` + +or + +```bash +yarn build:watch +``` + +### Lint + +```bash +yarn lint +``` diff --git a/packages/base-contract/package.json b/packages/base-contract/package.json new file mode 100644 index 000000000..6edbed161 --- /dev/null +++ b/packages/base-contract/package.json @@ -0,0 +1,39 @@ +{ + "name": "@0xproject/base-contract", + "version": "0.0.1", + "description": "0x Base TS contract", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build:watch": "tsc -w", + "build": "tsc", + "clean": "shx rm -rf lib", + "lint": "tslint --project . 'src/**/*.ts'" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/base-contract/README.md", + "devDependencies": { + "@0xproject/tslint-config": "^0.4.9", + "@types/lodash": "^4.14.86", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typescript": "2.7.1", + "ethers-typescript-typings": "^0.0.1", + "web3-typescript-typings": "^0.9.11" + }, + "dependencies": { + "@0xproject/types": "^0.2.3", + "@0xproject/web3-wrapper": "^0.1.14", + "ethers-contracts": "^2.2.1", + "lodash": "^4.17.4", + "web3": "^0.20.0" + } +} diff --git a/packages/base-contract/src/index.ts b/packages/base-contract/src/index.ts new file mode 100644 index 000000000..cc1e16a13 --- /dev/null +++ b/packages/base-contract/src/index.ts @@ -0,0 +1,68 @@ +import { TxData, TxDataPayable } from '@0xproject/types'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as ethersContracts from 'ethers-contracts'; +import * as _ from 'lodash'; +import * as Web3 from 'web3'; + +export class BaseContract { + protected _ethersInterface: ethersContracts.Interface; + protected _web3Wrapper: Web3Wrapper; + public abi: Web3.ContractAbi; + public address: string; + protected static _transformABIData( + abis: Web3.DataItem[], + values: any[], + transformation: (type: string, value: any) => any, + ): any { + return _.map(values, (value: any, i: number) => + BaseContract._transformTypedData(abis[i].type, value, transformation), + ); + } + 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; + } + 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 async _applyDefaultsToTxDataAsync<T extends Partial<TxData | TxDataPayable>>( + txData: T, + estimateGasAsync?: (txData: T) => Promise<number>, + ): Promise<TxData> { + // Gas amount sourced with the following priorities: + // 1. Optional param passed in to public method call + // 2. Global config passed in at library instantiation + // 3. Gas estimate calculation + safety margin + const removeUndefinedProperties = _.pickBy; + const txDataWithDefaults = { + to: this.address, + ...removeUndefinedProperties(this._web3Wrapper.getContractDefaults()), + ...removeUndefinedProperties(txData as any), + // HACK: TS can't prove that T is spreadable. + // Awaiting https://github.com/Microsoft/TypeScript/pull/13288 to be merged + }; + if (_.isUndefined(txDataWithDefaults.gas) && !_.isUndefined(estimateGasAsync)) { + const estimatedGas = await estimateGasAsync(txData); + txDataWithDefaults.gas = estimatedGas; + } + return txDataWithDefaults; + } + constructor(web3Wrapper: Web3Wrapper, abi: Web3.ContractAbi, address: string) { + this._web3Wrapper = web3Wrapper; + this.abi = abi; + this.address = address; + this._ethersInterface = new ethersContracts.Interface(abi); + } +} diff --git a/packages/base-contract/tsconfig.json b/packages/base-contract/tsconfig.json new file mode 100644 index 000000000..8114d99cd --- /dev/null +++ b/packages/base-contract/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": [ + "./src/**/*", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/ethers-typescript-typings/index.d.ts" + ] +} diff --git a/packages/base-contract/tslint.json b/packages/base-contract/tslint.json new file mode 100644 index 000000000..ffaefe83a --- /dev/null +++ b/packages/base-contract/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0xproject/tslint-config"] +} diff --git a/packages/connect/README.md b/packages/connect/README.md index 63faf5271..7302322e6 100644 --- a/packages/connect/README.md +++ b/packages/connect/README.md @@ -10,8 +10,8 @@ yarn add @0xproject/connect ## Usage -* [Docs](https://0xproject.com/docs/connect) -* [Tutorials](https://0xproject.com/wiki#connect) +* [Docs](https://0xproject.com/docs/connect) +* [Tutorials](https://0xproject.com/wiki#connect) ## Contributing diff --git a/packages/0x.js/contract_templates/contract.handlebars b/packages/contract_templates/contract.handlebars index 33699b8a7..2e8ac3f06 100644 --- a/packages/0x.js/contract_templates/contract.handlebars +++ b/packages/contract_templates/contract.handlebars @@ -1,15 +1,17 @@ /** * This file is auto-generated using abi-gen. Don't edit directly. - * Templates can be found at https://github.com/0xProject/0x.js/tree/development/packages/0x.js/contract_templates. + * Templates can be found at https://github.com/0xProject/0x.js/tree/development/packages/contract_templates. */ // tslint:disable:no-consecutive-blank-lines // tslint:disable-next-line:no-unused-variable +import { BaseContract } from '@0xproject/base-contract'; import { TxData, TxDataPayable } from '@0xproject/types'; 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'; -import {BaseContract} from './base_contract'; - {{#if events}} export type {{contractName}}ContractEventArgs = {{#each events}} @@ -28,6 +30,7 @@ export enum {{contractName}}Events { {{/each}} {{/if}} +// tslint:disable:no-parameter-reassignment export class {{contractName}}Contract extends BaseContract { {{#each methods}} {{#this.constant}} @@ -37,8 +40,8 @@ export class {{contractName}}Contract extends BaseContract { {{> tx contractName=../contractName}} {{/this.constant}} {{/each}} - constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<TxData>) { - super(web3ContractInstance, defaults); - classUtils.bindAll(this, ['_web3ContractInstance', '_defaults']); + constructor(web3Wrapper: Web3Wrapper, abi: Web3.ContractAbi, address: string) { + super(web3Wrapper, abi, address); + classUtils.bindAll(this, ['_ethersInterface', 'address', 'abi', '_web3Wrapper']); } } // tslint:disable:max-file-line-count diff --git a/packages/contract_templates/partials/call.handlebars b/packages/contract_templates/partials/call.handlebars new file mode 100644 index 000000000..cfb9bea82 --- /dev/null +++ b/packages/contract_templates/partials/call.handlebars @@ -0,0 +1,3 @@ +public {{this.name}} = { + {{> callAsync}} +}; diff --git a/packages/contract_templates/partials/callAsync.handlebars b/packages/contract_templates/partials/callAsync.handlebars new file mode 100644 index 000000000..93d347145 --- /dev/null +++ b/packages/contract_templates/partials/callAsync.handlebars @@ -0,0 +1,30 @@ +{{#hasReturnValue}} +async callAsync( +{{> typed_params inputs=inputs}} +{{#this.payable}} + txData: TxDataPayable = {}, +{{/this.payable}} +{{^this.payable}} + txData: TxData = {}, +{{/this.payable}} + defaultBlock?: Web3.BlockParam, +): Promise<{{> return_type outputs=outputs}}> { + const self = this as {{contractName}}Contract; + const inputAbi = _.find(this.abi, {name: '{{this.name}}'}).inputs; + [{{> params inputs=inputs}}] = BaseContract._transformABIData(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(this)); + const encodedData = self._ethersInterface.functions.{{this.name}}( + {{> params inputs=inputs}} + ).data; + const callData = await self._applyDefaultsToTxDataAsync( + { + data: encodedData, + } + ) + const rawCallResult = await self._web3Wrapper.callAsync(callData, defaultBlock); + const outputAbi = _.find(this.abi, {name: '{{this.name}}'}).outputs as Web3.DataItem[]; + const outputParamsTypes = _.map(outputAbi, 'type'); + let resultArray = ethersContracts.Interface.decodeParams(outputParamsTypes, rawCallResult) as any; + resultArray = BaseContract._transformABIData(outputAbi, resultArray, BaseContract._lowercaseAddress.bind(this)); + return resultArray{{#singleReturnValue}}[0]{{/singleReturnValue}}; +}, +{{/hasReturnValue}} diff --git a/packages/0x.js/contract_templates/partials/event.handlebars b/packages/contract_templates/partials/event.handlebars index 6d68d4c0f..3c6100e4f 100644 --- a/packages/0x.js/contract_templates/partials/event.handlebars +++ b/packages/contract_templates/partials/event.handlebars @@ -1,5 +1,5 @@ export interface {{name}}ContractEventArgs { {{#each inputs}} - {{name}}: {{#returnType type}}{{/returnType}}; + {{name}}: {{#returnType type components}}{{/returnType}}; {{/each}} } diff --git a/packages/0x.js/contract_templates/partials/params.handlebars b/packages/contract_templates/partials/params.handlebars index ac5d4ae85..ac5d4ae85 100644 --- a/packages/0x.js/contract_templates/partials/params.handlebars +++ b/packages/contract_templates/partials/params.handlebars diff --git a/packages/contract_templates/partials/return_type.handlebars b/packages/contract_templates/partials/return_type.handlebars new file mode 100644 index 000000000..9dd509953 --- /dev/null +++ b/packages/contract_templates/partials/return_type.handlebars @@ -0,0 +1,10 @@ +{{#if outputs.length}} +{{#singleReturnValue}} +{{#returnType outputs.0.type components}}{{/returnType}} +{{/singleReturnValue}} +{{^singleReturnValue}} +[{{#each outputs}}{{#returnType type components}}{{/returnType}}{{#unless @last}}, {{/unless}}{{/each}}] +{{/singleReturnValue}} +{{else}} +void +{{/if}} diff --git a/packages/0x.js/contract_templates/partials/tx.handlebars b/packages/contract_templates/partials/tx.handlebars index 9df83266a..347a482d6 100644 --- a/packages/0x.js/contract_templates/partials/tx.handlebars +++ b/packages/contract_templates/partials/tx.handlebars @@ -9,19 +9,22 @@ public {{this.name}} = { {{/this.payable}} ): Promise<string> { const self = this as {{contractName}}Contract; + const inputAbi = _.find(this.abi, {name: '{{this.name}}'}).inputs; + [{{> params inputs=inputs}}] = BaseContract._transformABIData(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(this)); + const encodedData = this._ethersInterface.functions.{{this.name}}( + {{> params inputs=inputs}} + ).data const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( - txData, + { + ...txData, + data: encodedData, + }, self.{{this.name}}.estimateGasAsync.bind( self, {{> params inputs=inputs}} ), ); - const txHash = await promisify<string>( - self._web3ContractInstance.{{this.name}}, self._web3ContractInstance, - )( - {{> params inputs=inputs}} - txDataWithDefaults, - ); + const txHash = await this._web3Wrapper.sendTransactionAsync(txDataWithDefaults); return txHash; }, async estimateGasAsync( @@ -29,15 +32,16 @@ public {{this.name}} = { txData: TxData = {}, ): Promise<number> { const self = this as {{contractName}}Contract; - const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( - txData, - ); - const gas = await promisify<number>( - self._web3ContractInstance.{{this.name}}.estimateGas, self._web3ContractInstance, - )( + const encodedData = this._ethersInterface.functions.{{this.name}}( {{> params inputs=inputs}} - txDataWithDefaults, + ).data + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( + { + ...txData, + data: encodedData, + } ); + const gas = await this._web3Wrapper.estimateGasAsync(txDataWithDefaults); return gas; }, getABIEncodedTransactionData( @@ -45,7 +49,10 @@ public {{this.name}} = { txData: TxData = {}, ): string { const self = this as {{contractName}}Contract; - const abiEncodedTransactionData = self._web3ContractInstance.{{this.name}}.getData(); + const abiEncodedTransactionData = this._ethersInterface.functions.{{this.name}}( + {{> params inputs=inputs}} + ).data return abiEncodedTransactionData; }, + {{> callAsync}} }; diff --git a/packages/contract_templates/partials/typed_params.handlebars b/packages/contract_templates/partials/typed_params.handlebars new file mode 100644 index 000000000..c100e58f7 --- /dev/null +++ b/packages/contract_templates/partials/typed_params.handlebars @@ -0,0 +1,3 @@ +{{#each inputs}} + {{name}}: {{#parameterType type components}}{{/parameterType}}, +{{/each}} diff --git a/packages/contracts/README.md b/packages/contracts/README.md index 11b9e5056..c209edf02 100644 --- a/packages/contracts/README.md +++ b/packages/contracts/README.md @@ -4,11 +4,11 @@ Smart contracts that implement the 0x protocol. ## Usage -* [Docs](https://0xproject.com/docs/contracts) -* [Overview of 0x protocol architecture](https://0xproject.com/wiki#Architecture) -* [0x smart contract interactions](https://0xproject.com/wiki#Contract-Interactions) -* [Deployed smart contract addresses](https://0xproject.com/wiki#Deployed-Addresses) -* [0x protocol message format](https://0xproject.com/wiki#Message-Format) +* [Docs](https://0xproject.com/docs/contracts) +* [Overview of 0x protocol architecture](https://0xproject.com/wiki#Architecture) +* [0x smart contract interactions](https://0xproject.com/wiki#Contract-Interactions) +* [Deployed smart contract addresses](https://0xproject.com/wiki#Deployed-Addresses) +* [0x protocol message format](https://0xproject.com/wiki#Message-Format) ## Contributing diff --git a/packages/contracts/contract_templates/contract.handlebars b/packages/contracts/contract_templates/contract.handlebars deleted file mode 100644 index afb9708e9..000000000 --- a/packages/contracts/contract_templates/contract.handlebars +++ /dev/null @@ -1,26 +0,0 @@ -/** - * This file is auto-generated using abi-gen. Don't edit directly. - * Templates can be found at https://github.com/0xProject/0x.js/tree/development/packages/abi-gen-templates. - */ -// tslint:disable:async-suffix member-ordering no-consecutive-blank-lines -// tslint:disable-next-line:no-unused-variable -import { TxData, TxDataPayable } from '@0xproject/types'; -import { BigNumber, classUtils, promisify } from '@0xproject/utils'; -import * as Web3 from 'web3'; - -import {BaseContract} from './base_contract'; - -export class {{contractName}}Contract extends BaseContract { -{{#each methods}} - {{#this.constant}} - {{> call contractName=../contractName}} - {{/this.constant}} - {{^this.constant}} - {{> tx contractName=../contractName}} - {{/this.constant}} -{{/each}} - constructor(web3ContractInstance: Web3.ContractInstance, defaults?: Partial<TxData>) { - super(web3ContractInstance, defaults); - classUtils.bindAll(this, ['_web3ContractInstance', '_defaults']); - } -} // tslint:disable:max-file-line-count diff --git a/packages/contracts/contract_templates/partials/call.handlebars b/packages/contracts/contract_templates/partials/call.handlebars deleted file mode 100644 index 82a45b40e..000000000 --- a/packages/contracts/contract_templates/partials/call.handlebars +++ /dev/null @@ -1,10 +0,0 @@ -public async {{this.name}}( -{{> typed_params inputs=inputs}} - defaultBlock?: Web3.BlockParam, -): Promise<{{> return_type outputs=outputs}}> { - const self = this as {{contractName}}Contract; - const result = await self._web3ContractInstance.{{this.name}}.call( - {{> params inputs=inputs}} - ); - return result; -} diff --git a/packages/contracts/contract_templates/partials/params.handlebars b/packages/contracts/contract_templates/partials/params.handlebars deleted file mode 100644 index ac5d4ae85..000000000 --- a/packages/contracts/contract_templates/partials/params.handlebars +++ /dev/null @@ -1,3 +0,0 @@ -{{#each inputs}} -{{name}}, -{{/each}} diff --git a/packages/contracts/contract_templates/partials/return_type.handlebars b/packages/contracts/contract_templates/partials/return_type.handlebars deleted file mode 100644 index 40a5dd8b0..000000000 --- a/packages/contracts/contract_templates/partials/return_type.handlebars +++ /dev/null @@ -1,10 +0,0 @@ -{{#if outputs.length}} -{{#singleReturnValue}} -{{#returnType outputs.0.type}}{{/returnType}} -{{/singleReturnValue}} -{{^singleReturnValue}} -[{{#each outputs}}{{#returnType type}}{{/returnType}}{{#unless @last}}, {{/unless}}{{/each}}] -{{/singleReturnValue}} -{{else}} -void -{{/if}} diff --git a/packages/contracts/contract_templates/partials/tx.handlebars b/packages/contracts/contract_templates/partials/tx.handlebars deleted file mode 100644 index 69ae982d6..000000000 --- a/packages/contracts/contract_templates/partials/tx.handlebars +++ /dev/null @@ -1,36 +0,0 @@ -public {{this.name}} = { - async sendTransactionAsync( - {{> typed_params inputs=inputs}} - {{#this.payable}} - txData: TxDataPayable = {}, - {{/this.payable}} - {{^this.payable}} - txData: TxData = {}, - {{/this.payable}} - ): Promise<string> { - const self = this as {{contractName}}Contract; - const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(txData); - const txHash = await self._web3ContractInstance.{{this.name}}( - {{> params inputs=inputs}} - txDataWithDefaults, - ); - return txHash; - }, - async callAsync( - {{> typed_params inputs=inputs}} - {{#this.payable}} - txData: TxDataPayable = {}, - {{/this.payable}} - {{^this.payable}} - txData: TxData = {}, - {{/this.payable}} - ): Promise<{{> return_type outputs=outputs}}> { - const self = this as {{contractName}}Contract; - const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(txData); - const returnValue = await self._web3ContractInstance.{{this.name}}.call( - {{> params inputs=inputs}} - txDataWithDefaults, - ); - return returnValue; - }, -}; diff --git a/packages/contracts/contract_templates/partials/typed_params.handlebars b/packages/contracts/contract_templates/partials/typed_params.handlebars deleted file mode 100644 index 3ea4b2e95..000000000 --- a/packages/contracts/contract_templates/partials/typed_params.handlebars +++ /dev/null @@ -1,3 +0,0 @@ -{{#each inputs}} - {{name}}: {{#parameterType type}}{{/parameterType}}, -{{/each}} diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 6655df86f..7cac30069 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -17,7 +17,7 @@ "compile:comment": "Yarn workspaces do not link binaries correctly so we need to reference them directly https://github.com/yarnpkg/yarn/issues/3846", "compile": "node ../deployer/lib/src/cli.js compile --contracts ${npm_package_config_contracts} --contracts-dir src/contracts --artifacts-dir src/artifacts", "clean": "shx rm -rf ./lib", - "generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'src/artifacts/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken).json' --template contract_templates/contract.handlebars --partials 'contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated", + "generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'src/artifacts/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'", "migrate": "node ../deployer/lib/src/cli.js migrate", "lint": "tslint --project . 'migrations/**/*.ts' 'test/**/*.ts' 'util/**/*.ts' 'deploy/**/*.ts'", "test:circleci": "yarn test" @@ -38,7 +38,6 @@ "devDependencies": { "@0xproject/dev-utils": "^0.1.0", "@0xproject/tslint-config": "^0.4.9", - "@0xproject/types": "^0.2.3", "@types/bluebird": "^3.5.3", "@types/lodash": "^4.14.86", "@types/node": "^8.0.53", @@ -59,19 +58,23 @@ "types-bn": "^0.0.1", "types-ethereumjs-util": "0xProject/types-ethereumjs-util", "typescript": "2.7.1", + "ethers-typescript-typings": "^0.0.1", "web3-typescript-typings": "^0.9.11", "yargs": "^10.0.3" }, "dependencies": { "0x.js": "^0.32.4", + "@0xproject/web3-wrapper": "^0.1.14", "@0xproject/deployer": "^0.1.0", "@0xproject/json-schemas": "^0.7.12", + "@0xproject/types": "^0.2.3", "@0xproject/utils": "^0.3.4", "@0xproject/web3-wrapper": "^0.1.14", "bluebird": "^3.5.0", "bn.js": "^4.11.8", "ethereumjs-abi": "^0.6.4", "ethereumjs-util": "^5.1.1", + "ethers-contracts": "^2.2.1", "isomorphic-fetch": "^2.2.1", "lodash": "^4.17.4", "request": "^2.81.0", diff --git a/packages/contracts/src/contract_wrappers/generated/.gitignore b/packages/contracts/src/contract_wrappers/generated/.gitignore index b976a8737..72e8ffc0d 100644 --- a/packages/contracts/src/contract_wrappers/generated/.gitignore +++ b/packages/contracts/src/contract_wrappers/generated/.gitignore @@ -1,8 +1 @@ -dummy_token.ts -exchange.ts -multi_sig_wallet_with_time_lock_except_remove_authorized_address.ts -multi_sig_wallet_with_time_lock.ts -multi_sig_wallet.ts -token_registry.ts -token_transfer_proxy.ts -zrx_token.ts +* diff --git a/packages/contracts/src/contract_wrappers/generated/base_contract.ts b/packages/contracts/src/contract_wrappers/generated/base_contract.ts deleted file mode 100644 index 2d77b3ab1..000000000 --- a/packages/contracts/src/contract_wrappers/generated/base_contract.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {TxData, TxDataPayable} from '@0xproject/types'; -import * as _ from 'lodash'; -import * as Web3 from 'web3'; - -export class BaseContract { - public address: string; - protected _web3ContractInstance: Web3.ContractInstance; - protected _defaults: Partial<TxData>; - protected async _applyDefaultsToTxDataAsync<T extends TxData|TxDataPayable>( - txData: T, - estimateGasAsync?: (txData: T) => Promise<number>, - ): Promise<TxData> { - // Gas amount sourced with the following priorities: - // 1. Optional param passed in to public method call - // 2. Global config passed in at library instantiation - // 3. Gas estimate calculation + safety margin - const removeUndefinedProperties = _.pickBy; - const txDataWithDefaults = { - ...removeUndefinedProperties(this._defaults), - ...removeUndefinedProperties(txData as any), - // HACK: TS can't prove that T is spreadable. - // Awaiting https://github.com/Microsoft/TypeScript/pull/13288 to be merged - }; - if (_.isUndefined(txDataWithDefaults.gas) && !_.isUndefined(estimateGasAsync)) { - const estimatedGas = await estimateGasAsync(txData); - txDataWithDefaults.gas = estimatedGas; - } - return txDataWithDefaults; - } - constructor(web3ContractInstance: Web3.ContractInstance, defaults?: Partial<TxData>) { - this.address = web3ContractInstance.address; - this._web3ContractInstance = web3ContractInstance; - this._defaults = defaults || {}; - } -} diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index 9cfc68418..303d745aa 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -1,12 +1,4 @@ -import { - LogCancelContractEventArgs, - LogErrorContractEventArgs, - LogFillContractEventArgs, - LogWithDecodedArgs, - SignedOrder, - TransactionReceiptWithDecodedLogs, - ZeroEx, -} from '0x.js'; +import { LogWithDecodedArgs, SignedOrder, TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js'; import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; @@ -15,7 +7,12 @@ import ethUtil = require('ethereumjs-util'); import * as Web3 from 'web3'; import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token'; -import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; +import { + ExchangeContract, + LogCancelContractEventArgs, + LogErrorContractEventArgs, + LogFillContractEventArgs, +} from '../../src/contract_wrappers/generated/exchange'; import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy'; import { Balances } from '../../util/balances'; import { constants } from '../../util/constants'; @@ -63,16 +60,20 @@ describe('Exchange', () => { deployer.deployAsync(ContractName.DummyToken), deployer.deployAsync(ContractName.DummyToken), ]); - rep = new DummyTokenContract(repInstance); - dgd = new DummyTokenContract(dgdInstance); - zrx = new DummyTokenContract(zrxInstance); + rep = new DummyTokenContract(web3Wrapper, repInstance.abi, repInstance.address); + dgd = new DummyTokenContract(web3Wrapper, dgdInstance.abi, dgdInstance.address); + zrx = new DummyTokenContract(web3Wrapper, zrxInstance.abi, zrxInstance.address); const tokenTransferProxyInstance = await deployer.deployAsync(ContractName.TokenTransferProxy); - tokenTransferProxy = new TokenTransferProxyContract(tokenTransferProxyInstance); + tokenTransferProxy = new TokenTransferProxyContract( + web3Wrapper, + tokenTransferProxyInstance.abi, + tokenTransferProxyInstance.address, + ); const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [ zrx.address, tokenTransferProxy.address, ]); - exchange = new ExchangeContract(exchangeInstance); + exchange = new ExchangeContract(web3Wrapper, exchangeInstance.abi, exchangeInstance.address); await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] }); zeroEx = new ZeroEx(web3.currentProvider, { exchangeContractAddress: exchange.address, @@ -650,7 +651,7 @@ describe('Exchange', () => { it('should not change balances if makerTokenAddress is ZRX, makerTokenAmount + makerFee > maker allowance, \ and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - const makerZRXAllowance = await zrx.allowance(maker, tokenTransferProxy.address); + const makerZRXAllowance = await zrx.allowance.callAsync(maker, tokenTransferProxy.address); signedOrder = await orderFactory.newSignedOrderAsync({ makerTokenAddress: zrx.address, makerTokenAmount: new BigNumber(makerZRXAllowance), @@ -676,7 +677,7 @@ describe('Exchange', () => { it('should not change balances if takerTokenAddress is ZRX, takerTokenAmount + takerFee > taker allowance, \ and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - const takerZRXAllowance = await zrx.allowance(taker, tokenTransferProxy.address); + const takerZRXAllowance = await zrx.allowance.callAsync(taker, tokenTransferProxy.address); signedOrder = await orderFactory.newSignedOrderAsync({ takerTokenAddress: zrx.address, takerTokenAmount: new BigNumber(takerZRXAllowance), @@ -723,7 +724,7 @@ describe('Exchange', () => { const res = await exWrapper.fillOrderAsync(signedOrder, taker); expect(res.logs).to.have.length(1); const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>; - const errCode = log.args.errorId.toNumber(); + const errCode = log.args.errorId; expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED); }); @@ -734,7 +735,7 @@ describe('Exchange', () => { const res = await exWrapper.fillOrderAsync(signedOrder, taker); expect(res.logs).to.have.length(1); const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>; - const errCode = log.args.errorId.toNumber(); + const errCode = log.args.errorId; expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED); }); }); @@ -862,7 +863,7 @@ describe('Exchange', () => { const res = await exWrapper.cancelOrderAsync(signedOrder, maker); expect(res.logs).to.have.length(1); const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>; - const errCode = log.args.errorId.toNumber(); + const errCode = log.args.errorId; expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED); }); @@ -874,7 +875,7 @@ describe('Exchange', () => { const res = await exWrapper.cancelOrderAsync(signedOrder, maker); expect(res.logs).to.have.length(1); const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>; - const errCode = log.args.errorId.toNumber(); + const errCode = log.args.errorId; expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED); }); }); diff --git a/packages/contracts/test/exchange/helpers.ts b/packages/contracts/test/exchange/helpers.ts index 5fe00225e..9869c2155 100644 --- a/packages/contracts/test/exchange/helpers.ts +++ b/packages/contracts/test/exchange/helpers.ts @@ -5,7 +5,12 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import ethUtil = require('ethereumjs-util'); -import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; +import { + ExchangeContract, + LogCancelContractEventArgs, + LogErrorContractEventArgs, + LogFillContractEventArgs, +} from '../../src/contract_wrappers/generated/exchange'; import { constants } from '../../util/constants'; import { ExchangeWrapper } from '../../util/exchange_wrapper'; import { OrderFactory } from '../../util/order_factory'; @@ -42,7 +47,7 @@ describe('Exchange', () => { zrx.address, tokenTransferProxy.address, ]); - const exchange = new ExchangeContract(exchangeInstance); + const exchange = new ExchangeContract(web3Wrapper, exchangeInstance.abi, exchangeInstance.address); await tokenTransferProxy.addAuthorizedAddress(exchange.address, { from: accounts[0] }); const zeroEx = new ZeroEx(web3.currentProvider, { networkId: constants.TESTRPC_NETWORK_ID }); exchangeWrapper = new ExchangeWrapper(exchange, zeroEx); @@ -50,8 +55,8 @@ describe('Exchange', () => { exchangeContractAddress: exchange.address, maker, feeRecipient, - makerToken: rep.address, - takerToken: dgd.address, + makerTokenAddress: rep.address, + takerTokenAddress: dgd.address, makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), diff --git a/packages/contracts/test/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts index bf5a89222..4ea40cb59 100644 --- a/packages/contracts/test/exchange/wrapper.ts +++ b/packages/contracts/test/exchange/wrapper.ts @@ -7,7 +7,12 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token'; -import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; +import { + ExchangeContract, + LogCancelContractEventArgs, + LogErrorContractEventArgs, + LogFillContractEventArgs, +} from '../../src/contract_wrappers/generated/exchange'; import { TokenRegistryContract } from '../../src/contract_wrappers/generated/token_registry'; import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy'; import { Balances } from '../../util/balances'; @@ -55,18 +60,26 @@ describe('Exchange', () => { deployer.deployAsync(ContractName.DummyToken), deployer.deployAsync(ContractName.DummyToken), ]); - rep = new DummyTokenContract(repInstance); - dgd = new DummyTokenContract(dgdInstance); - zrx = new DummyTokenContract(zrxInstance); + rep = new DummyTokenContract(web3Wrapper, repInstance.abi, repInstance.address); + dgd = new DummyTokenContract(web3Wrapper, dgdInstance.abi, dgdInstance.address); + zrx = new DummyTokenContract(web3Wrapper, zrxInstance.abi, zrxInstance.address); const tokenRegistryInstance = await deployer.deployAsync(ContractName.TokenRegistry); - tokenRegistry = new TokenRegistryContract(tokenRegistryInstance); + tokenRegistry = new TokenRegistryContract( + web3Wrapper, + tokenRegistryInstance.abi, + tokenRegistryInstance.address, + ); const tokenTransferProxyInstance = await deployer.deployAsync(ContractName.TokenTransferProxy); - tokenTransferProxy = new TokenTransferProxyContract(tokenTransferProxyInstance); + tokenTransferProxy = new TokenTransferProxyContract( + web3Wrapper, + tokenTransferProxyInstance.abi, + tokenTransferProxyInstance.address, + ); const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [ zrx.address, tokenTransferProxy.address, ]); - exchange = new ExchangeContract(exchangeInstance); + exchange = new ExchangeContract(web3Wrapper, exchangeInstance.abi, exchangeInstance.address); await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] }); const zeroEx = new ZeroEx(web3.currentProvider, { networkId: constants.TESTRPC_NETWORK_ID }); exWrapper = new ExchangeWrapper(exchange, zeroEx); diff --git a/packages/contracts/test/multi_sig_with_time_lock.ts b/packages/contracts/test/multi_sig_with_time_lock.ts index 6812cb09f..a726814e4 100644 --- a/packages/contracts/test/multi_sig_with_time_lock.ts +++ b/packages/contracts/test/multi_sig_with_time_lock.ts @@ -59,10 +59,14 @@ describe('MultiSigWalletWithTimeLock', () => { SIGNATURES_REQUIRED, 0, ]); - multiSig = new MultiSigWalletWithTimeLockContract(multiSigInstance); + multiSig = new MultiSigWalletWithTimeLockContract( + web3Wrapper, + multiSigInstance.abi, + multiSigInstance.address, + ); multiSigWrapper = new MultiSigWrapper((multiSig as any) as MultiSigWalletContract); - const secondsTimeLocked = await multiSig.secondsTimeLocked(); + const secondsTimeLocked = await multiSig.secondsTimeLocked.callAsync(); initialSecondsTimeLocked = secondsTimeLocked.toNumber(); }); it('should throw when not called by wallet', async () => { @@ -113,7 +117,7 @@ describe('MultiSigWalletWithTimeLock', () => { const blockNum = await web3Wrapper.getBlockNumberAsync(); const blockInfo = await web3Wrapper.getBlockAsync(blockNum); const timestamp = new BigNumber(blockInfo.timestamp); - const confirmationTimeBigNum = new BigNumber(await multiSig.confirmationTimes(txId)); + const confirmationTimeBigNum = new BigNumber(await multiSig.confirmationTimes.callAsync(txId)); expect(timestamp).to.be.bignumber.equal(confirmationTimeBigNum); }); @@ -141,7 +145,7 @@ describe('MultiSigWalletWithTimeLock', () => { const res = await zeroEx.awaitTransactionMinedAsync(txHash); expect(res.logs).to.have.length(2); - const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked()); + const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.callAsync()); expect(secondsTimeLocked).to.be.bignumber.equal(SECONDS_TIME_LOCKED); }); }); @@ -152,10 +156,14 @@ describe('MultiSigWalletWithTimeLock', () => { SIGNATURES_REQUIRED, SECONDS_TIME_LOCKED, ]); - multiSig = new MultiSigWalletWithTimeLockContract(multiSigInstance); + multiSig = new MultiSigWalletWithTimeLockContract( + web3Wrapper, + multiSigInstance.abi, + multiSigInstance.address, + ); multiSigWrapper = new MultiSigWrapper((multiSig as any) as MultiSigWalletContract); - const secondsTimeLocked = await multiSig.secondsTimeLocked(); + const secondsTimeLocked = await multiSig.secondsTimeLocked.callAsync(); initialSecondsTimeLocked = secondsTimeLocked.toNumber(); const destination = multiSig.address; const from = owners[0]; @@ -187,7 +195,7 @@ describe('MultiSigWalletWithTimeLock', () => { await rpc.increaseTimeAsync(SECONDS_TIME_LOCKED.toNumber()); await multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] }); - const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked()); + const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.callAsync()); expect(secondsTimeLocked).to.be.bignumber.equal(newSecondsTimeLocked); }); }); diff --git a/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts b/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts index 7e9d44730..c0299e1e1 100644 --- a/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts +++ b/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts @@ -49,7 +49,11 @@ describe('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', () => { [authorizedAddress, unauthorizedAddress] = accounts; const initialOwner = accounts[0]; const tokenTransferProxyInstance = await deployer.deployAsync(ContractName.TokenTransferProxy); - tokenTransferProxy = new TokenTransferProxyContract(tokenTransferProxyInstance); + tokenTransferProxy = new TokenTransferProxyContract( + web3Wrapper, + tokenTransferProxyInstance.abi, + tokenTransferProxyInstance.address, + ); await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(authorizedAddress, { from: initialOwner, }); @@ -57,7 +61,11 @@ describe('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', () => { ContractName.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress, [owners, requiredApprovals, SECONDS_TIME_LOCKED, tokenTransferProxy.address], ); - multiSig = new MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressContract(multiSigInstance); + multiSig = new MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressContract( + web3Wrapper, + multiSigInstance.abi, + multiSigInstance.address, + ); await tokenTransferProxy.transferOwnership.sendTransactionAsync(multiSig.address, { from: initialOwner, }); @@ -74,12 +82,14 @@ describe('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', () => { describe('isFunctionRemoveAuthorizedAddress', () => { it('should throw if data is not for removeAuthorizedAddress', async () => { const data = MultiSigWrapper.encodeFnArgs('addAuthorizedAddress', PROXY_ABI, [owners[0]]); - return expect(multiSig.isFunctionRemoveAuthorizedAddress(data)).to.be.rejectedWith(constants.REVERT); + return expect(multiSig.isFunctionRemoveAuthorizedAddress.callAsync(data)).to.be.rejectedWith( + constants.REVERT, + ); }); it('should return true if data is for removeAuthorizedAddress', async () => { const data = MultiSigWrapper.encodeFnArgs('removeAuthorizedAddress', PROXY_ABI, [owners[0]]); - const isFunctionRemoveAuthorizedAddress = await multiSig.isFunctionRemoveAuthorizedAddress(data); + const isFunctionRemoveAuthorizedAddress = await multiSig.isFunctionRemoveAuthorizedAddress.callAsync(data); expect(isFunctionRemoveAuthorizedAddress).to.be.true(); }); }); @@ -114,7 +124,7 @@ describe('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', () => { const log = abiDecoder.tryToDecodeLogOrNoop(res.logs[0]) as LogWithDecodedArgs<SubmissionContractEventArgs>; const txId = log.args.transactionId; await multiSig.confirmTransaction.sendTransactionAsync(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed(txId); + const isConfirmed = await multiSig.isConfirmed.callAsync(txId); expect(isConfirmed).to.be.true(); return expect( @@ -133,7 +143,7 @@ describe('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', () => { const log = abiDecoder.tryToDecodeLogOrNoop(res.logs[0]) as LogWithDecodedArgs<SubmissionContractEventArgs>; const txId = log.args.transactionId; await multiSig.confirmTransaction.sendTransactionAsync(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed(txId); + const isConfirmed = await multiSig.isConfirmed.callAsync(txId); expect(isConfirmed).to.be.true(); return expect( @@ -152,10 +162,10 @@ describe('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', () => { const log = abiDecoder.tryToDecodeLogOrNoop(res.logs[0]) as LogWithDecodedArgs<SubmissionContractEventArgs>; const txId = log.args.transactionId; await multiSig.confirmTransaction.sendTransactionAsync(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed(txId); + const isConfirmed = await multiSig.isConfirmed.callAsync(txId); expect(isConfirmed).to.be.true(); await multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }); - const isAuthorized = await tokenTransferProxy.authorized(authorizedAddress); + const isAuthorized = await tokenTransferProxy.authorized.callAsync(authorizedAddress); expect(isAuthorized).to.be.false(); }); @@ -170,10 +180,10 @@ describe('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', () => { const log = abiDecoder.tryToDecodeLogOrNoop(res.logs[0]) as LogWithDecodedArgs<SubmissionContractEventArgs>; const txId = log.args.transactionId; await multiSig.confirmTransaction.sendTransactionAsync(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed(txId); + const isConfirmed = await multiSig.isConfirmed.callAsync(txId); expect(isConfirmed).to.be.true(); await multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }); - const tx = await multiSig.transactions(txId); + const tx = await multiSig.transactions.callAsync(txId); const isExecuted = tx[3]; expect(isExecuted).to.be.true(); return expect( diff --git a/packages/contracts/test/token_registry.ts b/packages/contracts/test/token_registry.ts index 867282d2c..eee14ad9f 100644 --- a/packages/contracts/test/token_registry.ts +++ b/packages/contracts/test/token_registry.ts @@ -31,7 +31,7 @@ describe('TokenRegistry', () => { owner = accounts[0]; notOwner = accounts[1]; const tokenRegInstance = await deployer.deployAsync(ContractName.TokenRegistry); - tokenReg = new TokenRegistryContract(tokenRegInstance); + tokenReg = new TokenRegistryContract(web3Wrapper, tokenRegInstance.abi, tokenRegInstance.address); tokenRegWrapper = new TokenRegWrapper(tokenReg); }); beforeEach(async () => { diff --git a/packages/contracts/test/token_transfer_proxy/auth.ts b/packages/contracts/test/token_transfer_proxy/auth.ts index 9d453b079..4f497dd0d 100644 --- a/packages/contracts/test/token_transfer_proxy/auth.ts +++ b/packages/contracts/test/token_transfer_proxy/auth.ts @@ -25,7 +25,11 @@ describe('TokenTransferProxy', () => { owner = address = accounts[0]; notOwner = accounts[1]; const tokenTransferProxyInstance = await deployer.deployAsync(ContractName.TokenTransferProxy); - tokenTransferProxy = new TokenTransferProxyContract(tokenTransferProxyInstance); + tokenTransferProxy = new TokenTransferProxyContract( + web3Wrapper, + tokenTransferProxyInstance.abi, + tokenTransferProxyInstance.address, + ); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -41,7 +45,7 @@ describe('TokenTransferProxy', () => { }); it('should allow owner to add an authorized address', async () => { await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }); - const isAuthorized = await tokenTransferProxy.authorized(address); + const isAuthorized = await tokenTransferProxy.authorized.callAsync(address); expect(isAuthorized).to.be.true(); }); it('should throw if owner attempts to authorize a duplicate address', async () => { @@ -67,7 +71,7 @@ describe('TokenTransferProxy', () => { await tokenTransferProxy.removeAuthorizedAddress.sendTransactionAsync(address, { from: owner, }); - const isAuthorized = await tokenTransferProxy.authorized(address); + const isAuthorized = await tokenTransferProxy.authorized.callAsync(address); expect(isAuthorized).to.be.false(); }); @@ -82,19 +86,19 @@ describe('TokenTransferProxy', () => { describe('getAuthorizedAddresses', () => { it('should return all authorized addresses', async () => { - const initial = await tokenTransferProxy.getAuthorizedAddresses(); + const initial = await tokenTransferProxy.getAuthorizedAddresses.callAsync(); expect(initial).to.have.length(0); await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(address, { from: owner, }); - const afterAdd = await tokenTransferProxy.getAuthorizedAddresses(); + const afterAdd = await tokenTransferProxy.getAuthorizedAddresses.callAsync(); expect(afterAdd).to.have.length(1); expect(afterAdd).to.include(address); await tokenTransferProxy.removeAuthorizedAddress.sendTransactionAsync(address, { from: owner, }); - const afterRemove = await tokenTransferProxy.getAuthorizedAddresses(); + const afterRemove = await tokenTransferProxy.getAuthorizedAddresses.callAsync(); expect(afterRemove).to.have.length(0); }); }); diff --git a/packages/contracts/test/token_transfer_proxy/transfer_from.ts b/packages/contracts/test/token_transfer_proxy/transfer_from.ts index c35a7276a..6b86a0e97 100644 --- a/packages/contracts/test/token_transfer_proxy/transfer_from.ts +++ b/packages/contracts/test/token_transfer_proxy/transfer_from.ts @@ -33,9 +33,13 @@ describe('TokenTransferProxy', () => { accounts = await web3Wrapper.getAvailableAddressesAsync(); owner = notAuthorized = accounts[0]; const tokenTransferProxyInstance = await deployer.deployAsync(ContractName.TokenTransferProxy); - tokenTransferProxy = new TokenTransferProxyContract(tokenTransferProxyInstance); + tokenTransferProxy = new TokenTransferProxyContract( + web3Wrapper, + tokenTransferProxyInstance.abi, + tokenTransferProxyInstance.address, + ); const repInstance = await deployer.deployAsync(ContractName.DummyToken); - rep = new DummyTokenContract(repInstance); + rep = new DummyTokenContract(web3Wrapper, repInstance.abi, repInstance.address); dmyBalances = new Balances([rep], [accounts[0], accounts[1]]); await Promise.all([ diff --git a/packages/contracts/test/unlimited_allowance_token.ts b/packages/contracts/test/unlimited_allowance_token.ts index f0a66e76b..03eb581ad 100644 --- a/packages/contracts/test/unlimited_allowance_token.ts +++ b/packages/contracts/test/unlimited_allowance_token.ts @@ -35,7 +35,7 @@ describe('UnlimitedAllowanceToken', () => { owner = accounts[0]; spender = accounts[1]; const tokenInstance = await deployer.deployAsync(ContractName.DummyToken); - token = new DummyTokenContract(tokenInstance); + token = new DummyTokenContract(web3Wrapper, tokenInstance.abi, tokenInstance.address); await token.mint.sendTransactionAsync(MAX_MINT_VALUE, { from: owner }); tokenAddress = token.address; }); diff --git a/packages/contracts/test/zrx_token.ts b/packages/contracts/test/zrx_token.ts index 1610ada12..4ccc66b36 100644 --- a/packages/contracts/test/zrx_token.ts +++ b/packages/contracts/test/zrx_token.ts @@ -36,7 +36,7 @@ describe('ZRXToken', () => { networkId: constants.TESTRPC_NETWORK_ID, }); const zrxInstance = await deployer.deployAsync(ContractName.ZRXToken); - zrx = new ZRXTokenContract(zrxInstance); + zrx = new ZRXTokenContract(web3Wrapper, zrxInstance.abi, zrxInstance.address); zrxAddress = zrx.address; MAX_UINT = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; }); @@ -48,25 +48,25 @@ describe('ZRXToken', () => { }); describe('constants', () => { it('should have 18 decimals', async () => { - const decimals = new BigNumber(await zrx.decimals()); + const decimals = new BigNumber(await zrx.decimals.callAsync()); const expectedDecimals = 18; expect(decimals).to.be.bignumber.equal(expectedDecimals); }); it('should have a total supply of 1 billion tokens', async () => { - const totalSupply = new BigNumber(await zrx.totalSupply()); + const totalSupply = new BigNumber(await zrx.totalSupply.callAsync()); const expectedTotalSupply = 1000000000; expect(ZeroEx.toUnitAmount(totalSupply, 18)).to.be.bignumber.equal(expectedTotalSupply); }); it('should be named 0x Protocol Token', async () => { - const name = await zrx.name(); + const name = await zrx.name.callAsync(); const expectedName = '0x Protocol Token'; expect(name).to.be.equal(expectedName); }); it('should have the symbol ZRX', async () => { - const symbol = await zrx.symbol(); + const symbol = await zrx.symbol.callAsync(); const expectedSymbol = 'ZRX'; expect(symbol).to.be.equal(expectedSymbol); }); @@ -75,7 +75,7 @@ describe('ZRXToken', () => { describe('constructor', () => { it('should initialize owner balance to totalSupply', async () => { const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const totalSupply = new BigNumber(await zrx.totalSupply()); + const totalSupply = new BigNumber(await zrx.totalSupply.callAsync()); expect(totalSupply).to.be.bignumber.equal(ownerBalance); }); }); diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json index b618ca4e7..490531eeb 100644 --- a/packages/contracts/tsconfig.json +++ b/packages/contracts/tsconfig.json @@ -11,6 +11,7 @@ "../../node_modules/types-ethereumjs-util/index.d.ts", "../../node_modules/chai-typescript-typings/index.d.ts", "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/ethers-typescript-typings/index.d.ts", "../../node_modules/chai-as-promised-typescript-typings/index.d.ts", "../../node_modules/types-ethereumjs-util/index.d.ts", "../../node_modules/types-bn/index.d.ts", diff --git a/packages/contracts/util/balances.ts b/packages/contracts/util/balances.ts index 0abc305d8..d03d4b3c5 100644 --- a/packages/contracts/util/balances.ts +++ b/packages/contracts/util/balances.ts @@ -17,7 +17,7 @@ export class Balances { const balancesByOwner: BalancesByOwner = {}; for (const tokenContractInstance of this._tokenContractInstances) { for (const ownerAddress of this._ownerAddresses) { - let balance = await tokenContractInstance.balanceOf(ownerAddress); + let balance = await tokenContractInstance.balanceOf.callAsync(ownerAddress); balance = new BigNumber(balance); if (_.isUndefined(balancesByOwner[ownerAddress])) { balancesByOwner[ownerAddress] = {}; diff --git a/packages/contracts/util/exchange_wrapper.ts b/packages/contracts/util/exchange_wrapper.ts index 03d04629d..f016067fe 100644 --- a/packages/contracts/util/exchange_wrapper.ts +++ b/packages/contracts/util/exchange_wrapper.ts @@ -186,11 +186,11 @@ export class ExchangeWrapper { public async getOrderHashAsync(signedOrder: SignedOrder): Promise<string> { const shouldThrowOnInsufficientBalanceOrAllowance = false; const params = signedOrderUtils.getOrderAddressesAndValues(signedOrder); - const orderHash = await this._exchange.getOrderHash(params.orderAddresses, params.orderValues); + const orderHash = await this._exchange.getOrderHash.callAsync(params.orderAddresses, params.orderValues); return orderHash; } public async isValidSignatureAsync(signedOrder: SignedOrder): Promise<boolean> { - const isValidSignature = await this._exchange.isValidSignature( + const isValidSignature = await this._exchange.isValidSignature.callAsync( signedOrder.maker, ZeroEx.getOrderHashHex(signedOrder), signedOrder.ecSignature.v, @@ -204,7 +204,7 @@ export class ExchangeWrapper { denominator: BigNumber, target: BigNumber, ): Promise<boolean> { - const isRoundingError = await this._exchange.isRoundingError(numerator, denominator, target); + const isRoundingError = await this._exchange.isRoundingError.callAsync(numerator, denominator, target); return isRoundingError; } public async getPartialAmountAsync( @@ -212,7 +212,9 @@ export class ExchangeWrapper { denominator: BigNumber, target: BigNumber, ): Promise<BigNumber> { - const partialAmount = new BigNumber(await this._exchange.getPartialAmount(numerator, denominator, target)); + const partialAmount = new BigNumber( + await this._exchange.getPartialAmount.callAsync(numerator, denominator, target), + ); return partialAmount; } } diff --git a/packages/contracts/util/token_registry_wrapper.ts b/packages/contracts/util/token_registry_wrapper.ts index d0af17103..d78c8a64e 100644 --- a/packages/contracts/util/token_registry_wrapper.ts +++ b/packages/contracts/util/token_registry_wrapper.ts @@ -22,36 +22,36 @@ export class TokenRegWrapper { return tx; } public async getTokenMetaDataAsync(tokenAddress: string) { - const data = await this._tokenReg.getTokenMetaData(tokenAddress); + const data = await this._tokenReg.getTokenMetaData.callAsync(tokenAddress); const token: Token = { address: data[0], name: data[1], symbol: data[2], - decimals: data[3].toNumber(), + decimals: data[3], ipfsHash: data[4], swarmHash: data[5], }; return token; } public async getTokenByNameAsync(tokenName: string) { - const data = await this._tokenReg.getTokenByName(tokenName); + const data = await this._tokenReg.getTokenByName.callAsync(tokenName); const token: Token = { address: data[0], name: data[1], symbol: data[2], - decimals: data[3].toNumber(), + decimals: data[3], ipfsHash: data[4], swarmHash: data[5], }; return token; } public async getTokenBySymbolAsync(tokenSymbol: string) { - const data = await this._tokenReg.getTokenBySymbol(tokenSymbol); + const data = await this._tokenReg.getTokenBySymbol.callAsync(tokenSymbol); const token: Token = { address: data[0], name: data[1], symbol: data[2], - decimals: data[3].toNumber(), + decimals: data[3], ipfsHash: data[4], swarmHash: data[5], }; diff --git a/packages/contracts/util/types.ts b/packages/contracts/util/types.ts index 65bc26f79..d6e4c587d 100644 --- a/packages/contracts/util/types.ts +++ b/packages/contracts/util/types.ts @@ -41,8 +41,8 @@ export interface DefaultOrderParams { exchangeContractAddress: string; maker: string; feeRecipient: string; - makerToken: string; - takerToken: string; + makerTokenAddress: string; + takerTokenAddress: string; makerTokenAmount: BigNumber; takerTokenAmount: BigNumber; makerFee: BigNumber; diff --git a/packages/deployer/package.json b/packages/deployer/package.json index f969e4eda..2a6668fa6 100644 --- a/packages/deployer/package.json +++ b/packages/deployer/package.json @@ -36,6 +36,7 @@ "tslint": "5.8.0", "types-bn": "^0.0.1", "typescript": "2.7.1", + "ethers-typescript-typings": "^0.0.1", "web3-typescript-typings": "^0.9.11" }, "dependencies": { diff --git a/packages/deployer/src/cli.ts b/packages/deployer/src/cli.ts index ba156ac20..c976e8f97 100644 --- a/packages/deployer/src/cli.ts +++ b/packages/deployer/src/cli.ts @@ -10,8 +10,8 @@ import { constants } from './utils/constants'; import { CliOptions, CompilerOptions, DeployerOptions } from './utils/types'; const DEFAULT_OPTIMIZER_ENABLED = false; -const DEFAULT_CONTRACTS_DIR = path.resolve('src'); -const DEFAULT_ARTIFACTS_DIR = path.resolve('artifacts'); +const DEFAULT_CONTRACTS_DIR = path.resolve('src/contracts'); +const DEFAULT_ARTIFACTS_DIR = path.resolve('src/artifacts'); const DEFAULT_NETWORK_ID = 50; const DEFAULT_JSONRPC_PORT = 8545; const DEFAULT_GAS_PRICE = (10 ** 9 * 2).toString(); @@ -100,6 +100,9 @@ async function onDeployCommand(argv: CliOptions): Promise<void> { */ function getContractsSetFromList(contracts: string): Set<string> { const specifiedContracts = new Set(); + if (contracts === '*') { + return new Set(['*']); + } const contractsArray = contracts.split(','); _.forEach(contractsArray, contractName => { const fileName = `${contractName}${constants.SOLIDITY_FILE_EXTENSION}`; diff --git a/packages/deployer/src/deployer.ts b/packages/deployer/src/deployer.ts index 021645fd1..6710bcc85 100644 --- a/packages/deployer/src/deployer.ts +++ b/packages/deployer/src/deployer.ts @@ -174,7 +174,7 @@ export class Deployer { const block = await this.web3Wrapper.getBlockAsync('latest'); let gas: number; try { - const gasEstimate: number = await this.web3Wrapper.estimateGasAsync(data); + const gasEstimate: number = await this.web3Wrapper.estimateGasAsync({ data }); gas = Math.min(gasEstimate + EXTRA_GAS, block.gasLimit); } catch (err) { gas = block.gasLimit; diff --git a/packages/deployer/tsconfig.json b/packages/deployer/tsconfig.json index 4e1edb510..897446b66 100644 --- a/packages/deployer/tsconfig.json +++ b/packages/deployer/tsconfig.json @@ -11,6 +11,7 @@ "../../node_modules/types-bn/index.d.ts", "../../node_modules/types-ethereumjs-util/index.d.ts", "../../node_modules/chai-typescript-typings/index.d.ts", + "../../node_modules/ethers-typescript-typings/index.d.ts", "../../node_modules/web3-typescript-typings/index.d.ts" ] } diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 515edc353..dc6410211 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -24,7 +24,6 @@ "homepage": "https://github.com/0xProject/0x.js/packages/dev-utils/README.md", "devDependencies": { "@0xproject/tslint-config": "^0.4.9", - "@0xproject/types": "^0.2.3", "@0xproject/web3-wrapper": "^0.1.14", "@types/lodash": "^4.14.86", "@types/mocha": "^2.2.42", @@ -40,6 +39,7 @@ }, "dependencies": { "@0xproject/subproviders": "^0.5.0", + "@0xproject/types": "^0.2.3", "@0xproject/utils": "^0.3.4", "ethereumjs-util": "^5.1.2", "lodash": "^4.17.4", diff --git a/packages/dev-utils/tsconfig.json b/packages/dev-utils/tsconfig.json index ace978fea..1ed3fbc9c 100644 --- a/packages/dev-utils/tsconfig.json +++ b/packages/dev-utils/tsconfig.json @@ -8,6 +8,7 @@ "./test/**/*", "../../node_modules/types-bn/index.d.ts", "../../node_modules/chai-typescript-typings/index.d.ts", + "../../node_modules/ethers-typescript-typings/index.d.ts", "../../node_modules/web3-typescript-typings/index.d.ts", "../../node_modules/types-ethereumjs-util/index.d.ts" ] diff --git a/packages/ethers-typescript-typings/.npmignore b/packages/ethers-typescript-typings/.npmignore new file mode 100644 index 000000000..104d718ed --- /dev/null +++ b/packages/ethers-typescript-typings/.npmignore @@ -0,0 +1,3 @@ +.* +yarn-error.log +/scripts/ diff --git a/packages/ethers-typescript-typings/CHANGELOG.md b/packages/ethers-typescript-typings/CHANGELOG.md new file mode 100644 index 000000000..d67d5c73d --- /dev/null +++ b/packages/ethers-typescript-typings/CHANGELOG.md @@ -0,0 +1,5 @@ +# CHANGELOG + +## v0.0.1 - _TBD, 2018_ + + * Initial types (#413) diff --git a/packages/ethers-typescript-typings/README.md b/packages/ethers-typescript-typings/README.md new file mode 100644 index 000000000..56ce5f138 --- /dev/null +++ b/packages/ethers-typescript-typings/README.md @@ -0,0 +1,49 @@ +## ethers-typescript-typings + +There currently isn't an official [Ethers][ethers] +type definition included in the [DefinitelyTyped][definitelytyped] project. +Until that happens, we will continue to improve our own type definition. +If it get's close to comprehensive, we'll add it to [DefinitelyTyped][definitelytyped]. + +[ethers]: https://github.com/ethers-io/ethers.js +[definitelytyped]: https://github.com/DefinitelyTyped/DefinitelyTyped + +## Installation + +```bash +yarn add -D ethers-typescript-typings +``` + +## Usage + +Add the following line within an `include` section of your `tsconfig.json` + +```json +"./node_modules/ethers-typescript-typings/index.d.ts" +``` + +## Contributing + +We strongly encourage that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Lint + +```bash +yarn lint +``` diff --git a/packages/ethers-typescript-typings/index.d.ts b/packages/ethers-typescript-typings/index.d.ts new file mode 100644 index 000000000..e5d38819e --- /dev/null +++ b/packages/ethers-typescript-typings/index.d.ts @@ -0,0 +1,28 @@ +declare module 'ethers-contracts' { + export interface TransactionDescription { + name: string; + signature: string; + sighash: string; + data: string; + } + export interface CallDescription extends TransactionDescription { + parse: (...args: any[]) => any; + } + export interface FunctionDescription { + (...params: any[]): TransactionDescription | CallDescription; + inputs: { names: string[]; types: string[] }; + outputs: { names: string[]; types: string[] }; + } + export interface EventDescription { + parse: (...args: any[]) => any; + inputs: { names: string[]; types: string[] }; + signature: string; + topic: string; + } + export class Interface { + public functions: { [functionName: string]: FunctionDescription }; + public events: { [eventName: string]: EventDescription }; + public static decodeParams(types: string[], data: string): any[]; + constructor(abi: any); + } +} diff --git a/packages/ethers-typescript-typings/package.json b/packages/ethers-typescript-typings/package.json new file mode 100644 index 000000000..0e6f517c8 --- /dev/null +++ b/packages/ethers-typescript-typings/package.json @@ -0,0 +1,28 @@ +{ + "name": "ethers-typescript-typings", + "version": "0.0.1", + "description": "Typescript type definitions for ethers.js", + "main": "index.d.ts", + "types": "index.d.ts", + "scripts": { + "lint": "tslint index.d.ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/0xProject/0x.js.git" + }, + "author": "Fabio Berger", + "contributors": [ + "Leonid Logvinov <logvinov.leon@gmail.com>" + ], + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/ethers-typescript-typings#readme", + "devDependencies": { + "tslint": "5.8.0", + "tslint-config-0xproject": "^0.0.2", + "typescript": "2.7.1" + } +} diff --git a/packages/ethers-typescript-typings/scripts/postpublish.js b/packages/ethers-typescript-typings/scripts/postpublish.js new file mode 100644 index 000000000..b3e5407c8 --- /dev/null +++ b/packages/ethers-typescript-typings/scripts/postpublish.js @@ -0,0 +1,5 @@ +const postpublish_utils = require('../../../scripts/postpublish_utils'); +const packageJSON = require('../package.json'); + +const subPackageName = packageJSON.name; +postpublish_utils.standardPostPublishAsync(subPackageName);
\ No newline at end of file diff --git a/packages/ethers-typescript-typings/tslint.json b/packages/ethers-typescript-typings/tslint.json new file mode 100644 index 000000000..9a93a1f74 --- /dev/null +++ b/packages/ethers-typescript-typings/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["tslint-config-0xproject"] +} diff --git a/packages/json-schemas/CHANGELOG.md b/packages/json-schemas/CHANGELOG.md index 9a9fc12de..7fdae6cf3 100644 --- a/packages/json-schemas/CHANGELOG.md +++ b/packages/json-schemas/CHANGELOG.md @@ -2,7 +2,7 @@ ## v0.7.10 - _February 9, 2018_ -* Fix publishing issue where .npmignore was not properly excluding undesired content (#389) +* Fix publishing issue where .npmignore was not properly excluding undesired content (#389) ## v0.7.0 - _December 20, 2017_ diff --git a/packages/subproviders/README.md b/packages/subproviders/README.md index 954729713..39e4a46e7 100644 --- a/packages/subproviders/README.md +++ b/packages/subproviders/README.md @@ -96,10 +96,10 @@ yarn run test:unit In order to run the integration tests, make sure you have a Ledger Nano S available. -* Plug it into your computer -* Unlock the device -* Open the on-device Ethereum app -* Make sure "browser support" is disabled +* Plug it into your computer +* Unlock the device +* Open the on-device Ethereum app +* Make sure "browser support" is disabled Then run: diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 0c791b80f..5e9c2ed89 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@0xproject/assert": "^0.0.20", + "@0xproject/types": "^0.2.3", "@0xproject/utils": "^0.3.4", "bn.js": "^4.11.8", "es6-promisify": "^5.0.0", @@ -33,7 +34,6 @@ }, "devDependencies": { "@0xproject/tslint-config": "^0.4.9", - "@0xproject/types": "^0.2.3", "@0xproject/utils": "^0.3.4", "@types/lodash": "^4.14.86", "@types/mocha": "^2.2.42", diff --git a/packages/testnet-faucets/src/ts/dispense_asset_tasks.ts b/packages/testnet-faucets/src/ts/dispense_asset_tasks.ts index 9aa47463c..56b0a9e45 100644 --- a/packages/testnet-faucets/src/ts/dispense_asset_tasks.ts +++ b/packages/testnet-faucets/src/ts/dispense_asset_tasks.ts @@ -9,11 +9,21 @@ import { utils } from './utils'; const DISPENSE_AMOUNT_ETHER = 0.1; const DISPENSE_AMOUNT_TOKEN = 0.1; +const DISPENSE_MAX_AMOUNT_TOKEN = 2; +const DISPENSE_MAX_AMOUNT_ETHER = 2; export const dispenseAssetTasks = { dispenseEtherTask(recipientAddress: string, web3: Web3) { return async () => { utils.consoleLog(`Processing ETH ${recipientAddress}`); + const userBalance = await promisify<BigNumber>(web3.eth.getBalance)(recipientAddress); + const maxAmountInWei = new BigNumber(web3.toWei(DISPENSE_MAX_AMOUNT_ETHER, 'ether')); + if (userBalance.greaterThanOrEqualTo(maxAmountInWei)) { + utils.consoleLog( + `User exceeded ETH balance maximum (${maxAmountInWei}) ${recipientAddress} ${userBalance} `, + ); + return; + } const sendTransactionAsync = promisify(web3.eth.sendTransaction); const txHash = await sendTransactionAsync({ from: configs.DISPENSER_ADDRESS, @@ -32,6 +42,17 @@ export const dispenseAssetTasks = { throw new Error(`Unsupported asset type: ${tokenSymbol}`); } const baseUnitAmount = ZeroEx.toBaseUnitAmount(amountToDispense, token.decimals); + const userBalanceBaseUnits = await zeroEx.token.getBalanceAsync(token.address, recipientAddress); + const maxAmountBaseUnits = ZeroEx.toBaseUnitAmount( + new BigNumber(DISPENSE_MAX_AMOUNT_TOKEN), + token.decimals, + ); + if (userBalanceBaseUnits.greaterThanOrEqualTo(maxAmountBaseUnits)) { + utils.consoleLog( + `User exceeded token balance maximum (${maxAmountBaseUnits}) ${recipientAddress} ${userBalanceBaseUnits} `, + ); + return; + } const txHash = await zeroEx.token.transferAsync( token.address, configs.DISPENSER_ADDRESS, diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index b1cb721d2..a4f293d60 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## v0.2.4 - _TBD, 2018_ + + * Add `data` to `TxData` (#413) + * Add `number` as an option to `ContractEventArg` (#413) + ## v0.2.1 - _February 9, 2018_ * Fix publishing issue where .npmignore was not properly excluding undesired content (#389) diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index cb17936f7..6242d4268 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -2,6 +2,7 @@ import { BigNumber } from 'bignumber.js'; import * as Web3 from 'web3'; export interface TxData { + data?: string; from?: string; gas?: number; gasPrice?: BigNumber; @@ -38,7 +39,7 @@ export enum AbiType { Fallback = 'fallback', } -export type ContractEventArg = string | BigNumber; +export type ContractEventArg = string | BigNumber | number; export interface DecodedLogArgs { [argName: string]: ContractEventArg; diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 19ee80e4f..81792bee8 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.4.0 - _TBD, 2018_ + + * Use `ethers-contracts` as a backend to decode event args (#413) + ## v0.3.2 - _February 9, 2018_ * Fix publishing issue where .npmignore was not properly excluding undesired content (#389) diff --git a/packages/utils/package.json b/packages/utils/package.json index 750ecaa2f..4f63aa4ce 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -21,16 +21,18 @@ "homepage": "https://github.com/0xProject/0x.js/packages/utils/README.md", "devDependencies": { "@0xproject/tslint-config": "^0.4.9", - "@0xproject/types": "^0.2.3", "@types/lodash": "^4.14.86", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.8.0", "typescript": "2.7.1", + "ethers-typescript-typings": "^0.0.1", "web3-typescript-typings": "^0.9.11" }, "dependencies": { + "@0xproject/types": "^0.2.3", "bignumber.js": "~4.1.0", + "ethers-contracts": "^2.2.1", "js-sha3": "^0.7.0", "lodash": "^4.17.4", "web3": "^0.20.0" diff --git a/packages/utils/src/abi_decoder.ts b/packages/utils/src/abi_decoder.ts index 368973b1b..2b496eb17 100644 --- a/packages/utils/src/abi_decoder.ts +++ b/packages/utils/src/abi_decoder.ts @@ -1,7 +1,7 @@ import { AbiType, DecodedLogArgs, LogWithDecodedArgs, RawLog, SolidityTypes } from '@0xproject/types'; +import * as ethersContracts from 'ethers-contracts'; import * as _ from 'lodash'; import * as Web3 from 'web3'; -import * as SolidityCoder from 'web3/lib/solidity/coder'; import { BigNumber } from './configured_bignumber'; @@ -27,31 +27,29 @@ export class AbiDecoder { if (_.isUndefined(event)) { return log; } + const ethersInterface = new ethersContracts.Interface([event]); const logData = log.data; const decodedParams: DecodedLogArgs = {}; - let dataIndex = 0; let topicsIndex = 1; const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed); const dataTypes = _.map(nonIndexedInputs, input => input.type); - const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice('0x'.length)); + const decodedData = ethersInterface.events[event.name].parse(log.data); let failedToDecode = false; - _.forEach(event.inputs, (param: Web3.EventParameter) => { + _.forEach(event.inputs, (param: Web3.EventParameter, i: number) => { // Indexed parameters are stored in topics. Non-indexed ones in decodedData - let value: BigNumber | string = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++]; + let value: BigNumber | string | number = param.indexed ? log.topics[topicsIndex++] : decodedData[i]; if (_.isUndefined(value)) { failedToDecode = true; return; } if (param.type === SolidityTypes.Address) { value = AbiDecoder._padZeros(new BigNumber(value).toString(16)); - } else if ( - param.type === SolidityTypes.Uint256 || - param.type === SolidityTypes.Uint8 || - param.type === SolidityTypes.Uint - ) { + } else if (param.type === SolidityTypes.Uint256 || param.type === SolidityTypes.Uint) { value = new BigNumber(value); + } else if (param.type === SolidityTypes.Uint8) { + value = new BigNumber(value).toNumber(); } decodedParams[param.name] = value; }); @@ -67,11 +65,14 @@ export class AbiDecoder { } } private _addABI(abiArray: Web3.AbiDefinition[]): void { + if (_.isUndefined(abiArray)) { + return; + } + const ethersInterface = new ethersContracts.Interface(abiArray); _.map(abiArray, (abi: Web3.AbiDefinition) => { if (abi.type === AbiType.Event) { - const signature = `${abi.name}(${_.map(abi.inputs, input => input.type).join(',')})`; - const signatureHash = new Web3().sha3(signature); - this._methodIds[signatureHash] = abi; + const topic = ethersInterface.events[abi.name].topic; + this._methodIds[topic] = abi; } }); this._savedABIs = this._savedABIs.concat(abiArray); diff --git a/packages/utils/src/globals.d.ts b/packages/utils/src/globals.d.ts deleted file mode 100644 index ade9e59db..000000000 --- a/packages/utils/src/globals.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module 'web3/lib/solidity/coder' { - const decodeParams: (types: string[], data: string) => any[]; -} diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index 3d967d05f..8114d99cd 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -3,5 +3,9 @@ "compilerOptions": { "outDir": "lib" }, - "include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] + "include": [ + "./src/**/*", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/ethers-typescript-typings/index.d.ts" + ] } diff --git a/packages/web3-wrapper/CHANGELOG.md b/packages/web3-wrapper/CHANGELOG.md index eb31f7e3c..eef665d24 100644 --- a/packages/web3-wrapper/CHANGELOG.md +++ b/packages/web3-wrapper/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## v0.2.0 _TBD, 2018_ + + * Ensure all returned user addresses are lowercase (#373) + * Add `web3Wrapper.callAsync` (#413) + * Make `web3Wrapper.estimateGas` accept whole `txData` instead of `data` (#413) + * Remove `web3Wrapper.getContractInstance` (#413) + ## v0.1.12 _February 9, 2018_ * Fix publishing issue where .npmignore was not properly excluding undesired content (#389) diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index 8e861b1d5..55f1ffff0 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -21,7 +21,6 @@ "homepage": "https://github.com/0xProject/0x.js/packages/web3-wrapper/README.md", "devDependencies": { "@0xproject/tslint-config": "^0.4.9", - "@0xproject/types": "^0.2.3", "@types/lodash": "^4.14.86", "npm-run-all": "^4.1.2", "shx": "^0.2.2", @@ -30,7 +29,10 @@ "web3-typescript-typings": "^0.9.11" }, "dependencies": { + "@0xproject/types": "^0.2.3", "@0xproject/utils": "^0.3.4", + "ethers-contracts": "^2.2.1", + "ethers-typescript-typings": "^0.0.1", "lodash": "^4.17.4", "web3": "^0.20.0" } diff --git a/packages/web3-wrapper/src/index.ts b/packages/web3-wrapper/src/index.ts index a2878fc2a..a07805344 100644 --- a/packages/web3-wrapper/src/index.ts +++ b/packages/web3-wrapper/src/index.ts @@ -41,7 +41,8 @@ export class Web3Wrapper { } public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> { const addresses = await this.getAvailableAddressesAsync(); - return _.includes(addresses, senderAddress); + const normalizedAddress = senderAddress.toLowerCase(); + return _.includes(addresses, normalizedAddress); } public async getNodeVersionAsync(): Promise<string> { const nodeVersion = await promisify<string>(this._web3.version.getNode)(); @@ -96,7 +97,8 @@ export class Web3Wrapper { } public async getAvailableAddressesAsync(): Promise<string[]> { const addresses = await promisify<string[]>(this._web3.eth.getAccounts)(); - return addresses; + const normalizedAddresses = _.map(addresses, address => address.toLowerCase()); + return normalizedAddresses; } public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> { let fromBlock = filter.fromBlock; @@ -126,14 +128,14 @@ export class Web3Wrapper { const web3Contract = this._web3.eth.contract(abi); return web3Contract; } - public getContractInstance(abi: Web3.ContractAbi, address: string): Web3.ContractInstance { - const web3ContractInstance = this.getContractFromAbi(abi).at(address); - return web3ContractInstance; - } - public async estimateGasAsync(data: string): Promise<number> { - const gas = await promisify<number>(this._web3.eth.estimateGas)({ data }); + public async estimateGasAsync(txData: Partial<Web3.TxData>): Promise<number> { + const gas = await promisify<number>(this._web3.eth.estimateGas)(txData); return gas; } + public async callAsync(callData: Web3.CallData, defaultBlock?: Web3.BlockParam): Promise<string> { + const rawCalllResult = await promisify<string>(this._web3.eth.call)(callData, defaultBlock); + return rawCalllResult; + } public async sendTransactionAsync(txData: Web3.TxData): Promise<string> { const txHash = await promisify<string>(this._web3.eth.sendTransaction)(txData); return txHash; diff --git a/packages/web3-wrapper/tsconfig.json b/packages/web3-wrapper/tsconfig.json index 3d967d05f..7bae7f9f0 100644 --- a/packages/web3-wrapper/tsconfig.json +++ b/packages/web3-wrapper/tsconfig.json @@ -3,5 +3,9 @@ "compilerOptions": { "outDir": "lib" }, - "include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] + "include": [ + "./src/**/*", + "../../node_modules/ethers-typescript-typings/index.d.ts", + "../../node_modules/web3-typescript-typings/index.d.ts" + ] } diff --git a/packages/website/README.md b/packages/website/README.md index 7d3187781..042df52de 100644 --- a/packages/website/README.md +++ b/packages/website/README.md @@ -58,11 +58,11 @@ yarn lint ##### Toolkit -* [Material Design Icon Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html#directional) -* [BassCSS toolkit](http://basscss.com/) -* [Material-UI component library](http://www.material-ui.com/#/) +* [Material Design Icon Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html#directional) +* [BassCSS toolkit](http://basscss.com/) +* [Material-UI component library](http://www.material-ui.com/#/) ##### Recommended Atom packages: -* [atom-typescript](https://atom.io/packages/atom-typescript) -* [linter-tslint](https://atom.io/packages/linter-tslint) +* [atom-typescript](https://atom.io/packages/atom-typescript) +* [linter-tslint](https://atom.io/packages/linter-tslint) diff --git a/packages/website/md/docs/smart_contracts/introduction.md b/packages/website/md/docs/smart_contracts/introduction.md index 20396289b..566a573b6 100644 --- a/packages/website/md/docs/smart_contracts/introduction.md +++ b/packages/website/md/docs/smart_contracts/introduction.md @@ -2,7 +2,7 @@ Welcome to the [0x smart contracts](https://github.com/0xProject/contracts) docu ### Helpful wiki articles: -* [Overview of 0x protocol architecture](https://0xproject.com/wiki#Architecture) -* [0x smart contract interactions](https://0xproject.com/wiki#Contract-Interactions) -* [Deployed smart contract addresses](https://0xproject.com/wiki#Deployed-Addresses) -* [0x protocol message format](https://0xproject.com/wiki#Message-Format) +* [Overview of 0x protocol architecture](https://0xproject.com/wiki#Architecture) +* [0x smart contract interactions](https://0xproject.com/wiki#Contract-Interactions) +* [Deployed smart contract addresses](https://0xproject.com/wiki#Deployed-Addresses) +* [0x protocol message format](https://0xproject.com/wiki#Message-Format) diff --git a/packages/website/package.json b/packages/website/package.json index 1d313390a..ceb3853f9 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -87,6 +87,7 @@ "copy-webpack-plugin": "^4.0.1", "copyfiles": "^1.2.0", "css-loader": "0.23.x", + "ethers-typescript-typings": "^0.0.1", "exports-loader": "0.6.x", "imports-loader": "0.6.x", "json-loader": "^0.5.4", diff --git a/packages/website/public/index.html b/packages/website/public/index.html index 75ca9a4ed..c28e4abf4 100644 --- a/packages/website/public/index.html +++ b/packages/website/public/index.html @@ -23,6 +23,18 @@ </head> <body style="margin: 0px; min-width: 355px;"> + <!-- Global site tag (gtag.js) - Google Analytics --> + <script async src="https://www.googletagmanager.com/gtag/js?id=UA-98720122-1"></script> + <script> + window.dataLayer = window.dataLayer || []; +function gtag() { + dataLayer.push(arguments); +} +gtag('js', new Date()); + +gtag('config', 'UA-98720122-1'); +</script> + <!-- End Google Analytics --> <!-- Facebook SDK --> <div id="fb-root"></div> <script> diff --git a/packages/website/translations/english.json b/packages/website/translations/english.json index 8aebe1848..9a1c9b2c8 100644 --- a/packages/website/translations/english.json +++ b/packages/website/translations/english.json @@ -67,7 +67,7 @@ "STANDARD_RELAYER_API": "standard relayer API", "PORTAL_DAPP": "portal dApp", "WEBSITE": "website", - "DEVELOPERS": "home", - "HOME": "rocket.chat", - "ROCKETCHAT": "developers" + "DEVELOPERS": "developers", + "HOME": "home", + "ROCKETCHAT": "rocket.chat" } diff --git a/packages/website/translations/russian.json b/packages/website/translations/russian.json index a4e47264b..c103960c0 100644 --- a/packages/website/translations/russian.json +++ b/packages/website/translations/russian.json @@ -3,7 +3,7 @@ "TOP_TAGLINE": "0x — это протокол с открытым кодом, позволяющий торговать токенами ERC20, на блокчейне Ethereum.", "BUILD_CALL_TO_ACTION": "Разрабатывайте на 0x", "COMMUNITY_CALL_TO_ACTION": "Сообщество", - "PROJECTS_HEADER": "Прожкты разработанные на 0х", + "PROJECTS_HEADER": "Проекты разработанные на 0х", "FULL_LIST_PROMPT": "Просмотреть", "FULL_LIST_LINK": "полный список", "TOKENIZED_SECTION_HEADER": "Сегодняшний мир движется к токенизации ценности", diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index db3872a32..85b4e892f 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -672,7 +672,7 @@ export class Blockchain { } } private _stopWatchingExchangeLogFillEvents(): void { - this._zeroEx.exchange._unsubscribeAll(); + this._zeroEx.exchange.unsubscribeAll(); } private async _getTokenRegistryTokensByAddressAsync(): Promise<TokenByAddress> { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx index aa597be1c..810460cac 100644 --- a/packages/website/ts/components/footer.tsx +++ b/packages/website/ts/components/footer.tsx @@ -201,11 +201,9 @@ export class Footer extends React.Component<FooterProps, FooterState> { {item.isExternal ? ( <a className="text-decoration-none" style={linkStyle} target="_blank" href={item.path}> {!_.isUndefined(iconIfExists) ? ( - <div className="sm-mx-auto" style={{ minWidth: 65 }}> - <div className="flex"> - <div className="pr1">{this._renderIcon(iconIfExists)}</div> - <div>{item.title}</div> - </div> + <div className="inline-block"> + <div className="pr1 table-cell">{this._renderIcon(iconIfExists)}</div> + <div className="table-cell">{item.title}</div> </div> ) : ( item.title diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index 2723c2218..b2b2d2ebd 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -319,7 +319,6 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { shouldDisplaySectionHeaders={false} onMenuItemClick={this._onMenuButtonClick.bind(this)} selectedVersion={this.props.docsVersion} - docPath={this.props.docsInfo.websitePath} versions={this.props.availableDocVersions} /> </div> diff --git a/packages/website/ts/containers/connect_documentation.tsx b/packages/website/ts/containers/connect_documentation.tsx index 5c5d26b44..464bdcd74 100644 --- a/packages/website/ts/containers/connect_documentation.tsx +++ b/packages/website/ts/containers/connect_documentation.tsx @@ -2,15 +2,14 @@ import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; -import { Documentation as DocumentationComponent, DocumentationAllProps } from 'ts/pages/documentation/documentation'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocsInfoConfig, Environments, WebsitePaths } from 'ts/types'; +import { DocPackages, DocsInfoConfig, Environments, SupportedDocJson, WebsitePaths } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; -import { typeDocUtils } from 'ts/utils/typedoc_utils'; /* tslint:disable:no-var-requires */ const IntroMarkdown = require('md/docs/connect/introduction'); @@ -25,16 +24,11 @@ const connectDocSections = { types: constants.TYPES_SECTION_NAME, }; -const s3BucketName = - configs.ENVIRONMENT === Environments.DEVELOPMENT ? 'staging-connect-docs-jsons' : 'connect-docs-jsons'; -const docsJsonRoot = `https://s3.amazonaws.com/${s3BucketName}`; - const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.Connect, + type: SupportedDocJson.TypeDoc, displayName: '0x Connect', - subPackageName: 'connect', packageUrl: 'https://github.com/0xProject/0x.js', - websitePath: WebsitePaths.Connect, - docsJsonRoot, menu: { introduction: [connectDocSections.introduction], install: [connectDocSections.installation], @@ -77,7 +71,6 @@ const docsInfoConfig: DocsInfoConfig = { menuSubsectionToVersionWhenIntroduced: {}, sections: connectDocSections, visibleConstructors: [connectDocSections.httpClient, connectDocSections.webSocketOrderbookChannel], - convertToDocAgnosticFormatFn: typeDocUtils.convertToDocAgnosticFormat.bind(typeDocUtils), }; const docsInfo = new DocsInfo(docsInfoConfig); @@ -92,7 +85,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ +const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, translate: state.translate, @@ -103,6 +96,6 @@ const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ dispatcher: new Dispatcher(dispatch), }); -export const Documentation: React.ComponentClass<DocumentationAllProps> = connect(mapStateToProps, mapDispatchToProps)( - DocumentationComponent, +export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, ); diff --git a/packages/website/ts/containers/smart_contracts_documentation.tsx b/packages/website/ts/containers/smart_contracts_documentation.tsx index c4731f929..a839529aa 100644 --- a/packages/website/ts/containers/smart_contracts_documentation.tsx +++ b/packages/website/ts/containers/smart_contracts_documentation.tsx @@ -2,12 +2,18 @@ import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; -import { Documentation as DocumentationComponent, DocumentationAllProps } from 'ts/pages/documentation/documentation'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocsInfoConfig, SmartContractDocSections as Sections, WebsitePaths } from 'ts/types'; -import { doxityUtils } from 'ts/utils/doxity_utils'; +import { + DocPackages, + DocsInfoConfig, + Networks, + SmartContractDocSections as Sections, + SupportedDocJson, + WebsitePaths, +} from 'ts/types'; import { Translate } from 'ts/utils/translate'; /* tslint:disable:no-var-requires */ @@ -15,10 +21,10 @@ const IntroMarkdown = require('md/docs/smart_contracts/introduction'); /* tslint:enable:no-var-requires */ const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.SmartContracts, + type: SupportedDocJson.Doxity, displayName: '0x Smart Contracts', packageUrl: 'https://github.com/0xProject/contracts', - websitePath: WebsitePaths.SmartContracts, - docsJsonRoot: 'https://s3.amazonaws.com/smart-contracts-docs-json', menu: { introduction: [Sections.Introduction], contracts: [Sections.Exchange, Sections.TokenRegistry, Sections.ZRXToken, Sections.TokenTransferProxy], @@ -34,7 +40,34 @@ const docsInfoConfig: DocsInfoConfig = { ZRXToken: Sections.ZRXToken, }, visibleConstructors: [Sections.Exchange, Sections.TokenRegistry, Sections.ZRXToken, Sections.TokenTransferProxy], - convertToDocAgnosticFormatFn: doxityUtils.convertToDocAgnosticFormat.bind(doxityUtils), + contractsByVersionByNetworkId: { + '1.0.0': { + [Networks.Mainnet]: { + [Sections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093', + [Sections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', + [Sections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498', + [Sections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c', + }, + [Networks.Ropsten]: { + [Sections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac', + [Sections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', + [Sections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', + [Sections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed', + }, + [Networks.Kovan]: { + [Sections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', + [Sections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4', + [Sections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', + [Sections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f', + }, + [Networks.Rinkeby]: { + [Sections.Exchange]: '0x1d16ef40fac01cec8adac2ac49427b9384192c05', + [Sections.TokenTransferProxy]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', + [Sections.ZRXToken]: '0x00f58d6d585f84b2d7267940cede30ce2fe6eae8', + [Sections.TokenRegistry]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', + }, + }, + }, }; const docsInfo = new DocsInfo(docsInfoConfig); @@ -49,7 +82,7 @@ interface ConnectedDispatch { docsInfo: DocsInfo; } -const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ +const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, translate: state.translate, @@ -60,6 +93,6 @@ const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ docsInfo, }); -export const Documentation: React.ComponentClass<DocumentationAllProps> = connect(mapStateToProps, mapDispatchToProps)( - DocumentationComponent, +export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, ); diff --git a/packages/website/ts/containers/zero_ex_js_documentation.tsx b/packages/website/ts/containers/zero_ex_js_documentation.tsx index a32a87f7f..500bf8d96 100644 --- a/packages/website/ts/containers/zero_ex_js_documentation.tsx +++ b/packages/website/ts/containers/zero_ex_js_documentation.tsx @@ -2,15 +2,14 @@ import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; -import { Documentation as DocumentationComponent, DocumentationAllProps } from 'ts/pages/documentation/documentation'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocsInfoConfig, Environments, WebsitePaths } from 'ts/types'; +import { DocPackages, DocsInfoConfig, Environments, SupportedDocJson, WebsitePaths } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; -import { typeDocUtils } from 'ts/utils/typedoc_utils'; /* tslint:disable:no-var-requires */ const IntroMarkdown = require('md/docs/0xjs/introduction'); @@ -37,15 +36,11 @@ const zeroExJsDocSections = { types: constants.TYPES_SECTION_NAME, }; -const s3BucketName = configs.ENVIRONMENT === Environments.DEVELOPMENT ? 'staging-0xjs-docs-jsons' : '0xjs-docs-jsons'; -const docsJsonRoot = `https://s3.amazonaws.com/${s3BucketName}`; - const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.ZeroExJs, + type: SupportedDocJson.TypeDoc, displayName: '0x.js', packageUrl: 'https://github.com/0xProject/0x.js', - subPackageName: '0x.js', - websitePath: WebsitePaths.ZeroExJs, - docsJsonRoot, menu: { introduction: [zeroExJsDocSections.introduction], install: [zeroExJsDocSections.installation], @@ -69,7 +64,8 @@ const docsInfoConfig: DocsInfoConfig = { [zeroExJsDocSections.versioning]: versioningMarkdown, }, // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is - // currently no way to extract the re-exported types from index.ts via TypeDoc :( + // currently no way to extract the re-exported types from index.ts via TypeDoc :( Make sure to only + // ADD types here, DO NOT REMOVE types since they might still be needed for older supported versions publicTypes: [ 'Order', 'SignedOrder', @@ -147,7 +143,6 @@ const docsInfoConfig: DocsInfoConfig = { }, sections: zeroExJsDocSections, visibleConstructors: [zeroExJsDocSections.zeroEx], - convertToDocAgnosticFormatFn: typeDocUtils.convertToDocAgnosticFormat.bind(typeDocUtils), }; const docsInfo = new DocsInfo(docsInfoConfig); @@ -162,7 +157,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ +const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, docsInfo, @@ -173,6 +168,6 @@ const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ dispatcher: new Dispatcher(dispatch), }); -export const Documentation: React.ComponentClass<DocumentationAllProps> = connect(mapStateToProps, mapDispatchToProps)( - DocumentationComponent, +export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, ); diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx new file mode 100644 index 000000000..2c8f1c103 --- /dev/null +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -0,0 +1,132 @@ +import findVersions = require('find-versions'); +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle = require('react-document-title'); +import semverSort = require('semver-sort'); +import { TopBar } from 'ts/components/top_bar/top_bar'; +import { DocsInfo } from 'ts/pages/documentation/docs_info'; +import { Documentation } from 'ts/pages/documentation/documentation'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { DocAgnosticFormat, DocPackages, DoxityDocObj, Environments, MenuSubsectionsBySection } from 'ts/types'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { docUtils } from 'ts/utils/doc_utils'; +import { Translate } from 'ts/utils/translate'; + +const docIdToS3BucketName: { [id: string]: string } = { + [DocPackages.ZeroExJs]: '0xjs-docs-jsons', + [DocPackages.SmartContracts]: 'smart-contracts-docs-json', + [DocPackages.Connect]: + configs.ENVIRONMENT === Environments.DEVELOPMENT ? 'staging-connect-docs-jsons' : 'connect-docs-jsons', +}; + +const docIdToSubpackageName: { [id: string]: string } = { + [DocPackages.ZeroExJs]: '0x.js', + [DocPackages.Connect]: 'connect', + [DocPackages.SmartContracts]: 'contracts', +}; + +export interface DocPageProps { + location: Location; + dispatcher: Dispatcher; + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; + translate: Translate; +} + +interface DocPageState { + docAgnosticFormat?: DocAgnosticFormat; +} + +export class DocPage extends React.Component<DocPageProps, DocPageState> { + private _isUnmounted: boolean; + constructor(props: DocPageProps) { + super(props); + this._isUnmounted = false; + this.state = { + docAgnosticFormat: undefined, + }; + } + public componentWillMount() { + const pathName = this.props.location.pathname; + const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); + const versions = findVersions(lastSegment); + const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined; + // tslint:disable-next-line:no-floating-promises + this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); + } + public componentWillUnmount() { + this._isUnmounted = true; + } + + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) + ? {} + : this.props.docsInfo.getMenuSubsectionsBySection(this.state.docAgnosticFormat); + const sourceUrl = this._getSourceUrl(); + return ( + <div> + <DocumentTitle title={`${this.props.docsInfo.displayName} Documentation`} /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + docsVersion={this.props.docsVersion} + availableDocVersions={this.props.availableDocVersions} + menu={this.props.docsInfo.getMenu(this.props.docsVersion)} + menuSubsectionsBySection={menuSubsectionsBySection} + docsInfo={this.props.docsInfo} + translate={this.props.translate} + /> + <Documentation + location={this.props.location} + docsVersion={this.props.docsVersion} + availableDocVersions={this.props.availableDocVersions} + docsInfo={this.props.docsInfo} + docAgnosticFormat={this.state.docAgnosticFormat} + menuSubsectionsBySection={menuSubsectionsBySection} + sourceUrl={sourceUrl} + /> + </div> + ); + } + private async _fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> { + const s3BucketName = docIdToS3BucketName[this.props.docsInfo.id]; + const docsJsonRoot = `${constants.S3_BUCKET_ROOT}/${s3BucketName}`; + const versionToFileName = await docUtils.getVersionToFileNameAsync(docsJsonRoot); + const versions = _.keys(versionToFileName); + this.props.dispatcher.updateAvailableDocVersions(versions); + const sortedVersions = semverSort.desc(versions); + const latestVersion = sortedVersions[0]; + + let versionToFetch = latestVersion; + if (!_.isUndefined(preferredVersionIfExists)) { + const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; + if (!_.isUndefined(preferredVersionFileNameIfExists)) { + versionToFetch = preferredVersionIfExists; + } + } + this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); + + const versionFileNameToFetch = versionToFileName[versionToFetch]; + const versionDocObj = await docUtils.getJSONDocFileAsync(versionFileNameToFetch, docsJsonRoot); + const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj); + + if (!this._isUnmounted) { + this.setState({ + docAgnosticFormat, + }); + } + } + private _getSourceUrl() { + const url = this.props.docsInfo.packageUrl; + const pkg = docIdToSubpackageName[this.props.docsInfo.id]; + let tagPrefix = pkg; + const packagesWithNamespace = ['connect']; + if (_.includes(packagesWithNamespace, pkg)) { + tagPrefix = `@0xproject/${pkg}`; + } + const sourceUrl = `${url}/blob/${tagPrefix}%40${this.props.docsVersion}/packages/${pkg}`; + return sourceUrl; + } +} diff --git a/packages/website/ts/pages/documentation/docs_info.ts b/packages/website/ts/pages/documentation/docs_info.ts index 4b1ec122a..31e151fe8 100644 --- a/packages/website/ts/pages/documentation/docs_info.ts +++ b/packages/website/ts/pages/documentation/docs_info.ts @@ -1,33 +1,37 @@ import compareVersions = require('compare-versions'); import * as _ from 'lodash'; import { + ContractsByVersionByNetworkId, DocAgnosticFormat, DocsInfoConfig, DocsMenu, DoxityDocObj, MenuSubsectionsBySection, SectionsMap, + SupportedDocJson, TypeDocNode, } from 'ts/types'; +import { doxityUtils } from 'ts/utils/doxity_utils'; +import { typeDocUtils } from 'ts/utils/typedoc_utils'; export class DocsInfo { + public id: string; + public type: SupportedDocJson; public displayName: string; public packageUrl: string; - public subPackageName?: string; - public websitePath: string; - public docsJsonRoot: string; public menu: DocsMenu; public sections: SectionsMap; public sectionNameToMarkdown: { [sectionName: string]: string }; + public contractsByVersionByNetworkId?: ContractsByVersionByNetworkId; private _docsInfo: DocsInfoConfig; constructor(config: DocsInfoConfig) { + this.id = config.id; + this.type = config.type; this.displayName = config.displayName; this.packageUrl = config.packageUrl; - this.subPackageName = config.subPackageName; - this.websitePath = config.websitePath; - this.docsJsonRoot = config.docsJsonRoot; this.sections = config.sections; this.sectionNameToMarkdown = config.sectionNameToMarkdown; + this.contractsByVersionByNetworkId = config.contractsByVersionByNetworkId; this._docsInfo = config; } public isPublicType(typeName: string): boolean { @@ -106,6 +110,10 @@ export class DocsInfo { return _.includes(this._docsInfo.visibleConstructors, sectionName); } public convertToDocAgnosticFormat(docObj: DoxityDocObj | TypeDocNode): DocAgnosticFormat { - return this._docsInfo.convertToDocAgnosticFormatFn(docObj, this); + if (this.type === SupportedDocJson.Doxity) { + return doxityUtils.convertToDocAgnosticFormat(docObj as DoxityDocObj); + } else { + return typeDocUtils.convertToDocAgnosticFormat(docObj as TypeDocNode, this); + } } } diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/website/ts/pages/documentation/documentation.tsx index 285471166..7eed78fc3 100644 --- a/packages/website/ts/pages/documentation/documentation.tsx +++ b/packages/website/ts/pages/documentation/documentation.tsx @@ -1,11 +1,7 @@ -import findVersions = require('find-versions'); import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; -import DocumentTitle = require('react-document-title'); import { scroller } from 'react-scroll'; -import semverSort = require('semver-sort'); -import { TopBar } from 'ts/components/top_bar/top_bar'; import { Badge } from 'ts/components/ui/badge'; import { Comment } from 'ts/pages/documentation/comment'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; @@ -17,25 +13,23 @@ import { TypeDefinition } from 'ts/pages/documentation/type_definition'; import { MarkdownSection } from 'ts/pages/shared/markdown_section'; import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; import { SectionHeader } from 'ts/pages/shared/section_header'; -import { Dispatcher } from 'ts/redux/dispatcher'; import { AddressByContractName, DocAgnosticFormat, DoxityDocObj, EtherscanLinkSuffixes, Event, + MenuSubsectionsBySection, Networks, Property, SolidityMethod, Styles, + SupportedDocJson, TypeDefinitionByName, TypescriptMethod, } from 'ts/types'; import { colors } from 'ts/utils/colors'; -import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; -import { docUtils } from 'ts/utils/doc_utils'; -import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; const TOP_BAR_HEIGHT = 60; @@ -48,20 +42,18 @@ const networkNameToColor: { [network: string]: string } = { [Networks.Rinkeby]: colors.darkYellow, }; -export interface DocumentationAllProps { - source: string; +export interface DocumentationProps { location: Location; - dispatcher: Dispatcher; docsVersion: string; availableDocVersions: string[]; docsInfo: DocsInfo; - translate: Translate; -} - -interface DocumentationState { docAgnosticFormat?: DocAgnosticFormat; + menuSubsectionsBySection: MenuSubsectionsBySection; + sourceUrl: string; } +interface DocumentationState {} + const styles: Styles = { mainContainers: { position: 'absolute', @@ -81,57 +73,17 @@ const styles: Styles = { }, }; -export class Documentation extends React.Component<DocumentationAllProps, DocumentationState> { - private _isUnmounted: boolean; - constructor(props: DocumentationAllProps) { - super(props); - this._isUnmounted = false; - this.state = { - docAgnosticFormat: undefined, - }; - } - public componentWillMount() { - const pathName = this.props.location.pathname; - const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); - const versions = findVersions(lastSegment); - const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined; - // tslint:disable-next-line:no-floating-promises - this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); - } - public componentWillUnmount() { - this._isUnmounted = true; +export class Documentation extends React.Component<DocumentationProps, DocumentationState> { + public componentDidUpdate(prevProps: DocumentationProps, prevState: DocumentationState) { + if (!_.isEqual(prevProps.docAgnosticFormat, this.props.docAgnosticFormat)) { + this._scrollToHash(); + } } public render() { - const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) - ? {} - : this.props.docsInfo.getMenuSubsectionsBySection(this.state.docAgnosticFormat); return ( <div> - <DocumentTitle title={`${this.props.docsInfo.displayName} Documentation`} /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - docsVersion={this.props.docsVersion} - availableDocVersions={this.props.availableDocVersions} - menu={this.props.docsInfo.getMenu(this.props.docsVersion)} - menuSubsectionsBySection={menuSubsectionsBySection} - docsInfo={this.props.docsInfo} - translate={this.props.translate} - /> - {_.isUndefined(this.state.docAgnosticFormat) ? ( - <div className="col col-12" style={styles.mainContainers}> - <div - className="relative sm-px2 sm-pt2 sm-m1" - style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} - > - <div className="center pb2"> - <CircularProgress size={40} thickness={5} /> - </div> - <div className="center pt2" style={{ paddingBottom: 11 }}> - Loading documentation... - </div> - </div> - </div> + {_.isUndefined(this.props.docAgnosticFormat) ? ( + this._renderLoading() ) : ( <div style={{ width: '100%', height: '100%', backgroundColor: colors.gray40 }}> <div @@ -155,8 +107,7 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume versions={this.props.availableDocVersions} title={this.props.docsInfo.displayName} topLevelMenu={this.props.docsInfo.getMenu(this.props.docsVersion)} - menuSubsectionsBySection={menuSubsectionsBySection} - docPath={this.props.docsInfo.websitePath} + menuSubsectionsBySection={this.props.menuSubsectionsBySection} /> </div> </div> @@ -175,11 +126,28 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume </div> ); } + private _renderLoading() { + return ( + <div className="col col-12" style={styles.mainContainers}> + <div + className="relative sm-px2 sm-pt2 sm-m1" + style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} + > + <div className="center pb2"> + <CircularProgress size={40} thickness={5} /> + </div> + <div className="center pt2" style={{ paddingBottom: 11 }}> + Loading documentation... + </div> + </div> + </div> + ); + } private _renderDocumentation(): React.ReactNode { const subMenus = _.values(this.props.docsInfo.getMenu()); const orderedSectionNames = _.flatten(subMenus); - const typeDefinitionByName = this.props.docsInfo.getTypeDefinitionsByName(this.state.docAgnosticFormat); + const typeDefinitionByName = this.props.docsInfo.getTypeDefinitionsByName(this.props.docAgnosticFormat); const renderedSections = _.map(orderedSectionNames, this._renderSection.bind(this, typeDefinitionByName)); return renderedSections; @@ -196,7 +164,7 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume ); } - const docSection = this.state.docAgnosticFormat[sectionName]; + const docSection = this.props.docAgnosticFormat[sectionName]; if (_.isUndefined(docSection)) { return null; } @@ -278,7 +246,13 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume ); } private _renderNetworkBadgesIfExists(sectionName: string) { - const networkToAddressByContractName = configs.CONTRACT_ADDRESS[this.props.docsVersion]; + if (this.props.docsInfo.type !== SupportedDocJson.Doxity) { + return null; + } + + const networkToAddressByContractName = this.props.docsInfo.contractsByVersionByNetworkId[ + this.props.docsVersion + ]; const badges = _.map( networkToAddressByContractName, (addressByContractName: AddressByContractName, networkName: string) => { @@ -326,8 +300,7 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume <SourceLink version={this.props.docsVersion} source={property.source} - baseUrl={this.props.docsInfo.packageUrl} - subPackageName={this.props.docsInfo.subPackageName} + sourceUrl={this.props.sourceUrl} /> )} {property.comment && <Comment comment={property.comment} className="py2" />} @@ -348,6 +321,7 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume typeDefinitionByName={typeDefinitionByName} libraryVersion={this.props.docsVersion} docsInfo={this.props.docsInfo} + sourceUrl={this.props.sourceUrl} /> ); } @@ -364,38 +338,4 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume containerId: 'documentation', }); } - private async _fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> { - const versionToFileName = await docUtils.getVersionToFileNameAsync(this.props.docsInfo.docsJsonRoot); - const versions = _.keys(versionToFileName); - this.props.dispatcher.updateAvailableDocVersions(versions); - const sortedVersions = semverSort.desc(versions); - const latestVersion = sortedVersions[0]; - - let versionToFetch = latestVersion; - if (!_.isUndefined(preferredVersionIfExists)) { - const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; - if (!_.isUndefined(preferredVersionFileNameIfExists)) { - versionToFetch = preferredVersionIfExists; - } - } - this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); - - const versionFileNameToFetch = versionToFileName[versionToFetch]; - const versionDocObj = await docUtils.getJSONDocFileAsync( - versionFileNameToFetch, - this.props.docsInfo.docsJsonRoot, - ); - const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj); - - if (!this._isUnmounted) { - this.setState( - { - docAgnosticFormat, - }, - () => { - this._scrollToHash(); - }, - ); - } - } } diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx index 1bc6aa4f4..d2c96bf8c 100644 --- a/packages/website/ts/pages/documentation/method_block.tsx +++ b/packages/website/ts/pages/documentation/method_block.tsx @@ -15,6 +15,7 @@ interface MethodBlockProps { libraryVersion: string; typeDefinitionByName: TypeDefinitionByName; docsInfo: DocsInfo; + sourceUrl: string; } interface MethodBlockState { @@ -80,8 +81,7 @@ export class MethodBlock extends React.Component<MethodBlockProps, MethodBlockSt <SourceLink version={this.props.libraryVersion} source={(method as TypescriptMethod).source} - baseUrl={this.props.docsInfo.packageUrl} - subPackageName={this.props.docsInfo.subPackageName} + sourceUrl={this.props.sourceUrl} /> )} {method.comment && <Comment comment={method.comment} className="py2" />} diff --git a/packages/website/ts/pages/documentation/source_link.tsx b/packages/website/ts/pages/documentation/source_link.tsx index 6588ee39e..31f80aba3 100644 --- a/packages/website/ts/pages/documentation/source_link.tsx +++ b/packages/website/ts/pages/documentation/source_link.tsx @@ -5,22 +5,13 @@ import { colors } from 'ts/utils/colors'; interface SourceLinkProps { source: Source; - baseUrl: string; + sourceUrl: string; version: string; - subPackageName: string; } -const packagesWithNamespace = ['connect']; - export function SourceLink(props: SourceLinkProps) { const src = props.source; - const url = props.baseUrl; - const pkg = props.subPackageName; - let tagPrefix = pkg; - if (_.includes(packagesWithNamespace, pkg)) { - tagPrefix = `@0xproject/${pkg}`; - } - const sourceCodeUrl = `${url}/blob/${tagPrefix}%40${props.version}/packages/${pkg}/${src.fileName}#L${src.line}`; + const sourceCodeUrl = `${props.sourceUrl}/${src.fileName}#L${src.line}`; return ( <div className="pt2" style={{ fontSize: 14 }}> <a href={sourceCodeUrl} target="_blank" className="underline" style={{ color: colors.grey }}> diff --git a/packages/website/ts/pages/shared/nested_sidebar_menu.tsx b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx index ba794ee9f..1a08ca9f9 100644 --- a/packages/website/ts/pages/shared/nested_sidebar_menu.tsx +++ b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx @@ -16,7 +16,6 @@ interface NestedSidebarMenuProps { onMenuItemClick?: () => void; selectedVersion?: string; versions?: string[]; - docPath?: string; } interface NestedSidebarMenuState {} @@ -69,13 +68,8 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N <div> {this._renderEmblem()} {!_.isUndefined(this.props.versions) && - !_.isUndefined(this.props.selectedVersion) && - !_.isUndefined(this.props.docPath) && ( - <VersionDropDown - selectedVersion={this.props.selectedVersion} - versions={this.props.versions} - docPath={this.props.docPath} - /> + !_.isUndefined(this.props.selectedVersion) && ( + <VersionDropDown selectedVersion={this.props.selectedVersion} versions={this.props.versions} /> )} <div className="pl1">{navigation}</div> </div> diff --git a/packages/website/ts/pages/shared/version_drop_down.tsx b/packages/website/ts/pages/shared/version_drop_down.tsx index b922e1048..3b331af9b 100644 --- a/packages/website/ts/pages/shared/version_drop_down.tsx +++ b/packages/website/ts/pages/shared/version_drop_down.tsx @@ -6,7 +6,6 @@ import * as React from 'react'; interface VersionDropDownProps { selectedVersion: string; versions: string[]; - docPath: string; } interface VersionDropDownState {} @@ -31,7 +30,17 @@ export class VersionDropDown extends React.Component<VersionDropDownProps, Versi }); return items; } - private _updateSelectedVersion(e: any, index: number, value: string) { - window.location.href = `${this.props.docPath}/${value}${window.location.hash}`; + private _updateSelectedVersion(e: any, index: number, semver: string) { + const port = window.location.port; + const hasPort = !_.isUndefined(port); + let path = window.location.pathname; + const lastChar = path[path.length - 1]; + if (_.isFinite(_.parseInt(lastChar))) { + const pathSections = path.split('/'); + pathSections.pop(); + path = pathSections.join('/'); + } + const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}${path}`; + window.location.href = `${baseUrl}/${semver}${window.location.hash}`; } } diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index f6413eec5..f0db537e6 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -627,20 +627,39 @@ export interface SectionsMap { [sectionName: string]: string; } +export enum DocPackages { + Connect = 'CONNECT', + ZeroExJs = 'ZERO_EX_JS', + SmartContracts = 'SMART_CONTRACTS', +} + +export enum SupportedDocJson { + Doxity = 'DOXITY', + TypeDoc = 'TYPEDOC', +} + +export interface ContractsByVersionByNetworkId { + [version: string]: { + [networkName: string]: { + [contractName: string]: string; + }; + }; +} + export interface DocsInfoConfig { + id: string; + type: SupportedDocJson; displayName: string; packageUrl: string; - websitePath: string; - docsJsonRoot: string; menu: DocsMenu; sections: SectionsMap; sectionNameToMarkdown: { [sectionName: string]: string }; visibleConstructors: string[]; - convertToDocAgnosticFormatFn: (docObj: DoxityDocObj | TypeDocNode, docsInfo?: any) => DocAgnosticFormat; subPackageName?: string; publicTypes?: string[]; sectionNameToModulePath?: { [sectionName: string]: string[] }; menuSubsectionToVersionWhenIntroduced?: { [sectionName: string]: string }; + contractsByVersionByNetworkId?: ContractsByVersionByNetworkId; } export interface TimestampMsRange { diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 8e359f8bd..7e9ba69de 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -1,12 +1,5 @@ import * as _ from 'lodash'; -import { - ContractAddresses, - Environments, - Networks, - OutdatedWrappedEtherByNetworkId, - PublicNodeUrlsByNetworkId, - SmartContractDocSections, -} from 'ts/types'; +import { ContractAddresses, Environments, OutdatedWrappedEtherByNetworkId, PublicNodeUrlsByNetworkId } from 'ts/types'; const BASE_URL = window.location.origin; const isDevelopment = _.includes( @@ -19,34 +12,6 @@ export const configs = { BACKEND_BASE_URL: 'https://website-api.0xproject.com', BASE_URL, BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', - CONTRACT_ADDRESS: { - '1.0.0': { - [Networks.Mainnet]: { - [SmartContractDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093', - [SmartContractDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', - [SmartContractDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498', - [SmartContractDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c', - }, - [Networks.Ropsten]: { - [SmartContractDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac', - [SmartContractDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', - [SmartContractDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', - [SmartContractDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed', - }, - [Networks.Kovan]: { - [SmartContractDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', - [SmartContractDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4', - [SmartContractDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', - [SmartContractDocSections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f', - }, - [Networks.Rinkeby]: { - [SmartContractDocSections.Exchange]: '0x1d16ef40fac01cec8adac2ac49427b9384192c05', - [SmartContractDocSections.TokenTransferProxy]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', - [SmartContractDocSections.ZRXToken]: '0x00f58d6d585f84b2d7267940cede30ce2fe6eae8', - [SmartContractDocSections.TokenRegistry]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', - }, - }, - } as ContractAddresses, DEFAULT_DERIVATION_PATH: `44'/60'/0'`, // WARNING: ZRX & WETH MUST always be default trackedTokens DEFAULT_TRACKED_TOKEN_SYMBOLS: ['WETH', 'ZRX'], diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index 6af821dbe..3476b7375 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -42,6 +42,7 @@ export const constants = { PROVIDER_NAME_GENERIC: 'Injected Web3', PROVIDER_NAME_PUBLIC: '0x Public', ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65', + S3_BUCKET_ROOT: 'https://s3.amazonaws.com', SUCCESS_STATUS: 200, UNAVAILABLE_STATUS: 503, TAKER_FEE: new BigNumber(0), diff --git a/packages/website/ts/utils/doxity_utils.ts b/packages/website/ts/utils/doxity_utils.ts index 5f1d02132..35ce05672 100644 --- a/packages/website/ts/utils/doxity_utils.ts +++ b/packages/website/ts/utils/doxity_utils.ts @@ -45,11 +45,17 @@ export const doxityUtils = { const methods: SolidityMethod[] = _.map<DoxityAbiDoc, SolidityMethod>( doxityMethods, (doxityMethod: DoxityAbiDoc) => { - // We assume that none of our functions returns more then a single value - const outputIfExists = !_.isUndefined(doxityMethod.outputs) ? doxityMethod.outputs[0] : undefined; - const returnTypeIfExists = !_.isUndefined(outputIfExists) - ? this._convertType(outputIfExists.type) - : undefined; + const outputs = !_.isUndefined(doxityMethod.outputs) ? doxityMethod.outputs : []; + let returnTypeIfExists: Type; + if (outputs.length === 0) { + // no-op. It's already undefined + } else if (outputs.length === 1) { + const outputsType = outputs[0].type; + returnTypeIfExists = this._convertType(outputsType); + } else { + const outputsType = `[${_.map(outputs, output => output.type).join(', ')}]`; + returnTypeIfExists = this._convertType(outputsType); + } // For ZRXToken, we want to convert it to zrxToken, rather then simply zRXToken const callPath = contractName !== 'ZRXToken' diff --git a/packages/website/ts/utils/typedoc_utils.ts b/packages/website/ts/utils/typedoc_utils.ts index 11ec8da58..992475911 100644 --- a/packages/website/ts/utils/typedoc_utils.ts +++ b/packages/website/ts/utils/typedoc_utils.ts @@ -4,6 +4,7 @@ import { CustomType, CustomTypeChild, DocAgnosticFormat, + DocPackages, DocSection, IndexSignature, KindString, @@ -108,7 +109,7 @@ export const typeDocUtils = { isConstructor, docsInfo.sections, sectionName, - docsInfo.subPackageName, + docsInfo.id, ); docSection.constructors.push(constructor); break; @@ -121,7 +122,7 @@ export const typeDocUtils = { isConstructor, docsInfo.sections, sectionName, - docsInfo.subPackageName, + docsInfo.id, ); docSection.methods.push(method); } @@ -133,7 +134,7 @@ export const typeDocUtils = { entity, docsInfo.sections, sectionName, - docsInfo.subPackageName, + docsInfo.id, ); docSection.properties.push(property); } @@ -149,7 +150,7 @@ export const typeDocUtils = { entity, docsInfo.sections, sectionName, - docsInfo.subPackageName, + docsInfo.id, ); docSection.types.push(customType); } @@ -161,21 +162,16 @@ export const typeDocUtils = { }); return docSection; }, - _convertCustomType( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): CustomType { + _convertCustomType(entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: string): CustomType { const typeIfExists = !_.isUndefined(entity.type) - ? typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName) + ? typeDocUtils._convertType(entity.type, sections, sectionName, docId) : undefined; const isConstructor = false; const methodIfExists = !_.isUndefined(entity.declaration) - ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, subPackageName) + ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, docId) : undefined; const indexSignatureIfExists = !_.isUndefined(entity.indexSignature) - ? typeDocUtils._convertIndexSignature(entity.indexSignature[0], sections, sectionName, subPackageName) + ? typeDocUtils._convertIndexSignature(entity.indexSignature[0], sections, sectionName, docId) : undefined; const commentIfExists = !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) @@ -185,7 +181,7 @@ export const typeDocUtils = { const childrenIfExist = !_.isUndefined(entity.children) ? _.map(entity.children, (child: TypeDocNode) => { const childTypeIfExists = !_.isUndefined(child.type) - ? typeDocUtils._convertType(child.type, sections, sectionName, subPackageName) + ? typeDocUtils._convertType(child.type, sections, sectionName, docId) : undefined; const c: CustomTypeChild = { name: child.name, @@ -212,27 +208,22 @@ export const typeDocUtils = { entity: TypeDocNode, sections: SectionsMap, sectionName: string, - subPackageName: string, + docId: string, ): IndexSignature { const key = entity.parameters[0]; const indexSignature = { keyName: key.name, - keyType: typeDocUtils._convertType(key.type, sections, sectionName, subPackageName), + keyType: typeDocUtils._convertType(key.type, sections, sectionName, docId), valueName: entity.type.name, }; return indexSignature; }, - _convertProperty( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): Property { + _convertProperty(entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: string): Property { const source = entity.sources[0]; const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined; const property = { name: entity.name, - type: typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName), + type: typeDocUtils._convertType(entity.type, sections, sectionName, docId), source: { fileName: source.fileName, line: source.line, @@ -246,7 +237,7 @@ export const typeDocUtils = { isConstructor: boolean, sections: SectionsMap, sectionName: string, - subPackageName: string, + docId: string, ): TypescriptMethod { const signature = entity.signatures[0]; const source = entity.sources[0]; @@ -258,7 +249,7 @@ export const typeDocUtils = { let callPath; if (isConstructor || entity.name === '__type') { callPath = ''; - } else if (subPackageName === '0x.js') { + } else if (docId === DocPackages.ZeroExJs) { const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.'; callPath = !_.isUndefined(sections.zeroEx) && sectionName !== sections.zeroEx @@ -269,12 +260,12 @@ export const typeDocUtils = { } const parameters = _.map(signature.parameters, param => { - return typeDocUtils._convertParameter(param, sections, sectionName, subPackageName); + return typeDocUtils._convertParameter(param, sections, sectionName, docId); }); - const returnType = typeDocUtils._convertType(signature.type, sections, sectionName, subPackageName); + const returnType = typeDocUtils._convertType(signature.type, sections, sectionName, docId); const typeParameter = _.isUndefined(signature.typeParameter) ? undefined - : typeDocUtils._convertTypeParameter(signature.typeParameter[0], sections, sectionName, subPackageName); + : typeDocUtils._convertTypeParameter(signature.typeParameter[0], sections, sectionName, docId); const method = { isConstructor, @@ -297,21 +288,16 @@ export const typeDocUtils = { entity: TypeDocNode, sections: SectionsMap, sectionName: string, - subPackageName: string, + docId: string, ): TypeParameter { - const type = typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName); + const type = typeDocUtils._convertType(entity.type, sections, sectionName, docId); const parameter = { name: entity.name, type, }; return parameter; }, - _convertParameter( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): Parameter { + _convertParameter(entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: string): Parameter { let comment = '<No comment>'; if (entity.comment && entity.comment.shortText) { comment = entity.comment.shortText; @@ -321,7 +307,7 @@ export const typeDocUtils = { const isOptional = !_.isUndefined(entity.flags.isOptional) ? entity.flags.isOptional : false; - const type = typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName); + const type = typeDocUtils._convertType(entity.type, sections, sectionName, docId); const parameter = { name: entity.name, @@ -331,17 +317,17 @@ export const typeDocUtils = { }; return parameter; }, - _convertType(entity: TypeDocType, sections: SectionsMap, sectionName: string, subPackageName: string): Type { + _convertType(entity: TypeDocType, sections: SectionsMap, sectionName: string, docId: string): Type { const typeArguments = _.map(entity.typeArguments, typeArgument => { - return typeDocUtils._convertType(typeArgument, sections, sectionName, subPackageName); + return typeDocUtils._convertType(typeArgument, sections, sectionName, docId); }); const types = _.map(entity.types, t => { - return typeDocUtils._convertType(t, sections, sectionName, subPackageName); + return typeDocUtils._convertType(t, sections, sectionName, docId); }); const isConstructor = false; const methodIfExists = !_.isUndefined(entity.declaration) - ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, subPackageName) + ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, docId) : undefined; const elementTypeIfExists = !_.isUndefined(entity.elementType) diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json index 38b177d0b..99f465bc2 100644 --- a/packages/website/tsconfig.json +++ b/packages/website/tsconfig.json @@ -13,5 +13,9 @@ "*": ["node_modules/@types/*", "*"] } }, - "include": ["./ts/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] + "include": [ + "./ts/**/*", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/ethers-typescript-typings/index.d.ts" + ] } @@ -3302,6 +3302,21 @@ ethereumjs-wallet@^0.6.0: utf8 "^2.1.1" uuid "^2.0.1" +ethers-contracts@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ethers-contracts/-/ethers-contracts-2.2.1.tgz#e2bf5dd5e157313ba454b50c646c8472fcd0a8b3" + dependencies: + ethers-utils "^2.1.0" + +ethers-utils@^2.1.0: + version "2.1.11" + resolved "https://registry.yarnpkg.com/ethers-utils/-/ethers-utils-2.1.11.tgz#b27535ca3226118be300211c39c896b1e5e21641" + dependencies: + bn.js "^4.4.0" + hash.js "^1.0.0" + js-sha3 "0.5.7" + xmlhttprequest "1.8.0" + ethjs-abi@0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.1.8.tgz#cd288583ed628cdfadaf8adefa3ba1dbcbca6c18" @@ -5094,6 +5109,10 @@ js-sha3@0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.5.tgz#baf0c0e8c54ad5903447df96ade7a4a1bca79a4a" +js-sha3@0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" + js-sha3@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.3.1.tgz#86122802142f0828502a0d1dee1d95e253bb0243" @@ -7252,9 +7271,9 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827" +prettier@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.0.tgz#c024f70cab158c993f50fc0c25ffe738cb8b0f85" pretty-bytes@^1.0.4: version "1.0.4" @@ -10205,11 +10224,7 @@ xml-js@^1.3.2: dependencies: sax "^1.2.4" -xmlbuilder@9.0.4: - version "9.0.4" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" - -xmlhttprequest@*: +xmlhttprequest@*, xmlhttprequest@1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" |