Compare commits
	
		
			130 Commits
		
	
	
		
			@0xproject
			...
			@0xproject
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 00a4fa5f7c | ||
|  | 4475fefd07 | ||
|  | cd08a9c121 | ||
|  | b0c4eb8333 | ||
|  | 368dbda8f0 | ||
|  | bc4149683e | ||
|  | 6174d9ebb7 | ||
|  | e4fc8a8414 | ||
|  | 907972c466 | ||
|  | 49f5fe635f | ||
|  | 77290c1efa | ||
|  | 4ac43a9fd2 | ||
|  | cc77d1dd49 | ||
|  | 51161784e8 | ||
|  | cb7660fbe7 | ||
|  | 82e51b8787 | ||
|  | fffa96bba7 | ||
|  | e6cb2e0fcd | ||
|  | 38abeaed9c | ||
|  | 90c9e3496a | ||
|  | 9fc8a6e214 | ||
|  | 9df87a199a | ||
|  | 7e9ba50502 | ||
|  | 41559c39b9 | ||
|  | 6a6b424c86 | ||
|  | 3a5c6ed00f | ||
|  | db54588d05 | ||
|  | 52fde551e4 | ||
|  | 40cf805e5e | ||
|  | 09d6496135 | ||
|  | c4dadf4bfd | ||
|  | 35ba3e6f7c | ||
|  | 3ac182ee91 | ||
|  | 00e7c70b4d | ||
|  | 0aa9ed3839 | ||
|  | d652deea23 | ||
|  | 878db3b849 | ||
|  | ec2e726be0 | ||
|  | 287830d6e0 | ||
|  | c7a7ae7e18 | ||
|  | 1c7ba6a315 | ||
|  | 0a6f107243 | ||
|  | a93f95c55e | ||
|  | 6833e243b7 | ||
|  | 81dc893d1d | ||
|  | f8e565bc06 | ||
|  | ba15fb6a06 | ||
|  | 1e6b83719a | ||
|  | 9fcb2dda73 | ||
|  | 9a5ec8d030 | ||
|  | ac872e5181 | ||
|  | 70863cca08 | ||
|  | 5a1dce15be | ||
|  | d291256158 | ||
|  | 8c706ac639 | ||
|  | f697814849 | ||
|  | ca5c9e77c0 | ||
|  | a32b201afe | ||
|  | 0ecdf1e213 | ||
|  | 057891b342 | ||
|  | 407f63ef20 | ||
|  | f938c989e3 | ||
|  | c8500cab10 | ||
|  | c28c3db63f | ||
|  | a09ee90739 | ||
|  | 7d5a23969d | ||
|  | 56c3c29feb | ||
|  | c75212bef0 | ||
|  | 6d0dedc62c | ||
|  | cf12daea2f | ||
|  | 6f88e9bdbd | ||
|  | d8cb56caa3 | ||
|  | 044415e23d | ||
|  | 6b866d6053 | ||
|  | 74ce893f52 | ||
|  | cc1fac9bbe | ||
|  | 94e01be9ed | ||
|  | e215992859 | ||
|  | e6f5cac878 | ||
|  | 29971f36cf | ||
|  | 3e4493b389 | ||
|  | 749c6ecc30 | ||
|  | e6e7bae445 | ||
|  | a1d8943552 | ||
|  | 07e56b3cc7 | ||
|  | b16f5f55fb | ||
|  | d92fd43791 | ||
|  | e706fa76ac | ||
|  | 11328bd93d | ||
|  | bc686fcbf3 | ||
|  | 80291caf7c | ||
|  | cd5e9a5115 | ||
|  | 82b51db17e | ||
|  | 3557cd93fc | ||
|  | 0629a7d143 | ||
|  | a27112cbef | ||
|  | d039a1adda | ||
|  | bb4d449e92 | ||
|  | 241534a63d | ||
|  | 1932aff35c | ||
|  | 4f27991959 | ||
|  | 8ce4f9c784 | ||
|  | 7351bf0b14 | ||
|  | f6080367fe | ||
|  | 7f78d7da9d | ||
|  | 6734f2f1bc | ||
|  | 0fb7617a78 | ||
|  | 4219af1430 | ||
|  | c109d1f545 | ||
|  | 50fab9feb3 | ||
|  | 3dad6ee55e | ||
|  | 5d70df771b | ||
|  | ab5df342e1 | ||
|  | 6a9669a409 | ||
|  | e68942ee78 | ||
|  | 4159e45aff | ||
|  | 92497d7df4 | ||
|  | 070eff6f3a | ||
|  | 681ed822ec | ||
|  | 0a1ae2c311 | ||
|  | c5f8b9c2d2 | ||
|  | 7f36574a57 | ||
|  | b637ca105a | ||
|  | 9ffddb47b8 | ||
|  | 7bcaac4e10 | ||
|  | d4592c0a60 | ||
|  | 1d6699585e | ||
|  | a75c298de0 | ||
|  | d603d8da47 | ||
|  | a551d0a6dd | 
| @@ -18,11 +18,11 @@ jobs: | ||||
|             - yarn-packages-master | ||||
|             - yarn-packages- | ||||
|       - run: | ||||
|           name: yarn | ||||
|           command: yarn --frozen-lockfile install || true | ||||
|           name: install-yarn | ||||
|           command: sudo npm install --global yarn@1.9.4 | ||||
|       - run: | ||||
|           name: yarn | ||||
|           command: yarn --frozen-lockfile install | ||||
|           command: yarn --frozen-lockfile install || yarn --frozen-lockfile install | ||||
|       - save_cache: | ||||
|           name: Save Yarn Package Cache | ||||
|           key: yarn-packages-{{ .Branch }}-{{ checksum "yarn.lock" }} | ||||
| @@ -254,4 +254,4 @@ workflows: | ||||
|             - build | ||||
|       - submit-coverage: | ||||
|           requires: | ||||
|             - test-rest | ||||
|             - test-rest | ||||
|   | ||||
| @@ -15,7 +15,7 @@ lib | ||||
| /packages/contract-wrappers/src/artifacts | ||||
| /packages/order-watcher/src/artifacts | ||||
| /packages/metacoin/artifacts | ||||
| /packages/sra-api/public/ | ||||
| /packages/sra-spec/public/ | ||||
| /packages/contract-wrappers/test/artifacts | ||||
| /packages/order-watcher/test/artifacts | ||||
| /packages/migrations/artifacts/1.0.0 | ||||
|   | ||||
| @@ -33,7 +33,7 @@ If you're developing on 0x now or are interested in using 0x infrastructure in t | ||||
| | [`@0xproject/monorepo-scripts`](/packages/monorepo-scripts)     | [](https://www.npmjs.com/package/@0xproject/monorepo-scripts)     | Monorepo scripts                                                                                                          | | ||||
| | [`@0xproject/react-docs`](/packages/react-docs)                 | [](https://www.npmjs.com/package/@0xproject/react-docs)                 | React documentation component for rendering TypeDoc & Doxity generated JSON                                               | | ||||
| | [`@0xproject/react-shared`](/packages/react-shared)             | [](https://www.npmjs.com/package/@0xproject/react-shared)             | 0x shared react components                                                                                                | | ||||
| | [`@0xproject/sra-api`](/packages/sra-api)                       | [](https://www.npmjs.com/package/@0xproject/sra-api)                       | OpenAPI specification for the standard relayer API                                                                        | | ||||
| | [`@0xproject/sra-spec`](/packages/sra-spec)                     | [](https://www.npmjs.com/package/@0xproject/sra-spec)                     | OpenAPI specification for the standard relayer API                                                                        | | ||||
| | [`@0xproject/sra-report`](/packages/sra-report)                 | [](https://www.npmjs.com/package/@0xproject/sra-report)                 | Generate reports for standard relayer API compliance                                                                      | | ||||
| | [`@0xproject/sol-cov`](/packages/sol-cov)                       | [](https://www.npmjs.com/package/@0xproject/sol-cov)                       | Solidity test coverage tool                                                                                               | | ||||
| | [`@0xproject/subproviders`](/packages/subproviders)             | [](https://www.npmjs.com/package/@0xproject/subproviders)             | Useful web3 subproviders (e.g LedgerSubprovider)                                                                          | | ||||
|   | ||||
| @@ -1,4 +1,22 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "1.0.1-rc.6", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Fix missing `BlockParamLiteral` type import issue" | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1535377027 | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.1-rc.5", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": | ||||
|                     "Fix `main` and `types` package.json entries so that they point to the new location of index.d.ts and index.js" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.1-rc.4", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.1-rc.6 - _August 27, 2018_ | ||||
|  | ||||
|     * Fix missing `BlockParamLiteral` type import issue | ||||
|  | ||||
| ## v1.0.1-rc.5 - _Invalid date_ | ||||
|  | ||||
|     * Fix `main` and `types` package.json entries so that they point to the new location of index.d.ts and index.js | ||||
|  | ||||
| ## v1.0.1-rc.4 - _August 24, 2018_ | ||||
|  | ||||
|     * Re-organize the exported interface of 0x.js. Remove the `ZeroEx` class, and instead export the same exports as `0x.js`'s sub-packages: `@0xproject/contract-wrappers`, `@0xproject/order-utils` and `@0xproject/order-watcher` (#963) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "0x.js", | ||||
|     "version": "1.0.1-rc.4", | ||||
|     "version": "1.0.1-rc.6", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -12,8 +12,8 @@ | ||||
|         "tokens", | ||||
|         "exchange" | ||||
|     ], | ||||
|     "main": "lib/src/index.js", | ||||
|     "types": "lib/src/index.d.ts", | ||||
|     "main": "lib/index.js", | ||||
|     "types": "lib/index.d.ts", | ||||
|     "scripts": { | ||||
|         "watch_without_deps": "tsc -w", | ||||
|         "build": "yarn build:all", | ||||
| @@ -42,10 +42,10 @@ | ||||
|     }, | ||||
|     "license": "Apache-2.0", | ||||
|     "devDependencies": { | ||||
|         "@0xproject/abi-gen": "^1.0.6", | ||||
|         "@0xproject/dev-utils": "^1.0.5", | ||||
|         "@0xproject/migrations": "^1.0.5", | ||||
|         "@0xproject/monorepo-scripts": "^1.0.6", | ||||
|         "@0xproject/abi-gen": "^1.0.7", | ||||
|         "@0xproject/dev-utils": "^1.0.6", | ||||
|         "@0xproject/migrations": "^1.0.6", | ||||
|         "@0xproject/monorepo-scripts": "^1.0.7", | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "@types/mocha": "^2.2.42", | ||||
| @@ -73,16 +73,16 @@ | ||||
|         "webpack": "^3.1.0" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/assert": "^1.0.6", | ||||
|         "@0xproject/base-contract": "^2.0.0", | ||||
|         "@0xproject/contract-wrappers": "^1.0.1-rc.4", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.4", | ||||
|         "@0xproject/order-watcher": "^1.0.1-rc.4", | ||||
|         "@0xproject/subproviders": "^2.0.0", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/assert": "^1.0.7", | ||||
|         "@0xproject/base-contract": "^2.0.1", | ||||
|         "@0xproject/contract-wrappers": "^1.0.1-rc.5", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.6", | ||||
|         "@0xproject/order-watcher": "^1.0.1-rc.5", | ||||
|         "@0xproject/subproviders": "^2.0.1", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/web3-wrapper": "^2.0.0", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@0xproject/web3-wrapper": "^2.0.1", | ||||
|         "ethereum-types": "^1.0.5", | ||||
|         "ethers": "3.0.22", | ||||
|         "lodash": "^4.17.5", | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1535377027, | ||||
|         "version": "1.0.7", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1535133899, | ||||
|         "version": "1.0.6", | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.7 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.6 - _August 24, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/abi-gen", | ||||
|     "version": "1.0.6", | ||||
|     "version": "1.0.7", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -32,7 +32,7 @@ | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/packages/abi-gen/README.md", | ||||
|     "dependencies": { | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "chalk": "^2.3.0", | ||||
|         "ethereum-types": "^1.0.5", | ||||
|         "glob": "^7.1.2", | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1535377027, | ||||
|         "version": "1.0.7", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1535133899, | ||||
|         "version": "1.0.6", | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.7 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.6 - _August 24, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/assert", | ||||
|     "version": "1.0.6", | ||||
|     "version": "1.0.7", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -45,9 +45,9 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.5", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "lodash": "^4.17.5", | ||||
|         "valid-url": "^1.0.9" | ||||
|     }, | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1535377027, | ||||
|         "version": "2.0.1", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1535133899, | ||||
|         "version": "2.0.0", | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v2.0.1 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v2.0.0 - _August 24, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/base-contract", | ||||
|     "version": "2.0.0", | ||||
|     "version": "2.0.1", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -42,8 +42,8 @@ | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/web3-wrapper": "^2.0.0", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@0xproject/web3-wrapper": "^2.0.1", | ||||
|         "ethereum-types": "^1.0.5", | ||||
|         "ethers": "3.0.22", | ||||
|         "lodash": "^4.17.5" | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "2.0.0-rc.2", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1535377027 | ||||
|     }, | ||||
|     { | ||||
|         "version": "2.0.0-rc.1", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v2.0.0-rc.2 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v2.0.0-rc.1 - _August 24, 2018_ | ||||
|  | ||||
|     * Updated for SRA v2 (#974) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/connect", | ||||
|     "version": "2.0.0-rc.1", | ||||
|     "version": "2.0.0-rc.2", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -44,11 +44,11 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/packages/connect/README.md", | ||||
|     "dependencies": { | ||||
|         "@0xproject/assert": "^1.0.6", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.5", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/assert": "^1.0.7", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.6", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "lodash": "^4.17.5", | ||||
|         "query-string": "^5.0.1", | ||||
|         "sinon": "^4.0.0", | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "1.0.1-rc.5", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Fix missing `BlockParamLiteral` type import issue" | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1535377027 | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.1-rc.4", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## 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) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/contract-wrappers", | ||||
|     "version": "1.0.1-rc.4", | ||||
|     "version": "1.0.1-rc.5", | ||||
|     "description": "Smart TS wrappers for 0x smart contracts", | ||||
|     "keywords": [ | ||||
|         "0xproject", | ||||
| @@ -44,10 +44,10 @@ | ||||
|         "node": ">=6.0.0" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@0xproject/abi-gen": "^1.0.6", | ||||
|         "@0xproject/dev-utils": "^1.0.5", | ||||
|         "@0xproject/migrations": "^1.0.5", | ||||
|         "@0xproject/subproviders": "^2.0.0", | ||||
|         "@0xproject/abi-gen": "^1.0.7", | ||||
|         "@0xproject/dev-utils": "^1.0.6", | ||||
|         "@0xproject/migrations": "^1.0.6", | ||||
|         "@0xproject/subproviders": "^2.0.1", | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "@types/mocha": "^2.2.42", | ||||
| @@ -74,15 +74,15 @@ | ||||
|         "web3-provider-engine": "14.0.6" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/assert": "^1.0.6", | ||||
|         "@0xproject/base-contract": "^2.0.0", | ||||
|         "@0xproject/fill-scenarios": "^1.0.1-rc.4", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.5", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.4", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/assert": "^1.0.7", | ||||
|         "@0xproject/base-contract": "^2.0.1", | ||||
|         "@0xproject/fill-scenarios": "^1.0.1-rc.5", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.6", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.6", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/web3-wrapper": "^2.0.0", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@0xproject/web3-wrapper": "^2.0.1", | ||||
|         "ethereum-types": "^1.0.5", | ||||
|         "ethereumjs-blockstream": "5.0.0", | ||||
|         "ethereumjs-util": "^5.1.1", | ||||
|   | ||||
| @@ -1,7 +1,14 @@ | ||||
| import { AbiDecoder, intervalUtils, logUtils } from '@0xproject/utils'; | ||||
| import { Web3Wrapper } from '@0xproject/web3-wrapper'; | ||||
| import { ContractArtifact } from 'ethereum-types'; | ||||
| import { BlockParamLiteral, ContractAbi, FilterObject, LogEntry, LogWithDecodedArgs, RawLog } from 'ethereum-types'; | ||||
| import { | ||||
|     BlockParamLiteral, | ||||
|     ContractAbi, | ||||
|     ContractArtifact, | ||||
|     FilterObject, | ||||
|     LogEntry, | ||||
|     LogWithDecodedArgs, | ||||
|     RawLog, | ||||
| } from 'ethereum-types'; | ||||
| import { Block, BlockAndLogStreamer, Log } from 'ethereumjs-blockstream'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| // tslint:disable:no-unused-variable | ||||
| // tslint:disable:no-unbound-method | ||||
| import { BaseContract } from '@0xproject/base-contract'; | ||||
| import { BlockParam, CallData, ContractAbi, ContractArtifact, DecodedLogArgs, MethodAbi, Provider, TxData, TxDataPayable } from 'ethereum-types'; | ||||
| import { BlockParam, BlockParamLiteral, CallData, ContractAbi, ContractArtifact, DecodedLogArgs, MethodAbi, Provider, TxData, TxDataPayable } from 'ethereum-types'; | ||||
| import { BigNumber, classUtils, logUtils } from '@0xproject/utils'; | ||||
| import { Web3Wrapper } from '@0xproject/web3-wrapper'; | ||||
| import * as ethers from 'ethers'; | ||||
|   | ||||
| @@ -40,6 +40,7 @@ | ||||
|         "MultiSigWallet", | ||||
|         "MultiSigWalletWithTimeLock", | ||||
|         "OrderValidator", | ||||
|         "ReentrantERC20Token", | ||||
|         "TestAssetProxyOwner", | ||||
|         "TestAssetProxyDispatcher", | ||||
|         "TestConstants", | ||||
| @@ -47,6 +48,7 @@ | ||||
|         "TestLibs", | ||||
|         "TestExchangeInternals", | ||||
|         "TestSignatureValidator", | ||||
|         "TestStaticCallReceiver", | ||||
|         "TokenRegistry", | ||||
|         "Validator", | ||||
|         "Wallet", | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|     "private": true, | ||||
|     "name": "contracts", | ||||
|     "version": "2.1.41", | ||||
|     "version": "2.1.42", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -33,7 +33,7 @@ | ||||
|         "lint-contracts": "solhint src/2.0.0/**/**/**/**/*.sol" | ||||
|     }, | ||||
|     "config": { | ||||
|         "abis": "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|DummyNoReturnERC20Token|ERC20Proxy|ERC721Proxy|Forwarder|Exchange|ExchangeWrapper|IAssetData|IAssetProxy|InvalidERC721Receiver|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|OrderValidator|TestAssetProxyOwner|TestAssetProxyDispatcher|TestConstants|TestExchangeInternals|TestLibBytes|TestLibs|TestSignatureValidator|Validator|Wallet|TokenRegistry|Whitelist|WETH9|ZRXToken).json" | ||||
|         "abis": "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|DummyNoReturnERC20Token|ERC20Proxy|ERC721Proxy|Forwarder|Exchange|ExchangeWrapper|IAssetData|IAssetProxy|InvalidERC721Receiver|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|OrderValidator|ReentrantERC20Token|TestAssetProxyOwner|TestAssetProxyDispatcher|TestConstants|TestExchangeInternals|TestLibBytes|TestLibs|TestSignatureValidator|TestStaticCallReceiver|Validator|Wallet|TokenRegistry|Whitelist|WETH9|ZRXToken).json" | ||||
|     }, | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
| @@ -46,11 +46,11 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/packages/contracts/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0xproject/abi-gen": "^1.0.6", | ||||
|         "@0xproject/dev-utils": "^1.0.5", | ||||
|         "@0xproject/sol-compiler": "^1.1.0", | ||||
|         "@0xproject/sol-cov": "^2.1.0", | ||||
|         "@0xproject/subproviders": "^2.0.0", | ||||
|         "@0xproject/abi-gen": "^1.0.7", | ||||
|         "@0xproject/dev-utils": "^1.0.6", | ||||
|         "@0xproject/sol-compiler": "^1.1.1", | ||||
|         "@0xproject/sol-cov": "^2.1.1", | ||||
|         "@0xproject/subproviders": "^2.0.1", | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "@types/bn.js": "^4.11.0", | ||||
|         "@types/ethereumjs-abi": "^0.6.0", | ||||
| @@ -73,12 +73,12 @@ | ||||
|         "yargs": "^10.0.3" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/base-contract": "^2.0.0", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.4", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/base-contract": "^2.0.1", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.6", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/web3-wrapper": "^2.0.0", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@0xproject/web3-wrapper": "^2.0.1", | ||||
|         "@types/js-combinatorics": "^0.5.29", | ||||
|         "bn.js": "^4.11.8", | ||||
|         "ethereum-types": "^1.0.5", | ||||
|   | ||||
| @@ -37,7 +37,7 @@ contract Whitelist is | ||||
|     bytes internal TX_ORIGIN_SIGNATURE; | ||||
|     // solhint-enable var-name-mixedcase | ||||
|  | ||||
|     byte constant internal VALIDATOR_SIGNATURE_BYTE = "\x06"; | ||||
|     byte constant internal VALIDATOR_SIGNATURE_BYTE = "\x05"; | ||||
|  | ||||
|     constructor (address _exchange) | ||||
|         public | ||||
|   | ||||
| @@ -163,7 +163,7 @@ contract MixinExchangeWrapper is | ||||
|  | ||||
|             // Convert the remaining amount of makerAsset to buy into remaining amount | ||||
|             // of takerAsset to sell, assuming entire amount can be sold in the current order | ||||
|             uint256 remainingTakerAssetFillAmount = getPartialAmount( | ||||
|             uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( | ||||
|                 orders[i].takerAssetAmount, | ||||
|                 orders[i].makerAssetAmount, | ||||
|                 remainingMakerAssetFillAmount | ||||
| @@ -231,7 +231,7 @@ contract MixinExchangeWrapper is | ||||
|  | ||||
|             // Convert the remaining amount of ZRX to buy into remaining amount | ||||
|             // of WETH to sell, assuming entire amount can be sold in the current order. | ||||
|             uint256 remainingWethSellAmount = getPartialAmount( | ||||
|             uint256 remainingWethSellAmount = getPartialAmountFloor( | ||||
|                 orders[i].takerAssetAmount, | ||||
|                 safeSub(orders[i].makerAssetAmount, orders[i].takerFee),  // our exchange rate after fees  | ||||
|                 remainingZrxBuyAmount | ||||
|   | ||||
| @@ -87,7 +87,7 @@ contract MixinForwarderCore is | ||||
|         uint256 makerAssetAmountPurchased; | ||||
|         if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) { | ||||
|             // Calculate amount of WETH that won't be spent on ETH fees. | ||||
|             wethSellAmount = getPartialAmount( | ||||
|             wethSellAmount = getPartialAmountFloor( | ||||
|                 PERCENTAGE_DENOMINATOR, | ||||
|                 safeAdd(PERCENTAGE_DENOMINATOR, feePercentage), | ||||
|                 msg.value | ||||
| @@ -103,7 +103,7 @@ contract MixinForwarderCore is | ||||
|             makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid); | ||||
|         } else { | ||||
|             // 5% of WETH is reserved for filling feeOrders and paying feeRecipient. | ||||
|             wethSellAmount = getPartialAmount( | ||||
|             wethSellAmount = getPartialAmountFloor( | ||||
|                 MAX_WETH_FILL_PERCENTAGE, | ||||
|                 PERCENTAGE_DENOMINATOR, | ||||
|                 msg.value | ||||
|   | ||||
| @@ -82,7 +82,7 @@ contract MixinWeth is | ||||
|         uint256 wethRemaining = safeSub(msg.value, wethSold); | ||||
|  | ||||
|         // Calculate ETH fee to pay to feeRecipient. | ||||
|         uint256 ethFee = getPartialAmount( | ||||
|         uint256 ethFee = getPartialAmountFloor( | ||||
|             feePercentage, | ||||
|             PERCENTAGE_DENOMINATOR, | ||||
|             wethSoldExcludingFeeOrders | ||||
|   | ||||
| @@ -118,6 +118,9 @@ contract ERC20Proxy is | ||||
|                 mstore(96, 0) | ||||
|                 revert(0, 100) | ||||
|             } | ||||
|  | ||||
|             // Revert if undefined function is called | ||||
|             revert(0, 0) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -152,6 +152,9 @@ contract ERC721Proxy is | ||||
|                 mstore(96, 0) | ||||
|                 revert(0, 100) | ||||
|             } | ||||
|  | ||||
|             // Revert if undefined function is called | ||||
|             revert(0, 0) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -83,7 +83,7 @@ contract MixinAssetProxyDispatcher is | ||||
|         internal | ||||
|     { | ||||
|         // Do nothing if no amount should be transferred. | ||||
|         if (amount > 0) { | ||||
|         if (amount > 0 && from != to) { | ||||
|             // Ensure assetData length is valid | ||||
|             require( | ||||
|                 assetData.length > 3, | ||||
|   | ||||
| @@ -19,6 +19,7 @@ | ||||
| pragma solidity 0.4.24; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../../utils/ReentrancyGuard/ReentrancyGuard.sol"; | ||||
| import "./libs/LibConstants.sol"; | ||||
| import "./libs/LibFillResults.sol"; | ||||
| import "./libs/LibOrder.sol"; | ||||
| @@ -30,6 +31,7 @@ import "./mixins/MAssetProxyDispatcher.sol"; | ||||
|  | ||||
|  | ||||
| contract MixinExchangeCore is | ||||
|     ReentrancyGuard, | ||||
|     LibConstants, | ||||
|     LibMath, | ||||
|     LibOrder, | ||||
| @@ -54,6 +56,7 @@ contract MixinExchangeCore is | ||||
|     /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled. | ||||
|     function cancelOrdersUpTo(uint256 targetOrderEpoch) | ||||
|         external | ||||
|         nonReentrant | ||||
|     { | ||||
|         address makerAddress = getCurrentContextAddress(); | ||||
|         // If this function is called via `executeTransaction`, we only update the orderEpoch for the makerAddress/msg.sender combination. | ||||
| @@ -86,43 +89,14 @@ contract MixinExchangeCore is | ||||
|         bytes memory signature | ||||
|     ) | ||||
|         public | ||||
|         nonReentrant | ||||
|         returns (FillResults memory fillResults) | ||||
|     { | ||||
|         // Fetch order info | ||||
|         OrderInfo memory orderInfo = getOrderInfo(order); | ||||
|  | ||||
|         // Fetch taker address | ||||
|         address takerAddress = getCurrentContextAddress(); | ||||
|  | ||||
|         // Get amount of takerAsset to fill | ||||
|         uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderInfo.orderTakerAssetFilledAmount); | ||||
|         uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); | ||||
|  | ||||
|         // Validate context | ||||
|         assertValidFill( | ||||
|         fillResults = fillOrderInternal( | ||||
|             order, | ||||
|             orderInfo, | ||||
|             takerAddress, | ||||
|             takerAssetFillAmount, | ||||
|             takerAssetFilledAmount, | ||||
|             signature | ||||
|         ); | ||||
|  | ||||
|         // Compute proportional fill amounts | ||||
|         fillResults = calculateFillResults(order, takerAssetFilledAmount); | ||||
|  | ||||
|         // Update exchange internal state | ||||
|         updateFilledState( | ||||
|             order, | ||||
|             takerAddress, | ||||
|             orderInfo.orderHash, | ||||
|             orderInfo.orderTakerAssetFilledAmount, | ||||
|             fillResults | ||||
|         ); | ||||
|      | ||||
|         // Settle order | ||||
|         settleOrder(order, takerAddress, fillResults); | ||||
|  | ||||
|         return fillResults; | ||||
|     } | ||||
|  | ||||
| @@ -131,6 +105,7 @@ contract MixinExchangeCore is | ||||
|     /// @param order Order to cancel. Order must be OrderStatus.FILLABLE. | ||||
|     function cancelOrder(Order memory order) | ||||
|         public | ||||
|         nonReentrant | ||||
|     { | ||||
|         // Fetch current order status | ||||
|         OrderInfo memory orderInfo = getOrderInfo(order); | ||||
| @@ -203,6 +178,64 @@ contract MixinExchangeCore is | ||||
|         return orderInfo; | ||||
|     } | ||||
|  | ||||
|     /// @dev Fills the input order. | ||||
|     /// @param order Order struct containing order specifications. | ||||
|     /// @param takerAssetFillAmount Desired amount of takerAsset to sell. | ||||
|     /// @param signature Proof that order has been created by maker. | ||||
|     /// @return Amounts filled and fees paid by maker and taker. | ||||
|     function fillOrderInternal( | ||||
|         Order memory order, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         bytes memory signature | ||||
|     ) | ||||
|         internal | ||||
|         returns (FillResults memory fillResults) | ||||
|     { | ||||
|         // Fetch order info | ||||
|         OrderInfo memory orderInfo = getOrderInfo(order); | ||||
|  | ||||
|         // Fetch taker address | ||||
|         address takerAddress = getCurrentContextAddress(); | ||||
|          | ||||
|         // Assert that the order is fillable by taker | ||||
|         assertFillableOrder( | ||||
|             order, | ||||
|             orderInfo, | ||||
|             takerAddress, | ||||
|             signature | ||||
|         ); | ||||
|          | ||||
|         // Get amount of takerAsset to fill | ||||
|         uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderInfo.orderTakerAssetFilledAmount); | ||||
|         uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); | ||||
|  | ||||
|         // Validate context | ||||
|         assertValidFill( | ||||
|             order, | ||||
|             orderInfo, | ||||
|             takerAssetFillAmount, | ||||
|             takerAssetFilledAmount, | ||||
|             fillResults.makerAssetFilledAmount | ||||
|         ); | ||||
|  | ||||
|         // Compute proportional fill amounts | ||||
|         fillResults = calculateFillResults(order, takerAssetFilledAmount); | ||||
|  | ||||
|         // Update exchange internal state | ||||
|         updateFilledState( | ||||
|             order, | ||||
|             takerAddress, | ||||
|             orderInfo.orderHash, | ||||
|             orderInfo.orderTakerAssetFilledAmount, | ||||
|             fillResults | ||||
|         ); | ||||
|      | ||||
|         // Settle order | ||||
|         settleOrder(order, takerAddress, fillResults); | ||||
|  | ||||
|         return fillResults; | ||||
|     } | ||||
|  | ||||
|     /// @dev Updates state with results of a fill order. | ||||
|     /// @param order that was filled. | ||||
|     /// @param takerAddress Address of taker who filled the order. | ||||
| @@ -259,20 +292,16 @@ contract MixinExchangeCore is | ||||
|             order.takerAssetData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|      | ||||
|     /// @dev Validates context for fillOrder. Succeeds or throws. | ||||
|     /// @param order to be filled. | ||||
|     /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. | ||||
|     /// @param takerAddress Address of order taker. | ||||
|     /// @param takerAssetFillAmount Desired amount of order to fill by taker. | ||||
|     /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. | ||||
|     /// @param signature Proof that the orders was created by its maker. | ||||
|     function assertValidFill( | ||||
|     function assertFillableOrder( | ||||
|         Order memory order, | ||||
|         OrderInfo memory orderInfo, | ||||
|         address takerAddress, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         uint256 takerAssetFilledAmount, | ||||
|         bytes memory signature | ||||
|     ) | ||||
|         internal | ||||
| @@ -283,13 +312,7 @@ contract MixinExchangeCore is | ||||
|             orderInfo.orderStatus == uint8(OrderStatus.FILLABLE), | ||||
|             "ORDER_UNFILLABLE" | ||||
|         ); | ||||
|  | ||||
|         // Revert if fill amount is invalid | ||||
|         require( | ||||
|             takerAssetFillAmount != 0, | ||||
|             "INVALID_TAKER_AMOUNT" | ||||
|         ); | ||||
|  | ||||
|          | ||||
|         // Validate sender is allowed to fill this order | ||||
|         if (order.senderAddress != address(0)) { | ||||
|             require( | ||||
| @@ -297,7 +320,7 @@ contract MixinExchangeCore is | ||||
|                 "INVALID_SENDER" | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|          | ||||
|         // Validate taker is allowed to fill this order | ||||
|         if (order.takerAddress != address(0)) { | ||||
|             require( | ||||
| @@ -305,7 +328,7 @@ contract MixinExchangeCore is | ||||
|                 "INVALID_TAKER" | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|          | ||||
|         // Validate Maker signature (check only if first time seen) | ||||
|         if (orderInfo.orderTakerAssetFilledAmount == 0) { | ||||
|             require( | ||||
| @@ -317,10 +340,74 @@ contract MixinExchangeCore is | ||||
|                 "INVALID_ORDER_SIGNATURE" | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|      | ||||
|     /// @dev Validates context for fillOrder. Succeeds or throws. | ||||
|     /// @param order to be filled. | ||||
|     /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. | ||||
|     /// @param takerAssetFillAmount Desired amount of order to fill by taker. | ||||
|     /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. | ||||
|     /// @param makerAssetFilledAmount Amount of makerAsset that will be transfered. | ||||
|     function assertValidFill( | ||||
|         Order memory order, | ||||
|         OrderInfo memory orderInfo, | ||||
|         uint256 takerAssetFillAmount,  // TODO: use FillResults | ||||
|         uint256 takerAssetFilledAmount, | ||||
|         uint256 makerAssetFilledAmount | ||||
|     ) | ||||
|         internal | ||||
|         view | ||||
|     { | ||||
|         // Revert if fill amount is invalid | ||||
|         // TODO: reconsider necessity for v2.1 | ||||
|         require( | ||||
|             takerAssetFillAmount != 0, | ||||
|             "INVALID_TAKER_AMOUNT" | ||||
|         ); | ||||
|          | ||||
|         // Make sure taker does not pay more than desired amount | ||||
|         // NOTE: This assertion should never fail, it is here | ||||
|         //       as an extra defence against potential bugs. | ||||
|         require( | ||||
|             takerAssetFilledAmount <= takerAssetFillAmount, | ||||
|             "TAKER_OVERPAY" | ||||
|         ); | ||||
|          | ||||
|         // Make sure order is not overfilled | ||||
|         // NOTE: This assertion should never fail, it is here | ||||
|         //       as an extra defence against potential bugs. | ||||
|         require( | ||||
|             safeAdd(orderInfo.orderTakerAssetFilledAmount, takerAssetFilledAmount) <= order.takerAssetAmount, | ||||
|             "ORDER_OVERFILL" | ||||
|         ); | ||||
|          | ||||
|         // Make sure order is filled at acceptable price. | ||||
|         // The order has an implied price from the makers perspective: | ||||
|         //    order price = order.makerAssetAmount / order.takerAssetAmount | ||||
|         // i.e. the number of makerAsset maker is paying per takerAsset. The | ||||
|         // maker is guaranteed to get this price or a better (lower) one. The | ||||
|         // actual price maker is getting in this fill is: | ||||
|         //    fill price = makerAssetFilledAmount / takerAssetFilledAmount | ||||
|         // We need `fill price <= order price` for the fill to be fair to maker. | ||||
|         // This amounts to: | ||||
|         //     makerAssetFilledAmount        order.makerAssetAmount | ||||
|         //    ------------------------  <=  ----------------------- | ||||
|         //     takerAssetFilledAmount        order.takerAssetAmount | ||||
|         // or, equivalently: | ||||
|         //     makerAssetFilledAmount * order.takerAssetAmount <= | ||||
|         //     order.makerAssetAmount * takerAssetFilledAmount | ||||
|         // NOTE: This assertion should never fail, it is here | ||||
|         //       as an extra defence against potential bugs. | ||||
|         require( | ||||
|             safeMul(makerAssetFilledAmount, order.takerAssetAmount) | ||||
|             <=  | ||||
|             safeMul(order.makerAssetAmount, takerAssetFilledAmount), | ||||
|             "INVALID_FILL_PRICE" | ||||
|         ); | ||||
|          | ||||
|         // Validate fill order rounding | ||||
|         require( | ||||
|             !isRoundingError( | ||||
|             !isRoundingErrorFloor( | ||||
|                 takerAssetFilledAmount, | ||||
|                 order.takerAssetAmount, | ||||
|                 order.makerAssetAmount | ||||
| @@ -376,17 +463,17 @@ contract MixinExchangeCore is | ||||
|     { | ||||
|         // Compute proportional transfer amounts | ||||
|         fillResults.takerAssetFilledAmount = takerAssetFilledAmount; | ||||
|         fillResults.makerAssetFilledAmount = getPartialAmount( | ||||
|         fillResults.makerAssetFilledAmount = getPartialAmountFloor( | ||||
|             takerAssetFilledAmount, | ||||
|             order.takerAssetAmount, | ||||
|             order.makerAssetAmount | ||||
|         ); | ||||
|         fillResults.makerFeePaid = getPartialAmount( | ||||
|         fillResults.makerFeePaid = getPartialAmountFloor( | ||||
|             takerAssetFilledAmount, | ||||
|             order.takerAssetAmount, | ||||
|             order.makerFee | ||||
|         ); | ||||
|         fillResults.takerFeePaid = getPartialAmount( | ||||
|         fillResults.takerFeePaid = getPartialAmountFloor( | ||||
|             takerAssetFilledAmount, | ||||
|             order.takerAssetAmount, | ||||
|             order.takerFee | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
| pragma solidity 0.4.24; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../../utils/ReentrancyGuard/ReentrancyGuard.sol"; | ||||
| import "./libs/LibConstants.sol"; | ||||
| import "./libs/LibMath.sol"; | ||||
| import "./libs/LibOrder.sol"; | ||||
| @@ -25,6 +26,7 @@ import "./mixins/MAssetProxyDispatcher.sol"; | ||||
|  | ||||
|  | ||||
| contract MixinMatchOrders is | ||||
|     ReentrancyGuard, | ||||
|     LibConstants, | ||||
|     LibMath, | ||||
|     MAssetProxyDispatcher, | ||||
| @@ -48,6 +50,7 @@ contract MixinMatchOrders is | ||||
|         bytes memory rightSignature | ||||
|     ) | ||||
|         public | ||||
|         nonReentrant | ||||
|         returns (LibFillResults.MatchedFillResults memory matchedFillResults) | ||||
|     { | ||||
|         // We assume that rightOrder.takerAssetData == leftOrder.makerAssetData and rightOrder.makerAssetData == leftOrder.takerAssetData. | ||||
| @@ -61,8 +64,20 @@ contract MixinMatchOrders is | ||||
|  | ||||
|         // Fetch taker address | ||||
|         address takerAddress = getCurrentContextAddress(); | ||||
|  | ||||
|          | ||||
|         // Either our context is valid or we revert | ||||
|         assertFillableOrder( | ||||
|             leftOrder, | ||||
|             leftOrderInfo, | ||||
|             takerAddress, | ||||
|             leftSignature | ||||
|         ); | ||||
|         assertFillableOrder( | ||||
|             rightOrder, | ||||
|             rightOrderInfo, | ||||
|             takerAddress, | ||||
|             rightSignature | ||||
|         ); | ||||
|         assertValidMatch(leftOrder, rightOrder); | ||||
|  | ||||
|         // Compute proportional fill amounts | ||||
| @@ -77,20 +92,18 @@ contract MixinMatchOrders is | ||||
|         assertValidFill( | ||||
|             leftOrder, | ||||
|             leftOrderInfo, | ||||
|             takerAddress, | ||||
|             matchedFillResults.left.takerAssetFilledAmount, | ||||
|             matchedFillResults.left.takerAssetFilledAmount, | ||||
|             leftSignature | ||||
|             matchedFillResults.left.makerAssetFilledAmount | ||||
|         ); | ||||
|         assertValidFill( | ||||
|             rightOrder, | ||||
|             rightOrderInfo, | ||||
|             takerAddress, | ||||
|             matchedFillResults.right.takerAssetFilledAmount, | ||||
|             matchedFillResults.right.takerAssetFilledAmount, | ||||
|             rightSignature | ||||
|             matchedFillResults.right.makerAssetFilledAmount | ||||
|         ); | ||||
|  | ||||
|          | ||||
|         // Update exchange state | ||||
|         updateFilledState( | ||||
|             leftOrder, | ||||
| @@ -106,7 +119,7 @@ contract MixinMatchOrders is | ||||
|             rightOrderInfo.orderTakerAssetFilledAmount, | ||||
|             matchedFillResults.right | ||||
|         ); | ||||
|      | ||||
|  | ||||
|         // Settle matched orders. Succeeds or throws. | ||||
|         settleMatchedOrders( | ||||
|             leftOrder, | ||||
| @@ -162,62 +175,85 @@ contract MixinMatchOrders is | ||||
|         pure | ||||
|         returns (LibFillResults.MatchedFillResults memory matchedFillResults) | ||||
|     { | ||||
|         // We settle orders at the exchange rate of the right order. | ||||
|         // The amount saved by the left maker goes to the taker. | ||||
|         // Either the left or right order will be fully filled; possibly both. | ||||
|         // The left order is fully filled iff the right order can sell more than left can buy. | ||||
|         // That is: the amount required to fill the left order is less than or equal to | ||||
|         //          the amount we can spend from the right order: | ||||
|         //          <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightMakerToTakerRatio> | ||||
|         //          <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount> | ||||
|         //          <leftTakerAssetAmountRemaining> * <rightOrder.takerAssetAmount> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> | ||||
|         // Derive maker asset amounts for left & right orders, given store taker assert amounts | ||||
|         uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderTakerAssetFilledAmount); | ||||
|         uint256 leftMakerAssetAmountRemaining = getPartialAmountFloor( | ||||
|             leftOrder.makerAssetAmount, | ||||
|             leftOrder.takerAssetAmount, | ||||
|             leftTakerAssetAmountRemaining | ||||
|         ); | ||||
|         uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderTakerAssetFilledAmount); | ||||
|         uint256 leftTakerAssetFilledAmount; | ||||
|         uint256 rightTakerAssetFilledAmount; | ||||
|         if ( | ||||
|             safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <= | ||||
|             safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount) | ||||
|         ) { | ||||
|             // Left order will be fully filled: maximally fill left | ||||
|             leftTakerAssetFilledAmount = leftTakerAssetAmountRemaining; | ||||
|         uint256 rightMakerAssetAmountRemaining = getPartialAmountFloor( | ||||
|             rightOrder.makerAssetAmount, | ||||
|             rightOrder.takerAssetAmount, | ||||
|             rightTakerAssetAmountRemaining | ||||
|         ); | ||||
|  | ||||
|             // The right order receives an amount proportional to how much was spent. | ||||
|             rightTakerAssetFilledAmount = getPartialAmount( | ||||
|                 rightOrder.takerAssetAmount, | ||||
|                 rightOrder.makerAssetAmount, | ||||
|                 leftTakerAssetFilledAmount | ||||
|         // Calculate fill results for maker and taker assets: at least one order will be fully filled. | ||||
|         // The maximum amount the left maker can buy is `leftTakerAssetAmountRemaining` | ||||
|         // The maximum amount the right maker can sell is `rightMakerAssetAmountRemaining` | ||||
|         // We have two distinct cases for calculating the fill results: | ||||
|         // Case 1. | ||||
|         //   If the left maker can buy more than the right maker can sell, then only the right order is fully filled. | ||||
|         //   If the left maker can buy exactly what the right maker can sell, then both orders are fully filled. | ||||
|         // Case 2. | ||||
|         //   If the left maker cannot buy more than the right maker can sell, then only the left order is fully filled. | ||||
|         if (leftTakerAssetAmountRemaining >= rightMakerAssetAmountRemaining) { | ||||
|             // Case 1: Right order is fully filled | ||||
|             matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; | ||||
|             matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; | ||||
|             matchedFillResults.left.takerAssetFilledAmount = matchedFillResults.right.makerAssetFilledAmount; | ||||
|             // Round down to ensure the maker's exchange rate does not exceed the price specified by the order.  | ||||
|             // We favor the maker when the exchange rate must be rounded. | ||||
|             matchedFillResults.left.makerAssetFilledAmount = getPartialAmountFloor( | ||||
|                 leftOrder.makerAssetAmount, | ||||
|                 leftOrder.takerAssetAmount, | ||||
|                 matchedFillResults.left.takerAssetFilledAmount | ||||
|             ); | ||||
|         } else { | ||||
|             // Right order will be fully filled: maximally fill right | ||||
|             rightTakerAssetFilledAmount = rightTakerAssetAmountRemaining; | ||||
|  | ||||
|             // The left order receives an amount proportional to how much was spent. | ||||
|             leftTakerAssetFilledAmount = getPartialAmount( | ||||
|                 rightOrder.makerAssetAmount, | ||||
|             // Case 2: Left order is fully filled | ||||
|             matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; | ||||
|             matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; | ||||
|             matchedFillResults.right.makerAssetFilledAmount = matchedFillResults.left.takerAssetFilledAmount; | ||||
|             // Round up to ensure the maker's exchange rate does not exceed the price specified by the order. | ||||
|             // We favor the maker when the exchange rate must be rounded. | ||||
|             matchedFillResults.right.takerAssetFilledAmount = getPartialAmountCeil( | ||||
|                 rightOrder.takerAssetAmount, | ||||
|                 rightTakerAssetFilledAmount | ||||
|                 rightOrder.makerAssetAmount, | ||||
|                 matchedFillResults.right.makerAssetFilledAmount | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         // Calculate fill results for left order | ||||
|         matchedFillResults.left = calculateFillResults( | ||||
|             leftOrder, | ||||
|             leftTakerAssetFilledAmount | ||||
|         ); | ||||
|  | ||||
|         // Calculate fill results for right order | ||||
|         matchedFillResults.right = calculateFillResults( | ||||
|             rightOrder, | ||||
|             rightTakerAssetFilledAmount | ||||
|         ); | ||||
|  | ||||
|         // Calculate amount given to taker | ||||
|         matchedFillResults.leftMakerAssetSpreadAmount = safeSub( | ||||
|             matchedFillResults.left.makerAssetFilledAmount, | ||||
|             matchedFillResults.right.takerAssetFilledAmount | ||||
|         ); | ||||
|  | ||||
|         // Compute fees for left order | ||||
|         matchedFillResults.left.makerFeePaid = getPartialAmountFloor( | ||||
|             matchedFillResults.left.makerAssetFilledAmount, | ||||
|             leftOrder.makerAssetAmount, | ||||
|             leftOrder.makerFee | ||||
|         ); | ||||
|         matchedFillResults.left.takerFeePaid = getPartialAmountFloor( | ||||
|             matchedFillResults.left.takerAssetFilledAmount, | ||||
|             leftOrder.takerAssetAmount, | ||||
|             leftOrder.takerFee | ||||
|         ); | ||||
|  | ||||
|         // Compute fees for right order | ||||
|         matchedFillResults.right.makerFeePaid = getPartialAmountFloor( | ||||
|             matchedFillResults.right.makerAssetFilledAmount, | ||||
|             rightOrder.makerAssetAmount, | ||||
|             rightOrder.makerFee | ||||
|         ); | ||||
|         matchedFillResults.right.takerFeePaid = getPartialAmountFloor( | ||||
|             matchedFillResults.right.takerAssetFilledAmount, | ||||
|             rightOrder.takerAssetAmount, | ||||
|             rightOrder.takerFee | ||||
|         ); | ||||
|  | ||||
|         // Return fill results | ||||
|         return matchedFillResults; | ||||
|     } | ||||
|   | ||||
| @@ -19,6 +19,7 @@ | ||||
| pragma solidity 0.4.24; | ||||
|  | ||||
| import "../../utils/LibBytes/LibBytes.sol"; | ||||
| import "../../utils/ReentrancyGuard/ReentrancyGuard.sol"; | ||||
| import "./mixins/MSignatureValidator.sol"; | ||||
| import "./mixins/MTransactions.sol"; | ||||
| import "./interfaces/IWallet.sol"; | ||||
| @@ -26,6 +27,7 @@ import "./interfaces/IValidator.sol"; | ||||
|  | ||||
|  | ||||
| contract MixinSignatureValidator is | ||||
|     ReentrancyGuard, | ||||
|     MSignatureValidator, | ||||
|     MTransactions | ||||
| { | ||||
| @@ -48,14 +50,16 @@ contract MixinSignatureValidator is | ||||
|     ) | ||||
|         external | ||||
|     { | ||||
|         require( | ||||
|             isValidSignature( | ||||
|                 hash, | ||||
|                 signerAddress, | ||||
|                 signature | ||||
|             ), | ||||
|             "INVALID_SIGNATURE" | ||||
|         ); | ||||
|         if (signerAddress != msg.sender) { | ||||
|             require( | ||||
|                 isValidSignature( | ||||
|                     hash, | ||||
|                     signerAddress, | ||||
|                     signature | ||||
|                 ), | ||||
|                 "INVALID_SIGNATURE" | ||||
|             ); | ||||
|         } | ||||
|         preSigned[hash][signerAddress] = true; | ||||
|     } | ||||
|  | ||||
| @@ -67,6 +71,7 @@ contract MixinSignatureValidator is | ||||
|         bool approval | ||||
|     ) | ||||
|         external | ||||
|         nonReentrant | ||||
|     { | ||||
|         address signerAddress = getCurrentContextAddress(); | ||||
|         allowedValidators[signerAddress][validatorAddress] = approval; | ||||
| @@ -172,26 +177,14 @@ contract MixinSignatureValidator is | ||||
|             isValid = signerAddress == recovered; | ||||
|             return isValid; | ||||
|  | ||||
|         // Implicitly signed by caller. | ||||
|         // The signer has initiated the call. In the case of non-contract | ||||
|         // accounts it means the transaction itself was signed. | ||||
|         // Example: let's say for a particular operation three signatures | ||||
|         // A, B and C are required. To submit the transaction, A and B can | ||||
|         // give a signature to C, who can then submit the transaction using | ||||
|         // `Caller` for his own signature. Or A and C can sign and B can | ||||
|         // submit using `Caller`. Having `Caller` allows this flexibility. | ||||
|         } else if (signatureType == SignatureType.Caller) { | ||||
|             require( | ||||
|                 signature.length == 0, | ||||
|                 "LENGTH_0_REQUIRED" | ||||
|             ); | ||||
|             isValid = signerAddress == msg.sender; | ||||
|             return isValid; | ||||
|  | ||||
|         // Signature verified by wallet contract. | ||||
|         // If used with an order, the maker of the order is the wallet contract. | ||||
|         } else if (signatureType == SignatureType.Wallet) { | ||||
|             isValid = IWallet(signerAddress).isValidSignature(hash, signature); | ||||
|             isValid = isValidWalletSignature( | ||||
|                 hash, | ||||
|                 signerAddress, | ||||
|                 signature | ||||
|             ); | ||||
|             return isValid; | ||||
|  | ||||
|         // Signature verified by validator contract. | ||||
| @@ -209,7 +202,8 @@ contract MixinSignatureValidator is | ||||
|             if (!allowedValidators[signerAddress][validatorAddress]) { | ||||
|                 return false; | ||||
|             } | ||||
|             isValid = IValidator(validatorAddress).isValidSignature( | ||||
|             isValid = isValidValidatorSignature( | ||||
|                 validatorAddress, | ||||
|                 hash, | ||||
|                 signerAddress, | ||||
|                 signature | ||||
| @@ -220,34 +214,6 @@ contract MixinSignatureValidator is | ||||
|         } else if (signatureType == SignatureType.PreSigned) { | ||||
|             isValid = preSigned[hash][signerAddress]; | ||||
|             return isValid; | ||||
|  | ||||
|         // Signature from Trezor hardware wallet. | ||||
|         // It differs from web3.eth_sign in the encoding of message length | ||||
|         // (Bitcoin varint encoding vs ascii-decimal, the latter is not | ||||
|         // self-terminating which leads to ambiguities). | ||||
|         // See also: | ||||
|         // https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer | ||||
|         // https://github.com/trezor/trezor-mcu/blob/master/firmware/ethereum.c#L602 | ||||
|         // https://github.com/trezor/trezor-mcu/blob/master/firmware/crypto.c#L36 | ||||
|         } else if (signatureType == SignatureType.Trezor) { | ||||
|             require( | ||||
|                 signature.length == 65, | ||||
|                 "LENGTH_65_REQUIRED" | ||||
|             ); | ||||
|             v = uint8(signature[0]); | ||||
|             r = signature.readBytes32(1); | ||||
|             s = signature.readBytes32(33); | ||||
|             recovered = ecrecover( | ||||
|                 keccak256(abi.encodePacked( | ||||
|                     "\x19Ethereum Signed Message:\n\x20", | ||||
|                     hash | ||||
|                 )), | ||||
|                 v, | ||||
|                 r, | ||||
|                 s | ||||
|             ); | ||||
|             isValid = signerAddress == recovered; | ||||
|             return isValid; | ||||
|         } | ||||
|  | ||||
|         // Anything else is illegal (We do not return false because | ||||
| @@ -257,4 +223,102 @@ contract MixinSignatureValidator is | ||||
|         // signature was invalid.) | ||||
|         revert("SIGNATURE_UNSUPPORTED"); | ||||
|     } | ||||
|  | ||||
|     /// @dev Verifies signature using logic defined by Wallet contract. | ||||
|     /// @param hash Any 32 byte hash. | ||||
|     /// @param walletAddress Address that should have signed the given hash | ||||
|     ///                      and defines its own signature verification method. | ||||
|     /// @param signature Proof that the hash has been signed by signer. | ||||
|     /// @return True if signature is valid for given wallet.. | ||||
|     function isValidWalletSignature( | ||||
|         bytes32 hash, | ||||
|         address walletAddress, | ||||
|         bytes signature | ||||
|     ) | ||||
|         internal | ||||
|         view | ||||
|         returns (bool isValid) | ||||
|     { | ||||
|         bytes memory calldata = abi.encodeWithSelector( | ||||
|             IWallet(walletAddress).isValidSignature.selector, | ||||
|             hash, | ||||
|             signature | ||||
|         ); | ||||
|         assembly { | ||||
|             let cdStart := add(calldata, 32) | ||||
|             let success := staticcall( | ||||
|                 gas,              // forward all gas | ||||
|                 walletAddress,    // address of Wallet contract | ||||
|                 cdStart,          // pointer to start of input | ||||
|                 mload(calldata),  // length of input | ||||
|                 cdStart,          // write input over output | ||||
|                 32                // output size is 32 bytes | ||||
|             ) | ||||
|  | ||||
|             switch success | ||||
|             case 0 { | ||||
|                 // Revert with `Error("WALLET_ERROR")` | ||||
|                 mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) | ||||
|                 mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) | ||||
|                 mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000) | ||||
|                 mstore(96, 0) | ||||
|                 revert(0, 100) | ||||
|             } | ||||
|             case 1 { | ||||
|                 // Signature is valid if call did not revert and returned true | ||||
|                 isValid := mload(cdStart) | ||||
|             } | ||||
|         } | ||||
|         return isValid; | ||||
|     } | ||||
|  | ||||
|     /// @dev Verifies signature using logic defined by Validator contract. | ||||
|     /// @param validatorAddress Address of validator contract. | ||||
|     /// @param hash Any 32 byte hash. | ||||
|     /// @param signerAddress Address that should have signed the given hash. | ||||
|     /// @param signature Proof that the hash has been signed by signer. | ||||
|     /// @return True if the address recovered from the provided signature matches the input signer address. | ||||
|     function isValidValidatorSignature( | ||||
|         address validatorAddress, | ||||
|         bytes32 hash, | ||||
|         address signerAddress, | ||||
|         bytes signature | ||||
|     ) | ||||
|         internal | ||||
|         view | ||||
|         returns (bool isValid) | ||||
|     { | ||||
|         bytes memory calldata = abi.encodeWithSelector( | ||||
|             IValidator(signerAddress).isValidSignature.selector, | ||||
|             hash, | ||||
|             signerAddress, | ||||
|             signature | ||||
|         ); | ||||
|         assembly { | ||||
|             let cdStart := add(calldata, 32) | ||||
|             let success := staticcall( | ||||
|                 gas,               // forward all gas | ||||
|                 validatorAddress,  // address of Validator contract | ||||
|                 cdStart,           // pointer to start of input | ||||
|                 mload(calldata),   // length of input | ||||
|                 cdStart,           // write input over output | ||||
|                 32                 // output size is 32 bytes | ||||
|             ) | ||||
|  | ||||
|             switch success | ||||
|             case 0 { | ||||
|                 // Revert with `Error("VALIDATOR_ERROR")` | ||||
|                 mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) | ||||
|                 mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) | ||||
|                 mstore(64, 0x0000000f56414c494441544f525f4552524f5200000000000000000000000000) | ||||
|                 mstore(96, 0) | ||||
|                 revert(0, 100) | ||||
|             } | ||||
|             case 1 { | ||||
|                 // Signature is valid if call did not revert and returned true | ||||
|                 isValid := mload(cdStart) | ||||
|             } | ||||
|         } | ||||
|         return isValid; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -155,7 +155,8 @@ contract MixinTransactions is | ||||
|         view | ||||
|         returns (address) | ||||
|     { | ||||
|         address contextAddress = currentContextAddress == address(0) ? msg.sender : currentContextAddress; | ||||
|         address currentContextAddress_ = currentContextAddress; | ||||
|         address contextAddress = currentContextAddress_ == address(0) ? msg.sender : currentContextAddress_; | ||||
|         return contextAddress; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -19,18 +19,22 @@ | ||||
| pragma solidity 0.4.24; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../../utils/ReentrancyGuard/ReentrancyGuard.sol"; | ||||
| import "./libs/LibMath.sol"; | ||||
| import "./libs/LibOrder.sol"; | ||||
| import "./libs/LibFillResults.sol"; | ||||
| import "./libs/LibAbiEncoder.sol"; | ||||
| import "./mixins/MExchangeCore.sol"; | ||||
| import "./mixins/MWrapperFunctions.sol"; | ||||
|  | ||||
|  | ||||
| contract MixinWrapperFunctions is | ||||
|     ReentrancyGuard, | ||||
|     LibMath, | ||||
|     LibFillResults, | ||||
|     LibAbiEncoder, | ||||
|     MExchangeCore | ||||
|     MExchangeCore, | ||||
|     MWrapperFunctions | ||||
| { | ||||
|  | ||||
|     /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. | ||||
| @@ -43,17 +47,14 @@ contract MixinWrapperFunctions is | ||||
|         bytes memory signature | ||||
|     ) | ||||
|         public | ||||
|         nonReentrant | ||||
|         returns (FillResults memory fillResults) | ||||
|     { | ||||
|         fillResults = fillOrder( | ||||
|         fillResults = fillOrKillOrderInternal( | ||||
|             order, | ||||
|             takerAssetFillAmount, | ||||
|             signature | ||||
|         ); | ||||
|         require( | ||||
|             fillResults.takerAssetFilledAmount == takerAssetFillAmount, | ||||
|             "COMPLETE_FILL_FAILED" | ||||
|         ); | ||||
|         return fillResults; | ||||
|     } | ||||
|  | ||||
| @@ -88,14 +89,7 @@ contract MixinWrapperFunctions is | ||||
|                 fillOrderCalldata,                  // write output over input | ||||
|                 128                                 // output size is 128 bytes | ||||
|             ) | ||||
|             switch success | ||||
|             case 0 { | ||||
|                 mstore(fillResults, 0) | ||||
|                 mstore(add(fillResults, 32), 0) | ||||
|                 mstore(add(fillResults, 64), 0) | ||||
|                 mstore(add(fillResults, 96), 0) | ||||
|             } | ||||
|             case 1 { | ||||
|             if success { | ||||
|                 mstore(fillResults, mload(fillOrderCalldata)) | ||||
|                 mstore(add(fillResults, 32), mload(add(fillOrderCalldata, 32))) | ||||
|                 mstore(add(fillResults, 64), mload(add(fillOrderCalldata, 64))) | ||||
| @@ -117,11 +111,12 @@ contract MixinWrapperFunctions is | ||||
|         bytes[] memory signatures | ||||
|     ) | ||||
|         public | ||||
|         nonReentrant | ||||
|         returns (FillResults memory totalFillResults) | ||||
|     { | ||||
|         uint256 ordersLength = orders.length; | ||||
|         for (uint256 i = 0; i != ordersLength; i++) { | ||||
|             FillResults memory singleFillResults = fillOrder( | ||||
|             FillResults memory singleFillResults = fillOrderInternal( | ||||
|                 orders[i], | ||||
|                 takerAssetFillAmounts[i], | ||||
|                 signatures[i] | ||||
| @@ -143,11 +138,12 @@ contract MixinWrapperFunctions is | ||||
|         bytes[] memory signatures | ||||
|     ) | ||||
|         public | ||||
|         nonReentrant | ||||
|         returns (FillResults memory totalFillResults) | ||||
|     { | ||||
|         uint256 ordersLength = orders.length; | ||||
|         for (uint256 i = 0; i != ordersLength; i++) { | ||||
|             FillResults memory singleFillResults = fillOrKillOrder( | ||||
|             FillResults memory singleFillResults = fillOrKillOrderInternal( | ||||
|                 orders[i], | ||||
|                 takerAssetFillAmounts[i], | ||||
|                 signatures[i] | ||||
| @@ -195,6 +191,7 @@ contract MixinWrapperFunctions is | ||||
|         bytes[] memory signatures | ||||
|     ) | ||||
|         public | ||||
|         nonReentrant | ||||
|         returns (FillResults memory totalFillResults) | ||||
|     { | ||||
|         bytes memory takerAssetData = orders[0].takerAssetData; | ||||
| @@ -210,7 +207,7 @@ contract MixinWrapperFunctions is | ||||
|             uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount); | ||||
|  | ||||
|             // Attempt to sell the remaining amount of takerAsset | ||||
|             FillResults memory singleFillResults = fillOrder( | ||||
|             FillResults memory singleFillResults = fillOrderInternal( | ||||
|                 orders[i], | ||||
|                 remainingTakerAssetFillAmount, | ||||
|                 signatures[i] | ||||
| @@ -282,6 +279,7 @@ contract MixinWrapperFunctions is | ||||
|         bytes[] memory signatures | ||||
|     ) | ||||
|         public | ||||
|         nonReentrant | ||||
|         returns (FillResults memory totalFillResults) | ||||
|     { | ||||
|         bytes memory makerAssetData = orders[0].makerAssetData; | ||||
| @@ -298,14 +296,14 @@ contract MixinWrapperFunctions is | ||||
|  | ||||
|             // Convert the remaining amount of makerAsset to buy into remaining amount | ||||
|             // of takerAsset to sell, assuming entire amount can be sold in the current order | ||||
|             uint256 remainingTakerAssetFillAmount = getPartialAmount( | ||||
|             uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( | ||||
|                 orders[i].takerAssetAmount, | ||||
|                 orders[i].makerAssetAmount, | ||||
|                 remainingMakerAssetFillAmount | ||||
|             ); | ||||
|  | ||||
|             // Attempt to sell the remaining amount of takerAsset | ||||
|             FillResults memory singleFillResults = fillOrder( | ||||
|             FillResults memory singleFillResults = fillOrderInternal( | ||||
|                 orders[i], | ||||
|                 remainingTakerAssetFillAmount, | ||||
|                 signatures[i] | ||||
| @@ -350,7 +348,7 @@ contract MixinWrapperFunctions is | ||||
|  | ||||
|             // Convert the remaining amount of makerAsset to buy into remaining amount | ||||
|             // of takerAsset to sell, assuming entire amount can be sold in the current order | ||||
|             uint256 remainingTakerAssetFillAmount = getPartialAmount( | ||||
|             uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( | ||||
|                 orders[i].takerAssetAmount, | ||||
|                 orders[i].makerAssetAmount, | ||||
|                 remainingMakerAssetFillAmount | ||||
| @@ -400,4 +398,28 @@ contract MixinWrapperFunctions is | ||||
|         } | ||||
|         return ordersInfo; | ||||
|     } | ||||
|  | ||||
|     /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. | ||||
|     /// @param order Order struct containing order specifications. | ||||
|     /// @param takerAssetFillAmount Desired amount of takerAsset to sell. | ||||
|     /// @param signature Proof that order has been created by maker. | ||||
|     function fillOrKillOrderInternal( | ||||
|         LibOrder.Order memory order, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         bytes memory signature | ||||
|     ) | ||||
|         internal | ||||
|         returns (FillResults memory fillResults) | ||||
|     { | ||||
|         fillResults = fillOrderInternal( | ||||
|             order, | ||||
|             takerAssetFillAmount, | ||||
|             signature | ||||
|         ); | ||||
|         require( | ||||
|             fillResults.takerAssetFilledAmount == takerAssetFillAmount, | ||||
|             "COMPLETE_FILL_FAILED" | ||||
|         ); | ||||
|         return fillResults; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -25,12 +25,12 @@ contract LibMath is | ||||
|     SafeMath | ||||
| { | ||||
|  | ||||
|     /// @dev Calculates partial value given a numerator and denominator. | ||||
|     /// @dev Calculates partial value given a numerator and denominator rounded down. | ||||
|     /// @param numerator Numerator. | ||||
|     /// @param denominator Denominator. | ||||
|     /// @param target Value to calculate partial of. | ||||
|     /// @return Partial value of target. | ||||
|     function getPartialAmount( | ||||
|     /// @return Partial value of target rounded down. | ||||
|     function getPartialAmountFloor( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
| @@ -39,19 +39,56 @@ contract LibMath is | ||||
|         pure | ||||
|         returns (uint256 partialAmount) | ||||
|     { | ||||
|         require( | ||||
|             denominator > 0, | ||||
|             "DIVISION_BY_ZERO" | ||||
|         ); | ||||
|          | ||||
|         partialAmount = safeDiv( | ||||
|             safeMul(numerator, target), | ||||
|             denominator | ||||
|         ); | ||||
|         return partialAmount; | ||||
|     } | ||||
|  | ||||
|     /// @dev Checks if rounding error > 0.1%. | ||||
|      | ||||
|     /// @dev Calculates partial value given a numerator and denominator rounded down. | ||||
|     /// @param numerator Numerator. | ||||
|     /// @param denominator Denominator. | ||||
|     /// @param target Value to calculate partial of. | ||||
|     /// @return Partial value of target rounded up. | ||||
|     function getPartialAmountCeil( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
|     ) | ||||
|         internal | ||||
|         pure | ||||
|         returns (uint256 partialAmount) | ||||
|     { | ||||
|         require( | ||||
|             denominator > 0, | ||||
|             "DIVISION_BY_ZERO" | ||||
|         ); | ||||
|          | ||||
|         // safeDiv computes `floor(a / b)`. We use the identity (a, b integer): | ||||
|         //       ceil(a / b) = floor((a + b - 1) / b) | ||||
|         // To implement `ceil(a / b)` using safeDiv. | ||||
|         partialAmount = safeDiv( | ||||
|             safeAdd( | ||||
|                 safeMul(numerator, target), | ||||
|                 safeSub(denominator, 1) | ||||
|             ), | ||||
|             denominator | ||||
|         ); | ||||
|         return partialAmount; | ||||
|     } | ||||
|      | ||||
|     /// @dev Checks if rounding error >= 0.1% when rounding down. | ||||
|     /// @param numerator Numerator. | ||||
|     /// @param denominator Denominator. | ||||
|     /// @param target Value to multiply with numerator/denominator. | ||||
|     /// @return Rounding error is present. | ||||
|     function isRoundingError( | ||||
|     function isRoundingErrorFloor( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
| @@ -60,16 +97,73 @@ contract LibMath is | ||||
|         pure | ||||
|         returns (bool isError) | ||||
|     { | ||||
|         uint256 remainder = mulmod(target, numerator, denominator); | ||||
|         if (remainder == 0) { | ||||
|             return false; // No rounding error. | ||||
|         } | ||||
|  | ||||
|         uint256 errPercentageTimes1000000 = safeDiv( | ||||
|             safeMul(remainder, 1000000), | ||||
|             safeMul(numerator, target) | ||||
|         require( | ||||
|             denominator > 0, | ||||
|             "DIVISION_BY_ZERO" | ||||
|         ); | ||||
|         isError = errPercentageTimes1000000 > 1000; | ||||
|          | ||||
|         // The absolute rounding error is the difference between the rounded | ||||
|         // value and the ideal value. The relative rounding error is the | ||||
|         // absolute rounding error divided by the absolute value of the | ||||
|         // ideal value. This is undefined when the ideal value is zero. | ||||
|         // | ||||
|         // The ideal value is `numerator * target / denominator`. | ||||
|         // Let's call `numerator * target % denominator` the remainder. | ||||
|         // The absolute error is `remainder / denominator`. | ||||
|         // | ||||
|         // When the ideal value is zero, we require the absolute error to | ||||
|         // be zero. Fortunately, this is always the case. The ideal value is | ||||
|         // zero iff `numerator == 0` and/or `target == 0`. In this case the | ||||
|         // remainder and absolute error are also zero.  | ||||
|         if (target == 0 || numerator == 0) { | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         // Otherwise, we want the relative rounding error to be strictly | ||||
|         // less than 0.1%. | ||||
|         // The relative error is `remainder / (numerator * target)`. | ||||
|         // We want the relative error less than 1 / 1000: | ||||
|         //        remainder / (numerator * denominator)  <  1 / 1000 | ||||
|         // or equivalently: | ||||
|         //        1000 * remainder  <  numerator * target | ||||
|         // so we have a rounding error iff: | ||||
|         //        1000 * remainder  >=  numerator * target | ||||
|         uint256 remainder = mulmod(target, numerator, denominator); | ||||
|         isError = safeMul(1000, remainder) >= safeMul(numerator, target); | ||||
|         return isError; | ||||
|     } | ||||
|      | ||||
|     /// @dev Checks if rounding error >= 0.1% when rounding up. | ||||
|     /// @param numerator Numerator. | ||||
|     /// @param denominator Denominator. | ||||
|     /// @param target Value to multiply with numerator/denominator. | ||||
|     /// @return Rounding error is present. | ||||
|     function isRoundingErrorCeil( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
|     ) | ||||
|         internal | ||||
|         pure | ||||
|         returns (bool isError) | ||||
|     { | ||||
|         require( | ||||
|             denominator > 0, | ||||
|             "DIVISION_BY_ZERO" | ||||
|         ); | ||||
|          | ||||
|         // See the comments in `isRoundingError`. | ||||
|         if (target == 0 || numerator == 0) { | ||||
|             // When either is zero, the ideal value and rounded value are zero | ||||
|             // and there is no rounding error. (Although the relative error | ||||
|             // is undefined.) | ||||
|             return false; | ||||
|         } | ||||
|         // Compute remainder as before | ||||
|         uint256 remainder = mulmod(target, numerator, denominator); | ||||
|         // TODO: safeMod | ||||
|         remainder = safeSub(denominator, remainder) % denominator; | ||||
|         isError = safeMul(1000, remainder) >= safeMul(numerator, target); | ||||
|         return isError; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -59,6 +59,19 @@ contract MExchangeCore is | ||||
|         uint256 orderEpoch                    // Orders with specified makerAddress and senderAddress with a salt less than this value are considered cancelled. | ||||
|     ); | ||||
|  | ||||
|     /// @dev Fills the input order. | ||||
|     /// @param order Order struct containing order specifications. | ||||
|     /// @param takerAssetFillAmount Desired amount of takerAsset to sell. | ||||
|     /// @param signature Proof that order has been created by maker. | ||||
|     /// @return Amounts filled and fees paid by maker and taker. | ||||
|     function fillOrderInternal( | ||||
|         LibOrder.Order memory order, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         bytes memory signature | ||||
|     ) | ||||
|         internal | ||||
|         returns (LibFillResults.FillResults memory fillResults); | ||||
|  | ||||
|     /// @dev Updates state with results of a fill order. | ||||
|     /// @param order that was filled. | ||||
|     /// @param takerAddress Address of taker who filled the order. | ||||
| @@ -83,21 +96,33 @@ contract MExchangeCore is | ||||
|         bytes32 orderHash | ||||
|     ) | ||||
|         internal; | ||||
|  | ||||
|      | ||||
|     /// @dev Validates context for fillOrder. Succeeds or throws. | ||||
|     /// @param order to be filled. | ||||
|     /// @param orderInfo Status, orderHash, and amount already filled of order. | ||||
|     /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. | ||||
|     /// @param takerAddress Address of order taker. | ||||
|     /// @param takerAssetFillAmount Desired amount of order to fill by taker. | ||||
|     /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. | ||||
|     /// @param signature Proof that the orders was created by its maker. | ||||
|     function assertValidFill( | ||||
|     function assertFillableOrder( | ||||
|         LibOrder.Order memory order, | ||||
|         LibOrder.OrderInfo memory orderInfo, | ||||
|         address takerAddress, | ||||
|         bytes memory signature | ||||
|     ) | ||||
|         internal | ||||
|         view; | ||||
|      | ||||
|     /// @dev Validates context for fillOrder. Succeeds or throws. | ||||
|     /// @param order to be filled. | ||||
|     /// @param orderInfo Status, orderHash, and amount already filled of order. | ||||
|     /// @param takerAssetFillAmount Desired amount of order to fill by taker. | ||||
|     /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. | ||||
|     /// @param makerAssetFilledAmount Amount of makerAsset that will be transfered. | ||||
|     function assertValidFill( | ||||
|         LibOrder.Order memory order, | ||||
|         LibOrder.OrderInfo memory orderInfo, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         uint256 takerAssetFilledAmount, | ||||
|         bytes memory signature | ||||
|         uint256 makerAssetFilledAmount | ||||
|     ) | ||||
|         internal | ||||
|         view; | ||||
|   | ||||
| @@ -36,11 +36,40 @@ contract MSignatureValidator is | ||||
|         Invalid,         // 0x01 | ||||
|         EIP712,          // 0x02 | ||||
|         EthSign,         // 0x03 | ||||
|         Caller,          // 0x04 | ||||
|         Wallet,          // 0x05 | ||||
|         Validator,       // 0x06 | ||||
|         PreSigned,       // 0x07 | ||||
|         Trezor,          // 0x08 | ||||
|         NSignatureTypes  // 0x09, number of signature types. Always leave at end. | ||||
|         Wallet,          // 0x04 | ||||
|         Validator,       // 0x05 | ||||
|         PreSigned,       // 0x06 | ||||
|         NSignatureTypes  // 0x07, number of signature types. Always leave at end. | ||||
|     } | ||||
|  | ||||
|     /// @dev Verifies signature using logic defined by Wallet contract. | ||||
|     /// @param hash Any 32 byte hash. | ||||
|     /// @param walletAddress Address that should have signed the given hash | ||||
|     ///                      and defines its own signature verification method. | ||||
|     /// @param signature Proof that the hash has been signed by signer. | ||||
|     /// @return True if the address recovered from the provided signature matches the input signer address. | ||||
|     function isValidWalletSignature( | ||||
|         bytes32 hash, | ||||
|         address walletAddress, | ||||
|         bytes signature | ||||
|     ) | ||||
|         internal | ||||
|         view | ||||
|         returns (bool isValid); | ||||
|  | ||||
|     /// @dev Verifies signature using logic defined by Validator contract. | ||||
|     /// @param validatorAddress Address of validator contract. | ||||
|     /// @param hash Any 32 byte hash. | ||||
|     /// @param signerAddress Address that should have signed the given hash. | ||||
|     /// @param signature Proof that the hash has been signed by signer. | ||||
|     /// @return True if the address recovered from the provided signature matches the input signer address. | ||||
|     function isValidValidatorSignature( | ||||
|         address validatorAddress, | ||||
|         bytes32 hash, | ||||
|         address signerAddress, | ||||
|         bytes signature | ||||
|     ) | ||||
|         internal | ||||
|         view | ||||
|         returns (bool isValid); | ||||
| } | ||||
|   | ||||
							
								
								
									
										40
									
								
								packages/contracts/src/2.0.0/protocol/Exchange/mixins/MWrapperFunctions.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								packages/contracts/src/2.0.0/protocol/Exchange/mixins/MWrapperFunctions.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2018 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity 0.4.24; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../libs/LibOrder.sol"; | ||||
| import "../libs/LibFillResults.sol"; | ||||
| import "../interfaces/IWrapperFunctions.sol"; | ||||
|  | ||||
|  | ||||
| contract MWrapperFunctions { | ||||
|  | ||||
|     /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. | ||||
|     /// @param order LibOrder.Order struct containing order specifications. | ||||
|     /// @param takerAssetFillAmount Desired amount of takerAsset to sell. | ||||
|     /// @param signature Proof that order has been created by maker. | ||||
|     function fillOrKillOrderInternal( | ||||
|         LibOrder.Order memory order, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         bytes memory signature | ||||
|     ) | ||||
|         internal | ||||
|         returns (LibFillResults.FillResults memory fillResults); | ||||
| } | ||||
							
								
								
									
										182
									
								
								packages/contracts/src/2.0.0/test/ReentrantERC20Token/ReentrantERC20Token.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								packages/contracts/src/2.0.0/test/ReentrantERC20Token/ReentrantERC20Token.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,182 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2018 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity 0.4.24; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../../utils/LibBytes/LibBytes.sol"; | ||||
| import "../../tokens/ERC20Token/ERC20Token.sol"; | ||||
| import "../../protocol/Exchange/interfaces/IExchange.sol"; | ||||
| import "../../protocol/Exchange/libs/LibOrder.sol"; | ||||
|  | ||||
|  | ||||
| contract ReentrantERC20Token is | ||||
|     ERC20Token | ||||
| { | ||||
|  | ||||
|     using LibBytes for bytes; | ||||
|  | ||||
|     // solhint-disable-next-line var-name-mixedcase | ||||
|     IExchange internal EXCHANGE; | ||||
|  | ||||
|     bytes internal constant REENTRANCY_ILLEGAL_REVERT_REASON = abi.encodeWithSelector( | ||||
|         bytes4(keccak256("Error(string)")), | ||||
|         "REENTRANCY_ILLEGAL" | ||||
|     ); | ||||
|  | ||||
|     // All of these functions are potentially vulnerable to reentrancy | ||||
|     // We do not test any "noThrow" functions because `fillOrderNoThrow` makes a delegatecall to `fillOrder` | ||||
|     enum ExchangeFunction { | ||||
|         FILL_ORDER, | ||||
|         FILL_OR_KILL_ORDER, | ||||
|         BATCH_FILL_ORDERS, | ||||
|         BATCH_FILL_OR_KILL_ORDERS, | ||||
|         MARKET_BUY_ORDERS, | ||||
|         MARKET_SELL_ORDERS, | ||||
|         MATCH_ORDERS, | ||||
|         CANCEL_ORDER, | ||||
|         CANCEL_ORDERS_UP_TO, | ||||
|         SET_SIGNATURE_VALIDATOR_APPROVAL | ||||
|     } | ||||
|  | ||||
|     uint8 internal currentFunctionId = 0; | ||||
|  | ||||
|     constructor (address _exchange) | ||||
|         public | ||||
|     { | ||||
|         EXCHANGE = IExchange(_exchange); | ||||
|     } | ||||
|  | ||||
|     /// @dev Set the current function that will be called when `transferFrom` is called. | ||||
|     /// @param _currentFunctionId Id that corresponds to function name. | ||||
|     function setCurrentFunction(uint8 _currentFunctionId) | ||||
|         external | ||||
|     { | ||||
|         currentFunctionId = _currentFunctionId; | ||||
|     } | ||||
|  | ||||
|     /// @dev A version of `transferFrom` that attempts to reenter the Exchange contract. | ||||
|     /// @param _from The address of the sender | ||||
|     /// @param _to The address of the recipient | ||||
|     /// @param _value The amount of token to be transferred | ||||
|     function transferFrom( | ||||
|         address _from, | ||||
|         address _to, | ||||
|         uint256 _value | ||||
|     ) | ||||
|         external | ||||
|         returns (bool) | ||||
|     { | ||||
|         // This order would normally be invalid, but it will be used strictly for testing reentrnacy. | ||||
|         // Any reentrancy checks will happen before any other checks that invalidate the order. | ||||
|         LibOrder.Order memory order; | ||||
|  | ||||
|         // Initialize remaining null parameters | ||||
|         bytes memory signature; | ||||
|         LibOrder.Order[] memory orders; | ||||
|         uint256[] memory takerAssetFillAmounts; | ||||
|         bytes[] memory signatures; | ||||
|         bytes memory calldata; | ||||
|  | ||||
|         // Create calldata for function that corresponds to currentFunctionId | ||||
|         if (currentFunctionId == uint8(ExchangeFunction.FILL_ORDER)) { | ||||
|             calldata = abi.encodeWithSelector( | ||||
|                 EXCHANGE.fillOrder.selector, | ||||
|                 order, | ||||
|                 0, | ||||
|                 signature | ||||
|             ); | ||||
|         } else if (currentFunctionId == uint8(ExchangeFunction.FILL_OR_KILL_ORDER)) { | ||||
|             calldata = abi.encodeWithSelector( | ||||
|                 EXCHANGE.fillOrKillOrder.selector, | ||||
|                 order, | ||||
|                 0, | ||||
|                 signature | ||||
|             ); | ||||
|         } else if (currentFunctionId == uint8(ExchangeFunction.BATCH_FILL_ORDERS)) { | ||||
|             calldata = abi.encodeWithSelector( | ||||
|                 EXCHANGE.batchFillOrders.selector, | ||||
|                 orders, | ||||
|                 takerAssetFillAmounts, | ||||
|                 signatures | ||||
|             ); | ||||
|         } else if (currentFunctionId == uint8(ExchangeFunction.BATCH_FILL_OR_KILL_ORDERS)) { | ||||
|             calldata = abi.encodeWithSelector( | ||||
|                 EXCHANGE.batchFillOrKillOrders.selector, | ||||
|                 orders, | ||||
|                 takerAssetFillAmounts, | ||||
|                 signatures | ||||
|             ); | ||||
|         } else if (currentFunctionId == uint8(ExchangeFunction.MARKET_BUY_ORDERS)) { | ||||
|             calldata = abi.encodeWithSelector( | ||||
|                 EXCHANGE.marketBuyOrders.selector, | ||||
|                 orders, | ||||
|                 0, | ||||
|                 signatures | ||||
|             ); | ||||
|         } else if (currentFunctionId == uint8(ExchangeFunction.MARKET_SELL_ORDERS)) { | ||||
|             calldata = abi.encodeWithSelector( | ||||
|                 EXCHANGE.marketSellOrders.selector, | ||||
|                 orders, | ||||
|                 0, | ||||
|                 signatures | ||||
|             ); | ||||
|         } else if (currentFunctionId == uint8(ExchangeFunction.MATCH_ORDERS)) { | ||||
|             calldata = abi.encodeWithSelector( | ||||
|                 EXCHANGE.matchOrders.selector, | ||||
|                 order, | ||||
|                 order, | ||||
|                 signature, | ||||
|                 signature | ||||
|             ); | ||||
|         } else if (currentFunctionId == uint8(ExchangeFunction.CANCEL_ORDER)) { | ||||
|             calldata = abi.encodeWithSelector( | ||||
|                 EXCHANGE.cancelOrder.selector, | ||||
|                 order | ||||
|             ); | ||||
|         } else if (currentFunctionId == uint8(ExchangeFunction.CANCEL_ORDERS_UP_TO)) { | ||||
|             calldata = abi.encodeWithSelector( | ||||
|                 EXCHANGE.cancelOrdersUpTo.selector, | ||||
|                 0 | ||||
|             ); | ||||
|         } else if (currentFunctionId == uint8(ExchangeFunction.SET_SIGNATURE_VALIDATOR_APPROVAL)) { | ||||
|             calldata = abi.encodeWithSelector( | ||||
|                 EXCHANGE.setSignatureValidatorApproval.selector, | ||||
|                 address(0), | ||||
|                 false | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         // Call Exchange function, swallow error | ||||
|         address(EXCHANGE).call(calldata); | ||||
|  | ||||
|         // Revert reason is 100 bytes | ||||
|         bytes memory returnData = new bytes(100); | ||||
|  | ||||
|         // Copy return data | ||||
|         assembly { | ||||
|             returndatacopy(add(returnData, 32), 0, 100) | ||||
|         } | ||||
|  | ||||
|         // Revert if function reverted with REENTRANCY_ILLEGAL error | ||||
|         require(!REENTRANCY_ILLEGAL_REVERT_REASON.equals(returnData)); | ||||
|  | ||||
|         // Transfer will return true if function failed for any other reason | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @@ -67,7 +67,7 @@ contract TestExchangeInternals is | ||||
|     /// @param denominator Denominator. | ||||
|     /// @param target Value to calculate partial of. | ||||
|     /// @return Partial value of target. | ||||
|     function publicGetPartialAmount( | ||||
|     function publicGetPartialAmountFloor( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
| @@ -76,15 +76,32 @@ contract TestExchangeInternals is | ||||
|         pure | ||||
|         returns (uint256 partialAmount) | ||||
|     { | ||||
|         return getPartialAmount(numerator, denominator, target); | ||||
|         return getPartialAmountFloor(numerator, denominator, target); | ||||
|     } | ||||
|  | ||||
|     /// @dev Checks if rounding error > 0.1%. | ||||
|     /// @dev Calculates partial value given a numerator and denominator. | ||||
|     /// @param numerator Numerator. | ||||
|     /// @param denominator Denominator. | ||||
|     /// @param target Value to calculate partial of. | ||||
|     /// @return Partial value of target. | ||||
|     function publicGetPartialAmountCeil( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
|     ) | ||||
|         public | ||||
|         pure | ||||
|         returns (uint256 partialAmount) | ||||
|     { | ||||
|         return getPartialAmountCeil(numerator, denominator, target); | ||||
|     } | ||||
|  | ||||
|     /// @dev Checks if rounding error >= 0.1%. | ||||
|     /// @param numerator Numerator. | ||||
|     /// @param denominator Denominator. | ||||
|     /// @param target Value to multiply with numerator/denominator. | ||||
|     /// @return Rounding error is present. | ||||
|     function publicIsRoundingError( | ||||
|     function publicIsRoundingErrorFloor( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
| @@ -93,7 +110,24 @@ contract TestExchangeInternals is | ||||
|         pure | ||||
|         returns (bool isError) | ||||
|     { | ||||
|         return isRoundingError(numerator, denominator, target); | ||||
|         return isRoundingErrorFloor(numerator, denominator, target); | ||||
|     } | ||||
|  | ||||
|     /// @dev Checks if rounding error >= 0.1%. | ||||
|     /// @param numerator Numerator. | ||||
|     /// @param denominator Denominator. | ||||
|     /// @param target Value to multiply with numerator/denominator. | ||||
|     /// @return Rounding error is present. | ||||
|     function publicIsRoundingErrorCeil( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
|     ) | ||||
|         public | ||||
|         pure | ||||
|         returns (bool isError) | ||||
|     { | ||||
|         return isRoundingErrorCeil(numerator, denominator, target); | ||||
|     } | ||||
|   | ||||
|     /// @dev Updates state with results of a fill order. | ||||
|   | ||||
| @@ -49,7 +49,7 @@ contract TestLibs is | ||||
|         return fillOrderCalldata; | ||||
|     } | ||||
|  | ||||
|     function publicGetPartialAmount( | ||||
|     function publicGetPartialAmountFloor( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
| @@ -58,7 +58,7 @@ contract TestLibs is | ||||
|         pure | ||||
|         returns (uint256 partialAmount) | ||||
|     { | ||||
|         partialAmount = getPartialAmount( | ||||
|         partialAmount = getPartialAmountFloor( | ||||
|             numerator, | ||||
|             denominator, | ||||
|             target | ||||
| @@ -66,7 +66,24 @@ contract TestLibs is | ||||
|         return partialAmount; | ||||
|     } | ||||
|  | ||||
|     function publicIsRoundingError( | ||||
|     function publicGetPartialAmountCeil( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
|     ) | ||||
|         public | ||||
|         pure | ||||
|         returns (uint256 partialAmount) | ||||
|     { | ||||
|         partialAmount = getPartialAmountCeil( | ||||
|             numerator, | ||||
|             denominator, | ||||
|             target | ||||
|         ); | ||||
|         return partialAmount; | ||||
|     } | ||||
|  | ||||
|     function publicIsRoundingErrorFloor( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
| @@ -75,7 +92,24 @@ contract TestLibs is | ||||
|         pure | ||||
|         returns (bool isError) | ||||
|     { | ||||
|         isError = isRoundingError( | ||||
|         isError = isRoundingErrorFloor( | ||||
|             numerator, | ||||
|             denominator, | ||||
|             target | ||||
|         ); | ||||
|         return isError; | ||||
|     } | ||||
|  | ||||
|     function publicIsRoundingErrorCeil( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
|     ) | ||||
|         public | ||||
|         pure | ||||
|         returns (bool isError) | ||||
|     { | ||||
|         isError = isRoundingErrorCeil( | ||||
|             numerator, | ||||
|             denominator, | ||||
|             target | ||||
|   | ||||
							
								
								
									
										81
									
								
								packages/contracts/src/2.0.0/test/TestStaticCallReceiver/TestStaticCallReceiver.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								packages/contracts/src/2.0.0/test/TestStaticCallReceiver/TestStaticCallReceiver.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2018 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity 0.4.24; | ||||
|  | ||||
| import "../../tokens/ERC20Token/IERC20Token.sol"; | ||||
|  | ||||
|  | ||||
| // solhint-disable no-unused-vars | ||||
| contract TestStaticCallReceiver { | ||||
|  | ||||
|     uint256 internal state = 1; | ||||
|  | ||||
|     /// @dev Updates state and returns true. Intended to be used with `Validator` signature type. | ||||
|     /// @param hash Message hash that is signed. | ||||
|     /// @param signerAddress Address that should have signed the given hash. | ||||
|     /// @param signature Proof of signing. | ||||
|     /// @return Validity of order signature. | ||||
|     function isValidSignature( | ||||
|         bytes32 hash, | ||||
|         address signerAddress, | ||||
|         bytes signature | ||||
|     ) | ||||
|         external | ||||
|         returns (bool isValid) | ||||
|     { | ||||
|         updateState(); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /// @dev Updates state and returns true. Intended to be used with `Wallet` signature type. | ||||
|     /// @param hash Message hash that is signed. | ||||
|     /// @param signature Proof of signing. | ||||
|     /// @return Validity of order signature. | ||||
|     function isValidSignature( | ||||
|         bytes32 hash, | ||||
|         bytes signature | ||||
|     ) | ||||
|         external | ||||
|         returns (bool isValid) | ||||
|     { | ||||
|         updateState(); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /// @dev Approves an ERC20 token to spend tokens from this address. | ||||
|     /// @param token Address of ERC20 token. | ||||
|     /// @param spender Address that will spend tokens. | ||||
|     /// @param value Amount of tokens spender is approved to spend. | ||||
|     function approveERC20( | ||||
|         address token, | ||||
|         address spender, | ||||
|         uint256 value | ||||
|     ) | ||||
|         external | ||||
|     { | ||||
|         IERC20Token(token).approve(spender, value); | ||||
|     } | ||||
|  | ||||
|     /// @dev Increments state variable. | ||||
|     function updateState() | ||||
|         internal | ||||
|     { | ||||
|         state++; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										44
									
								
								packages/contracts/src/2.0.0/utils/ReentrancyGuard/ReentrancyGuard.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								packages/contracts/src/2.0.0/utils/ReentrancyGuard/ReentrancyGuard.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2018 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
| pragma solidity 0.4.24; | ||||
|  | ||||
|  | ||||
| contract ReentrancyGuard { | ||||
|  | ||||
|     // Locked state of mutex | ||||
|     bool private locked = false; | ||||
|  | ||||
|     /// @dev Functions with this modifer cannot be reentered. The mutex will be locked | ||||
|     ///      before function execution and unlocked after. | ||||
|     modifier nonReentrant() { | ||||
|         // Ensure mutex is unlocked | ||||
|         require( | ||||
|             !locked, | ||||
|             "REENTRANCY_ILLEGAL" | ||||
|         ); | ||||
|  | ||||
|         // Lock mutex before function call | ||||
|         locked = true; | ||||
|  | ||||
|         // Perform function call | ||||
|         _; | ||||
|  | ||||
|         // Unlock mutex after function call | ||||
|         locked = false; | ||||
|     } | ||||
| } | ||||
| @@ -12,7 +12,7 @@ import { ERC20ProxyContract } from '../../generated_contract_wrappers/erc20_prox | ||||
| import { ERC721ProxyContract } from '../../generated_contract_wrappers/erc721_proxy'; | ||||
| import { IAssetProxyContract } from '../../generated_contract_wrappers/i_asset_proxy'; | ||||
| import { artifacts } from '../utils/artifacts'; | ||||
| import { expectTransactionFailedAsync } from '../utils/assertions'; | ||||
| import { expectTransactionFailedAsync, expectTransactionFailedWithoutReasonAsync } from '../utils/assertions'; | ||||
| import { chaiSetup } from '../utils/chai_setup'; | ||||
| import { constants } from '../utils/constants'; | ||||
| import { ERC20Wrapper } from '../utils/erc20_wrapper'; | ||||
| @@ -99,6 +99,17 @@ describe('Asset Transfer Proxies', () => { | ||||
|         await blockchainLifecycle.revertAsync(); | ||||
|     }); | ||||
|     describe('Transfer Proxy - ERC20', () => { | ||||
|         it('should revert if undefined function is called', async () => { | ||||
|             const undefinedSelector = '0x01020304'; | ||||
|             await expectTransactionFailedWithoutReasonAsync( | ||||
|                 web3Wrapper.sendTransactionAsync({ | ||||
|                     from: owner, | ||||
|                     to: erc20Proxy.address, | ||||
|                     value: constants.ZERO_AMOUNT, | ||||
|                     data: undefinedSelector, | ||||
|                 }), | ||||
|             ); | ||||
|         }); | ||||
|         describe('transferFrom', () => { | ||||
|             it('should successfully transfer tokens', async () => { | ||||
|                 // Construct ERC20 asset data | ||||
| @@ -219,6 +230,17 @@ describe('Asset Transfer Proxies', () => { | ||||
|     }); | ||||
|  | ||||
|     describe('Transfer Proxy - ERC721', () => { | ||||
|         it('should revert if undefined function is called', async () => { | ||||
|             const undefinedSelector = '0x01020304'; | ||||
|             await expectTransactionFailedWithoutReasonAsync( | ||||
|                 web3Wrapper.sendTransactionAsync({ | ||||
|                     from: owner, | ||||
|                     to: erc721Proxy.address, | ||||
|                     value: constants.ZERO_AMOUNT, | ||||
|                     data: undefinedSelector, | ||||
|                 }), | ||||
|             ); | ||||
|         }); | ||||
|         describe('transferFrom', () => { | ||||
|             it('should successfully transfer tokens', async () => { | ||||
|                 // Construct ERC721 asset data | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { BlockchainLifecycle } from '@0xproject/dev-utils'; | ||||
| import { assetDataUtils, orderHashUtils } from '@0xproject/order-utils'; | ||||
| import { RevertReason, SignedOrder } from '@0xproject/types'; | ||||
| import { RevertReason, SignatureType, SignedOrder } from '@0xproject/types'; | ||||
| import { BigNumber } from '@0xproject/utils'; | ||||
| import { Web3Wrapper } from '@0xproject/web3-wrapper'; | ||||
| import * as chai from 'chai'; | ||||
| @@ -14,6 +14,8 @@ import { DummyNoReturnERC20TokenContract } from '../../generated_contract_wrappe | ||||
| import { ERC20ProxyContract } from '../../generated_contract_wrappers/erc20_proxy'; | ||||
| import { ERC721ProxyContract } from '../../generated_contract_wrappers/erc721_proxy'; | ||||
| import { ExchangeCancelEventArgs, ExchangeContract } from '../../generated_contract_wrappers/exchange'; | ||||
| import { ReentrantERC20TokenContract } from '../../generated_contract_wrappers/reentrant_erc20_token'; | ||||
| import { TestStaticCallReceiverContract } from '../../generated_contract_wrappers/test_static_call_receiver'; | ||||
| import { artifacts } from '../utils/artifacts'; | ||||
| import { expectTransactionFailedAsync } from '../utils/assertions'; | ||||
| import { getLatestBlockTimestampAsync, increaseTimeAndMineBlockAsync } from '../utils/block_timestamp'; | ||||
| @@ -41,9 +43,12 @@ describe('Exchange core', () => { | ||||
|     let zrxToken: DummyERC20TokenContract; | ||||
|     let erc721Token: DummyERC721TokenContract; | ||||
|     let noReturnErc20Token: DummyNoReturnERC20TokenContract; | ||||
|     let reentrantErc20Token: ReentrantERC20TokenContract; | ||||
|     let exchange: ExchangeContract; | ||||
|     let erc20Proxy: ERC20ProxyContract; | ||||
|     let erc721Proxy: ERC721ProxyContract; | ||||
|     let maliciousWallet: TestStaticCallReceiverContract; | ||||
|     let maliciousValidator: TestStaticCallReceiverContract; | ||||
|  | ||||
|     let signedOrder: SignedOrder; | ||||
|     let erc20Balances: ERC20BalancesByOwner; | ||||
| @@ -109,6 +114,18 @@ describe('Exchange core', () => { | ||||
|             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|         ); | ||||
|  | ||||
|         maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.TestStaticCallReceiver, | ||||
|             provider, | ||||
|             txDefaults, | ||||
|         ); | ||||
|         reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.ReentrantERC20Token, | ||||
|             provider, | ||||
|             txDefaults, | ||||
|             exchange.address, | ||||
|         ); | ||||
|  | ||||
|         defaultMakerAssetAddress = erc20TokenA.address; | ||||
|         defaultTakerAssetAddress = erc20TokenB.address; | ||||
|  | ||||
| @@ -135,6 +152,26 @@ describe('Exchange core', () => { | ||||
|             signedOrder = await orderFactory.newSignedOrderAsync(); | ||||
|         }); | ||||
|  | ||||
|         const reentrancyTest = (functionNames: string[]) => { | ||||
|             _.forEach(functionNames, async (functionName: string, functionId: number) => { | ||||
|                 const description = `should not allow fillOrder to reenter the Exchange contract via ${functionName}`; | ||||
|                 it(description, async () => { | ||||
|                     signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                         makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), | ||||
|                     }); | ||||
|                     await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                         await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), | ||||
|                         constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|                     ); | ||||
|                     await expectTransactionFailedAsync( | ||||
|                         exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), | ||||
|                         RevertReason.TransferFailed, | ||||
|                     ); | ||||
|                 }); | ||||
|             }); | ||||
|         }; | ||||
|         describe('fillOrder reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); | ||||
|  | ||||
|         it('should throw if signature is invalid', async () => { | ||||
|             signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                 makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), | ||||
| @@ -161,6 +198,51 @@ describe('Exchange core', () => { | ||||
|                 RevertReason.OrderUnfillable, | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('should revert if `isValidSignature` tries to update state when SignatureType=Wallet', async () => { | ||||
|             const maliciousMakerAddress = maliciousWallet.address; | ||||
|             await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                 await erc20TokenA.setBalance.sendTransactionAsync( | ||||
|                     maliciousMakerAddress, | ||||
|                     constants.INITIAL_ERC20_BALANCE, | ||||
|                 ), | ||||
|                 constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|             ); | ||||
|             await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                 await maliciousWallet.approveERC20.sendTransactionAsync( | ||||
|                     erc20TokenA.address, | ||||
|                     erc20Proxy.address, | ||||
|                     constants.INITIAL_ERC20_ALLOWANCE, | ||||
|                 ), | ||||
|                 constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|             ); | ||||
|             signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                 makerAddress: maliciousMakerAddress, | ||||
|                 makerFee: constants.ZERO_AMOUNT, | ||||
|             }); | ||||
|             signedOrder.signature = `0x0${SignatureType.Wallet}`; | ||||
|             await expectTransactionFailedAsync( | ||||
|                 exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), | ||||
|                 RevertReason.WalletError, | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('should revert if `isValidSignature` tries to update state when SignatureType=Validator', async () => { | ||||
|             const isApproved = true; | ||||
|             await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                 await exchange.setSignatureValidatorApproval.sendTransactionAsync( | ||||
|                     maliciousValidator.address, | ||||
|                     isApproved, | ||||
|                     { from: makerAddress }, | ||||
|                 ), | ||||
|                 constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|             ); | ||||
|             signedOrder.signature = `${maliciousValidator.address}0${SignatureType.Validator}`; | ||||
|             await expectTransactionFailedAsync( | ||||
|                 exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), | ||||
|                 RevertReason.ValidatorError, | ||||
|             ); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('Testing exchange of ERC20 tokens with no return values', () => { | ||||
| @@ -448,7 +530,7 @@ describe('Exchange core', () => { | ||||
|                 // HACK(albrow): We need to hardcode the gas estimate here because | ||||
|                 // the Geth gas estimator doesn't work with the way we use | ||||
|                 // delegatecall and swallow errors. | ||||
|                 gas: 490000, | ||||
|                 gas: 600000, | ||||
|             }); | ||||
|  | ||||
|             const newBalances = await erc20Wrapper.getBalancesAsync(); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { BlockchainLifecycle } from '@0xproject/dev-utils'; | ||||
| import { Order, RevertReason, SignedOrder } from '@0xproject/types'; | ||||
| import { BigNumber } from '@0xproject/utils'; | ||||
| import * as chai from 'chai'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { TestExchangeInternalsContract } from '../../generated_contract_wrappers/test_exchange_internals'; | ||||
| @@ -16,6 +17,8 @@ import { FillResults } from '../utils/types'; | ||||
| import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; | ||||
|  | ||||
| chaiSetup.configure(); | ||||
| const expect = chai.expect; | ||||
|  | ||||
| const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); | ||||
|  | ||||
| const MAX_UINT256 = new BigNumber(2).pow(256).minus(1); | ||||
| @@ -43,26 +46,11 @@ const emptySignedOrder: SignedOrder = { | ||||
|  | ||||
| const overflowErrorForCall = new Error(RevertReason.Uint256Overflow); | ||||
|  | ||||
| async function referenceGetPartialAmountAsync( | ||||
|     numerator: BigNumber, | ||||
|     denominator: BigNumber, | ||||
|     target: BigNumber, | ||||
| ): Promise<BigNumber> { | ||||
|     const invalidOpcodeErrorForCall = new Error(await getInvalidOpcodeErrorMessageForCallAsync()); | ||||
|     const product = numerator.mul(target); | ||||
|     if (product.greaterThan(MAX_UINT256)) { | ||||
|         throw overflowErrorForCall; | ||||
|     } | ||||
|     if (denominator.eq(0)) { | ||||
|         throw invalidOpcodeErrorForCall; | ||||
|     } | ||||
|     return product.dividedToIntegerBy(denominator); | ||||
| } | ||||
|  | ||||
| describe('Exchange core internal functions', () => { | ||||
|     let testExchange: TestExchangeInternalsContract; | ||||
|     let invalidOpcodeErrorForCall: Error | undefined; | ||||
|     let overflowErrorForSendTransaction: Error | undefined; | ||||
|     let divisionByZeroErrorForCall: Error | undefined; | ||||
|  | ||||
|     before(async () => { | ||||
|         await blockchainLifecycle.startAsync(); | ||||
| @@ -79,11 +67,29 @@ describe('Exchange core internal functions', () => { | ||||
|         overflowErrorForSendTransaction = new Error( | ||||
|             await getRevertReasonOrErrorMessageForSendTransactionAsync(RevertReason.Uint256Overflow), | ||||
|         ); | ||||
|         divisionByZeroErrorForCall = new Error( | ||||
|             await getRevertReasonOrErrorMessageForSendTransactionAsync(RevertReason.DivisionByZero), | ||||
|         ); | ||||
|         invalidOpcodeErrorForCall = new Error(await getInvalidOpcodeErrorMessageForCallAsync()); | ||||
|     }); | ||||
|     // Note(albrow): Don't forget to add beforeEach and afterEach calls to reset | ||||
|     // the blockchain state for any tests which modify it! | ||||
|  | ||||
|     async function referenceGetPartialAmountFloorAsync( | ||||
|         numerator: BigNumber, | ||||
|         denominator: BigNumber, | ||||
|         target: BigNumber, | ||||
|     ): Promise<BigNumber> { | ||||
|         if (denominator.eq(0)) { | ||||
|             throw divisionByZeroErrorForCall; | ||||
|         } | ||||
|         const product = numerator.mul(target); | ||||
|         if (product.greaterThan(MAX_UINT256)) { | ||||
|             throw overflowErrorForCall; | ||||
|         } | ||||
|         return product.dividedToIntegerBy(denominator); | ||||
|     } | ||||
|  | ||||
|     describe('addFillResults', async () => { | ||||
|         function makeFillResults(value: BigNumber): FillResults { | ||||
|             return { | ||||
| @@ -159,18 +165,18 @@ describe('Exchange core internal functions', () => { | ||||
|             // implementation or the Solidity implementation of | ||||
|             // calculateFillResults. | ||||
|             return { | ||||
|                 makerAssetFilledAmount: await referenceGetPartialAmountAsync( | ||||
|                 makerAssetFilledAmount: await referenceGetPartialAmountFloorAsync( | ||||
|                     takerAssetFilledAmount, | ||||
|                     orderTakerAssetAmount, | ||||
|                     otherAmount, | ||||
|                 ), | ||||
|                 takerAssetFilledAmount, | ||||
|                 makerFeePaid: await referenceGetPartialAmountAsync( | ||||
|                 makerFeePaid: await referenceGetPartialAmountFloorAsync( | ||||
|                     takerAssetFilledAmount, | ||||
|                     orderTakerAssetAmount, | ||||
|                     otherAmount, | ||||
|                 ), | ||||
|                 takerFeePaid: await referenceGetPartialAmountAsync( | ||||
|                 takerFeePaid: await referenceGetPartialAmountFloorAsync( | ||||
|                     takerAssetFilledAmount, | ||||
|                     orderTakerAssetAmount, | ||||
|                     otherAmount, | ||||
| @@ -193,18 +199,55 @@ describe('Exchange core internal functions', () => { | ||||
|         ); | ||||
|     }); | ||||
|  | ||||
|     describe('getPartialAmount', async () => { | ||||
|         async function testGetPartialAmountAsync( | ||||
|     describe('getPartialAmountFloor', async () => { | ||||
|         async function testGetPartialAmountFloorAsync( | ||||
|             numerator: BigNumber, | ||||
|             denominator: BigNumber, | ||||
|             target: BigNumber, | ||||
|         ): Promise<BigNumber> { | ||||
|             return testExchange.publicGetPartialAmount.callAsync(numerator, denominator, target); | ||||
|             return testExchange.publicGetPartialAmountFloor.callAsync(numerator, denominator, target); | ||||
|         } | ||||
|         await testCombinatoriallyWithReferenceFuncAsync( | ||||
|             'getPartialAmount', | ||||
|             referenceGetPartialAmountAsync, | ||||
|             testGetPartialAmountAsync, | ||||
|             referenceGetPartialAmountFloorAsync, | ||||
|             testGetPartialAmountFloorAsync, | ||||
|             [uint256Values, uint256Values, uint256Values], | ||||
|         ); | ||||
|     }); | ||||
|  | ||||
|     describe('getPartialAmountCeil', async () => { | ||||
|         async function referenceGetPartialAmountCeilAsync( | ||||
|             numerator: BigNumber, | ||||
|             denominator: BigNumber, | ||||
|             target: BigNumber, | ||||
|         ): Promise<BigNumber> { | ||||
|             if (denominator.eq(0)) { | ||||
|                 throw divisionByZeroErrorForCall; | ||||
|             } | ||||
|             const product = numerator.mul(target); | ||||
|             const offset = product.add(denominator.sub(1)); | ||||
|             if (offset.greaterThan(MAX_UINT256)) { | ||||
|                 throw overflowErrorForCall; | ||||
|             } | ||||
|             const result = offset.dividedToIntegerBy(denominator); | ||||
|             if (product.mod(denominator).eq(0)) { | ||||
|                 expect(result.mul(denominator)).to.be.bignumber.eq(product); | ||||
|             } else { | ||||
|                 expect(result.mul(denominator)).to.be.bignumber.gt(product); | ||||
|             } | ||||
|             return result; | ||||
|         } | ||||
|         async function testGetPartialAmountCeilAsync( | ||||
|             numerator: BigNumber, | ||||
|             denominator: BigNumber, | ||||
|             target: BigNumber, | ||||
|         ): Promise<BigNumber> { | ||||
|             return testExchange.publicGetPartialAmountCeil.callAsync(numerator, denominator, target); | ||||
|         } | ||||
|         await testCombinatoriallyWithReferenceFuncAsync( | ||||
|             'getPartialAmountCeil', | ||||
|             referenceGetPartialAmountCeilAsync, | ||||
|             testGetPartialAmountCeilAsync, | ||||
|             [uint256Values, uint256Values, uint256Values], | ||||
|         ); | ||||
|     }); | ||||
| @@ -215,33 +258,33 @@ describe('Exchange core internal functions', () => { | ||||
|             denominator: BigNumber, | ||||
|             target: BigNumber, | ||||
|         ): Promise<boolean> { | ||||
|             const product = numerator.mul(target); | ||||
|             if (denominator.eq(0)) { | ||||
|                 throw invalidOpcodeErrorForCall; | ||||
|                 throw divisionByZeroErrorForCall; | ||||
|             } | ||||
|             const remainder = product.mod(denominator); | ||||
|             if (remainder.eq(0)) { | ||||
|             if (numerator.eq(0)) { | ||||
|                 return false; | ||||
|             } | ||||
|             if (target.eq(0)) { | ||||
|                 return false; | ||||
|             } | ||||
|             const product = numerator.mul(target); | ||||
|             const remainder = product.mod(denominator); | ||||
|             const remainderTimes1000 = remainder.mul('1000'); | ||||
|             const isError = remainderTimes1000.gt(product); | ||||
|             if (product.greaterThan(MAX_UINT256)) { | ||||
|                 throw overflowErrorForCall; | ||||
|             } | ||||
|             if (product.eq(0)) { | ||||
|                 throw invalidOpcodeErrorForCall; | ||||
|             } | ||||
|             const remainderTimes1000000 = remainder.mul('1000000'); | ||||
|             if (remainderTimes1000000.greaterThan(MAX_UINT256)) { | ||||
|             if (remainderTimes1000.greaterThan(MAX_UINT256)) { | ||||
|                 throw overflowErrorForCall; | ||||
|             } | ||||
|             const errPercentageTimes1000000 = remainderTimes1000000.dividedToIntegerBy(product); | ||||
|             return errPercentageTimes1000000.greaterThan('1000'); | ||||
|             return isError; | ||||
|         } | ||||
|         async function testIsRoundingErrorAsync( | ||||
|             numerator: BigNumber, | ||||
|             denominator: BigNumber, | ||||
|             target: BigNumber, | ||||
|         ): Promise<boolean> { | ||||
|             return testExchange.publicIsRoundingError.callAsync(numerator, denominator, target); | ||||
|             return testExchange.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target); | ||||
|         } | ||||
|         await testCombinatoriallyWithReferenceFuncAsync( | ||||
|             'isRoundingError', | ||||
| @@ -251,6 +294,49 @@ describe('Exchange core internal functions', () => { | ||||
|         ); | ||||
|     }); | ||||
|  | ||||
|     describe('isRoundingErrorCeil', async () => { | ||||
|         async function referenceIsRoundingErrorAsync( | ||||
|             numerator: BigNumber, | ||||
|             denominator: BigNumber, | ||||
|             target: BigNumber, | ||||
|         ): Promise<boolean> { | ||||
|             if (denominator.eq(0)) { | ||||
|                 throw divisionByZeroErrorForCall; | ||||
|             } | ||||
|             if (numerator.eq(0)) { | ||||
|                 return false; | ||||
|             } | ||||
|             if (target.eq(0)) { | ||||
|                 return false; | ||||
|             } | ||||
|             const product = numerator.mul(target); | ||||
|             const remainder = product.mod(denominator); | ||||
|             const error = denominator.sub(remainder).mod(denominator); | ||||
|             const errorTimes1000 = error.mul('1000'); | ||||
|             const isError = errorTimes1000.gt(product); | ||||
|             if (product.greaterThan(MAX_UINT256)) { | ||||
|                 throw overflowErrorForCall; | ||||
|             } | ||||
|             if (errorTimes1000.greaterThan(MAX_UINT256)) { | ||||
|                 throw overflowErrorForCall; | ||||
|             } | ||||
|             return isError; | ||||
|         } | ||||
|         async function testIsRoundingErrorCeilAsync( | ||||
|             numerator: BigNumber, | ||||
|             denominator: BigNumber, | ||||
|             target: BigNumber, | ||||
|         ): Promise<boolean> { | ||||
|             return testExchange.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target); | ||||
|         } | ||||
|         await testCombinatoriallyWithReferenceFuncAsync( | ||||
|             'isRoundingErrorCeil', | ||||
|             referenceIsRoundingErrorAsync, | ||||
|             testIsRoundingErrorCeilAsync, | ||||
|             [uint256Values, uint256Values, uint256Values], | ||||
|         ); | ||||
|     }); | ||||
|  | ||||
|     describe('updateFilledState', async () => { | ||||
|         // Note(albrow): Since updateFilledState modifies the state by calling | ||||
|         // sendTransaction, we must reset the state after each test. | ||||
|   | ||||
| @@ -71,29 +71,57 @@ describe('Exchange libs', () => { | ||||
|     // combinatorial tests in test/exchange/internal. They test specific edge | ||||
|     // cases that are not covered by the combinatorial tests. | ||||
|     describe('LibMath', () => { | ||||
|         it('should return false if there is a rounding error of 0.1%', async () => { | ||||
|             const numerator = new BigNumber(20); | ||||
|             const denominator = new BigNumber(999); | ||||
|             const target = new BigNumber(50); | ||||
|             // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1% | ||||
|             const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target); | ||||
|             expect(isRoundingError).to.be.false(); | ||||
|         describe('isRoundingError', () => { | ||||
|             it('should return true if there is a rounding error of 0.1%', async () => { | ||||
|                 const numerator = new BigNumber(20); | ||||
|                 const denominator = new BigNumber(999); | ||||
|                 const target = new BigNumber(50); | ||||
|                 // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1% | ||||
|                 const isRoundingError = await libs.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target); | ||||
|                 expect(isRoundingError).to.be.true(); | ||||
|             }); | ||||
|             it('should return false if there is a rounding of 0.09%', async () => { | ||||
|                 const numerator = new BigNumber(20); | ||||
|                 const denominator = new BigNumber(9991); | ||||
|                 const target = new BigNumber(500); | ||||
|                 // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09% | ||||
|                 const isRoundingError = await libs.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target); | ||||
|                 expect(isRoundingError).to.be.false(); | ||||
|             }); | ||||
|             it('should return true if there is a rounding error of 0.11%', async () => { | ||||
|                 const numerator = new BigNumber(20); | ||||
|                 const denominator = new BigNumber(9989); | ||||
|                 const target = new BigNumber(500); | ||||
|                 // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011% | ||||
|                 const isRoundingError = await libs.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target); | ||||
|                 expect(isRoundingError).to.be.true(); | ||||
|             }); | ||||
|         }); | ||||
|         it('should return false if there is a rounding of 0.09%', async () => { | ||||
|             const numerator = new BigNumber(20); | ||||
|             const denominator = new BigNumber(9991); | ||||
|             const target = new BigNumber(500); | ||||
|             // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09% | ||||
|             const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target); | ||||
|             expect(isRoundingError).to.be.false(); | ||||
|         }); | ||||
|         it('should return true if there is a rounding error of 0.11%', async () => { | ||||
|             const numerator = new BigNumber(20); | ||||
|             const denominator = new BigNumber(9989); | ||||
|             const target = new BigNumber(500); | ||||
|             // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011% | ||||
|             const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target); | ||||
|             expect(isRoundingError).to.be.true(); | ||||
|         describe('isRoundingErrorCeil', () => { | ||||
|             it('should return true if there is a rounding error of 0.1%', async () => { | ||||
|                 const numerator = new BigNumber(20); | ||||
|                 const denominator = new BigNumber(1001); | ||||
|                 const target = new BigNumber(50); | ||||
|                 // rounding error = (ceil(20*50/1001) - (20*50/1001)) / (20*50/1001) = 0.1% | ||||
|                 const isRoundingError = await libs.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target); | ||||
|                 expect(isRoundingError).to.be.true(); | ||||
|             }); | ||||
|             it('should return false if there is a rounding of 0.09%', async () => { | ||||
|                 const numerator = new BigNumber(20); | ||||
|                 const denominator = new BigNumber(10009); | ||||
|                 const target = new BigNumber(500); | ||||
|                 // rounding error = (ceil(20*500/10009) - (20*500/10009)) / (20*500/10009) = 0.09% | ||||
|                 const isRoundingError = await libs.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target); | ||||
|                 expect(isRoundingError).to.be.false(); | ||||
|             }); | ||||
|             it('should return true if there is a rounding error of 0.11%', async () => { | ||||
|                 const numerator = new BigNumber(20); | ||||
|                 const denominator = new BigNumber(10011); | ||||
|                 const target = new BigNumber(500); | ||||
|                 // rounding error = (ceil(20*500/10011) - (20*500/10011)) / (20*500/10011) = 0.11% | ||||
|                 const isRoundingError = await libs.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target); | ||||
|                 expect(isRoundingError).to.be.true(); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -9,11 +9,12 @@ import { | ||||
|     TestSignatureValidatorContract, | ||||
|     TestSignatureValidatorSignatureValidatorApprovalEventArgs, | ||||
| } from '../../generated_contract_wrappers/test_signature_validator'; | ||||
| import { TestStaticCallReceiverContract } from '../../generated_contract_wrappers/test_static_call_receiver'; | ||||
| import { ValidatorContract } from '../../generated_contract_wrappers/validator'; | ||||
| import { WalletContract } from '../../generated_contract_wrappers/wallet'; | ||||
| import { addressUtils } from '../utils/address_utils'; | ||||
| import { artifacts } from '../utils/artifacts'; | ||||
| import { expectContractCallFailed } from '../utils/assertions'; | ||||
| import { expectContractCallFailed, expectContractCallFailedWithoutReasonAsync } from '../utils/assertions'; | ||||
| import { chaiSetup } from '../utils/chai_setup'; | ||||
| import { constants } from '../utils/constants'; | ||||
| import { LogDecoder } from '../utils/log_decoder'; | ||||
| @@ -31,6 +32,8 @@ describe('MixinSignatureValidator', () => { | ||||
|     let signatureValidator: TestSignatureValidatorContract; | ||||
|     let testWallet: WalletContract; | ||||
|     let testValidator: ValidatorContract; | ||||
|     let maliciousWallet: TestStaticCallReceiverContract; | ||||
|     let maliciousValidator: TestStaticCallReceiverContract; | ||||
|     let signerAddress: string; | ||||
|     let signerPrivateKey: Buffer; | ||||
|     let notSignerAddress: string; | ||||
| @@ -65,6 +68,11 @@ describe('MixinSignatureValidator', () => { | ||||
|             txDefaults, | ||||
|             signerAddress, | ||||
|         ); | ||||
|         maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.TestStaticCallReceiver, | ||||
|             provider, | ||||
|             txDefaults, | ||||
|         ); | ||||
|         signatureValidatorLogDecoder = new LogDecoder(web3Wrapper); | ||||
|         await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|             await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(testValidator.address, true, { | ||||
| @@ -72,6 +80,16 @@ describe('MixinSignatureValidator', () => { | ||||
|             }), | ||||
|             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|         ); | ||||
|         await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|             await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( | ||||
|                 maliciousValidator.address, | ||||
|                 true, | ||||
|                 { | ||||
|                     from: signerAddress, | ||||
|                 }, | ||||
|             ), | ||||
|             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|         ); | ||||
|  | ||||
|         const defaultOrderParams = { | ||||
|             ...constants.STATIC_ORDER_PARAMS, | ||||
| @@ -263,32 +281,6 @@ describe('MixinSignatureValidator', () => { | ||||
|             expect(isValidSignature).to.be.false(); | ||||
|         }); | ||||
|  | ||||
|         it('should return true when SignatureType=Caller and signer is caller', async () => { | ||||
|             const signature = ethUtil.toBuffer(`0x${SignatureType.Caller}`); | ||||
|             const signatureHex = ethUtil.bufferToHex(signature); | ||||
|             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); | ||||
|             const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( | ||||
|                 orderHashHex, | ||||
|                 signerAddress, | ||||
|                 signatureHex, | ||||
|                 { from: signerAddress }, | ||||
|             ); | ||||
|             expect(isValidSignature).to.be.true(); | ||||
|         }); | ||||
|  | ||||
|         it('should return false when SignatureType=Caller and signer is not caller', async () => { | ||||
|             const signature = ethUtil.toBuffer(`0x${SignatureType.Caller}`); | ||||
|             const signatureHex = ethUtil.bufferToHex(signature); | ||||
|             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); | ||||
|             const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( | ||||
|                 orderHashHex, | ||||
|                 signerAddress, | ||||
|                 signatureHex, | ||||
|                 { from: notSignerAddress }, | ||||
|             ); | ||||
|             expect(isValidSignature).to.be.false(); | ||||
|         }); | ||||
|  | ||||
|         it('should return true when SignatureType=Wallet and signature is valid', async () => { | ||||
|             // Create EIP712 signature | ||||
|             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); | ||||
| @@ -334,6 +326,29 @@ describe('MixinSignatureValidator', () => { | ||||
|             expect(isValidSignature).to.be.false(); | ||||
|         }); | ||||
|  | ||||
|         it('should revert when `isValidSignature` attempts to update state and SignatureType=Wallet', async () => { | ||||
|             // Create EIP712 signature | ||||
|             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); | ||||
|             const orderHashBuffer = ethUtil.toBuffer(orderHashHex); | ||||
|             const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); | ||||
|             // Create 0x signature from EIP712 signature | ||||
|             const signature = Buffer.concat([ | ||||
|                 ethUtil.toBuffer(ecSignature.v), | ||||
|                 ecSignature.r, | ||||
|                 ecSignature.s, | ||||
|                 ethUtil.toBuffer(`0x${SignatureType.Wallet}`), | ||||
|             ]); | ||||
|             const signatureHex = ethUtil.bufferToHex(signature); | ||||
|             await expectContractCallFailed( | ||||
|                 signatureValidator.publicIsValidSignature.callAsync( | ||||
|                     orderHashHex, | ||||
|                     maliciousWallet.address, | ||||
|                     signatureHex, | ||||
|                 ), | ||||
|                 RevertReason.WalletError, | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('should return true when SignatureType=Validator, signature is valid and validator is approved', async () => { | ||||
|             const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); | ||||
|             const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); | ||||
| @@ -364,6 +379,17 @@ describe('MixinSignatureValidator', () => { | ||||
|             expect(isValidSignature).to.be.false(); | ||||
|         }); | ||||
|  | ||||
|         it('should revert when `isValidSignature` attempts to update state and SignatureType=Validator', async () => { | ||||
|             const validatorAddress = ethUtil.toBuffer(`${maliciousValidator.address}`); | ||||
|             const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); | ||||
|             const signature = Buffer.concat([validatorAddress, signatureType]); | ||||
|             const signatureHex = ethUtil.bufferToHex(signature); | ||||
|             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); | ||||
|             await expectContractCallFailed( | ||||
|                 signatureValidator.publicIsValidSignature.callAsync(orderHashHex, signerAddress, signatureHex), | ||||
|                 RevertReason.ValidatorError, | ||||
|             ); | ||||
|         }); | ||||
|         it('should return false when SignatureType=Validator, signature is valid and validator is not approved', async () => { | ||||
|             // Set approval of signature validator to false | ||||
|             await web3Wrapper.awaitTransactionSuccessAsync( | ||||
| @@ -388,53 +414,6 @@ describe('MixinSignatureValidator', () => { | ||||
|             expect(isValidSignature).to.be.false(); | ||||
|         }); | ||||
|  | ||||
|         it('should return true when SignatureType=Trezor and signature is valid', async () => { | ||||
|             // Create Trezor signature | ||||
|             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); | ||||
|             const orderHashWithTrezorPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex, SignerType.Trezor); | ||||
|             const orderHashWithTrezorPrefixBuffer = ethUtil.toBuffer(orderHashWithTrezorPrefixHex); | ||||
|             const ecSignature = ethUtil.ecsign(orderHashWithTrezorPrefixBuffer, signerPrivateKey); | ||||
|             // Create 0x signature from Trezor signature | ||||
|             const signature = Buffer.concat([ | ||||
|                 ethUtil.toBuffer(ecSignature.v), | ||||
|                 ecSignature.r, | ||||
|                 ecSignature.s, | ||||
|                 ethUtil.toBuffer(`0x${SignatureType.Trezor}`), | ||||
|             ]); | ||||
|             const signatureHex = ethUtil.bufferToHex(signature); | ||||
|             // Validate signature | ||||
|             const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( | ||||
|                 orderHashHex, | ||||
|                 signerAddress, | ||||
|                 signatureHex, | ||||
|             ); | ||||
|             expect(isValidSignature).to.be.true(); | ||||
|         }); | ||||
|  | ||||
|         it('should return false when SignatureType=Trezor and signature is invalid', async () => { | ||||
|             // Create Trezor signature | ||||
|             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); | ||||
|             const orderHashWithTrezorPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex, SignerType.Trezor); | ||||
|             const orderHashWithTrezorPrefixBuffer = ethUtil.toBuffer(orderHashWithTrezorPrefixHex); | ||||
|             const ecSignature = ethUtil.ecsign(orderHashWithTrezorPrefixBuffer, signerPrivateKey); | ||||
|             // Create 0x signature from Trezor signature | ||||
|             const signature = Buffer.concat([ | ||||
|                 ethUtil.toBuffer(ecSignature.v), | ||||
|                 ecSignature.r, | ||||
|                 ecSignature.s, | ||||
|                 ethUtil.toBuffer(`0x${SignatureType.Trezor}`), | ||||
|             ]); | ||||
|             const signatureHex = ethUtil.bufferToHex(signature); | ||||
|             // Validate signature. | ||||
|             // This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress` | ||||
|             const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( | ||||
|                 orderHashHex, | ||||
|                 notSignerAddress, | ||||
|                 signatureHex, | ||||
|             ); | ||||
|             expect(isValidSignature).to.be.false(); | ||||
|         }); | ||||
|  | ||||
|         it('should return true when SignatureType=Presigned and signer has presigned hash', async () => { | ||||
|             // Presign hash | ||||
|             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); | ||||
| @@ -468,6 +447,42 @@ describe('MixinSignatureValidator', () => { | ||||
|             ); | ||||
|             expect(isValidSignature).to.be.false(); | ||||
|         }); | ||||
|  | ||||
|         it('should return true when message was signed by a Trezor One (firmware version 1.6.2)', async () => { | ||||
|             // messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b | ||||
|             const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++')); | ||||
|             const signer = '0xc28b145f10f0bcf0fc000e778615f8fd73490bad'; | ||||
|             const v = ethUtil.toBuffer('0x1c'); | ||||
|             const r = ethUtil.toBuffer('0x7b888b596ccf87f0bacab0dcb483124973f7420f169b4824d7a12534ac1e9832'); | ||||
|             const s = ethUtil.toBuffer('0x0c8e14f7edc01459e13965f1da56e0c23ed11e2cca932571eee1292178f90424'); | ||||
|             const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); | ||||
|             const signature = Buffer.concat([v, r, s, trezorSignatureType]); | ||||
|             const signatureHex = ethUtil.bufferToHex(signature); | ||||
|             const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( | ||||
|                 messageHash, | ||||
|                 signer, | ||||
|                 signatureHex, | ||||
|             ); | ||||
|             expect(isValidSignature).to.be.true(); | ||||
|         }); | ||||
|  | ||||
|         it('should return true when message was signed by a Trezor Model T (firmware version 2.0.7)', async () => { | ||||
|             // messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b | ||||
|             const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++')); | ||||
|             const signer = '0x98ce6d9345e8ffa7d99ee0822272fae9d2c0e895'; | ||||
|             const v = ethUtil.toBuffer('0x1c'); | ||||
|             const r = ethUtil.toBuffer('0x423b71062c327f0ec4fe199b8da0f34185e59b4c1cb4cc23df86cac4a601fb3f'); | ||||
|             const s = ethUtil.toBuffer('0x53810d6591b5348b7ee08ee812c874b0fdfb942c9849d59512c90e295221091f'); | ||||
|             const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); | ||||
|             const signature = Buffer.concat([v, r, s, trezorSignatureType]); | ||||
|             const signatureHex = ethUtil.bufferToHex(signature); | ||||
|             const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( | ||||
|                 messageHash, | ||||
|                 signer, | ||||
|                 signatureHex, | ||||
|             ); | ||||
|             expect(isValidSignature).to.be.true(); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('setSignatureValidatorApproval', () => { | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import { DummyERC721TokenContract } from '../../generated_contract_wrappers/dumm | ||||
| import { ERC20ProxyContract } from '../../generated_contract_wrappers/erc20_proxy'; | ||||
| import { ERC721ProxyContract } from '../../generated_contract_wrappers/erc721_proxy'; | ||||
| import { ExchangeContract } from '../../generated_contract_wrappers/exchange'; | ||||
| import { ReentrantERC20TokenContract } from '../../generated_contract_wrappers/reentrant_erc20_token'; | ||||
| import { artifacts } from '../utils/artifacts'; | ||||
| import { expectTransactionFailedAsync } from '../utils/assertions'; | ||||
| import { getLatestBlockTimestampAsync, increaseTimeAndMineBlockAsync } from '../utils/block_timestamp'; | ||||
| @@ -40,6 +41,7 @@ describe('Exchange wrappers', () => { | ||||
|     let exchange: ExchangeContract; | ||||
|     let erc20Proxy: ERC20ProxyContract; | ||||
|     let erc721Proxy: ERC721ProxyContract; | ||||
|     let reentrantErc20Token: ReentrantERC20TokenContract; | ||||
|  | ||||
|     let exchangeWrapper: ExchangeWrapper; | ||||
|     let erc20Wrapper: ERC20Wrapper; | ||||
| @@ -104,6 +106,13 @@ describe('Exchange wrappers', () => { | ||||
|             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|         ); | ||||
|  | ||||
|         reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.ReentrantERC20Token, | ||||
|             provider, | ||||
|             txDefaults, | ||||
|             exchange.address, | ||||
|         ); | ||||
|  | ||||
|         defaultMakerAssetAddress = erc20TokenA.address; | ||||
|         defaultTakerAssetAddress = erc20TokenB.address; | ||||
|  | ||||
| @@ -126,6 +135,26 @@ describe('Exchange wrappers', () => { | ||||
|         await blockchainLifecycle.revertAsync(); | ||||
|     }); | ||||
|     describe('fillOrKillOrder', () => { | ||||
|         const reentrancyTest = (functionNames: string[]) => { | ||||
|             _.forEach(functionNames, async (functionName: string, functionId: number) => { | ||||
|                 const description = `should not allow fillOrKillOrder to reenter the Exchange contract via ${functionName}`; | ||||
|                 it(description, async () => { | ||||
|                     const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                         makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), | ||||
|                     }); | ||||
|                     await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                         await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), | ||||
|                         constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|                     ); | ||||
|                     await expectTransactionFailedAsync( | ||||
|                         exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), | ||||
|                         RevertReason.TransferFailed, | ||||
|                     ); | ||||
|                 }); | ||||
|             }); | ||||
|         }; | ||||
|         describe('fillOrKillOrder reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); | ||||
|  | ||||
|         it('should transfer the correct amounts', async () => { | ||||
|             const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                 makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), | ||||
| @@ -197,6 +226,25 @@ describe('Exchange wrappers', () => { | ||||
|     }); | ||||
|  | ||||
|     describe('fillOrderNoThrow', () => { | ||||
|         const reentrancyTest = (functionNames: string[]) => { | ||||
|             _.forEach(functionNames, async (functionName: string, functionId: number) => { | ||||
|                 const description = `should not allow fillOrderNoThrow to reenter the Exchange contract via ${functionName}`; | ||||
|                 it(description, async () => { | ||||
|                     const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                         makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), | ||||
|                     }); | ||||
|                     await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                         await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), | ||||
|                         constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|                     ); | ||||
|                     await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); | ||||
|                     const newBalances = await erc20Wrapper.getBalancesAsync(); | ||||
|                     expect(erc20Balances).to.deep.equal(newBalances); | ||||
|                 }); | ||||
|             }); | ||||
|         }; | ||||
|         describe('fillOrderNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); | ||||
|  | ||||
|         it('should transfer the correct amounts', async () => { | ||||
|             const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                 makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), | ||||
| @@ -397,6 +445,26 @@ describe('Exchange wrappers', () => { | ||||
|         }); | ||||
|  | ||||
|         describe('batchFillOrders', () => { | ||||
|             const reentrancyTest = (functionNames: string[]) => { | ||||
|                 _.forEach(functionNames, async (functionName: string, functionId: number) => { | ||||
|                     const description = `should not allow batchFillOrders to reenter the Exchange contract via ${functionName}`; | ||||
|                     it(description, async () => { | ||||
|                         const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                             makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), | ||||
|                         }); | ||||
|                         await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                             await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), | ||||
|                             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|                         ); | ||||
|                         await expectTransactionFailedAsync( | ||||
|                             exchangeWrapper.batchFillOrdersAsync([signedOrder], takerAddress), | ||||
|                             RevertReason.TransferFailed, | ||||
|                         ); | ||||
|                     }); | ||||
|                 }); | ||||
|             }; | ||||
|             describe('batchFillOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); | ||||
|  | ||||
|             it('should transfer the correct amounts', async () => { | ||||
|                 const takerAssetFillAmounts: BigNumber[] = []; | ||||
|                 const makerAssetAddress = erc20TokenA.address; | ||||
| @@ -446,6 +514,26 @@ describe('Exchange wrappers', () => { | ||||
|         }); | ||||
|  | ||||
|         describe('batchFillOrKillOrders', () => { | ||||
|             const reentrancyTest = (functionNames: string[]) => { | ||||
|                 _.forEach(functionNames, async (functionName: string, functionId: number) => { | ||||
|                     const description = `should not allow batchFillOrKillOrders to reenter the Exchange contract via ${functionName}`; | ||||
|                     it(description, async () => { | ||||
|                         const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                             makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), | ||||
|                         }); | ||||
|                         await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                             await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), | ||||
|                             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|                         ); | ||||
|                         await expectTransactionFailedAsync( | ||||
|                             exchangeWrapper.batchFillOrKillOrdersAsync([signedOrder], takerAddress), | ||||
|                             RevertReason.TransferFailed, | ||||
|                         ); | ||||
|                     }); | ||||
|                 }); | ||||
|             }; | ||||
|             describe('batchFillOrKillOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); | ||||
|  | ||||
|             it('should transfer the correct amounts', async () => { | ||||
|                 const takerAssetFillAmounts: BigNumber[] = []; | ||||
|                 const makerAssetAddress = erc20TokenA.address; | ||||
| @@ -512,6 +600,25 @@ describe('Exchange wrappers', () => { | ||||
|         }); | ||||
|  | ||||
|         describe('batchFillOrdersNoThrow', async () => { | ||||
|             const reentrancyTest = (functionNames: string[]) => { | ||||
|                 _.forEach(functionNames, async (functionName: string, functionId: number) => { | ||||
|                     const description = `should not allow batchFillOrdersNoThrow to reenter the Exchange contract via ${functionName}`; | ||||
|                     it(description, async () => { | ||||
|                         const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                             makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), | ||||
|                         }); | ||||
|                         await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                             await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), | ||||
|                             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|                         ); | ||||
|                         await exchangeWrapper.batchFillOrdersNoThrowAsync([signedOrder], takerAddress); | ||||
|                         const newBalances = await erc20Wrapper.getBalancesAsync(); | ||||
|                         expect(erc20Balances).to.deep.equal(newBalances); | ||||
|                     }); | ||||
|                 }); | ||||
|             }; | ||||
|             describe('batchFillOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); | ||||
|  | ||||
|             it('should transfer the correct amounts', async () => { | ||||
|                 const takerAssetFillAmounts: BigNumber[] = []; | ||||
|                 const makerAssetAddress = erc20TokenA.address; | ||||
| @@ -625,6 +732,28 @@ describe('Exchange wrappers', () => { | ||||
|         }); | ||||
|  | ||||
|         describe('marketSellOrders', () => { | ||||
|             const reentrancyTest = (functionNames: string[]) => { | ||||
|                 _.forEach(functionNames, async (functionName: string, functionId: number) => { | ||||
|                     const description = `should not allow marketSellOrders to reenter the Exchange contract via ${functionName}`; | ||||
|                     it(description, async () => { | ||||
|                         const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                             makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), | ||||
|                         }); | ||||
|                         await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                             await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), | ||||
|                             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|                         ); | ||||
|                         await expectTransactionFailedAsync( | ||||
|                             exchangeWrapper.marketSellOrdersAsync([signedOrder], takerAddress, { | ||||
|                                 takerAssetFillAmount: signedOrder.takerAssetAmount, | ||||
|                             }), | ||||
|                             RevertReason.TransferFailed, | ||||
|                         ); | ||||
|                     }); | ||||
|                 }); | ||||
|             }; | ||||
|             describe('marketSellOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); | ||||
|  | ||||
|             it('should stop when the entire takerAssetFillAmount is filled', async () => { | ||||
|                 const takerAssetFillAmount = signedOrders[0].takerAssetAmount.plus( | ||||
|                     signedOrders[1].takerAssetAmount.div(2), | ||||
| @@ -717,6 +846,27 @@ describe('Exchange wrappers', () => { | ||||
|         }); | ||||
|  | ||||
|         describe('marketSellOrdersNoThrow', () => { | ||||
|             const reentrancyTest = (functionNames: string[]) => { | ||||
|                 _.forEach(functionNames, async (functionName: string, functionId: number) => { | ||||
|                     const description = `should not allow marketSellOrdersNoThrow to reenter the Exchange contract via ${functionName}`; | ||||
|                     it(description, async () => { | ||||
|                         const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                             makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), | ||||
|                         }); | ||||
|                         await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                             await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), | ||||
|                             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|                         ); | ||||
|                         await exchangeWrapper.marketSellOrdersNoThrowAsync([signedOrder], takerAddress, { | ||||
|                             takerAssetFillAmount: signedOrder.takerAssetAmount, | ||||
|                         }); | ||||
|                         const newBalances = await erc20Wrapper.getBalancesAsync(); | ||||
|                         expect(erc20Balances).to.deep.equal(newBalances); | ||||
|                     }); | ||||
|                 }); | ||||
|             }; | ||||
|             describe('marketSellOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); | ||||
|  | ||||
|             it('should stop when the entire takerAssetFillAmount is filled', async () => { | ||||
|                 const takerAssetFillAmount = signedOrders[0].takerAssetAmount.plus( | ||||
|                     signedOrders[1].takerAssetAmount.div(2), | ||||
| @@ -843,6 +993,28 @@ describe('Exchange wrappers', () => { | ||||
|         }); | ||||
|  | ||||
|         describe('marketBuyOrders', () => { | ||||
|             const reentrancyTest = (functionNames: string[]) => { | ||||
|                 _.forEach(functionNames, async (functionName: string, functionId: number) => { | ||||
|                     const description = `should not allow marketBuyOrders to reenter the Exchange contract via ${functionName}`; | ||||
|                     it(description, async () => { | ||||
|                         const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                             makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), | ||||
|                         }); | ||||
|                         await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                             await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), | ||||
|                             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|                         ); | ||||
|                         await expectTransactionFailedAsync( | ||||
|                             exchangeWrapper.marketBuyOrdersAsync([signedOrder], takerAddress, { | ||||
|                                 makerAssetFillAmount: signedOrder.makerAssetAmount, | ||||
|                             }), | ||||
|                             RevertReason.TransferFailed, | ||||
|                         ); | ||||
|                     }); | ||||
|                 }); | ||||
|             }; | ||||
|             describe('marketBuyOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); | ||||
|  | ||||
|             it('should stop when the entire makerAssetFillAmount is filled', async () => { | ||||
|                 const makerAssetFillAmount = signedOrders[0].makerAssetAmount.plus( | ||||
|                     signedOrders[1].makerAssetAmount.div(2), | ||||
| @@ -933,6 +1105,27 @@ describe('Exchange wrappers', () => { | ||||
|         }); | ||||
|  | ||||
|         describe('marketBuyOrdersNoThrow', () => { | ||||
|             const reentrancyTest = (functionNames: string[]) => { | ||||
|                 _.forEach(functionNames, async (functionName: string, functionId: number) => { | ||||
|                     const description = `should not allow marketBuyOrdersNoThrow to reenter the Exchange contract via ${functionName}`; | ||||
|                     it(description, async () => { | ||||
|                         const signedOrder = await orderFactory.newSignedOrderAsync({ | ||||
|                             makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), | ||||
|                         }); | ||||
|                         await web3Wrapper.awaitTransactionSuccessAsync( | ||||
|                             await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), | ||||
|                             constants.AWAIT_TRANSACTION_MINED_MS, | ||||
|                         ); | ||||
|                         await exchangeWrapper.marketBuyOrdersNoThrowAsync([signedOrder], takerAddress, { | ||||
|                             makerAssetFillAmount: signedOrder.makerAssetAmount, | ||||
|                         }); | ||||
|                         const newBalances = await erc20Wrapper.getBalancesAsync(); | ||||
|                         expect(erc20Balances).to.deep.equal(newBalances); | ||||
|                     }); | ||||
|                 }); | ||||
|             }; | ||||
|             describe('marketBuyOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); | ||||
|  | ||||
|             it('should stop when the entire makerAssetFillAmount is filled', async () => { | ||||
|                 const makerAssetFillAmount = signedOrders[0].makerAssetAmount.plus( | ||||
|                     signedOrders[1].makerAssetAmount.div(2), | ||||
|   | ||||
| @@ -16,6 +16,7 @@ import * as MixinAuthorizable from '../../artifacts/MixinAuthorizable.json'; | ||||
| import * as MultiSigWallet from '../../artifacts/MultiSigWallet.json'; | ||||
| import * as MultiSigWalletWithTimeLock from '../../artifacts/MultiSigWalletWithTimeLock.json'; | ||||
| import * as OrderValidator from '../../artifacts/OrderValidator.json'; | ||||
| import * as ReentrantERC20Token from '../../artifacts/ReentrantERC20Token.json'; | ||||
| import * as TestAssetProxyDispatcher from '../../artifacts/TestAssetProxyDispatcher.json'; | ||||
| import * as TestAssetProxyOwner from '../../artifacts/TestAssetProxyOwner.json'; | ||||
| import * as TestConstants from '../../artifacts/TestConstants.json'; | ||||
| @@ -23,6 +24,7 @@ import * as TestExchangeInternals from '../../artifacts/TestExchangeInternals.js | ||||
| import * as TestLibBytes from '../../artifacts/TestLibBytes.json'; | ||||
| import * as TestLibs from '../../artifacts/TestLibs.json'; | ||||
| import * as TestSignatureValidator from '../../artifacts/TestSignatureValidator.json'; | ||||
| import * as TestStaticCallReceiver from '../../artifacts/TestStaticCallReceiver.json'; | ||||
| import * as TokenRegistry from '../../artifacts/TokenRegistry.json'; | ||||
| import * as Validator from '../../artifacts/Validator.json'; | ||||
| import * as Wallet from '../../artifacts/Wallet.json'; | ||||
| @@ -48,6 +50,7 @@ export const artifacts = { | ||||
|     MultiSigWallet: (MultiSigWallet as any) as ContractArtifact, | ||||
|     MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as ContractArtifact, | ||||
|     OrderValidator: (OrderValidator as any) as ContractArtifact, | ||||
|     ReentrantERC20Token: (ReentrantERC20Token as any) as ContractArtifact, | ||||
|     TestAssetProxyOwner: (TestAssetProxyOwner as any) as ContractArtifact, | ||||
|     TestAssetProxyDispatcher: (TestAssetProxyDispatcher as any) as ContractArtifact, | ||||
|     TestConstants: (TestConstants as any) as ContractArtifact, | ||||
| @@ -55,6 +58,7 @@ export const artifacts = { | ||||
|     TestLibs: (TestLibs as any) as ContractArtifact, | ||||
|     TestExchangeInternals: (TestExchangeInternals as any) as ContractArtifact, | ||||
|     TestSignatureValidator: (TestSignatureValidator as any) as ContractArtifact, | ||||
|     TestStaticCallReceiver: (TestStaticCallReceiver as any) as ContractArtifact, | ||||
|     Validator: (Validator as any) as ContractArtifact, | ||||
|     Wallet: (Wallet as any) as ContractArtifact, | ||||
|     TokenRegistry: (TokenRegistry as any) as ContractArtifact, | ||||
|   | ||||
| @@ -51,4 +51,16 @@ export const constants = { | ||||
|     WORD_LENGTH: 32, | ||||
|     ZERO_AMOUNT: new BigNumber(0), | ||||
|     PERCENTAGE_DENOMINATOR: new BigNumber(10).pow(18), | ||||
|     FUNCTIONS_WITH_MUTEX: [ | ||||
|         'FILL_ORDER', | ||||
|         'FILL_OR_KILL_ORDER', | ||||
|         'BATCH_FILL_ORDERS', | ||||
|         'BATCH_FILL_OR_KILL_ORDERS', | ||||
|         'MARKET_BUY_ORDERS', | ||||
|         'MARKET_SELL_ORDERS', | ||||
|         'MATCH_ORDERS', | ||||
|         'CANCEL_ORDER', | ||||
|         'CANCEL_ORDERS_UP_TO', | ||||
|         'SET_SIGNATURE_VALIDATOR_APPROVAL', | ||||
|     ], | ||||
| }; | ||||
|   | ||||
| @@ -467,17 +467,17 @@ export class FillOrderCombinatorialUtils { | ||||
|             ? remainingTakerAmountToFill | ||||
|             : alreadyFilledTakerAmount.add(takerAssetFillAmount); | ||||
|  | ||||
|         const expFilledMakerAmount = orderUtils.getPartialAmount( | ||||
|         const expFilledMakerAmount = orderUtils.getPartialAmountFloor( | ||||
|             expFilledTakerAmount, | ||||
|             signedOrder.takerAssetAmount, | ||||
|             signedOrder.makerAssetAmount, | ||||
|         ); | ||||
|         const expMakerFeePaid = orderUtils.getPartialAmount( | ||||
|         const expMakerFeePaid = orderUtils.getPartialAmountFloor( | ||||
|             expFilledTakerAmount, | ||||
|             signedOrder.takerAssetAmount, | ||||
|             signedOrder.makerFee, | ||||
|         ); | ||||
|         const expTakerFeePaid = orderUtils.getPartialAmount( | ||||
|         const expTakerFeePaid = orderUtils.getPartialAmountFloor( | ||||
|             expFilledTakerAmount, | ||||
|             signedOrder.takerAssetAmount, | ||||
|             signedOrder.takerFee, | ||||
| @@ -668,7 +668,7 @@ export class FillOrderCombinatorialUtils { | ||||
|         signedOrder: SignedOrder, | ||||
|         takerAssetFillAmount: BigNumber, | ||||
|     ): Promise<void> { | ||||
|         const makerAssetFillAmount = orderUtils.getPartialAmount( | ||||
|         const makerAssetFillAmount = orderUtils.getPartialAmountFloor( | ||||
|             takerAssetFillAmount, | ||||
|             signedOrder.takerAssetAmount, | ||||
|             signedOrder.makerAssetAmount, | ||||
| @@ -705,7 +705,7 @@ export class FillOrderCombinatorialUtils { | ||||
|                 ); | ||||
|         } | ||||
|  | ||||
|         const makerFee = orderUtils.getPartialAmount( | ||||
|         const makerFee = orderUtils.getPartialAmountFloor( | ||||
|             takerAssetFillAmount, | ||||
|             signedOrder.takerAssetAmount, | ||||
|             signedOrder.makerFee, | ||||
| @@ -829,7 +829,7 @@ export class FillOrderCombinatorialUtils { | ||||
|                 ); | ||||
|         } | ||||
|  | ||||
|         const takerFee = orderUtils.getPartialAmount( | ||||
|         const takerFee = orderUtils.getPartialAmountFloor( | ||||
|             takerAssetFillAmount, | ||||
|             signedOrder.takerAssetAmount, | ||||
|             signedOrder.takerFee, | ||||
|   | ||||
| @@ -4,11 +4,20 @@ import { BigNumber } from '@0xproject/utils'; | ||||
| import * as chai from 'chai'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { TransactionReceiptWithDecodedLogs } from '../../../../node_modules/ethereum-types'; | ||||
|  | ||||
| import { chaiSetup } from './chai_setup'; | ||||
| import { ERC20Wrapper } from './erc20_wrapper'; | ||||
| import { ERC721Wrapper } from './erc721_wrapper'; | ||||
| import { ExchangeWrapper } from './exchange_wrapper'; | ||||
| import { ERC20BalancesByOwner, ERC721TokenIdsByOwner, TransferAmountsByMatchOrders as TransferAmounts } from './types'; | ||||
| import { | ||||
|     ERC20BalancesByOwner, | ||||
|     ERC721TokenIdsByOwner, | ||||
|     OrderInfo, | ||||
|     OrderStatus, | ||||
|     TransferAmountsByMatchOrders as TransferAmounts, | ||||
|     TransferAmountsLoggedByMatchOrders as LoggedTransferAmounts, | ||||
| } from './types'; | ||||
|  | ||||
| chaiSetup.configure(); | ||||
| const expect = chai.expect; | ||||
| @@ -18,43 +27,107 @@ export class MatchOrderTester { | ||||
|     private readonly _erc20Wrapper: ERC20Wrapper; | ||||
|     private readonly _erc721Wrapper: ERC721Wrapper; | ||||
|     private readonly _feeTokenAddress: string; | ||||
|  | ||||
|     /// @dev Compares a pair of ERC20 balances and a pair of ERC721 token owners. | ||||
|     /// @param expectedNewERC20BalancesByOwner Expected ERC20 balances. | ||||
|     /// @param realERC20BalancesByOwner Actual ERC20 balances. | ||||
|     /// @param expectedNewERC721TokenIdsByOwner Expected ERC721 token owners. | ||||
|     /// @param realERC721TokenIdsByOwner Actual ERC20 token owners. | ||||
|     /// @return True only if ERC20 balances match and ERC721 token owners match. | ||||
|     private static _compareExpectedAndRealBalances( | ||||
|         expectedNewERC20BalancesByOwner: ERC20BalancesByOwner, | ||||
|         realERC20BalancesByOwner: ERC20BalancesByOwner, | ||||
|         expectedNewERC721TokenIdsByOwner: ERC721TokenIdsByOwner, | ||||
|         realERC721TokenIdsByOwner: ERC721TokenIdsByOwner, | ||||
|     ): boolean { | ||||
|         // ERC20 Balances | ||||
|         const doesErc20BalancesMatch = _.isEqual(expectedNewERC20BalancesByOwner, realERC20BalancesByOwner); | ||||
|         if (!doesErc20BalancesMatch) { | ||||
|             return false; | ||||
|         } | ||||
|         // ERC721 Token Ids | ||||
|         const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues( | ||||
|             expectedNewERC721TokenIdsByOwner, | ||||
|             tokenIdsByOwner => { | ||||
|                 _.mapValues(tokenIdsByOwner, tokenIds => { | ||||
|                     _.sortBy(tokenIds); | ||||
|                 }); | ||||
|             }, | ||||
|     /// @dev Checks values from the logs produced by Exchange.matchOrders against the expected transfer amounts. | ||||
|     ///      Values include the amounts transferred from the left/right makers and taker, along with | ||||
|     ///      the fees paid on each matched order. These are also the return values of MatchOrders. | ||||
|     /// @param signedOrderLeft First matched order. | ||||
|     /// @param signedOrderRight Second matched order. | ||||
|     /// @param transactionReceipt Transaction receipt and logs produced by Exchange.matchOrders. | ||||
|     /// @param takerAddress Address of taker (account that called Exchange.matchOrders) | ||||
|     /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. | ||||
|     private static async _assertLogsAsync( | ||||
|         signedOrderLeft: SignedOrder, | ||||
|         signedOrderRight: SignedOrder, | ||||
|         transactionReceipt: TransactionReceiptWithDecodedLogs, | ||||
|         takerAddress: string, | ||||
|         expectedTransferAmounts: TransferAmounts, | ||||
|     ): Promise<void> { | ||||
|         // Should have two fill event logs -- one for each order. | ||||
|         const transactionFillLogs = _.filter(transactionReceipt.logs, ['event', 'Fill']); | ||||
|         expect(transactionFillLogs.length, 'Checking number of logs').to.be.equal(2); | ||||
|         // First log is for left fill | ||||
|         const leftLog = (transactionFillLogs[0] as any).args as LoggedTransferAmounts; | ||||
|         expect(leftLog.makerAddress, 'Checking logged maker address of left order').to.be.equal( | ||||
|             signedOrderLeft.makerAddress, | ||||
|         ); | ||||
|         expect(leftLog.takerAddress, 'Checking logged taker address of right order').to.be.equal(takerAddress); | ||||
|         const amountBoughtByLeftMaker = new BigNumber(leftLog.takerAssetFilledAmount); | ||||
|         const amountSoldByLeftMaker = new BigNumber(leftLog.makerAssetFilledAmount); | ||||
|         const feePaidByLeftMaker = new BigNumber(leftLog.makerFeePaid); | ||||
|         const feePaidByTakerLeft = new BigNumber(leftLog.takerFeePaid); | ||||
|         // Second log is for right fill | ||||
|         const rightLog = (transactionFillLogs[1] as any).args as LoggedTransferAmounts; | ||||
|         expect(rightLog.makerAddress, 'Checking logged maker address of right order').to.be.equal( | ||||
|             signedOrderRight.makerAddress, | ||||
|         ); | ||||
|         expect(rightLog.takerAddress, 'Checking loggerd taker address of right order').to.be.equal(takerAddress); | ||||
|         const amountBoughtByRightMaker = new BigNumber(rightLog.takerAssetFilledAmount); | ||||
|         const amountSoldByRightMaker = new BigNumber(rightLog.makerAssetFilledAmount); | ||||
|         const feePaidByRightMaker = new BigNumber(rightLog.makerFeePaid); | ||||
|         const feePaidByTakerRight = new BigNumber(rightLog.takerFeePaid); | ||||
|         // Derive amount received by taker | ||||
|         const amountReceivedByTaker = amountSoldByLeftMaker.sub(amountBoughtByRightMaker); | ||||
|         // Assert log values - left order | ||||
|         expect(amountBoughtByLeftMaker, 'Checking logged amount bought by left maker').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.amountBoughtByLeftMaker, | ||||
|         ); | ||||
|         expect(amountSoldByLeftMaker, 'Checking logged amount sold by left maker').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.amountSoldByLeftMaker, | ||||
|         ); | ||||
|         expect(feePaidByLeftMaker, 'Checking logged fee paid by left maker').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.feePaidByLeftMaker, | ||||
|         ); | ||||
|         expect(feePaidByTakerLeft, 'Checking logged fee paid on left order by taker').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.feePaidByTakerLeft, | ||||
|         ); | ||||
|         // Assert log values - right order | ||||
|         expect(amountBoughtByRightMaker, 'Checking logged amount bought by right maker').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.amountBoughtByRightMaker, | ||||
|         ); | ||||
|         expect(amountSoldByRightMaker, 'Checking logged amount sold by right maker').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.amountSoldByRightMaker, | ||||
|         ); | ||||
|         expect(feePaidByRightMaker, 'Checking logged fee paid by right maker').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.feePaidByRightMaker, | ||||
|         ); | ||||
|         expect(feePaidByTakerRight, 'Checking logged fee paid on right order by taker').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.feePaidByTakerRight, | ||||
|         ); | ||||
|         // Assert derived amount received by taker | ||||
|         expect(amountReceivedByTaker, 'Checking logged amount received by taker').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.amountReceivedByTaker, | ||||
|         ); | ||||
|     } | ||||
|     /// @dev Asserts all expected ERC20 and ERC721 account holdings match the real holdings. | ||||
|     /// @param expectedERC20BalancesByOwner Expected ERC20 balances. | ||||
|     /// @param realERC20BalancesByOwner Real ERC20 balances. | ||||
|     /// @param expectedERC721TokenIdsByOwner Expected ERC721 token owners. | ||||
|     /// @param realERC721TokenIdsByOwner Real ERC20 token owners. | ||||
|     private static async _assertAllKnownBalancesAsync( | ||||
|         expectedERC20BalancesByOwner: ERC20BalancesByOwner, | ||||
|         realERC20BalancesByOwner: ERC20BalancesByOwner, | ||||
|         expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner, | ||||
|         realERC721TokenIdsByOwner: ERC721TokenIdsByOwner, | ||||
|     ): Promise<void> { | ||||
|         // ERC20 Balances | ||||
|         const areERC20BalancesEqual = _.isEqual(expectedERC20BalancesByOwner, realERC20BalancesByOwner); | ||||
|         expect(areERC20BalancesEqual, 'Checking all known ERC20 account balances').to.be.true(); | ||||
|         // ERC721 Token Ids | ||||
|         const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues(expectedERC721TokenIdsByOwner, tokenIdsByOwner => { | ||||
|             _.mapValues(tokenIdsByOwner, tokenIds => { | ||||
|                 _.sortBy(tokenIds); | ||||
|             }); | ||||
|         }); | ||||
|         const sortedNewERC721TokenIdsByOwner = _.mapValues(realERC721TokenIdsByOwner, tokenIdsByOwner => { | ||||
|             _.mapValues(tokenIdsByOwner, tokenIds => { | ||||
|                 _.sortBy(tokenIds); | ||||
|             }); | ||||
|         }); | ||||
|         const doesErc721TokenIdsMatch = _.isEqual( | ||||
|         const areERC721TokenIdsEqual = _.isEqual( | ||||
|             sortedExpectedNewERC721TokenIdsByOwner, | ||||
|             sortedNewERC721TokenIdsByOwner, | ||||
|         ); | ||||
|         return doesErc721TokenIdsMatch; | ||||
|         expect(areERC721TokenIdsEqual, 'Checking all known ERC721 account balances').to.be.true(); | ||||
|     } | ||||
|     /// @dev Constructs new MatchOrderTester. | ||||
|     /// @param exchangeWrapper Used to call to the Exchange. | ||||
| @@ -72,150 +145,199 @@ export class MatchOrderTester { | ||||
|         this._erc721Wrapper = erc721Wrapper; | ||||
|         this._feeTokenAddress = feeTokenAddress; | ||||
|     } | ||||
|     /// @dev Matches two complementary orders and validates results. | ||||
|     ///      Validation either succeeds or throws. | ||||
|     /// @dev Matches two complementary orders and asserts results. | ||||
|     /// @param signedOrderLeft First matched order. | ||||
|     /// @param signedOrderRight Second matched order. | ||||
|     /// @param takerAddress Address of taker (the address who matched the two orders) | ||||
|     /// @param erc20BalancesByOwner Current ERC20 balances. | ||||
|     /// @param erc721TokenIdsByOwner Current ERC721 token owners. | ||||
|     /// @param initialTakerAssetFilledAmountLeft Current amount the left order has been filled. | ||||
|     /// @param initialTakerAssetFilledAmountRight Current amount the right order has been filled. | ||||
|     /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. | ||||
|     /// @param initialLeftOrderFilledAmount How much left order has been filled, prior to matching orders. | ||||
|     /// @param initialRightOrderFilledAmount How much the right order has been filled, prior to matching orders. | ||||
|     /// @return New ERC20 balances & ERC721 token owners. | ||||
|     public async matchOrdersAndVerifyBalancesAsync( | ||||
|     public async matchOrdersAndAssertEffectsAsync( | ||||
|         signedOrderLeft: SignedOrder, | ||||
|         signedOrderRight: SignedOrder, | ||||
|         takerAddress: string, | ||||
|         erc20BalancesByOwner: ERC20BalancesByOwner, | ||||
|         erc721TokenIdsByOwner: ERC721TokenIdsByOwner, | ||||
|         initialTakerAssetFilledAmountLeft?: BigNumber, | ||||
|         initialTakerAssetFilledAmountRight?: BigNumber, | ||||
|         expectedTransferAmounts: TransferAmounts, | ||||
|         initialLeftOrderFilledAmount: BigNumber = new BigNumber(0), | ||||
|         initialRightOrderFilledAmount: BigNumber = new BigNumber(0), | ||||
|     ): Promise<[ERC20BalancesByOwner, ERC721TokenIdsByOwner]> { | ||||
|         // Verify Left order preconditions | ||||
|         // Assert initial order states | ||||
|         await this._assertInitialOrderStatesAsync( | ||||
|             signedOrderLeft, | ||||
|             signedOrderRight, | ||||
|             initialLeftOrderFilledAmount, | ||||
|             initialRightOrderFilledAmount, | ||||
|         ); | ||||
|         // Match left & right orders | ||||
|         const transactionReceipt = await this._exchangeWrapper.matchOrdersAsync( | ||||
|             signedOrderLeft, | ||||
|             signedOrderRight, | ||||
|             takerAddress, | ||||
|         ); | ||||
|         const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync(); | ||||
|         const newERC721TokenIdsByOwner = await this._erc721Wrapper.getBalancesAsync(); | ||||
|         // Assert logs | ||||
|         await MatchOrderTester._assertLogsAsync( | ||||
|             signedOrderLeft, | ||||
|             signedOrderRight, | ||||
|             transactionReceipt, | ||||
|             takerAddress, | ||||
|             expectedTransferAmounts, | ||||
|         ); | ||||
|         // Assert exchange state | ||||
|         await this._assertExchangeStateAsync( | ||||
|             signedOrderLeft, | ||||
|             signedOrderRight, | ||||
|             initialLeftOrderFilledAmount, | ||||
|             initialRightOrderFilledAmount, | ||||
|             expectedTransferAmounts, | ||||
|         ); | ||||
|         // Assert balances of makers, taker, and fee recipients | ||||
|         await this._assertBalancesAsync( | ||||
|             signedOrderLeft, | ||||
|             signedOrderRight, | ||||
|             erc20BalancesByOwner, | ||||
|             erc721TokenIdsByOwner, | ||||
|             newERC20BalancesByOwner, | ||||
|             newERC721TokenIdsByOwner, | ||||
|             expectedTransferAmounts, | ||||
|             takerAddress, | ||||
|         ); | ||||
|         return [newERC20BalancesByOwner, newERC721TokenIdsByOwner]; | ||||
|     } | ||||
|     /// @dev Asserts initial exchange state for the left and right orders. | ||||
|     /// @param signedOrderLeft First matched order. | ||||
|     /// @param signedOrderRight Second matched order. | ||||
|     /// @param expectedOrderFilledAmountLeft How much left order has been filled, prior to matching orders. | ||||
|     /// @param expectedOrderFilledAmountRight How much the right order has been filled, prior to matching orders. | ||||
|     private async _assertInitialOrderStatesAsync( | ||||
|         signedOrderLeft: SignedOrder, | ||||
|         signedOrderRight: SignedOrder, | ||||
|         expectedOrderFilledAmountLeft: BigNumber, | ||||
|         expectedOrderFilledAmountRight: BigNumber, | ||||
|     ): Promise<void> { | ||||
|         // Assert left order initial state | ||||
|         const orderTakerAssetFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( | ||||
|             orderHashUtils.getOrderHashHex(signedOrderLeft), | ||||
|         ); | ||||
|         const expectedOrderFilledAmountLeft = initialTakerAssetFilledAmountLeft | ||||
|             ? initialTakerAssetFilledAmountLeft | ||||
|             : new BigNumber(0); | ||||
|         expect(expectedOrderFilledAmountLeft).to.be.bignumber.equal(orderTakerAssetFilledAmountLeft); | ||||
|         // Verify Right order preconditions | ||||
|         expect(orderTakerAssetFilledAmountLeft, 'Checking inital state of left order').to.be.bignumber.equal( | ||||
|             expectedOrderFilledAmountLeft, | ||||
|         ); | ||||
|         // Assert right order initial state | ||||
|         const orderTakerAssetFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( | ||||
|             orderHashUtils.getOrderHashHex(signedOrderRight), | ||||
|         ); | ||||
|         const expectedOrderFilledAmountRight = initialTakerAssetFilledAmountRight | ||||
|             ? initialTakerAssetFilledAmountRight | ||||
|             : new BigNumber(0); | ||||
|         expect(expectedOrderFilledAmountRight).to.be.bignumber.equal(orderTakerAssetFilledAmountRight); | ||||
|         // Match left & right orders | ||||
|         await this._exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress); | ||||
|         const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync(); | ||||
|         const newERC721TokenIdsByOwner = await this._erc721Wrapper.getBalancesAsync(); | ||||
|         // Calculate expected balance changes | ||||
|         const expectedTransferAmounts = await this._calculateExpectedTransferAmountsAsync( | ||||
|             signedOrderLeft, | ||||
|             signedOrderRight, | ||||
|             orderTakerAssetFilledAmountLeft, | ||||
|             orderTakerAssetFilledAmountRight, | ||||
|         expect(orderTakerAssetFilledAmountRight, 'Checking inital state of right order').to.be.bignumber.equal( | ||||
|             expectedOrderFilledAmountRight, | ||||
|         ); | ||||
|     } | ||||
|     /// @dev Asserts the exchange state against the expected amounts transferred by from matching orders. | ||||
|     /// @param signedOrderLeft First matched order. | ||||
|     /// @param signedOrderRight Second matched order. | ||||
|     /// @param initialLeftOrderFilledAmount How much left order has been filled, prior to matching orders. | ||||
|     /// @param initialRightOrderFilledAmount How much the right order has been filled, prior to matching orders. | ||||
|     /// @return TransferAmounts A struct containing the expected transfer amounts. | ||||
|     private async _assertExchangeStateAsync( | ||||
|         signedOrderLeft: SignedOrder, | ||||
|         signedOrderRight: SignedOrder, | ||||
|         initialLeftOrderFilledAmount: BigNumber, | ||||
|         initialRightOrderFilledAmount: BigNumber, | ||||
|         expectedTransferAmounts: TransferAmounts, | ||||
|     ): Promise<void> { | ||||
|         // Assert state for left order: amount bought by left maker | ||||
|         let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( | ||||
|             orderHashUtils.getOrderHashHex(signedOrderLeft), | ||||
|         ); | ||||
|         amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(initialLeftOrderFilledAmount); | ||||
|         expect(amountBoughtByLeftMaker, 'Checking exchange state for left order').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.amountBoughtByLeftMaker, | ||||
|         ); | ||||
|         // Assert state for right order: amount bought by right maker | ||||
|         let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( | ||||
|             orderHashUtils.getOrderHashHex(signedOrderRight), | ||||
|         ); | ||||
|         amountBoughtByRightMaker = amountBoughtByRightMaker.minus(initialRightOrderFilledAmount); | ||||
|         expect(amountBoughtByRightMaker, 'Checking exchange state for right order').to.be.bignumber.equal( | ||||
|             expectedTransferAmounts.amountBoughtByRightMaker, | ||||
|         ); | ||||
|         // Assert left order status | ||||
|         const maxAmountBoughtByLeftMaker = signedOrderLeft.takerAssetAmount.minus(initialLeftOrderFilledAmount); | ||||
|         const leftOrderInfo: OrderInfo = await this._exchangeWrapper.getOrderInfoAsync(signedOrderLeft); | ||||
|         const leftExpectedStatus = expectedTransferAmounts.amountBoughtByLeftMaker.equals(maxAmountBoughtByLeftMaker) | ||||
|             ? OrderStatus.FULLY_FILLED | ||||
|             : OrderStatus.FILLABLE; | ||||
|         expect(leftOrderInfo.orderStatus as OrderStatus, 'Checking exchange status for left order').to.be.equal( | ||||
|             leftExpectedStatus, | ||||
|         ); | ||||
|         // Assert right order status | ||||
|         const maxAmountBoughtByRightMaker = signedOrderRight.takerAssetAmount.minus(initialRightOrderFilledAmount); | ||||
|         const rightOrderInfo: OrderInfo = await this._exchangeWrapper.getOrderInfoAsync(signedOrderRight); | ||||
|         const rightExpectedStatus = expectedTransferAmounts.amountBoughtByRightMaker.equals(maxAmountBoughtByRightMaker) | ||||
|             ? OrderStatus.FULLY_FILLED | ||||
|             : OrderStatus.FILLABLE; | ||||
|         expect(rightOrderInfo.orderStatus as OrderStatus, 'Checking exchange status for right order').to.be.equal( | ||||
|             rightExpectedStatus, | ||||
|         ); | ||||
|     } | ||||
|     /// @dev Asserts account balances after matching orders. | ||||
|     /// @param signedOrderLeft First matched order. | ||||
|     /// @param signedOrderRight Second matched order. | ||||
|     /// @param initialERC20BalancesByOwner ERC20 balances prior to order matching. | ||||
|     /// @param initialERC721TokenIdsByOwner ERC721 token owners prior to order matching. | ||||
|     /// @param finalERC20BalancesByOwner ERC20 balances after order matching. | ||||
|     /// @param finalERC721TokenIdsByOwner ERC721 token owners after order matching. | ||||
|     /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. | ||||
|     /// @param takerAddress Address of taker (account that called Exchange.matchOrders). | ||||
|     private async _assertBalancesAsync( | ||||
|         signedOrderLeft: SignedOrder, | ||||
|         signedOrderRight: SignedOrder, | ||||
|         initialERC20BalancesByOwner: ERC20BalancesByOwner, | ||||
|         initialERC721TokenIdsByOwner: ERC721TokenIdsByOwner, | ||||
|         finalERC20BalancesByOwner: ERC20BalancesByOwner, | ||||
|         finalERC721TokenIdsByOwner: ERC721TokenIdsByOwner, | ||||
|         expectedTransferAmounts: TransferAmounts, | ||||
|         takerAddress: string, | ||||
|     ): Promise<void> { | ||||
|         let expectedERC20BalancesByOwner: ERC20BalancesByOwner; | ||||
|         let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner; | ||||
|         [expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = this._calculateExpectedBalances( | ||||
|             signedOrderLeft, | ||||
|             signedOrderRight, | ||||
|             takerAddress, | ||||
|             erc20BalancesByOwner, | ||||
|             erc721TokenIdsByOwner, | ||||
|             initialERC20BalancesByOwner, | ||||
|             initialERC721TokenIdsByOwner, | ||||
|             expectedTransferAmounts, | ||||
|         ); | ||||
|         // Assert our expected balances are equal to the actual balances | ||||
|         const didExpectedBalancesMatchRealBalances = MatchOrderTester._compareExpectedAndRealBalances( | ||||
|         // Assert balances of makers, taker, and fee recipients | ||||
|         await this._assertMakerTakerAndFeeRecipientBalancesAsync( | ||||
|             signedOrderLeft, | ||||
|             signedOrderRight, | ||||
|             expectedERC20BalancesByOwner, | ||||
|             newERC20BalancesByOwner, | ||||
|             finalERC20BalancesByOwner, | ||||
|             expectedERC721TokenIdsByOwner, | ||||
|             newERC721TokenIdsByOwner, | ||||
|             finalERC721TokenIdsByOwner, | ||||
|             takerAddress, | ||||
|         ); | ||||
|         expect(didExpectedBalancesMatchRealBalances).to.be.true(); | ||||
|         return [newERC20BalancesByOwner, newERC721TokenIdsByOwner]; | ||||
|     } | ||||
|     /// @dev Calculates expected transfer amounts between order makers, fee recipients, and | ||||
|     ///      the taker when two orders are matched. | ||||
|     /// @param signedOrderLeft First matched order. | ||||
|     /// @param signedOrderRight Second matched order. | ||||
|     /// @param orderTakerAssetFilledAmountLeft How much left order has been filled, prior to matching orders. | ||||
|     /// @param orderTakerAssetFilledAmountRight How much the right order has been filled, prior to matching orders. | ||||
|     /// @return TransferAmounts A struct containing the expected transfer amounts. | ||||
|     private async _calculateExpectedTransferAmountsAsync( | ||||
|         signedOrderLeft: SignedOrder, | ||||
|         signedOrderRight: SignedOrder, | ||||
|         orderTakerAssetFilledAmountLeft: BigNumber, | ||||
|         orderTakerAssetFilledAmountRight: BigNumber, | ||||
|     ): Promise<TransferAmounts> { | ||||
|         let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( | ||||
|             orderHashUtils.getOrderHashHex(signedOrderLeft), | ||||
|         // Assert balances for all known accounts | ||||
|         await MatchOrderTester._assertAllKnownBalancesAsync( | ||||
|             expectedERC20BalancesByOwner, | ||||
|             finalERC20BalancesByOwner, | ||||
|             expectedERC721TokenIdsByOwner, | ||||
|             finalERC721TokenIdsByOwner, | ||||
|         ); | ||||
|         amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(orderTakerAssetFilledAmountLeft); | ||||
|         const amountSoldByLeftMaker = amountBoughtByLeftMaker | ||||
|             .times(signedOrderLeft.makerAssetAmount) | ||||
|             .dividedToIntegerBy(signedOrderLeft.takerAssetAmount); | ||||
|         const amountReceivedByRightMaker = amountBoughtByLeftMaker | ||||
|             .times(signedOrderRight.takerAssetAmount) | ||||
|             .dividedToIntegerBy(signedOrderRight.makerAssetAmount); | ||||
|         const amountReceivedByTaker = amountSoldByLeftMaker.minus(amountReceivedByRightMaker); | ||||
|         let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( | ||||
|             orderHashUtils.getOrderHashHex(signedOrderRight), | ||||
|         ); | ||||
|         amountBoughtByRightMaker = amountBoughtByRightMaker.minus(orderTakerAssetFilledAmountRight); | ||||
|         const amountSoldByRightMaker = amountBoughtByRightMaker | ||||
|             .times(signedOrderRight.makerAssetAmount) | ||||
|             .dividedToIntegerBy(signedOrderRight.takerAssetAmount); | ||||
|         const amountReceivedByLeftMaker = amountSoldByRightMaker; | ||||
|         const feePaidByLeftMaker = signedOrderLeft.makerFee | ||||
|             .times(amountSoldByLeftMaker) | ||||
|             .dividedToIntegerBy(signedOrderLeft.makerAssetAmount); | ||||
|         const feePaidByRightMaker = signedOrderRight.makerFee | ||||
|             .times(amountSoldByRightMaker) | ||||
|             .dividedToIntegerBy(signedOrderRight.makerAssetAmount); | ||||
|         const feePaidByTakerLeft = signedOrderLeft.takerFee | ||||
|             .times(amountSoldByLeftMaker) | ||||
|             .dividedToIntegerBy(signedOrderLeft.makerAssetAmount); | ||||
|         const feePaidByTakerRight = signedOrderRight.takerFee | ||||
|             .times(amountSoldByRightMaker) | ||||
|             .dividedToIntegerBy(signedOrderRight.makerAssetAmount); | ||||
|         const totalFeePaidByTaker = feePaidByTakerLeft.add(feePaidByTakerRight); | ||||
|         const feeReceivedLeft = feePaidByLeftMaker.add(feePaidByTakerLeft); | ||||
|         const feeReceivedRight = feePaidByRightMaker.add(feePaidByTakerRight); | ||||
|         // Return values | ||||
|         const expectedTransferAmounts = { | ||||
|             // Left Maker | ||||
|             amountBoughtByLeftMaker, | ||||
|             amountSoldByLeftMaker, | ||||
|             amountReceivedByLeftMaker, | ||||
|             feePaidByLeftMaker, | ||||
|             // Right Maker | ||||
|             amountBoughtByRightMaker, | ||||
|             amountSoldByRightMaker, | ||||
|             amountReceivedByRightMaker, | ||||
|             feePaidByRightMaker, | ||||
|             // Taker | ||||
|             amountReceivedByTaker, | ||||
|             feePaidByTakerLeft, | ||||
|             feePaidByTakerRight, | ||||
|             totalFeePaidByTaker, | ||||
|             // Fee Recipients | ||||
|             feeReceivedLeft, | ||||
|             feeReceivedRight, | ||||
|         }; | ||||
|         return expectedTransferAmounts; | ||||
|     } | ||||
|     /// @dev Calculates the expected balances of order makers, fee recipients, and the taker, | ||||
|     ///      as a result of matching two orders. | ||||
|     /// @param signedOrderLeft First matched order. | ||||
|     /// @param signedOrderRight First matched order. | ||||
|     /// @param signedOrderRight Second matched order. | ||||
|     /// @param takerAddress Address of taker (the address who matched the two orders) | ||||
|     /// @param erc20BalancesByOwner Current ERC20 balances. | ||||
|     /// @param erc721TokenIdsByOwner Current ERC721 token owners. | ||||
|     /// @param expectedTransferAmounts A struct containing the expected transfer amounts. | ||||
|     /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. | ||||
|     /// @return Expected ERC20 balances & ERC721 token owners after orders have been matched. | ||||
|     private _calculateExpectedBalances( | ||||
|         signedOrderLeft: SignedOrder, | ||||
| @@ -247,7 +369,7 @@ export class MatchOrderTester { | ||||
|             expectedNewERC20BalancesByOwner[makerAddressRight][ | ||||
|                 takerAssetAddressRight | ||||
|             ] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].add( | ||||
|                 expectedTransferAmounts.amountReceivedByRightMaker, | ||||
|                 expectedTransferAmounts.amountBoughtByRightMaker, | ||||
|             ); | ||||
|             // Taker | ||||
|             expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ | ||||
| @@ -277,7 +399,7 @@ export class MatchOrderTester { | ||||
|             // Left Maker | ||||
|             expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ | ||||
|                 makerAddressLeft | ||||
|             ][takerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByLeftMaker); | ||||
|             ][takerAssetAddressLeft].add(expectedTransferAmounts.amountBoughtByLeftMaker); | ||||
|             // Right Maker | ||||
|             expectedNewERC20BalancesByOwner[makerAddressRight][ | ||||
|                 makerAssetAddressRight | ||||
| @@ -307,20 +429,138 @@ export class MatchOrderTester { | ||||
|         // Taker Fees | ||||
|         expectedNewERC20BalancesByOwner[takerAddress][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ | ||||
|             takerAddress | ||||
|         ][this._feeTokenAddress].minus(expectedTransferAmounts.totalFeePaidByTaker); | ||||
|         ][this._feeTokenAddress].minus( | ||||
|             expectedTransferAmounts.feePaidByTakerLeft.add(expectedTransferAmounts.feePaidByTakerRight), | ||||
|         ); | ||||
|         // Left Fee Recipient Fees | ||||
|         expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][ | ||||
|             this._feeTokenAddress | ||||
|         ] = expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][this._feeTokenAddress].add( | ||||
|             expectedTransferAmounts.feeReceivedLeft, | ||||
|             expectedTransferAmounts.feePaidByLeftMaker.add(expectedTransferAmounts.feePaidByTakerLeft), | ||||
|         ); | ||||
|         // Right Fee Recipient Fees | ||||
|         expectedNewERC20BalancesByOwner[feeRecipientAddressRight][ | ||||
|             this._feeTokenAddress | ||||
|         ] = expectedNewERC20BalancesByOwner[feeRecipientAddressRight][this._feeTokenAddress].add( | ||||
|             expectedTransferAmounts.feeReceivedRight, | ||||
|             expectedTransferAmounts.feePaidByRightMaker.add(expectedTransferAmounts.feePaidByTakerRight), | ||||
|         ); | ||||
|  | ||||
|         return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner]; | ||||
|     } | ||||
| } | ||||
|     /// @dev Asserts ERC20 account balances and ERC721 token holdings that result from order matching. | ||||
|     ///      Specifically checks balances of makers, taker and fee recipients. | ||||
|     /// @param signedOrderLeft First matched order. | ||||
|     /// @param signedOrderRight Second matched order. | ||||
|     /// @param expectedERC20BalancesByOwner Expected ERC20 balances. | ||||
|     /// @param realERC20BalancesByOwner Real ERC20 balances. | ||||
|     /// @param expectedERC721TokenIdsByOwner Expected ERC721 token owners. | ||||
|     /// @param realERC721TokenIdsByOwner Real ERC20 token owners. | ||||
|     /// @param takerAddress Address of taker (account that called Exchange.matchOrders). | ||||
|     private async _assertMakerTakerAndFeeRecipientBalancesAsync( | ||||
|         signedOrderLeft: SignedOrder, | ||||
|         signedOrderRight: SignedOrder, | ||||
|         expectedERC20BalancesByOwner: ERC20BalancesByOwner, | ||||
|         realERC20BalancesByOwner: ERC20BalancesByOwner, | ||||
|         expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner, | ||||
|         realERC721TokenIdsByOwner: ERC721TokenIdsByOwner, | ||||
|         takerAddress: string, | ||||
|     ): Promise<void> { | ||||
|         // Individual balance comparisons | ||||
|         const makerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.makerAssetData); | ||||
|         const makerERC20AssetDataLeft = | ||||
|             makerAssetProxyIdLeft === AssetProxyId.ERC20 | ||||
|                 ? assetDataUtils.decodeERC20AssetData(signedOrderLeft.makerAssetData) | ||||
|                 : assetDataUtils.decodeERC721AssetData(signedOrderLeft.makerAssetData); | ||||
|         const makerAssetAddressLeft = makerERC20AssetDataLeft.tokenAddress; | ||||
|         const makerAssetProxyIdRight = assetDataUtils.decodeAssetProxyId(signedOrderRight.makerAssetData); | ||||
|         const makerERC20AssetDataRight = | ||||
|             makerAssetProxyIdRight === AssetProxyId.ERC20 | ||||
|                 ? assetDataUtils.decodeERC20AssetData(signedOrderRight.makerAssetData) | ||||
|                 : assetDataUtils.decodeERC721AssetData(signedOrderRight.makerAssetData); | ||||
|         const makerAssetAddressRight = makerERC20AssetDataRight.tokenAddress; | ||||
|         if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { | ||||
|             expect( | ||||
|                 realERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft], | ||||
|                 'Checking left maker egress ERC20 account balance', | ||||
|             ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft]); | ||||
|             expect( | ||||
|                 realERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft], | ||||
|                 'Checking right maker ingress ERC20 account balance', | ||||
|             ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft]); | ||||
|             expect( | ||||
|                 realERC20BalancesByOwner[takerAddress][makerAssetAddressLeft], | ||||
|                 'Checking taker ingress ERC20 account balance', | ||||
|             ).to.be.bignumber.equal(expectedERC20BalancesByOwner[takerAddress][makerAssetAddressLeft]); | ||||
|         } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { | ||||
|             expect( | ||||
|                 realERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft].sort(), | ||||
|                 'Checking left maker egress ERC721 account holdings', | ||||
|             ).to.be.deep.equal( | ||||
|                 expectedERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft].sort(), | ||||
|             ); | ||||
|             expect( | ||||
|                 realERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft].sort(), | ||||
|                 'Checking right maker ERC721 account holdings', | ||||
|             ).to.be.deep.equal( | ||||
|                 expectedERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft].sort(), | ||||
|             ); | ||||
|             expect( | ||||
|                 realERC721TokenIdsByOwner[takerAddress][makerAssetAddressLeft].sort(), | ||||
|                 'Checking taker ingress ERC721 account holdings', | ||||
|             ).to.be.deep.equal(expectedERC721TokenIdsByOwner[takerAddress][makerAssetAddressLeft].sort()); | ||||
|         } else { | ||||
|             throw new Error(`Unhandled Asset Proxy ID: ${makerAssetProxyIdLeft}`); | ||||
|         } | ||||
|         if (makerAssetProxyIdRight === AssetProxyId.ERC20) { | ||||
|             expect( | ||||
|                 realERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight], | ||||
|                 'Checking left maker ingress ERC20 account balance', | ||||
|             ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight]); | ||||
|             expect( | ||||
|                 realERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressRight], | ||||
|                 'Checking right maker egress ERC20 account balance', | ||||
|             ).to.be.bignumber.equal( | ||||
|                 expectedERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressRight], | ||||
|             ); | ||||
|         } else if (makerAssetProxyIdRight === AssetProxyId.ERC721) { | ||||
|             expect( | ||||
|                 realERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight].sort(), | ||||
|                 'Checking left maker ingress ERC721 account holdings', | ||||
|             ).to.be.deep.equal( | ||||
|                 expectedERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight].sort(), | ||||
|             ); | ||||
|             expect( | ||||
|                 realERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressRight], | ||||
|                 'Checking right maker agress ERC721 account holdings', | ||||
|             ).to.be.deep.equal(expectedERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressRight]); | ||||
|         } else { | ||||
|             throw new Error(`Unhandled Asset Proxy ID: ${makerAssetProxyIdRight}`); | ||||
|         } | ||||
|         // Paid fees | ||||
|         expect( | ||||
|             realERC20BalancesByOwner[signedOrderLeft.makerAddress][this._feeTokenAddress], | ||||
|             'Checking left maker egress ERC20 account fees', | ||||
|         ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][this._feeTokenAddress]); | ||||
|         expect( | ||||
|             realERC20BalancesByOwner[signedOrderRight.makerAddress][this._feeTokenAddress], | ||||
|             'Checking right maker egress ERC20 account fees', | ||||
|         ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderRight.makerAddress][this._feeTokenAddress]); | ||||
|         expect( | ||||
|             realERC20BalancesByOwner[takerAddress][this._feeTokenAddress], | ||||
|             'Checking taker egress ERC20 account fees', | ||||
|         ).to.be.bignumber.equal(expectedERC20BalancesByOwner[takerAddress][this._feeTokenAddress]); | ||||
|         // Received fees | ||||
|         expect( | ||||
|             realERC20BalancesByOwner[signedOrderLeft.feeRecipientAddress][this._feeTokenAddress], | ||||
|             'Checking left fee recipient ingress ERC20 account fees', | ||||
|         ).to.be.bignumber.equal( | ||||
|             expectedERC20BalancesByOwner[signedOrderLeft.feeRecipientAddress][this._feeTokenAddress], | ||||
|         ); | ||||
|         expect( | ||||
|             realERC20BalancesByOwner[signedOrderRight.feeRecipientAddress][this._feeTokenAddress], | ||||
|             'Checking right fee receipient ingress ERC20 account fees', | ||||
|         ).to.be.bignumber.equal( | ||||
|             expectedERC20BalancesByOwner[signedOrderRight.feeRecipientAddress][this._feeTokenAddress], | ||||
|         ); | ||||
|     } | ||||
| } // tslint:disable-line:max-file-line-count | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import { constants } from './constants'; | ||||
| import { CancelOrder, MatchOrder } from './types'; | ||||
|  | ||||
| export const orderUtils = { | ||||
|     getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { | ||||
|     getPartialAmountFloor(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { | ||||
|         const partialAmount = numerator | ||||
|             .mul(target) | ||||
|             .div(denominator) | ||||
|   | ||||
| @@ -117,21 +117,24 @@ export interface TransferAmountsByMatchOrders { | ||||
|     // Left Maker | ||||
|     amountBoughtByLeftMaker: BigNumber; | ||||
|     amountSoldByLeftMaker: BigNumber; | ||||
|     amountReceivedByLeftMaker: BigNumber; | ||||
|     feePaidByLeftMaker: BigNumber; | ||||
|     // Right Maker | ||||
|     amountBoughtByRightMaker: BigNumber; | ||||
|     amountSoldByRightMaker: BigNumber; | ||||
|     amountReceivedByRightMaker: BigNumber; | ||||
|     feePaidByRightMaker: BigNumber; | ||||
|     // Taker | ||||
|     amountReceivedByTaker: BigNumber; | ||||
|     feePaidByTakerLeft: BigNumber; | ||||
|     feePaidByTakerRight: BigNumber; | ||||
|     totalFeePaidByTaker: BigNumber; | ||||
|     // Fee Recipients | ||||
|     feeReceivedLeft: BigNumber; | ||||
|     feeReceivedRight: BigNumber; | ||||
| } | ||||
|  | ||||
| export interface TransferAmountsLoggedByMatchOrders { | ||||
|     makerAddress: string; | ||||
|     takerAddress: string; | ||||
|     makerAssetFilledAmount: string; | ||||
|     takerAssetFilledAmount: string; | ||||
|     makerFeePaid: string; | ||||
|     takerFeePaid: string; | ||||
| } | ||||
|  | ||||
| export interface OrderInfo { | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1535377027, | ||||
|         "version": "1.0.6", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1535133899, | ||||
|         "version": "1.0.5", | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.6 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.5 - _August 24, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/dev-utils", | ||||
|     "version": "1.0.5", | ||||
|     "version": "1.0.6", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -43,11 +43,11 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/subproviders": "^2.0.0", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/subproviders": "^2.0.1", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/web3-wrapper": "^2.0.0", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@0xproject/web3-wrapper": "^2.0.1", | ||||
|         "ethereum-types": "^1.0.5", | ||||
|         "lodash": "^4.17.5" | ||||
|     }, | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "1.0.1-rc.5", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1535377027 | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.1-rc.4", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.1-rc.5 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.1-rc.4 - _August 24, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/fill-scenarios", | ||||
|     "version": "1.0.1-rc.4", | ||||
|     "version": "1.0.1-rc.5", | ||||
|     "description": "0x order fill scenario generator", | ||||
|     "main": "lib/index.js", | ||||
|     "types": "lib/index.d.ts", | ||||
| @@ -27,7 +27,7 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/packages/fill-scenarios/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0xproject/abi-gen": "^1.0.6", | ||||
|         "@0xproject/abi-gen": "^1.0.7", | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "copyfiles": "^2.0.0", | ||||
| @@ -38,12 +38,12 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/base-contract": "^2.0.0", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.4", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/base-contract": "^2.0.1", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.6", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/web3-wrapper": "^2.0.0", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@0xproject/web3-wrapper": "^2.0.1", | ||||
|         "ethereum-types": "^1.0.5", | ||||
|         "ethers": "3.0.22", | ||||
|         "lodash": "^4.17.5" | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "1.0.1-rc.2", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1535377027 | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.1-rc.1", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.1-rc.2 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.1-rc.1 - _August 24, 2018_ | ||||
|  | ||||
|     * Add initial forwarderHelperFactory (#997) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/forwarder-helper", | ||||
|     "version": "1.0.1-rc.1", | ||||
|     "version": "1.0.1-rc.2", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -39,12 +39,12 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/packages/forwarder-helper/README.md", | ||||
|     "dependencies": { | ||||
|         "@0xproject/assert": "^1.0.6", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.5", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.4", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/assert": "^1.0.7", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.6", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.6", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@types/node": "^8.0.53", | ||||
|         "lodash": "^4.17.10" | ||||
|     }, | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "1.0.1-rc.6", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1535377027 | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.1-rc.5", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.1-rc.6 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.1-rc.5 - _August 24, 2018_ | ||||
|  | ||||
|     * Update incorrect relayer api fee recipients response schema (#974) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/json-schemas", | ||||
|     "version": "1.0.1-rc.5", | ||||
|     "version": "1.0.1-rc.6", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -46,7 +46,7 @@ | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@types/lodash.foreach": "^4.5.3", | ||||
|         "@types/lodash.values": "^4.3.3", | ||||
|         "@types/mocha": "^2.2.42", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/metacoin", | ||||
|     "version": "0.0.16", | ||||
|     "version": "0.0.17", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -29,15 +29,15 @@ | ||||
|     "author": "", | ||||
|     "license": "Apache-2.0", | ||||
|     "dependencies": { | ||||
|         "@0xproject/abi-gen": "^1.0.6", | ||||
|         "@0xproject/base-contract": "^2.0.0", | ||||
|         "@0xproject/sol-cov": "^2.1.0", | ||||
|         "@0xproject/subproviders": "^2.0.0", | ||||
|         "@0xproject/abi-gen": "^1.0.7", | ||||
|         "@0xproject/base-contract": "^2.0.1", | ||||
|         "@0xproject/sol-cov": "^2.1.1", | ||||
|         "@0xproject/subproviders": "^2.0.1", | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/web3-wrapper": "^2.0.0", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@0xproject/web3-wrapper": "^2.0.1", | ||||
|         "@types/mocha": "^5.2.2", | ||||
|         "copyfiles": "^2.0.0", | ||||
|         "ethereum-types": "^1.0.5", | ||||
| @@ -46,8 +46,8 @@ | ||||
|         "run-s": "^0.0.0" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@0xproject/dev-utils": "^1.0.5", | ||||
|         "@0xproject/sol-compiler": "^1.1.0", | ||||
|         "@0xproject/dev-utils": "^1.0.6", | ||||
|         "@0xproject/sol-compiler": "^1.1.1", | ||||
|         "chai": "^4.0.1", | ||||
|         "chai-as-promised": "^7.1.0", | ||||
|         "chai-bignumber": "^2.0.1", | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1535377027, | ||||
|         "version": "1.0.6", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1535133899, | ||||
|         "version": "1.0.5", | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.6 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.5 - _August 24, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -782,5 +782,11 @@ | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 	"networks": {} | ||||
| 	"networks": { | ||||
| 		"50": { | ||||
| 			"address": "0xe86bb98fcf9bff3512c74589b78fb168200cc546", | ||||
| 			"links": {}, | ||||
| 			"constructorArgs": "[\"0x48bacb9266a570d521063ef5dd96e61686dbe788\",\"0xf47261b0000000000000000000000000871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c\"]" | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/migrations", | ||||
|     "version": "1.0.5", | ||||
|     "version": "1.0.6", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -35,10 +35,10 @@ | ||||
|     }, | ||||
|     "license": "Apache-2.0", | ||||
|     "devDependencies": { | ||||
|         "@0xproject/abi-gen": "^1.0.6", | ||||
|         "@0xproject/dev-utils": "^1.0.5", | ||||
|         "@0xproject/abi-gen": "^1.0.7", | ||||
|         "@0xproject/dev-utils": "^1.0.6", | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@types/yargs": "^10.0.0", | ||||
|         "copyfiles": "^2.0.0", | ||||
|         "make-promises-safe": "^1.1.0", | ||||
| @@ -49,13 +49,13 @@ | ||||
|         "yargs": "^10.0.3" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/base-contract": "^2.0.0", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.4", | ||||
|         "@0xproject/sol-compiler": "^1.1.0", | ||||
|         "@0xproject/subproviders": "^2.0.0", | ||||
|         "@0xproject/base-contract": "^2.0.1", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.6", | ||||
|         "@0xproject/sol-compiler": "^1.1.1", | ||||
|         "@0xproject/subproviders": "^2.0.1", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/web3-wrapper": "^2.0.0", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@0xproject/web3-wrapper": "^2.0.1", | ||||
|         "@ledgerhq/hw-app-eth": "^4.3.0", | ||||
|         "ethereum-types": "^1.0.5", | ||||
|         "ethers": "3.0.22", | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|     "private": true, | ||||
|     "name": "@0xproject/monorepo-scripts", | ||||
|     "version": "1.0.6", | ||||
|     "version": "1.0.7", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
|   | ||||
| @@ -74,9 +74,11 @@ async function confirmAsync(message: string): Promise<void> { | ||||
|     }); | ||||
|     utils.log(`Calling 'lerna publish'...`); | ||||
|     await lernaPublishAsync(packageToNextVersion); | ||||
|     const isStaging = false; | ||||
|     const shouldUploadDocs = !configs.IS_LOCAL_PUBLISH; | ||||
|     await generateAndUploadDocJsonsAsync(packagesWithDocs, isStaging, shouldUploadDocs); | ||||
|     if (!configs.IS_LOCAL_PUBLISH) { | ||||
|         const isStaging = false; | ||||
|         const shouldUploadDocs = true; | ||||
|         await generateAndUploadDocJsonsAsync(packagesWithDocs, isStaging, shouldUploadDocs); | ||||
|     } | ||||
|     const isDryRun = configs.IS_LOCAL_PUBLISH; | ||||
|     await publishReleaseNotesAsync(updatedPublicPackages, isDryRun); | ||||
| })().catch(err => { | ||||
|   | ||||
| @@ -85,11 +85,13 @@ function logIfDefined(x: any): void { | ||||
|             logIfDefined(packageError.error.stdout); | ||||
|             logIfDefined(packageError.error.stack); | ||||
|         }); | ||||
|         process.exit(1); | ||||
|     } else { | ||||
|         process.exit(0); | ||||
|     } | ||||
| })().catch(err => { | ||||
|     utils.log(`Unexpected error: ${err.message}`); | ||||
|     process.exit(0); | ||||
|     process.exit(1); | ||||
| }); | ||||
|  | ||||
| async function testInstallPackageAsync( | ||||
| @@ -144,7 +146,7 @@ async function testInstallPackageAsync( | ||||
|         const transpiledIndexFilePath = path.join(testDirectory, 'index.js'); | ||||
|         utils.log(`Running test script with ${packageName} imported`); | ||||
|         await execAsync(`node ${transpiledIndexFilePath}`); | ||||
|         utils.log(`Successfilly ran test script with ${packageName} imported`); | ||||
|         utils.log(`Successfully ran test script with ${packageName} imported`); | ||||
|     } | ||||
|     await rimrafAsync(testDirectory); | ||||
| } | ||||
|   | ||||
| @@ -19,11 +19,6 @@ CHANGELOG | ||||
|  | ||||
| export const changelogUtils = { | ||||
|     getChangelogMdTitle(versionChangelog: VersionChangelog): string { | ||||
|         if (_.isUndefined(versionChangelog.timestamp)) { | ||||
|             throw new Error( | ||||
|                 'All CHANGELOG.json entries must be updated to include a timestamp before generating their MD version', | ||||
|             ); | ||||
|         } | ||||
|         const date = moment(`${versionChangelog.timestamp}`, 'X').format('MMMM D, YYYY'); | ||||
|         const title = `\n## v${versionChangelog.version} - _${date}_\n\n`; | ||||
|         return title; | ||||
|   | ||||
| @@ -1,4 +1,22 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "1.0.1-rc.6", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Fix missing `BlockParamLiteral` type import issue" | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1535377027 | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.1-rc.5", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Remove Caller and Trezor SignatureTypes", | ||||
|                 "pr": 1015 | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.1-rc.4", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.1-rc.6 - _August 27, 2018_ | ||||
|  | ||||
|     * Fix missing `BlockParamLiteral` type import issue | ||||
|  | ||||
| ## v1.0.1-rc.5 - _Invalid date_ | ||||
|  | ||||
|     * Remove Caller and Trezor SignatureTypes (#1015) | ||||
|  | ||||
| ## v1.0.1-rc.4 - _August 24, 2018_ | ||||
|  | ||||
|     * Remove rounding error being thrown when maker amount is very small (#959) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/order-utils", | ||||
|     "version": "1.0.1-rc.4", | ||||
|     "version": "1.0.1-rc.6", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -40,7 +40,7 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0xproject/dev-utils": "^1.0.5", | ||||
|         "@0xproject/dev-utils": "^1.0.6", | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "@types/bn.js": "^4.11.0", | ||||
|         "@types/lodash": "4.14.104", | ||||
| @@ -59,13 +59,13 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/assert": "^1.0.6", | ||||
|         "@0xproject/base-contract": "^2.0.0", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.5", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/assert": "^1.0.7", | ||||
|         "@0xproject/base-contract": "^2.0.1", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.6", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/web3-wrapper": "^2.0.0", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@0xproject/web3-wrapper": "^2.0.1", | ||||
|         "@types/node": "^8.0.53", | ||||
|         "bn.js": "^4.11.8", | ||||
|         "ethereum-types": "^1.0.5", | ||||
|   | ||||
| @@ -81,7 +81,7 @@ export class OrderStateUtils { | ||||
|         const remainingTakerAssetAmount = signedOrder.takerAssetAmount.minus( | ||||
|             sidedOrderRelevantState.filledTakerAssetAmount, | ||||
|         ); | ||||
|         const isRoundingError = OrderValidationUtils.isRoundingError( | ||||
|         const isRoundingError = OrderValidationUtils.isRoundingErrorFloor( | ||||
|             remainingTakerAssetAmount, | ||||
|             signedOrder.takerAssetAmount, | ||||
|             signedOrder.makerAssetAmount, | ||||
| @@ -191,7 +191,7 @@ export class OrderStateUtils { | ||||
|         ); | ||||
|         const remainingFillableTakerAssetAmountGivenMakersStatus = signedOrder.makerAssetAmount.eq(0) | ||||
|             ? new BigNumber(0) | ||||
|             : utils.getPartialAmount( | ||||
|             : utils.getPartialAmountFloor( | ||||
|                   orderRelevantMakerState.remainingFillableAssetAmount, | ||||
|                   signedOrder.makerAssetAmount, | ||||
|                   signedOrder.takerAssetAmount, | ||||
|   | ||||
| @@ -24,7 +24,7 @@ export class OrderValidationUtils { | ||||
|      * @param denominator Denominator value.  When used to check an order, pass in `order.takerAssetAmount` | ||||
|      * @param target Target value. When used to check an order, pass in `order.makerAssetAmount` | ||||
|      */ | ||||
|     public static isRoundingError(numerator: BigNumber, denominator: BigNumber, target: BigNumber): boolean { | ||||
|     public static isRoundingErrorFloor(numerator: BigNumber, denominator: BigNumber, target: BigNumber): boolean { | ||||
|         // Solidity's mulmod() in JS | ||||
|         // Source: https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#mathematical-and-cryptographic-functions | ||||
|         if (denominator.eq(0)) { | ||||
| @@ -58,7 +58,7 @@ export class OrderValidationUtils { | ||||
|         zrxAssetData: string, | ||||
|     ): Promise<void> { | ||||
|         try { | ||||
|             const fillMakerTokenAmount = utils.getPartialAmount( | ||||
|             const fillMakerTokenAmount = utils.getPartialAmountFloor( | ||||
|                 fillTakerAssetAmount, | ||||
|                 signedOrder.takerAssetAmount, | ||||
|                 signedOrder.makerAssetAmount, | ||||
| @@ -79,7 +79,7 @@ export class OrderValidationUtils { | ||||
|                 TradeSide.Taker, | ||||
|                 TransferType.Trade, | ||||
|             ); | ||||
|             const makerFeeAmount = utils.getPartialAmount( | ||||
|             const makerFeeAmount = utils.getPartialAmountFloor( | ||||
|                 fillTakerAssetAmount, | ||||
|                 signedOrder.takerAssetAmount, | ||||
|                 signedOrder.makerFee, | ||||
| @@ -92,7 +92,7 @@ export class OrderValidationUtils { | ||||
|                 TradeSide.Maker, | ||||
|                 TransferType.Fee, | ||||
|             ); | ||||
|             const takerFeeAmount = utils.getPartialAmount( | ||||
|             const takerFeeAmount = utils.getPartialAmountFloor( | ||||
|                 fillTakerAssetAmount, | ||||
|                 signedOrder.takerAssetAmount, | ||||
|                 signedOrder.takerFee, | ||||
| @@ -218,7 +218,7 @@ export class OrderValidationUtils { | ||||
|             zrxAssetData, | ||||
|         ); | ||||
|  | ||||
|         const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingError( | ||||
|         const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingErrorFloor( | ||||
|             desiredFillTakerTokenAmount, | ||||
|             signedOrder.takerAssetAmount, | ||||
|             signedOrder.makerAssetAmount, | ||||
|   | ||||
| @@ -53,11 +53,6 @@ export const signatureUtils = { | ||||
|                 return signatureUtils.isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); | ||||
|             } | ||||
|  | ||||
|             case SignatureType.Caller: | ||||
|                 // HACK: We currently do not "validate" the caller signature type. | ||||
|                 // It can only be validated during Exchange contract execution. | ||||
|                 throw new Error('Caller signature type cannot be validated off-chain'); | ||||
|  | ||||
|             case SignatureType.Wallet: { | ||||
|                 const isValid = await signatureUtils.isValidWalletSignatureAsync( | ||||
|                     provider, | ||||
| @@ -82,12 +77,6 @@ export const signatureUtils = { | ||||
|                 return signatureUtils.isValidPresignedSignatureAsync(provider, data, signerAddress); | ||||
|             } | ||||
|  | ||||
|             case SignatureType.Trezor: { | ||||
|                 const prefixedMessageHex = signatureUtils.addSignedMessagePrefix(data, SignerType.Trezor); | ||||
|                 const ecSignature = signatureUtils.parseECSignature(signature); | ||||
|                 return signatureUtils.isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); | ||||
|             } | ||||
|  | ||||
|             default: | ||||
|                 throw new Error(`Unhandled SignatureType: ${signatureTypeIndexIfExists}`); | ||||
|         } | ||||
| @@ -293,10 +282,6 @@ export const signatureUtils = { | ||||
|                 signatureType = SignatureType.EthSign; | ||||
|                 break; | ||||
|             } | ||||
|             case SignerType.Trezor: { | ||||
|                 signatureType = SignatureType.Trezor; | ||||
|                 break; | ||||
|             } | ||||
|             default: | ||||
|                 throw new Error(`Unrecognized SignerType: ${signerType}`); | ||||
|         } | ||||
| @@ -306,7 +291,7 @@ export const signatureUtils = { | ||||
|     /** | ||||
|      * Combines the signature proof and the Signature Type. | ||||
|      * @param signature The hex encoded signature proof | ||||
|      * @param signatureType The signature type, i.e EthSign, Trezor, Wallet etc. | ||||
|      * @param signatureType The signature type, i.e EthSign, Wallet etc. | ||||
|      * @return Hex encoded string of signature proof with Signature Type | ||||
|      */ | ||||
|     convertToSignatureWithType(signature: string, signatureType: SignatureType): string { | ||||
| @@ -333,12 +318,6 @@ export const signatureUtils = { | ||||
|                 const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); | ||||
|                 return prefixedMsgHex; | ||||
|             } | ||||
|             case SignerType.Trezor: { | ||||
|                 const msgBuff = ethUtil.toBuffer(message); | ||||
|                 const prefixedMsgBuff = hashTrezorPersonalMessage(msgBuff); | ||||
|                 const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); | ||||
|                 return prefixedMsgHex; | ||||
|             } | ||||
|             default: | ||||
|                 throw new Error(`Unrecognized SignerType: ${signerType}`); | ||||
|         } | ||||
| @@ -350,7 +329,7 @@ export const signatureUtils = { | ||||
|      */ | ||||
|     parseECSignature(signature: string): ECSignature { | ||||
|         assert.isHexString('signature', signature); | ||||
|         const ecSignatureTypes = [SignatureType.EthSign, SignatureType.EIP712, SignatureType.Trezor]; | ||||
|         const ecSignatureTypes = [SignatureType.EthSign, SignatureType.EIP712]; | ||||
|         assert.isOneOfExpectedSignatureTypes(signature, ecSignatureTypes); | ||||
|  | ||||
|         // tslint:disable-next-line:custom-no-magic-numbers | ||||
| @@ -361,11 +340,6 @@ export const signatureUtils = { | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| function hashTrezorPersonalMessage(message: Buffer): Buffer { | ||||
|     const prefix = ethUtil.toBuffer('\x19Ethereum Signed Message:\n' + String.fromCharCode(message.byteLength)); | ||||
|     return ethUtil.sha3(Buffer.concat([prefix, message])); | ||||
| } | ||||
|  | ||||
| function parseValidatorSignature(signature: string): ValidatorSignature { | ||||
|     assert.isOneOfExpectedSignatureTypes(signature, [SignatureType.Validator]); | ||||
|     // tslint:disable:custom-no-magic-numbers | ||||
|   | ||||
| @@ -12,7 +12,7 @@ export const utils = { | ||||
|         const milisecondsInSecond = 1000; | ||||
|         return new BigNumber(Date.now() / milisecondsInSecond).round(); | ||||
|     }, | ||||
|     getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { | ||||
|     getPartialAmountFloor(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { | ||||
|         const fillMakerTokenAmount = numerator | ||||
|             .mul(target) | ||||
|             .div(denominator) | ||||
|   | ||||
| @@ -16,7 +16,7 @@ describe('OrderValidationUtils', () => { | ||||
|             const denominator = new BigNumber(999); | ||||
|             const target = new BigNumber(50); | ||||
|             // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1% | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); | ||||
|             expect(isRoundingError).to.be.false(); | ||||
|         }); | ||||
|  | ||||
| @@ -25,7 +25,7 @@ describe('OrderValidationUtils', () => { | ||||
|             const denominator = new BigNumber(9991); | ||||
|             const target = new BigNumber(500); | ||||
|             // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09% | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); | ||||
|             expect(isRoundingError).to.be.false(); | ||||
|         }); | ||||
|  | ||||
| @@ -34,7 +34,7 @@ describe('OrderValidationUtils', () => { | ||||
|             const denominator = new BigNumber(9989); | ||||
|             const target = new BigNumber(500); | ||||
|             // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011% | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); | ||||
|             expect(isRoundingError).to.be.true(); | ||||
|         }); | ||||
|  | ||||
| @@ -43,7 +43,7 @@ describe('OrderValidationUtils', () => { | ||||
|             const denominator = new BigNumber(7); | ||||
|             const target = new BigNumber(10); | ||||
|             // rounding error = ((3*10/7) - floor(3*10/7)) / (3*10/7) = 6.67% | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); | ||||
|             expect(isRoundingError).to.be.true(); | ||||
|         }); | ||||
|  | ||||
| @@ -52,7 +52,7 @@ describe('OrderValidationUtils', () => { | ||||
|             const denominator = new BigNumber(2); | ||||
|             const target = new BigNumber(10); | ||||
|  | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); | ||||
|             expect(isRoundingError).to.be.false(); | ||||
|         }); | ||||
|  | ||||
| @@ -63,7 +63,7 @@ describe('OrderValidationUtils', () => { | ||||
|             const target = new BigNumber(105762562); | ||||
|             // rounding error = ((76564*105762562/676373677) - floor(76564*105762562/676373677)) / | ||||
|             // (76564*105762562/676373677) = 0.0007% | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); | ||||
|             const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); | ||||
|             expect(isRoundingError).to.be.false(); | ||||
|         }); | ||||
|     }); | ||||
|   | ||||
| @@ -76,20 +76,6 @@ describe('Signature utils', () => { | ||||
|             ); | ||||
|             expect(isValidSignatureLocal).to.be.true(); | ||||
|         }); | ||||
|  | ||||
|         it('should return true for a valid Trezor signature', async () => { | ||||
|             dataHex = '0xd0d994e31c88f33fd8a572552a70ed339de579e5ba49ee1d17cc978bbe1cdd21'; | ||||
|             address = '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb'; | ||||
|             const trezorSignature = | ||||
|                 '0x1ce4760660e6495b5ae6723087bea073b3a99ce98ea81fdf00c240279c010e63d05b87bc34c4d67d4776e8d5aeb023a67484f4eaf0fd353b40893e5101e845cd9908'; | ||||
|             const isValidSignatureLocal = await signatureUtils.isValidSignatureAsync( | ||||
|                 provider, | ||||
|                 dataHex, | ||||
|                 trezorSignature, | ||||
|                 address, | ||||
|             ); | ||||
|             expect(isValidSignatureLocal).to.be.true(); | ||||
|         }); | ||||
|     }); | ||||
|     describe('#isValidECSignature', () => { | ||||
|         const signature = { | ||||
| @@ -270,15 +256,6 @@ describe('Signature utils', () => { | ||||
|             r: '0xaca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d64393', | ||||
|             s: '0x46b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf2', | ||||
|         }; | ||||
|         it('should concatenate v,r,s and append the Trezor signature type', async () => { | ||||
|             const expectedSignatureWithSignatureType = | ||||
|                 '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf208'; | ||||
|             const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex( | ||||
|                 ecSignature, | ||||
|                 SignerType.Trezor, | ||||
|             ); | ||||
|             expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType); | ||||
|         }); | ||||
|         it('should concatenate v,r,s and append the EthSign signature type when SignerType is Default', async () => { | ||||
|             const expectedSignatureWithSignatureType = | ||||
|                 '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203'; | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "1.0.1-rc.5", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Fix missing `BlockParamLiteral` type import issue" | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1535377027 | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.1-rc.4", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.1-rc.5 - _August 27, 2018_ | ||||
|  | ||||
|     * Fix missing `BlockParamLiteral` type import issue | ||||
|  | ||||
| ## v1.0.1-rc.4 - _August 24, 2018_ | ||||
|  | ||||
|     * Export types: `ExchangeContractErrs`, `OrderRelevantState`, `JSONRPCRequestPayload`, `JSONRPCErrorCallback` and `JSONRPCResponsePayload` (#924) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/order-watcher", | ||||
|     "version": "1.0.1-rc.4", | ||||
|     "version": "1.0.1-rc.5", | ||||
|     "description": "An order watcher daemon that watches for order validity", | ||||
|     "keywords": [ | ||||
|         "0x", | ||||
| @@ -43,9 +43,9 @@ | ||||
|         "node": ">=6.0.0" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@0xproject/abi-gen": "^1.0.6", | ||||
|         "@0xproject/dev-utils": "^1.0.5", | ||||
|         "@0xproject/migrations": "^1.0.5", | ||||
|         "@0xproject/abi-gen": "^1.0.7", | ||||
|         "@0xproject/dev-utils": "^1.0.6", | ||||
|         "@0xproject/migrations": "^1.0.6", | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "@types/bintrees": "^1.0.2", | ||||
|         "@types/lodash": "4.14.104", | ||||
| @@ -71,16 +71,16 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/assert": "^1.0.6", | ||||
|         "@0xproject/base-contract": "^2.0.0", | ||||
|         "@0xproject/contract-wrappers": "^1.0.1-rc.4", | ||||
|         "@0xproject/fill-scenarios": "^1.0.1-rc.4", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.5", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.4", | ||||
|         "@0xproject/types": "^1.0.1-rc.5", | ||||
|         "@0xproject/assert": "^1.0.7", | ||||
|         "@0xproject/base-contract": "^2.0.1", | ||||
|         "@0xproject/contract-wrappers": "^1.0.1-rc.5", | ||||
|         "@0xproject/fill-scenarios": "^1.0.1-rc.5", | ||||
|         "@0xproject/json-schemas": "^1.0.1-rc.6", | ||||
|         "@0xproject/order-utils": "^1.0.1-rc.6", | ||||
|         "@0xproject/types": "^1.0.1-rc.6", | ||||
|         "@0xproject/typescript-typings": "^1.0.5", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/web3-wrapper": "^2.0.0", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@0xproject/web3-wrapper": "^2.0.1", | ||||
|         "bintrees": "^1.0.2", | ||||
|         "ethereum-types": "^1.0.5", | ||||
|         "ethereumjs-blockstream": "5.0.0", | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1535377027, | ||||
|         "version": "1.0.7", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1535133899, | ||||
|         "version": "1.0.6", | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.7 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.6 - _August 24, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/react-docs", | ||||
|     "version": "1.0.6", | ||||
|     "version": "1.0.7", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -24,7 +24,7 @@ | ||||
|         "url": "https://github.com/0xProject/0x-monorepo.git" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@0xproject/dev-utils": "^1.0.5", | ||||
|         "@0xproject/dev-utils": "^1.0.6", | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "@types/compare-versions": "^3.0.0", | ||||
|         "copyfiles": "^2.0.0", | ||||
| @@ -34,8 +34,8 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0xproject/react-shared": "^1.0.7", | ||||
|         "@0xproject/utils": "^1.0.6", | ||||
|         "@0xproject/react-shared": "^1.0.8", | ||||
|         "@0xproject/utils": "^1.0.7", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "@types/material-ui": "0.18.0", | ||||
|         "@types/node": "^8.0.53", | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1535377027, | ||||
|         "version": "1.0.8", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1535133899, | ||||
|         "version": "1.0.7", | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.8 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.7 - _August 24, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0xproject/react-shared", | ||||
|     "version": "1.0.7", | ||||
|     "version": "1.0.8", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -24,7 +24,7 @@ | ||||
|         "url": "https://github.com/0xProject/0x-monorepo.git" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@0xproject/dev-utils": "^1.0.5", | ||||
|         "@0xproject/dev-utils": "^1.0.6", | ||||
|         "@0xproject/tslint-config": "^1.0.6", | ||||
|         "copyfiles": "^2.0.0", | ||||
|         "make-promises-safe": "^1.1.0", | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1535377027, | ||||
|         "version": "1.1.1", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.1.0", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.1.1 - _August 27, 2018_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.1.0 - _August 24, 2018_ | ||||
|  | ||||
|     * Quicken compilation by sending multiple contracts to the same solcjs invocation, batching them together based on compiler version requirements. (#965) | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user