diff options
author | Brandon Millman <brandon.millman@gmail.com> | 2018-10-05 07:06:05 +0800 |
---|---|---|
committer | Brandon Millman <brandon.millman@gmail.com> | 2018-10-05 07:06:05 +0800 |
commit | e5153737d8386380675f28dd7cda70deeb1ea37c (patch) | |
tree | 81b061d2fa1af5952acc5abb41003f043ff8fce1 /packages/contract-wrappers | |
parent | 88766a02c7e6688e72d5c4c69ce68028b322f154 (diff) | |
parent | b04b649ec044b05f5c37bec214b7f992feb5998e (diff) | |
download | dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar.gz dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar.bz2 dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar.lz dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar.xz dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar.zst dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.zip |
Merge branch 'development'
* development: (939 commits)
Add asset-buyer to published packages section in README
Publish
Updated CHANGELOGS
Update BuyQuote interface
force re-build
Add website build to instructions
Revert format and re-add changes
Build website in parallel with other tests since no other test relies on it being built to run
Add back sourceMap support for both dev/prod
Upgrade webpack
Add missing default options
Remove unused constants
Add fee order with a takerFee
Add additional order factory methods and refactor test to use them
Add comments about buy quote calculation
Update CHANGELOG
Fix linter
Add additional test for slippage
Add buy_quote_calculator_test
Add 0x Instant to bundle analysis
...
Diffstat (limited to 'packages/contract-wrappers')
40 files changed, 1580 insertions, 145 deletions
diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index fdf779338..a96cb3a59 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,5 +1,128 @@ [ { + "version": "2.0.2", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1538693146 + }, + { + "timestamp": 1538157789, + "version": "2.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.0.0", + "changes": [ + { + "note": + "Fixes dropped events in subscriptions by fetching logs by blockHash instead of blockNumber. Support for fetching by blockHash was added in Geth > v1.8.13 and Parity > v2.1.0. Infura works too.", + "pr": 1080 + }, + { + "note": + "Fix misunderstanding about blockstream interface callbacks and pass the raw JSON RPC responses to it", + "pr": 1080 + } + ], + "timestamp": 1537907159 + }, + { + "version": "1.0.5", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1537875740 + }, + { + "version": "1.0.4", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1537541580 + }, + { + "version": "1.0.3", + "changes": [ + { + "note": "Drastically reduce the bundle size by removing unused parts of included contract artifacts." + } + ], + "timestamp": 1537369748 + }, + { + "version": "1.0.2", + "changes": [ + { + "note": "Add ZRX & WETH mainnet contract addresses into the included artifacts" + } + ], + "timestamp": 1537265493 + }, + { + "version": "1.0.1", + "changes": [ + { + "note": "Add `OrderValidatorWrapper`" + }, + { + "note": + "Fix bug where contracts not deployed on a network showed an `EXCHANGE_CONTRACT_DOES_NOT_EXIST` error instead of `CONTRACT_NOT_DEPLOYED_ON_NETWORK`", + "pr": 1044 + }, + { + "note": + "Export `AssetBalanceAndProxyAllowanceFetcher` and `OrderFilledCancelledFetcher` implementations", + "pr": 1054 + }, + { + "note": + "Add `validateOrderFillableOrThrowAsync` and `validateFillOrderThrowIfInvalidAsync` to ExchangeWrapper", + "pr": 1054 + } + ], + "timestamp": 1536142250 + }, + { + "version": "1.0.1-rc.5", + "changes": [ + { + "note": "Fix missing `BlockParamLiteral` type import issue" + } + ], + "timestamp": 1535377027 + }, + { + "version": "1.0.1-rc.4", + "changes": [ + { + "note": + "Export missing types: `TransactionEncoder`, `ContractAbi`, `JSONRPCRequestPayload`, `JSONRPCResponsePayload`, `JSONRPCErrorCallback`, `AbiDefinition`, `FunctionAbi`, `EventAbi`, `EventParameter`, `DecodedLogArgs`, `MethodAbi`, `ConstructorAbi`, `FallbackAbi`, `DataItem`, `ConstructorStateMutability`, `StateMutability` & `ExchangeSignatureValidatorApprovalEventArgs`", + "pr": 924 + }, + { + "note": + "Remove superfluous exported types: `ContractEvent`, `Token`, `OrderFillRequest`, `ContractEventArgs`, `LogEvent`, `OnOrderStateChangeCallback`, `ECSignature`, `OrderStateValid`, `OrderStateInvalid`, `OrderState`, `FilterObject`, `TransactionReceipt` & `TransactionReceiptWithDecodedLogs`", + "pr": 924 + }, + { + "note": "Added Transaction Encoder for use with 0x Exchange executeTransaction", + "pr": 975 + } + ], + "timestamp": 1535133899 + }, + { "version": "1.0.1-rc.3", "changes": [ { diff --git a/packages/contract-wrappers/CHANGELOG.md b/packages/contract-wrappers/CHANGELOG.md index c2ad7218e..60675ddbe 100644 --- a/packages/contract-wrappers/CHANGELOG.md +++ b/packages/contract-wrappers/CHANGELOG.md @@ -5,6 +5,52 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.2 - _October 4, 2018_ + + * Dependencies updated + +## v2.0.1 - _September 28, 2018_ + + * Dependencies updated + +## v2.0.0 - _September 25, 2018_ + + * Fixes dropped events in subscriptions by fetching logs by blockHash instead of blockNumber. Support for fetching by blockHash was added in Geth > v1.8.13 and Parity > v2.1.0. Infura works too. (#1080) + * Fix misunderstanding about blockstream interface callbacks and pass the raw JSON RPC responses to it (#1080) + +## v1.0.5 - _September 25, 2018_ + + * Dependencies updated + +## v1.0.4 - _September 21, 2018_ + + * Dependencies updated + +## v1.0.3 - _September 19, 2018_ + + * Drastically reduce the bundle size by removing unused parts of included contract artifacts. + +## v1.0.2 - _September 18, 2018_ + + * Add ZRX & WETH mainnet contract addresses into the included artifacts + +## v1.0.1 - _September 5, 2018_ + + * Add `OrderValidatorWrapper` + * Fix bug where contracts not deployed on a network showed an `EXCHANGE_CONTRACT_DOES_NOT_EXIST` error instead of `CONTRACT_NOT_DEPLOYED_ON_NETWORK` (#1044) + * Export `AssetBalanceAndProxyAllowanceFetcher` and `OrderFilledCancelledFetcher` implementations (#1054) + * Add `validateOrderFillableOrThrowAsync` and `validateFillOrderThrowIfInvalidAsync` to ExchangeWrapper (#1054) + +## v1.0.1-rc.5 - _August 27, 2018_ + + * Fix missing `BlockParamLiteral` type import issue + +## v1.0.1-rc.4 - _August 24, 2018_ + + * Export missing types: `TransactionEncoder`, `ContractAbi`, `JSONRPCRequestPayload`, `JSONRPCResponsePayload`, `JSONRPCErrorCallback`, `AbiDefinition`, `FunctionAbi`, `EventAbi`, `EventParameter`, `DecodedLogArgs`, `MethodAbi`, `ConstructorAbi`, `FallbackAbi`, `DataItem`, `ConstructorStateMutability`, `StateMutability` & `ExchangeSignatureValidatorApprovalEventArgs` (#924) + * Remove superfluous exported types: `ContractEvent`, `Token`, `OrderFillRequest`, `ContractEventArgs`, `LogEvent`, `OnOrderStateChangeCallback`, `ECSignature`, `OrderStateValid`, `OrderStateInvalid`, `OrderState`, `FilterObject`, `TransactionReceipt` & `TransactionReceiptWithDecodedLogs` (#924) + * Added Transaction Encoder for use with 0x Exchange executeTransaction (#975) + ## v1.0.1-rc.3 - _August 13, 2018_ * Added strict encoding/decoding checks for sendTransaction and call (#915) diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json index 7667b5b7f..e83caad97 100644 --- a/packages/contract-wrappers/package.json +++ b/packages/contract-wrappers/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/contract-wrappers", - "version": "1.0.1-rc.3", + "version": "2.0.2", "description": "Smart TS wrappers for 0x smart contracts", "keywords": [ "0xproject", @@ -11,26 +11,27 @@ "main": "lib/src/index.js", "types": "lib/src/index.d.ts", "scripts": { - "watch_without_deps": "yarn pre_build && tsc -w", - "build": "yarn pre_build && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts", - "pre_build": "run-s update_artifacts_v2_beta update_artifacts_v2 generate_contract_wrappers copy_artifacts", - "generate_contract_wrappers": "abi-gen --abis 'src/artifacts/@(Exchange|DummyERC20Token|DummyERC721Token|ZRXToken|ERC20Token|ERC721Token|WETH9|ERC20Proxy|ERC721Proxy|Forwarder).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers", + "build": "yarn pre_build && tsc -b", + "build:ci": "yarn build", + "pre_build": "run-s update_artifacts generate_contract_wrappers copy_artifacts", + "generate_contract_wrappers": "abi-gen --abis 'src/artifacts/@(Exchange|DummyERC20Token|DummyERC721Token|ZRXToken|ERC20Token|ERC721Token|WETH9|ERC20Proxy|ERC721Proxy|Forwarder|OrderValidator).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers", "lint": "tslint --project . --exclude **/src/contract_wrappers/**/* --exclude **/lib/**/*", "test:circleci": "run-s test:coverage", "test": "yarn run_mocha", "rebuild_and_test": "run-s build test", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", - "update_artifacts_v2_beta": "for i in ${npm_package_config_contracts_v2_beta}; do copyfiles -u 4 ../migrations/artifacts/2.0.0-beta-testnet/$i.json src/artifacts; done;", - "update_artifacts_v2": "for i in ${npm_package_config_contracts_v2}; do copyfiles -u 4 ../migrations/artifacts/2.0.0/$i.json src/artifacts; done;", + "update_artifacts": "for i in ${npm_package_config_contracts_v2}; do copyfiles -u 4 ../migrations/artifacts/2.0.0-trimmed/$i.json src/artifacts; done;", "copy_artifacts": "copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts", - "clean": "shx rm -rf _bundles lib test_temp scripts test/artifacts src/contract_wrappers/generated src/artifacts", + "clean": "shx rm -rf _bundles lib test_temp test/artifacts src/contract_wrappers/generated src/artifacts generated_docs", "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js lib/test/global_hooks.js --timeout 10000 --bail --exit", - "manual:postpublish": "yarn build; node ./scripts/postpublish.js" + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" }, "config": { - "contracts_v2_beta": "Exchange ERC20Proxy ERC20Token ERC721Proxy ERC721Token WETH9 ZRXToken Forwarder", - "contracts_v2": "DummyERC20Token DummyERC721Token" + "contracts_v2": "AssetProxyOwner Exchange ERC20Proxy ERC20Token ERC721Proxy ERC721Token WETH9 ZRXToken Forwarder OrderValidator DummyERC20Token DummyERC721Token", + "postpublish": { + "assets": [] + } }, "repository": { "type": "git", @@ -41,23 +42,21 @@ "node": ">=6.0.0" }, "devDependencies": { - "@0xproject/abi-gen": "^1.0.5", - "@0xproject/dev-utils": "^1.0.4", - "@0xproject/migrations": "^1.0.4", - "@0xproject/monorepo-scripts": "^1.0.5", - "@0xproject/sol-compiler": "^1.0.5", - "@0xproject/subproviders": "^1.0.5", - "@0xproject/tslint-config": "^1.0.5", + "@0xproject/abi-gen": "^1.0.13", + "@0xproject/dev-utils": "^1.0.12", + "@0xproject/migrations": "^1.0.14", + "@0xproject/subproviders": "^2.0.7", + "@0xproject/tslint-config": "^1.0.8", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", - "@types/node": "^8.0.53", + "@types/node": "*", "@types/sinon": "^2.2.2", "@types/uuid": "^3.4.2", - "awesome-typescript-loader": "^3.1.3", + "@types/web3-provider-engine": "^14.0.0", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", @@ -68,23 +67,24 @@ "sinon": "^4.0.0", "source-map-support": "^0.5.0", "tslint": "5.11.0", - "typescript": "2.9.2", + "typedoc": "0.12.0", + "typescript": "3.0.1", "web3-provider-engine": "14.0.6" }, "dependencies": { - "@0xproject/assert": "^1.0.5", - "@0xproject/base-contract": "^2.0.0-rc.1", - "@0xproject/fill-scenarios": "^1.0.1-rc.3", - "@0xproject/json-schemas": "^1.0.1-rc.4", - "@0xproject/order-utils": "^1.0.1-rc.3", - "@0xproject/types": "^1.0.1-rc.4", - "@0xproject/typescript-typings": "^1.0.4", - "@0xproject/utils": "^1.0.5", - "@0xproject/web3-wrapper": "^1.2.0", - "ethereum-types": "^1.0.4", - "ethereumjs-blockstream": "5.0.0", + "@0xproject/assert": "^1.0.13", + "@0xproject/base-contract": "^3.0.1", + "@0xproject/fill-scenarios": "^1.0.7", + "@0xproject/json-schemas": "^1.0.7", + "@0xproject/order-utils": "^1.0.7", + "@0xproject/types": "^1.1.4", + "@0xproject/typescript-typings": "^3.0.2", + "@0xproject/utils": "^2.0.2", + "@0xproject/web3-wrapper": "^3.0.3", + "ethereum-types": "^1.0.11", + "ethereumjs-blockstream": "6.0.0", "ethereumjs-util": "^5.1.1", - "ethers": "3.0.22", + "ethers": "4.0.0-beta.14", "js-sha3": "^0.7.0", "lodash": "^4.17.5", "uuid": "^3.1.0" diff --git a/packages/contract-wrappers/src/artifacts.ts b/packages/contract-wrappers/src/artifacts.ts index 2481b311a..925f34162 100644 --- a/packages/contract-wrappers/src/artifacts.ts +++ b/packages/contract-wrappers/src/artifacts.ts @@ -1,4 +1,4 @@ -import { ContractArtifact } from '@0xproject/sol-compiler'; +import { ContractArtifact } from 'ethereum-types'; import * as DummyERC20Token from './artifacts/DummyERC20Token.json'; import * as DummyERC721Token from './artifacts/DummyERC721Token.json'; @@ -8,6 +8,7 @@ import * as ERC721Proxy from './artifacts/ERC721Proxy.json'; import * as ERC721Token from './artifacts/ERC721Token.json'; import * as Exchange from './artifacts/Exchange.json'; import * as Forwarder from './artifacts/Forwarder.json'; +import * as OrderValidator from './artifacts/OrderValidator.json'; import * as EtherToken from './artifacts/WETH9.json'; import * as ZRXToken from './artifacts/ZRXToken.json'; @@ -22,4 +23,5 @@ export const artifacts = { ERC20Proxy: (ERC20Proxy as any) as ContractArtifact, ERC721Proxy: (ERC721Proxy as any) as ContractArtifact, Forwarder: (Forwarder as any) as ContractArtifact, + OrderValidator: (OrderValidator as any) as ContractArtifact, }; diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts index 4277a0746..89402029b 100644 --- a/packages/contract-wrappers/src/contract_wrappers.ts +++ b/packages/contract-wrappers/src/contract_wrappers.ts @@ -12,6 +12,7 @@ import { ERC721TokenWrapper } from './contract_wrappers/erc721_token_wrapper'; import { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper'; import { ExchangeWrapper } from './contract_wrappers/exchange_wrapper'; import { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper'; +import { OrderValidatorWrapper } from './contract_wrappers/order_validator_wrapper'; import { ContractWrappersConfigSchema } from './schemas/contract_wrappers_config_schema'; import { contractWrappersPrivateNetworkConfigSchema } from './schemas/contract_wrappers_private_network_config_schema'; import { contractWrappersPublicNetworkConfigSchema } from './schemas/contract_wrappers_public_network_config_schema'; @@ -52,8 +53,12 @@ export class ContractWrappers { * An instance of the ForwarderWrapper class containing methods for interacting with any Forwarder smart contract. */ public forwarder: ForwarderWrapper; + /** + * An instance of the OrderValidatorWrapper class containing methods for interacting with any OrderValidator smart contract. + */ + public orderValidator: OrderValidatorWrapper; - private _web3Wrapper: Web3Wrapper; + private readonly _web3Wrapper: Web3Wrapper; /** * Instantiates a new ContractWrappers instance. * @param provider The Provider instance you would like the 0x.js library to use for interacting with @@ -106,6 +111,8 @@ export class ContractWrappers { this.exchange = new ExchangeWrapper( this._web3Wrapper, config.networkId, + this.erc20Token, + this.erc721Token, config.exchangeContractAddress, config.zrxContractAddress, blockPollingIntervalMs, @@ -116,6 +123,7 @@ export class ContractWrappers { config.forwarderContractAddress, config.zrxContractAddress, ); + this.orderValidator = new OrderValidatorWrapper(this._web3Wrapper, config.networkId); } /** * Sets a new web3 provider for 0x.js. Updating the provider will stop all diff --git a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts index daf70253a..f7a89e3be 100644 --- a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts @@ -1,7 +1,15 @@ -import { ContractArtifact } from '@0xproject/sol-compiler'; import { AbiDecoder, intervalUtils, logUtils } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { BlockParamLiteral, ContractAbi, FilterObject, LogEntry, LogWithDecodedArgs, RawLog } from 'ethereum-types'; +import { marshaller, Web3Wrapper } from '@0xproject/web3-wrapper'; +import { + BlockParamLiteral, + ContractAbi, + ContractArtifact, + FilterObject, + LogEntry, + LogWithDecodedArgs, + RawLog, + RawLogEntry, +} from 'ethereum-types'; import { Block, BlockAndLogStreamer, Log } from 'ethereumjs-blockstream'; import * as _ from 'lodash'; @@ -138,16 +146,20 @@ export abstract class ContractWrapper { } protected _getContractAddress(artifact: ContractArtifact, addressIfExists?: string): string { if (_.isUndefined(addressIfExists)) { + if (_.isUndefined(artifact.networks[this._networkId])) { + throw new Error(ContractWrappersError.ContractNotDeployedOnNetwork); + } const contractAddress = artifact.networks[this._networkId].address; if (_.isUndefined(contractAddress)) { - throw new Error(ContractWrappersError.ExchangeContractDoesNotExist); + throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contractName]); } return contractAddress; } else { return addressIfExists; } } - private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, log: LogEntry): void { + private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, rawLog: RawLogEntry): void { + const log: LogEntry = marshaller.unmarshalLog(rawLog); _.forEach(this._filters, (filter: FilterObject, filterToken: string) => { if (filterUtils.matchesFilter(log, filter)) { const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>; @@ -164,8 +176,8 @@ export abstract class ContractWrapper { throw new Error(ContractWrappersError.SubscriptionAlreadyPresent); } this._blockAndLogStreamerIfExists = new BlockAndLogStreamer( - this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper), - this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), + this._blockstreamGetBlockOrNullAsync.bind(this), + this._blockstreamGetLogsAsync.bind(this), ContractWrapper._onBlockAndLogStreamerError.bind(this, isVerbose), ); const catchAllLogFilter = {}; @@ -184,6 +196,32 @@ export abstract class ContractWrapper { this._onLogStateChanged.bind(this, isRemoved), ); } + // This method only exists in order to comply with the expected interface of Blockstream's constructor + private async _blockstreamGetBlockOrNullAsync(hash: string): Promise<Block | null> { + const shouldIncludeTransactionData = false; + const blockOrNull = await this._web3Wrapper.sendRawPayloadAsync<Block | null>({ + method: 'eth_getBlockByHash', + params: [hash, shouldIncludeTransactionData], + }); + return blockOrNull; + } + // This method only exists in order to comply with the expected interface of Blockstream's constructor + private async _blockstreamGetLatestBlockOrNullAsync(): Promise<Block | null> { + const shouldIncludeTransactionData = false; + const blockOrNull = await this._web3Wrapper.sendRawPayloadAsync<Block | null>({ + method: 'eth_getBlockByNumber', + params: [BlockParamLiteral.Latest, shouldIncludeTransactionData], + }); + return blockOrNull; + } + // This method only exists in order to comply with the expected interface of Blockstream's constructor + private async _blockstreamGetLogsAsync(filterOptions: FilterObject): Promise<RawLogEntry[]> { + const logs = await this._web3Wrapper.sendRawPayloadAsync<RawLogEntry[]>({ + method: 'eth_getLogs', + params: [filterOptions], + }); + return logs as RawLogEntry[]; + } // HACK: This should be a package-scoped method (which doesn't exist in TS) // We don't want this method available in the public interface for all classes // who inherit from ContractWrapper, and it is only used by the internal implementation @@ -202,11 +240,14 @@ export abstract class ContractWrapper { delete this._blockAndLogStreamerIfExists; } private async _reconcileBlockAsync(): Promise<void> { - const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest); + const latestBlockOrNull = await this._blockstreamGetLatestBlockOrNullAsync(); + if (_.isNull(latestBlockOrNull)) { + return; // noop + } // We need to coerce to Block type cause Web3.Block includes types for mempool blocks if (!_.isUndefined(this._blockAndLogStreamerIfExists)) { // If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined - await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlock as any) as Block); + await this._blockAndLogStreamerIfExists.reconcileNewBlock(latestBlockOrNull); } } } diff --git a/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts index 821d1a8a2..ff027d78a 100644 --- a/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts @@ -16,6 +16,13 @@ export class ERC20ProxyWrapper extends ContractWrapper { public abi: ContractAbi = artifacts.ERC20Proxy.compilerOutput.abi; private _erc20ProxyContractIfExists?: ERC20ProxyContract; private _contractAddressIfExists?: string; + /** + * Instantiate ERC20ProxyWrapper + * @param web3Wrapper Web3Wrapper instance to use + * @param networkId Desired networkId + * @param contractAddressIfExists The contract address to use. This is usually pulled from + * the artifacts but needs to be specified when using with your own custom testnet. + */ constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { super(web3Wrapper, networkId); this._contractAddressIfExists = contractAddressIfExists; diff --git a/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts index 17bda5085..4625cef6a 100644 --- a/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts @@ -34,6 +34,13 @@ export class ERC20TokenWrapper extends ContractWrapper { public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; private _tokenContractsByAddress: { [address: string]: ERC20TokenContract }; private _erc20ProxyWrapper: ERC20ProxyWrapper; + /** + * Instantiate ERC20TokenWrapper + * @param web3Wrapper Web3Wrapper instance to use + * @param networkId Desired networkId + * @param erc20ProxyWrapper The ERC20ProxyWrapper instance to use + * @param blockPollingIntervalMs The block polling interval to use for active subscriptions + */ constructor( web3Wrapper: Web3Wrapper, networkId: number, diff --git a/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts index 38ecd4687..933c1dc27 100644 --- a/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts @@ -16,6 +16,13 @@ export class ERC721ProxyWrapper extends ContractWrapper { public abi: ContractAbi = artifacts.ERC20Proxy.compilerOutput.abi; private _erc721ProxyContractIfExists?: ERC721ProxyContract; private _contractAddressIfExists?: string; + /** + * Instantiate ERC721ProxyWrapper + * @param web3Wrapper Web3Wrapper instance to use + * @param networkId Desired networkId + * @param contractAddressIfExists The contract address to use. This is usually pulled from + * the artifacts but needs to be specified when using with your own custom testnet. + */ constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { super(web3Wrapper, networkId); this._contractAddressIfExists = contractAddressIfExists; diff --git a/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts index 7231e0bde..590dbbf74 100644 --- a/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts @@ -33,6 +33,13 @@ export class ERC721TokenWrapper extends ContractWrapper { public abi: ContractAbi = artifacts.ERC721Token.compilerOutput.abi; private _tokenContractsByAddress: { [address: string]: ERC721TokenContract }; private _erc721ProxyWrapper: ERC721ProxyWrapper; + /** + * Instantiate ERC721TokenWrapper + * @param web3Wrapper Web3Wrapper instance to use + * @param networkId Desired networkId + * @param erc721ProxyWrapper The ERC721ProxyWrapper instance to use + * @param blockPollingIntervalMs The block polling interval to use for active subscriptions + */ constructor( web3Wrapper: Web3Wrapper, networkId: number, diff --git a/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts index 5046d3667..1ac01812e 100644 --- a/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts @@ -24,6 +24,13 @@ export class EtherTokenWrapper extends ContractWrapper { [address: string]: WETH9Contract; } = {}; private _erc20TokenWrapper: ERC20TokenWrapper; + /** + * Instantiate EtherTokenWrapper. + * @param web3Wrapper Web3Wrapper instance to use + * @param networkId Desired networkId + * @param erc20TokenWrapper The ERC20TokenWrapper instance to use + * @param blockPollingIntervalMs The block polling interval to use for active subscriptions + */ constructor( web3Wrapper: Web3Wrapper, networkId: number, diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 48bd00f90..dc9ffcbf7 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1,12 +1,19 @@ import { schemas } from '@0xproject/json-schemas'; -import { assetDataUtils } from '@0xproject/order-utils'; +import { + assetDataUtils, + BalanceAndProxyAllowanceLazyStore, + ExchangeTransferSimulator, + OrderValidationUtils, +} from '@0xproject/order-utils'; import { AssetProxyId, Order, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { ContractAbi, LogWithDecodedArgs } from 'ethereum-types'; +import { BlockParamLiteral, ContractAbi, LogWithDecodedArgs } from 'ethereum-types'; import * as _ from 'lodash'; import { artifacts } from '../artifacts'; +import { AssetBalanceAndProxyAllowanceFetcher } from '../fetchers/asset_balance_and_proxy_allowance_fetcher'; +import { OrderFilledCancelledFetcher } from '../fetchers/order_filled_cancelled_fetcher'; import { methodOptsSchema } from '../schemas/method_opts_schema'; import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema'; import { txOptsSchema } from '../schemas/tx_opts_schema'; @@ -17,12 +24,17 @@ import { IndexedFilterValues, MethodOpts, OrderInfo, + OrderStatus, OrderTransactionOpts, + ValidateOrderFillableOpts, } from '../types'; import { assert } from '../utils/assert'; import { decorators } from '../utils/decorators'; +import { TransactionEncoder } from '../utils/transaction_encoder'; import { ContractWrapper } from './contract_wrapper'; +import { ERC20TokenWrapper } from './erc20_token_wrapper'; +import { ERC721TokenWrapper } from './erc721_token_wrapper'; import { ExchangeContract, ExchangeEventArgs, ExchangeEvents } from './generated/exchange'; /** @@ -32,16 +44,32 @@ import { ExchangeContract, ExchangeEventArgs, ExchangeEvents } from './generated export class ExchangeWrapper extends ContractWrapper { public abi: ContractAbi = artifacts.Exchange.compilerOutput.abi; private _exchangeContractIfExists?: ExchangeContract; + private _erc721TokenWrapper: ERC721TokenWrapper; + private _erc20TokenWrapper: ERC20TokenWrapper; private _contractAddressIfExists?: string; private _zrxContractAddressIfExists?: string; + /** + * Instantiate ExchangeWrapper + * @param web3Wrapper Web3Wrapper instance to use + * @param networkId Desired networkId + * @param contractAddressIfExists The exchange contract address to use. This is usually pulled from + * the artifacts but needs to be specified when using with your own custom testnet. + * @param zrxContractAddressIfExists The ZRXToken contract address to use. This is usually pulled from + * the artifacts but needs to be specified when using with your own custom testnet. + * @param blockPollingIntervalMs The block polling interval to use for active subscriptions + */ constructor( web3Wrapper: Web3Wrapper, networkId: number, + erc20TokenWrapper: ERC20TokenWrapper, + erc721TokenWrapper: ERC721TokenWrapper, contractAddressIfExists?: string, zrxContractAddressIfExists?: string, blockPollingIntervalMs?: number, ) { super(web3Wrapper, networkId, blockPollingIntervalMs); + this._erc20TokenWrapper = erc20TokenWrapper; + this._erc721TokenWrapper = erc721TokenWrapper; this._contractAddressIfExists = contractAddressIfExists; this._zrxContractAddressIfExists = zrxContractAddressIfExists; } @@ -665,6 +693,7 @@ export class ExchangeWrapper extends ContractWrapper { * @param leftSignedOrder First order to match. * @param rightSignedOrder Second order to match. * @param takerAddress The address that sends the transaction and gets the spread. + * @param orderTransactionOpts Optional arguments this method accepts. * @return Transaction hash. */ @decorators.asyncZeroExErrorHandler @@ -723,6 +752,7 @@ export class ExchangeWrapper extends ContractWrapper { * @param signerAddress Address that should have signed the given hash. * @param signature Proof that the hash has been signed by signer. * @param senderAddress Address that should send the transaction. + * @param orderTransactionOpts Optional arguments this method accepts. * @returns Transaction hash. */ @decorators.asyncZeroExErrorHandler @@ -901,7 +931,7 @@ export class ExchangeWrapper extends ContractWrapper { /** * Cancel a given order. * @param order An object that conforms to the Order or SignedOrder interface. The order you would like to cancel. - * @param transactionOpts Optional arguments this method accepts. + * @param orderTransactionOpts Optional arguments this method accepts. * @return Transaction hash. */ @decorators.asyncZeroExErrorHandler @@ -1072,6 +1102,64 @@ export class ExchangeWrapper extends ContractWrapper { return logs; } /** + * Validate if the supplied order is fillable, and throw if it isn't + * @param signedOrder SignedOrder of interest + * @param opts ValidateOrderFillableOpts options (e.g expectedFillTakerTokenAmount. + * If it isn't supplied, we check if the order is fillable for a non-zero amount) + */ + public async validateOrderFillableOrThrowAsync( + signedOrder: SignedOrder, + opts: ValidateOrderFillableOpts = {}, + ): Promise<void> { + const balanceAllowanceFetcher = new AssetBalanceAndProxyAllowanceFetcher( + this._erc20TokenWrapper, + this._erc721TokenWrapper, + BlockParamLiteral.Latest, + ); + const balanceAllowanceStore = new BalanceAndProxyAllowanceLazyStore(balanceAllowanceFetcher); + const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore); + + const expectedFillTakerTokenAmountIfExists = opts.expectedFillTakerTokenAmount; + const filledCancelledFetcher = new OrderFilledCancelledFetcher(this, BlockParamLiteral.Latest); + const orderValidationUtils = new OrderValidationUtils(filledCancelledFetcher); + await orderValidationUtils.validateOrderFillableOrThrowAsync( + exchangeTradeSimulator, + signedOrder, + this.getZRXAssetData(), + expectedFillTakerTokenAmountIfExists, + ); + } + /** + * Validate a call to FillOrder and throw if it wouldn't succeed + * @param signedOrder SignedOrder of interest + * @param fillTakerAssetAmount Amount we'd like to fill the order for + * @param takerAddress The taker of the order + */ + public async validateFillOrderThrowIfInvalidAsync( + signedOrder: SignedOrder, + fillTakerAssetAmount: BigNumber, + takerAddress: string, + ): Promise<void> { + const balanceAllowanceFetcher = new AssetBalanceAndProxyAllowanceFetcher( + this._erc20TokenWrapper, + this._erc721TokenWrapper, + BlockParamLiteral.Latest, + ); + const balanceAllowanceStore = new BalanceAndProxyAllowanceLazyStore(balanceAllowanceFetcher); + const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore); + + const filledCancelledFetcher = new OrderFilledCancelledFetcher(this, BlockParamLiteral.Latest); + const orderValidationUtils = new OrderValidationUtils(filledCancelledFetcher); + await orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTradeSimulator, + this._web3Wrapper.getProvider(), + signedOrder, + fillTakerAssetAmount, + takerAddress, + this.getZRXAssetData(), + ); + } + /** * Retrieves the Ethereum address of the Exchange contract deployed on the network * that the user-passed web3 provider is connected to. * @returns The Ethereum address of the Exchange contract being used. @@ -1097,6 +1185,16 @@ export class ExchangeWrapper extends ContractWrapper { const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxTokenAddress); return zrxAssetData; } + /** + * Returns a Transaction Encoder. Transaction messages exist for the purpose of calling methods on the Exchange contract + * in the context of another address. + * @return TransactionEncoder + */ + public async transactionEncoderAsync(): Promise<TransactionEncoder> { + const exchangeInstance = await this._getExchangeContractAsync(); + const encoder = new TransactionEncoder(exchangeInstance); + return encoder; + } // tslint:disable:no-unused-variable private _invalidateContractInstances(): void { this.unsubscribeAll(); diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index 13ef0fe01..906222731 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -12,6 +12,7 @@ import { TransactionOpts } from '../types'; import { assert } from '../utils/assert'; import { calldataOptimizationUtils } from '../utils/calldata_optimization_utils'; import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; import { ContractWrapper } from './contract_wrapper'; import { ForwarderContract } from './generated/forwarder'; @@ -57,7 +58,7 @@ export class ForwarderWrapper extends ContractWrapper { takerAddress: string, ethAmount: BigNumber, signedFeeOrders: SignedOrder[] = [], - feePercentage: BigNumber = constants.ZERO_AMOUNT, + feePercentage: number = 0, feeRecipientAddress: string = constants.NULL_ADDRESS, txOpts: TransactionOpts = {}, ): Promise<string> { @@ -66,7 +67,7 @@ export class ForwarderWrapper extends ContractWrapper { await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); assert.isBigNumber('ethAmount', ethAmount); assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); - assert.isBigNumber('feePercentage', feePercentage); + assert.isNumber('feePercentage', feePercentage); assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress); assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); // other assertions @@ -76,6 +77,8 @@ export class ForwarderWrapper extends ContractWrapper { this.getZRXTokenAddress(), this.getEtherTokenAddress(), ); + // format feePercentage + const formattedFeePercentage = utils.numberPercentageToEtherTokenAmountPercentage(feePercentage); // lowercase input addresses const normalizedTakerAddress = takerAddress.toLowerCase(); const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); @@ -89,7 +92,7 @@ export class ForwarderWrapper extends ContractWrapper { _.map(optimizedMarketOrders, order => order.signature), optimizedFeeOrders, _.map(optimizedFeeOrders, order => order.signature), - feePercentage, + formattedFeePercentage, feeRecipientAddress, { value: ethAmount, @@ -124,7 +127,7 @@ export class ForwarderWrapper extends ContractWrapper { takerAddress: string, ethAmount: BigNumber, signedFeeOrders: SignedOrder[] = [], - feePercentage: BigNumber = constants.ZERO_AMOUNT, + feePercentage: number = 0, feeRecipientAddress: string = constants.NULL_ADDRESS, txOpts: TransactionOpts = {}, ): Promise<string> { @@ -134,7 +137,7 @@ export class ForwarderWrapper extends ContractWrapper { await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); assert.isBigNumber('ethAmount', ethAmount); assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); - assert.isBigNumber('feePercentage', feePercentage); + assert.isNumber('feePercentage', feePercentage); assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress); assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); // other assertions @@ -144,6 +147,8 @@ export class ForwarderWrapper extends ContractWrapper { this.getZRXTokenAddress(), this.getEtherTokenAddress(), ); + // format feePercentage + const formattedFeePercentage = utils.numberPercentageToEtherTokenAmountPercentage(feePercentage); // lowercase input addresses const normalizedTakerAddress = takerAddress.toLowerCase(); const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); @@ -158,7 +163,7 @@ export class ForwarderWrapper extends ContractWrapper { _.map(optimizedMarketOrders, order => order.signature), optimizedFeeOrders, _.map(optimizedFeeOrders, order => order.signature), - feePercentage, + formattedFeePercentage, feeRecipientAddress, { value: ethAmount, diff --git a/packages/contract-wrappers/src/contract_wrappers/order_validator_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/order_validator_wrapper.ts new file mode 100644 index 000000000..1da88f624 --- /dev/null +++ b/packages/contract-wrappers/src/contract_wrappers/order_validator_wrapper.ts @@ -0,0 +1,187 @@ +import { schemas } from '@0xproject/json-schemas'; +import { SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { ContractAbi } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { artifacts } from '../artifacts'; +import { BalanceAndAllowance, OrderAndTraderInfo, TraderInfo } from '../types'; +import { assert } from '../utils/assert'; + +import { ContractWrapper } from './contract_wrapper'; +import { OrderValidatorContract } from './generated/order_validator'; + +/** + * This class includes the functionality related to interacting with the OrderValidator contract. + */ +export class OrderValidatorWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.OrderValidator.compilerOutput.abi; + private _orderValidatorContractIfExists?: OrderValidatorContract; + /** + * Instantiate OrderValidatorWrapper + * @param web3Wrapper Web3Wrapper instance to use + * @param networkId Desired networkId + */ + constructor(web3Wrapper: Web3Wrapper, networkId: number) { + super(web3Wrapper, networkId); + } + /** + * Get an object conforming to OrderAndTraderInfo containing on-chain information of the provided order and address + * @param order An object conforming to SignedOrder + * @param takerAddress An ethereum address + * @return OrderAndTraderInfo + */ + public async getOrderAndTraderInfoAsync(order: SignedOrder, takerAddress: string): Promise<OrderAndTraderInfo> { + assert.doesConformToSchema('order', order, schemas.signedOrderSchema); + assert.isETHAddressHex('takerAddress', takerAddress); + const OrderValidatorContractInstance = await this._getOrderValidatorContractAsync(); + const orderAndTraderInfo = await OrderValidatorContractInstance.getOrderAndTraderInfo.callAsync( + order, + takerAddress, + ); + const result = { + orderInfo: orderAndTraderInfo[0], + traderInfo: orderAndTraderInfo[1], + }; + return result; + } + /** + * Get an array of objects conforming to OrderAndTraderInfo containing on-chain information of the provided orders and addresses + * @param orders An array of objects conforming to SignedOrder + * @param takerAddresses An array of ethereum addresses + * @return array of OrderAndTraderInfo + */ + public async getOrdersAndTradersInfoAsync( + orders: SignedOrder[], + takerAddresses: string[], + ): Promise<OrderAndTraderInfo[]> { + assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema); + _.forEach(takerAddresses, (takerAddress, index) => + assert.isETHAddressHex(`takerAddresses[${index}]`, takerAddress), + ); + assert.assert(orders.length === takerAddresses.length, 'Expected orders.length to equal takerAddresses.length'); + const OrderValidatorContractInstance = await this._getOrderValidatorContractAsync(); + const ordersAndTradersInfo = await OrderValidatorContractInstance.getOrdersAndTradersInfo.callAsync( + orders, + takerAddresses, + ); + const orderInfos = ordersAndTradersInfo[0]; + const traderInfos = ordersAndTradersInfo[1]; + const result = _.map(orderInfos, (orderInfo, index) => { + const traderInfo = traderInfos[index]; + return { + orderInfo, + traderInfo, + }; + }); + return result; + } + /** + * Get an object conforming to TraderInfo containing on-chain balance and allowances for maker and taker of order + * @param order An object conforming to SignedOrder + * @param takerAddress An ethereum address + * @return TraderInfo + */ + public async getTraderInfoAsync(order: SignedOrder, takerAddress: string): Promise<TraderInfo> { + assert.doesConformToSchema('order', order, schemas.signedOrderSchema); + assert.isETHAddressHex('takerAddress', takerAddress); + const OrderValidatorContractInstance = await this._getOrderValidatorContractAsync(); + const result = await OrderValidatorContractInstance.getTraderInfo.callAsync(order, takerAddress); + return result; + } + /** + * Get an array of objects conforming to TraderInfo containing on-chain balance and allowances for maker and taker of order + * @param orders An array of objects conforming to SignedOrder + * @param takerAddresses An array of ethereum addresses + * @return array of TraderInfo + */ + public async getTradersInfoAsync(orders: SignedOrder[], takerAddresses: string[]): Promise<TraderInfo[]> { + assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema); + _.forEach(takerAddresses, (takerAddress, index) => + assert.isETHAddressHex(`takerAddresses[${index}]`, takerAddress), + ); + assert.assert(orders.length === takerAddresses.length, 'Expected orders.length to equal takerAddresses.length'); + const OrderValidatorContractInstance = await this._getOrderValidatorContractAsync(); + const result = await OrderValidatorContractInstance.getTradersInfo.callAsync(orders, takerAddresses); + return result; + } + /** + * Get an object conforming to BalanceAndAllowance containing on-chain balance and allowance for some address and assetData + * @param address An ethereum address + * @param assetData An encoded string that can be decoded by a specified proxy contract + * @return BalanceAndAllowance + */ + public async getBalanceAndAllowanceAsync(address: string, assetData: string): Promise<BalanceAndAllowance> { + assert.isETHAddressHex('address', address); + assert.isHexString('assetData', assetData); + const OrderValidatorContractInstance = await this._getOrderValidatorContractAsync(); + const balanceAndAllowance = await OrderValidatorContractInstance.getBalanceAndAllowance.callAsync( + address, + assetData, + ); + const result = { + balance: balanceAndAllowance[0], + allowance: balanceAndAllowance[1], + }; + return result; + } + /** + * Get an array of objects conforming to BalanceAndAllowance containing on-chain balance and allowance for some address and array of assetDatas + * @param address An ethereum address + * @param assetDatas An array of encoded strings that can be decoded by a specified proxy contract + * @return BalanceAndAllowance + */ + public async getBalancesAndAllowancesAsync(address: string, assetDatas: string[]): Promise<BalanceAndAllowance[]> { + assert.isETHAddressHex('address', address); + _.forEach(assetDatas, (assetData, index) => assert.isHexString(`assetDatas[${index}]`, assetData)); + const OrderValidatorContractInstance = await this._getOrderValidatorContractAsync(); + const balancesAndAllowances = await OrderValidatorContractInstance.getBalancesAndAllowances.callAsync( + address, + assetDatas, + ); + const balances = balancesAndAllowances[0]; + const allowances = balancesAndAllowances[1]; + const result = _.map(balances, (balance, index) => { + const allowance = allowances[index]; + return { + balance, + allowance, + }; + }); + return result; + } + /** + * Get owner address of tokenId by calling `token.ownerOf(tokenId)`, but returns a null owner instead of reverting on an unowned token. + * @param tokenAddress An ethereum address + * @param tokenId An ERC721 tokenId + * @return Owner of tokenId or null address if unowned + */ + public async getERC721TokenOwnerAsync(tokenAddress: string, tokenId: BigNumber): Promise<string | undefined> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isBigNumber('tokenId', tokenId); + const OrderValidatorContractInstance = await this._getOrderValidatorContractAsync(); + const result = await OrderValidatorContractInstance.getERC721TokenOwner.callAsync(tokenAddress, tokenId); + return result; + } + // HACK: We don't want this method to be visible to the other units within that package but not to the end user. + // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused. + // tslint:disable-next-line:no-unused-variable + private _invalidateContractInstance(): void { + delete this._orderValidatorContractIfExists; + } + private async _getOrderValidatorContractAsync(): Promise<OrderValidatorContract> { + if (!_.isUndefined(this._orderValidatorContractIfExists)) { + return this._orderValidatorContractIfExists; + } + const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(artifacts.OrderValidator); + const contractInstance = new OrderValidatorContract( + abi, + address, + this._web3Wrapper.getProvider(), + this._web3Wrapper.getContractDefaults(), + ); + this._orderValidatorContractIfExists = contractInstance; + return this._orderValidatorContractIfExists; + } +} diff --git a/packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts b/packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts new file mode 100644 index 000000000..023cd5ac3 --- /dev/null +++ b/packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts @@ -0,0 +1,77 @@ +// tslint:disable:no-unnecessary-type-assertion +import { AbstractBalanceAndProxyAllowanceFetcher, assetDataUtils } from '@0xproject/order-utils'; +import { AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import { BlockParamLiteral } from 'ethereum-types'; + +import { ERC20TokenWrapper } from '../contract_wrappers/erc20_token_wrapper'; +import { ERC721TokenWrapper } from '../contract_wrappers/erc721_token_wrapper'; + +export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher { + private readonly _erc20Token: ERC20TokenWrapper; + private readonly _erc721Token: ERC721TokenWrapper; + private readonly _stateLayer: BlockParamLiteral; + constructor(erc20Token: ERC20TokenWrapper, erc721Token: ERC721TokenWrapper, stateLayer: BlockParamLiteral) { + this._erc20Token = erc20Token; + this._erc721Token = erc721Token; + this._stateLayer = stateLayer; + } + public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { + const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); + if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { + const decodedERC20AssetData = decodedAssetData as ERC20AssetData; + const balance = await this._erc20Token.getBalanceAsync(decodedERC20AssetData.tokenAddress, userAddress, { + defaultBlock: this._stateLayer, + }); + return balance; + } else { + const decodedERC721AssetData = decodedAssetData as ERC721AssetData; + const tokenOwner = await this._erc721Token.getOwnerOfAsync( + decodedERC721AssetData.tokenAddress, + decodedERC721AssetData.tokenId, + { + defaultBlock: this._stateLayer, + }, + ); + const balance = tokenOwner === userAddress ? new BigNumber(1) : new BigNumber(0); + return balance; + } + } + public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { + const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); + if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { + const decodedERC20AssetData = decodedAssetData as ERC20AssetData; + const proxyAllowance = await this._erc20Token.getProxyAllowanceAsync( + decodedERC20AssetData.tokenAddress, + userAddress, + { + defaultBlock: this._stateLayer, + }, + ); + return proxyAllowance; + } else { + const decodedERC721AssetData = decodedAssetData as ERC721AssetData; + + const isApprovedForAll = await this._erc721Token.isProxyApprovedForAllAsync( + decodedERC721AssetData.tokenAddress, + userAddress, + { + defaultBlock: this._stateLayer, + }, + ); + if (isApprovedForAll) { + return new BigNumber(this._erc20Token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + } else { + const isApproved = await this._erc721Token.isProxyApprovedAsync( + decodedERC721AssetData.tokenAddress, + decodedERC721AssetData.tokenId, + { + defaultBlock: this._stateLayer, + }, + ); + const proxyAllowance = isApproved ? new BigNumber(1) : new BigNumber(0); + return proxyAllowance; + } + } + } +} diff --git a/packages/contract-wrappers/src/fetchers/order_filled_cancelled_fetcher.ts b/packages/contract-wrappers/src/fetchers/order_filled_cancelled_fetcher.ts new file mode 100644 index 000000000..7a252aed3 --- /dev/null +++ b/packages/contract-wrappers/src/fetchers/order_filled_cancelled_fetcher.ts @@ -0,0 +1,29 @@ +// tslint:disable:no-unnecessary-type-assertion +import { AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils'; +import { BigNumber } from '@0xproject/utils'; +import { BlockParamLiteral } from 'ethereum-types'; + +import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper'; + +export class OrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher { + private readonly _exchange: ExchangeWrapper; + private readonly _stateLayer: BlockParamLiteral; + constructor(exchange: ExchangeWrapper, stateLayer: BlockParamLiteral) { + this._exchange = exchange; + this._stateLayer = stateLayer; + } + public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { + const filledTakerAmount = this._exchange.getFilledTakerAssetAmountAsync(orderHash, { + defaultBlock: this._stateLayer, + }); + return filledTakerAmount; + } + public async isOrderCancelledAsync(orderHash: string): Promise<boolean> { + const isCancelled = await this._exchange.isCancelledAsync(orderHash); + return isCancelled; + } + public getZRXAssetData(): string { + const zrxAssetData = this._exchange.getZRXAssetData(); + return zrxAssetData; + } +} diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index 1986e0004..2fcdd2ddb 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -6,46 +6,51 @@ export { ExchangeWrapper } from './contract_wrappers/exchange_wrapper'; export { ERC20ProxyWrapper } from './contract_wrappers/erc20_proxy_wrapper'; export { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper'; export { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper'; +export { OrderValidatorWrapper } from './contract_wrappers/order_validator_wrapper'; + +export { TransactionEncoder } from './utils/transaction_encoder'; export { ContractWrappersError, - EventCallback, - ContractEvent, - Token, IndexedFilterValues, BlockRange, - OrderFillRequest, - ContractEventArgs, ContractWrappersConfig, MethodOpts, OrderTransactionOpts, TransactionOpts, - LogEvent, - DecodedLogEvent, - OnOrderStateChangeCallback, OrderStatus, OrderInfo, + EventCallback, + DecodedLogEvent, + BalanceAndAllowance, + OrderAndTraderInfo, + TraderInfo, + ValidateOrderFillableOpts, } from './types'; -export { - Order, - SignedOrder, - ECSignature, - OrderStateValid, - OrderStateInvalid, - OrderState, - AssetProxyId, -} from '@0xproject/types'; +export { Order, SignedOrder, AssetProxyId } from '@0xproject/types'; export { BlockParamLiteral, - FilterObject, BlockParam, ContractEventArg, - LogWithDecodedArgs, Provider, - TransactionReceipt, - TransactionReceiptWithDecodedLogs, + ContractAbi, + JSONRPCRequestPayload, + JSONRPCResponsePayload, + JSONRPCErrorCallback, + AbiDefinition, + LogWithDecodedArgs, + FunctionAbi, + EventAbi, + EventParameter, + DecodedLogArgs, + MethodAbi, + ConstructorAbi, + FallbackAbi, + DataItem, + ConstructorStateMutability, + StateMutability, } from 'ethereum-types'; export { @@ -75,8 +80,14 @@ export { export { ExchangeCancelUpToEventArgs, ExchangeAssetProxyRegisteredEventArgs, + ExchangeSignatureValidatorApprovalEventArgs, ExchangeFillEventArgs, ExchangeCancelEventArgs, ExchangeEventArgs, ExchangeEvents, } from './contract_wrappers/generated/exchange'; + +export { AbstractBalanceAndProxyAllowanceFetcher, AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils'; + +export { AssetBalanceAndProxyAllowanceFetcher } from './fetchers/asset_balance_and_proxy_allowance_fetcher'; +export { OrderFilledCancelledFetcher } from './fetchers/order_filled_cancelled_fetcher'; diff --git a/packages/contract-wrappers/src/monorepo_scripts/postpublish.ts b/packages/contract-wrappers/src/monorepo_scripts/postpublish.ts deleted file mode 100644 index dcb99d0f7..000000000 --- a/packages/contract-wrappers/src/monorepo_scripts/postpublish.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { postpublishUtils } from '@0xproject/monorepo-scripts'; - -import * as packageJSON from '../package.json'; -import * as tsConfigJSON from '../tsconfig.json'; - -const cwd = `${__dirname}/..`; -// tslint:disable-next-line:no-floating-promises -postpublishUtils.runAsync(packageJSON, tsConfigJSON, cwd); diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts index 2b3cdc591..e0b12b7c9 100644 --- a/packages/contract-wrappers/src/types.ts +++ b/packages/contract-wrappers/src/types.ts @@ -188,3 +188,24 @@ export enum OrderStatus { FULLY_FILLED, CANCELLED, } + +export interface TraderInfo { + makerBalance: BigNumber; + makerAllowance: BigNumber; + takerBalance: BigNumber; + takerAllowance: BigNumber; + makerZrxBalance: BigNumber; + makerZrxAllowance: BigNumber; + takerZrxBalance: BigNumber; + takerZrxAllowance: BigNumber; +} + +export interface OrderAndTraderInfo { + orderInfo: OrderInfo; + traderInfo: TraderInfo; +} + +export interface BalanceAndAllowance { + balance: BigNumber; + allowance: BigNumber; +} diff --git a/packages/contract-wrappers/src/utils/assert.ts b/packages/contract-wrappers/src/utils/assert.ts index 183642170..30726c546 100644 --- a/packages/contract-wrappers/src/utils/assert.ts +++ b/packages/contract-wrappers/src/utils/assert.ts @@ -1,8 +1,8 @@ import { assert as sharedAssert } from '@0xproject/assert'; // HACK: We need those two unused imports because they're actually used by sharedAssert which gets injected here import { Schema } from '@0xproject/json-schemas'; // tslint:disable-line:no-unused-variable -import { assetDataUtils, isValidSignatureAsync } from '@0xproject/order-utils'; -import { ECSignature, Order } from '@0xproject/types'; // tslint:disable-line:no-unused-variable +import { assetDataUtils, signatureUtils } from '@0xproject/order-utils'; +import { Order } from '@0xproject/types'; // tslint:disable-line:no-unused-variable import { BigNumber } from '@0xproject/utils'; // tslint:disable-line:no-unused-variable import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; @@ -18,7 +18,7 @@ export const assert = { signature: string, signerAddress: string, ): Promise<void> { - const isValid = await isValidSignatureAsync(provider, orderHash, signature, signerAddress); + const isValid = await signatureUtils.isValidSignatureAsync(provider, orderHash, signature, signerAddress); sharedAssert.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`); }, isValidSubscriptionToken(variableName: string, subscriptionToken: string): void { diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts index 2df11538c..78441decf 100644 --- a/packages/contract-wrappers/src/utils/constants.ts +++ b/packages/contract-wrappers/src/utils/constants.ts @@ -12,4 +12,6 @@ export const constants = { UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), DEFAULT_BLOCK_POLLING_INTERVAL: 1000, ZERO_AMOUNT: new BigNumber(0), + ONE_AMOUNT: new BigNumber(1), + ETHER_TOKEN_DECIMALS: 18, }; diff --git a/packages/contract-wrappers/src/utils/decorators.ts b/packages/contract-wrappers/src/utils/decorators.ts index 6e77450e8..e17246015 100644 --- a/packages/contract-wrappers/src/utils/decorators.ts +++ b/packages/contract-wrappers/src/utils/decorators.ts @@ -1,4 +1,3 @@ -import { RevertReason } from '@0xproject/types'; import * as _ from 'lodash'; import { AsyncMethod, ContractWrappersError, SyncMethod } from '../types'; @@ -24,7 +23,7 @@ const contractCallErrorTransformer = (error: Error) => { const schemaErrorTransformer = (error: Error) => { if (_.includes(error.message, constants.INVALID_TAKER_FORMAT)) { const errMsg = - 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; + 'Order taker must be of type string. If you want anyone to be able to fill an order - pass NULL_ADDRESS'; return new Error(errMsg); } return error; @@ -46,7 +45,7 @@ const asyncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => { // tslint:disable-next-line:only-arrow-functions descriptor.value = async function(...args: any[]): Promise<any> { try { - const result = await originalMethod.apply(this, args); + const result = await originalMethod.apply(this, args); // tslint:disable-line:no-invalid-this return result; } catch (error) { const transformedError = errorTransformer(error); @@ -73,7 +72,7 @@ const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => { // tslint:disable-next-line:only-arrow-functions descriptor.value = function(...args: any[]): any { try { - const result = originalMethod.apply(this, args); + const result = originalMethod.apply(this, args); // tslint:disable-line:no-invalid-this return result; } catch (error) { const transformedError = errorTransformer(error); diff --git a/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts b/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts index 279f2a796..a7c4a238f 100644 --- a/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts +++ b/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts @@ -34,7 +34,7 @@ const ERR_MSG_MAPPING = { }; export class ExchangeTransferSimulator { - private _store: AbstractBalanceAndProxyAllowanceLazyStore; + private readonly _store: AbstractBalanceAndProxyAllowanceLazyStore; private static _throwValidationError( failureReason: FailureReason, tradeSide: TradeSide, diff --git a/packages/contract-wrappers/src/utils/filter_utils.ts b/packages/contract-wrappers/src/utils/filter_utils.ts index 0e73987f7..c05be062c 100644 --- a/packages/contract-wrappers/src/utils/filter_utils.ts +++ b/packages/contract-wrappers/src/utils/filter_utils.ts @@ -1,4 +1,4 @@ -import { ConstructorAbi, ContractAbi, EventAbi, FallbackAbi, FilterObject, LogEntry, MethodAbi } from 'ethereum-types'; +import { ContractAbi, EventAbi, FilterObject, LogEntry } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as jsSHA3 from 'js-sha3'; import * as _ from 'lodash'; diff --git a/packages/contract-wrappers/src/utils/transaction_encoder.ts b/packages/contract-wrappers/src/utils/transaction_encoder.ts new file mode 100644 index 000000000..87cbb43fd --- /dev/null +++ b/packages/contract-wrappers/src/utils/transaction_encoder.ts @@ -0,0 +1,293 @@ +import { schemas } from '@0xproject/json-schemas'; +import { EIP712Schema, EIP712Types, eip712Utils } from '@0xproject/order-utils'; +import { Order, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import _ = require('lodash'); + +import { ExchangeContract } from '../contract_wrappers/generated/exchange'; + +import { assert } from './assert'; + +const EIP712_ZEROEX_TRANSACTION_SCHEMA: EIP712Schema = { + name: 'ZeroExTransaction', + parameters: [ + { name: 'salt', type: EIP712Types.Uint256 }, + { name: 'signerAddress', type: EIP712Types.Address }, + { name: 'data', type: EIP712Types.Bytes }, + ], +}; + +/** + * Transaction Encoder. Transaction messages exist for the purpose of calling methods on the Exchange contract + * in the context of another address. For example, UserA can encode and sign a fillOrder transaction and UserB + * can submit this to the blockchain. The Exchange context executes as if UserA had directly submitted this transaction. + */ +export class TransactionEncoder { + private readonly _exchangeInstance: ExchangeContract; + constructor(exchangeInstance: ExchangeContract) { + this._exchangeInstance = exchangeInstance; + } + /** + * Encodes the transaction data for use with the Exchange contract. + * @param data The ABI Encoded 0x Exchange method. I.e fillOrder + * @param salt A random value to provide uniqueness and prevent replay attacks. + * @param signerAddress The address which will sign this transaction. + * @return An unsigned hex encoded transaction for use in 0x Exchange executeTransaction. + */ + public getTransactionHex(data: string, salt: BigNumber, signerAddress: string): string { + const exchangeAddress = this._getExchangeContract().address; + const executeTransactionData = { + salt, + signerAddress, + data, + }; + const executeTransactionHashBuff = eip712Utils.structHash( + EIP712_ZEROEX_TRANSACTION_SCHEMA, + executeTransactionData, + ); + const eip721MessageBuffer = eip712Utils.createEIP712Message(executeTransactionHashBuff, exchangeAddress); + const messageHex = `0x${eip721MessageBuffer.toString('hex')}`; + return messageHex; + } + /** + * Encodes a fillOrder transaction. + * @param signedOrder An object that conforms to the SignedOrder interface. + * @param takerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public fillOrderTx(signedOrder: SignedOrder, takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount); + const abiEncodedData = this._getExchangeContract().fillOrder.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + return abiEncodedData; + } + /** + * Encodes a fillOrderNoThrow transaction. + * @param signedOrder An object that conforms to the SignedOrder interface. + * @param takerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public fillOrderNoThrowTx(signedOrder: SignedOrder, takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount); + const abiEncodedData = this._getExchangeContract().fillOrderNoThrow.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + return abiEncodedData; + } + /** + * Encodes a fillOrKillOrder transaction. + * @param signedOrder An object that conforms to the SignedOrder interface. + * @param takerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public fillOrKillOrderTx(signedOrder: SignedOrder, takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount); + const abiEncodedData = this._getExchangeContract().fillOrKillOrder.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + return abiEncodedData; + } + /** + * Encodes a batchFillOrders transaction. + * @param signedOrders An array of signed orders to fill. + * @param takerAssetFillAmounts The amounts of the orders (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public batchFillOrdersTx(signedOrders: SignedOrder[], takerAssetFillAmounts: BigNumber[]): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + _.forEach(takerAssetFillAmounts, takerAssetFillAmount => + assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount), + ); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._getExchangeContract().batchFillOrders.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmounts, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a batchFillOrKillOrders transaction. + * @param signedOrders An array of signed orders to fill. + * @param takerAssetFillAmounts The amounts of the orders (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public batchFillOrKillOrdersTx(signedOrders: SignedOrder[], takerAssetFillAmounts: BigNumber[]): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + _.forEach(takerAssetFillAmounts, takerAssetFillAmount => + assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount), + ); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._getExchangeContract().batchFillOrKillOrders.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmounts, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a batchFillOrdersNoThrow transaction. + * @param signedOrders An array of signed orders to fill. + * @param takerAssetFillAmounts The amounts of the orders (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public batchFillOrdersNoThrowTx(signedOrders: SignedOrder[], takerAssetFillAmounts: BigNumber[]): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + _.forEach(takerAssetFillAmounts, takerAssetFillAmount => + assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount), + ); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._getExchangeContract().batchFillOrdersNoThrow.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmounts, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a batchCancelOrders transaction. + * @param signedOrders An array of orders to cancel. + * @return Hex encoded abi of the function call. + */ + public batchCancelOrdersTx(signedOrders: SignedOrder[]): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + const abiEncodedData = this._getExchangeContract().batchCancelOrders.getABIEncodedTransactionData(signedOrders); + return abiEncodedData; + } + /** + * Encodes a cancelOrdersUpTo transaction. + * @param targetOrderEpoch Target order epoch. + * @return Hex encoded abi of the function call. + */ + public cancelOrdersUpToTx(targetOrderEpoch: BigNumber): string { + assert.isBigNumber('targetOrderEpoch', targetOrderEpoch); + const abiEncodedData = this._getExchangeContract().cancelOrdersUpTo.getABIEncodedTransactionData( + targetOrderEpoch, + ); + return abiEncodedData; + } + /** + * Encodes a cancelOrder transaction. + * @param order An object that conforms to the Order or SignedOrder interface. The order you would like to cancel. + * @return Hex encoded abi of the function call. + */ + public cancelOrderTx(order: Order | SignedOrder): string { + assert.doesConformToSchema('order', order, schemas.orderSchema); + const abiEncodedData = this._getExchangeContract().cancelOrder.getABIEncodedTransactionData(order); + return abiEncodedData; + } + /** + * Encodes a marketSellOrders transaction. + * @param signedOrders An array of signed orders to fill. + * @param takerAssetFillAmount Taker asset fill amount. + * @return Hex encoded abi of the function call. + */ + public marketSellOrdersTx(signedOrders: SignedOrder[], takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._getExchangeContract().marketSellOrders.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a marketSellOrdersNoThrow transaction. + * @param signedOrders An array of signed orders to fill. + * @param takerAssetFillAmount Taker asset fill amount. + * @return Hex encoded abi of the function call. + */ + public marketSellOrdersNoThrowTx(signedOrders: SignedOrder[], takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._getExchangeContract().marketSellOrdersNoThrow.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a maketBuyOrders transaction. + * @param signedOrders An array of signed orders to fill. + * @param makerAssetFillAmount Maker asset fill amount. + * @return Hex encoded abi of the function call. + */ + public marketBuyOrdersTx(signedOrders: SignedOrder[], makerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._getExchangeContract().marketBuyOrders.getABIEncodedTransactionData( + signedOrders, + makerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a maketBuyOrdersNoThrow transaction. + * @param signedOrders An array of signed orders to fill. + * @param makerAssetFillAmount Maker asset fill amount. + * @return Hex encoded abi of the function call. + */ + public marketBuyOrdersNoThrowTx(signedOrders: SignedOrder[], makerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._getExchangeContract().marketBuyOrdersNoThrow.getABIEncodedTransactionData( + signedOrders, + makerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a preSign transaction. + * @param hash Hash to pre-sign + * @param signerAddress Address that should have signed the given hash. + * @param signature Proof that the hash has been signed by signer. + * @return Hex encoded abi of the function call. + */ + public preSignTx(hash: string, signerAddress: string, signature: string): string { + assert.isHexString('hash', hash); + assert.isETHAddressHex('signerAddress', signerAddress); + assert.isHexString('signature', signature); + const abiEncodedData = this._getExchangeContract().preSign.getABIEncodedTransactionData( + hash, + signerAddress, + signature, + ); + return abiEncodedData; + } + /** + * Encodes a setSignatureValidatorApproval transaction. + * @param validatorAddress Validator contract address. + * @param isApproved Boolean value to set approval to. + * @return Hex encoded abi of the function call. + */ + public setSignatureValidatorApprovalTx(validatorAddress: string, isApproved: boolean): string { + assert.isETHAddressHex('validatorAddress', validatorAddress); + assert.isBoolean('isApproved', isApproved); + const abiEncodedData = this._getExchangeContract().setSignatureValidatorApproval.getABIEncodedTransactionData( + validatorAddress, + isApproved, + ); + return abiEncodedData; + } + private _getExchangeContract(): ExchangeContract { + return this._exchangeInstance; + } +} diff --git a/packages/contract-wrappers/src/utils/utils.ts b/packages/contract-wrappers/src/utils/utils.ts index 689a7ee0a..f7949ec34 100644 --- a/packages/contract-wrappers/src/utils/utils.ts +++ b/packages/contract-wrappers/src/utils/utils.ts @@ -1,4 +1,7 @@ import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; + +import { constants } from './constants'; export const utils = { getCurrentUnixTimestampSec(): BigNumber { @@ -8,4 +11,7 @@ export const utils = { getCurrentUnixTimestampMs(): BigNumber { return new BigNumber(Date.now()); }, + numberPercentageToEtherTokenAmountPercentage(percentage: number): BigNumber { + return Web3Wrapper.toBaseUnitAmount(constants.ONE_AMOUNT, constants.ETHER_TOKEN_DECIMALS).mul(percentage); + }, }; diff --git a/packages/contract-wrappers/test/calldata_optimization_utils_test.ts b/packages/contract-wrappers/test/calldata_optimization_utils_test.ts index a4cea772f..94e55bffa 100644 --- a/packages/contract-wrappers/test/calldata_optimization_utils_test.ts +++ b/packages/contract-wrappers/test/calldata_optimization_utils_test.ts @@ -1,9 +1,8 @@ -import { orderFactory } from '@0xproject/order-utils'; +import { orderFactory } from '@0xproject/order-utils/lib/src/order_factory'; import * as chai from 'chai'; import * as _ from 'lodash'; import 'mocha'; -import { assert } from '../src/utils/assert'; import { calldataOptimizationUtils } from '../src/utils/calldata_optimization_utils'; import { constants } from '../src/utils/constants'; diff --git a/packages/contract-wrappers/test/erc20_wrapper_test.ts b/packages/contract-wrappers/test/erc20_wrapper_test.ts index cf7ac527e..fde587dec 100644 --- a/packages/contract-wrappers/test/erc20_wrapper_test.ts +++ b/packages/contract-wrappers/test/erc20_wrapper_test.ts @@ -11,12 +11,13 @@ import { BlockRange, ContractWrappers, ContractWrappersError, - DecodedLogEvent, ERC20TokenApprovalEventArgs, ERC20TokenEvents, ERC20TokenTransferEventArgs, } from '../src'; +import { DecodedLogEvent } from '../src/types'; + import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; import { tokenUtils } from './utils/token_utils'; diff --git a/packages/contract-wrappers/test/erc721_wrapper_test.ts b/packages/contract-wrappers/test/erc721_wrapper_test.ts index f005ac2e0..10bac6086 100644 --- a/packages/contract-wrappers/test/erc721_wrapper_test.ts +++ b/packages/contract-wrappers/test/erc721_wrapper_test.ts @@ -11,12 +11,12 @@ import { BlockRange, ContractWrappers, ContractWrappersError, - DecodedLogEvent, ERC721TokenApprovalEventArgs, ERC721TokenApprovalForAllEventArgs, ERC721TokenEvents, ERC721TokenTransferEventArgs, } from '../src'; +import { DecodedLogEvent } from '../src/types'; import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; @@ -229,11 +229,17 @@ describe('ERC721Wrapper', () => { it('should set the proxy approval', async () => { const tokenId = await tokenUtils.mintDummyERC721Async(tokenAddress, ownerAddress); - const approvalBeforeSet = await contractWrappers.erc721Token.isProxyApprovedAsync(tokenAddress, tokenId); - expect(approvalBeforeSet).to.be.false(); + const isProxyApprovedBeforeSet = await contractWrappers.erc721Token.isProxyApprovedAsync( + tokenAddress, + tokenId, + ); + expect(isProxyApprovedBeforeSet).to.be.false(); await contractWrappers.erc721Token.setProxyApprovalAsync(tokenAddress, tokenId); - const approvalAfterSet = await contractWrappers.erc721Token.isProxyApprovedAsync(tokenAddress, tokenId); - expect(approvalAfterSet).to.be.true(); + const isProxyApprovedAfterSet = await contractWrappers.erc721Token.isProxyApprovedAsync( + tokenAddress, + tokenId, + ); + expect(isProxyApprovedAfterSet).to.be.true(); }); }); describe('#subscribe', () => { @@ -357,7 +363,6 @@ describe('ERC721Wrapper', () => { ); contractWrappers.erc721Token.unsubscribe(subscriptionToken); - const tokenId = await tokenUtils.mintDummyERC721Async(tokenAddress, ownerAddress); const isApproved = true; await web3Wrapper.awaitTransactionSuccessAsync( await contractWrappers.erc721Token.setApprovalForAllAsync( @@ -373,15 +378,11 @@ describe('ERC721Wrapper', () => { }); }); describe('#getLogsAsync', () => { - let tokenTransferProxyAddress: string; const blockRange: BlockRange = { fromBlock: 0, toBlock: BlockParamLiteral.Latest, }; let txHash: string; - before(() => { - tokenTransferProxyAddress = contractWrappers.erc721Proxy.getContractAddress(); - }); it('should get logs with decoded args emitted by ApprovalForAll', async () => { const isApprovedForAll = true; txHash = await contractWrappers.erc721Token.setApprovalForAllAsync( diff --git a/packages/contract-wrappers/test/ether_token_wrapper_test.ts b/packages/contract-wrappers/test/ether_token_wrapper_test.ts index 48bd6d3f6..c48fc224f 100644 --- a/packages/contract-wrappers/test/ether_token_wrapper_test.ts +++ b/packages/contract-wrappers/test/ether_token_wrapper_test.ts @@ -10,7 +10,6 @@ import { BlockRange, ContractWrappers, ContractWrappersError, - DecodedLogEvent, WETH9ApprovalEventArgs, WETH9DepositEventArgs, WETH9Events, @@ -18,6 +17,8 @@ import { WETH9WithdrawalEventArgs, } from '../src'; +import { DecodedLogEvent } from '../src/types'; + import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; import { tokenUtils } from './utils/token_utils'; @@ -343,7 +344,7 @@ describe('EtherTokenWrapper', () => { etherTokenAddress = tokenUtils.getWethTokenAddress(); erc20ProxyAddress = contractWrappers.erc20Proxy.getContractAddress(); // Start the block range after all migrations to avoid unexpected logs - const currentBlock = await web3Wrapper.getBlockNumberAsync(); + const currentBlock: number = await web3Wrapper.getBlockNumberAsync(); const fromBlock = currentBlock + 1; blockRange = { fromBlock, diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index fa3b49eb9..b3bd7c633 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -7,14 +7,8 @@ import * as chai from 'chai'; import { BlockParamLiteral } from 'ethereum-types'; import 'mocha'; -import { - ContractWrappers, - DecodedLogEvent, - ExchangeCancelEventArgs, - ExchangeEvents, - ExchangeFillEventArgs, - OrderStatus, -} from '../src'; +import { ContractWrappers, ExchangeCancelEventArgs, ExchangeEvents, ExchangeFillEventArgs, OrderStatus } from '../src'; +import { DecodedLogEvent } from '../src/types'; import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; @@ -364,7 +358,7 @@ describe('ExchangeWrapper', () => { describe('#getVersionAsync', () => { it('should return version the hash', async () => { const version = await contractWrappers.exchange.getVersionAsync(); - const VERSION = '2.0.1-alpha'; + const VERSION = '2.0.0'; expect(version).to.be.equal(VERSION); }); }); diff --git a/packages/contract-wrappers/test/forwarder_wrapper_test.ts b/packages/contract-wrappers/test/forwarder_wrapper_test.ts index 3f3b40e0b..f77b47337 100644 --- a/packages/contract-wrappers/test/forwarder_wrapper_test.ts +++ b/packages/contract-wrappers/test/forwarder_wrapper_test.ts @@ -1,20 +1,12 @@ -import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { FillScenarios } from '@0xproject/fill-scenarios'; -import { assetDataUtils, orderHashUtils } from '@0xproject/order-utils'; -import { DoneCallback, SignedOrder } from '@0xproject/types'; +import { assetDataUtils } from '@0xproject/order-utils'; +import { SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; -import { BlockParamLiteral } from 'ethereum-types'; import 'mocha'; -import { - ContractWrappers, - DecodedLogEvent, - ExchangeCancelEventArgs, - ExchangeEvents, - ExchangeFillEventArgs, - OrderStatus, -} from '../src'; +import { ContractWrappers, OrderStatus } from '../src'; import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; @@ -31,10 +23,8 @@ describe('ForwarderWrapper', () => { blockPollingIntervalMs: 0, }; const fillableAmount = new BigNumber(5); - const takerTokenFillAmount = new BigNumber(5); let contractWrappers: ContractWrappers; let fillScenarios: FillScenarios; - let forwarderContractAddress: string; let exchangeContractAddress: string; let zrxTokenAddress: string; let userAddresses: string[]; @@ -52,7 +42,6 @@ describe('ForwarderWrapper', () => { before(async () => { await blockchainLifecycle.startAsync(); contractWrappers = new ContractWrappers(provider, contractWrappersConfig); - forwarderContractAddress = contractWrappers.forwarder.getContractAddress(); exchangeContractAddress = contractWrappers.exchange.getContractAddress(); userAddresses = await web3Wrapper.getAvailableAddressesAsync(); zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); diff --git a/packages/contract-wrappers/test/global_hooks.ts b/packages/contract-wrappers/test/global_hooks.ts index 05b23d5b8..8731318e7 100644 --- a/packages/contract-wrappers/test/global_hooks.ts +++ b/packages/contract-wrappers/test/global_hooks.ts @@ -12,6 +12,6 @@ before('migrate contracts', async function(): Promise<void> { gas: devConstants.GAS_LIMIT, from: devConstants.TESTRPC_FIRST_ADDRESS, }; - const artifactsDir = `../migrations/artifacts/2.0.0`; + const artifactsDir = `src/artifacts`; await runV2MigrationsAsync(provider, artifactsDir, txDefaults); }); diff --git a/packages/contract-wrappers/test/order_validator_wrapper_test.ts b/packages/contract-wrappers/test/order_validator_wrapper_test.ts new file mode 100644 index 000000000..baac3eeee --- /dev/null +++ b/packages/contract-wrappers/test/order_validator_wrapper_test.ts @@ -0,0 +1,140 @@ +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { FillScenarios } from '@0xproject/fill-scenarios'; +import { assetDataUtils } from '@0xproject/order-utils'; +import { SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; + +import { ContractWrappers, OrderStatus } from '../src'; +import { OrderInfo, TraderInfo } from '../src/types'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { tokenUtils } from './utils/token_utils'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('OrderValidator', () => { + const contractWrappersConfig = { + networkId: constants.TESTRPC_NETWORK_ID, + blockPollingIntervalMs: 0, + }; + const fillableAmount = new BigNumber(5); + let contractWrappers: ContractWrappers; + let fillScenarios: FillScenarios; + let exchangeContractAddress: string; + let zrxTokenAddress: string; + let zrxTokenAssetData: string; + let userAddresses: string[]; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + let anotherMakerAddress: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAssetData: string; + let takerAssetData: string; + let signedOrder: SignedOrder; + let anotherSignedOrder: SignedOrder; + before(async () => { + await blockchainLifecycle.startAsync(); + contractWrappers = new ContractWrappers(provider, contractWrappersConfig); + exchangeContractAddress = contractWrappers.exchange.getContractAddress(); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); + zrxTokenAssetData = assetDataUtils.encodeERC20AssetData(zrxTokenAddress); + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + exchangeContractAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), + ); + [coinbase, makerAddress, takerAddress, feeRecipient, anotherMakerAddress] = userAddresses; + [makerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + takerTokenAddress = tokenUtils.getWethTokenAddress(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + constants.NULL_ADDRESS, + fillableAmount, + ); + anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + zrxTokenAssetData, + takerAssetData, + makerAddress, + constants.NULL_ADDRESS, + fillableAmount, + ); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#getOrdersAndTradersInfoAsync', () => { + let signedOrders: SignedOrder[]; + let takerAddresses: string[]; + let ordersInfo: OrderInfo[]; + let tradersInfo: TraderInfo[]; + beforeEach(async () => { + signedOrders = [signedOrder, anotherSignedOrder]; + takerAddresses = [takerAddress, takerAddress]; + const ordersAndTradersInfo = await contractWrappers.orderValidator.getOrdersAndTradersInfoAsync( + signedOrders, + takerAddresses, + ); + ordersInfo = _.map(ordersAndTradersInfo, orderAndTraderInfo => orderAndTraderInfo.orderInfo); + tradersInfo = _.map(ordersAndTradersInfo, orderAndTraderInfo => orderAndTraderInfo.traderInfo); + }); + it('should return the same number of order infos and trader infos as input orders', async () => { + expect(ordersInfo.length).to.be.equal(signedOrders.length); + expect(tradersInfo.length).to.be.equal(takerAddresses.length); + }); + it('should return correct on-chain order info for input orders', async () => { + const firstOrderInfo = ordersInfo[0]; + const secondOrderInfo = ordersInfo[1]; + expect(firstOrderInfo.orderStatus).to.be.equal(OrderStatus.FILLABLE); + expect(firstOrderInfo.orderTakerAssetFilledAmount).to.bignumber.equal(constants.ZERO_AMOUNT); + expect(secondOrderInfo.orderStatus).to.be.equal(OrderStatus.FILLABLE); + expect(secondOrderInfo.orderTakerAssetFilledAmount).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('should return correct on-chain trader info for input takers', async () => { + const firstTraderInfo = tradersInfo[0]; + const secondTraderInfo = tradersInfo[1]; + expect(firstTraderInfo.makerBalance).to.bignumber.equal(new BigNumber(5)); + expect(firstTraderInfo.makerAllowance).to.bignumber.equal(new BigNumber(5)); + expect(firstTraderInfo.takerBalance).to.bignumber.equal(new BigNumber(0)); + expect(firstTraderInfo.takerAllowance).to.bignumber.equal(new BigNumber(0)); + expect(firstTraderInfo.makerZrxBalance).to.bignumber.equal(new BigNumber(5)); + expect(firstTraderInfo.makerZrxAllowance).to.bignumber.equal(new BigNumber(5)); + expect(firstTraderInfo.takerZrxBalance).to.bignumber.equal(new BigNumber(0)); + expect(firstTraderInfo.takerZrxAllowance).to.bignumber.equal(new BigNumber(0)); + expect(secondTraderInfo.makerBalance).to.bignumber.equal(new BigNumber(5)); + expect(secondTraderInfo.makerAllowance).to.bignumber.equal(new BigNumber(5)); + expect(secondTraderInfo.takerBalance).to.bignumber.equal(new BigNumber(0)); + expect(secondTraderInfo.takerAllowance).to.bignumber.equal(new BigNumber(0)); + expect(secondTraderInfo.makerZrxBalance).to.bignumber.equal(new BigNumber(5)); + expect(secondTraderInfo.makerZrxAllowance).to.bignumber.equal(new BigNumber(5)); + expect(secondTraderInfo.takerZrxBalance).to.bignumber.equal(new BigNumber(0)); + expect(secondTraderInfo.takerZrxAllowance).to.bignumber.equal(new BigNumber(0)); + }); + }); +}); diff --git a/packages/contract-wrappers/test/revert_validation_test.ts b/packages/contract-wrappers/test/revert_validation_test.ts new file mode 100644 index 000000000..da011c1d7 --- /dev/null +++ b/packages/contract-wrappers/test/revert_validation_test.ts @@ -0,0 +1,122 @@ +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; +import { FillScenarios } from '@0xproject/fill-scenarios'; +import { runV2MigrationsAsync } from '@0xproject/migrations'; +import { assetDataUtils } from '@0xproject/order-utils'; +import { SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as chai from 'chai'; +import 'mocha'; + +import { ContractWrappers } from '../src'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { tokenUtils } from './utils/token_utils'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('Revert Validation ExchangeWrapper', () => { + let contractWrappers: ContractWrappers; + let userAddresses: string[]; + let zrxTokenAddress: string; + let fillScenarios: FillScenarios; + let exchangeContractAddress: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let anotherMakerAddress: string; + let takerAddress: string; + let makerAssetData: string; + let takerAssetData: string; + let feeRecipient: string; + let txHash: string; + let blockchainLifecycle: BlockchainLifecycle; + let web3Wrapper: Web3Wrapper; + const fillableAmount = new BigNumber(5); + const takerTokenFillAmount = new BigNumber(5); + let signedOrder: SignedOrder; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + blockPollingIntervalMs: 0, + }; + before(async () => { + // vmErrorsOnRPCResponse is useful for quick feedback and testing during development + // but is not the default behaviour in production. Here we ensure our failure cases + // are handled in an environment which behaves similar to production + const provider = web3Factory.getRpcProvider({ + shouldUseInProcessGanache: true, + shouldThrowErrorsOnGanacheRPCResponse: false, + }); + web3Wrapper = new Web3Wrapper(provider); + blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + const txDefaults = { + gas: devConstants.GAS_LIMIT, + from: devConstants.TESTRPC_FIRST_ADDRESS, + }; + const artifactsDir = `src/artifacts`; + // Re-deploy the artifacts in this provider, rather than in the default provider exposed in + // the beforeAll hook. This is due to the fact that the default provider enabled vmErrorsOnRPCResponse + // and we are explicity testing with vmErrorsOnRPCResponse disabled. + await runV2MigrationsAsync(provider, artifactsDir, txDefaults); + await blockchainLifecycle.startAsync(); + contractWrappers = new ContractWrappers(provider, config); + exchangeContractAddress = contractWrappers.exchange.getContractAddress(); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + exchangeContractAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), + ); + [coinbase, makerAddress, takerAddress, feeRecipient, anotherMakerAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#fillOrderAsync', () => { + it('should throw the revert reason when shouldValidate is true and a fill would revert', async () => { + // Create a scenario where the fill will revert + const makerTokenBalance = await contractWrappers.erc20Token.getBalanceAsync( + makerTokenAddress, + makerAddress, + ); + // Transfer all of the tokens from maker to create a failure scenario + txHash = await contractWrappers.erc20Token.transferAsync( + makerTokenAddress, + makerAddress, + takerAddress, + makerTokenBalance, + ); + await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + expect( + contractWrappers.exchange.fillOrderAsync(signedOrder, takerTokenFillAmount, takerAddress, { + shouldValidate: true, + }), + ).to.be.rejectedWith('TRANSFER_FAILED'); + }); + }); +}); diff --git a/packages/contract-wrappers/test/subscription_test.ts b/packages/contract-wrappers/test/subscription_test.ts index 80d17576f..6ec7519fe 100644 --- a/packages/contract-wrappers/test/subscription_test.ts +++ b/packages/contract-wrappers/test/subscription_test.ts @@ -1,11 +1,11 @@ -import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { DoneCallback } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import 'mocha'; import * as Sinon from 'sinon'; -import { ContractWrappers, DecodedLogEvent, ERC20TokenApprovalEventArgs, ERC20TokenEvents, Token } from '../src'; +import { ContractWrappers, ERC20TokenApprovalEventArgs, ERC20TokenEvents } from '../src'; +import { DecodedLogEvent } from '../src/types'; import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; @@ -17,17 +17,11 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); describe('SubscriptionTest', () => { let contractWrappers: ContractWrappers; - let userAddresses: string[]; - let coinbase: string; - let addressWithoutFunds: string; const config = { networkId: constants.TESTRPC_NETWORK_ID, }; before(async () => { contractWrappers = new ContractWrappers(provider, config); - userAddresses = await web3Wrapper.getAvailableAddressesAsync(); - coinbase = userAddresses[0]; - addressWithoutFunds = userAddresses[1]; }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -38,7 +32,6 @@ describe('SubscriptionTest', () => { describe('#subscribe', () => { const indexFilterValues = {}; let tokenAddress: string; - const allowanceAmount = new BigNumber(42); let stubs: Sinon.SinonStub[] = []; before(() => { const tokenAddresses = tokenUtils.getDummyERC20TokenAddresses(); @@ -52,7 +45,7 @@ describe('SubscriptionTest', () => { it('Should allow unsubscribeAll to be called successfully after an error', (done: DoneCallback) => { (async () => { const callback = (err: Error | null, _logEvent?: DecodedLogEvent<ERC20TokenApprovalEventArgs>) => - _.noop; + _.noop.bind(_); contractWrappers.erc20Token.subscribe( tokenAddress, ERC20TokenEvents.Approval, @@ -60,7 +53,7 @@ describe('SubscriptionTest', () => { callback, ); stubs = [ - Sinon.stub((contractWrappers as any)._web3Wrapper, 'getBlockAsync').throws( + Sinon.stub((contractWrappers as any)._web3Wrapper, 'getBlockIfExistsAsync').throws( new Error('JSON RPC error'), ), ]; diff --git a/packages/contract-wrappers/test/transaction_encoder_test.ts b/packages/contract-wrappers/test/transaction_encoder_test.ts new file mode 100644 index 000000000..a397e43a8 --- /dev/null +++ b/packages/contract-wrappers/test/transaction_encoder_test.ts @@ -0,0 +1,211 @@ +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { FillScenarios } from '@0xproject/fill-scenarios'; +import { assetDataUtils, generatePseudoRandomSalt, orderHashUtils, signatureUtils } from '@0xproject/order-utils'; +import { SignedOrder, SignerType } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import 'mocha'; + +import { ContractWrappers } from '../src'; +import { TransactionEncoder } from '../src/utils/transaction_encoder'; + +import { constants } from './utils/constants'; +import { tokenUtils } from './utils/token_utils'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('TransactionEncoder', () => { + let contractWrappers: ContractWrappers; + let userAddresses: string[]; + let fillScenarios: FillScenarios; + let exchangeContractAddress: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let senderAddress: string; + let takerAddress: string; + let makerAssetData: string; + let takerAssetData: string; + let txHash: string; + const fillableAmount = new BigNumber(5); + const takerTokenFillAmount = new BigNumber(5); + let signedOrder: SignedOrder; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + blockPollingIntervalMs: 0, + }; + before(async () => { + await blockchainLifecycle.startAsync(); + contractWrappers = new ContractWrappers(provider, config); + exchangeContractAddress = contractWrappers.exchange.getContractAddress(); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + const zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + exchangeContractAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), + ); + [coinbase, makerAddress, takerAddress, senderAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('encode and executeTransaction', () => { + const executeTransactionOrThrowAsync = async ( + encoder: TransactionEncoder, + data: string, + signerAddress: string = takerAddress, + ): Promise<void> => { + const salt = generatePseudoRandomSalt(); + const encodedTransaction = encoder.getTransactionHex(data, salt, signerAddress); + const signature = await signatureUtils.ecSignOrderHashAsync( + provider, + encodedTransaction, + signerAddress, + SignerType.Default, + ); + txHash = await contractWrappers.exchange.executeTransactionAsync( + salt, + signerAddress, + data, + signature, + senderAddress, + ); + await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + }; + describe('#fillOrderTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.fillOrderTx(signedOrder, takerTokenFillAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#fillOrderNoThrowTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.fillOrderNoThrowTx(signedOrder, takerTokenFillAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#fillOrKillOrderTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.fillOrKillOrderTx(signedOrder, takerTokenFillAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#marketSellOrdersTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.marketSellOrdersTx([signedOrder], takerTokenFillAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#marketSellOrdersNoThrowTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.marketSellOrdersNoThrowTx([signedOrder], takerTokenFillAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#marketBuyOrdersTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.marketBuyOrdersTx([signedOrder], fillableAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#marketBuyOrdersNoThrowTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.marketBuyOrdersNoThrowTx([signedOrder], fillableAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#preSignTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + const signature = signedOrder.signature; + const data = encoder.preSignTx(orderHash, makerAddress, signature); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#setSignatureValidatorApprovalTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const isApproved = true; + const data = encoder.setSignatureValidatorApprovalTx(senderAddress, isApproved); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#batchFillOrdersTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.batchFillOrdersTx([signedOrder], [takerTokenFillAmount]); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#batchFillOrKillOrdersTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.batchFillOrKillOrdersTx([signedOrder], [takerTokenFillAmount]); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#batchFillOrdersNoThrowTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.batchFillOrdersNoThrowTx([signedOrder], [takerTokenFillAmount]); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#batchCancelOrdersTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.batchCancelOrdersTx([signedOrder]); + const signerAddress = makerAddress; + await executeTransactionOrThrowAsync(encoder, data, signerAddress); + }); + }); + describe('#cancelOrderTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.cancelOrderTx(signedOrder); + const signerAddress = makerAddress; + await executeTransactionOrThrowAsync(encoder, data, signerAddress); + }); + }); + describe('#cancelOrdersUpToTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const targetEpoch = signedOrder.salt; + const data = encoder.cancelOrdersUpToTx(targetEpoch); + const signerAddress = makerAddress; + await executeTransactionOrThrowAsync(encoder, data, signerAddress); + }); + }); + }); +}); diff --git a/packages/contract-wrappers/test/utils/constants.ts b/packages/contract-wrappers/test/utils/constants.ts index 60c3370a2..f38728b77 100644 --- a/packages/contract-wrappers/test/utils/constants.ts +++ b/packages/contract-wrappers/test/utils/constants.ts @@ -15,4 +15,5 @@ export const constants = { DUMMY_TOKEN_TOTAL_SUPPLY: new BigNumber(10 ** 27), // tslint:disable-line:custom-no-magic-numbers NUM_DUMMY_ERC20_TO_DEPLOY: 3, NUM_DUMMY_ERC721_TO_DEPLOY: 1, + ZERO_AMOUNT: new BigNumber(0), }; diff --git a/packages/contract-wrappers/tsconfig.json b/packages/contract-wrappers/tsconfig.json index e35816553..2ee711adc 100644 --- a/packages/contract-wrappers/tsconfig.json +++ b/packages/contract-wrappers/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig", "compilerOptions": { - "outDir": "lib" + "outDir": "lib", + "rootDir": "." }, "include": ["./src/**/*", "./test/**/*"] } diff --git a/packages/contract-wrappers/typedoc-tsconfig.json b/packages/contract-wrappers/typedoc-tsconfig.json new file mode 100644 index 000000000..c9b0af1ae --- /dev/null +++ b/packages/contract-wrappers/typedoc-tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../typedoc-tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "./test/**/*"] +} |