Compare commits
37 Commits
@0x/contra
...
@0x/migrat
Author | SHA1 | Date | |
---|---|---|---|
|
4c3fbe83ac | ||
|
7caa43d02c | ||
|
3d4c03c9df | ||
|
22e1ed35d3 | ||
|
dabe6fd793 | ||
|
3cc639c8d0 | ||
|
22c8e0b6db | ||
|
f3ca4293bc | ||
|
db3e076d03 | ||
|
1a6759820a | ||
|
61c5e7b948 | ||
|
5fd78ef32f | ||
|
14ff9b827c | ||
|
598dc2cd71 | ||
|
08e1c5109f | ||
|
6f7a843742 | ||
|
c9c9615bb5 | ||
|
f98609686d | ||
|
5b8bbc34e8 | ||
|
49cb00a9ab | ||
|
74b240fb88 | ||
|
514f9d2621 | ||
|
fa78d1092a | ||
|
297342092b | ||
|
076f263a86 | ||
|
0c56207abc | ||
|
23953d8a5a | ||
|
c6919eb25a | ||
|
d509604b52 | ||
|
a74a3450eb | ||
|
72c5399b9d | ||
|
b29196b983 | ||
|
ce76a7033d | ||
|
3f4bb933d1 | ||
|
501b7b9b65 | ||
|
c0ea88e864 | ||
|
48f8bea460 |
7
.github/workflows/publish.yml
vendored
7
.github/workflows/publish.yml
vendored
@@ -7,6 +7,9 @@ on:
|
||||
description: 'required CI status'
|
||||
default: 'success'
|
||||
required: true
|
||||
prerelease:
|
||||
description: 'prerelease name'
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
@@ -21,7 +24,7 @@ jobs:
|
||||
(echo "::error ::${{ github.ref }} does not have a successful CI status" && false)
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: 'development'
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
@@ -41,7 +44,9 @@ jobs:
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
PUBLISH_PRERELEASE: ${{ github.event.inputs.prerelease }}
|
||||
- name: 'merge into main branch'
|
||||
if: github.event.inputs.prerelease == '' # unless it's a prerelease
|
||||
run: |
|
||||
git checkout main && \
|
||||
git merge ${{ github.ref }} && \
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "3.7.8",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "3.7.7",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "3.7.6",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "3.7.5",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.7.8 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.7.7 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.7.6 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.7.5 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-asset-proxy",
|
||||
"version": "3.7.5",
|
||||
"version": "3.7.8",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -51,13 +51,13 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/protocol",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contract-wrappers": "^13.12.2",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contract-wrappers": "^13.14.0",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@types/lodash": "4.14.104",
|
||||
@@ -80,11 +80,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/contracts-erc1155": "^2.1.23",
|
||||
"@0x/contracts-erc20": "^3.3.2",
|
||||
"@0x/contracts-erc721": "^3.1.23",
|
||||
"@0x/contracts-exchange-libs": "^4.3.23",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/contracts-erc1155": "^2.1.26",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-erc721": "^3.1.26",
|
||||
"@0x/contracts-exchange-libs": "^4.3.26",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/types": "^3.3.1",
|
||||
"@0x/typescript-typings": "^5.1.6",
|
||||
"@0x/utils": "^6.2.0",
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "1.1.26",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "1.1.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "1.1.24",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "1.1.23",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.1.26 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.1.25 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.1.24 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.1.23 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-broker",
|
||||
"version": "1.1.23",
|
||||
"version": "1.1.26",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -51,16 +51,16 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/extensions",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-asset-proxy": "^3.7.5",
|
||||
"@0x/contracts-erc20": "^3.3.2",
|
||||
"@0x/contracts-erc721": "^3.1.23",
|
||||
"@0x/contracts-exchange": "^3.2.24",
|
||||
"@0x/contracts-exchange-libs": "^4.3.23",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-asset-proxy": "^3.7.8",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-erc721": "^3.1.26",
|
||||
"@0x/contracts-exchange": "^3.2.27",
|
||||
"@0x/contracts-exchange-libs": "^4.3.26",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
@@ -85,7 +85,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/typescript-typings": "^5.1.6",
|
||||
"@0x/utils": "^6.2.0",
|
||||
"ethereum-types": "^3.4.0"
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "3.1.27",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "3.1.26",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "3.1.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "3.1.24",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.1.27 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.1.26 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.1.25 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.1.24 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-coordinator",
|
||||
"version": "3.1.24",
|
||||
"version": "3.1.27",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,14 +52,14 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/extensions",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-asset-proxy": "^3.7.5",
|
||||
"@0x/contracts-dev-utils": "^1.3.22",
|
||||
"@0x/contracts-erc20": "^3.3.2",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-asset-proxy": "^3.7.8",
|
||||
"@0x/contracts-dev-utils": "^1.3.25",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/web3-wrapper": "^7.4.1",
|
||||
@@ -84,10 +84,10 @@
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.21",
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/contract-addresses": "^5.9.0",
|
||||
"@0x/contracts-exchange": "^3.2.24",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/contract-addresses": "^5.11.0",
|
||||
"@0x/contracts-exchange": "^3.2.27",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/json-schemas": "^5.4.1",
|
||||
"@0x/types": "^3.3.1",
|
||||
"@0x/typescript-typings": "^5.1.6",
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "1.3.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "1.3.24",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "1.3.23",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "1.3.22",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.3.25 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.3.24 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.3.23 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.3.22 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-dev-utils",
|
||||
"version": "1.3.22",
|
||||
"version": "1.3.25",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -41,13 +41,13 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/dev-utils",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/assert": "^3.0.21",
|
||||
"@0x/contracts-asset-proxy": "^3.7.5",
|
||||
"@0x/contracts-erc20": "^3.3.2",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/contracts-asset-proxy": "^3.7.8",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "2.1.26",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "2.1.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "2.1.24",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "2.1.23",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.1.26 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.1.25 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.1.24 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.1.23 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-erc1155",
|
||||
"version": "2.1.23",
|
||||
"version": "2.1.26",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,11 +52,11 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/tokens",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
@@ -81,7 +81,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/utils": "^6.2.0",
|
||||
"@0x/web3-wrapper": "^7.4.1",
|
||||
"lodash": "^4.17.11"
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "3.3.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "3.3.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "3.3.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "3.3.2",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.3.5 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.4 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.3 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.2 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-erc20",
|
||||
"version": "3.3.2",
|
||||
"version": "3.3.5",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -51,12 +51,12 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/tokens",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
|
@@ -6,6 +6,7 @@ export {
|
||||
WETH9Events,
|
||||
WETH9DepositEventArgs,
|
||||
WETH9TransferEventArgs,
|
||||
WETH9WithdrawalEventArgs,
|
||||
ZRXTokenContract,
|
||||
DummyERC20TokenTransferEventArgs,
|
||||
ERC20TokenEventArgs,
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "3.1.26",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "3.1.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "3.1.24",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "3.1.23",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.1.26 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.1.25 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.1.24 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.1.23 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-erc721",
|
||||
"version": "3.1.23",
|
||||
"version": "3.1.26",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,12 +52,12 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/tokens",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "4.2.27",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "4.2.26",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "4.2.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "4.2.24",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v4.2.27 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.2.26 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.2.25 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.2.24 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-exchange-forwarder",
|
||||
"version": "4.2.24",
|
||||
"version": "4.2.27",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,20 +52,20 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/extensions",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-asset-proxy": "^3.7.5",
|
||||
"@0x/contracts-dev-utils": "^1.3.22",
|
||||
"@0x/contracts-erc1155": "^2.1.23",
|
||||
"@0x/contracts-erc20": "^3.3.2",
|
||||
"@0x/contracts-erc721": "^3.1.23",
|
||||
"@0x/contracts-exchange": "^3.2.24",
|
||||
"@0x/contracts-exchange-libs": "^4.3.23",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-asset-proxy": "^3.7.8",
|
||||
"@0x/contracts-dev-utils": "^1.3.25",
|
||||
"@0x/contracts-erc1155": "^2.1.26",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-erc721": "^3.1.26",
|
||||
"@0x/contracts-exchange": "^3.2.27",
|
||||
"@0x/contracts-exchange-libs": "^4.3.26",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "4.3.26",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "4.3.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "4.3.24",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "4.3.23",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v4.3.26 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.3.25 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.3.24 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.3.23 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-exchange-libs",
|
||||
"version": "4.3.23",
|
||||
"version": "4.3.26",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,10 +52,10 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/libs",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/subproviders": "^6.4.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
@@ -81,9 +81,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/types": "^3.3.1",
|
||||
"@0x/typescript-typings": "^5.1.6",
|
||||
"@0x/utils": "^6.2.0",
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "3.2.27",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "3.2.26",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "3.2.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "3.2.24",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.2.27 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.2.26 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.2.25 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.2.24 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-exchange",
|
||||
"version": "3.2.24",
|
||||
"version": "3.2.27",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,16 +52,16 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/protocol",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-asset-proxy": "^3.7.5",
|
||||
"@0x/contracts-exchange-libs": "^4.3.23",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-multisig": "^4.1.24",
|
||||
"@0x/contracts-staking": "^2.0.31",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-asset-proxy": "^3.7.8",
|
||||
"@0x/contracts-exchange-libs": "^4.3.26",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-multisig": "^4.1.27",
|
||||
"@0x/contracts-staking": "^2.0.34",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
@@ -89,11 +89,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/contracts-dev-utils": "^1.3.22",
|
||||
"@0x/contracts-erc1155": "^2.1.23",
|
||||
"@0x/contracts-erc20": "^3.3.2",
|
||||
"@0x/contracts-erc721": "^3.1.23",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/contracts-dev-utils": "^1.3.25",
|
||||
"@0x/contracts-erc1155": "^2.1.26",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-erc721": "^3.1.26",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/utils": "^6.2.0",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "6.2.21",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "6.2.20",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "6.2.19",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "6.2.18",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v6.2.21 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v6.2.20 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v6.2.19 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v6.2.18 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-extensions",
|
||||
"version": "6.2.18",
|
||||
"version": "6.2.21",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,18 +52,18 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/extensions",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-asset-proxy": "^3.7.5",
|
||||
"@0x/contracts-dev-utils": "^1.3.22",
|
||||
"@0x/contracts-erc20": "^3.3.2",
|
||||
"@0x/contracts-erc721": "^3.1.23",
|
||||
"@0x/contracts-exchange": "^3.2.24",
|
||||
"@0x/contracts-exchange-libs": "^4.3.23",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-asset-proxy": "^3.7.8",
|
||||
"@0x/contracts-dev-utils": "^1.3.25",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-erc721": "^3.1.26",
|
||||
"@0x/contracts-exchange": "^3.2.27",
|
||||
"@0x/contracts-exchange-libs": "^4.3.26",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
@@ -91,7 +91,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/typescript-typings": "^5.1.6",
|
||||
"ethereum-types": "^3.4.0"
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-integrations",
|
||||
"version": "2.7.25",
|
||||
"version": "2.7.29",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
@@ -52,23 +52,23 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/extensions",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contract-addresses": "^5.9.0",
|
||||
"@0x/contract-wrappers": "^13.12.2",
|
||||
"@0x/contracts-broker": "^1.1.23",
|
||||
"@0x/contracts-coordinator": "^3.1.24",
|
||||
"@0x/contracts-dev-utils": "^1.3.22",
|
||||
"@0x/contracts-exchange-forwarder": "^4.2.24",
|
||||
"@0x/contracts-exchange-libs": "^4.3.23",
|
||||
"@0x/contracts-extensions": "^6.2.18",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contract-addresses": "^5.11.0",
|
||||
"@0x/contract-wrappers": "^13.14.0",
|
||||
"@0x/contracts-broker": "^1.1.26",
|
||||
"@0x/contracts-coordinator": "^3.1.27",
|
||||
"@0x/contracts-dev-utils": "^1.3.25",
|
||||
"@0x/contracts-exchange-forwarder": "^4.2.27",
|
||||
"@0x/contracts-exchange-libs": "^4.3.26",
|
||||
"@0x/contracts-extensions": "^6.2.21",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/coordinator-server": "^1.0.5",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/migrations": "^6.5.11",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/protocol-utils": "^1.1.5",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/migrations": "^7.0.1",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/protocol-utils": "^1.3.1",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/web3-wrapper": "^7.4.1",
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.0",
|
||||
@@ -93,17 +93,17 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/asset-swapper": "^5.8.2",
|
||||
"@0x/asset-swapper": "^6.3.0",
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/contracts-asset-proxy": "^3.7.5",
|
||||
"@0x/contracts-erc1155": "^2.1.23",
|
||||
"@0x/contracts-erc20": "^3.3.2",
|
||||
"@0x/contracts-erc721": "^3.1.23",
|
||||
"@0x/contracts-exchange": "^3.2.24",
|
||||
"@0x/contracts-multisig": "^4.1.24",
|
||||
"@0x/contracts-staking": "^2.0.31",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-zero-ex": "^0.18.1",
|
||||
"@0x/contracts-asset-proxy": "^3.7.8",
|
||||
"@0x/contracts-erc1155": "^2.1.26",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-erc721": "^3.1.26",
|
||||
"@0x/contracts-exchange": "^3.2.27",
|
||||
"@0x/contracts-multisig": "^4.1.27",
|
||||
"@0x/contracts-staking": "^2.0.34",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/contracts-zero-ex": "^0.20.0",
|
||||
"@0x/subproviders": "^6.4.1",
|
||||
"@0x/types": "^3.3.1",
|
||||
"@0x/typescript-typings": "^5.1.6",
|
||||
|
@@ -1,239 +0,0 @@
|
||||
import { MarketBuySwapQuote, MarketSellSwapQuote, Orderbook, SwapQuoter } from '@0x/asset-swapper';
|
||||
import { blockchainTests, expect, Numberish } from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { FillResults, SignedOrder } from '@0x/types';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { TestMainnetAggregatorFillsContract } from '../wrappers';
|
||||
|
||||
import { tokens } from './tokens';
|
||||
|
||||
blockchainTests.live('Aggregator Mainnet Tests', env => {
|
||||
// Mainnet address of the `TestMainnetAggregatorFills` contract.
|
||||
const TEST_CONTRACT_ADDRESS = '0x37Ca306F42748b7fe105F89FCBb2CD03D27c8146';
|
||||
const TAKER_ADDRESS = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'; // Vitalik
|
||||
const ORDERBOOK_POLLING_MS = 1000;
|
||||
const GAS_PRICE = new BigNumber(1);
|
||||
const TAKER_ASSET_ETH_VALUE = 500e18;
|
||||
const MIN_BALANCE = 500.1e18;
|
||||
const SYMBOLS = ['ETH', 'DAI', 'USDC', 'FOAM'];
|
||||
const TEST_PAIRS = _.flatten(SYMBOLS.map(m => SYMBOLS.filter(t => t !== m).map(t => [m, t])));
|
||||
const FILL_VALUES = [1, 10, 1e2, 1e3, 1e4, 2.5e4, 5e4];
|
||||
|
||||
let testContract: TestMainnetAggregatorFillsContract;
|
||||
let swapQuoter: SwapQuoter;
|
||||
let takerEthBalance: BigNumber;
|
||||
const orderbooks: { [name: string]: Orderbook } = {};
|
||||
|
||||
async function getTakerOrdersAsync(takerAssetSymbol: string): Promise<SignedOrder[]> {
|
||||
if (takerAssetSymbol === 'ETH') {
|
||||
return [];
|
||||
}
|
||||
return getOrdersAsync(takerAssetSymbol, 'ETH');
|
||||
}
|
||||
|
||||
// Fetches ETH -> taker asset orders for the forwarder contract.
|
||||
async function getOrdersAsync(makerAssetSymbol: string, takerAssetSymbol: string): Promise<SignedOrder[]> {
|
||||
const takerTokenAddress = tokens[takerAssetSymbol].address;
|
||||
const makerTokenAddress = tokens[makerAssetSymbol].address;
|
||||
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenAddress);
|
||||
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
|
||||
const orders = _.flatten(
|
||||
await Promise.all(
|
||||
Object.keys(orderbooks).map(async name =>
|
||||
getOrdersFromOrderBookAsync(name, makerAssetData, takerAssetData),
|
||||
),
|
||||
),
|
||||
);
|
||||
const uniqueOrders: SignedOrder[] = [];
|
||||
for (const order of orders) {
|
||||
if (!order.makerFee.eq(0) || !order.takerFee.eq(0)) {
|
||||
continue;
|
||||
}
|
||||
if (uniqueOrders.findIndex(o => isSameOrder(order, o)) === -1) {
|
||||
uniqueOrders.push(order);
|
||||
}
|
||||
}
|
||||
return uniqueOrders;
|
||||
}
|
||||
|
||||
async function getOrdersFromOrderBookAsync(
|
||||
name: string,
|
||||
makerAssetData: string,
|
||||
takerAssetData: string,
|
||||
): Promise<SignedOrder[]> {
|
||||
try {
|
||||
return (await orderbooks[name].getOrdersAsync(makerAssetData, takerAssetData)).map(r => r.order);
|
||||
} catch (err) {
|
||||
logUtils.warn(`Failed to retrieve orders from orderbook "${name}".`);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function isSameOrder(a: SignedOrder, b: SignedOrder): boolean {
|
||||
for (const [k, v] of Object.entries(a)) {
|
||||
if (k in (b as any)) {
|
||||
if (BigNumber.isBigNumber(v) && !v.eq((b as any)[k])) {
|
||||
return false;
|
||||
}
|
||||
if (v !== (b as any)[k]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function toTokenUnits(symbol: string, weis: Numberish): BigNumber {
|
||||
return new BigNumber(weis).div(new BigNumber(10).pow(tokens[symbol].decimals));
|
||||
}
|
||||
|
||||
function fromTokenUnits(symbol: string, units: Numberish): BigNumber {
|
||||
return new BigNumber(units)
|
||||
.times(new BigNumber(10).pow(tokens[symbol].decimals))
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
}
|
||||
|
||||
interface MarketOperationResult {
|
||||
makerAssetBalanceBefore: BigNumber;
|
||||
takerAssetBalanceBefore: BigNumber;
|
||||
makerAssetBalanceAfter: BigNumber;
|
||||
takerAssetBalanceAfter: BigNumber;
|
||||
fillResults: FillResults;
|
||||
}
|
||||
|
||||
// Liquidity is low right now so it's possible we didn't have
|
||||
// enough taker assets to cover the orders, so occasionally we'll get incomplete
|
||||
// fills. This function will catch those cases.
|
||||
// TODO(dorothy-zbornak): Remove this special case when liquidity is up.
|
||||
function checkHadEnoughTakerAsset(
|
||||
quote: MarketBuySwapQuote | MarketSellSwapQuote,
|
||||
result: MarketOperationResult,
|
||||
): boolean {
|
||||
if (result.takerAssetBalanceBefore.gte(quote.worstCaseQuoteInfo.takerAssetAmount)) {
|
||||
return true;
|
||||
}
|
||||
const takerAssetPct = result.takerAssetBalanceBefore
|
||||
.div(quote.worstCaseQuoteInfo.takerAssetAmount)
|
||||
.times(100)
|
||||
.toNumber()
|
||||
.toFixed(1);
|
||||
logUtils.warn(`Could not acquire enough taker asset to complete the fill: ${takerAssetPct}%`);
|
||||
expect(result.fillResults.makerAssetFilledAmount).to.bignumber.lt(quote.worstCaseQuoteInfo.makerAssetAmount);
|
||||
return false;
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
testContract = new TestMainnetAggregatorFillsContract(TEST_CONTRACT_ADDRESS, env.provider, {
|
||||
...env.txDefaults,
|
||||
gasPrice: GAS_PRICE,
|
||||
gas: 10e6,
|
||||
});
|
||||
swapQuoter = SwapQuoter.getSwapQuoterForStandardRelayerAPIUrl(env.provider, 'https://api.0x.org/sra');
|
||||
// Pool orderbooks because we're desperate for liquidity.
|
||||
orderbooks.swapQuoter = swapQuoter.orderbook;
|
||||
orderbooks.bamboo = Orderbook.getOrderbookForPollingProvider({
|
||||
httpEndpoint: 'https://sra.bamboorelay.com/0x/v3',
|
||||
pollingIntervalMs: ORDERBOOK_POLLING_MS,
|
||||
});
|
||||
// TODO(dorothy-zbornak): Uncomment when radar's SRA is up.
|
||||
// orderbooks.radar = Orderbook.getOrderbookForPollingProvider({
|
||||
// httpEndpoint: 'https://api-v3.radarrelay.com/v3',
|
||||
// pollingIntervalMs: ORDERBOOK_POLLING_MS,
|
||||
// });
|
||||
takerEthBalance = await env.web3Wrapper.getBalanceInWeiAsync(TAKER_ADDRESS);
|
||||
});
|
||||
|
||||
it('taker has minimum ETH', async () => {
|
||||
expect(takerEthBalance).to.bignumber.gte(MIN_BALANCE);
|
||||
});
|
||||
|
||||
describe('market sells', () => {
|
||||
for (const [makerSymbol, takerSymbol] of TEST_PAIRS) {
|
||||
for (const fillValue of FILL_VALUES) {
|
||||
const fillAmount = fromTokenUnits(takerSymbol, new BigNumber(fillValue).div(tokens[takerSymbol].price));
|
||||
it(`sell ${toTokenUnits(takerSymbol, fillAmount)} ${takerSymbol} for ${makerSymbol}`, async () => {
|
||||
const [quote, takerOrders] = await Promise.all([
|
||||
swapQuoter.getMarketSellSwapQuoteAsync(
|
||||
tokens[makerSymbol].address,
|
||||
tokens[takerSymbol].address,
|
||||
fillAmount,
|
||||
{ gasPrice: GAS_PRICE },
|
||||
),
|
||||
getTakerOrdersAsync(takerSymbol),
|
||||
]);
|
||||
// Buy taker assets from `takerOrders` and and perform a
|
||||
// market sell on the bridge orders.
|
||||
const fill = await testContract
|
||||
.marketSell(
|
||||
tokens[makerSymbol].address,
|
||||
tokens[takerSymbol].address,
|
||||
quote.orders,
|
||||
takerOrders,
|
||||
quote.orders.map(o => o.signature),
|
||||
takerOrders.map(o => o.signature),
|
||||
quote.takerAssetFillAmount,
|
||||
)
|
||||
.callAsync({
|
||||
value: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount.plus(TAKER_ASSET_ETH_VALUE),
|
||||
from: TAKER_ADDRESS,
|
||||
gasPrice: quote.gasPrice,
|
||||
});
|
||||
if (checkHadEnoughTakerAsset(quote, fill)) {
|
||||
expect(fill.fillResults.makerAssetFilledAmount, 'makerAssetFilledAmount').to.bignumber.gte(
|
||||
quote.worstCaseQuoteInfo.makerAssetAmount,
|
||||
);
|
||||
expect(fill.fillResults.takerAssetFilledAmount, 'takerAssetFilledAmount').to.bignumber.lte(
|
||||
quote.takerAssetFillAmount,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('market buys', () => {
|
||||
for (const [makerSymbol, takerSymbol] of TEST_PAIRS) {
|
||||
for (const fillValue of FILL_VALUES) {
|
||||
const fillAmount = fromTokenUnits(makerSymbol, new BigNumber(fillValue).div(tokens[makerSymbol].price));
|
||||
it(`buy ${toTokenUnits(makerSymbol, fillAmount)} ${makerSymbol} with ${takerSymbol}`, async () => {
|
||||
const [quote, takerOrders] = await Promise.all([
|
||||
swapQuoter.getMarketBuySwapQuoteAsync(
|
||||
tokens[makerSymbol].address,
|
||||
tokens[takerSymbol].address,
|
||||
fillAmount,
|
||||
{ gasPrice: GAS_PRICE },
|
||||
),
|
||||
getTakerOrdersAsync(takerSymbol),
|
||||
]);
|
||||
// Buy taker assets from `takerOrders` and and perform a
|
||||
// market buy on the bridge orders.
|
||||
const fill = await testContract
|
||||
.marketBuy(
|
||||
tokens[makerSymbol].address,
|
||||
tokens[takerSymbol].address,
|
||||
quote.orders,
|
||||
takerOrders,
|
||||
quote.orders.map(o => o.signature),
|
||||
takerOrders.map(o => o.signature),
|
||||
quote.makerAssetFillAmount,
|
||||
)
|
||||
.callAsync({
|
||||
value: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount.plus(TAKER_ASSET_ETH_VALUE),
|
||||
from: TAKER_ADDRESS,
|
||||
gasPrice: quote.gasPrice,
|
||||
});
|
||||
if (checkHadEnoughTakerAsset(quote, fill)) {
|
||||
expect(fill.fillResults.takerAssetFilledAmount, 'takerAssetFilledAmount').to.bignumber.lte(
|
||||
quote.worstCaseQuoteInfo.takerAssetAmount,
|
||||
);
|
||||
expect(fill.fillResults.makerAssetFilledAmount, 'makerAssetFilledAmount').to.bignumber.gte(
|
||||
quote.makerAssetFillAmount,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
@@ -1,77 +0,0 @@
|
||||
export const tokens: { [symbol: string]: { address: string; decimals: number; price: number } } = {
|
||||
ETH: {
|
||||
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
decimals: 18,
|
||||
price: 133,
|
||||
},
|
||||
SAI: {
|
||||
address: '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
|
||||
decimals: 18,
|
||||
price: 1,
|
||||
},
|
||||
DAI: {
|
||||
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
||||
decimals: 18,
|
||||
price: 1,
|
||||
},
|
||||
USDC: {
|
||||
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
||||
decimals: 6,
|
||||
price: 1,
|
||||
},
|
||||
WBTC: {
|
||||
address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
|
||||
decimals: 8,
|
||||
price: 6900,
|
||||
},
|
||||
MKR: {
|
||||
address: '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||
decimals: 18,
|
||||
price: 454,
|
||||
},
|
||||
BAT: {
|
||||
address: '0x0D8775F648430679A709E98d2b0Cb6250d2887EF',
|
||||
decimals: 18,
|
||||
price: 0.17,
|
||||
},
|
||||
OMG: {
|
||||
address: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07',
|
||||
decimals: 18,
|
||||
price: 0.65,
|
||||
},
|
||||
ZRX: {
|
||||
address: '0xE41d2489571d322189246DaFA5ebDe1F4699F498',
|
||||
decimals: 18,
|
||||
price: 0.19,
|
||||
},
|
||||
ZIL: {
|
||||
address: '0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27',
|
||||
decimals: 12,
|
||||
price: 0.004,
|
||||
},
|
||||
FOAM: {
|
||||
address: '0x4946Fcea7C692606e8908002e55A582af44AC121',
|
||||
decimals: 18,
|
||||
price: 0.004,
|
||||
},
|
||||
USDT: {
|
||||
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||
decimals: 6,
|
||||
price: 0.019,
|
||||
},
|
||||
REP: {
|
||||
address: '0x1985365e9f78359a9B6AD760e32412f4a445E862',
|
||||
decimals: 18,
|
||||
price: 8.9,
|
||||
},
|
||||
MANA: {
|
||||
address: '0x0F5D2fB29fb7d3CFeE444a200298f468908cC942',
|
||||
decimals: 18,
|
||||
price: 0.025,
|
||||
},
|
||||
LINK: {
|
||||
address: '0x514910771AF9Ca656af840dff83E8264EcF986CA',
|
||||
decimals: 18,
|
||||
price: 1.8,
|
||||
},
|
||||
};
|
@@ -1,360 +0,0 @@
|
||||
import { ContractAddresses } from '@0x/contract-addresses';
|
||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { IExchangeContract } from '@0x/contracts-exchange';
|
||||
import { blockchainTests, constants, expect, getRandomPortion, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||
import {
|
||||
artifacts as exchangeProxyArtifacts,
|
||||
IZeroExContract,
|
||||
LogMetadataTransformerContract,
|
||||
} from '@0x/contracts-zero-ex';
|
||||
import { migrateOnceAsync } from '@0x/migrations';
|
||||
import { assetDataUtils, signatureUtils, SignedExchangeProxyMetaTransaction } from '@0x/order-utils';
|
||||
import {
|
||||
encodeFillQuoteTransformerData,
|
||||
encodePayTakerTransformerData,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
FillQuoteTransformerSide,
|
||||
findTransformerNonce,
|
||||
Signature,
|
||||
} from '@0x/protocol-utils';
|
||||
import { AssetProxyId, Order, SignedOrder } from '@0x/types';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
|
||||
const { MAX_UINT256, NULL_ADDRESS, NULL_BYTES, ZERO_AMOUNT } = constants;
|
||||
|
||||
function sigstruct(signature: string): Signature {
|
||||
return {
|
||||
v: parseInt(hexUtils.slice(signature, 0, 1), 16),
|
||||
signatureType: parseInt(hexUtils.slice(signature, 65, 66), 16),
|
||||
r: hexUtils.slice(signature, 1, 33),
|
||||
s: hexUtils.slice(signature, 33, 65),
|
||||
};
|
||||
}
|
||||
|
||||
blockchainTests.resets('exchange proxy - meta-transactions', env => {
|
||||
const quoteSignerKey = hexUtils.random();
|
||||
const quoteSigner = hexUtils.toHex(ethjs.privateToAddress(ethjs.toBuffer(quoteSignerKey)));
|
||||
let owner: string;
|
||||
let relayer: string;
|
||||
let maker: string;
|
||||
let taker: string;
|
||||
let flashWalletAddress: string;
|
||||
let zeroEx: IZeroExContract;
|
||||
let exchange: IExchangeContract;
|
||||
let inputToken: DummyERC20TokenContract;
|
||||
let outputToken: DummyERC20TokenContract;
|
||||
let feeToken: DummyERC20TokenContract;
|
||||
let addresses: ContractAddresses;
|
||||
let protocolFee: BigNumber;
|
||||
let metadataTransformer: LogMetadataTransformerContract;
|
||||
const GAS_PRICE = new BigNumber('1e9');
|
||||
const MAKER_BALANCE = new BigNumber('100e18');
|
||||
const TAKER_BALANCE = new BigNumber('100e18');
|
||||
const TAKER_FEE_BALANCE = new BigNumber('100e18');
|
||||
|
||||
before(async () => {
|
||||
[, relayer, maker, taker] = await env.getAccountAddressesAsync();
|
||||
addresses = await migrateOnceAsync(env.provider);
|
||||
zeroEx = new IZeroExContract(addresses.exchangeProxy, env.provider, env.txDefaults, {
|
||||
LogMetadataTransformer: LogMetadataTransformerContract.ABI(),
|
||||
DummyERC20Token: DummyERC20TokenContract.ABI(),
|
||||
});
|
||||
exchange = new IExchangeContract(addresses.exchange, env.provider, env.txDefaults);
|
||||
[inputToken, outputToken, feeToken] = await Promise.all(
|
||||
[...new Array(3)].map(i =>
|
||||
DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
erc20Artifacts.DummyERC20Token,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
{},
|
||||
`DummyToken-${i}`,
|
||||
`TOK${i}`,
|
||||
new BigNumber(18),
|
||||
BigNumber.max(MAKER_BALANCE, TAKER_BALANCE),
|
||||
),
|
||||
),
|
||||
);
|
||||
// LogMetadataTransformer is not deployed in migrations.
|
||||
metadataTransformer = await LogMetadataTransformerContract.deployFrom0xArtifactAsync(
|
||||
exchangeProxyArtifacts.LogMetadataTransformer,
|
||||
env.provider,
|
||||
{
|
||||
...env.txDefaults,
|
||||
from: addresses.exchangeProxyTransformerDeployer,
|
||||
},
|
||||
{},
|
||||
);
|
||||
owner = await zeroEx.owner().callAsync();
|
||||
protocolFee = await exchange.protocolFeeMultiplier().callAsync();
|
||||
flashWalletAddress = await zeroEx.getTransformWallet().callAsync();
|
||||
const erc20Proxy = await exchange.getAssetProxy(AssetProxyId.ERC20).callAsync();
|
||||
const allowanceTarget = await zeroEx.getAllowanceTarget().callAsync();
|
||||
await outputToken.mint(MAKER_BALANCE).awaitTransactionSuccessAsync({ from: maker });
|
||||
await inputToken.mint(TAKER_BALANCE).awaitTransactionSuccessAsync({ from: taker });
|
||||
await feeToken.mint(TAKER_FEE_BALANCE).awaitTransactionSuccessAsync({ from: taker });
|
||||
await outputToken.approve(erc20Proxy, MAX_UINT256).awaitTransactionSuccessAsync({ from: maker });
|
||||
await inputToken.approve(allowanceTarget, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker });
|
||||
await feeToken.approve(allowanceTarget, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker });
|
||||
await zeroEx.setQuoteSigner(quoteSigner).awaitTransactionSuccessAsync({ from: owner });
|
||||
});
|
||||
|
||||
interface Transformation {
|
||||
deploymentNonce: number;
|
||||
data: string;
|
||||
}
|
||||
|
||||
interface SwapInfo {
|
||||
inputTokenAddress: string;
|
||||
outputTokenAddress: string;
|
||||
inputTokenAmount: BigNumber;
|
||||
minOutputTokenAmount: BigNumber;
|
||||
transformations: Transformation[];
|
||||
orders: SignedOrder[];
|
||||
}
|
||||
|
||||
async function generateSwapAsync(orderFields: Partial<Order> = {}, isRfqt: boolean = false): Promise<SwapInfo> {
|
||||
const order = await signatureUtils.ecSignTypedDataOrderAsync(
|
||||
env.provider,
|
||||
{
|
||||
chainId: 1337,
|
||||
exchangeAddress: exchange.address,
|
||||
expirationTimeSeconds: new BigNumber(Date.now()),
|
||||
salt: new BigNumber(hexUtils.random()),
|
||||
feeRecipientAddress: NULL_ADDRESS,
|
||||
senderAddress: NULL_ADDRESS,
|
||||
takerAddress: isRfqt ? flashWalletAddress : NULL_ADDRESS,
|
||||
makerAddress: maker,
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(outputToken.address),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(inputToken.address),
|
||||
makerFeeAssetData: NULL_BYTES,
|
||||
takerFeeAssetData: NULL_BYTES,
|
||||
takerAssetAmount: getRandomPortion(TAKER_BALANCE),
|
||||
makerAssetAmount: getRandomPortion(MAKER_BALANCE),
|
||||
makerFee: ZERO_AMOUNT,
|
||||
takerFee: ZERO_AMOUNT,
|
||||
...orderFields,
|
||||
},
|
||||
maker,
|
||||
);
|
||||
const transformations = [
|
||||
{
|
||||
deploymentNonce: findTransformerNonce(
|
||||
addresses.transformers.fillQuoteTransformer,
|
||||
addresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
data: encodeFillQuoteTransformerData({
|
||||
orders: [order],
|
||||
signatures: [order.signature],
|
||||
buyToken: outputToken.address,
|
||||
sellToken: inputToken.address,
|
||||
fillAmount: order.takerAssetAmount,
|
||||
maxOrderFillAmounts: [],
|
||||
refundReceiver: hexUtils.leftPad(2, 20), // Send refund to sender.
|
||||
rfqtTakerAddress: isRfqt ? taker : NULL_ADDRESS,
|
||||
side: FillQuoteTransformerSide.Sell,
|
||||
}),
|
||||
},
|
||||
{
|
||||
deploymentNonce: findTransformerNonce(
|
||||
addresses.transformers.payTakerTransformer,
|
||||
addresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
data: encodePayTakerTransformerData({
|
||||
tokens: [inputToken.address, outputToken.address, ETH_TOKEN_ADDRESS],
|
||||
amounts: [MAX_UINT256, MAX_UINT256, MAX_UINT256],
|
||||
}),
|
||||
},
|
||||
{
|
||||
deploymentNonce: findTransformerNonce(
|
||||
metadataTransformer.address,
|
||||
addresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
data: NULL_BYTES,
|
||||
},
|
||||
];
|
||||
return {
|
||||
transformations,
|
||||
orders: [order],
|
||||
inputTokenAddress: inputToken.address,
|
||||
outputTokenAddress: outputToken.address,
|
||||
inputTokenAmount: order.takerAssetAmount,
|
||||
minOutputTokenAmount: order.makerAssetAmount,
|
||||
};
|
||||
}
|
||||
|
||||
function getSwapData(swap: SwapInfo): string {
|
||||
return zeroEx
|
||||
.transformERC20(
|
||||
swap.inputTokenAddress,
|
||||
swap.outputTokenAddress,
|
||||
swap.inputTokenAmount,
|
||||
swap.minOutputTokenAmount,
|
||||
swap.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
}
|
||||
|
||||
async function createMetaTransactionAsync(
|
||||
data: string,
|
||||
value: BigNumber,
|
||||
fee?: BigNumber | number,
|
||||
): Promise<SignedExchangeProxyMetaTransaction> {
|
||||
return signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync(
|
||||
env.provider,
|
||||
{
|
||||
value,
|
||||
signer: taker,
|
||||
sender: relayer,
|
||||
minGasPrice: GAS_PRICE,
|
||||
maxGasPrice: GAS_PRICE,
|
||||
expirationTimeSeconds: new BigNumber(Math.floor(Date.now() / 1000) + 60),
|
||||
salt: new BigNumber(hexUtils.random()),
|
||||
callData: data,
|
||||
feeToken: feeToken.address,
|
||||
feeAmount: fee !== undefined ? new BigNumber(fee) : getRandomPortion(TAKER_FEE_BALANCE),
|
||||
domain: {
|
||||
chainId: 1,
|
||||
name: 'ZeroEx',
|
||||
version: '1.0.0',
|
||||
verifyingContract: zeroEx.address,
|
||||
},
|
||||
},
|
||||
taker,
|
||||
);
|
||||
}
|
||||
|
||||
it('can call `transformERC20()` with calldata and no relayer fee', async () => {
|
||||
const swap = await generateSwapAsync();
|
||||
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
|
||||
const mtx = await createMetaTransactionAsync(getSwapData(swap), _protocolFee, 0);
|
||||
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
|
||||
const receipt = await zeroEx
|
||||
.executeMetaTransaction(mtx, sigstruct(mtx.signature))
|
||||
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
|
||||
const relayerEthRefund = relayerEthBalanceBefore
|
||||
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
|
||||
.minus(GAS_PRICE.times(receipt.gasUsed));
|
||||
// Ensure the relayer got back the unused protocol fees.
|
||||
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
|
||||
// Ensure the relayer got paid no mtx fees.
|
||||
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
|
||||
// Ensure the taker got output tokens.
|
||||
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
|
||||
// Ensure the maker got input tokens.
|
||||
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
|
||||
// Check events.
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[
|
||||
{
|
||||
taker,
|
||||
sender: zeroEx.address,
|
||||
data: NULL_BYTES,
|
||||
},
|
||||
],
|
||||
'TransformerMetadata',
|
||||
);
|
||||
});
|
||||
|
||||
it('can call `transformERC20()` with calldata and a relayer fee', async () => {
|
||||
const swap = await generateSwapAsync();
|
||||
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
|
||||
const mtx = await createMetaTransactionAsync(getSwapData(swap), _protocolFee);
|
||||
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
|
||||
const receipt = await zeroEx
|
||||
.executeMetaTransaction(mtx, sigstruct(mtx.signature))
|
||||
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
|
||||
const relayerEthRefund = relayerEthBalanceBefore
|
||||
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
|
||||
.minus(GAS_PRICE.times(receipt.gasUsed));
|
||||
// Ensure the relayer got back the unused protocol fees.
|
||||
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
|
||||
// Ensure the relayer got paid mtx fees.
|
||||
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(mtx.feeAmount);
|
||||
// Ensure the taker got output tokens.
|
||||
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
|
||||
// Ensure the maker got input tokens.
|
||||
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
|
||||
// Check events.
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[
|
||||
{
|
||||
taker,
|
||||
sender: zeroEx.address,
|
||||
data: NULL_BYTES,
|
||||
},
|
||||
],
|
||||
'TransformerMetadata',
|
||||
);
|
||||
});
|
||||
|
||||
it('`transformERC20()` can fill RFQT order', async () => {
|
||||
const swap = await generateSwapAsync({}, true);
|
||||
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
|
||||
const mtx = await createMetaTransactionAsync(getSwapData(swap), _protocolFee, 0);
|
||||
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
|
||||
const receipt = await zeroEx
|
||||
.executeMetaTransaction(mtx, sigstruct(mtx.signature))
|
||||
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
|
||||
const relayerEthRefund = relayerEthBalanceBefore
|
||||
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
|
||||
.minus(GAS_PRICE.times(receipt.gasUsed));
|
||||
// Ensure the relayer got back the unused protocol fees.
|
||||
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
|
||||
// Ensure the relayer got paid no mtx fees.
|
||||
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
|
||||
// Ensure the taker got output tokens.
|
||||
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
|
||||
// Ensure the maker got input tokens.
|
||||
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
|
||||
// Check events.
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[
|
||||
{
|
||||
taker,
|
||||
sender: zeroEx.address,
|
||||
data: NULL_BYTES,
|
||||
},
|
||||
],
|
||||
'TransformerMetadata',
|
||||
);
|
||||
});
|
||||
|
||||
it('`transformERC20()` can fill RFQT order if quote signer configured', async () => {
|
||||
const swap = await generateSwapAsync({}, true);
|
||||
const callData = getSwapData(swap);
|
||||
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
|
||||
const mtx = await createMetaTransactionAsync(callData, _protocolFee, 0);
|
||||
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
|
||||
await zeroEx.setQuoteSigner(NULL_ADDRESS).awaitTransactionSuccessAsync({ from: owner });
|
||||
const receipt = await zeroEx
|
||||
.executeMetaTransaction(mtx, sigstruct(mtx.signature))
|
||||
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
|
||||
const relayerEthRefund = relayerEthBalanceBefore
|
||||
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
|
||||
.minus(GAS_PRICE.times(receipt.gasUsed));
|
||||
// Ensure the relayer got back the unused protocol fees.
|
||||
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
|
||||
// Ensure the relayer got paid no mtx fees.
|
||||
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
|
||||
// Ensure the taker got output tokens.
|
||||
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
|
||||
// Ensure the maker got input tokens.
|
||||
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
|
||||
// Check events.
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[
|
||||
{
|
||||
taker,
|
||||
sender: zeroEx.address,
|
||||
data: NULL_BYTES,
|
||||
},
|
||||
],
|
||||
'TransformerMetadata',
|
||||
);
|
||||
});
|
||||
});
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "4.1.27",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "4.1.26",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "4.1.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "4.1.24",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v4.1.27 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.1.26 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.1.25 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.1.24 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-multisig",
|
||||
"version": "4.1.24",
|
||||
"version": "4.1.27",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -49,14 +49,14 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/multisig",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-asset-proxy": "^3.7.5",
|
||||
"@0x/contracts-erc20": "^3.3.2",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-asset-proxy": "^3.7.8",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
"@0x/utils": "^6.2.0",
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "2.0.34",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "2.0.33",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "2.0.32",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "2.0.31",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.0.34 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.33 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.32 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.31 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-staking",
|
||||
"version": "2.0.31",
|
||||
"version": "2.0.34",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -53,16 +53,16 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/tokens",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-asset-proxy": "^3.7.5",
|
||||
"@0x/contracts-dev-utils": "^1.3.22",
|
||||
"@0x/contracts-erc20": "^3.3.2",
|
||||
"@0x/contracts-exchange-libs": "^4.3.23",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-utils": "^4.7.2",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-asset-proxy": "^3.7.8",
|
||||
"@0x/contracts-dev-utils": "^1.3.25",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-exchange-libs": "^4.3.26",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-utils": "^4.7.5",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
@@ -88,7 +88,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/typescript-typings": "^5.1.6",
|
||||
"@0x/utils": "^6.2.0",
|
||||
"ethereum-types": "^3.4.0",
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "5.3.23",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "5.3.22",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "5.3.21",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "5.3.20",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v5.3.23 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.3.22 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.3.21 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.3.20 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-test-utils",
|
||||
"version": "5.3.20",
|
||||
"version": "5.3.23",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/test-utils",
|
||||
"devDependencies": {
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"shx": "^0.2.2",
|
||||
@@ -44,13 +44,13 @@
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.21",
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/contract-addresses": "^5.9.0",
|
||||
"@0x/contract-addresses": "^5.11.0",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/json-schemas": "^5.4.1",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/sol-coverage": "^4.0.29",
|
||||
"@0x/sol-profiler": "^4.1.19",
|
||||
"@0x/sol-trace": "^3.0.29",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/sol-coverage": "^4.0.31",
|
||||
"@0x/sol-profiler": "^4.1.21",
|
||||
"@0x/sol-trace": "^3.0.31",
|
||||
"@0x/subproviders": "^6.4.1",
|
||||
"@0x/types": "^3.3.1",
|
||||
"@0x/typescript-typings": "^5.1.6",
|
||||
|
@@ -1,4 +1,32 @@
|
||||
[
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Make the proposal/quorum thresholds updatable",
|
||||
"pr": 165
|
||||
}
|
||||
],
|
||||
"timestamp": 1616005394
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "1.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "1.0.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"changes": [
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.1.0 - _March 17, 2021_
|
||||
|
||||
* Make the proposal/quorum thresholds updatable (#165)
|
||||
|
||||
## v1.0.2 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.1 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.0 - _January 28, 2021_
|
||||
|
||||
* Create this package (#120)
|
||||
|
@@ -96,6 +96,18 @@ interface IZrxTreasury {
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
/// @dev Updates the proposal and quorum thresholds to the given
|
||||
/// values. Note that this function is only callable by the
|
||||
/// treasury contract itself, so the threshold can only be
|
||||
/// updated via a successful treasury proposal.
|
||||
/// @param newProposalThreshold The new value for the proposal threshold.
|
||||
/// @param newQuorumThreshold The new value for the quorum threshold.
|
||||
function updateThresholds(
|
||||
uint256 newProposalThreshold,
|
||||
uint256 newQuorumThreshold
|
||||
)
|
||||
external;
|
||||
|
||||
/// @dev Creates a proposal to send ZRX from this treasury on the
|
||||
/// the given actions. Must have at least `proposalThreshold`
|
||||
/// of voting power to call this function. See `getVotingPower`
|
||||
|
@@ -42,8 +42,8 @@ contract ZrxTreasury is
|
||||
DefaultPoolOperator public immutable override defaultPoolOperator;
|
||||
bytes32 public immutable override defaultPoolId;
|
||||
uint256 public immutable override votingPeriod;
|
||||
uint256 public immutable override proposalThreshold;
|
||||
uint256 public immutable override quorumThreshold;
|
||||
uint256 public override proposalThreshold;
|
||||
uint256 public override quorumThreshold;
|
||||
|
||||
// Storage
|
||||
Proposal[] public proposals;
|
||||
@@ -82,6 +82,24 @@ contract ZrxTreasury is
|
||||
receive() external payable {}
|
||||
// solhint-enable
|
||||
|
||||
/// @dev Updates the proposal and quorum thresholds to the given
|
||||
/// values. Note that this function is only callable by the
|
||||
/// treasury contract itself, so the threshold can only be
|
||||
/// updated via a successful treasury proposal.
|
||||
/// @param newProposalThreshold The new value for the proposal threshold.
|
||||
/// @param newQuorumThreshold The new value for the quorum threshold.
|
||||
function updateThresholds(
|
||||
uint256 newProposalThreshold,
|
||||
uint256 newQuorumThreshold
|
||||
)
|
||||
external
|
||||
override
|
||||
{
|
||||
require(msg.sender == address(this), "updateThresholds/ONLY_SELF");
|
||||
proposalThreshold = newProposalThreshold;
|
||||
quorumThreshold = newQuorumThreshold;
|
||||
}
|
||||
|
||||
/// @dev Creates a proposal to send ZRX from this treasury on the
|
||||
/// the given actions. Must have at least `proposalThreshold`
|
||||
/// of voting power to call this function. See `getVotingPower`
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-treasury",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -46,14 +46,14 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/treasury",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.13",
|
||||
"@0x/contract-addresses": "^5.8.0",
|
||||
"@0x/contracts-asset-proxy": "^3.7.3",
|
||||
"@0x/contracts-erc20": "^3.3.0",
|
||||
"@0x/contracts-gen": "^2.0.24",
|
||||
"@0x/contracts-staking": "^2.0.29",
|
||||
"@0x/contracts-test-utils": "^5.3.18",
|
||||
"@0x/sol-compiler": "^4.4.1",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contract-addresses": "^5.11.0",
|
||||
"@0x/contracts-asset-proxy": "^3.7.8",
|
||||
"@0x/contracts-erc20": "^3.3.5",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-staking": "^2.0.34",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@types/isomorphic-fetch": "^0.0.35",
|
||||
@@ -72,13 +72,13 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.2.14",
|
||||
"@0x/protocol-utils": "^1.1.3",
|
||||
"@0x/subproviders": "^6.2.3",
|
||||
"@0x/base-contract": "^6.2.18",
|
||||
"@0x/protocol-utils": "^1.3.1",
|
||||
"@0x/subproviders": "^6.4.1",
|
||||
"@0x/types": "^3.3.1",
|
||||
"@0x/typescript-typings": "^5.1.6",
|
||||
"@0x/utils": "^6.1.1",
|
||||
"@0x/web3-wrapper": "^7.3.0",
|
||||
"@0x/utils": "^6.2.0",
|
||||
"@0x/web3-wrapper": "^7.4.1",
|
||||
"ethereum-types": "^3.4.0",
|
||||
"ethereumjs-util": "^5.1.1"
|
||||
},
|
||||
|
@@ -580,4 +580,47 @@ blockchainTests.resets('Treasury governance', env => {
|
||||
expect(await weth.balanceOf(staking.address).callAsync()).to.bignumber.equal(wethAmount);
|
||||
});
|
||||
});
|
||||
describe('Can update thresholds via proposal', () => {
|
||||
it('Updates proposal and quorum thresholds', async () => {
|
||||
// Delegator has enough ZRX to create and pass a proposal
|
||||
await staking.stake(TREASURY_PARAMS.quorumThreshold).awaitTransactionSuccessAsync({ from: delegator });
|
||||
await staking
|
||||
.moveStake(
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, defaultPoolId),
|
||||
TREASURY_PARAMS.quorumThreshold,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: delegator });
|
||||
await fastForwardToNextEpochAsync();
|
||||
const currentEpoch = await staking.currentEpoch().callAsync();
|
||||
const newProposalThreshold = new BigNumber(420);
|
||||
const newQuorumThreshold = new BigNumber(1337);
|
||||
const updateThresholdsAction = {
|
||||
target: treasury.address,
|
||||
data: treasury
|
||||
.updateThresholds(newProposalThreshold, newQuorumThreshold)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: constants.ZERO_AMOUNT,
|
||||
};
|
||||
const tx = treasury.propose(
|
||||
[updateThresholdsAction],
|
||||
currentEpoch.plus(3),
|
||||
`Updates proposal threshold to ${newProposalThreshold} and quorum threshold to ${newQuorumThreshold}`,
|
||||
[],
|
||||
);
|
||||
const proposalId = await tx.callAsync({ from: delegator });
|
||||
await tx.awaitTransactionSuccessAsync({ from: delegator });
|
||||
await fastForwardToNextEpochAsync();
|
||||
await fastForwardToNextEpochAsync();
|
||||
await treasury.castVote(proposalId, true, []).awaitTransactionSuccessAsync({ from: delegator });
|
||||
await fastForwardToNextEpochAsync();
|
||||
await treasury
|
||||
.execute(proposalId, [updateThresholdsAction])
|
||||
.awaitTransactionSuccessAsync({ from: delegator });
|
||||
const proposalThreshold = await treasury.proposalThreshold().callAsync();
|
||||
const quorumThreshold = await treasury.quorumThreshold().callAsync();
|
||||
expect(proposalThreshold).to.bignumber.equal(newProposalThreshold);
|
||||
expect(quorumThreshold).to.bignumber.equal(newQuorumThreshold);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1616005394,
|
||||
"version": "4.7.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1614141718,
|
||||
"version": "4.7.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1612950500,
|
||||
"version": "4.7.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1611648096,
|
||||
"version": "4.7.2",
|
||||
|
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v4.7.5 - _March 17, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.7.4 - _February 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.7.3 - _February 10, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.7.2 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-utils",
|
||||
"version": "4.7.2",
|
||||
"version": "4.7.5",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -50,12 +50,12 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/utils",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.19",
|
||||
"@0x/contracts-gen": "^2.0.30",
|
||||
"@0x/contracts-test-utils": "^5.3.20",
|
||||
"@0x/abi-gen": "^5.4.21",
|
||||
"@0x/contracts-gen": "^2.0.32",
|
||||
"@0x/contracts-test-utils": "^5.3.23",
|
||||
"@0x/dev-utils": "^4.2.1",
|
||||
"@0x/order-utils": "^10.4.15",
|
||||
"@0x/sol-compiler": "^4.5.2",
|
||||
"@0x/order-utils": "^10.4.18",
|
||||
"@0x/sol-compiler": "^4.6.1",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
"@0x/web3-wrapper": "^7.4.1",
|
||||
|
@@ -1,4 +1,66 @@
|
||||
[
|
||||
{
|
||||
"version": "0.20.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `MooniswapLiquidityProvider`",
|
||||
"pr": 143
|
||||
},
|
||||
{
|
||||
"note": "Emit `LiquidityProviderFill` event in `CurveLiquidityProvider`",
|
||||
"pr": 143
|
||||
},
|
||||
{
|
||||
"note": "Add BatchFillNativeOrdersFeature and MultiplexFeature",
|
||||
"pr": 140
|
||||
},
|
||||
{
|
||||
"note": "Export MultiplexFeatureContract",
|
||||
"pr": 168
|
||||
}
|
||||
],
|
||||
"timestamp": 1616005394
|
||||
},
|
||||
{
|
||||
"version": "0.19.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `CurveLiquidityProvider` and misc refactors",
|
||||
"pr": 127
|
||||
},
|
||||
{
|
||||
"note": "Export `CurveLiquidityProviderContract`",
|
||||
"pr": 144
|
||||
},
|
||||
{
|
||||
"note": "Add `DodoV2`",
|
||||
"pr": 152
|
||||
},
|
||||
{
|
||||
"note": "Add `Linkswap`",
|
||||
"pr": 153
|
||||
},
|
||||
{
|
||||
"note": "refund ETH with no gas limit in FQT",
|
||||
"pr": 155
|
||||
},
|
||||
{
|
||||
"note": "Added an opt-in `PositiveSlippageAffiliateFee`",
|
||||
"pr": 101
|
||||
}
|
||||
],
|
||||
"timestamp": 1614141718
|
||||
},
|
||||
{
|
||||
"version": "0.18.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update FQT for v4 native orders",
|
||||
"pr": 104
|
||||
}
|
||||
],
|
||||
"timestamp": 1612950500
|
||||
},
|
||||
{
|
||||
"version": "0.18.1",
|
||||
"changes": [
|
||||
|
@@ -5,6 +5,26 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v0.20.0 - _March 17, 2021_
|
||||
|
||||
* Add `MooniswapLiquidityProvider` (#143)
|
||||
* Emit `LiquidityProviderFill` event in `CurveLiquidityProvider` (#143)
|
||||
* Add BatchFillNativeOrdersFeature and MultiplexFeature (#140)
|
||||
* Export MultiplexFeatureContract (#168)
|
||||
|
||||
## v0.19.0 - _February 24, 2021_
|
||||
|
||||
* Add `CurveLiquidityProvider` and misc refactors (#127)
|
||||
* Export `CurveLiquidityProviderContract` (#144)
|
||||
* Add `DodoV2` (#152)
|
||||
* Add `Linkswap` (#153)
|
||||
* refund ETH with no gas limit in FQT (#155)
|
||||
* Added an opt-in `PositiveSlippageAffiliateFee` (#101)
|
||||
|
||||
## v0.18.2 - _February 10, 2021_
|
||||
|
||||
* Update FQT for v4 native orders (#104)
|
||||
|
||||
## v0.18.1 - _January 26, 2021_
|
||||
|
||||
* Swallow reverts in `batchGetLimitOrderRelevantStates()` and `batchGetRfqOrderRelevantStates()` (#117)
|
||||
|
@@ -20,14 +20,16 @@
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./features/IOwnableFeature.sol";
|
||||
import "./features/ISimpleFunctionRegistryFeature.sol";
|
||||
import "./features/ITokenSpenderFeature.sol";
|
||||
import "./features/ITransformERC20Feature.sol";
|
||||
import "./features/IMetaTransactionsFeature.sol";
|
||||
import "./features/IUniswapFeature.sol";
|
||||
import "./features/ILiquidityProviderFeature.sol";
|
||||
import "./features/INativeOrdersFeature.sol";
|
||||
import "./features/interfaces/IOwnableFeature.sol";
|
||||
import "./features/interfaces/ISimpleFunctionRegistryFeature.sol";
|
||||
import "./features/interfaces/ITokenSpenderFeature.sol";
|
||||
import "./features/interfaces/ITransformERC20Feature.sol";
|
||||
import "./features/interfaces/IMetaTransactionsFeature.sol";
|
||||
import "./features/interfaces/IUniswapFeature.sol";
|
||||
import "./features/interfaces/ILiquidityProviderFeature.sol";
|
||||
import "./features/interfaces/INativeOrdersFeature.sol";
|
||||
import "./features/interfaces/IBatchFillNativeOrdersFeature.sol";
|
||||
import "./features/interfaces/IMultiplexFeature.sol";
|
||||
|
||||
|
||||
/// @dev Interface for a fully featured Exchange Proxy.
|
||||
@@ -39,7 +41,9 @@ interface IZeroEx is
|
||||
IMetaTransactionsFeature,
|
||||
IUniswapFeature,
|
||||
ILiquidityProviderFeature,
|
||||
INativeOrdersFeature
|
||||
INativeOrdersFeature,
|
||||
IBatchFillNativeOrdersFeature,
|
||||
IMultiplexFeature
|
||||
{
|
||||
// solhint-disable state-visibility
|
||||
|
||||
|
@@ -170,4 +170,21 @@ library LibNativeOrdersRichErrors {
|
||||
maker
|
||||
);
|
||||
}
|
||||
|
||||
function BatchFillIncompleteError(
|
||||
bytes32 orderHash,
|
||||
uint256 takerTokenFilledAmount,
|
||||
uint256 takerTokenFillAmount
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("BatchFillIncompleteError(bytes32,uint256,uint256)")),
|
||||
orderHash,
|
||||
takerTokenFilledAmount,
|
||||
takerTokenFillAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,9 @@
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../vendor/ILiquidityProvider.sol";
|
||||
|
||||
|
||||
interface ILiquidityProviderSandbox {
|
||||
|
||||
@@ -32,9 +35,9 @@ interface ILiquidityProviderSandbox {
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellTokenForToken(
|
||||
address provider,
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@@ -49,8 +52,8 @@ interface ILiquidityProviderSandbox {
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellEthForToken(
|
||||
address provider,
|
||||
address outputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@@ -65,8 +68,8 @@ interface ILiquidityProviderSandbox {
|
||||
/// @param minBuyAmount The minimum acceptable amount of ETH to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellTokenForEth(
|
||||
address provider,
|
||||
address inputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
|
@@ -17,6 +17,7 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibOwnableRichErrorsV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../vendor/ILiquidityProvider.sol";
|
||||
import "../vendor/v3/IERC20Bridge.sol";
|
||||
import "./ILiquidityProviderSandbox.sol";
|
||||
@@ -58,9 +59,9 @@ contract LiquidityProviderSandbox is
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellTokenForToken(
|
||||
address provider,
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@@ -69,7 +70,7 @@ contract LiquidityProviderSandbox is
|
||||
onlyOwner
|
||||
override
|
||||
{
|
||||
ILiquidityProvider(provider).sellTokenForToken(
|
||||
provider.sellTokenForToken(
|
||||
inputToken,
|
||||
outputToken,
|
||||
recipient,
|
||||
@@ -86,8 +87,8 @@ contract LiquidityProviderSandbox is
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellEthForToken(
|
||||
address provider,
|
||||
address outputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@@ -96,7 +97,7 @@ contract LiquidityProviderSandbox is
|
||||
onlyOwner
|
||||
override
|
||||
{
|
||||
ILiquidityProvider(provider).sellEthForToken(
|
||||
provider.sellEthForToken(
|
||||
outputToken,
|
||||
recipient,
|
||||
minBuyAmount,
|
||||
@@ -112,8 +113,8 @@ contract LiquidityProviderSandbox is
|
||||
/// @param minBuyAmount The minimum acceptable amount of ETH to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellTokenForEth(
|
||||
address provider,
|
||||
address inputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@@ -122,7 +123,7 @@ contract LiquidityProviderSandbox is
|
||||
onlyOwner
|
||||
override
|
||||
{
|
||||
ILiquidityProvider(provider).sellTokenForEth(
|
||||
provider.sellTokenForEth(
|
||||
inputToken,
|
||||
payable(recipient),
|
||||
minBuyAmount,
|
||||
|
@@ -0,0 +1,198 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||
import "../errors/LibNativeOrdersRichErrors.sol";
|
||||
import "../fixins/FixinCommon.sol";
|
||||
import "../fixins/FixinEIP712.sol";
|
||||
import "../migrations/LibMigrate.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/IBatchFillNativeOrdersFeature.sol";
|
||||
import "./interfaces/INativeOrdersFeature.sol";
|
||||
import "./libs/LibNativeOrder.sol";
|
||||
import "./libs/LibSignature.sol";
|
||||
|
||||
|
||||
/// @dev Feature for batch/market filling limit and RFQ orders.
|
||||
contract BatchFillNativeOrdersFeature is
|
||||
IFeature,
|
||||
IBatchFillNativeOrdersFeature,
|
||||
FixinCommon,
|
||||
FixinEIP712
|
||||
{
|
||||
using LibSafeMathV06 for uint128;
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "BatchFill";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||
|
||||
constructor(address zeroExAddress)
|
||||
public
|
||||
FixinEIP712(zeroExAddress)
|
||||
{
|
||||
// solhint-disable no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initialize and register this feature.
|
||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||
/// @return success `LibMigrate.SUCCESS` on success.
|
||||
function migrate()
|
||||
external
|
||||
returns (bytes4 success)
|
||||
{
|
||||
_registerFeatureFunction(this.batchFillLimitOrders.selector);
|
||||
_registerFeatureFunction(this.batchFillRfqOrders.selector);
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
}
|
||||
|
||||
/// @dev Fills multiple limit orders.
|
||||
/// @param orders Array of limit orders.
|
||||
/// @param signatures Array of signatures corresponding to each order.
|
||||
/// @param takerTokenFillAmounts Array of desired amounts to fill each order.
|
||||
/// @param revertIfIncomplete If true, reverts if this function fails to
|
||||
/// fill the full fill amount for any individual order.
|
||||
/// @return takerTokenFilledAmounts Array of amounts filled, in taker token.
|
||||
/// @return makerTokenFilledAmounts Array of amounts filled, in maker token.
|
||||
function batchFillLimitOrders(
|
||||
LibNativeOrder.LimitOrder[] calldata orders,
|
||||
LibSignature.Signature[] calldata signatures,
|
||||
uint128[] calldata takerTokenFillAmounts,
|
||||
bool revertIfIncomplete
|
||||
)
|
||||
external
|
||||
payable
|
||||
override
|
||||
returns (
|
||||
uint128[] memory takerTokenFilledAmounts,
|
||||
uint128[] memory makerTokenFilledAmounts
|
||||
)
|
||||
{
|
||||
require(
|
||||
orders.length == signatures.length && orders.length == takerTokenFillAmounts.length,
|
||||
'BatchFillNativeOrdersFeature::batchFillLimitOrders/MISMATCHED_ARRAY_LENGTHS'
|
||||
);
|
||||
takerTokenFilledAmounts = new uint128[](orders.length);
|
||||
makerTokenFilledAmounts = new uint128[](orders.length);
|
||||
uint256 protocolFee = uint256(INativeOrdersFeature(address(this)).getProtocolFeeMultiplier())
|
||||
.safeMul(tx.gasprice);
|
||||
uint256 ethProtocolFeePaid;
|
||||
for (uint256 i = 0; i != orders.length; i++) {
|
||||
try
|
||||
INativeOrdersFeature(address(this))._fillLimitOrder
|
||||
(
|
||||
orders[i],
|
||||
signatures[i],
|
||||
takerTokenFillAmounts[i],
|
||||
msg.sender,
|
||||
msg.sender
|
||||
)
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
// Update amounts filled.
|
||||
(takerTokenFilledAmounts[i], makerTokenFilledAmounts[i]) =
|
||||
(takerTokenFilledAmount, makerTokenFilledAmount);
|
||||
ethProtocolFeePaid = ethProtocolFeePaid.safeAdd(protocolFee);
|
||||
} catch {}
|
||||
|
||||
if (
|
||||
revertIfIncomplete &&
|
||||
takerTokenFilledAmounts[i] < takerTokenFillAmounts[i]
|
||||
) {
|
||||
bytes32 orderHash = _getEIP712Hash(
|
||||
LibNativeOrder.getLimitOrderStructHash(orders[i])
|
||||
);
|
||||
// Did not fill the amount requested.
|
||||
LibNativeOrdersRichErrors.BatchFillIncompleteError(
|
||||
orderHash,
|
||||
takerTokenFilledAmounts[i],
|
||||
takerTokenFillAmounts[i]
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
LibNativeOrder.refundExcessProtocolFeeToSender(ethProtocolFeePaid);
|
||||
}
|
||||
|
||||
/// @dev Fills multiple RFQ orders.
|
||||
/// @param orders Array of RFQ orders.
|
||||
/// @param signatures Array of signatures corresponding to each order.
|
||||
/// @param takerTokenFillAmounts Array of desired amounts to fill each order.
|
||||
/// @param revertIfIncomplete If true, reverts if this function fails to
|
||||
/// fill the full fill amount for any individual order.
|
||||
/// @return takerTokenFilledAmounts Array of amounts filled, in taker token.
|
||||
/// @return makerTokenFilledAmounts Array of amounts filled, in maker token.
|
||||
function batchFillRfqOrders(
|
||||
LibNativeOrder.RfqOrder[] calldata orders,
|
||||
LibSignature.Signature[] calldata signatures,
|
||||
uint128[] calldata takerTokenFillAmounts,
|
||||
bool revertIfIncomplete
|
||||
)
|
||||
external
|
||||
override
|
||||
returns (
|
||||
uint128[] memory takerTokenFilledAmounts,
|
||||
uint128[] memory makerTokenFilledAmounts
|
||||
)
|
||||
{
|
||||
require(
|
||||
orders.length == signatures.length && orders.length == takerTokenFillAmounts.length,
|
||||
'BatchFillNativeOrdersFeature::batchFillRfqOrders/MISMATCHED_ARRAY_LENGTHS'
|
||||
);
|
||||
takerTokenFilledAmounts = new uint128[](orders.length);
|
||||
makerTokenFilledAmounts = new uint128[](orders.length);
|
||||
for (uint256 i = 0; i != orders.length; i++) {
|
||||
try
|
||||
INativeOrdersFeature(address(this))._fillRfqOrder
|
||||
(
|
||||
orders[i],
|
||||
signatures[i],
|
||||
takerTokenFillAmounts[i],
|
||||
msg.sender
|
||||
)
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
// Update amounts filled.
|
||||
(takerTokenFilledAmounts[i], makerTokenFilledAmounts[i]) =
|
||||
(takerTokenFilledAmount, makerTokenFilledAmount);
|
||||
} catch {}
|
||||
|
||||
if (
|
||||
revertIfIncomplete &&
|
||||
takerTokenFilledAmounts[i] < takerTokenFillAmounts[i]
|
||||
) {
|
||||
// Did not fill the amount requested.
|
||||
bytes32 orderHash = _getEIP712Hash(
|
||||
LibNativeOrder.getRfqOrderStructHash(orders[i])
|
||||
);
|
||||
LibNativeOrdersRichErrors.BatchFillIncompleteError(
|
||||
orderHash,
|
||||
takerTokenFilledAmounts[i],
|
||||
takerTokenFillAmounts[i]
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -23,7 +23,7 @@ pragma experimental ABIEncoderV2;
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "../migrations/LibBootstrap.sol";
|
||||
import "../storage/LibProxyStorage.sol";
|
||||
import "./IBootstrapFeature.sol";
|
||||
import "./interfaces/IBootstrapFeature.sol";
|
||||
|
||||
|
||||
/// @dev Detachable `bootstrap()` feature.
|
||||
|
@@ -23,14 +23,16 @@ pragma experimental ABIEncoderV2;
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../errors/LibLiquidityProviderRichErrors.sol";
|
||||
import "../external/ILiquidityProviderSandbox.sol";
|
||||
import "../external/LiquidityProviderSandbox.sol";
|
||||
import "../fixins/FixinCommon.sol";
|
||||
import "../fixins/FixinTokenSpender.sol";
|
||||
import "../migrations/LibMigrate.sol";
|
||||
import "./IFeature.sol";
|
||||
import "./ILiquidityProviderFeature.sol";
|
||||
import "../transformers/LibERC20Transformer.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/ILiquidityProviderFeature.sol";
|
||||
|
||||
|
||||
contract LiquidityProviderFeature is
|
||||
@@ -45,23 +47,11 @@ contract LiquidityProviderFeature is
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "LiquidityProviderFeature";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 2);
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 3);
|
||||
|
||||
/// @dev ETH pseudo-token address.
|
||||
address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||
/// @dev The sandbox contract address.
|
||||
ILiquidityProviderSandbox public immutable sandbox;
|
||||
|
||||
/// @dev Event for data pipeline.
|
||||
event LiquidityProviderSwap(
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
uint256 inputTokenAmount,
|
||||
uint256 outputTokenAmount,
|
||||
address provider,
|
||||
address recipient
|
||||
);
|
||||
|
||||
constructor(LiquidityProviderSandbox sandbox_, bytes32 greedyTokensBloomFilter)
|
||||
public
|
||||
FixinCommon()
|
||||
@@ -95,9 +85,9 @@ contract LiquidityProviderFeature is
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function sellToLiquidityProvider(
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
address payable provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
ILiquidityProvider provider,
|
||||
address recipient,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount,
|
||||
@@ -114,21 +104,21 @@ contract LiquidityProviderFeature is
|
||||
|
||||
// Forward all attached ETH to the provider.
|
||||
if (msg.value > 0) {
|
||||
provider.transfer(msg.value);
|
||||
payable(address(provider)).transfer(msg.value);
|
||||
}
|
||||
|
||||
if (inputToken != ETH_TOKEN_ADDRESS) {
|
||||
if (!LibERC20Transformer.isTokenETH(inputToken)) {
|
||||
// Transfer input ERC20 tokens to the provider.
|
||||
_transferERC20Tokens(
|
||||
IERC20TokenV06(inputToken),
|
||||
inputToken,
|
||||
msg.sender,
|
||||
provider,
|
||||
address(provider),
|
||||
sellAmount
|
||||
);
|
||||
}
|
||||
|
||||
if (inputToken == ETH_TOKEN_ADDRESS) {
|
||||
uint256 balanceBefore = IERC20TokenV06(outputToken).balanceOf(recipient);
|
||||
if (LibERC20Transformer.isTokenETH(inputToken)) {
|
||||
uint256 balanceBefore = outputToken.balanceOf(recipient);
|
||||
sandbox.executeSellEthForToken(
|
||||
provider,
|
||||
outputToken,
|
||||
@@ -137,7 +127,7 @@ contract LiquidityProviderFeature is
|
||||
auxiliaryData
|
||||
);
|
||||
boughtAmount = IERC20TokenV06(outputToken).balanceOf(recipient).safeSub(balanceBefore);
|
||||
} else if (outputToken == ETH_TOKEN_ADDRESS) {
|
||||
} else if (LibERC20Transformer.isTokenETH(outputToken)) {
|
||||
uint256 balanceBefore = recipient.balance;
|
||||
sandbox.executeSellTokenForEth(
|
||||
provider,
|
||||
@@ -148,7 +138,7 @@ contract LiquidityProviderFeature is
|
||||
);
|
||||
boughtAmount = recipient.balance.safeSub(balanceBefore);
|
||||
} else {
|
||||
uint256 balanceBefore = IERC20TokenV06(outputToken).balanceOf(recipient);
|
||||
uint256 balanceBefore = outputToken.balanceOf(recipient);
|
||||
sandbox.executeSellTokenForToken(
|
||||
provider,
|
||||
inputToken,
|
||||
@@ -157,14 +147,14 @@ contract LiquidityProviderFeature is
|
||||
minBuyAmount,
|
||||
auxiliaryData
|
||||
);
|
||||
boughtAmount = IERC20TokenV06(outputToken).balanceOf(recipient).safeSub(balanceBefore);
|
||||
boughtAmount = outputToken.balanceOf(recipient).safeSub(balanceBefore);
|
||||
}
|
||||
|
||||
if (boughtAmount < minBuyAmount) {
|
||||
LibLiquidityProviderRichErrors.LiquidityProviderIncompleteSellError(
|
||||
provider,
|
||||
outputToken,
|
||||
inputToken,
|
||||
address(provider),
|
||||
address(outputToken),
|
||||
address(inputToken),
|
||||
sellAmount,
|
||||
boughtAmount,
|
||||
minBuyAmount
|
||||
|
@@ -30,11 +30,11 @@ import "../fixins/FixinTokenSpender.sol";
|
||||
import "../fixins/FixinEIP712.sol";
|
||||
import "../migrations/LibMigrate.sol";
|
||||
import "../storage/LibMetaTransactionsStorage.sol";
|
||||
import "./IMetaTransactionsFeature.sol";
|
||||
import "./ITransformERC20Feature.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/IMetaTransactionsFeature.sol";
|
||||
import "./interfaces/INativeOrdersFeature.sol";
|
||||
import "./interfaces/ITransformERC20Feature.sol";
|
||||
import "./libs/LibSignature.sol";
|
||||
import "./IFeature.sol";
|
||||
import "./INativeOrdersFeature.sol";
|
||||
|
||||
/// @dev MetaTransactions feature.
|
||||
contract MetaTransactionsFeature is
|
||||
|
805
contracts/zero-ex/contracts/src/features/MultiplexFeature.sol
Normal file
805
contracts/zero-ex/contracts/src/features/MultiplexFeature.sol
Normal file
@@ -0,0 +1,805 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "../external/ILiquidityProviderSandbox.sol";
|
||||
import "../fixins/FixinCommon.sol";
|
||||
import "../fixins/FixinEIP712.sol";
|
||||
import "../fixins/FixinTokenSpender.sol";
|
||||
import "../migrations/LibMigrate.sol";
|
||||
import "../transformers/LibERC20Transformer.sol";
|
||||
import "../vendor/ILiquidityProvider.sol";
|
||||
import "../vendor/IUniswapV2Pair.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/IMultiplexFeature.sol";
|
||||
import "./interfaces/INativeOrdersFeature.sol";
|
||||
import "./interfaces/ITransformERC20Feature.sol";
|
||||
import "./libs/LibNativeOrder.sol";
|
||||
|
||||
|
||||
/// @dev This feature enables efficient batch and multi-hop trades
|
||||
/// using different liquidity sources.
|
||||
contract MultiplexFeature is
|
||||
IFeature,
|
||||
IMultiplexFeature,
|
||||
FixinCommon,
|
||||
FixinEIP712,
|
||||
FixinTokenSpender
|
||||
{
|
||||
using LibERC20Transformer for IERC20TokenV06;
|
||||
using LibSafeMathV06 for uint128;
|
||||
using LibSafeMathV06 for uint256;
|
||||
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "MultiplexFeature";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||
|
||||
/// @dev The WETH token contract.
|
||||
IEtherTokenV06 private immutable weth;
|
||||
/// @dev The sandbox contract address.
|
||||
ILiquidityProviderSandbox public immutable sandbox;
|
||||
// address of the UniswapV2Factory contract.
|
||||
address private constant UNISWAP_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
|
||||
// address of the (Sushiswap) UniswapV2Factory contract.
|
||||
address private constant SUSHISWAP_FACTORY = 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac;
|
||||
// Init code hash of the UniswapV2Pair contract.
|
||||
uint256 private constant UNISWAP_PAIR_INIT_CODE_HASH = 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f;
|
||||
// Init code hash of the (Sushiswap) UniswapV2Pair contract.
|
||||
uint256 private constant SUSHISWAP_PAIR_INIT_CODE_HASH = 0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303;
|
||||
|
||||
constructor(
|
||||
address zeroExAddress,
|
||||
IEtherTokenV06 weth_,
|
||||
ILiquidityProviderSandbox sandbox_,
|
||||
bytes32 greedyTokensBloomFilter
|
||||
)
|
||||
public
|
||||
FixinEIP712(zeroExAddress)
|
||||
FixinTokenSpender(greedyTokensBloomFilter)
|
||||
{
|
||||
weth = weth_;
|
||||
sandbox = sandbox_;
|
||||
}
|
||||
|
||||
/// @dev Initialize and register this feature.
|
||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||
/// @return success `LibMigrate.SUCCESS` on success.
|
||||
function migrate()
|
||||
external
|
||||
returns (bytes4 success)
|
||||
{
|
||||
_registerFeatureFunction(this.batchFill.selector);
|
||||
_registerFeatureFunction(this.multiHopFill.selector);
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
}
|
||||
|
||||
/// @dev Executes a batch of fills selling `fillData.inputToken`
|
||||
/// for `fillData.outputToken` in sequence. Refer to the
|
||||
/// internal variant `_batchFill` for the allowed nested
|
||||
/// operations.
|
||||
/// @param fillData Encodes the input/output tokens, the sell
|
||||
/// amount, and the nested operations for this batch fill.
|
||||
/// @param minBuyAmount The minimum amount of `fillData.outputToken`
|
||||
/// to buy. Reverts if this amount is not met.
|
||||
/// @return outputTokenAmount The amount of the output token bought.
|
||||
function batchFill(
|
||||
BatchFillData memory fillData,
|
||||
uint256 minBuyAmount
|
||||
)
|
||||
public
|
||||
payable
|
||||
override
|
||||
returns (uint256 outputTokenAmount)
|
||||
{
|
||||
// Cache the sender's balance of the output token.
|
||||
outputTokenAmount = fillData.outputToken.getTokenBalanceOf(msg.sender);
|
||||
// Cache the contract's ETH balance prior to this call.
|
||||
uint256 ethBalanceBefore = address(this).balance.safeSub(msg.value);
|
||||
|
||||
// Perform the batch fill.
|
||||
_batchFill(fillData);
|
||||
|
||||
// The `outputTokenAmount` returned by `_batchFill` may not
|
||||
// be fully accurate (e.g. due to some janky token).
|
||||
outputTokenAmount = fillData.outputToken.getTokenBalanceOf(msg.sender)
|
||||
.safeSub(outputTokenAmount);
|
||||
require(
|
||||
outputTokenAmount >= minBuyAmount,
|
||||
"MultiplexFeature::batchFill/UNDERBOUGHT"
|
||||
);
|
||||
|
||||
uint256 ethBalanceAfter = address(this).balance;
|
||||
require(
|
||||
ethBalanceAfter >= ethBalanceBefore,
|
||||
"MultiplexFeature::batchFill/OVERSPENT_ETH"
|
||||
);
|
||||
// Refund ETH
|
||||
if (ethBalanceAfter > ethBalanceBefore) {
|
||||
_transferEth(msg.sender, ethBalanceAfter - ethBalanceBefore);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Executes a sequence of fills "hopping" through the
|
||||
/// path of tokens given by `fillData.tokens`. Refer to the
|
||||
/// internal variant `_multiHopFill` for the allowed nested
|
||||
/// operations.
|
||||
/// @param fillData Encodes the path of tokens, the sell amount,
|
||||
/// and the nested operations for this multi-hop fill.
|
||||
/// @param minBuyAmount The minimum amount of the output token
|
||||
/// to buy. Reverts if this amount is not met.
|
||||
/// @return outputTokenAmount The amount of the output token bought.
|
||||
function multiHopFill(
|
||||
MultiHopFillData memory fillData,
|
||||
uint256 minBuyAmount
|
||||
)
|
||||
public
|
||||
payable
|
||||
override
|
||||
returns (uint256 outputTokenAmount)
|
||||
{
|
||||
IERC20TokenV06 outputToken = IERC20TokenV06(fillData.tokens[fillData.tokens.length - 1]);
|
||||
// Cache the sender's balance of the output token.
|
||||
outputTokenAmount = outputToken.getTokenBalanceOf(msg.sender);
|
||||
// Cache the contract's ETH balance prior to this call.
|
||||
uint256 ethBalanceBefore = address(this).balance.safeSub(msg.value);
|
||||
|
||||
// Perform the multi-hop fill. Pass in `msg.value` as the maximum
|
||||
// allowable amount of ETH for the wrapped calls to consume.
|
||||
_multiHopFill(fillData, msg.value);
|
||||
|
||||
// The `outputTokenAmount` returned by `_multiHopFill` may not
|
||||
// be fully accurate (e.g. due to some janky token).
|
||||
outputTokenAmount = outputToken.getTokenBalanceOf(msg.sender)
|
||||
.safeSub(outputTokenAmount);
|
||||
require(
|
||||
outputTokenAmount >= minBuyAmount,
|
||||
"MultiplexFeature::multiHopFill/UNDERBOUGHT"
|
||||
);
|
||||
|
||||
uint256 ethBalanceAfter = address(this).balance;
|
||||
require(
|
||||
ethBalanceAfter >= ethBalanceBefore,
|
||||
"MultiplexFeature::multiHopFill/OVERSPENT_ETH"
|
||||
);
|
||||
// Refund ETH
|
||||
if (ethBalanceAfter > ethBalanceBefore) {
|
||||
_transferEth(msg.sender, ethBalanceAfter - ethBalanceBefore);
|
||||
}
|
||||
}
|
||||
|
||||
// Similar to FQT. If `fillData.sellAmount` is set to `type(uint256).max`,
|
||||
// this is effectively a batch fill. Otherwise it can be set to perform a
|
||||
// market sell of some amount. Note that the `outputTokenAmount` returned
|
||||
// by this function could theoretically be inaccurate if `msg.sender` has
|
||||
// set a token allowance on an external contract that gets called during
|
||||
// the execution of this function.
|
||||
function _batchFill(BatchFillData memory fillData)
|
||||
internal
|
||||
returns (uint256 outputTokenAmount, uint256 remainingEth)
|
||||
{
|
||||
// Track the remaining ETH allocated to this call.
|
||||
remainingEth = msg.value;
|
||||
// Track the amount of input token sold.
|
||||
uint256 soldAmount;
|
||||
for (uint256 i = 0; i != fillData.calls.length; i++) {
|
||||
// Check if we've hit our target.
|
||||
if (soldAmount >= fillData.sellAmount) { break; }
|
||||
WrappedBatchCall memory wrappedCall = fillData.calls[i];
|
||||
// Compute the fill amount.
|
||||
uint256 inputTokenAmount = LibSafeMathV06.min256(
|
||||
wrappedCall.sellAmount,
|
||||
fillData.sellAmount.safeSub(soldAmount)
|
||||
);
|
||||
if (wrappedCall.selector == INativeOrdersFeature._fillRfqOrder.selector) {
|
||||
// Decode the RFQ order and signature.
|
||||
(
|
||||
LibNativeOrder.RfqOrder memory order,
|
||||
LibSignature.Signature memory signature
|
||||
) = abi.decode(
|
||||
wrappedCall.data,
|
||||
(LibNativeOrder.RfqOrder, LibSignature.Signature)
|
||||
);
|
||||
if (order.expiry <= uint64(block.timestamp)) {
|
||||
bytes32 orderHash = _getEIP712Hash(
|
||||
LibNativeOrder.getRfqOrderStructHash(order)
|
||||
);
|
||||
emit ExpiredRfqOrder(
|
||||
orderHash,
|
||||
order.maker,
|
||||
order.expiry
|
||||
);
|
||||
continue;
|
||||
}
|
||||
require(
|
||||
order.takerToken == fillData.inputToken &&
|
||||
order.makerToken == fillData.outputToken,
|
||||
"MultiplexFeature::_batchFill/RFQ_ORDER_INVALID_TOKENS"
|
||||
);
|
||||
// Try filling the RFQ order. Swallows reverts.
|
||||
try
|
||||
INativeOrdersFeature(address(this))._fillRfqOrder
|
||||
(
|
||||
order,
|
||||
signature,
|
||||
inputTokenAmount.safeDowncastToUint128(),
|
||||
msg.sender
|
||||
)
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
// Increment the sold and bought amounts.
|
||||
soldAmount = soldAmount.safeAdd(takerTokenFilledAmount);
|
||||
outputTokenAmount = outputTokenAmount.safeAdd(makerTokenFilledAmount);
|
||||
} catch {}
|
||||
} else if (wrappedCall.selector == this._sellToUniswap.selector) {
|
||||
(address[] memory tokens, bool isSushi) = abi.decode(
|
||||
wrappedCall.data,
|
||||
(address[], bool)
|
||||
);
|
||||
require(
|
||||
tokens.length >= 2 &&
|
||||
tokens[0] == address(fillData.inputToken) &&
|
||||
tokens[tokens.length - 1] == address(fillData.outputToken),
|
||||
"MultiplexFeature::_batchFill/UNISWAP_INVALID_TOKENS"
|
||||
);
|
||||
// Perform the Uniswap/Sushiswap trade.
|
||||
uint256 outputTokenAmount_ = _sellToUniswap(
|
||||
tokens,
|
||||
inputTokenAmount,
|
||||
isSushi,
|
||||
address(0),
|
||||
msg.sender
|
||||
);
|
||||
// Increment the sold and bought amounts.
|
||||
soldAmount = soldAmount.safeAdd(inputTokenAmount);
|
||||
outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_);
|
||||
} else if (wrappedCall.selector == this._sellToLiquidityProvider.selector) {
|
||||
(address provider, bytes memory auxiliaryData) = abi.decode(
|
||||
wrappedCall.data,
|
||||
(address, bytes)
|
||||
);
|
||||
if (fillData.inputToken.isTokenETH()) {
|
||||
inputTokenAmount = LibSafeMathV06.min256(
|
||||
inputTokenAmount,
|
||||
remainingEth
|
||||
);
|
||||
// Transfer the input ETH to the provider.
|
||||
_transferEth(payable(provider), inputTokenAmount);
|
||||
// Count that ETH as spent.
|
||||
remainingEth -= inputTokenAmount;
|
||||
} else {
|
||||
// Transfer input ERC20 tokens to the provider.
|
||||
_transferERC20Tokens(
|
||||
fillData.inputToken,
|
||||
msg.sender,
|
||||
provider,
|
||||
inputTokenAmount
|
||||
);
|
||||
}
|
||||
// Perform the PLP trade.
|
||||
uint256 outputTokenAmount_ = _sellToLiquidityProvider(
|
||||
fillData.inputToken,
|
||||
fillData.outputToken,
|
||||
inputTokenAmount,
|
||||
ILiquidityProvider(provider),
|
||||
msg.sender,
|
||||
auxiliaryData
|
||||
);
|
||||
// Increment the sold and bought amounts.
|
||||
soldAmount = soldAmount.safeAdd(inputTokenAmount);
|
||||
outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_);
|
||||
} else if (wrappedCall.selector == ITransformERC20Feature._transformERC20.selector) {
|
||||
ITransformERC20Feature.TransformERC20Args memory args;
|
||||
args.taker = msg.sender;
|
||||
args.inputToken = fillData.inputToken;
|
||||
args.outputToken = fillData.outputToken;
|
||||
args.inputTokenAmount = inputTokenAmount;
|
||||
args.minOutputTokenAmount = 0;
|
||||
uint256 ethValue;
|
||||
(args.transformations, ethValue) = abi.decode(
|
||||
wrappedCall.data,
|
||||
(ITransformERC20Feature.Transformation[], uint256)
|
||||
);
|
||||
// Do not spend more than the remaining ETH.
|
||||
ethValue = LibSafeMathV06.min256(
|
||||
ethValue,
|
||||
remainingEth
|
||||
);
|
||||
if (ethValue > 0) {
|
||||
require(
|
||||
args.inputToken.isTokenETH(),
|
||||
"MultiplexFeature::_batchFill/ETH_TRANSFORM_ONLY"
|
||||
);
|
||||
}
|
||||
try ITransformERC20Feature(address(this))._transformERC20
|
||||
{value: ethValue}
|
||||
(args)
|
||||
returns (uint256 outputTokenAmount_)
|
||||
{
|
||||
remainingEth -= ethValue;
|
||||
soldAmount = soldAmount.safeAdd(inputTokenAmount);
|
||||
outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_);
|
||||
} catch {}
|
||||
} else if (wrappedCall.selector == this._multiHopFill.selector) {
|
||||
MultiHopFillData memory multiHopFillData;
|
||||
uint256 ethValue;
|
||||
(
|
||||
multiHopFillData.tokens,
|
||||
multiHopFillData.calls,
|
||||
ethValue
|
||||
) = abi.decode(
|
||||
wrappedCall.data,
|
||||
(address[], WrappedMultiHopCall[], uint256)
|
||||
);
|
||||
multiHopFillData.sellAmount = inputTokenAmount;
|
||||
// Do not spend more than the remaining ETH.
|
||||
ethValue = LibSafeMathV06.min256(
|
||||
ethValue,
|
||||
remainingEth
|
||||
);
|
||||
// Subtract the ethValue allocated to the nested multi-hop fill.
|
||||
remainingEth -= ethValue;
|
||||
(uint256 outputTokenAmount_, uint256 leftoverEth) =
|
||||
_multiHopFill(multiHopFillData, ethValue);
|
||||
// Increment the sold and bought amounts.
|
||||
soldAmount = soldAmount.safeAdd(inputTokenAmount);
|
||||
outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_);
|
||||
// Add back any ETH that wasn't used by the nested multi-hop fill.
|
||||
remainingEth += leftoverEth;
|
||||
} else {
|
||||
revert("MultiplexFeature::_batchFill/UNRECOGNIZED_SELECTOR");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal variant of `multiHopFill`. This function can be nested within
|
||||
// a `_batchFill`.
|
||||
// This function executes a sequence of fills "hopping" through the
|
||||
// path of tokens given by `fillData.tokens`. The nested operations that
|
||||
// can be used as "hops" are:
|
||||
// - WETH.deposit (wraps ETH)
|
||||
// - WETH.withdraw (unwraps WETH)
|
||||
// - _sellToUniswap (executes a Uniswap/Sushiswap swap)
|
||||
// - _sellToLiquidityProvider (executes a PLP swap)
|
||||
// - _transformERC20 (executes arbitrary ERC20 Transformations)
|
||||
// This function optimizes the number of ERC20 transfers performed
|
||||
// by having each hop transfer its output tokens directly to the
|
||||
// target address of the next hop. Note that the `outputTokenAmount` returned
|
||||
// by this function could theoretically be inaccurate if `msg.sender` has
|
||||
// set a token allowance on an external contract that gets called during
|
||||
// the execution of this function.
|
||||
function _multiHopFill(MultiHopFillData memory fillData, uint256 totalEth)
|
||||
public
|
||||
returns (uint256 outputTokenAmount, uint256 remainingEth)
|
||||
{
|
||||
// There should be one call/hop between every two tokens
|
||||
// in the path.
|
||||
// tokens[0]––calls[0]––>tokens[1]––...––calls[n-1]––>tokens[n]
|
||||
require(
|
||||
fillData.tokens.length == fillData.calls.length + 1,
|
||||
"MultiplexFeature::_multiHopFill/MISMATCHED_ARRAY_LENGTHS"
|
||||
);
|
||||
// Track the remaining ETH allocated to this call.
|
||||
remainingEth = totalEth;
|
||||
// This variable is used as the input and output amounts of
|
||||
// each hop. After the final hop, this will contain the output
|
||||
// amount of the multi-hop fill.
|
||||
outputTokenAmount = fillData.sellAmount;
|
||||
// This variable is used to cache the address to target in the
|
||||
// next hop. See `_computeHopRecipient` for details.
|
||||
address nextTarget;
|
||||
for (uint256 i = 0; i != fillData.calls.length; i++) {
|
||||
WrappedMultiHopCall memory wrappedCall = fillData.calls[i];
|
||||
if (wrappedCall.selector == this._sellToUniswap.selector) {
|
||||
// If the next hop supports a "transfer then execute" pattern,
|
||||
// the recipient will not be `msg.sender`. See `_computeHopRecipient`
|
||||
// for details.
|
||||
address recipient = _computeHopRecipient(fillData.calls, i);
|
||||
(address[] memory tokens, bool isSushi) = abi.decode(
|
||||
wrappedCall.data,
|
||||
(address[], bool)
|
||||
);
|
||||
// Perform the Uniswap/Sushiswap trade.
|
||||
outputTokenAmount = _sellToUniswap(
|
||||
tokens,
|
||||
outputTokenAmount,
|
||||
isSushi,
|
||||
nextTarget,
|
||||
recipient
|
||||
);
|
||||
// If the recipient was not `msg.sender`, it must be the target
|
||||
// contract for the next hop.
|
||||
nextTarget = recipient == msg.sender ? address(0) : recipient;
|
||||
} else if (wrappedCall.selector == this._sellToLiquidityProvider.selector) {
|
||||
// If the next hop supports a "transfer then execute" pattern,
|
||||
// the recipient will not be `msg.sender`. See `_computeHopRecipient`
|
||||
// for details.
|
||||
address recipient = _computeHopRecipient(fillData.calls, i);
|
||||
// If `nextTarget` was not set in the previous hop, then we
|
||||
// need to send in the input ETH/tokens to the liquidity provider
|
||||
// contract before executing the trade.
|
||||
if (nextTarget == address(0)) {
|
||||
(address provider, bytes memory auxiliaryData) = abi.decode(
|
||||
wrappedCall.data,
|
||||
(address, bytes)
|
||||
);
|
||||
// Transfer input ETH or ERC20 tokens to the liquidity
|
||||
// provider contract.
|
||||
if (IERC20TokenV06(fillData.tokens[i]).isTokenETH()) {
|
||||
outputTokenAmount = LibSafeMathV06.min256(
|
||||
outputTokenAmount,
|
||||
remainingEth
|
||||
);
|
||||
_transferEth(payable(provider), outputTokenAmount);
|
||||
remainingEth -= outputTokenAmount;
|
||||
} else {
|
||||
_transferERC20Tokens(
|
||||
IERC20TokenV06(fillData.tokens[i]),
|
||||
msg.sender,
|
||||
provider,
|
||||
outputTokenAmount
|
||||
);
|
||||
}
|
||||
outputTokenAmount = _sellToLiquidityProvider(
|
||||
IERC20TokenV06(fillData.tokens[i]),
|
||||
IERC20TokenV06(fillData.tokens[i + 1]),
|
||||
outputTokenAmount,
|
||||
ILiquidityProvider(provider),
|
||||
recipient,
|
||||
auxiliaryData
|
||||
);
|
||||
} else {
|
||||
(, bytes memory auxiliaryData) = abi.decode(
|
||||
wrappedCall.data,
|
||||
(address, bytes)
|
||||
);
|
||||
// Tokens and ETH have already been transferred to
|
||||
// the liquidity provider contract in the previous hop.
|
||||
outputTokenAmount = _sellToLiquidityProvider(
|
||||
IERC20TokenV06(fillData.tokens[i]),
|
||||
IERC20TokenV06(fillData.tokens[i + 1]),
|
||||
outputTokenAmount,
|
||||
ILiquidityProvider(nextTarget),
|
||||
recipient,
|
||||
auxiliaryData
|
||||
);
|
||||
}
|
||||
// If the recipient was not `msg.sender`, it must be the target
|
||||
// contract for the next hop.
|
||||
nextTarget = recipient == msg.sender ? address(0) : recipient;
|
||||
} else if (wrappedCall.selector == ITransformERC20Feature._transformERC20.selector) {
|
||||
ITransformERC20Feature.TransformERC20Args memory args;
|
||||
args.inputToken = IERC20TokenV06(fillData.tokens[i]);
|
||||
args.outputToken = IERC20TokenV06(fillData.tokens[i + 1]);
|
||||
args.minOutputTokenAmount = 0;
|
||||
args.taker = payable(_computeHopRecipient(fillData.calls, i));
|
||||
if (nextTarget != address(0)) {
|
||||
// If `nextTarget` was set in the previous hop, then the input
|
||||
// token was already sent to the FlashWallet. Setting
|
||||
// `inputTokenAmount` to 0 indicates that no tokens need to
|
||||
// be pulled into the FlashWallet before executing the
|
||||
// transformations.
|
||||
args.inputTokenAmount = 0;
|
||||
} else if (
|
||||
args.taker != msg.sender &&
|
||||
!args.inputToken.isTokenETH()
|
||||
) {
|
||||
address flashWallet = address(
|
||||
ITransformERC20Feature(address(this)).getTransformWallet()
|
||||
);
|
||||
// The input token has _not_ already been sent to the
|
||||
// FlashWallet. We also want PayTakerTransformer to
|
||||
// send the output token to some address other than
|
||||
// msg.sender, so we must transfer the input token
|
||||
// to the FlashWallet here.
|
||||
_transferERC20Tokens(
|
||||
args.inputToken,
|
||||
msg.sender,
|
||||
flashWallet,
|
||||
outputTokenAmount
|
||||
);
|
||||
args.inputTokenAmount = 0;
|
||||
} else {
|
||||
// Otherwise, either:
|
||||
// (1) args.taker == msg.sender, in which case
|
||||
// `_transformERC20` will pull the input token
|
||||
// into the FlashWallet, or
|
||||
// (2) args.inputToken == ETH_TOKEN_ADDRESS, in which
|
||||
// case ETH is attached to the call and no token
|
||||
// transfer occurs.
|
||||
args.inputTokenAmount = outputTokenAmount;
|
||||
}
|
||||
uint256 ethValue;
|
||||
(args.transformations, ethValue) = abi.decode(
|
||||
wrappedCall.data,
|
||||
(ITransformERC20Feature.Transformation[], uint256)
|
||||
);
|
||||
// Do not spend more than the remaining ETH.
|
||||
ethValue = LibSafeMathV06.min256(ethValue, remainingEth);
|
||||
if (ethValue > 0) {
|
||||
require(
|
||||
args.inputToken.isTokenETH(),
|
||||
"MultiplexFeature::_multiHopFill/ETH_TRANSFORM_ONLY"
|
||||
);
|
||||
}
|
||||
// Call `_transformERC20`.
|
||||
outputTokenAmount = ITransformERC20Feature(address(this))
|
||||
._transformERC20{value: ethValue}(args);
|
||||
// Decrement the remaining ETH.
|
||||
remainingEth -= ethValue;
|
||||
// If the recipient was not `msg.sender`, it must be the target
|
||||
// contract for the next hop.
|
||||
nextTarget = args.taker == msg.sender ? address(0) : args.taker;
|
||||
} else if (wrappedCall.selector == IEtherTokenV06.deposit.selector) {
|
||||
require(
|
||||
i == 0,
|
||||
"MultiplexFeature::_multiHopFill/DEPOSIT_FIRST_HOP_ONLY"
|
||||
);
|
||||
uint256 ethValue = LibSafeMathV06.min256(outputTokenAmount, remainingEth);
|
||||
// Wrap ETH.
|
||||
weth.deposit{value: ethValue}();
|
||||
nextTarget = _computeHopRecipient(fillData.calls, i);
|
||||
weth.transfer(nextTarget, ethValue);
|
||||
remainingEth -= ethValue;
|
||||
} else if (wrappedCall.selector == IEtherTokenV06.withdraw.selector) {
|
||||
require(
|
||||
i == fillData.calls.length - 1,
|
||||
"MultiplexFeature::_multiHopFill/WITHDRAW_LAST_HOP_ONLY"
|
||||
);
|
||||
// Unwrap WETH and send to `msg.sender`.
|
||||
weth.withdraw(outputTokenAmount);
|
||||
_transferEth(msg.sender, outputTokenAmount);
|
||||
nextTarget = address(0);
|
||||
} else {
|
||||
revert("MultiplexFeature::_multiHopFill/UNRECOGNIZED_SELECTOR");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Similar to the UniswapFeature, but with a couple of differences:
|
||||
// - Does not perform the transfer in if `pairAddress` is given,
|
||||
// which indicates that the transfer in was already performed
|
||||
// in the previous hop of a multi-hop fill.
|
||||
// - Does not include a minBuyAmount check (which is performed in
|
||||
// either `batchFill` or `multiHopFill`).
|
||||
// - Takes a `recipient` address parameter, so the output of the
|
||||
// final `swap` call can be sent to an address other than `msg.sender`.
|
||||
function _sellToUniswap(
|
||||
address[] memory tokens,
|
||||
uint256 sellAmount,
|
||||
bool isSushi,
|
||||
address pairAddress,
|
||||
address recipient
|
||||
)
|
||||
public
|
||||
returns (uint256 outputTokenAmount)
|
||||
{
|
||||
require(tokens.length > 1, "MultiplexFeature::_sellToUniswap/InvalidTokensLength");
|
||||
|
||||
if (pairAddress == address(0)) {
|
||||
pairAddress = _computeUniswapPairAddress(tokens[0], tokens[1], isSushi);
|
||||
_transferERC20Tokens(
|
||||
IERC20TokenV06(tokens[0]),
|
||||
msg.sender,
|
||||
pairAddress,
|
||||
sellAmount
|
||||
);
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < tokens.length - 1; i++) {
|
||||
(address inputToken, address outputToken) = (tokens[i], tokens[i + 1]);
|
||||
outputTokenAmount = _computeUniswapOutputAmount(
|
||||
pairAddress,
|
||||
inputToken,
|
||||
outputToken,
|
||||
sellAmount
|
||||
);
|
||||
(uint256 amount0Out, uint256 amount1Out) = inputToken < outputToken
|
||||
? (uint256(0), outputTokenAmount)
|
||||
: (outputTokenAmount, uint256(0));
|
||||
address to = i < tokens.length - 2
|
||||
? _computeUniswapPairAddress(outputToken, tokens[i + 2], isSushi)
|
||||
: recipient;
|
||||
IUniswapV2Pair(pairAddress).swap(
|
||||
amount0Out,
|
||||
amount1Out,
|
||||
to,
|
||||
new bytes(0)
|
||||
);
|
||||
pairAddress = to;
|
||||
sellAmount = outputTokenAmount;
|
||||
}
|
||||
}
|
||||
|
||||
// Same as the LiquidityProviderFeature, but without the transfer in
|
||||
// (which is potentially done in the previous hop of a multi-hop fill)
|
||||
// and without the minBuyAmount check (which is performed at the top, i.e.
|
||||
// in either `batchFill` or `multiHopFill`).
|
||||
function _sellToLiquidityProvider(
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
uint256 inputTokenAmount,
|
||||
ILiquidityProvider provider,
|
||||
address recipient,
|
||||
bytes memory auxiliaryData
|
||||
)
|
||||
public
|
||||
returns (uint256 outputTokenAmount)
|
||||
{
|
||||
uint256 balanceBefore = IERC20TokenV06(outputToken).getTokenBalanceOf(recipient);
|
||||
if (IERC20TokenV06(inputToken).isTokenETH()) {
|
||||
sandbox.executeSellEthForToken(
|
||||
provider,
|
||||
outputToken,
|
||||
recipient,
|
||||
0,
|
||||
auxiliaryData
|
||||
);
|
||||
} else if (IERC20TokenV06(outputToken).isTokenETH()) {
|
||||
sandbox.executeSellTokenForEth(
|
||||
provider,
|
||||
inputToken,
|
||||
recipient,
|
||||
0,
|
||||
auxiliaryData
|
||||
);
|
||||
} else {
|
||||
sandbox.executeSellTokenForToken(
|
||||
provider,
|
||||
inputToken,
|
||||
outputToken,
|
||||
recipient,
|
||||
0,
|
||||
auxiliaryData
|
||||
);
|
||||
}
|
||||
outputTokenAmount = IERC20TokenV06(outputToken).getTokenBalanceOf(recipient)
|
||||
.safeSub(balanceBefore);
|
||||
emit LiquidityProviderSwap(
|
||||
address(inputToken),
|
||||
address(outputToken),
|
||||
inputTokenAmount,
|
||||
outputTokenAmount,
|
||||
address(provider),
|
||||
recipient
|
||||
);
|
||||
return outputTokenAmount;
|
||||
}
|
||||
|
||||
function _transferEth(address payable recipient, uint256 amount)
|
||||
private
|
||||
{
|
||||
(bool success,) = recipient.call{value: amount}("");
|
||||
require(success, "MultiplexFeature::_transferEth/TRANSFER_FALIED");
|
||||
}
|
||||
|
||||
// Some liquidity sources (e.g. Uniswap, Sushiswap, and PLP) can be passed
|
||||
// a `recipient` parameter so the boguht tokens are transferred to the
|
||||
// `recipient` address rather than `msg.sender`.
|
||||
// Some liquidity sources (also Uniswap, Sushiswap, and PLP incidentally)
|
||||
// support a "transfer then execute" pattern, where the token being sold
|
||||
// can be transferred into the contract before calling a swap function to
|
||||
// execute the trade.
|
||||
// If the current hop in a multi-hop fill satisfies the first condition,
|
||||
// and the next hop satisfies the second condition, the tokens bought
|
||||
// in the current hop can be directly sent to the target contract of
|
||||
// the next hop to save a transfer.
|
||||
function _computeHopRecipient(
|
||||
WrappedMultiHopCall[] memory calls,
|
||||
uint256 i
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (address recipient)
|
||||
{
|
||||
recipient = msg.sender;
|
||||
if (i < calls.length - 1) {
|
||||
WrappedMultiHopCall memory nextCall = calls[i + 1];
|
||||
if (nextCall.selector == this._sellToUniswap.selector) {
|
||||
(address[] memory tokens, bool isSushi) = abi.decode(
|
||||
nextCall.data,
|
||||
(address[], bool)
|
||||
);
|
||||
recipient = _computeUniswapPairAddress(tokens[0], tokens[1], isSushi);
|
||||
} else if (nextCall.selector == this._sellToLiquidityProvider.selector) {
|
||||
(recipient,) = abi.decode(
|
||||
nextCall.data,
|
||||
(address, bytes)
|
||||
);
|
||||
} else if (nextCall.selector == IEtherTokenV06.withdraw.selector) {
|
||||
recipient = address(this);
|
||||
} else if (nextCall.selector == ITransformERC20Feature._transformERC20.selector) {
|
||||
recipient = address(
|
||||
ITransformERC20Feature(address(this)).getTransformWallet()
|
||||
);
|
||||
}
|
||||
}
|
||||
require(
|
||||
recipient != address(0),
|
||||
"MultiplexFeature::_computeHopRecipient/RECIPIENT_IS_NULL"
|
||||
);
|
||||
}
|
||||
|
||||
// Computes the the amount of output token that would be bought
|
||||
// from Uniswap/Sushiswap given the input amount.
|
||||
function _computeUniswapOutputAmount(
|
||||
address pairAddress,
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
uint256 inputAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 outputAmount)
|
||||
{
|
||||
require(
|
||||
inputAmount > 0,
|
||||
"MultiplexFeature::_computeUniswapOutputAmount/INSUFFICIENT_INPUT_AMOUNT"
|
||||
);
|
||||
(uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pairAddress).getReserves();
|
||||
require(
|
||||
reserve0 > 0 && reserve1 > 0,
|
||||
'MultiplexFeature::_computeUniswapOutputAmount/INSUFFICIENT_LIQUIDITY'
|
||||
);
|
||||
(uint256 inputReserve, uint256 outputReserve) = inputToken < outputToken
|
||||
? (reserve0, reserve1)
|
||||
: (reserve1, reserve0);
|
||||
uint256 inputAmountWithFee = inputAmount.safeMul(997);
|
||||
uint256 numerator = inputAmountWithFee.safeMul(outputReserve);
|
||||
uint256 denominator = inputReserve.safeMul(1000).safeAdd(inputAmountWithFee);
|
||||
return numerator / denominator;
|
||||
}
|
||||
|
||||
// Computes the Uniswap/Sushiswap pair contract address for the
|
||||
// given tokens.
|
||||
function _computeUniswapPairAddress(
|
||||
address tokenA,
|
||||
address tokenB,
|
||||
bool isSushi
|
||||
)
|
||||
private
|
||||
pure
|
||||
returns (address pairAddress)
|
||||
{
|
||||
(address token0, address token1) = tokenA < tokenB
|
||||
? (tokenA, tokenB)
|
||||
: (tokenB, tokenA);
|
||||
if (isSushi) {
|
||||
return address(uint256(keccak256(abi.encodePacked(
|
||||
hex'ff',
|
||||
SUSHISWAP_FACTORY,
|
||||
keccak256(abi.encodePacked(token0, token1)),
|
||||
SUSHISWAP_PAIR_INIT_CODE_HASH
|
||||
))));
|
||||
} else {
|
||||
return address(uint256(keccak256(abi.encodePacked(
|
||||
hex'ff',
|
||||
UNISWAP_FACTORY,
|
||||
keccak256(abi.encodePacked(token0, token1)),
|
||||
UNISWAP_PAIR_INIT_CODE_HASH
|
||||
))));
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -26,8 +26,8 @@ import "../errors/LibOwnableRichErrors.sol";
|
||||
import "../storage/LibOwnableStorage.sol";
|
||||
import "../migrations/LibBootstrap.sol";
|
||||
import "../migrations/LibMigrate.sol";
|
||||
import "./IFeature.sol";
|
||||
import "./IOwnableFeature.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/IOwnableFeature.sol";
|
||||
import "./SimpleFunctionRegistryFeature.sol";
|
||||
|
||||
|
||||
|
@@ -26,8 +26,8 @@ import "../storage/LibProxyStorage.sol";
|
||||
import "../storage/LibSimpleFunctionRegistryStorage.sol";
|
||||
import "../errors/LibSimpleFunctionRegistryRichErrors.sol";
|
||||
import "../migrations/LibBootstrap.sol";
|
||||
import "./IFeature.sol";
|
||||
import "./ISimpleFunctionRegistryFeature.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/ISimpleFunctionRegistryFeature.sol";
|
||||
|
||||
|
||||
/// @dev Basic registry management features.
|
||||
|
@@ -29,8 +29,8 @@ import "../fixins/FixinCommon.sol";
|
||||
import "../migrations/LibMigrate.sol";
|
||||
import "../external/IAllowanceTarget.sol";
|
||||
import "../storage/LibTokenSpenderStorage.sol";
|
||||
import "./ITokenSpenderFeature.sol";
|
||||
import "./IFeature.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/ITokenSpenderFeature.sol";
|
||||
|
||||
|
||||
/// @dev Feature that allows spending token allowances.
|
||||
|
@@ -33,8 +33,8 @@ import "../external/FlashWallet.sol";
|
||||
import "../storage/LibTransformERC20Storage.sol";
|
||||
import "../transformers/IERC20Transformer.sol";
|
||||
import "../transformers/LibERC20Transformer.sol";
|
||||
import "./ITransformERC20Feature.sol";
|
||||
import "./IFeature.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/ITransformERC20Feature.sol";
|
||||
|
||||
|
||||
/// @dev Feature to composably transform between ERC20 tokens.
|
||||
@@ -313,7 +313,7 @@ contract TransformERC20Feature is
|
||||
to.transfer(msg.value);
|
||||
}
|
||||
// Transfer input tokens.
|
||||
if (!LibERC20Transformer.isTokenETH(inputToken)) {
|
||||
if (!LibERC20Transformer.isTokenETH(inputToken) && amount != 0) {
|
||||
// Token is not ETH, so pull ERC20 tokens.
|
||||
_transferERC20Tokens(
|
||||
inputToken,
|
||||
|
@@ -25,8 +25,8 @@ import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
import "../migrations/LibMigrate.sol";
|
||||
import "../external/IAllowanceTarget.sol";
|
||||
import "../fixins/FixinCommon.sol";
|
||||
import "./IFeature.sol";
|
||||
import "./IUniswapFeature.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/IUniswapFeature.sol";
|
||||
|
||||
|
||||
/// @dev VIP uniswap fill functions.
|
||||
@@ -380,7 +380,7 @@ contract UniswapFeature is
|
||||
// will eat all our gas.
|
||||
if isTokenPossiblyGreedy(token) {
|
||||
// Check if we have enough direct allowance by calling
|
||||
// `token.allowance()``
|
||||
// `token.allowance()`
|
||||
mstore(0xB00, ALLOWANCE_CALL_SELECTOR_32)
|
||||
mstore(0xB04, caller())
|
||||
mstore(0xB24, address())
|
||||
|
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../libs/LibNativeOrder.sol";
|
||||
import "../libs/LibSignature.sol";
|
||||
|
||||
|
||||
/// @dev Feature for batch/market filling limit and RFQ orders.
|
||||
interface IBatchFillNativeOrdersFeature {
|
||||
|
||||
/// @dev Fills multiple limit orders.
|
||||
/// @param orders Array of limit orders.
|
||||
/// @param signatures Array of signatures corresponding to each order.
|
||||
/// @param takerTokenFillAmounts Array of desired amounts to fill each order.
|
||||
/// @param revertIfIncomplete If true, reverts if this function fails to
|
||||
/// fill the full fill amount for any individual order.
|
||||
/// @return takerTokenFilledAmounts Array of amounts filled, in taker token.
|
||||
/// @return makerTokenFilledAmounts Array of amounts filled, in maker token.
|
||||
function batchFillLimitOrders(
|
||||
LibNativeOrder.LimitOrder[] calldata orders,
|
||||
LibSignature.Signature[] calldata signatures,
|
||||
uint128[] calldata takerTokenFillAmounts,
|
||||
bool revertIfIncomplete
|
||||
)
|
||||
external
|
||||
payable
|
||||
returns (
|
||||
uint128[] memory takerTokenFilledAmounts,
|
||||
uint128[] memory makerTokenFilledAmounts
|
||||
);
|
||||
|
||||
/// @dev Fills multiple RFQ orders.
|
||||
/// @param orders Array of RFQ orders.
|
||||
/// @param signatures Array of signatures corresponding to each order.
|
||||
/// @param takerTokenFillAmounts Array of desired amounts to fill each order.
|
||||
/// @param revertIfIncomplete If true, reverts if this function fails to
|
||||
/// fill the full fill amount for any individual order.
|
||||
/// @return takerTokenFilledAmounts Array of amounts filled, in taker token.
|
||||
/// @return makerTokenFilledAmounts Array of amounts filled, in maker token.
|
||||
function batchFillRfqOrders(
|
||||
LibNativeOrder.RfqOrder[] calldata orders,
|
||||
LibSignature.Signature[] calldata signatures,
|
||||
uint128[] calldata takerTokenFillAmounts,
|
||||
bool revertIfIncomplete
|
||||
)
|
||||
external
|
||||
returns (
|
||||
uint128[] memory takerTokenFilledAmounts,
|
||||
uint128[] memory makerTokenFilledAmounts
|
||||
);
|
||||
}
|
@@ -20,10 +20,23 @@
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../../vendor/ILiquidityProvider.sol";
|
||||
|
||||
|
||||
/// @dev Feature to swap directly with an on-chain liquidity provider.
|
||||
interface ILiquidityProviderFeature {
|
||||
|
||||
/// @dev Event for data pipeline.
|
||||
event LiquidityProviderSwap(
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
uint256 inputTokenAmount,
|
||||
uint256 outputTokenAmount,
|
||||
ILiquidityProvider provider,
|
||||
address recipient
|
||||
);
|
||||
|
||||
/// @dev Sells `sellAmount` of `inputToken` to the liquidity provider
|
||||
/// at the given `provider` address.
|
||||
/// @param inputToken The token being sold.
|
||||
@@ -38,9 +51,9 @@ interface ILiquidityProviderFeature {
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function sellToLiquidityProvider(
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
address payable provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
ILiquidityProvider provider,
|
||||
address recipient,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount,
|
@@ -21,7 +21,7 @@ pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "./libs/LibSignature.sol";
|
||||
import "../libs/LibSignature.sol";
|
||||
|
||||
/// @dev Meta-transactions feature.
|
||||
interface IMetaTransactionsFeature {
|
@@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
|
||||
|
||||
interface IMultiplexFeature {
|
||||
|
||||
// Parameters for `batchFill`.
|
||||
struct BatchFillData {
|
||||
// The token being sold.
|
||||
IERC20TokenV06 inputToken;
|
||||
// The token being bought.
|
||||
IERC20TokenV06 outputToken;
|
||||
// The amount of `inputToken` to sell.
|
||||
uint256 sellAmount;
|
||||
// The nested calls to perform.
|
||||
WrappedBatchCall[] calls;
|
||||
}
|
||||
|
||||
// Represents a call nested within a `batchFill`.
|
||||
struct WrappedBatchCall {
|
||||
// The selector of the function to call.
|
||||
bytes4 selector;
|
||||
// Amount of `inputToken` to sell.
|
||||
uint256 sellAmount;
|
||||
// ABI-encoded parameters needed to perform the call.
|
||||
bytes data;
|
||||
}
|
||||
|
||||
// Parameters for `multiHopFill`.
|
||||
struct MultiHopFillData {
|
||||
// The sell path, i.e.
|
||||
// tokens = [inputToken, hopToken1, ..., hopTokenN, outputToken]
|
||||
address[] tokens;
|
||||
// The amount of `tokens[0]` to sell.
|
||||
uint256 sellAmount;
|
||||
// The nested calls to perform.
|
||||
WrappedMultiHopCall[] calls;
|
||||
}
|
||||
|
||||
// Represents a call nested within a `multiHopFill`.
|
||||
struct WrappedMultiHopCall {
|
||||
// The selector of the function to call.
|
||||
bytes4 selector;
|
||||
// ABI-encoded parameters needed to perform the call.
|
||||
bytes data;
|
||||
}
|
||||
|
||||
event LiquidityProviderSwap(
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
uint256 inputTokenAmount,
|
||||
uint256 outputTokenAmount,
|
||||
address provider,
|
||||
address recipient
|
||||
);
|
||||
|
||||
event ExpiredRfqOrder(
|
||||
bytes32 orderHash,
|
||||
address maker,
|
||||
uint64 expiry
|
||||
);
|
||||
|
||||
/// @dev Executes a batch of fills selling `fillData.inputToken`
|
||||
/// for `fillData.outputToken` in sequence. Refer to the
|
||||
/// internal variant `_batchFill` for the allowed nested
|
||||
/// operations.
|
||||
/// @param fillData Encodes the input/output tokens, the sell
|
||||
/// amount, and the nested operations for this batch fill.
|
||||
/// @param minBuyAmount The minimum amount of `fillData.outputToken`
|
||||
/// to buy. Reverts if this amount is not met.
|
||||
/// @return outputTokenAmount The amount of the output token bought.
|
||||
function batchFill(
|
||||
BatchFillData calldata fillData,
|
||||
uint256 minBuyAmount
|
||||
)
|
||||
external
|
||||
payable
|
||||
returns (uint256 outputTokenAmount);
|
||||
|
||||
/// @dev Executes a sequence of fills "hopping" through the
|
||||
/// path of tokens given by `fillData.tokens`. Refer to the
|
||||
/// internal variant `_multiHopFill` for the allowed nested
|
||||
/// operations.
|
||||
/// @param fillData Encodes the path of tokens, the sell amount,
|
||||
/// and the nested operations for this multi-hop fill.
|
||||
/// @param minBuyAmount The minimum amount of the output token
|
||||
/// to buy. Reverts if this amount is not met.
|
||||
/// @return outputTokenAmount The amount of the output token bought.
|
||||
function multiHopFill(
|
||||
MultiHopFillData calldata fillData,
|
||||
uint256 minBuyAmount
|
||||
)
|
||||
external
|
||||
payable
|
||||
returns (uint256 outputTokenAmount);
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../libs/LibSignature.sol";
|
||||
import "../libs/LibNativeOrder.sol";
|
||||
|
||||
|
||||
/// @dev Events emitted by NativeOrdersFeature.
|
||||
interface INativeOrdersEvents {
|
||||
|
||||
/// @dev Emitted whenever a `LimitOrder` is filled.
|
||||
/// @param orderHash The canonical hash of the order.
|
||||
/// @param maker The maker of the order.
|
||||
/// @param taker The taker of the order.
|
||||
/// @param feeRecipient Fee recipient of the order.
|
||||
/// @param takerTokenFilledAmount How much taker token was filled.
|
||||
/// @param makerTokenFilledAmount How much maker token was filled.
|
||||
/// @param protocolFeePaid How much protocol fee was paid.
|
||||
/// @param pool The fee pool associated with this order.
|
||||
event LimitOrderFilled(
|
||||
bytes32 orderHash,
|
||||
address maker,
|
||||
address taker,
|
||||
address feeRecipient,
|
||||
address makerToken,
|
||||
address takerToken,
|
||||
uint128 takerTokenFilledAmount,
|
||||
uint128 makerTokenFilledAmount,
|
||||
uint128 takerTokenFeeFilledAmount,
|
||||
uint256 protocolFeePaid,
|
||||
bytes32 pool
|
||||
);
|
||||
|
||||
/// @dev Emitted whenever an `RfqOrder` is filled.
|
||||
/// @param orderHash The canonical hash of the order.
|
||||
/// @param maker The maker of the order.
|
||||
/// @param taker The taker of the order.
|
||||
/// @param takerTokenFilledAmount How much taker token was filled.
|
||||
/// @param makerTokenFilledAmount How much maker token was filled.
|
||||
/// @param pool The fee pool associated with this order.
|
||||
event RfqOrderFilled(
|
||||
bytes32 orderHash,
|
||||
address maker,
|
||||
address taker,
|
||||
address makerToken,
|
||||
address takerToken,
|
||||
uint128 takerTokenFilledAmount,
|
||||
uint128 makerTokenFilledAmount,
|
||||
bytes32 pool
|
||||
);
|
||||
|
||||
/// @dev Emitted whenever a limit or RFQ order is cancelled.
|
||||
/// @param orderHash The canonical hash of the order.
|
||||
/// @param maker The order maker.
|
||||
event OrderCancelled(
|
||||
bytes32 orderHash,
|
||||
address maker
|
||||
);
|
||||
|
||||
/// @dev Emitted whenever Limit orders are cancelled by pair by a maker.
|
||||
/// @param maker The maker of the order.
|
||||
/// @param makerToken The maker token in a pair for the orders cancelled.
|
||||
/// @param takerToken The taker token in a pair for the orders cancelled.
|
||||
/// @param minValidSalt The new minimum valid salt an order with this pair must
|
||||
/// have.
|
||||
event PairCancelledLimitOrders(
|
||||
address maker,
|
||||
address makerToken,
|
||||
address takerToken,
|
||||
uint256 minValidSalt
|
||||
);
|
||||
|
||||
/// @dev Emitted whenever RFQ orders are cancelled by pair by a maker.
|
||||
/// @param maker The maker of the order.
|
||||
/// @param makerToken The maker token in a pair for the orders cancelled.
|
||||
/// @param takerToken The taker token in a pair for the orders cancelled.
|
||||
/// @param minValidSalt The new minimum valid salt an order with this pair must
|
||||
/// have.
|
||||
event PairCancelledRfqOrders(
|
||||
address maker,
|
||||
address makerToken,
|
||||
address takerToken,
|
||||
uint256 minValidSalt
|
||||
);
|
||||
|
||||
/// @dev Emitted when new addresses are allowed or disallowed to fill
|
||||
/// orders with a given txOrigin.
|
||||
/// @param origin The address doing the allowing.
|
||||
/// @param addrs The address being allowed/disallowed.
|
||||
/// @param allowed Indicates whether the address should be allowed.
|
||||
event RfqOrderOriginsAllowed(
|
||||
address origin,
|
||||
address[] addrs,
|
||||
bool allowed
|
||||
);
|
||||
}
|
@@ -21,98 +21,15 @@ pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "./libs/LibSignature.sol";
|
||||
import "./libs/LibNativeOrder.sol";
|
||||
import "../libs/LibSignature.sol";
|
||||
import "../libs/LibNativeOrder.sol";
|
||||
import "./INativeOrdersEvents.sol";
|
||||
|
||||
|
||||
/// @dev Feature for interacting with limit orders.
|
||||
interface INativeOrdersFeature {
|
||||
|
||||
/// @dev Emitted whenever a `LimitOrder` is filled.
|
||||
/// @param orderHash The canonical hash of the order.
|
||||
/// @param maker The maker of the order.
|
||||
/// @param taker The taker of the order.
|
||||
/// @param feeRecipient Fee recipient of the order.
|
||||
/// @param takerTokenFilledAmount How much taker token was filled.
|
||||
/// @param makerTokenFilledAmount How much maker token was filled.
|
||||
/// @param protocolFeePaid How much protocol fee was paid.
|
||||
/// @param pool The fee pool associated with this order.
|
||||
event LimitOrderFilled(
|
||||
bytes32 orderHash,
|
||||
address maker,
|
||||
address taker,
|
||||
address feeRecipient,
|
||||
address makerToken,
|
||||
address takerToken,
|
||||
uint128 takerTokenFilledAmount,
|
||||
uint128 makerTokenFilledAmount,
|
||||
uint128 takerTokenFeeFilledAmount,
|
||||
uint256 protocolFeePaid,
|
||||
bytes32 pool
|
||||
);
|
||||
|
||||
/// @dev Emitted whenever an `RfqOrder` is filled.
|
||||
/// @param orderHash The canonical hash of the order.
|
||||
/// @param maker The maker of the order.
|
||||
/// @param taker The taker of the order.
|
||||
/// @param takerTokenFilledAmount How much taker token was filled.
|
||||
/// @param makerTokenFilledAmount How much maker token was filled.
|
||||
/// @param pool The fee pool associated with this order.
|
||||
event RfqOrderFilled(
|
||||
bytes32 orderHash,
|
||||
address maker,
|
||||
address taker,
|
||||
address makerToken,
|
||||
address takerToken,
|
||||
uint128 takerTokenFilledAmount,
|
||||
uint128 makerTokenFilledAmount,
|
||||
bytes32 pool
|
||||
);
|
||||
|
||||
/// @dev Emitted whenever a limit or RFQ order is cancelled.
|
||||
/// @param orderHash The canonical hash of the order.
|
||||
/// @param maker The order maker.
|
||||
event OrderCancelled(
|
||||
bytes32 orderHash,
|
||||
address maker
|
||||
);
|
||||
|
||||
/// @dev Emitted whenever Limit orders are cancelled by pair by a maker.
|
||||
/// @param maker The maker of the order.
|
||||
/// @param makerToken The maker token in a pair for the orders cancelled.
|
||||
/// @param takerToken The taker token in a pair for the orders cancelled.
|
||||
/// @param minValidSalt The new minimum valid salt an order with this pair must
|
||||
/// have.
|
||||
event PairCancelledLimitOrders(
|
||||
address maker,
|
||||
address makerToken,
|
||||
address takerToken,
|
||||
uint256 minValidSalt
|
||||
);
|
||||
|
||||
/// @dev Emitted whenever RFQ orders are cancelled by pair by a maker.
|
||||
/// @param maker The maker of the order.
|
||||
/// @param makerToken The maker token in a pair for the orders cancelled.
|
||||
/// @param takerToken The taker token in a pair for the orders cancelled.
|
||||
/// @param minValidSalt The new minimum valid salt an order with this pair must
|
||||
/// have.
|
||||
event PairCancelledRfqOrders(
|
||||
address maker,
|
||||
address makerToken,
|
||||
address takerToken,
|
||||
uint256 minValidSalt
|
||||
);
|
||||
|
||||
/// @dev Emitted when new addresses are allowed or disallowed to fill
|
||||
/// orders with a given txOrigin.
|
||||
/// @param origin The address doing the allowing.
|
||||
/// @param addrs The address being allowed/disallowed.
|
||||
/// @param allowed Indicates whether the address should be allowed.
|
||||
event RfqOrderOriginsAllowed(
|
||||
address origin,
|
||||
address[] addrs,
|
||||
bool allowed
|
||||
);
|
||||
interface INativeOrdersFeature is
|
||||
INativeOrdersEvents
|
||||
{
|
||||
|
||||
/// @dev Transfers protocol fees from the `FeeCollector` pools into
|
||||
/// the staking contract.
|
@@ -21,8 +21,8 @@ pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../transformers/IERC20Transformer.sol";
|
||||
import "../external/IFlashWallet.sol";
|
||||
import "../../transformers/IERC20Transformer.sol";
|
||||
import "../../external/IFlashWallet.sol";
|
||||
|
||||
|
||||
/// @dev Feature to composably transform between ERC20 tokens.
|
@@ -21,10 +21,15 @@ pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "../../errors/LibNativeOrdersRichErrors.sol";
|
||||
|
||||
|
||||
/// @dev A library for common native order operations.
|
||||
library LibNativeOrder {
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
enum OrderStatus {
|
||||
INVALID,
|
||||
@@ -216,4 +221,23 @@ library LibNativeOrder {
|
||||
structHash := keccak256(mem, 0x160)
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Refund any leftover protocol fees in `msg.value` to `msg.sender`.
|
||||
/// @param ethProtocolFeePaid How much ETH was paid in protocol fees.
|
||||
function refundExcessProtocolFeeToSender(uint256 ethProtocolFeePaid)
|
||||
internal
|
||||
{
|
||||
if (msg.value > ethProtocolFeePaid && msg.sender != address(this)) {
|
||||
uint256 refundAmount = msg.value.safeSub(ethProtocolFeePaid);
|
||||
(bool success,) = msg
|
||||
.sender
|
||||
.call{value: refundAmount}("");
|
||||
if (!success) {
|
||||
LibNativeOrdersRichErrors.ProtocolFeeRefundFailed(
|
||||
msg.sender,
|
||||
refundAmount
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,266 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "../../errors/LibNativeOrdersRichErrors.sol";
|
||||
import "../../storage/LibNativeOrdersStorage.sol";
|
||||
import "../interfaces/INativeOrdersEvents.sol";
|
||||
import "../libs/LibSignature.sol";
|
||||
import "../libs/LibNativeOrder.sol";
|
||||
import "./NativeOrdersInfo.sol";
|
||||
|
||||
/// @dev Feature for cancelling limit and RFQ orders.
|
||||
abstract contract NativeOrdersCancellation is
|
||||
INativeOrdersEvents,
|
||||
NativeOrdersInfo
|
||||
{
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
/// @dev Highest bit of a uint256, used to flag cancelled orders.
|
||||
uint256 private constant HIGH_BIT = 1 << 255;
|
||||
|
||||
constructor(
|
||||
address zeroExAddress,
|
||||
bytes32 greedyTokensBloomFilter
|
||||
)
|
||||
internal
|
||||
NativeOrdersInfo(zeroExAddress, greedyTokensBloomFilter)
|
||||
{
|
||||
// solhint-disable no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Cancel a single limit order. The caller must be the maker.
|
||||
/// Silently succeeds if the order has already been cancelled.
|
||||
/// @param order The limit order.
|
||||
function cancelLimitOrder(LibNativeOrder.LimitOrder memory order)
|
||||
public
|
||||
{
|
||||
bytes32 orderHash = getLimitOrderHash(order);
|
||||
if (msg.sender != order.maker) {
|
||||
LibNativeOrdersRichErrors.OnlyOrderMakerAllowed(
|
||||
orderHash,
|
||||
msg.sender,
|
||||
order.maker
|
||||
).rrevert();
|
||||
}
|
||||
_cancelOrderHash(orderHash, order.maker);
|
||||
}
|
||||
|
||||
/// @dev Cancel a single RFQ order. The caller must be the maker.
|
||||
/// Silently succeeds if the order has already been cancelled.
|
||||
/// @param order The RFQ order.
|
||||
function cancelRfqOrder(LibNativeOrder.RfqOrder memory order)
|
||||
public
|
||||
{
|
||||
bytes32 orderHash = getRfqOrderHash(order);
|
||||
if (msg.sender != order.maker) {
|
||||
LibNativeOrdersRichErrors.OnlyOrderMakerAllowed(
|
||||
orderHash,
|
||||
msg.sender,
|
||||
order.maker
|
||||
).rrevert();
|
||||
}
|
||||
_cancelOrderHash(orderHash, order.maker);
|
||||
}
|
||||
|
||||
/// @dev Cancel multiple limit orders. The caller must be the maker.
|
||||
/// Silently succeeds if the order has already been cancelled.
|
||||
/// @param orders The limit orders.
|
||||
function batchCancelLimitOrders(LibNativeOrder.LimitOrder[] memory orders)
|
||||
public
|
||||
{
|
||||
for (uint256 i = 0; i < orders.length; ++i) {
|
||||
cancelLimitOrder(orders[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Cancel multiple RFQ orders. The caller must be the maker.
|
||||
/// Silently succeeds if the order has already been cancelled.
|
||||
/// @param orders The RFQ orders.
|
||||
function batchCancelRfqOrders(LibNativeOrder.RfqOrder[] memory orders)
|
||||
public
|
||||
{
|
||||
for (uint256 i = 0; i < orders.length; ++i) {
|
||||
cancelRfqOrder(orders[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Cancel all limit orders for a given maker and pair with a salt less
|
||||
/// than the value provided. The caller must be the maker. Subsequent
|
||||
/// calls to this function with the same caller and pair require the
|
||||
/// new salt to be >= the old salt.
|
||||
/// @param makerToken The maker token.
|
||||
/// @param takerToken The taker token.
|
||||
/// @param minValidSalt The new minimum valid salt.
|
||||
function cancelPairLimitOrders(
|
||||
IERC20TokenV06 makerToken,
|
||||
IERC20TokenV06 takerToken,
|
||||
uint256 minValidSalt
|
||||
)
|
||||
public
|
||||
{
|
||||
LibNativeOrdersStorage.Storage storage stor =
|
||||
LibNativeOrdersStorage.getStorage();
|
||||
|
||||
uint256 oldMinValidSalt =
|
||||
stor.limitOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||
[msg.sender]
|
||||
[address(makerToken)]
|
||||
[address(takerToken)];
|
||||
|
||||
// New min salt must >= the old one.
|
||||
if (oldMinValidSalt > minValidSalt) {
|
||||
LibNativeOrdersRichErrors.
|
||||
CancelSaltTooLowError(minValidSalt, oldMinValidSalt)
|
||||
.rrevert();
|
||||
}
|
||||
|
||||
stor.limitOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||
[msg.sender]
|
||||
[address(makerToken)]
|
||||
[address(takerToken)] = minValidSalt;
|
||||
|
||||
emit PairCancelledLimitOrders(
|
||||
msg.sender,
|
||||
address(makerToken),
|
||||
address(takerToken),
|
||||
minValidSalt
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Cancel all limit orders for a given maker and pair with a salt less
|
||||
/// than the value provided. The caller must be the maker. Subsequent
|
||||
/// calls to this function with the same caller and pair require the
|
||||
/// new salt to be >= the old salt.
|
||||
/// @param makerTokens The maker tokens.
|
||||
/// @param takerTokens The taker tokens.
|
||||
/// @param minValidSalts The new minimum valid salts.
|
||||
function batchCancelPairLimitOrders(
|
||||
IERC20TokenV06[] memory makerTokens,
|
||||
IERC20TokenV06[] memory takerTokens,
|
||||
uint256[] memory minValidSalts
|
||||
)
|
||||
public
|
||||
{
|
||||
require(
|
||||
makerTokens.length == takerTokens.length &&
|
||||
makerTokens.length == minValidSalts.length,
|
||||
"NativeOrdersFeature/MISMATCHED_PAIR_ORDERS_ARRAY_LENGTHS"
|
||||
);
|
||||
|
||||
for (uint256 i = 0; i < makerTokens.length; ++i) {
|
||||
cancelPairLimitOrders(
|
||||
makerTokens[i],
|
||||
takerTokens[i],
|
||||
minValidSalts[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Cancel all RFQ orders for a given maker and pair with a salt less
|
||||
/// than the value provided. The caller must be the maker. Subsequent
|
||||
/// calls to this function with the same caller and pair require the
|
||||
/// new salt to be >= the old salt.
|
||||
/// @param makerToken The maker token.
|
||||
/// @param takerToken The taker token.
|
||||
/// @param minValidSalt The new minimum valid salt.
|
||||
function cancelPairRfqOrders(
|
||||
IERC20TokenV06 makerToken,
|
||||
IERC20TokenV06 takerToken,
|
||||
uint256 minValidSalt
|
||||
)
|
||||
public
|
||||
{
|
||||
LibNativeOrdersStorage.Storage storage stor =
|
||||
LibNativeOrdersStorage.getStorage();
|
||||
|
||||
uint256 oldMinValidSalt =
|
||||
stor.rfqOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||
[msg.sender]
|
||||
[address(makerToken)]
|
||||
[address(takerToken)];
|
||||
|
||||
// New min salt must >= the old one.
|
||||
if (oldMinValidSalt > minValidSalt) {
|
||||
LibNativeOrdersRichErrors.
|
||||
CancelSaltTooLowError(minValidSalt, oldMinValidSalt)
|
||||
.rrevert();
|
||||
}
|
||||
|
||||
stor.rfqOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||
[msg.sender]
|
||||
[address(makerToken)]
|
||||
[address(takerToken)] = minValidSalt;
|
||||
|
||||
emit PairCancelledRfqOrders(
|
||||
msg.sender,
|
||||
address(makerToken),
|
||||
address(takerToken),
|
||||
minValidSalt
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Cancel all RFQ orders for a given maker and pair with a salt less
|
||||
/// than the value provided. The caller must be the maker. Subsequent
|
||||
/// calls to this function with the same caller and pair require the
|
||||
/// new salt to be >= the old salt.
|
||||
/// @param makerTokens The maker tokens.
|
||||
/// @param takerTokens The taker tokens.
|
||||
/// @param minValidSalts The new minimum valid salts.
|
||||
function batchCancelPairRfqOrders(
|
||||
IERC20TokenV06[] memory makerTokens,
|
||||
IERC20TokenV06[] memory takerTokens,
|
||||
uint256[] memory minValidSalts
|
||||
)
|
||||
public
|
||||
{
|
||||
require(
|
||||
makerTokens.length == takerTokens.length &&
|
||||
makerTokens.length == minValidSalts.length,
|
||||
"NativeOrdersFeature/MISMATCHED_PAIR_ORDERS_ARRAY_LENGTHS"
|
||||
);
|
||||
|
||||
for (uint256 i = 0; i < makerTokens.length; ++i) {
|
||||
cancelPairRfqOrders(
|
||||
makerTokens[i],
|
||||
takerTokens[i],
|
||||
minValidSalts[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Cancel a limit or RFQ order directly by its order hash.
|
||||
/// @param orderHash The order's order hash.
|
||||
/// @param maker The order's maker.
|
||||
function _cancelOrderHash(bytes32 orderHash, address maker)
|
||||
private
|
||||
{
|
||||
LibNativeOrdersStorage.Storage storage stor =
|
||||
LibNativeOrdersStorage.getStorage();
|
||||
// Set the high bit on the raw taker token fill amount to indicate
|
||||
// a cancel. It's OK to cancel twice.
|
||||
stor.orderHashToTakerTokenFilledAmount[orderHash] |= HIGH_BIT;
|
||||
|
||||
emit OrderCancelled(orderHash, maker);
|
||||
}
|
||||
}
|
@@ -0,0 +1,394 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||
import "../../fixins/FixinEIP712.sol";
|
||||
import "../../fixins/FixinTokenSpender.sol";
|
||||
import "../../storage/LibNativeOrdersStorage.sol";
|
||||
import "../libs/LibSignature.sol";
|
||||
import "../libs/LibNativeOrder.sol";
|
||||
|
||||
|
||||
/// @dev Feature for getting info about limit and RFQ orders.
|
||||
abstract contract NativeOrdersInfo is
|
||||
FixinEIP712,
|
||||
FixinTokenSpender
|
||||
{
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
// @dev Params for `_getActualFillableTakerTokenAmount()`.
|
||||
struct GetActualFillableTakerTokenAmountParams {
|
||||
address maker;
|
||||
IERC20TokenV06 makerToken;
|
||||
uint128 orderMakerAmount;
|
||||
uint128 orderTakerAmount;
|
||||
LibNativeOrder.OrderInfo orderInfo;
|
||||
}
|
||||
|
||||
/// @dev Highest bit of a uint256, used to flag cancelled orders.
|
||||
uint256 private constant HIGH_BIT = 1 << 255;
|
||||
|
||||
constructor(
|
||||
address zeroExAddress,
|
||||
bytes32 greedyTokensBloomFilter
|
||||
)
|
||||
internal
|
||||
FixinEIP712(zeroExAddress)
|
||||
FixinTokenSpender(greedyTokensBloomFilter)
|
||||
{
|
||||
// solhint-disable no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Get the order info for a limit order.
|
||||
/// @param order The limit order.
|
||||
/// @return orderInfo Info about the order.
|
||||
function getLimitOrderInfo(LibNativeOrder.LimitOrder memory order)
|
||||
public
|
||||
view
|
||||
returns (LibNativeOrder.OrderInfo memory orderInfo)
|
||||
{
|
||||
// Recover maker and compute order hash.
|
||||
orderInfo.orderHash = getLimitOrderHash(order);
|
||||
uint256 minValidSalt = LibNativeOrdersStorage.getStorage()
|
||||
.limitOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||
[order.maker]
|
||||
[address(order.makerToken)]
|
||||
[address(order.takerToken)];
|
||||
_populateCommonOrderInfoFields(
|
||||
orderInfo,
|
||||
order.takerAmount,
|
||||
order.expiry,
|
||||
order.salt,
|
||||
minValidSalt
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Get the order info for an RFQ order.
|
||||
/// @param order The RFQ order.
|
||||
/// @return orderInfo Info about the order.
|
||||
function getRfqOrderInfo(LibNativeOrder.RfqOrder memory order)
|
||||
public
|
||||
view
|
||||
returns (LibNativeOrder.OrderInfo memory orderInfo)
|
||||
{
|
||||
// Recover maker and compute order hash.
|
||||
orderInfo.orderHash = getRfqOrderHash(order);
|
||||
uint256 minValidSalt = LibNativeOrdersStorage.getStorage()
|
||||
.rfqOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||
[order.maker]
|
||||
[address(order.makerToken)]
|
||||
[address(order.takerToken)];
|
||||
_populateCommonOrderInfoFields(
|
||||
orderInfo,
|
||||
order.takerAmount,
|
||||
order.expiry,
|
||||
order.salt,
|
||||
minValidSalt
|
||||
);
|
||||
|
||||
// Check for missing txOrigin.
|
||||
if (order.txOrigin == address(0)) {
|
||||
orderInfo.status = LibNativeOrder.OrderStatus.INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Get the canonical hash of a limit order.
|
||||
/// @param order The limit order.
|
||||
/// @return orderHash The order hash.
|
||||
function getLimitOrderHash(LibNativeOrder.LimitOrder memory order)
|
||||
public
|
||||
view
|
||||
returns (bytes32 orderHash)
|
||||
{
|
||||
return _getEIP712Hash(
|
||||
LibNativeOrder.getLimitOrderStructHash(order)
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Get the canonical hash of an RFQ order.
|
||||
/// @param order The RFQ order.
|
||||
/// @return orderHash The order hash.
|
||||
function getRfqOrderHash(LibNativeOrder.RfqOrder memory order)
|
||||
public
|
||||
view
|
||||
returns (bytes32 orderHash)
|
||||
{
|
||||
return _getEIP712Hash(
|
||||
LibNativeOrder.getRfqOrderStructHash(order)
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Get order info, fillable amount, and signature validity for a limit order.
|
||||
/// Fillable amount is determined using balances and allowances of the maker.
|
||||
/// @param order The limit order.
|
||||
/// @param signature The order signature.
|
||||
/// @return orderInfo Info about the order.
|
||||
/// @return actualFillableTakerTokenAmount How much of the order is fillable
|
||||
/// based on maker funds, in taker tokens.
|
||||
/// @return isSignatureValid Whether the signature is valid.
|
||||
function getLimitOrderRelevantState(
|
||||
LibNativeOrder.LimitOrder memory order,
|
||||
LibSignature.Signature calldata signature
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
LibNativeOrder.OrderInfo memory orderInfo,
|
||||
uint128 actualFillableTakerTokenAmount,
|
||||
bool isSignatureValid
|
||||
)
|
||||
{
|
||||
orderInfo = getLimitOrderInfo(order);
|
||||
actualFillableTakerTokenAmount = _getActualFillableTakerTokenAmount(
|
||||
GetActualFillableTakerTokenAmountParams({
|
||||
maker: order.maker,
|
||||
makerToken: order.makerToken,
|
||||
orderMakerAmount: order.makerAmount,
|
||||
orderTakerAmount: order.takerAmount,
|
||||
orderInfo: orderInfo
|
||||
})
|
||||
);
|
||||
isSignatureValid = order.maker ==
|
||||
LibSignature.getSignerOfHash(orderInfo.orderHash, signature);
|
||||
}
|
||||
|
||||
/// @dev Get order info, fillable amount, and signature validity for an RFQ order.
|
||||
/// Fillable amount is determined using balances and allowances of the maker.
|
||||
/// @param order The RFQ order.
|
||||
/// @param signature The order signature.
|
||||
/// @return orderInfo Info about the order.
|
||||
/// @return actualFillableTakerTokenAmount How much of the order is fillable
|
||||
/// based on maker funds, in taker tokens.
|
||||
/// @return isSignatureValid Whether the signature is valid.
|
||||
function getRfqOrderRelevantState(
|
||||
LibNativeOrder.RfqOrder memory order,
|
||||
LibSignature.Signature memory signature
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
LibNativeOrder.OrderInfo memory orderInfo,
|
||||
uint128 actualFillableTakerTokenAmount,
|
||||
bool isSignatureValid
|
||||
)
|
||||
{
|
||||
orderInfo = getRfqOrderInfo(order);
|
||||
actualFillableTakerTokenAmount = _getActualFillableTakerTokenAmount(
|
||||
GetActualFillableTakerTokenAmountParams({
|
||||
maker: order.maker,
|
||||
makerToken: order.makerToken,
|
||||
orderMakerAmount: order.makerAmount,
|
||||
orderTakerAmount: order.takerAmount,
|
||||
orderInfo: orderInfo
|
||||
})
|
||||
);
|
||||
isSignatureValid = order.maker ==
|
||||
LibSignature.getSignerOfHash(orderInfo.orderHash, signature);
|
||||
}
|
||||
|
||||
/// @dev Batch version of `getLimitOrderRelevantState()`, without reverting.
|
||||
/// Orders that would normally cause `getLimitOrderRelevantState()`
|
||||
/// to revert will have empty results.
|
||||
/// @param orders The limit orders.
|
||||
/// @param signatures The order signatures.
|
||||
/// @return orderInfos Info about the orders.
|
||||
/// @return actualFillableTakerTokenAmounts How much of each order is fillable
|
||||
/// based on maker funds, in taker tokens.
|
||||
/// @return isSignatureValids Whether each signature is valid for the order.
|
||||
function batchGetLimitOrderRelevantStates(
|
||||
LibNativeOrder.LimitOrder[] calldata orders,
|
||||
LibSignature.Signature[] calldata signatures
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
LibNativeOrder.OrderInfo[] memory orderInfos,
|
||||
uint128[] memory actualFillableTakerTokenAmounts,
|
||||
bool[] memory isSignatureValids
|
||||
)
|
||||
{
|
||||
require(
|
||||
orders.length == signatures.length,
|
||||
"NativeOrdersFeature/MISMATCHED_ARRAY_LENGTHS"
|
||||
);
|
||||
orderInfos = new LibNativeOrder.OrderInfo[](orders.length);
|
||||
actualFillableTakerTokenAmounts = new uint128[](orders.length);
|
||||
isSignatureValids = new bool[](orders.length);
|
||||
for (uint256 i = 0; i < orders.length; ++i) {
|
||||
try
|
||||
this.getLimitOrderRelevantState(orders[i], signatures[i])
|
||||
returns (
|
||||
LibNativeOrder.OrderInfo memory orderInfo,
|
||||
uint128 actualFillableTakerTokenAmount,
|
||||
bool isSignatureValid
|
||||
)
|
||||
{
|
||||
orderInfos[i] = orderInfo;
|
||||
actualFillableTakerTokenAmounts[i] = actualFillableTakerTokenAmount;
|
||||
isSignatureValids[i] = isSignatureValid;
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Batch version of `getRfqOrderRelevantState()`, without reverting.
|
||||
/// Orders that would normally cause `getRfqOrderRelevantState()`
|
||||
/// to revert will have empty results.
|
||||
/// @param orders The RFQ orders.
|
||||
/// @param signatures The order signatures.
|
||||
/// @return orderInfos Info about the orders.
|
||||
/// @return actualFillableTakerTokenAmounts How much of each order is fillable
|
||||
/// based on maker funds, in taker tokens.
|
||||
/// @return isSignatureValids Whether each signature is valid for the order.
|
||||
function batchGetRfqOrderRelevantStates(
|
||||
LibNativeOrder.RfqOrder[] calldata orders,
|
||||
LibSignature.Signature[] calldata signatures
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
LibNativeOrder.OrderInfo[] memory orderInfos,
|
||||
uint128[] memory actualFillableTakerTokenAmounts,
|
||||
bool[] memory isSignatureValids
|
||||
)
|
||||
{
|
||||
require(
|
||||
orders.length == signatures.length,
|
||||
"NativeOrdersFeature/MISMATCHED_ARRAY_LENGTHS"
|
||||
);
|
||||
orderInfos = new LibNativeOrder.OrderInfo[](orders.length);
|
||||
actualFillableTakerTokenAmounts = new uint128[](orders.length);
|
||||
isSignatureValids = new bool[](orders.length);
|
||||
for (uint256 i = 0; i < orders.length; ++i) {
|
||||
try
|
||||
this.getRfqOrderRelevantState(orders[i], signatures[i])
|
||||
returns (
|
||||
LibNativeOrder.OrderInfo memory orderInfo,
|
||||
uint128 actualFillableTakerTokenAmount,
|
||||
bool isSignatureValid
|
||||
)
|
||||
{
|
||||
orderInfos[i] = orderInfo;
|
||||
actualFillableTakerTokenAmounts[i] = actualFillableTakerTokenAmount;
|
||||
isSignatureValids[i] = isSignatureValid;
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Populate `status` and `takerTokenFilledAmount` fields in
|
||||
/// `orderInfo`, which use the same code path for both limit and
|
||||
/// RFQ orders.
|
||||
/// @param orderInfo `OrderInfo` with `orderHash` and `maker` filled.
|
||||
/// @param takerAmount The order's taker token amount..
|
||||
/// @param expiry The order's expiry.
|
||||
/// @param salt The order's salt.
|
||||
/// @param salt The minimum valid salt for the maker and pair combination.
|
||||
function _populateCommonOrderInfoFields(
|
||||
LibNativeOrder.OrderInfo memory orderInfo,
|
||||
uint128 takerAmount,
|
||||
uint64 expiry,
|
||||
uint256 salt,
|
||||
uint256 minValidSalt
|
||||
)
|
||||
private
|
||||
view
|
||||
{
|
||||
LibNativeOrdersStorage.Storage storage stor =
|
||||
LibNativeOrdersStorage.getStorage();
|
||||
|
||||
// Get the filled and direct cancel state.
|
||||
{
|
||||
// The high bit of the raw taker token filled amount will be set
|
||||
// if the order was cancelled.
|
||||
uint256 rawTakerTokenFilledAmount =
|
||||
stor.orderHashToTakerTokenFilledAmount[orderInfo.orderHash];
|
||||
orderInfo.takerTokenFilledAmount = uint128(rawTakerTokenFilledAmount);
|
||||
if (orderInfo.takerTokenFilledAmount >= takerAmount) {
|
||||
orderInfo.status = LibNativeOrder.OrderStatus.FILLED;
|
||||
return;
|
||||
}
|
||||
if (rawTakerTokenFilledAmount & HIGH_BIT != 0) {
|
||||
orderInfo.status = LibNativeOrder.OrderStatus.CANCELLED;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for expiration.
|
||||
if (expiry <= uint64(block.timestamp)) {
|
||||
orderInfo.status = LibNativeOrder.OrderStatus.EXPIRED;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the order was cancelled by salt.
|
||||
if (minValidSalt > salt) {
|
||||
orderInfo.status = LibNativeOrder.OrderStatus.CANCELLED;
|
||||
return;
|
||||
}
|
||||
orderInfo.status = LibNativeOrder.OrderStatus.FILLABLE;
|
||||
}
|
||||
|
||||
/// @dev Calculate the actual fillable taker token amount of an order
|
||||
/// based on maker allowance and balances.
|
||||
function _getActualFillableTakerTokenAmount(
|
||||
GetActualFillableTakerTokenAmountParams memory params
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint128 actualFillableTakerTokenAmount)
|
||||
{
|
||||
if (params.orderMakerAmount == 0 || params.orderTakerAmount == 0) {
|
||||
// Empty order.
|
||||
return 0;
|
||||
}
|
||||
if (params.orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
|
||||
// Not fillable.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the fillable maker amount based on the order quantities and
|
||||
// previously filled amount
|
||||
uint256 fillableMakerTokenAmount = LibMathV06.getPartialAmountFloor(
|
||||
uint256(
|
||||
params.orderTakerAmount
|
||||
- params.orderInfo.takerTokenFilledAmount
|
||||
),
|
||||
uint256(params.orderTakerAmount),
|
||||
uint256(params.orderMakerAmount)
|
||||
);
|
||||
// Clamp it to the amount of maker tokens we can spend on behalf of the
|
||||
// maker.
|
||||
fillableMakerTokenAmount = LibSafeMathV06.min256(
|
||||
fillableMakerTokenAmount,
|
||||
_getSpendableERC20BalanceOf(params.makerToken, params.maker)
|
||||
);
|
||||
// Convert to taker token amount.
|
||||
return LibMathV06.getPartialAmountCeil(
|
||||
fillableMakerTokenAmount,
|
||||
uint256(params.orderMakerAmount),
|
||||
uint256(params.orderTakerAmount)
|
||||
).safeDowncastToUint128();
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "../../fixins/FixinProtocolFees.sol";
|
||||
import "../../errors/LibNativeOrdersRichErrors.sol";
|
||||
import "../../vendor/v3/IStaking.sol";
|
||||
|
||||
|
||||
/// @dev Mixin for protocol fee utility functions.
|
||||
abstract contract NativeOrdersProtocolFees is
|
||||
FixinProtocolFees
|
||||
{
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
constructor(
|
||||
IEtherTokenV06 weth,
|
||||
IStaking staking,
|
||||
FeeCollectorController feeCollectorController,
|
||||
uint32 protocolFeeMultiplier
|
||||
)
|
||||
internal
|
||||
FixinProtocolFees(weth, staking, feeCollectorController, protocolFeeMultiplier)
|
||||
{
|
||||
// solhint-disable no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Transfers protocol fees from the `FeeCollector` pools into
|
||||
/// the staking contract.
|
||||
/// @param poolIds Staking pool IDs
|
||||
function transferProtocolFeesForPools(bytes32[] calldata poolIds)
|
||||
external
|
||||
{
|
||||
for (uint256 i = 0; i < poolIds.length; ++i) {
|
||||
_transferFeesForPool(poolIds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Get the protocol fee multiplier. This should be multiplied by the
|
||||
/// gas price to arrive at the required protocol fee to fill a native order.
|
||||
/// @return multiplier The protocol fee multiplier.
|
||||
function getProtocolFeeMultiplier()
|
||||
external
|
||||
view
|
||||
returns (uint32 multiplier)
|
||||
{
|
||||
return PROTOCOL_FEE_MULTIPLIER;
|
||||
}
|
||||
}
|
@@ -0,0 +1,569 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||
import "../../errors/LibNativeOrdersRichErrors.sol";
|
||||
import "../../fixins/FixinCommon.sol";
|
||||
import "../../storage/LibNativeOrdersStorage.sol";
|
||||
import "../../vendor/v3/IStaking.sol";
|
||||
import "../interfaces/INativeOrdersEvents.sol";
|
||||
import "../libs/LibSignature.sol";
|
||||
import "../libs/LibNativeOrder.sol";
|
||||
import "./NativeOrdersCancellation.sol";
|
||||
import "./NativeOrdersProtocolFees.sol";
|
||||
|
||||
|
||||
/// @dev Mixin for settling limit and RFQ orders.
|
||||
abstract contract NativeOrdersSettlement is
|
||||
INativeOrdersEvents,
|
||||
NativeOrdersCancellation,
|
||||
NativeOrdersProtocolFees,
|
||||
FixinCommon
|
||||
{
|
||||
using LibSafeMathV06 for uint128;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
/// @dev Params for `_settleOrder()`.
|
||||
struct SettleOrderInfo {
|
||||
// Order hash.
|
||||
bytes32 orderHash;
|
||||
// Maker of the order.
|
||||
address maker;
|
||||
// Taker of the order.
|
||||
address taker;
|
||||
// Maker token.
|
||||
IERC20TokenV06 makerToken;
|
||||
// Taker token.
|
||||
IERC20TokenV06 takerToken;
|
||||
// Maker token amount.
|
||||
uint128 makerAmount;
|
||||
// Taker token amount.
|
||||
uint128 takerAmount;
|
||||
// Maximum taker token amount to fill.
|
||||
uint128 takerTokenFillAmount;
|
||||
// How much taker token amount has already been filled in this order.
|
||||
uint128 takerTokenFilledAmount;
|
||||
}
|
||||
|
||||
/// @dev Params for `_fillLimitOrderPrivate()`
|
||||
struct FillLimitOrderPrivateParams {
|
||||
// The limit order.
|
||||
LibNativeOrder.LimitOrder order;
|
||||
// The order signature.
|
||||
LibSignature.Signature signature;
|
||||
// Maximum taker token to fill this order with.
|
||||
uint128 takerTokenFillAmount;
|
||||
// The order taker.
|
||||
address taker;
|
||||
// The order sender.
|
||||
address sender;
|
||||
}
|
||||
|
||||
// @dev Fill results returned by `_fillLimitOrderPrivate()` and
|
||||
/// `_fillRfqOrderPrivate()`.
|
||||
struct FillNativeOrderResults {
|
||||
uint256 ethProtocolFeePaid;
|
||||
uint128 takerTokenFilledAmount;
|
||||
uint128 makerTokenFilledAmount;
|
||||
uint128 takerTokenFeeFilledAmount;
|
||||
}
|
||||
|
||||
constructor(
|
||||
address zeroExAddress,
|
||||
IEtherTokenV06 weth,
|
||||
IStaking staking,
|
||||
FeeCollectorController feeCollectorController,
|
||||
uint32 protocolFeeMultiplier,
|
||||
bytes32 greedyTokensBloomFilter
|
||||
)
|
||||
public
|
||||
NativeOrdersCancellation(zeroExAddress, greedyTokensBloomFilter)
|
||||
NativeOrdersProtocolFees(weth, staking, feeCollectorController, protocolFeeMultiplier)
|
||||
{
|
||||
// solhint-disable no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Fill a limit order. The taker and sender will be the caller.
|
||||
/// @param order The limit order. ETH protocol fees can be
|
||||
/// attached to this call. Any unspent ETH will be refunded to
|
||||
/// the caller.
|
||||
/// @param signature The order signature.
|
||||
/// @param takerTokenFillAmount Maximum taker token amount to fill this order with.
|
||||
/// @return takerTokenFilledAmount How much maker token was filled.
|
||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||
function fillLimitOrder(
|
||||
LibNativeOrder.LimitOrder memory order,
|
||||
LibSignature.Signature memory signature,
|
||||
uint128 takerTokenFillAmount
|
||||
)
|
||||
public
|
||||
payable
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
FillNativeOrderResults memory results =
|
||||
_fillLimitOrderPrivate(FillLimitOrderPrivateParams({
|
||||
order: order,
|
||||
signature: signature,
|
||||
takerTokenFillAmount: takerTokenFillAmount,
|
||||
taker: msg.sender,
|
||||
sender: msg.sender
|
||||
}));
|
||||
LibNativeOrder.refundExcessProtocolFeeToSender(results.ethProtocolFeePaid);
|
||||
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||
results.takerTokenFilledAmount,
|
||||
results.makerTokenFilledAmount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Fill an RFQ order for up to `takerTokenFillAmount` taker tokens.
|
||||
/// The taker will be the caller. ETH should be attached to pay the
|
||||
/// protocol fee.
|
||||
/// @param order The RFQ order.
|
||||
/// @param signature The order signature.
|
||||
/// @param takerTokenFillAmount Maximum taker token amount to fill this order with.
|
||||
/// @return takerTokenFilledAmount How much maker token was filled.
|
||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||
function fillRfqOrder(
|
||||
LibNativeOrder.RfqOrder memory order,
|
||||
LibSignature.Signature memory signature,
|
||||
uint128 takerTokenFillAmount
|
||||
)
|
||||
public
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
FillNativeOrderResults memory results =
|
||||
_fillRfqOrderPrivate(
|
||||
order,
|
||||
signature,
|
||||
takerTokenFillAmount,
|
||||
msg.sender
|
||||
);
|
||||
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||
results.takerTokenFilledAmount,
|
||||
results.makerTokenFilledAmount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Fill an RFQ order for exactly `takerTokenFillAmount` taker tokens.
|
||||
/// The taker will be the caller. ETH protocol fees can be
|
||||
/// attached to this call. Any unspent ETH will be refunded to
|
||||
/// the caller.
|
||||
/// @param order The limit order.
|
||||
/// @param signature The order signature.
|
||||
/// @param takerTokenFillAmount How much taker token to fill this order with.
|
||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||
function fillOrKillLimitOrder(
|
||||
LibNativeOrder.LimitOrder memory order,
|
||||
LibSignature.Signature memory signature,
|
||||
uint128 takerTokenFillAmount
|
||||
)
|
||||
public
|
||||
payable
|
||||
returns (uint128 makerTokenFilledAmount)
|
||||
{
|
||||
FillNativeOrderResults memory results =
|
||||
_fillLimitOrderPrivate(FillLimitOrderPrivateParams({
|
||||
order: order,
|
||||
signature: signature,
|
||||
takerTokenFillAmount: takerTokenFillAmount,
|
||||
taker: msg.sender,
|
||||
sender: msg.sender
|
||||
}));
|
||||
// Must have filled exactly the amount requested.
|
||||
if (results.takerTokenFilledAmount < takerTokenFillAmount) {
|
||||
LibNativeOrdersRichErrors.FillOrKillFailedError(
|
||||
getLimitOrderHash(order),
|
||||
results.takerTokenFilledAmount,
|
||||
takerTokenFillAmount
|
||||
).rrevert();
|
||||
}
|
||||
LibNativeOrder.refundExcessProtocolFeeToSender(results.ethProtocolFeePaid);
|
||||
makerTokenFilledAmount = results.makerTokenFilledAmount;
|
||||
}
|
||||
|
||||
/// @dev Fill an RFQ order for exactly `takerTokenFillAmount` taker tokens.
|
||||
/// The taker will be the caller. ETH protocol fees can be
|
||||
/// attached to this call. Any unspent ETH will be refunded to
|
||||
/// the caller.
|
||||
/// @param order The RFQ order.
|
||||
/// @param signature The order signature.
|
||||
/// @param takerTokenFillAmount How much taker token to fill this order with.
|
||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||
function fillOrKillRfqOrder(
|
||||
LibNativeOrder.RfqOrder memory order,
|
||||
LibSignature.Signature memory signature,
|
||||
uint128 takerTokenFillAmount
|
||||
)
|
||||
public
|
||||
returns (uint128 makerTokenFilledAmount)
|
||||
{
|
||||
FillNativeOrderResults memory results =
|
||||
_fillRfqOrderPrivate(
|
||||
order,
|
||||
signature,
|
||||
takerTokenFillAmount,
|
||||
msg.sender
|
||||
);
|
||||
// Must have filled exactly the amount requested.
|
||||
if (results.takerTokenFilledAmount < takerTokenFillAmount) {
|
||||
LibNativeOrdersRichErrors.FillOrKillFailedError(
|
||||
getRfqOrderHash(order),
|
||||
results.takerTokenFilledAmount,
|
||||
takerTokenFillAmount
|
||||
).rrevert();
|
||||
}
|
||||
makerTokenFilledAmount = results.makerTokenFilledAmount;
|
||||
}
|
||||
|
||||
/// @dev Fill a limit order. Internal variant. ETH protocol fees can be
|
||||
/// attached to this call.
|
||||
/// @param order The limit order.
|
||||
/// @param signature The order signature.
|
||||
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
|
||||
/// @param taker The order taker.
|
||||
/// @param sender The order sender.
|
||||
/// @return takerTokenFilledAmount How much maker token was filled.
|
||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||
function _fillLimitOrder(
|
||||
LibNativeOrder.LimitOrder memory order,
|
||||
LibSignature.Signature memory signature,
|
||||
uint128 takerTokenFillAmount,
|
||||
address taker,
|
||||
address sender
|
||||
)
|
||||
public
|
||||
virtual
|
||||
payable
|
||||
onlySelf
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
FillNativeOrderResults memory results =
|
||||
_fillLimitOrderPrivate(FillLimitOrderPrivateParams({
|
||||
order: order,
|
||||
signature: signature,
|
||||
takerTokenFillAmount: takerTokenFillAmount,
|
||||
taker: taker,
|
||||
sender: sender
|
||||
}));
|
||||
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||
results.takerTokenFilledAmount,
|
||||
results.makerTokenFilledAmount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Fill an RFQ order. Internal variant. ETH protocol fees can be
|
||||
/// attached to this call. Any unspent ETH will be refunded to
|
||||
/// `msg.sender` (not `sender`).
|
||||
/// @param order The RFQ order.
|
||||
/// @param signature The order signature.
|
||||
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
|
||||
/// @param taker The order taker.
|
||||
/// @return takerTokenFilledAmount How much maker token was filled.
|
||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||
function _fillRfqOrder(
|
||||
LibNativeOrder.RfqOrder memory order,
|
||||
LibSignature.Signature memory signature,
|
||||
uint128 takerTokenFillAmount,
|
||||
address taker
|
||||
)
|
||||
public
|
||||
virtual
|
||||
onlySelf
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
FillNativeOrderResults memory results =
|
||||
_fillRfqOrderPrivate(
|
||||
order,
|
||||
signature,
|
||||
takerTokenFillAmount,
|
||||
taker
|
||||
);
|
||||
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||
results.takerTokenFilledAmount,
|
||||
results.makerTokenFilledAmount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Mark what tx.origin addresses are allowed to fill an order that
|
||||
/// specifies the message sender as its txOrigin.
|
||||
/// @param origins An array of origin addresses to update.
|
||||
/// @param allowed True to register, false to unregister.
|
||||
function registerAllowedRfqOrigins(
|
||||
address[] memory origins,
|
||||
bool allowed
|
||||
)
|
||||
external
|
||||
{
|
||||
require(msg.sender == tx.origin,
|
||||
"NativeOrdersFeature/NO_CONTRACT_ORIGINS");
|
||||
|
||||
LibNativeOrdersStorage.Storage storage stor =
|
||||
LibNativeOrdersStorage.getStorage();
|
||||
|
||||
for (uint256 i = 0; i < origins.length; i++) {
|
||||
stor.originRegistry[msg.sender][origins[i]] = allowed;
|
||||
}
|
||||
|
||||
emit RfqOrderOriginsAllowed(msg.sender, origins, allowed);
|
||||
}
|
||||
|
||||
/// @dev Fill a limit order. Private variant. Does not refund protocol fees.
|
||||
/// @param params Function params.
|
||||
/// @return results Results of the fill.
|
||||
function _fillLimitOrderPrivate(FillLimitOrderPrivateParams memory params)
|
||||
private
|
||||
returns (FillNativeOrderResults memory results)
|
||||
{
|
||||
LibNativeOrder.OrderInfo memory orderInfo = getLimitOrderInfo(params.order);
|
||||
|
||||
// Must be fillable.
|
||||
if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
|
||||
LibNativeOrdersRichErrors.OrderNotFillableError(
|
||||
orderInfo.orderHash,
|
||||
uint8(orderInfo.status)
|
||||
).rrevert();
|
||||
}
|
||||
|
||||
// Must be fillable by the taker.
|
||||
if (params.order.taker != address(0) && params.order.taker != params.taker) {
|
||||
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
|
||||
orderInfo.orderHash,
|
||||
params.taker,
|
||||
params.order.taker
|
||||
).rrevert();
|
||||
}
|
||||
|
||||
// Must be fillable by the sender.
|
||||
if (params.order.sender != address(0) && params.order.sender != params.sender) {
|
||||
LibNativeOrdersRichErrors.OrderNotFillableBySenderError(
|
||||
orderInfo.orderHash,
|
||||
params.sender,
|
||||
params.order.sender
|
||||
).rrevert();
|
||||
}
|
||||
|
||||
// Signature must be valid for the order.
|
||||
{
|
||||
address signer = LibSignature.getSignerOfHash(
|
||||
orderInfo.orderHash,
|
||||
params.signature
|
||||
);
|
||||
if (signer != params.order.maker) {
|
||||
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
|
||||
orderInfo.orderHash,
|
||||
signer,
|
||||
params.order.maker
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
|
||||
// Pay the protocol fee.
|
||||
results.ethProtocolFeePaid = _collectProtocolFee(params.order.pool);
|
||||
|
||||
// Settle between the maker and taker.
|
||||
(results.takerTokenFilledAmount, results.makerTokenFilledAmount) = _settleOrder(
|
||||
SettleOrderInfo({
|
||||
orderHash: orderInfo.orderHash,
|
||||
maker: params.order.maker,
|
||||
taker: params.taker,
|
||||
makerToken: IERC20TokenV06(params.order.makerToken),
|
||||
takerToken: IERC20TokenV06(params.order.takerToken),
|
||||
makerAmount: params.order.makerAmount,
|
||||
takerAmount: params.order.takerAmount,
|
||||
takerTokenFillAmount: params.takerTokenFillAmount,
|
||||
takerTokenFilledAmount: orderInfo.takerTokenFilledAmount
|
||||
})
|
||||
);
|
||||
|
||||
// Pay the fee recipient.
|
||||
if (params.order.takerTokenFeeAmount > 0) {
|
||||
results.takerTokenFeeFilledAmount = uint128(LibMathV06.getPartialAmountFloor(
|
||||
results.takerTokenFilledAmount,
|
||||
params.order.takerAmount,
|
||||
params.order.takerTokenFeeAmount
|
||||
));
|
||||
_transferERC20Tokens(
|
||||
params.order.takerToken,
|
||||
params.taker,
|
||||
params.order.feeRecipient,
|
||||
uint256(results.takerTokenFeeFilledAmount)
|
||||
);
|
||||
}
|
||||
|
||||
emit LimitOrderFilled(
|
||||
orderInfo.orderHash,
|
||||
params.order.maker,
|
||||
params.taker,
|
||||
params.order.feeRecipient,
|
||||
address(params.order.makerToken),
|
||||
address(params.order.takerToken),
|
||||
results.takerTokenFilledAmount,
|
||||
results.makerTokenFilledAmount,
|
||||
results.takerTokenFeeFilledAmount,
|
||||
results.ethProtocolFeePaid,
|
||||
params.order.pool
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Fill an RFQ order. Private variant. Does not refund protocol fees.
|
||||
/// @param order The RFQ order.
|
||||
/// @param signature The order signature.
|
||||
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
|
||||
/// @param taker The order taker.
|
||||
/// @return results Results of the fill.
|
||||
function _fillRfqOrderPrivate(
|
||||
LibNativeOrder.RfqOrder memory order,
|
||||
LibSignature.Signature memory signature,
|
||||
uint128 takerTokenFillAmount,
|
||||
address taker
|
||||
)
|
||||
private
|
||||
returns (FillNativeOrderResults memory results)
|
||||
{
|
||||
LibNativeOrder.OrderInfo memory orderInfo = getRfqOrderInfo(order);
|
||||
|
||||
// Must be fillable.
|
||||
if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
|
||||
LibNativeOrdersRichErrors.OrderNotFillableError(
|
||||
orderInfo.orderHash,
|
||||
uint8(orderInfo.status)
|
||||
).rrevert();
|
||||
}
|
||||
|
||||
{
|
||||
LibNativeOrdersStorage.Storage storage stor =
|
||||
LibNativeOrdersStorage.getStorage();
|
||||
|
||||
// Must be fillable by the tx.origin.
|
||||
if (order.txOrigin != tx.origin && !stor.originRegistry[order.txOrigin][tx.origin]) {
|
||||
LibNativeOrdersRichErrors.OrderNotFillableByOriginError(
|
||||
orderInfo.orderHash,
|
||||
tx.origin,
|
||||
order.txOrigin
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
|
||||
// Must be fillable by the taker.
|
||||
if (order.taker != address(0) && order.taker != taker) {
|
||||
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
|
||||
orderInfo.orderHash,
|
||||
taker,
|
||||
order.taker
|
||||
).rrevert();
|
||||
}
|
||||
|
||||
// Signature must be valid for the order.
|
||||
{
|
||||
address signer = LibSignature.getSignerOfHash(orderInfo.orderHash, signature);
|
||||
if (signer != order.maker) {
|
||||
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
|
||||
orderInfo.orderHash,
|
||||
signer,
|
||||
order.maker
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
|
||||
// Settle between the maker and taker.
|
||||
(results.takerTokenFilledAmount, results.makerTokenFilledAmount) = _settleOrder(
|
||||
SettleOrderInfo({
|
||||
orderHash: orderInfo.orderHash,
|
||||
maker: order.maker,
|
||||
taker: taker,
|
||||
makerToken: IERC20TokenV06(order.makerToken),
|
||||
takerToken: IERC20TokenV06(order.takerToken),
|
||||
makerAmount: order.makerAmount,
|
||||
takerAmount: order.takerAmount,
|
||||
takerTokenFillAmount: takerTokenFillAmount,
|
||||
takerTokenFilledAmount: orderInfo.takerTokenFilledAmount
|
||||
})
|
||||
);
|
||||
|
||||
emit RfqOrderFilled(
|
||||
orderInfo.orderHash,
|
||||
order.maker,
|
||||
taker,
|
||||
address(order.makerToken),
|
||||
address(order.takerToken),
|
||||
results.takerTokenFilledAmount,
|
||||
results.makerTokenFilledAmount,
|
||||
order.pool
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Settle the trade between an order's maker and taker.
|
||||
/// @param settleInfo Information needed to execute the settlement.
|
||||
/// @return takerTokenFilledAmount How much taker token was filled.
|
||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||
function _settleOrder(SettleOrderInfo memory settleInfo)
|
||||
private
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
// Clamp the taker token fill amount to the fillable amount.
|
||||
takerTokenFilledAmount = LibSafeMathV06.min128(
|
||||
settleInfo.takerTokenFillAmount,
|
||||
settleInfo.takerAmount.safeSub128(settleInfo.takerTokenFilledAmount)
|
||||
);
|
||||
// Compute the maker token amount.
|
||||
// This should never overflow because the values are all clamped to
|
||||
// (2^128-1).
|
||||
makerTokenFilledAmount = uint128(LibMathV06.getPartialAmountFloor(
|
||||
uint256(takerTokenFilledAmount),
|
||||
uint256(settleInfo.takerAmount),
|
||||
uint256(settleInfo.makerAmount)
|
||||
));
|
||||
|
||||
if (takerTokenFilledAmount == 0 || makerTokenFilledAmount == 0) {
|
||||
// Nothing to do.
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
// Update filled state for the order.
|
||||
LibNativeOrdersStorage
|
||||
.getStorage()
|
||||
.orderHashToTakerTokenFilledAmount[settleInfo.orderHash] =
|
||||
// OK to overwrite the whole word because we shouldn't get to this
|
||||
// function if the order is cancelled.
|
||||
settleInfo.takerTokenFilledAmount.safeAdd128(takerTokenFilledAmount);
|
||||
|
||||
// Transfer taker -> maker.
|
||||
_transferERC20Tokens(
|
||||
settleInfo.takerToken,
|
||||
settleInfo.taker,
|
||||
settleInfo.maker,
|
||||
takerTokenFilledAmount
|
||||
);
|
||||
|
||||
// Transfer maker -> taker.
|
||||
_transferERC20Tokens(
|
||||
settleInfo.makerToken,
|
||||
settleInfo.maker,
|
||||
settleInfo.taker,
|
||||
makerTokenFilledAmount
|
||||
);
|
||||
}
|
||||
}
|
@@ -23,8 +23,8 @@ pragma experimental ABIEncoderV2;
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "../errors/LibCommonRichErrors.sol";
|
||||
import "../errors/LibOwnableRichErrors.sol";
|
||||
import "../features/IOwnableFeature.sol";
|
||||
import "../features/ISimpleFunctionRegistryFeature.sol";
|
||||
import "../features/interfaces/IOwnableFeature.sol";
|
||||
import "../features/interfaces/ISimpleFunctionRegistryFeature.sol";
|
||||
|
||||
|
||||
/// @dev Common feature utilities.
|
||||
|
@@ -22,7 +22,7 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "../features/ITokenSpenderFeature.sol";
|
||||
import "../features/interfaces/ITokenSpenderFeature.sol";
|
||||
import "../errors/LibSpenderRichErrors.sol";
|
||||
import "../external/FeeCollector.sol";
|
||||
import "../vendor/v3/IStaking.sol";
|
||||
|
@@ -0,0 +1,223 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "../transformers/LibERC20Transformer.sol";
|
||||
import "../vendor/ILiquidityProvider.sol";
|
||||
|
||||
|
||||
contract CurveLiquidityProvider is
|
||||
ILiquidityProvider
|
||||
{
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
struct CurveData {
|
||||
address curveAddress;
|
||||
bytes4 exchangeFunctionSelector;
|
||||
int128 fromCoinIdx;
|
||||
int128 toCoinIdx;
|
||||
}
|
||||
|
||||
/// @dev This contract must be payable because takers can transfer funds
|
||||
/// in prior to calling the swap function.
|
||||
receive() external payable {}
|
||||
|
||||
/// @dev Trades `inputToken` for `outputToken`. The amount of `inputToken`
|
||||
/// to sell must be transferred to the contract prior to calling this
|
||||
/// function to trigger the trade.
|
||||
/// @param inputToken The token being sold.
|
||||
/// @param outputToken The token being bought.
|
||||
/// @param recipient The recipient of the bought tokens.
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function sellTokenForToken(
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
)
|
||||
external
|
||||
override
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
require(
|
||||
!LibERC20Transformer.isTokenETH(inputToken)
|
||||
&& !LibERC20Transformer.isTokenETH(outputToken),
|
||||
"CurveLiquidityProvider/INVALID_ARGS"
|
||||
);
|
||||
boughtAmount = _executeSwap(
|
||||
inputToken,
|
||||
outputToken,
|
||||
minBuyAmount,
|
||||
abi.decode(auxiliaryData, (CurveData)),
|
||||
recipient
|
||||
);
|
||||
// Every pool contract currently checks this but why not.
|
||||
require(boughtAmount >= minBuyAmount, "CurveLiquidityProvider/UNDERBOUGHT");
|
||||
outputToken.compatTransfer(recipient, boughtAmount);
|
||||
}
|
||||
|
||||
/// @dev Trades ETH for token. ETH must either be attached to this function
|
||||
/// call or sent to the contract prior to calling this function to
|
||||
/// trigger the trade.
|
||||
/// @param outputToken The token being bought.
|
||||
/// @param recipient The recipient of the bought tokens.
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function sellEthForToken(
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
)
|
||||
external
|
||||
payable
|
||||
override
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
require(
|
||||
!LibERC20Transformer.isTokenETH(outputToken),
|
||||
"CurveLiquidityProvider/INVALID_ARGS"
|
||||
);
|
||||
boughtAmount = _executeSwap(
|
||||
LibERC20Transformer.ETH_TOKEN,
|
||||
outputToken,
|
||||
minBuyAmount,
|
||||
abi.decode(auxiliaryData, (CurveData)),
|
||||
recipient
|
||||
);
|
||||
// Every pool contract currently checks this but why not.
|
||||
require(boughtAmount >= minBuyAmount, "CurveLiquidityProvider/UNDERBOUGHT");
|
||||
outputToken.compatTransfer(recipient, boughtAmount);
|
||||
}
|
||||
|
||||
/// @dev Trades token for ETH. The token must be sent to the contract prior
|
||||
/// to calling this function to trigger the trade.
|
||||
/// @param inputToken The token being sold.
|
||||
/// @param recipient The recipient of the bought tokens.
|
||||
/// @param minBuyAmount The minimum acceptable amount of ETH to buy.
|
||||
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
|
||||
/// @return boughtAmount The amount of ETH bought.
|
||||
function sellTokenForEth(
|
||||
IERC20TokenV06 inputToken,
|
||||
address payable recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
)
|
||||
external
|
||||
override
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
require(
|
||||
!LibERC20Transformer.isTokenETH(inputToken),
|
||||
"CurveLiquidityProvider/INVALID_ARGS"
|
||||
);
|
||||
boughtAmount = _executeSwap(
|
||||
inputToken,
|
||||
LibERC20Transformer.ETH_TOKEN,
|
||||
minBuyAmount,
|
||||
abi.decode(auxiliaryData, (CurveData)),
|
||||
recipient
|
||||
);
|
||||
// Every pool contract currently checks this but why not.
|
||||
require(boughtAmount >= minBuyAmount, "CurveLiquidityProvider/UNDERBOUGHT");
|
||||
recipient.transfer(boughtAmount);
|
||||
}
|
||||
|
||||
/// @dev Quotes the amount of `outputToken` that would be obtained by
|
||||
/// selling `sellAmount` of `inputToken`.
|
||||
function getSellQuote(
|
||||
IERC20TokenV06 /* inputToken */,
|
||||
IERC20TokenV06 /* outputToken */,
|
||||
uint256 /* sellAmount */
|
||||
)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (uint256)
|
||||
{
|
||||
revert("CurveLiquidityProvider/NOT_IMPLEMENTED");
|
||||
}
|
||||
|
||||
/// @dev Perform the swap against the curve pool. Handles any combination of
|
||||
/// tokens
|
||||
function _executeSwap(
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
uint256 minBuyAmount,
|
||||
CurveData memory data,
|
||||
address recipient // Only used to log event.
|
||||
)
|
||||
private
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
uint256 sellAmount =
|
||||
LibERC20Transformer.getTokenBalanceOf(inputToken, address(this));
|
||||
if (!LibERC20Transformer.isTokenETH(inputToken)) {
|
||||
inputToken.approveIfBelow(data.curveAddress, sellAmount);
|
||||
}
|
||||
|
||||
(bool success, bytes memory resultData) =
|
||||
data.curveAddress.call
|
||||
{ value: LibERC20Transformer.isTokenETH(inputToken) ? sellAmount : 0 }
|
||||
(abi.encodeWithSelector(
|
||||
data.exchangeFunctionSelector,
|
||||
data.fromCoinIdx,
|
||||
data.toCoinIdx,
|
||||
// dx
|
||||
sellAmount,
|
||||
// min dy
|
||||
minBuyAmount
|
||||
));
|
||||
if (!success) {
|
||||
resultData.rrevert();
|
||||
}
|
||||
if (resultData.length == 32) {
|
||||
// Pool returned a boughtAmount
|
||||
boughtAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
// Not all pool contracts return a `boughtAmount`, so we return
|
||||
// our balance of the output token if it wasn't returned.
|
||||
boughtAmount = LibERC20Transformer
|
||||
.getTokenBalanceOf(outputToken, address(this));
|
||||
}
|
||||
|
||||
emit LiquidityProviderFill(
|
||||
inputToken,
|
||||
outputToken,
|
||||
sellAmount,
|
||||
boughtAmount,
|
||||
bytes32("Curve"),
|
||||
address(data.curveAddress),
|
||||
msg.sender,
|
||||
recipient
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,227 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "../transformers/LibERC20Transformer.sol";
|
||||
import "../vendor/ILiquidityProvider.sol";
|
||||
import "../vendor/IMooniswapPool.sol";
|
||||
|
||||
|
||||
contract MooniswapLiquidityProvider is
|
||||
ILiquidityProvider
|
||||
{
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
IEtherTokenV06 private immutable WETH;
|
||||
|
||||
constructor(IEtherTokenV06 weth) public {
|
||||
WETH = weth;
|
||||
}
|
||||
|
||||
/// @dev This contract must be payable because takers can transfer funds
|
||||
/// in prior to calling the swap function.
|
||||
receive() external payable {}
|
||||
|
||||
/// @dev Trades `inputToken` for `outputToken`. The amount of `inputToken`
|
||||
/// to sell must be transferred to the contract prior to calling this
|
||||
/// function to trigger the trade.
|
||||
/// @param inputToken The token being sold.
|
||||
/// @param outputToken The token being bought.
|
||||
/// @param recipient The recipient of the bought tokens.
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function sellTokenForToken(
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
)
|
||||
external
|
||||
override
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
require(
|
||||
!LibERC20Transformer.isTokenETH(inputToken)
|
||||
&& !LibERC20Transformer.isTokenETH(outputToken)
|
||||
&& inputToken != outputToken,
|
||||
"MooniswapLiquidityProvider/INVALID_ARGS"
|
||||
);
|
||||
boughtAmount = _executeSwap(
|
||||
inputToken,
|
||||
outputToken,
|
||||
minBuyAmount,
|
||||
abi.decode(auxiliaryData, (IMooniswapPool)),
|
||||
recipient
|
||||
);
|
||||
outputToken.compatTransfer(recipient, boughtAmount);
|
||||
}
|
||||
|
||||
/// @dev Trades ETH for token. ETH must either be attached to this function
|
||||
/// call or sent to the contract prior to calling this function to
|
||||
/// trigger the trade.
|
||||
/// @param outputToken The token being bought.
|
||||
/// @param recipient The recipient of the bought tokens.
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function sellEthForToken(
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
)
|
||||
external
|
||||
payable
|
||||
override
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
require(
|
||||
!LibERC20Transformer.isTokenETH(outputToken),
|
||||
"MooniswapLiquidityProvider/INVALID_ARGS"
|
||||
);
|
||||
boughtAmount = _executeSwap(
|
||||
LibERC20Transformer.ETH_TOKEN,
|
||||
outputToken,
|
||||
minBuyAmount,
|
||||
abi.decode(auxiliaryData, (IMooniswapPool)),
|
||||
recipient
|
||||
);
|
||||
outputToken.compatTransfer(recipient, boughtAmount);
|
||||
}
|
||||
|
||||
/// @dev Trades token for ETH. The token must be sent to the contract prior
|
||||
/// to calling this function to trigger the trade.
|
||||
/// @param inputToken The token being sold.
|
||||
/// @param recipient The recipient of the bought tokens.
|
||||
/// @param minBuyAmount The minimum acceptable amount of ETH to buy.
|
||||
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
|
||||
/// @return boughtAmount The amount of ETH bought.
|
||||
function sellTokenForEth(
|
||||
IERC20TokenV06 inputToken,
|
||||
address payable recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
)
|
||||
external
|
||||
override
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
require(
|
||||
!LibERC20Transformer.isTokenETH(inputToken),
|
||||
"MooniswapLiquidityProvider/INVALID_ARGS"
|
||||
);
|
||||
boughtAmount = _executeSwap(
|
||||
inputToken,
|
||||
LibERC20Transformer.ETH_TOKEN,
|
||||
minBuyAmount,
|
||||
abi.decode(auxiliaryData, (IMooniswapPool)),
|
||||
recipient
|
||||
);
|
||||
recipient.call{value: boughtAmount}("");
|
||||
}
|
||||
|
||||
/// @dev Quotes the amount of `outputToken` that would be obtained by
|
||||
/// selling `sellAmount` of `inputToken`.
|
||||
function getSellQuote(
|
||||
IERC20TokenV06 /* inputToken */,
|
||||
IERC20TokenV06 /* outputToken */,
|
||||
uint256 /* sellAmount */
|
||||
)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (uint256)
|
||||
{
|
||||
revert("MooniswapLiquidityProvider/NOT_IMPLEMENTED");
|
||||
}
|
||||
|
||||
/// @dev Perform the swap against the curve pool. Handles any combination of
|
||||
/// tokens
|
||||
function _executeSwap(
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
uint256 minBuyAmount,
|
||||
IMooniswapPool pool,
|
||||
address recipient // Only used to log event
|
||||
)
|
||||
private
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
uint256 sellAmount =
|
||||
LibERC20Transformer.getTokenBalanceOf(inputToken, address(this));
|
||||
uint256 ethValue = 0;
|
||||
if (inputToken == WETH) {
|
||||
// Selling WETH. Unwrap to ETH.
|
||||
require(!_isTokenEthLike(outputToken), 'MooniswapLiquidityProvider/ETH_TO_ETH');
|
||||
WETH.withdraw(sellAmount);
|
||||
ethValue = sellAmount;
|
||||
} else if (LibERC20Transformer.isTokenETH(inputToken)) {
|
||||
// Selling ETH directly.
|
||||
ethValue = sellAmount;
|
||||
require(!_isTokenEthLike(outputToken), 'MooniswapLiquidityProvider/ETH_TO_ETH');
|
||||
} else {
|
||||
// Selling a regular ERC20.
|
||||
require(inputToken != outputToken, 'MooniswapLiquidityProvider/SAME_TOKEN');
|
||||
inputToken.approveIfBelow(address(pool), sellAmount);
|
||||
}
|
||||
|
||||
boughtAmount = pool.swap{value: ethValue}(
|
||||
_isTokenEthLike(inputToken) ? IERC20TokenV06(0) : inputToken,
|
||||
_isTokenEthLike(outputToken) ? IERC20TokenV06(0) : outputToken,
|
||||
sellAmount,
|
||||
minBuyAmount,
|
||||
address(0)
|
||||
);
|
||||
|
||||
if (outputToken == WETH) {
|
||||
WETH.deposit{value: boughtAmount}();
|
||||
}
|
||||
|
||||
emit LiquidityProviderFill(
|
||||
inputToken,
|
||||
outputToken,
|
||||
sellAmount,
|
||||
boughtAmount,
|
||||
bytes32("Mooniswap"),
|
||||
address(pool),
|
||||
msg.sender,
|
||||
recipient
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Check if a token is ETH or WETH.
|
||||
function _isTokenEthLike(IERC20TokenV06 token)
|
||||
private
|
||||
view
|
||||
returns (bool isEthOrWeth)
|
||||
{
|
||||
return LibERC20Transformer.isTokenETH(token) || token == WETH;
|
||||
}
|
||||
}
|
@@ -21,7 +21,7 @@ pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../ZeroEx.sol";
|
||||
import "../features/IOwnableFeature.sol";
|
||||
import "../features/interfaces/IOwnableFeature.sol";
|
||||
import "../features/TokenSpenderFeature.sol";
|
||||
import "../features/TransformERC20Feature.sol";
|
||||
import "../features/MetaTransactionsFeature.sol";
|
||||
|
@@ -21,7 +21,7 @@ pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../ZeroEx.sol";
|
||||
import "../features/IBootstrapFeature.sol";
|
||||
import "../features/interfaces/IBootstrapFeature.sol";
|
||||
import "../features/SimpleFunctionRegistryFeature.sol";
|
||||
import "../features/OwnableFeature.sol";
|
||||
import "./LibBootstrap.sol";
|
||||
|
@@ -22,13 +22,12 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||
import "../errors/LibTransformERC20RichErrors.sol";
|
||||
import "../vendor/v3/IExchange.sol";
|
||||
import "../vendor/v3/LibOrderHash.sol";
|
||||
import "../features/interfaces/INativeOrdersFeature.sol";
|
||||
import "../features/libs/LibNativeOrder.sol";
|
||||
import "./bridges/IBridgeAdapter.sol";
|
||||
import "./Transformer.sol";
|
||||
import "./LibERC20Transformer.sol";
|
||||
@@ -41,8 +40,8 @@ contract FillQuoteTransformer is
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
using LibERC20Transformer for IERC20TokenV06;
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibSafeMathV06 for uint128;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
using LibBytesV06 for bytes;
|
||||
|
||||
/// @dev Whether we are performing a market sell or buy.
|
||||
enum Side {
|
||||
@@ -50,6 +49,26 @@ contract FillQuoteTransformer is
|
||||
Buy
|
||||
}
|
||||
|
||||
enum OrderType {
|
||||
Bridge,
|
||||
Limit,
|
||||
Rfq
|
||||
}
|
||||
|
||||
struct LimitOrderInfo {
|
||||
LibNativeOrder.LimitOrder order;
|
||||
LibSignature.Signature signature;
|
||||
// Maximum taker token amount of this limit order to fill.
|
||||
uint256 maxTakerTokenFillAmount;
|
||||
}
|
||||
|
||||
struct RfqOrderInfo {
|
||||
LibNativeOrder.RfqOrder order;
|
||||
LibSignature.Signature signature;
|
||||
// Maximum taker token amount of this limit order to fill.
|
||||
uint256 maxTakerTokenFillAmount;
|
||||
}
|
||||
|
||||
/// @dev Transform data to ABI-encode and pass into `transform()`.
|
||||
struct TransformData {
|
||||
// Whether we are performing a market sell or buy.
|
||||
@@ -60,31 +79,34 @@ contract FillQuoteTransformer is
|
||||
// The token being bought.
|
||||
// This should be an actual token, not the ETH pseudo-token.
|
||||
IERC20TokenV06 buyToken;
|
||||
// The orders to fill.
|
||||
IExchange.Order[] orders;
|
||||
// Signatures for each respective order in `orders`.
|
||||
bytes[] signatures;
|
||||
// Maximum fill amount for each order. This may be shorter than the
|
||||
// number of orders, where missing entries will be treated as `uint256(-1)`.
|
||||
// For sells, this will be the maximum sell amount (taker asset).
|
||||
// For buys, this will be the maximum buy amount (maker asset).
|
||||
uint256[] maxOrderFillAmounts;
|
||||
|
||||
// External liquidity bridge orders. Sorted by fill sequence.
|
||||
IBridgeAdapter.BridgeOrder[] bridgeOrders;
|
||||
// Native limit orders. Sorted by fill sequence.
|
||||
LimitOrderInfo[] limitOrders;
|
||||
// Native RFQ orders. Sorted by fill sequence.
|
||||
RfqOrderInfo[] rfqOrders;
|
||||
|
||||
// The sequence to fill the orders in. Each item will fill the next
|
||||
// order of that type in either `bridgeOrders`, `limitOrders`,
|
||||
// or `rfqOrders.`
|
||||
OrderType[] fillSequence;
|
||||
|
||||
// Amount of `sellToken` to sell or `buyToken` to buy.
|
||||
// For sells, this may be `uint256(-1)` to sell the entire balance of
|
||||
// `sellToken`.
|
||||
// For sells, setting the high-bit indicates that
|
||||
// `sellAmount & LOW_BITS` should be treated as a `1e18` fraction of
|
||||
// the current balance of `sellToken`, where
|
||||
// `1e18+ == 100%` and `0.5e18 == 50%`, etc.
|
||||
uint256 fillAmount;
|
||||
|
||||
// Who to transfer unused protocol fees to.
|
||||
// May be a valid address or one of:
|
||||
// `address(0)`: Stay in flash wallet.
|
||||
// `address(1)`: Send to the taker.
|
||||
// `address(2)`: Send to the sender (caller of `transformERC20()`).
|
||||
address payable refundReceiver;
|
||||
// Required taker address for RFQT orders.
|
||||
// Null means any taker can fill it.
|
||||
address rfqtTakerAddress;
|
||||
}
|
||||
|
||||
/// @dev Results of a call to `_fillOrder()`.
|
||||
struct FillOrderResults {
|
||||
// The amount of taker tokens sold, according to balance checks.
|
||||
uint256 takerTokenSoldAmount;
|
||||
@@ -101,7 +123,8 @@ contract FillQuoteTransformer is
|
||||
uint256 soldAmount;
|
||||
uint256 protocolFee;
|
||||
uint256 takerTokenBalanceRemaining;
|
||||
bool isRfqtAllowed;
|
||||
uint256[3] currentIndices;
|
||||
OrderType currentOrderType;
|
||||
}
|
||||
|
||||
/// @dev Emitted when a trade is skipped due to a lack of funds
|
||||
@@ -109,12 +132,10 @@ contract FillQuoteTransformer is
|
||||
/// @param orderHash The hash of the order that was skipped.
|
||||
event ProtocolFeeUnfunded(bytes32 orderHash);
|
||||
|
||||
/// @dev The Exchange ERC20Proxy ID.
|
||||
bytes4 private constant ERC20_ASSET_PROXY_ID = 0xf47261b0;
|
||||
/// @dev The Exchange ERC20BridgeProxy ID.
|
||||
bytes4 private constant ERC20_BRIDGE_PROXY_ID = 0xdc1600f3;
|
||||
/// @dev Maximum uint256 value.
|
||||
uint256 private constant MAX_UINT256 = uint256(-1);
|
||||
/// @dev The highest bit of a uint256 value.
|
||||
uint256 private constant HIGH_BIT = 2 ** 255;
|
||||
/// @dev Mask of the lower 255 bits of a uint256 value.
|
||||
uint256 private constant LOWER_255_BITS = HIGH_BIT - 1;
|
||||
/// @dev If `refundReceiver` is set to this address, unpsent
|
||||
/// protocol fees will be sent to the taker.
|
||||
address private constant REFUND_RECEIVER_TAKER = address(1);
|
||||
@@ -122,33 +143,32 @@ contract FillQuoteTransformer is
|
||||
/// protocol fees will be sent to the sender.
|
||||
address private constant REFUND_RECEIVER_SENDER = address(2);
|
||||
|
||||
/// @dev The Exchange contract.
|
||||
IExchange public immutable exchange;
|
||||
/// @dev The ERC20Proxy address.
|
||||
address public immutable erc20Proxy;
|
||||
/// @dev The BridgeAdapter address
|
||||
IBridgeAdapter public immutable bridgeAdapter;
|
||||
|
||||
/// @dev The exchange proxy contract.
|
||||
INativeOrdersFeature public immutable zeroEx;
|
||||
|
||||
/// @dev Create this contract.
|
||||
/// @param exchange_ The Exchange V3 instance.
|
||||
constructor(IExchange exchange_, IBridgeAdapter bridgeAdapter_)
|
||||
/// @param bridgeAdapter_ The bridge adapter contract.
|
||||
/// @param zeroEx_ The Exchange Proxy contract.
|
||||
constructor(IBridgeAdapter bridgeAdapter_, INativeOrdersFeature zeroEx_)
|
||||
public
|
||||
Transformer()
|
||||
{
|
||||
exchange = exchange_;
|
||||
erc20Proxy = exchange_.getAssetProxy(ERC20_ASSET_PROXY_ID);
|
||||
bridgeAdapter = bridgeAdapter_;
|
||||
zeroEx = zeroEx_;
|
||||
}
|
||||
|
||||
/// @dev Sell this contract's entire balance of of `sellToken` in exchange
|
||||
/// for `buyToken` by filling `orders`. Protocol fees should be attached
|
||||
/// to this call. `buyToken` and excess ETH will be transferred back to the caller.
|
||||
/// @param context Context information.
|
||||
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
|
||||
/// @return magicBytes The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
|
||||
function transform(TransformContext calldata context)
|
||||
external
|
||||
override
|
||||
returns (bytes4 success)
|
||||
returns (bytes4 magicBytes)
|
||||
{
|
||||
TransformData memory data = abi.decode(context.data, (TransformData));
|
||||
FillState memory state;
|
||||
@@ -160,7 +180,11 @@ contract FillQuoteTransformer is
|
||||
context.data
|
||||
).rrevert();
|
||||
}
|
||||
if (data.orders.length != data.signatures.length) {
|
||||
|
||||
if (data.bridgeOrders.length
|
||||
+ data.limitOrders.length
|
||||
+ data.rfqOrders.length != data.fillSequence.length
|
||||
) {
|
||||
LibTransformERC20RichErrors.InvalidTransformDataError(
|
||||
LibTransformERC20RichErrors.InvalidTransformDataErrorCode.INVALID_ARRAY_LENGTH,
|
||||
context.data
|
||||
@@ -168,76 +192,58 @@ contract FillQuoteTransformer is
|
||||
}
|
||||
|
||||
state.takerTokenBalanceRemaining = data.sellToken.getTokenBalanceOf(address(this));
|
||||
if (data.side == Side.Sell && data.fillAmount == MAX_UINT256) {
|
||||
// If `sellAmount == -1 then we are selling
|
||||
// the entire balance of `sellToken`. This is useful in cases where
|
||||
// the exact sell amount is not exactly known in advance, like when
|
||||
// unwrapping Chai/cUSDC/cDAI.
|
||||
data.fillAmount = state.takerTokenBalanceRemaining;
|
||||
if (data.side == Side.Sell) {
|
||||
data.fillAmount = _normalizeFillAmount(data.fillAmount, state.takerTokenBalanceRemaining);
|
||||
}
|
||||
|
||||
// Approve the ERC20 proxy to spend `sellToken`.
|
||||
data.sellToken.approveIfBelow(erc20Proxy, data.fillAmount);
|
||||
// Approve the exchange proxy to spend our sell tokens if native orders
|
||||
// are present.
|
||||
if (data.limitOrders.length + data.rfqOrders.length != 0) {
|
||||
data.sellToken.approveIfBelow(address(zeroEx), data.fillAmount);
|
||||
// Compute the protocol fee if a limit order is present.
|
||||
if (data.limitOrders.length != 0) {
|
||||
state.protocolFee = uint256(zeroEx.getProtocolFeeMultiplier())
|
||||
.safeMul(tx.gasprice);
|
||||
}
|
||||
}
|
||||
|
||||
state.protocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice);
|
||||
state.ethRemaining = address(this).balance;
|
||||
// RFQT orders can only be filled if the actual taker matches the RFQT
|
||||
// taker (if set).
|
||||
state.isRfqtAllowed = data.rfqtTakerAddress == address(0)
|
||||
|| context.taker == data.rfqtTakerAddress;
|
||||
|
||||
// Fill the orders.
|
||||
for (uint256 i = 0; i < data.orders.length; ++i) {
|
||||
for (uint256 i = 0; i < data.fillSequence.length; ++i) {
|
||||
// Check if we've hit our targets.
|
||||
if (data.side == Side.Sell) {
|
||||
// Market sell check.
|
||||
if (state.soldAmount >= data.fillAmount) {
|
||||
break;
|
||||
}
|
||||
if (state.soldAmount >= data.fillAmount) { break; }
|
||||
} else {
|
||||
// Market buy check.
|
||||
if (state.boughtAmount >= data.fillAmount) {
|
||||
break;
|
||||
}
|
||||
if (state.boughtAmount >= data.fillAmount) { break; }
|
||||
}
|
||||
|
||||
state.currentOrderType = OrderType(data.fillSequence[i]);
|
||||
uint256 orderIndex = state.currentIndices[uint256(state.currentOrderType)];
|
||||
// Fill the order.
|
||||
FillOrderResults memory results;
|
||||
if (data.side == Side.Sell) {
|
||||
// Market sell.
|
||||
results = _sellToOrder(
|
||||
data.buyToken,
|
||||
data.sellToken,
|
||||
data.orders[i],
|
||||
data.signatures[i],
|
||||
data.fillAmount.safeSub(state.soldAmount).min256(
|
||||
data.maxOrderFillAmounts.length > i
|
||||
? data.maxOrderFillAmounts[i]
|
||||
: MAX_UINT256
|
||||
),
|
||||
state
|
||||
);
|
||||
if (state.currentOrderType == OrderType.Bridge) {
|
||||
results = _fillBridgeOrder(data.bridgeOrders[orderIndex], data, state);
|
||||
} else if (state.currentOrderType == OrderType.Limit) {
|
||||
results = _fillLimitOrder(data.limitOrders[orderIndex], data, state);
|
||||
} else if (state.currentOrderType == OrderType.Rfq) {
|
||||
results = _fillRfqOrder(data.rfqOrders[orderIndex], data, state);
|
||||
} else {
|
||||
// Market buy.
|
||||
results = _buyFromOrder(
|
||||
data.buyToken,
|
||||
data.sellToken,
|
||||
data.orders[i],
|
||||
data.signatures[i],
|
||||
data.fillAmount.safeSub(state.boughtAmount).min256(
|
||||
data.maxOrderFillAmounts.length > i
|
||||
? data.maxOrderFillAmounts[i]
|
||||
: MAX_UINT256
|
||||
),
|
||||
state
|
||||
);
|
||||
revert("INVALID_ORDER_TYPE");
|
||||
}
|
||||
|
||||
// Accumulate totals.
|
||||
state.soldAmount = state.soldAmount.safeAdd(results.takerTokenSoldAmount);
|
||||
state.boughtAmount = state.boughtAmount.safeAdd(results.makerTokenBoughtAmount);
|
||||
state.ethRemaining = state.ethRemaining.safeSub(results.protocolFeePaid);
|
||||
state.takerTokenBalanceRemaining = state.takerTokenBalanceRemaining.safeSub(results.takerTokenSoldAmount);
|
||||
state.soldAmount = state.soldAmount
|
||||
.safeAdd(results.takerTokenSoldAmount);
|
||||
state.boughtAmount = state.boughtAmount
|
||||
.safeAdd(results.makerTokenBoughtAmount);
|
||||
state.ethRemaining = state.ethRemaining
|
||||
.safeSub(results.protocolFeePaid);
|
||||
state.takerTokenBalanceRemaining = state.takerTokenBalanceRemaining
|
||||
.safeSub(results.takerTokenSoldAmount);
|
||||
state.currentIndices[uint256(state.currentOrderType)]++;
|
||||
}
|
||||
|
||||
// Ensure we hit our targets.
|
||||
@@ -265,234 +271,187 @@ contract FillQuoteTransformer is
|
||||
|
||||
// Refund unspent protocol fees.
|
||||
if (state.ethRemaining > 0 && data.refundReceiver != address(0)) {
|
||||
bool transferSuccess;
|
||||
if (data.refundReceiver == REFUND_RECEIVER_TAKER) {
|
||||
context.taker.transfer(state.ethRemaining);
|
||||
(transferSuccess,) = context.taker.call{value: state.ethRemaining}("");
|
||||
} else if (data.refundReceiver == REFUND_RECEIVER_SENDER) {
|
||||
context.sender.transfer(state.ethRemaining);
|
||||
(transferSuccess,) = context.sender.call{value: state.ethRemaining}("");
|
||||
} else {
|
||||
data.refundReceiver.transfer(state.ethRemaining);
|
||||
(transferSuccess,) = data.refundReceiver.call{value: state.ethRemaining}("");
|
||||
}
|
||||
require(transferSuccess, "FillQuoteTransformer/ETHER_TRANSFER_FALIED");
|
||||
}
|
||||
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||
}
|
||||
|
||||
/// @dev Try to sell up to `sellAmount` from an order.
|
||||
/// @param makerToken The maker/buy token.
|
||||
/// @param takerToken The taker/sell token.
|
||||
/// @param order The order to fill.
|
||||
/// @param signature The signature for `order`.
|
||||
/// @param sellAmount Amount of taker token to sell.
|
||||
/// @param state Intermediate state variables to get around stack limits.
|
||||
function _sellToOrder(
|
||||
IERC20TokenV06 makerToken,
|
||||
IERC20TokenV06 takerToken,
|
||||
IExchange.Order memory order,
|
||||
bytes memory signature,
|
||||
uint256 sellAmount,
|
||||
// Fill a single bridge order.
|
||||
function _fillBridgeOrder(
|
||||
IBridgeAdapter.BridgeOrder memory order,
|
||||
TransformData memory data,
|
||||
FillState memory state
|
||||
)
|
||||
private
|
||||
returns (FillOrderResults memory results)
|
||||
{
|
||||
IERC20TokenV06 takerFeeToken =
|
||||
_getTokenFromERC20AssetData(order.takerFeeAssetData);
|
||||
|
||||
uint256 takerTokenFillAmount = sellAmount;
|
||||
|
||||
if (order.takerFee != 0) {
|
||||
if (takerFeeToken == makerToken) {
|
||||
// Taker fee is payable in the maker token, so we need to
|
||||
// approve the proxy to spend the maker token.
|
||||
// It isn't worth computing the actual taker fee
|
||||
// since `approveIfBelow()` will set the allowance to infinite. We
|
||||
// just need a reasonable upper bound to avoid unnecessarily re-approving.
|
||||
takerFeeToken.approveIfBelow(erc20Proxy, order.takerFee);
|
||||
} else if (takerFeeToken == takerToken){
|
||||
// Taker fee is payable in the taker token, so we need to
|
||||
// reduce the fill amount to cover the fee.
|
||||
// takerTokenFillAmount' =
|
||||
// (takerTokenFillAmount * order.takerAssetAmount) /
|
||||
// (order.takerAssetAmount + order.takerFee)
|
||||
takerTokenFillAmount = LibMathV06.getPartialAmountCeil(
|
||||
order.takerAssetAmount,
|
||||
order.takerAssetAmount.safeAdd(order.takerFee),
|
||||
sellAmount
|
||||
);
|
||||
} else {
|
||||
// Only support taker or maker asset denominated taker fees.
|
||||
LibTransformERC20RichErrors.InvalidTakerFeeTokenError(
|
||||
address(takerFeeToken)
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the fill.
|
||||
return _fillOrder(
|
||||
order,
|
||||
signature,
|
||||
takerTokenFillAmount,
|
||||
uint256 takerTokenFillAmount = _computeTakerTokenFillAmount(
|
||||
data,
|
||||
state,
|
||||
takerFeeToken == takerToken
|
||||
order.takerTokenAmount,
|
||||
order.makerTokenAmount,
|
||||
0
|
||||
);
|
||||
|
||||
(bool success, bytes memory resultData) = address(bridgeAdapter).delegatecall(
|
||||
abi.encodeWithSelector(
|
||||
IBridgeAdapter.trade.selector,
|
||||
order,
|
||||
data.sellToken,
|
||||
data.buyToken,
|
||||
takerTokenFillAmount
|
||||
)
|
||||
);
|
||||
if (success) {
|
||||
results.makerTokenBoughtAmount = abi.decode(resultData, (uint256));
|
||||
results.takerTokenSoldAmount = takerTokenFillAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Try to buy up to `buyAmount` from an order.
|
||||
/// @param makerToken The maker/buy token.
|
||||
/// @param takerToken The taker/sell token.
|
||||
/// @param order The order to fill.
|
||||
/// @param signature The signature for `order`.
|
||||
/// @param buyAmount Amount of maker token to buy.
|
||||
/// @param state Intermediate state variables to get around stack limits.
|
||||
function _buyFromOrder(
|
||||
IERC20TokenV06 makerToken,
|
||||
IERC20TokenV06 takerToken,
|
||||
IExchange.Order memory order,
|
||||
bytes memory signature,
|
||||
uint256 buyAmount,
|
||||
// Fill a single limit order.
|
||||
function _fillLimitOrder(
|
||||
LimitOrderInfo memory orderInfo,
|
||||
TransformData memory data,
|
||||
FillState memory state
|
||||
)
|
||||
private
|
||||
returns (FillOrderResults memory results)
|
||||
{
|
||||
IERC20TokenV06 takerFeeToken =
|
||||
_getTokenFromERC20AssetData(order.takerFeeAssetData);
|
||||
// Compute the default taker token fill amount.
|
||||
uint256 takerTokenFillAmount = LibMathV06.getPartialAmountCeil(
|
||||
buyAmount,
|
||||
order.makerAssetAmount,
|
||||
order.takerAssetAmount
|
||||
uint256 takerTokenFillAmount = LibSafeMathV06.min256(
|
||||
_computeTakerTokenFillAmount(
|
||||
data,
|
||||
state,
|
||||
orderInfo.order.takerAmount,
|
||||
orderInfo.order.makerAmount,
|
||||
orderInfo.order.takerTokenFeeAmount
|
||||
),
|
||||
orderInfo.maxTakerTokenFillAmount
|
||||
);
|
||||
|
||||
if (order.takerFee != 0) {
|
||||
if (takerFeeToken == makerToken) {
|
||||
// Taker fee is payable in the maker token.
|
||||
// Adjust the taker token fill amount to account for maker
|
||||
// tokens being lost to the taker fee.
|
||||
// takerTokenFillAmount' =
|
||||
// (order.takerAssetAmount * buyAmount) /
|
||||
// (order.makerAssetAmount - order.takerFee)
|
||||
takerTokenFillAmount = LibMathV06.getPartialAmountCeil(
|
||||
buyAmount,
|
||||
order.makerAssetAmount.safeSub(order.takerFee),
|
||||
order.takerAssetAmount
|
||||
);
|
||||
// Approve the proxy to spend the maker token.
|
||||
// It isn't worth computing the actual taker fee
|
||||
// since `approveIfBelow()` will set the allowance to infinite. We
|
||||
// just need a reasonable upper bound to avoid unnecessarily re-approving.
|
||||
takerFeeToken.approveIfBelow(erc20Proxy, order.takerFee);
|
||||
} else if (takerFeeToken != takerToken) {
|
||||
// Only support taker or maker asset denominated taker fees.
|
||||
LibTransformERC20RichErrors.InvalidTakerFeeTokenError(
|
||||
address(takerFeeToken)
|
||||
).rrevert();
|
||||
}
|
||||
// Emit an event if we do not have sufficient ETH to cover the protocol fee.
|
||||
if (state.ethRemaining < state.protocolFee) {
|
||||
bytes32 orderHash = zeroEx.getLimitOrderHash(orderInfo.order);
|
||||
emit ProtocolFeeUnfunded(orderHash);
|
||||
return results; // Empty results.
|
||||
}
|
||||
|
||||
// Perform the fill.
|
||||
return _fillOrder(
|
||||
order,
|
||||
signature,
|
||||
takerTokenFillAmount,
|
||||
state,
|
||||
takerFeeToken == takerToken
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Attempt to fill an order. If the fill reverts, the revert will be
|
||||
/// swallowed and `results` will be zeroed out.
|
||||
/// @param order The order to fill.
|
||||
/// @param signature The order signature.
|
||||
/// @param takerAssetFillAmount How much taker asset to fill.
|
||||
/// @param state Intermediate state variables to get around stack limits.
|
||||
/// @param isTakerFeeInTakerToken Whether the taker fee token is the same as the
|
||||
/// taker token.
|
||||
function _fillOrder(
|
||||
IExchange.Order memory order,
|
||||
bytes memory signature,
|
||||
uint256 takerAssetFillAmount,
|
||||
FillState memory state,
|
||||
bool isTakerFeeInTakerToken
|
||||
)
|
||||
private
|
||||
returns (FillOrderResults memory results)
|
||||
{
|
||||
// Clamp to remaining taker asset amount or order size.
|
||||
uint256 availableTakerAssetFillAmount =
|
||||
takerAssetFillAmount.min256(order.takerAssetAmount);
|
||||
availableTakerAssetFillAmount =
|
||||
availableTakerAssetFillAmount.min256(state.takerTokenBalanceRemaining);
|
||||
|
||||
// If it is a Bridge order we fill this directly through the BridgeAdapter
|
||||
if (order.makerAssetData.readBytes4(0) == ERC20_BRIDGE_PROXY_ID) {
|
||||
(bool success, bytes memory resultData) = address(bridgeAdapter).delegatecall(
|
||||
abi.encodeWithSelector(
|
||||
IBridgeAdapter.trade.selector,
|
||||
order.makerAssetData,
|
||||
address(_getTokenFromERC20AssetData(order.takerAssetData)),
|
||||
availableTakerAssetFillAmount
|
||||
try
|
||||
zeroEx.fillLimitOrder
|
||||
{value: state.protocolFee}
|
||||
(
|
||||
orderInfo.order,
|
||||
orderInfo.signature,
|
||||
takerTokenFillAmount.safeDowncastToUint128()
|
||||
)
|
||||
);
|
||||
if (success) {
|
||||
results.makerTokenBoughtAmount = abi.decode(resultData, (uint256));
|
||||
results.takerTokenSoldAmount = availableTakerAssetFillAmount;
|
||||
// protocol fee paid remains 0
|
||||
}
|
||||
return results;
|
||||
} else {
|
||||
// If the order taker address is set to this contract's address then
|
||||
// this is an RFQT order, and we will only fill it if allowed to.
|
||||
if (order.takerAddress == address(this) && !state.isRfqtAllowed) {
|
||||
return results; // Empty results.
|
||||
}
|
||||
// Emit an event if we do not have sufficient ETH to cover the protocol fee.
|
||||
if (state.ethRemaining < state.protocolFee) {
|
||||
bytes32 orderHash = LibOrderHash.getTypedDataHash(
|
||||
order,
|
||||
exchange.EIP712_EXCHANGE_DOMAIN_HASH()
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
if (orderInfo.order.takerTokenFeeAmount > 0) {
|
||||
takerTokenFilledAmount = takerTokenFilledAmount.safeAdd128(
|
||||
LibMathV06.getPartialAmountFloor(
|
||||
takerTokenFilledAmount,
|
||||
orderInfo.order.takerAmount,
|
||||
orderInfo.order.takerTokenFeeAmount
|
||||
).safeDowncastToUint128()
|
||||
);
|
||||
emit ProtocolFeeUnfunded(orderHash);
|
||||
return results;
|
||||
}
|
||||
try
|
||||
exchange.fillOrder
|
||||
{value: state.protocolFee}
|
||||
(order, availableTakerAssetFillAmount, signature)
|
||||
returns (IExchange.FillResults memory fillResults)
|
||||
{
|
||||
results.makerTokenBoughtAmount = fillResults.makerAssetFilledAmount;
|
||||
results.takerTokenSoldAmount = fillResults.takerAssetFilledAmount;
|
||||
results.protocolFeePaid = fillResults.protocolFeePaid;
|
||||
// If the taker fee is payable in the taker asset, include the
|
||||
// taker fee in the total amount sold.
|
||||
if (isTakerFeeInTakerToken) {
|
||||
results.takerTokenSoldAmount =
|
||||
results.takerTokenSoldAmount.safeAdd(fillResults.takerFeePaid);
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
}
|
||||
}
|
||||
results.takerTokenSoldAmount = takerTokenFilledAmount;
|
||||
results.makerTokenBoughtAmount = makerTokenFilledAmount;
|
||||
results.protocolFeePaid = state.protocolFee;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/// @dev Extract the token from plain ERC20 asset data.
|
||||
/// If the asset-data is empty, a zero token address will be returned.
|
||||
/// @param assetData The order asset data.
|
||||
function _getTokenFromERC20AssetData(bytes memory assetData)
|
||||
// Fill a single RFQ order.
|
||||
function _fillRfqOrder(
|
||||
RfqOrderInfo memory orderInfo,
|
||||
TransformData memory data,
|
||||
FillState memory state
|
||||
)
|
||||
private
|
||||
returns (FillOrderResults memory results)
|
||||
{
|
||||
uint256 takerTokenFillAmount = LibSafeMathV06.min256(
|
||||
_computeTakerTokenFillAmount(
|
||||
data,
|
||||
state,
|
||||
orderInfo.order.takerAmount,
|
||||
orderInfo.order.makerAmount,
|
||||
0
|
||||
),
|
||||
orderInfo.maxTakerTokenFillAmount
|
||||
);
|
||||
|
||||
try
|
||||
zeroEx.fillRfqOrder
|
||||
(
|
||||
orderInfo.order,
|
||||
orderInfo.signature,
|
||||
takerTokenFillAmount.safeDowncastToUint128()
|
||||
)
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
results.takerTokenSoldAmount = takerTokenFilledAmount;
|
||||
results.makerTokenBoughtAmount = makerTokenFilledAmount;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Compute the next taker token fill amount of a generic order.
|
||||
function _computeTakerTokenFillAmount(
|
||||
TransformData memory data,
|
||||
FillState memory state,
|
||||
uint256 orderTakerAmount,
|
||||
uint256 orderMakerAmount,
|
||||
uint256 orderTakerTokenFeeAmount
|
||||
)
|
||||
private
|
||||
pure
|
||||
returns (IERC20TokenV06 token)
|
||||
returns (uint256 takerTokenFillAmount)
|
||||
{
|
||||
if (assetData.length == 0) {
|
||||
return IERC20TokenV06(address(0));
|
||||
if (data.side == Side.Sell) {
|
||||
takerTokenFillAmount = data.fillAmount.safeSub(state.soldAmount);
|
||||
if (orderTakerTokenFeeAmount != 0) {
|
||||
takerTokenFillAmount = LibMathV06.getPartialAmountCeil(
|
||||
takerTokenFillAmount,
|
||||
orderTakerAmount.safeAdd(orderTakerTokenFeeAmount),
|
||||
orderTakerAmount
|
||||
);
|
||||
}
|
||||
} else { // Buy
|
||||
takerTokenFillAmount = LibMathV06.getPartialAmountCeil(
|
||||
data.fillAmount.safeSub(state.boughtAmount),
|
||||
orderMakerAmount,
|
||||
orderTakerAmount
|
||||
);
|
||||
}
|
||||
if (assetData.length != 36 ||
|
||||
LibBytesV06.readBytes4(assetData, 0) != ERC20_ASSET_PROXY_ID)
|
||||
{
|
||||
LibTransformERC20RichErrors
|
||||
.InvalidERC20AssetDataError(assetData)
|
||||
.rrevert();
|
||||
return LibSafeMathV06.min256(
|
||||
LibSafeMathV06.min256(takerTokenFillAmount, orderTakerAmount),
|
||||
state.takerTokenBalanceRemaining
|
||||
);
|
||||
}
|
||||
|
||||
// Convert possible proportional values to absolute quantities.
|
||||
function _normalizeFillAmount(uint256 rawAmount, uint256 balance)
|
||||
private
|
||||
pure
|
||||
returns (uint256 normalized)
|
||||
{
|
||||
if ((rawAmount & HIGH_BIT) == HIGH_BIT) {
|
||||
// If the high bit of `rawAmount` is set then the lower 255 bits
|
||||
// specify a fraction of `balance`.
|
||||
return LibSafeMathV06.min256(
|
||||
balance
|
||||
* LibSafeMathV06.min256(rawAmount & LOWER_255_BITS, 1e18)
|
||||
/ 1e18,
|
||||
balance
|
||||
);
|
||||
}
|
||||
return IERC20TokenV06(LibBytesV06.readAddress(assetData, 16));
|
||||
return rawAmount;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 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.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "../errors/LibTransformERC20RichErrors.sol";
|
||||
import "./Transformer.sol";
|
||||
import "./LibERC20Transformer.sol";
|
||||
|
||||
|
||||
/// @dev A transformer that transfers tokens to arbitrary addresses.
|
||||
contract PositiveSlippageFeeTransformer is
|
||||
Transformer
|
||||
{
|
||||
using LibRichErrorsV06 for bytes;
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibERC20Transformer for IERC20TokenV06;
|
||||
|
||||
/// @dev Information for a single fee.
|
||||
struct TokenFee {
|
||||
// The token to transfer to `recipient`.
|
||||
IERC20TokenV06 token;
|
||||
// Amount of each `token` to transfer to `recipient`.
|
||||
uint256 bestCaseAmount;
|
||||
// Recipient of `token`.
|
||||
address payable recipient;
|
||||
}
|
||||
|
||||
/// @dev Transfers tokens to recipients.
|
||||
/// @param context Context information.
|
||||
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
|
||||
function transform(TransformContext calldata context)
|
||||
external
|
||||
override
|
||||
returns (bytes4 success)
|
||||
{
|
||||
TokenFee memory fee = abi.decode(context.data, (TokenFee));
|
||||
|
||||
uint256 transformerAmount = LibERC20Transformer.getTokenBalanceOf(fee.token, address(this));
|
||||
if (transformerAmount > fee.bestCaseAmount) {
|
||||
uint256 positiveSlippageAmount = transformerAmount - fee.bestCaseAmount;
|
||||
fee.token.transformerTransfer(fee.recipient, positiveSlippageAmount);
|
||||
}
|
||||
|
||||
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user