Merge branch 'development' into dev-dropdown

* development: (120 commits)
  Exit with non-error code at end of publishRelease
  Publish
  Updated CHANGELOGS
  Fix prettier
  Update changelog files for RC packages
  Skip doc generation for local publishes since we test this in a separate CI test
  Fix typo
  Use absolute path
  Run yarn a second time if the first fails
  Try relative to root dir
  Fix command
  Move md files to lib folder during build
  Change exit code to failure
  Fix sra-spec `main` and `types` in package.json
  Actual relative path
  Try relative path
  Issue #1025 BlockParam unroll
  Fix command
  Move md files to lib folder during build
  Fix sra-spec `main` and `types` in package.json
  ...
This commit is contained in:
Fabio Berger 2018-08-27 15:28:57 +01:00
commit b8241c0f80
171 changed files with 3499 additions and 1204 deletions

View File

@ -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" }}

View File

@ -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

View File

@ -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) | [![npm](https://img.shields.io/npm/v/@0xproject/monorepo-scripts.svg)](https://www.npmjs.com/package/@0xproject/monorepo-scripts) | Monorepo scripts |
| [`@0xproject/react-docs`](/packages/react-docs) | [![npm](https://img.shields.io/npm/v/@0xproject/react-docs.svg)](https://www.npmjs.com/package/@0xproject/react-docs) | React documentation component for rendering TypeDoc & Doxity generated JSON |
| [`@0xproject/react-shared`](/packages/react-shared) | [![npm](https://img.shields.io/npm/v/@0xproject/react-shared.svg)](https://www.npmjs.com/package/@0xproject/react-shared) | 0x shared react components |
| [`@0xproject/sra-api`](/packages/sra-api) | [![npm](https://img.shields.io/npm/v/@0xproject/sra-api.svg)](https://www.npmjs.com/package/@0xproject/sra-api) | OpenAPI specification for the standard relayer API |
| [`@0xproject/sra-spec`](/packages/sra-spec) | [![npm](https://img.shields.io/npm/v/@0xproject/sra-spec.svg)](https://www.npmjs.com/package/@0xproject/sra-spec) | OpenAPI specification for the standard relayer API |
| [`@0xproject/sra-report`](/packages/sra-report) | [![npm](https://img.shields.io/npm/v/@0xproject/sra-report.svg)](https://www.npmjs.com/package/@0xproject/sra-report) | Generate reports for standard relayer API compliance |
| [`@0xproject/sol-cov`](/packages/sol-cov) | [![npm](https://img.shields.io/npm/v/@0xproject/sol-cov.svg)](https://www.npmjs.com/package/@0xproject/sol-cov) | Solidity test coverage tool |
| [`@0xproject/subproviders`](/packages/subproviders) | [![npm](https://img.shields.io/npm/v/@0xproject/subproviders.svg)](https://www.npmjs.com/package/@0xproject/subproviders) | Useful web3 subproviders (e.g LedgerSubprovider) |

View File

@ -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": [
@ -7,7 +25,8 @@
"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`",
"pr": 963
}
]
],
"timestamp": 1535133899
},
{
"version": "1.0.1-rc.3",

View File

@ -5,7 +5,19 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.1-rc.3 - _August 13, 2018_
## 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)
## v1.0.1-rc.3 - _August 14, 2018_
* Dependencies updated
* Update ecSignOrderHashAsync to return the signature as a string for immediate use in contracts (#914)
@ -22,11 +34,11 @@ CHANGELOG
* Dependencies updated
## v1.0.0-rc.2 - _July 19, 2018_
## v1.0.0-rc.2 - _July 20, 2018_
* Remove `zeroEx.assetData` and instead re-export it's static functions directly off `ZeroEx`
## v1.0.0-rc.1 - _July 19, 2018_
## v1.0.0-rc.1 - _July 20, 2018_
* Remove tokenRegistry wrapper (#863)
* Rename `zeroEx.token` to `zeroEx.erc20Token`, and add `zeroEx.erc721Token` (#863)
@ -66,7 +78,7 @@ CHANGELOG
* Renamed createOrderStateWatcher to createOrderWatcherAsync since it is now async (#579)
* Renamed ZeroExError to ContractWrappersErrors since they now lives in the @0xproject/contract-wrappers subpackage (#579)
## v0.37.2 - _May 4, 2018_
## v0.37.2 - _May 5, 2018_
* Dependencies updated

View File

@ -1,6 +1,6 @@
{
"name": "0x.js",
"version": "1.0.1-rc.3",
"version": "1.0.1-rc.6",
"engines": {
"node": ">=6.12"
},
@ -12,15 +12,14 @@
"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",
"build:all": "run-p build:umd:prod build:commonjs",
"lint": "tslint --project . --exclude **/src/generated_contract_wrappers/**/*",
"test:circleci": "run-s test:coverage",
"test": "yarn run_mocha",
"rebuild_and_test": "run-s build test",
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
@ -43,11 +42,11 @@
},
"license": "Apache-2.0",
"devDependencies": {
"@0xproject/abi-gen": "^1.0.5",
"@0xproject/dev-utils": "^1.0.4",
"@0xproject/migrations": "^1.0.4",
"@0xproject/monorepo-scripts": "^1.0.5",
"@0xproject/tslint-config": "^1.0.5",
"@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",
"@types/node": "^8.0.53",
@ -74,17 +73,17 @@
"webpack": "^3.1.0"
},
"dependencies": {
"@0xproject/assert": "^1.0.5",
"@0xproject/base-contract": "^2.0.0-rc.1",
"@0xproject/contract-wrappers": "^1.0.1-rc.3",
"@0xproject/order-utils": "^1.0.1-rc.3",
"@0xproject/order-watcher": "1.0.1-rc.3",
"@0xproject/subproviders": "^1.0.5",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/web3-wrapper": "^1.2.0",
"ethereum-types": "^1.0.4",
"@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.7",
"@0xproject/web3-wrapper": "^2.0.1",
"ethereum-types": "^1.0.5",
"ethers": "3.0.22",
"lodash": "^4.17.5",
"web3-provider-engine": "14.0.6"

View File

@ -1,4 +1,22 @@
[
{
"timestamp": 1535377027,
"version": "1.0.7",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1535133899,
"version": "1.0.6",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1534210131,
"version": "1.0.5",

View File

@ -5,7 +5,15 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.5 - _August 13, 2018_
## v1.0.7 - _August 27, 2018_
* Dependencies updated
## v1.0.6 - _August 24, 2018_
* Dependencies updated
## v1.0.5 - _August 14, 2018_
* Dependencies updated
@ -25,7 +33,7 @@ CHANGELOG
* Fix the abi-gen entry point in package.json (#901)
## v1.0.0 - _July 19, 2018_
## v1.0.0 - _July 20, 2018_
* Convert e_r_c to erc in generated file names (#822)
* Remove the output directory before writing to it (#822)
@ -43,7 +51,7 @@ CHANGELOG
* Dependencies updated
## v0.3.1 - _May 31, 2018_
## v0.3.1 - _June 1, 2018_
* Incorrect publish that was unpublished
@ -51,7 +59,7 @@ CHANGELOG
* Properly export the executable binary (#588)
## v0.2.13 - _May 4, 2018_
## v0.2.13 - _May 5, 2018_
* Dependencies updated

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/abi-gen",
"version": "1.0.5",
"version": "1.0.7",
"engines": {
"node": ">=6.12"
},
@ -31,10 +31,10 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/abi-gen/README.md",
"dependencies": {
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/typescript-typings": "^1.0.5",
"@0xproject/utils": "^1.0.7",
"chalk": "^2.3.0",
"ethereum-types": "^1.0.4",
"ethereum-types": "^1.0.5",
"glob": "^7.1.2",
"handlebars": "^4.0.11",
"lodash": "^4.17.5",
@ -45,7 +45,7 @@
"yargs": "^10.0.3"
},
"devDependencies": {
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/tslint-config": "^1.0.6",
"@types/glob": "5.0.35",
"@types/handlebars": "^4.0.36",
"@types/mkdirp": "^0.5.1",

View File

@ -1,4 +1,22 @@
[
{
"timestamp": 1535377027,
"version": "1.0.7",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1535133899,
"version": "1.0.6",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1534210131,
"version": "1.0.5",

View File

@ -5,7 +5,15 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.5 - _August 13, 2018_
## v1.0.7 - _August 27, 2018_
* Dependencies updated
## v1.0.6 - _August 24, 2018_
* Dependencies updated
## v1.0.5 - _August 14, 2018_
* Dependencies updated
@ -25,7 +33,7 @@ CHANGELOG
* Dependencies updated
## v1.0.0 - _July 19, 2018_
## v1.0.0 - _July 20, 2018_
* Fix bug in string enum assertion. We erroneously were checking against the enum keys, not values (#821)
@ -41,7 +49,7 @@ CHANGELOG
* Dependencies updated
## v0.2.11 - _May 31, 2018_
## v0.2.11 - _June 1, 2018_
* Incorrect publish that was unpublished
@ -49,7 +57,7 @@ CHANGELOG
* Dependencies updated
## v0.2.9 - _May 4, 2018_
## v0.2.9 - _May 5, 2018_
* Dependencies updated

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/assert",
"version": "1.0.5",
"version": "1.0.7",
"engines": {
"node": ">=6.12"
},
@ -29,7 +29,7 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/assert/README.md",
"devDependencies": {
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/tslint-config": "^1.0.6",
"@types/lodash": "4.14.104",
"@types/mocha": "^2.2.42",
"@types/valid-url": "^1.0.2",
@ -45,9 +45,9 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0xproject/json-schemas": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/json-schemas": "^1.0.1-rc.6",
"@0xproject/typescript-typings": "^1.0.5",
"@0xproject/utils": "^1.0.7",
"lodash": "^4.17.5",
"valid-url": "^1.0.9"
},

View File

@ -1,4 +1,22 @@
[
{
"timestamp": 1535377027,
"version": "2.0.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1535133899,
"version": "2.0.0",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "2.0.0-rc.1",
"changes": [

View File

@ -5,7 +5,15 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v2.0.0-rc.1 - _August 13, 2018_
## v2.0.1 - _August 27, 2018_
* Dependencies updated
## v2.0.0 - _August 24, 2018_
* Dependencies updated
## v2.0.0-rc.1 - _August 14, 2018_
* Added strict encoding/decoding checks for sendTransaction and call (#915)
@ -25,7 +33,7 @@ CHANGELOG
* Dependencies updated
## v1.0.0 - _July 19, 2018_
## v1.0.0 - _July 20, 2018_
* Dependencies updated
@ -41,7 +49,7 @@ CHANGELOG
* Update EthersJs to fix the `value.toLowerCase()` is not a function bug caused by `ethers.js` breaking patch version https://github.com/ethers-io/ethers.js/issues/201
## v0.3.3 - _May 31, 2018_
## v0.3.3 - _June 1, 2018_
* Incorrect publish that was unpublished
@ -49,7 +57,7 @@ CHANGELOG
* Dependencies updated
## v0.3.1 - _May 4, 2018_
## v0.3.1 - _May 5, 2018_
* Dependencies updated

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/base-contract",
"version": "2.0.0-rc.1",
"version": "2.0.1",
"engines": {
"node": ">=6.12"
},
@ -29,7 +29,7 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/base-contract/README.md",
"devDependencies": {
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/tslint-config": "^1.0.6",
"@types/lodash": "4.14.104",
"chai": "^4.0.1",
"copyfiles": "^2.0.0",
@ -41,10 +41,10 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/web3-wrapper": "^1.2.0",
"ethereum-types": "^1.0.4",
"@0xproject/typescript-typings": "^1.0.5",
"@0xproject/utils": "^1.0.7",
"@0xproject/web3-wrapper": "^2.0.1",
"ethereum-types": "^1.0.5",
"ethers": "3.0.22",
"lodash": "^4.17.5"
},

View File

@ -1,4 +1,13 @@
[
{
"version": "2.0.0-rc.2",
"changes": [
{
"note": "Dependencies updated"
}
],
"timestamp": 1535377027
},
{
"version": "2.0.0-rc.1",
"changes": [
@ -10,7 +19,8 @@
"note": "Stopped exporting `Order` type",
"pr": 924
}
]
],
"timestamp": 1535133899
},
{
"timestamp": 1534210131,

View File

@ -5,7 +5,16 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.5 - _August 13, 2018_
## v2.0.0-rc.2 - _August 27, 2018_
* Dependencies updated
## v2.0.0-rc.1 - _August 24, 2018_
* Updated for SRA v2 (#974)
* Stopped exporting `Order` type (#924)
## v1.0.5 - _August 14, 2018_
* Dependencies updated
@ -25,7 +34,7 @@ CHANGELOG
* Dependencies updated
## v1.0.0 - _July 19, 2018_
## v1.0.0 - _July 20, 2018_
* Remove `WebSocketOrderbookChannel` from the public interface and replace with `orderbookChannelFactory`
@ -49,7 +58,7 @@ CHANGELOG
* Dependencies updated
## v0.6.12 - _May 4, 2018_
## v0.6.12 - _May 5, 2018_
* Dependencies updated

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/connect",
"version": "1.0.5",
"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.5",
"@0xproject/json-schemas": "^1.0.1-rc.4",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.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.7",
"lodash": "^4.17.5",
"query-string": "^5.0.1",
"sinon": "^4.0.0",
@ -56,7 +56,7 @@
"websocket": "^1.0.25"
},
"devDependencies": {
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/tslint-config": "^1.0.6",
"@types/fetch-mock": "^6.0.3",
"@types/lodash": "4.14.104",
"@types/mocha": "^2.2.42",

View File

@ -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": [
@ -16,7 +25,8 @@
"note": "Added Transaction Encoder for use with 0x Exchange executeTransaction",
"pr": 975
}
]
],
"timestamp": 1535133899
},
{
"version": "1.0.1-rc.3",

View File

@ -5,7 +5,17 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.1-rc.3 - _August 13, 2018_
## v1.0.1-rc.5 - _August 27, 2018_
* Fix missing `BlockParamLiteral` type import issue
## v1.0.1-rc.4 - _August 24, 2018_
* Export missing types: `TransactionEncoder`, `ContractAbi`, `JSONRPCRequestPayload`, `JSONRPCResponsePayload`, `JSONRPCErrorCallback`, `AbiDefinition`, `FunctionAbi`, `EventAbi`, `EventParameter`, `DecodedLogArgs`, `MethodAbi`, `ConstructorAbi`, `FallbackAbi`, `DataItem`, `ConstructorStateMutability`, `StateMutability` & `ExchangeSignatureValidatorApprovalEventArgs` (#924)
* Remove superfluous exported types: `ContractEvent`, `Token`, `OrderFillRequest`, `ContractEventArgs`, `LogEvent`, `OnOrderStateChangeCallback`, `ECSignature`, `OrderStateValid`, `OrderStateInvalid`, `OrderState`, `FilterObject`, `TransactionReceipt` & `TransactionReceiptWithDecodedLogs` (#924)
* Added Transaction Encoder for use with 0x Exchange executeTransaction (#975)
## v1.0.1-rc.3 - _August 14, 2018_
* Added strict encoding/decoding checks for sendTransaction and call (#915)
* Add ForwarderWrapper (#934)
@ -23,7 +33,7 @@ CHANGELOG
* Dependencies updated
## v1.0.0-rc.1 - _July 19, 2018_
## v1.0.0-rc.1 - _July 20, 2018_
* Update blockstream to v5.0 and propogate up caught errors to active subscriptions (#815)
* Update to v2 of 0x rpotocol (#822)

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/contract-wrappers",
"version": "1.0.1-rc.3",
"version": "1.0.1-rc.5",
"description": "Smart TS wrappers for 0x smart contracts",
"keywords": [
"0xproject",
@ -44,11 +44,11 @@
"node": ">=6.0.0"
},
"devDependencies": {
"@0xproject/abi-gen": "^1.0.5",
"@0xproject/dev-utils": "^1.0.4",
"@0xproject/migrations": "^1.0.4",
"@0xproject/subproviders": "^1.0.5",
"@0xproject/tslint-config": "^1.0.5",
"@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",
"@types/node": "^8.0.53",
@ -74,16 +74,16 @@
"web3-provider-engine": "14.0.6"
},
"dependencies": {
"@0xproject/assert": "^1.0.5",
"@0xproject/base-contract": "^2.0.0-rc.1",
"@0xproject/fill-scenarios": "^1.0.1-rc.3",
"@0xproject/json-schemas": "^1.0.1-rc.4",
"@0xproject/order-utils": "^1.0.1-rc.3",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/web3-wrapper": "^1.2.0",
"ethereum-types": "^1.0.4",
"@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.7",
"@0xproject/web3-wrapper": "^2.0.1",
"ethereum-types": "^1.0.5",
"ethereumjs-blockstream": "5.0.0",
"ethereumjs-util": "^5.1.1",
"ethers": "3.0.22",

View File

@ -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';

View File

@ -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';

View File

@ -40,6 +40,7 @@
"MultiSigWallet",
"MultiSigWalletWithTimeLock",
"OrderValidator",
"ReentrantERC20Token",
"TestAssetProxyOwner",
"TestAssetProxyDispatcher",
"TestConstants",
@ -47,6 +48,7 @@
"TestLibs",
"TestExchangeInternals",
"TestSignatureValidator",
"TestStaticCallReceiver",
"TokenRegistry",
"Validator",
"Wallet",

View File

@ -1,7 +1,7 @@
{
"private": true,
"name": "contracts",
"version": "2.1.40",
"version": "2.1.42",
"engines": {
"node": ">=6.12"
},
@ -20,14 +20,11 @@
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
"run_mocha":
"mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
"compile": "sol-compiler --contracts-dir src",
"clean": "shx rm -rf lib generated_contract_wrappers",
"generate_contract_wrappers":
"abi-gen --abis ${npm_package_config_abis} --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output generated_contract_wrappers --backend ethers",
"lint":
"tslint --project . --exclude **/src/generated_contract_wrappers/**/* --exclude **/lib/**/* && yarn lint-contracts",
"generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output generated_contract_wrappers --backend ethers",
"lint": "tslint --project . --exclude **/src/generated_contract_wrappers/**/* --exclude **/lib/**/* && yarn lint-contracts",
"coverage:report:text": "istanbul report text",
"coverage:report:html": "istanbul report html && open coverage/index.html",
"profiler:report:html": "istanbul report html && open coverage/index.html",
@ -36,8 +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",
@ -50,12 +46,12 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/contracts/README.md",
"devDependencies": {
"@0xproject/abi-gen": "^1.0.5",
"@0xproject/dev-utils": "^1.0.4",
"@0xproject/sol-cov": "^2.0.0",
"@0xproject/subproviders": "^1.0.5",
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/sol-compiler": "^1.0.5",
"@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",
"@types/lodash": "4.14.104",
@ -77,15 +73,15 @@
"yargs": "^10.0.3"
},
"dependencies": {
"@0xproject/base-contract": "^2.0.0-rc.1",
"@0xproject/order-utils": "^1.0.1-rc.3",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/web3-wrapper": "^1.2.0",
"@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.7",
"@0xproject/web3-wrapper": "^2.0.1",
"@types/js-combinatorics": "^0.5.29",
"bn.js": "^4.11.8",
"ethereum-types": "^1.0.4",
"ethereum-types": "^1.0.5",
"ethereumjs-abi": "0.6.5",
"ethereumjs-util": "^5.1.1",
"ethers": "3.0.22",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -118,6 +118,9 @@ contract ERC20Proxy is
mstore(96, 0)
revert(0, 100)
}
// Revert if undefined function is called
revert(0, 0)
}
}

View File

@ -152,6 +152,9 @@ contract ERC721Proxy is
mstore(96, 0)
revert(0, 100)
}
// Revert if undefined function is called
revert(0, 0)
}
}

View File

@ -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,

View File

@ -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.
@ -264,15 +297,11 @@ contract MixinExchangeCore is
/// @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
@ -284,12 +313,6 @@ contract MixinExchangeCore is
"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(
@ -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

View File

@ -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.
@ -63,6 +66,18 @@ contract MixinMatchOrders is
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,18 +92,16 @@ 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
@ -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;
// The right order receives an amount proportional to how much was spent.
rightTakerAssetFilledAmount = getPartialAmount(
rightOrder.takerAssetAmount,
uint256 rightMakerAssetAmountRemaining = getPartialAmountFloor(
rightOrder.makerAssetAmount,
leftTakerAssetFilledAmount
rightOrder.takerAssetAmount,
rightTakerAssetAmountRemaining
);
// 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;
}

View File

@ -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,6 +50,7 @@ contract MixinSignatureValidator is
)
external
{
if (signerAddress != msg.sender) {
require(
isValidSignature(
hash,
@ -56,6 +59,7 @@ contract MixinSignatureValidator is
),
"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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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,6 +39,11 @@ contract LibMath is
pure
returns (uint256 partialAmount)
{
require(
denominator > 0,
"DIVISION_BY_ZERO"
);
partialAmount = safeDiv(
safeMul(numerator, target),
denominator
@ -46,12 +51,44 @@ contract LibMath is
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.
require(
denominator > 0,
"DIVISION_BY_ZERO"
);
// 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;
}
uint256 errPercentageTimes1000000 = safeDiv(
safeMul(remainder, 1000000),
safeMul(numerator, target)
// 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"
);
isError = errPercentageTimes1000000 > 1000;
// 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;
}
}

View File

@ -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.
@ -86,18 +99,30 @@ contract MExchangeCore is
/// @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;

View File

@ -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);
}

View 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);
}

View 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;
}
}

View File

@ -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.

View File

@ -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

View 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++;
}
}

View 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;
}
}

View File

@ -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

View File

@ -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();

View File

@ -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.

View File

@ -71,20 +71,21 @@ 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 () => {
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.publicIsRoundingError.callAsync(numerator, denominator, target);
expect(isRoundingError).to.be.false();
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);
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 () => {
@ -92,10 +93,37 @@ describe('Exchange libs', () => {
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);
const isRoundingError = await libs.publicIsRoundingErrorFloor.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();
});
});
});
describe('LibOrder', () => {
describe('getOrderSchema', () => {

File diff suppressed because it is too large Load Diff

View File

@ -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', () => {

View File

@ -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),

View File

@ -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,

View File

@ -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',
],
};

View File

@ -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,

View File

@ -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;
/// @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(
expectedNewERC721TokenIdsByOwner,
tokenIdsByOwner => {
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

View File

@ -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)

View File

@ -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 {

View File

@ -1,4 +1,22 @@
[
{
"timestamp": 1535377027,
"version": "1.0.6",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1535133899,
"version": "1.0.5",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1534210131,
"version": "1.0.4",

View File

@ -5,7 +5,15 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.4 - _August 13, 2018_
## v1.0.6 - _August 27, 2018_
* Dependencies updated
## v1.0.5 - _August 24, 2018_
* Dependencies updated
## v1.0.4 - _August 14, 2018_
* Dependencies updated
@ -21,7 +29,7 @@ CHANGELOG
* Dependencies updated
## v1.0.0 - _July 19, 2018_
## v1.0.0 - _July 20, 2018_
* Dependencies updated
@ -47,7 +55,7 @@ CHANGELOG
* Pass SolCompilerArtifactAdapter to CoverageSubprovider (#589)
* Move callbackErrorReporter over from 0x.js (#579)
## v0.4.1 - _May 4, 2018_
## v0.4.1 - _May 5, 2018_
* Dependencies updated

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/dev-utils",
"version": "1.0.4",
"version": "1.0.6",
"engines": {
"node": ">=6.12"
},
@ -29,7 +29,7 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/dev-utils/README.md",
"devDependencies": {
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/tslint-config": "^1.0.6",
"@types/lodash": "4.14.104",
"@types/mocha": "^2.2.42",
"chai": "^4.0.1",
@ -43,12 +43,12 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0xproject/subproviders": "^1.0.5",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/web3-wrapper": "^1.2.0",
"ethereum-types": "^1.0.4",
"@0xproject/subproviders": "^2.0.1",
"@0xproject/types": "^1.0.1-rc.6",
"@0xproject/typescript-typings": "^1.0.5",
"@0xproject/utils": "^1.0.7",
"@0xproject/web3-wrapper": "^2.0.1",
"ethereum-types": "^1.0.5",
"lodash": "^4.17.5"
},
"publishConfig": {

View File

@ -1,4 +1,13 @@
[
{
"timestamp": 1535133899,
"version": "1.0.5",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1534210131,
"version": "1.0.4",

View File

@ -5,7 +5,11 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.4 - _August 13, 2018_
## v1.0.5 - _August 24, 2018_
* Dependencies updated
## v1.0.4 - _August 14, 2018_
* Dependencies updated
@ -21,11 +25,12 @@ CHANGELOG
* Dependencies updated
## v1.0.0 - _July 19, 2018_
## v1.0.0 - _July 20, 2018_
* Add `TraceParams` interface for `debug_traceTransaction` parameters (#675)
* Add `TransactionReceiptStatus` type (#812)
* Add Artifact types: `CompilerSettings`, `CompilerOptions`, `OutputField` (#924)
## v0.0.2 - _May 31, 2018_
## v0.0.2 - _June 1, 2018_
* Initial publish (#642)

View File

@ -1,6 +1,6 @@
{
"name": "ethereum-types",
"version": "1.0.4",
"version": "1.0.5",
"engines": {
"node": ">=6.12"
},
@ -29,7 +29,7 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/ethereum-types/README.md",
"devDependencies": {
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/tslint-config": "^1.0.6",
"copyfiles": "^2.0.0",
"make-promises-safe": "^1.1.0",
"shx": "^0.2.2",

View File

@ -1,4 +1,22 @@
[
{
"version": "1.0.1-rc.5",
"changes": [
{
"note": "Dependencies updated"
}
],
"timestamp": 1535377027
},
{
"version": "1.0.1-rc.4",
"changes": [
{
"note": "Dependencies updated"
}
],
"timestamp": 1535133899
},
{
"version": "1.0.1-rc.3",
"changes": [

View File

@ -5,7 +5,15 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.1-rc.3 - _August 13, 2018_
## v1.0.1-rc.5 - _August 27, 2018_
* Dependencies updated
## v1.0.1-rc.4 - _August 24, 2018_
* Dependencies updated
## v1.0.1-rc.3 - _August 14, 2018_
* Updated to use latest orderFactory interface, fixed `feeRecipient` spelling error in public interface (#936)
* Dependencies updated
@ -22,7 +30,7 @@ CHANGELOG
* Dependencies updated
## v1.0.0-rc.1 - _July 19, 2018_
## v1.0.0-rc.1 - _July 20, 2018_
* Make fill-scenarios compatible with V2 of 0x protocol (#656)

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/fill-scenarios",
"version": "1.0.1-rc.3",
"version": "1.0.1-rc.5",
"description": "0x order fill scenario generator",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -27,8 +27,8 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/fill-scenarios/README.md",
"devDependencies": {
"@0xproject/abi-gen": "^1.0.5",
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/abi-gen": "^1.0.7",
"@0xproject/tslint-config": "^1.0.6",
"@types/lodash": "4.14.104",
"copyfiles": "^2.0.0",
"make-promises-safe": "^1.1.0",
@ -38,13 +38,13 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0xproject/base-contract": "^2.0.0-rc.1",
"@0xproject/order-utils": "^1.0.1-rc.3",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/web3-wrapper": "^1.2.0",
"ethereum-types": "^1.0.4",
"@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.7",
"@0xproject/web3-wrapper": "^2.0.1",
"ethereum-types": "^1.0.5",
"ethers": "3.0.22",
"lodash": "^4.17.5"
},

View File

@ -1,4 +1,13 @@
[
{
"version": "1.0.1-rc.2",
"changes": [
{
"note": "Dependencies updated"
}
],
"timestamp": 1535377027
},
{
"version": "1.0.1-rc.1",
"changes": [
@ -6,6 +15,7 @@
"note": "Add initial forwarderHelperFactory",
"pr": 997
}
]
],
"timestamp": 1535133899
}
]

View File

@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.1-rc.4 - _August 13, 2018_
## v1.0.1-rc.2 - _August 27, 2018_
* Add inital spec for SRA v2 (#916)
* Dependencies updated
## v1.0.1-rc.1 - _August 24, 2018_
* Add initial forwarderHelperFactory (#997)

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/forwarder-helper",
"version": "1.0.0-rc.1",
"version": "1.0.1-rc.2",
"engines": {
"node": ">=6.12"
},
@ -15,15 +15,13 @@
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
"test:circleci": "yarn test:coverage",
"run_mocha":
"mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit",
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit",
"clean": "shx rm -rf lib test_temp scripts",
"build": "tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
"manual:postpublish": "yarn build; node ./scripts/postpublish.js",
"docs:stage": "node scripts/stage_docs.js",
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES",
"upload_docs_json":
"aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json"
"upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json"
},
"config": {
"postpublish": {
@ -41,17 +39,17 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/forwarder-helper/README.md",
"dependencies": {
"@0xproject/assert": "^1.0.5",
"@0xproject/json-schemas": "^1.0.1-rc.4",
"@0xproject/order-utils": "^1.0.1-rc.3",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/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.7",
"@types/node": "^8.0.53",
"lodash": "^4.17.10"
},
"devDependencies": {
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/tslint-config": "^1.0.6",
"@types/lodash": "^4.14.116",
"@types/mocha": "^2.2.42",
"chai": "^4.0.1",

View File

@ -1,4 +1,13 @@
[
{
"version": "1.0.1-rc.6",
"changes": [
{
"note": "Dependencies updated"
}
],
"timestamp": 1535377027
},
{
"version": "1.0.1-rc.5",
"changes": [
@ -6,7 +15,8 @@
"note": "Update incorrect relayer api fee recipients response schema",
"pr": 974
}
]
],
"timestamp": 1535133899
},
{
"version": "1.0.1-rc.4",

View File

@ -5,7 +5,15 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.1-rc.4 - _August 13, 2018_
## 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)
## v1.0.1-rc.4 - _August 14, 2018_
* Allow for additional properties in txData schema (#938)
* Change hexSchema to match `0x` (#937)
@ -27,7 +35,7 @@ CHANGELOG
* Dependencies updated
## v1.0.0-rc.1 - _July 19, 2018_
## v1.0.0-rc.1 - _July 20, 2018_
* Update schemas for V2 or 0x Protocol (#615)
* Added CallData schema (#821)
@ -53,7 +61,7 @@ CHANGELOG
* Dependencies updated
## v0.7.23 - _May 4, 2018_
## v0.7.23 - _May 5, 2018_
* Dependencies updated

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/json-schemas",
"version": "1.0.1-rc.4",
"version": "1.0.1-rc.6",
"engines": {
"node": ">=6.12"
},
@ -23,7 +23,9 @@
"config": {
"postpublish": {
"assets": [],
"docOmitExports": ["schemas"]
"docOmitExports": [
"schemas"
]
}
},
"repository": {
@ -37,14 +39,14 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/json-schemas/README.md",
"dependencies": {
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/typescript-typings": "^1.0.5",
"@types/node": "^8.0.53",
"jsonschema": "^1.2.0",
"lodash.values": "^4.3.0"
},
"devDependencies": {
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/utils": "^1.0.5",
"@0xproject/tslint-config": "^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",

View File

@ -6,7 +6,6 @@ export const relayerApiFeeRecipientsResponseSchema = {
{
properties: {
records: {
id: '/relayerApiFeeRecipientsSchema',
type: 'array',
items: { $ref: '/addressSchema' },
},

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/metacoin",
"version": "0.0.15",
"version": "0.0.17",
"engines": {
"node": ">=6.12"
},
@ -29,25 +29,25 @@
"author": "",
"license": "Apache-2.0",
"dependencies": {
"@0xproject/abi-gen": "^1.0.5",
"@0xproject/base-contract": "^2.0.0-rc.1",
"@0xproject/sol-cov": "^2.0.0",
"@0xproject/subproviders": "^1.0.5",
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/web3-wrapper": "^1.2.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.6",
"@0xproject/typescript-typings": "^1.0.5",
"@0xproject/utils": "^1.0.7",
"@0xproject/web3-wrapper": "^2.0.1",
"@types/mocha": "^5.2.2",
"copyfiles": "^2.0.0",
"ethereum-types": "^1.0.4",
"ethereum-types": "^1.0.5",
"ethers": "3.0.22",
"lodash": "^4.17.5",
"run-s": "^0.0.0"
},
"devDependencies": {
"@0xproject/dev-utils": "^1.0.4",
"@0xproject/sol-compiler": "^1.0.5",
"@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",

View File

@ -1,4 +1,22 @@
[
{
"timestamp": 1535377027,
"version": "1.0.6",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1535133899,
"version": "1.0.5",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1534210131,
"version": "1.0.4",

View File

@ -5,7 +5,15 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.4 - _August 13, 2018_
## v1.0.6 - _August 27, 2018_
* Dependencies updated
## v1.0.5 - _August 24, 2018_
* Dependencies updated
## v1.0.4 - _August 14, 2018_
* Dependencies updated
@ -21,7 +29,7 @@ CHANGELOG
* Dependencies updated
## v1.0.0 - _July 19, 2018_
## v1.0.0 - _July 20, 2018_
* Added migrations for 0x Protocol v2
@ -45,7 +53,7 @@ CHANGELOG
* Dependencies updated
## v0.0.5 - _May 4, 2018_
## v0.0.5 - _May 5, 2018_
* Dependencies updated

View File

@ -782,5 +782,11 @@
}
}
},
"networks": {}
"networks": {
"50": {
"address": "0xe86bb98fcf9bff3512c74589b78fb168200cc546",
"links": {},
"constructorArgs": "[\"0x48bacb9266a570d521063ef5dd96e61686dbe788\",\"0xf47261b0000000000000000000000000871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c\"]"
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/migrations",
"version": "1.0.4",
"version": "1.0.6",
"engines": {
"node": ">=6.12"
},
@ -35,10 +35,10 @@
},
"license": "Apache-2.0",
"devDependencies": {
"@0xproject/abi-gen": "^1.0.5",
"@0xproject/dev-utils": "^1.0.4",
"@0xproject/tslint-config": "^1.0.5",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/abi-gen": "^1.0.7",
"@0xproject/dev-utils": "^1.0.6",
"@0xproject/tslint-config": "^1.0.6",
"@0xproject/types": "^1.0.1-rc.6",
"@types/yargs": "^10.0.0",
"copyfiles": "^2.0.0",
"make-promises-safe": "^1.1.0",
@ -49,15 +49,15 @@
"yargs": "^10.0.3"
},
"dependencies": {
"@0xproject/base-contract": "^2.0.0-rc.1",
"@0xproject/order-utils": "^1.0.1-rc.3",
"@0xproject/sol-compiler": "^1.0.5",
"@0xproject/subproviders": "^1.0.5",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/web3-wrapper": "^1.2.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.7",
"@0xproject/web3-wrapper": "^2.0.1",
"@ledgerhq/hw-app-eth": "^4.3.0",
"ethereum-types": "^1.0.4",
"ethereum-types": "^1.0.5",
"ethers": "3.0.22",
"lodash": "^4.17.5"
},

View File

@ -1,7 +1,7 @@
{
"private": true,
"name": "@0xproject/monorepo-scripts",
"version": "1.0.5",
"version": "1.0.7",
"engines": {
"node": ">=6.12"
},

View File

@ -18,7 +18,6 @@ import { DocGenerateAndUploadUtils } from './utils/doc_generate_and_upload_utils
import { publishReleaseNotesAsync } from './utils/github_release_utils';
import { utils } from './utils/utils';
const DOC_GEN_COMMAND = 'docs:json';
const NPM_NAMESPACE = '@0xproject/';
const TODAYS_TIMESTAMP = moment().unix();
@ -75,9 +74,11 @@ async function confirmAsync(message: string): Promise<void> {
});
utils.log(`Calling 'lerna publish'...`);
await lernaPublishAsync(packageToNextVersion);
if (!configs.IS_LOCAL_PUBLISH) {
const isStaging = false;
const shouldUploadDocs = !configs.IS_LOCAL_PUBLISH;
const shouldUploadDocs = true;
await generateAndUploadDocJsonsAsync(packagesWithDocs, isStaging, shouldUploadDocs);
}
const isDryRun = configs.IS_LOCAL_PUBLISH;
await publishReleaseNotesAsync(updatedPublicPackages, isDryRun);
})().catch(err => {
@ -88,7 +89,7 @@ async function confirmAsync(message: string): Promise<void> {
function getPackagesWithDocs(allUpdatedPackages: Package[]): Package[] {
const rootPackageJsonPath = `${constants.monorepoRootPath}/package.json`;
const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath).toString());
const packagesWithDocPagesStringIfExist = _.get(rootPackageJson, 'configs.packagesWithDocPages', undefined);
const packagesWithDocPagesStringIfExist = _.get(rootPackageJson, 'config.packagesWithDocPages', undefined);
if (_.isUndefined(packagesWithDocPagesStringIfExist)) {
return []; // None to generate & publish
}

View File

@ -17,6 +17,7 @@ const args = yargs
const allUpdatedPackages = await utils.getUpdatedPackagesAsync(shouldIncludePrivate);
await publishReleaseNotesAsync(allUpdatedPackages, isDryRun);
process.exit(0);
})().catch(err => {
utils.log(err);
process.exit(1);

View File

@ -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);
}

View File

@ -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;

View File

@ -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": [
@ -39,7 +57,8 @@
"Update `findFeeOrdersThatCoverFeesForTargetOrders` to round the the nearest integer when calculating required fees",
"pr": 997
}
]
],
"timestamp": 1535133899
},
{
"version": "1.0.1-rc.3",

View File

@ -5,7 +5,26 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.1-rc.3 - _August 13, 2018_
## 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)
* Added rateUtils and sortingUtils (#953)
* Update marketUtils api such that all optional parameters are bundled into one optional param and more defaults are provided (#954)
* Instead of exporting signature util methods individually, they are now exported as `signatureUtils` (#924)
* Export types: `SignedOrder`, `Order`, `OrderRelevantState`, `OrderState`, `ECSignature`, `ERC20AssetData`, `ERC721AssetData`, `AssetProxyId`, `SignerType`, `SignatureType`, `OrderStateValid`, `OrderStateInvalid`, `ExchangeContractErrs`, `TradeSide`, `TransferType`, `FindFeeOrdersThatCoverFeesForTargetOrdersOpts`, `FindOrdersThatCoverMakerAssetFillAmountOpts`, `FeeOrdersAndRemainingFeeAmount`, `OrdersAndRemainingFillAmount`, `Provider`, `JSONRPCRequestPayload`, `JSONRPCErrorCallback` and `JSONRPCResponsePayload` (#924)
* Rename `resultOrders` to `resultFeeOrders` for object returned by `findFeeOrdersThatCoverFeesForTargetOrders` in `marketUtils` api (#997)
* Make `sortFeeOrdersByFeeAdjustedRate` in `sortingUtils` generic (#997)
* Update `findFeeOrdersThatCoverFeesForTargetOrders` to round the the nearest integer when calculating required fees (#997)
## v1.0.1-rc.3 - _August 14, 2018_
* Update ecSignOrderHashAsync to return signature string with signature type byte. Removes messagePrefixOpts. (#914)
* Added a synchronous `createOrder` method in `orderFactory`, updated public interfaces to support some optional parameters (#936)
@ -28,7 +47,7 @@ CHANGELOG
* Upgrade ethereumjs-abi dep including a fix so that addresses starting with 0 are properly decoded by `decodeERC20AssetData`
## v1.0.0-rc.1 - _July 19, 2018_
## v1.0.0-rc.1 - _July 20, 2018_
* Refactor to work with V2 of 0x protocol (#636)
* Export parseECSignature method (#684)
@ -54,7 +73,7 @@ CHANGELOG
* Add orderStateUtils, a module for computing order state needed to decide if an order is still valid
## v0.0.4 - _May 4, 2018_
## v0.0.4 - _May 5, 2018_
* Dependencies updated

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/order-utils",
"version": "1.0.1-rc.3",
"version": "1.0.1-rc.6",
"engines": {
"node": ">=6.12"
},
@ -40,8 +40,8 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md",
"devDependencies": {
"@0xproject/dev-utils": "^1.0.4",
"@0xproject/tslint-config": "^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",
"chai": "^4.0.1",
@ -59,16 +59,16 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0xproject/assert": "^1.0.5",
"@0xproject/base-contract": "^2.0.0-rc.1",
"@0xproject/json-schemas": "^1.0.1-rc.4",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/web3-wrapper": "^1.2.0",
"@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.7",
"@0xproject/web3-wrapper": "^2.0.1",
"@types/node": "^8.0.53",
"bn.js": "^4.11.8",
"ethereum-types": "^1.0.4",
"ethereum-types": "^1.0.5",
"ethereumjs-abi": "0.6.5",
"ethereumjs-util": "^5.1.1",
"ethers": "3.0.22",

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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();
});
});

View File

@ -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';

View File

@ -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": [
@ -11,7 +20,8 @@
"note": "Remove exporting types: `BlockParamLiteral`, `BlockParam`, `Order`",
"pr": 924
}
]
],
"timestamp": 1535133899
},
{
"version": "1.0.1-rc.3",

View File

@ -5,7 +5,16 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.1-rc.3 - _August 13, 2018_
## 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)
* Remove exporting types: `BlockParamLiteral`, `BlockParam`, `Order` (#924)
## v1.0.1-rc.3 - _August 14, 2018_
* Dependencies updated
@ -21,7 +30,7 @@ CHANGELOG
* Dependencies updated
## v1.0.0-rc.1 - _July 19, 2018_
## v1.0.0-rc.1 - _July 20, 2018_
* Add support for ERC721 event watching and Exchange V2 events (#887)

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/order-watcher",
"version": "1.0.1-rc.3",
"version": "1.0.1-rc.5",
"description": "An order watcher daemon that watches for order validity",
"keywords": [
"0x",
@ -43,10 +43,10 @@
"node": ">=6.0.0"
},
"devDependencies": {
"@0xproject/abi-gen": "^1.0.5",
"@0xproject/dev-utils": "^1.0.4",
"@0xproject/migrations": "^1.0.4",
"@0xproject/tslint-config": "^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",
"@types/mocha": "^2.2.42",
@ -71,18 +71,18 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0xproject/assert": "^1.0.5",
"@0xproject/base-contract": "^2.0.0-rc.1",
"@0xproject/contract-wrappers": "^1.0.1-rc.3",
"@0xproject/fill-scenarios": "^1.0.1-rc.3",
"@0xproject/json-schemas": "^1.0.1-rc.4",
"@0xproject/order-utils": "^1.0.1-rc.3",
"@0xproject/types": "^1.0.1-rc.4",
"@0xproject/typescript-typings": "^1.0.4",
"@0xproject/utils": "^1.0.5",
"@0xproject/web3-wrapper": "^1.2.0",
"@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.7",
"@0xproject/web3-wrapper": "^2.0.1",
"bintrees": "^1.0.2",
"ethereum-types": "^1.0.4",
"ethereum-types": "^1.0.5",
"ethereumjs-blockstream": "5.0.0",
"ethers": "3.0.22",
"lodash": "^4.17.5"

View File

@ -1,4 +1,22 @@
[
{
"timestamp": 1535377027,
"version": "1.0.7",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1535133899,
"version": "1.0.6",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1534210131,
"version": "1.0.5",

View File

@ -5,7 +5,15 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.5 - _August 13, 2018_
## v1.0.7 - _August 27, 2018_
* Dependencies updated
## v1.0.6 - _August 24, 2018_
* Dependencies updated
## v1.0.5 - _August 14, 2018_
* Dependencies updated
@ -25,7 +33,7 @@ CHANGELOG
* Dependencies updated
## v1.0.0 - _July 19, 2018_
## v1.0.0 - _July 20, 2018_
* Dependencies updated
@ -45,7 +53,7 @@ CHANGELOG
* Dependencies updated
## v0.0.13 - _May 31, 2018_
## v0.0.13 - _June 1, 2018_
* Incorrect publish that was unpublished
@ -53,7 +61,7 @@ CHANGELOG
* Dependencies updated
## v0.0.11 - _May 4, 2018_
## v0.0.11 - _May 5, 2018_
* Dependencies updated

View File

@ -1,6 +1,6 @@
{
"name": "@0xproject/react-docs",
"version": "1.0.5",
"version": "1.0.7",
"engines": {
"node": ">=6.12"
},
@ -24,8 +24,8 @@
"url": "https://github.com/0xProject/0x-monorepo.git"
},
"devDependencies": {
"@0xproject/dev-utils": "^1.0.4",
"@0xproject/tslint-config": "^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",
"make-promises-safe": "^1.1.0",
@ -34,8 +34,8 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0xproject/react-shared": "^1.0.6",
"@0xproject/utils": "^1.0.5",
"@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",

Some files were not shown because too many files have changed in this diff Show More