Compare commits

..

6 Commits

Author SHA1 Message Date
Jacob Evans
30835d664c boost 2021-07-03 18:08:47 +10:00
Jacob Evans
7972c2ce4e Fix buys exit early 2021-06-25 19:43:04 +10:00
Jacob Evans
173d4ce648 CHANGELOGs 2021-06-25 18:52:34 +10:00
Jacob Evans
59542f0585 Fix test which should be gas price adjusted 2021-06-25 18:06:56 +10:00
Jacob Evans
446ef9660e Rebased off development 2021-06-25 17:57:26 +10:00
Jacob Evans
9de1f0263a Swap Sampler
- Uses the settlement function (via Mixin) to perform sampling
- Removed the Gas schedule as gasUsed is recorded when sampling
- Remove most function selectors for Curve pools (only exchange is required)
- Allow banning of routes if they often return 0
- Introduce CurveV2Sampler required for SwapRevert sampler
2021-06-25 17:57:26 +10:00
219 changed files with 5805 additions and 10673 deletions

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "3.7.19",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "3.7.18",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "3.7.17",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "3.7.16",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v3.7.19 - _August 16, 2021_
* Dependencies updated
## v3.7.18 - _August 11, 2021_
* Dependencies updated
## v3.7.17 - _August 6, 2021_
* Dependencies updated
## v3.7.16 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-asset-proxy",
"version": "3.7.19",
"version": "3.7.16",
"engines": {
"node": ">=6.12"
},
@@ -52,10 +52,10 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/protocol",
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contract-wrappers": "^13.17.4",
"@0x/contract-wrappers": "^13.17.2",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/contracts-utils": "^4.7.13",
"@0x/dev-utils": "^4.2.7",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
@@ -80,11 +80,11 @@
},
"dependencies": {
"@0x/base-contract": "^6.4.0",
"@0x/contracts-erc1155": "^2.1.37",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contracts-erc721": "^3.1.37",
"@0x/contracts-exchange-libs": "^4.3.37",
"@0x/order-utils": "^10.4.28",
"@0x/contracts-erc1155": "^2.1.34",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-erc721": "^3.1.34",
"@0x/contracts-exchange-libs": "^4.3.34",
"@0x/order-utils": "^10.4.26",
"@0x/types": "^3.3.3",
"@0x/typescript-typings": "^5.2.0",
"@0x/utils": "^6.4.3",

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "1.1.37",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "1.1.36",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "1.1.35",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "1.1.34",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.1.37 - _August 16, 2021_
* Dependencies updated
## v1.1.36 - _August 11, 2021_
* Dependencies updated
## v1.1.35 - _August 6, 2021_
* Dependencies updated
## v1.1.34 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-broker",
"version": "1.1.37",
"version": "1.1.34",
"engines": {
"node": ">=6.12"
},
@@ -52,14 +52,14 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/extensions",
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contracts-erc721": "^3.1.37",
"@0x/contracts-exchange": "^3.2.38",
"@0x/contracts-exchange-libs": "^4.3.37",
"@0x/contracts-asset-proxy": "^3.7.16",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-erc721": "^3.1.34",
"@0x/contracts-exchange": "^3.2.35",
"@0x/contracts-exchange-libs": "^4.3.34",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/contracts-utils": "^4.7.13",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
"@0x/tslint-config": "^4.1.4",
@@ -85,7 +85,7 @@
},
"dependencies": {
"@0x/base-contract": "^6.4.0",
"@0x/order-utils": "^10.4.28",
"@0x/order-utils": "^10.4.26",
"@0x/typescript-typings": "^5.2.0",
"@0x/utils": "^6.4.3",
"ethereum-types": "^3.5.0"

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "3.1.38",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "3.1.37",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "3.1.36",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "3.1.35",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v3.1.38 - _August 16, 2021_
* Dependencies updated
## v3.1.37 - _August 11, 2021_
* Dependencies updated
## v3.1.36 - _August 6, 2021_
* Dependencies updated
## v3.1.35 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-coordinator",
"version": "3.1.38",
"version": "3.1.35",
"engines": {
"node": ">=6.12"
},
@@ -53,12 +53,12 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/extensions",
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-dev-utils": "^1.3.36",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contracts-asset-proxy": "^3.7.16",
"@0x/contracts-dev-utils": "^1.3.33",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-gen": "^2.0.38",
"@0x/dev-utils": "^4.2.7",
"@0x/order-utils": "^10.4.28",
"@0x/order-utils": "^10.4.26",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
"@0x/tslint-config": "^4.1.4",
@@ -84,10 +84,10 @@
"dependencies": {
"@0x/assert": "^3.0.27",
"@0x/base-contract": "^6.4.0",
"@0x/contract-addresses": "^6.6.0",
"@0x/contracts-exchange": "^3.2.38",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-utils": "^4.7.16",
"@0x/contract-addresses": "^6.4.0",
"@0x/contracts-exchange": "^3.2.35",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/contracts-utils": "^4.7.13",
"@0x/json-schemas": "^6.1.3",
"@0x/types": "^3.3.3",
"@0x/typescript-typings": "^5.2.0",

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "1.3.36",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "1.3.35",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "1.3.34",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "1.3.33",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.3.36 - _August 16, 2021_
* Dependencies updated
## v1.3.35 - _August 11, 2021_
* Dependencies updated
## v1.3.34 - _August 6, 2021_
* Dependencies updated
## v1.3.33 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-dev-utils",
"version": "1.3.36",
"version": "1.3.33",
"engines": {
"node": ">=6.12"
},
@@ -43,10 +43,10 @@
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/assert": "^3.0.27",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contracts-asset-proxy": "^3.7.16",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
"@0x/tslint-config": "^4.1.4",

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "2.1.37",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "2.1.36",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "2.1.35",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "2.1.34",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v2.1.37 - _August 16, 2021_
* Dependencies updated
## v2.1.36 - _August 11, 2021_
* Dependencies updated
## v2.1.35 - _August 6, 2021_
* Dependencies updated
## v2.1.34 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-erc1155",
"version": "2.1.37",
"version": "2.1.34",
"engines": {
"node": ">=6.12"
},
@@ -54,7 +54,7 @@
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-utils": "^4.7.13",
"@0x/dev-utils": "^4.2.7",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
@@ -81,7 +81,7 @@
},
"dependencies": {
"@0x/base-contract": "^6.4.0",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/utils": "^6.4.3",
"@0x/web3-wrapper": "^7.5.3",
"lodash": "^4.17.11"

View File

@@ -1,32 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "3.3.16",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "3.3.15",
"changes": [
{
"note": "Add ethers as a dependency",
"pr": 305
}
],
"timestamp": 1628665757
},
{
"timestamp": 1628225642,
"version": "3.3.14",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "3.3.13",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v3.3.16 - _August 16, 2021_
* Dependencies updated
## v3.3.15 - _August 11, 2021_
* Add ethers as a dependency (#305)
## v3.3.14 - _August 6, 2021_
* Dependencies updated
## v3.3.13 - _June 22, 2021_
* Dependencies updated

View File

@@ -28,7 +28,7 @@ library LibERC20TokenV06 {
bytes constant private DECIMALS_CALL_DATA = hex"313ce567";
/// @dev Calls `IERC20TokenV06(token).approve()`.
/// Reverts if the return data is invalid or the call reverts.
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
/// @param token The address of the token contract.
/// @param spender The address that receives an allowance.
/// @param allowance The allowance to set.
@@ -49,7 +49,7 @@ library LibERC20TokenV06 {
/// @dev Calls `IERC20TokenV06(token).approve()` and sets the allowance to the
/// maximum if the current approval is not already >= an amount.
/// Reverts if the return data is invalid or the call reverts.
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
/// @param token The address of the token contract.
/// @param spender The address that receives an allowance.
/// @param amount The minimum allowance needed.
@@ -66,7 +66,7 @@ library LibERC20TokenV06 {
}
/// @dev Calls `IERC20TokenV06(token).transfer()`.
/// Reverts if the return data is invalid or the call reverts.
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
/// @param token The address of the token contract.
/// @param to The address that receives the tokens
/// @param amount Number of tokens to transfer.
@@ -86,7 +86,7 @@ library LibERC20TokenV06 {
}
/// @dev Calls `IERC20TokenV06(token).transferFrom()`.
/// Reverts if the return data is invalid or the call reverts.
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
/// @param token The address of the token contract.
/// @param from The owner of the tokens.
/// @param to The address that receives the tokens
@@ -168,6 +168,27 @@ library LibERC20TokenV06 {
}
}
/// @dev Check if the data returned by a non-static call to an ERC20 token
/// is a successful result. Supported functions are `transfer()`,
/// `transferFrom()`, and `approve()`.
/// @param resultData The raw data returned by a non-static call to the ERC20 token.
/// @return isSuccessful Whether the result data indicates success.
function isSuccessfulResult(bytes memory resultData)
internal
pure
returns (bool isSuccessful)
{
if (resultData.length == 0) {
return true;
}
if (resultData.length >= 32) {
uint256 result = LibBytesV06.readUint256(resultData, 0);
if (result == 1) {
return true;
}
}
}
/// @dev Executes a call on address `target` with calldata `callData`
/// and asserts that either nothing was returned or a single boolean
/// was returned equal to `true`.
@@ -180,31 +201,9 @@ library LibERC20TokenV06 {
private
{
(bool didSucceed, bytes memory resultData) = target.call(callData);
// Revert if the call reverted.
if (!didSucceed) {
LibRichErrorsV06.rrevert(resultData);
}
// If we get back 0 returndata, this may be a non-standard ERC-20 that
// does not return a boolean. Check that it at least contains code.
if (resultData.length == 0) {
uint256 size;
assembly { size := extcodesize(target) }
require(size > 0, "invalid token address, contains no code");
if (didSucceed && isSuccessfulResult(resultData)) {
return;
}
// If we get back at least 32 bytes, we know the target address
// contains code, and we assume it is a token that returned a boolean
// success value, which must be true.
if (resultData.length >= 32) {
uint256 result = LibBytesV06.readUint256(resultData, 0);
if (result == 1) {
return;
} else {
LibRichErrorsV06.rrevert(resultData);
}
}
// If 0 < returndatasize < 32, the target is a contract, but not a
// valid token.
LibRichErrorsV06.rrevert(resultData);
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-erc20",
"version": "3.3.16",
"version": "3.3.13",
"engines": {
"node": ">=6.12"
},
@@ -53,8 +53,8 @@
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/contracts-utils": "^4.7.13",
"@0x/dev-utils": "^4.2.7",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
@@ -82,8 +82,7 @@
"typescript": "4.2.2"
},
"dependencies": {
"@0x/base-contract": "^6.4.0",
"ethers": "~4.0.4"
"@0x/base-contract": "^6.4.0"
},
"publishConfig": {
"access": "public"

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "3.1.37",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "3.1.36",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "3.1.35",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "3.1.34",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v3.1.37 - _August 16, 2021_
* Dependencies updated
## v3.1.36 - _August 11, 2021_
* Dependencies updated
## v3.1.35 - _August 6, 2021_
* Dependencies updated
## v3.1.34 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-erc721",
"version": "3.1.37",
"version": "3.1.34",
"engines": {
"node": ">=6.12"
},
@@ -54,8 +54,8 @@
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/contracts-utils": "^4.7.13",
"@0x/dev-utils": "^4.2.7",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "4.2.38",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "4.2.37",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "4.2.36",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "4.2.35",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v4.2.38 - _August 16, 2021_
* Dependencies updated
## v4.2.37 - _August 11, 2021_
* Dependencies updated
## v4.2.36 - _August 6, 2021_
* Dependencies updated
## v4.2.35 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-exchange-forwarder",
"version": "4.2.38",
"version": "4.2.35",
"engines": {
"node": ">=6.12"
},
@@ -53,18 +53,18 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/extensions",
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-dev-utils": "^1.3.36",
"@0x/contracts-erc1155": "^2.1.37",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contracts-erc721": "^3.1.37",
"@0x/contracts-exchange": "^3.2.38",
"@0x/contracts-exchange-libs": "^4.3.37",
"@0x/contracts-asset-proxy": "^3.7.16",
"@0x/contracts-dev-utils": "^1.3.33",
"@0x/contracts-erc1155": "^2.1.34",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-erc721": "^3.1.34",
"@0x/contracts-exchange": "^3.2.35",
"@0x/contracts-exchange-libs": "^4.3.34",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/contracts-utils": "^4.7.13",
"@0x/dev-utils": "^4.2.7",
"@0x/order-utils": "^10.4.28",
"@0x/order-utils": "^10.4.26",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
"@0x/tslint-config": "^4.1.4",

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "4.3.37",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "4.3.36",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "4.3.35",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "4.3.34",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v4.3.37 - _August 16, 2021_
* Dependencies updated
## v4.3.36 - _August 11, 2021_
* Dependencies updated
## v4.3.35 - _August 6, 2021_
* Dependencies updated
## v4.3.34 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-exchange-libs",
"version": "4.3.37",
"version": "4.3.34",
"engines": {
"node": ">=6.12"
},
@@ -81,9 +81,9 @@
},
"dependencies": {
"@0x/base-contract": "^6.4.0",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-utils": "^4.7.16",
"@0x/order-utils": "^10.4.28",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/contracts-utils": "^4.7.13",
"@0x/order-utils": "^10.4.26",
"@0x/types": "^3.3.3",
"@0x/typescript-typings": "^5.2.0",
"@0x/utils": "^6.4.3",

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "3.2.38",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "3.2.37",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "3.2.36",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "3.2.35",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v3.2.38 - _August 16, 2021_
* Dependencies updated
## v3.2.37 - _August 11, 2021_
* Dependencies updated
## v3.2.36 - _August 6, 2021_
* Dependencies updated
## v3.2.35 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-exchange",
"version": "3.2.38",
"version": "3.2.35",
"engines": {
"node": ">=6.12"
},
@@ -53,13 +53,13 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/protocol",
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-exchange-libs": "^4.3.37",
"@0x/contracts-asset-proxy": "^3.7.16",
"@0x/contracts-exchange-libs": "^4.3.34",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-multisig": "^4.1.38",
"@0x/contracts-staking": "^2.0.45",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-multisig": "^4.1.35",
"@0x/contracts-staking": "^2.0.42",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/contracts-utils": "^4.7.13",
"@0x/dev-utils": "^4.2.7",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
@@ -89,11 +89,11 @@
},
"dependencies": {
"@0x/base-contract": "^6.4.0",
"@0x/contracts-dev-utils": "^1.3.36",
"@0x/contracts-erc1155": "^2.1.37",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contracts-erc721": "^3.1.37",
"@0x/order-utils": "^10.4.28",
"@0x/contracts-dev-utils": "^1.3.33",
"@0x/contracts-erc1155": "^2.1.34",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-erc721": "^3.1.34",
"@0x/order-utils": "^10.4.26",
"@0x/utils": "^6.4.3",
"lodash": "^4.17.11"
},

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "6.2.32",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "6.2.31",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "6.2.30",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "6.2.29",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v6.2.32 - _August 16, 2021_
* Dependencies updated
## v6.2.31 - _August 11, 2021_
* Dependencies updated
## v6.2.30 - _August 6, 2021_
* Dependencies updated
## v6.2.29 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-extensions",
"version": "6.2.32",
"version": "6.2.29",
"engines": {
"node": ">=6.12"
},
@@ -53,16 +53,16 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/extensions",
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-dev-utils": "^1.3.36",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contracts-erc721": "^3.1.37",
"@0x/contracts-exchange": "^3.2.38",
"@0x/contracts-exchange-libs": "^4.3.37",
"@0x/contracts-asset-proxy": "^3.7.16",
"@0x/contracts-dev-utils": "^1.3.33",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-erc721": "^3.1.34",
"@0x/contracts-exchange": "^3.2.35",
"@0x/contracts-exchange-libs": "^4.3.34",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-utils": "^4.7.13",
"@0x/dev-utils": "^4.2.7",
"@0x/order-utils": "^10.4.28",
"@0x/order-utils": "^10.4.26",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
"@0x/tslint-config": "^4.1.4",
@@ -91,7 +91,7 @@
},
"dependencies": {
"@0x/base-contract": "^6.4.0",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/typescript-typings": "^5.2.0",
"ethereum-types": "^3.5.0"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-integrations",
"version": "2.7.64",
"version": "2.7.53",
"private": true,
"engines": {
"node": ">=6.12"
@@ -53,21 +53,21 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/extensions",
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contract-addresses": "^6.6.0",
"@0x/contract-wrappers": "^13.17.4",
"@0x/contracts-broker": "^1.1.37",
"@0x/contracts-coordinator": "^3.1.38",
"@0x/contracts-dev-utils": "^1.3.36",
"@0x/contracts-exchange-forwarder": "^4.2.38",
"@0x/contracts-exchange-libs": "^4.3.37",
"@0x/contracts-extensions": "^6.2.32",
"@0x/contract-addresses": "^6.4.0",
"@0x/contract-wrappers": "^13.17.2",
"@0x/contracts-broker": "^1.1.34",
"@0x/contracts-coordinator": "^3.1.35",
"@0x/contracts-dev-utils": "^1.3.33",
"@0x/contracts-exchange-forwarder": "^4.2.35",
"@0x/contracts-exchange-libs": "^4.3.34",
"@0x/contracts-extensions": "^6.2.29",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-utils": "^4.7.13",
"@0x/coordinator-server": "^1.0.5",
"@0x/dev-utils": "^4.2.7",
"@0x/migrations": "^8.1.1",
"@0x/order-utils": "^10.4.28",
"@0x/protocol-utils": "^1.8.2",
"@0x/migrations": "^8.0.11",
"@0x/order-utils": "^10.4.26",
"@0x/protocol-utils": "^1.7.2",
"@0x/sol-compiler": "^4.7.3",
"@0x/tslint-config": "^4.1.4",
"@0x/web3-wrapper": "^7.5.3",
@@ -93,17 +93,17 @@
"typescript": "4.2.2"
},
"dependencies": {
"@0x/asset-swapper": "^16.25.0",
"@0x/asset-swapper": "^6.18.2",
"@0x/base-contract": "^6.4.0",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-erc1155": "^2.1.37",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contracts-erc721": "^3.1.37",
"@0x/contracts-exchange": "^3.2.38",
"@0x/contracts-multisig": "^4.1.38",
"@0x/contracts-staking": "^2.0.45",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-zero-ex": "^0.28.0",
"@0x/contracts-asset-proxy": "^3.7.16",
"@0x/contracts-erc1155": "^2.1.34",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-erc721": "^3.1.34",
"@0x/contracts-exchange": "^3.2.35",
"@0x/contracts-multisig": "^4.1.35",
"@0x/contracts-staking": "^2.0.42",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/contracts-zero-ex": "^0.26.0",
"@0x/subproviders": "^6.5.3",
"@0x/types": "^3.3.3",
"@0x/typescript-typings": "^5.2.0",

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "4.1.38",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "4.1.37",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "4.1.36",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "4.1.35",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v4.1.38 - _August 16, 2021_
* Dependencies updated
## v4.1.37 - _August 11, 2021_
* Dependencies updated
## v4.1.36 - _August 6, 2021_
* Dependencies updated
## v4.1.35 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-multisig",
"version": "4.1.38",
"version": "4.1.35",
"engines": {
"node": ">=6.12"
},
@@ -50,11 +50,11 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/multisig",
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contracts-asset-proxy": "^3.7.16",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/contracts-utils": "^4.7.13",
"@0x/dev-utils": "^4.2.7",
"@0x/sol-compiler": "^4.7.3",
"@0x/tslint-config": "^4.1.4",

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "2.0.45",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "2.0.44",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "2.0.43",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "2.0.42",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v2.0.45 - _August 16, 2021_
* Dependencies updated
## v2.0.44 - _August 11, 2021_
* Dependencies updated
## v2.0.43 - _August 6, 2021_
* Dependencies updated
## v2.0.42 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-staking",
"version": "2.0.45",
"version": "2.0.42",
"engines": {
"node": ">=6.12"
},
@@ -54,14 +54,14 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/tokens",
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-dev-utils": "^1.3.36",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contracts-exchange-libs": "^4.3.37",
"@0x/contracts-asset-proxy": "^3.7.16",
"@0x/contracts-dev-utils": "^1.3.33",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-exchange-libs": "^4.3.34",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-utils": "^4.7.16",
"@0x/contracts-utils": "^4.7.13",
"@0x/dev-utils": "^4.2.7",
"@0x/order-utils": "^10.4.28",
"@0x/order-utils": "^10.4.26",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
"@0x/tslint-config": "^4.1.4",
@@ -88,7 +88,7 @@
},
"dependencies": {
"@0x/base-contract": "^6.4.0",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/typescript-typings": "^5.2.0",
"@0x/utils": "^6.4.3",
"ethereum-types": "^3.5.0",

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "5.4.8",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "5.4.7",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "5.4.6",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "5.4.5",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v5.4.8 - _August 16, 2021_
* Dependencies updated
## v5.4.7 - _August 11, 2021_
* Dependencies updated
## v5.4.6 - _August 6, 2021_
* Dependencies updated
## v5.4.5 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-test-utils",
"version": "5.4.8",
"version": "5.4.5",
"engines": {
"node": ">=6.12"
},
@@ -44,10 +44,10 @@
"dependencies": {
"@0x/assert": "^3.0.27",
"@0x/base-contract": "^6.4.0",
"@0x/contract-addresses": "^6.6.0",
"@0x/contract-addresses": "^6.4.0",
"@0x/dev-utils": "^4.2.7",
"@0x/json-schemas": "^6.1.3",
"@0x/order-utils": "^10.4.28",
"@0x/order-utils": "^10.4.26",
"@0x/sol-coverage": "^4.0.37",
"@0x/sol-profiler": "^4.1.27",
"@0x/sol-trace": "^3.0.37",

View File

@@ -40,6 +40,6 @@ export function verifyEventsFromLogs<TEventArgs>(
const _logs = filterLogsToArguments<TEventArgs>(logs, eventName);
expect(_logs.length, `Number of ${eventName} events emitted`).to.eq(expectedEvents.length);
_logs.forEach((log, index) => {
expect(log, `${eventName} event ${index}`).to.deep.equal({ ...log, ...expectedEvents[index] });
expect(log, `${eventName} event ${index}`).to.deep.equal(expectedEvents[index]);
});
}

View File

@@ -1,32 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "1.3.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "1.3.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "1.3.0",
"changes": [
{
"note": "Added proposal 1 params and test",
"pr": 298
}
],
"timestamp": 1628225642
},
{
"timestamp": 1624356181,
"version": "1.2.3",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.3.2 - _August 16, 2021_
* Dependencies updated
## v1.3.1 - _August 11, 2021_
* Dependencies updated
## v1.3.0 - _August 6, 2021_
* Added proposal 1 params and test (#298)
## v1.2.3 - _June 22, 2021_
* Dependencies updated

View File

@@ -21,10 +21,13 @@ pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "./IStaking.sol";
contract DefaultPoolOperator {
using LibERC20TokenV06 for IERC20TokenV06;
// Immutables
IStaking public immutable stakingProxy;
IERC20TokenV06 public immutable weth;
@@ -54,7 +57,7 @@ contract DefaultPoolOperator {
function returnStakingRewards()
external
{
uint256 wethBalance = weth.balanceOf(address(this));
weth.transfer(address(stakingProxy), wethBalance);
uint256 wethBalance = weth.compatBalanceOf(address(this));
weth.compatTransfer(address(stakingProxy), wethBalance);
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-treasury",
"version": "1.3.2",
"version": "1.2.3",
"engines": {
"node": ">=6.12"
},
@@ -47,12 +47,12 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/treasury",
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contract-addresses": "^6.6.0",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-erc20": "^3.3.16",
"@0x/contract-addresses": "^6.4.0",
"@0x/contracts-asset-proxy": "^3.7.16",
"@0x/contracts-erc20": "^3.3.13",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-staking": "^2.0.45",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-staking": "^2.0.42",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/sol-compiler": "^4.7.3",
"@0x/ts-doc-gen": "^0.0.28",
"@0x/tslint-config": "^4.1.4",
@@ -73,7 +73,7 @@
},
"dependencies": {
"@0x/base-contract": "^6.4.0",
"@0x/protocol-utils": "^1.8.2",
"@0x/protocol-utils": "^1.7.2",
"@0x/subproviders": "^6.5.3",
"@0x/types": "^3.3.3",
"@0x/typescript-typings": "^5.2.0",

File diff suppressed because one or more lines are too long

View File

@@ -151,72 +151,4 @@ blockchainTests.fork.skip('Treasury proposal mainnet fork tests', env => {
);
});
});
describe('Proposal 1', () => {
it('works', async () => {
const proposal = proposals[1];
let executionEpoch: BigNumber;
if (proposal.executionEpoch) {
executionEpoch = proposal.executionEpoch;
} else {
const currentEpoch = await staking.currentEpoch().callAsync();
executionEpoch = currentEpoch.plus(2);
}
const pools = await querySubgraphAsync(PROPOSER);
const proposeTx = treasury.propose(proposal.actions, executionEpoch, proposal.description, pools);
const calldata = proposeTx.getABIEncodedTransactionData();
logUtils.log('ZrxTreasury.propose calldata:');
logUtils.log(calldata);
const proposalId = await proposeTx.callAsync({ from: PROPOSER });
const receipt = await proposeTx.awaitTransactionSuccessAsync({ from: PROPOSER });
verifyEventsFromLogs(
receipt.logs,
[
{
...proposal,
proposalId,
executionEpoch,
proposer: PROPOSER,
operatedPoolIds: pools,
},
],
ZrxTreasuryEvents.ProposalCreated,
);
await fastForwardToNextEpochAsync();
await fastForwardToNextEpochAsync();
await treasury
.castVote(proposalId, true, VOTER_OPERATED_POOLS)
.awaitTransactionSuccessAsync({ from: VOTER });
await env.web3Wrapper.increaseTimeAsync(votingPeriod.plus(1).toNumber());
await env.web3Wrapper.mineBlockAsync();
const executeTx = await treasury.execute(proposalId, proposal.actions).awaitTransactionSuccessAsync();
verifyEventsFromLogs(
executeTx.logs,
[
{
proposalId,
},
],
ZrxTreasuryEvents.ProposalExecuted,
);
const recipient = '0xab66cc8fd10457ebc9d13b9760c835f0a4cbc487';
verifyEventsFromLogs(
executeTx.logs,
[
{
_from: TREASURY_ADDRESS,
_to: recipient,
_value: new BigNumber(330_813).times('1e18'),
},
{
_from: TREASURY_ADDRESS,
_to: recipient,
_value: new BigNumber(420000).times('1e18'),
},
],
ERC20TokenEvents.Transfer,
);
});
});
});

View File

@@ -1,31 +1,4 @@
[
{
"timestamp": 1629079369,
"version": "4.7.16",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628665757,
"version": "4.7.15",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1628225642,
"version": "4.7.14",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1624356181,
"version": "4.7.13",

View File

@@ -5,18 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v4.7.16 - _August 16, 2021_
* Dependencies updated
## v4.7.15 - _August 11, 2021_
* Dependencies updated
## v4.7.14 - _August 6, 2021_
* Dependencies updated
## v4.7.13 - _June 22, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-utils",
"version": "4.7.16",
"version": "4.7.13",
"engines": {
"node": ">=6.12"
},
@@ -52,9 +52,9 @@
"devDependencies": {
"@0x/abi-gen": "^5.6.0",
"@0x/contracts-gen": "^2.0.38",
"@0x/contracts-test-utils": "^5.4.8",
"@0x/contracts-test-utils": "^5.4.5",
"@0x/dev-utils": "^4.2.7",
"@0x/order-utils": "^10.4.28",
"@0x/order-utils": "^10.4.26",
"@0x/sol-compiler": "^4.7.3",
"@0x/tslint-config": "^4.1.4",
"@0x/types": "^3.3.3",

View File

@@ -1,43 +1,12 @@
[
{
"version": "0.28.0",
"changes": [
{
"note": "Transfer output tokens in TransformERC20Feature",
"pr": 279
},
{
"note": "Add support for takerToken=0xeee... in OtcOrdersFeature",
"pr": 287
},
{
"note": "Add support for OTC orders in MultiplexFeature",
"pr": 287
},
{
"note": "Multiplex v2: Refactor into multiple files, add ETH support, and other miscellanea",
"pr": 263
}
],
"timestamp": 1629079369
},
{
"timestamp": 1628665757,
"version": "0.27.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "0.27.0",
"changes": [
{
"note": "Add `Clipper` as a custom liquidity source"
"note": "Refactor Mixins which use WETH to also have an Internal variant, allowing WETH to be passed in for SwapRevertSampler",
"pr": 245
}
],
"timestamp": 1628225642
]
},
{
"version": "0.26.0",

View File

@@ -5,21 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v0.28.0 - _August 16, 2021_
* Transfer output tokens in TransformERC20Feature (#279)
* Add support for takerToken=0xeee... in OtcOrdersFeature (#287)
* Add support for OTC orders in MultiplexFeature (#287)
* Multiplex v2: Refactor into multiple files, add ETH support, and other miscellanea (#263)
## v0.27.1 - _August 11, 2021_
* Dependencies updated
## v0.27.0 - _August 6, 2021_
* Add `Clipper` as a custom liquidity source
## v0.26.0 - _June 22, 2021_
* Add Lido stETH deposit integration (#260)

View File

@@ -88,6 +88,23 @@ library LibNativeOrdersRichErrors {
);
}
function OrderNotSignedByTakerError(
bytes32 orderHash,
address signer,
address taker
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("OrderNotSignedByTakerError(bytes32,address,address)")),
orderHash,
signer,
taker
);
}
function InvalidSignerError(
address maker,
address signer

View File

@@ -48,7 +48,7 @@ contract BatchFillNativeOrdersFeature is
/// @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, 1, 0);
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
constructor(address zeroExAddress)
public
@@ -170,8 +170,6 @@ contract BatchFillNativeOrdersFeature is
orders[i],
signatures[i],
takerTokenFillAmounts[i],
msg.sender,
false,
msg.sender
)
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)

View File

@@ -78,7 +78,7 @@ contract MetaTransactionsFeature is
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "MetaTransactions";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0);
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 1);
/// @dev EIP712 typehash of the `MetaTransactionData` struct.
bytes32 public immutable MTX_EIP712_TYPEHASH = keccak256(
"MetaTransactionData("
@@ -415,9 +415,7 @@ contract MetaTransactionsFeature is
outputToken: args.outputToken,
inputTokenAmount: args.inputTokenAmount,
minOutputTokenAmount: args.minOutputTokenAmount,
transformations: args.transformations,
useSelfBalance: false,
recipient: state.mtx.signer
transformations: args.transformations
})
),
state.mtx.value
@@ -500,9 +498,7 @@ contract MetaTransactionsFeature is
order,
signature,
takerTokenFillAmount,
state.mtx.signer, // taker is mtx signer
false,
state.mtx.signer
state.mtx.signer // taker is mtx signer
),
state.mtx.value
);

View File

@@ -0,0 +1,820 @@
// 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 "./interfaces/IUniswapV3Feature.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, 1, 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_
)
public
FixinEIP712(zeroExAddress)
{
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 == IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector) {
(bool success, bytes memory resultData) = address(this).delegatecall(
abi.encodeWithSelector(
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
wrappedCall.data,
inputTokenAmount,
0,
msg.sender
)
);
if (success) {
uint256 outputTokenAmount_ = abi.decode(resultData, (uint256));
// 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.
_transferERC20TokensFrom(
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 {
_transferERC20TokensFrom(
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.
_transferERC20TokensFrom(
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);
_transferERC20TokensFrom(
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
))));
}
}
}

View File

@@ -34,7 +34,7 @@ contract NativeOrdersFeature is
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "LimitOrders";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 3, 0);
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0);
constructor(
address zeroExAddress,

View File

@@ -47,12 +47,21 @@ contract OtcOrdersFeature is
using LibSafeMathV06 for uint256;
using LibSafeMathV06 for uint128;
/// @dev Options for handling ETH/WETH conversion
/// @param LeaveAsWeth Neither unwrap nor wrap.
/// @param WrapEth Wrap attached ETH.
/// @param UnwrapWeth Unwrap WETH before transferring
/// to taker.
enum WethOptions {
LeaveAsWeth,
WrapEth,
UnwrapWeth
}
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "OtcOrders";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
/// @dev ETH pseudo-token address.
address constant private ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @dev The WETH token contract.
IEtherTokenV06 private immutable WETH;
@@ -71,12 +80,8 @@ contract OtcOrdersFeature is
returns (bytes4 success)
{
_registerFeatureFunction(this.fillOtcOrder.selector);
_registerFeatureFunction(this.fillOtcOrderForEth.selector);
_registerFeatureFunction(this.fillOtcOrderWithEth.selector);
_registerFeatureFunction(this.fillTakerSignedOtcOrderForEth.selector);
_registerFeatureFunction(this.fillTakerSignedOtcOrder.selector);
_registerFeatureFunction(this.batchFillTakerSignedOtcOrders.selector);
_registerFeatureFunction(this._fillOtcOrder.selector);
_registerFeatureFunction(this.getOtcOrderInfo.selector);
_registerFeatureFunction(this.getOtcOrderHash.selector);
_registerFeatureFunction(this.lastOtcTxOriginNonce.selector);
@@ -88,90 +93,36 @@ contract OtcOrdersFeature is
/// @param makerSignature The order signature from the maker.
/// @param takerTokenFillAmount Maximum taker token amount to fill this
/// order with.
/// @param unwrapWeth Whether or not to unwrap bought WETH into ETH
/// before transferring it to the taker. Should be set to false
/// if the maker token is not WETH.
/// @return takerTokenFilledAmount How much taker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function fillOtcOrder(
LibNativeOrder.OtcOrder memory order,
LibSignature.Signature memory makerSignature,
uint128 takerTokenFillAmount
uint128 takerTokenFillAmount,
bool unwrapWeth
)
public
override
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
_validateOtcOrder(
order,
orderInfo,
makerSignature,
msg.sender
);
(takerTokenFilledAmount, makerTokenFilledAmount) = _settleOtcOrder(
if (!_isSenderValidTaker(order.taker)) {
bytes32 orderHash = getOtcOrderHash(order);
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
orderHash,
msg.sender,
order.taker
).rrevert();
}
LibSignature.Signature memory nullSignature;
return _fillOtcOrderPrivate(
order,
makerSignature,
nullSignature,
takerTokenFillAmount,
msg.sender,
msg.sender
);
emit OtcOrderFilled(
orderInfo.orderHash,
order.maker,
msg.sender,
address(order.makerToken),
address(order.takerToken),
makerTokenFilledAmount,
takerTokenFilledAmount
);
}
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
/// Unwraps bought WETH into ETH. before sending it to
/// the taker.
/// @param order The OTC order.
/// @param makerSignature The order signature from the maker.
/// @param takerTokenFillAmount Maximum taker token amount to fill this
/// order with.
/// @return takerTokenFilledAmount How much taker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function fillOtcOrderForEth(
LibNativeOrder.OtcOrder memory order,
LibSignature.Signature memory makerSignature,
uint128 takerTokenFillAmount
)
public
override
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
require(
order.makerToken == WETH,
"OtcOrdersFeature::fillOtcOrderForEth/MAKER_TOKEN_NOT_WETH"
);
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
_validateOtcOrder(
order,
orderInfo,
makerSignature,
msg.sender
);
(takerTokenFilledAmount, makerTokenFilledAmount) = _settleOtcOrder(
order,
takerTokenFillAmount,
msg.sender,
address(this)
);
// Unwrap WETH
WETH.withdraw(makerTokenFilledAmount);
// Transfer ETH to taker
_transferEth(msg.sender, makerTokenFilledAmount);
emit OtcOrderFilled(
orderInfo.orderHash,
order.maker,
msg.sender,
address(order.makerToken),
address(order.takerToken),
makerTokenFilledAmount,
takerTokenFilledAmount
unwrapWeth ? WethOptions.UnwrapWeth : WethOptions.LeaveAsWeth
);
}
@@ -190,47 +141,21 @@ contract OtcOrdersFeature is
payable
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
if (order.takerToken == WETH) {
// Wrap ETH
WETH.deposit{value: msg.value}();
} else {
require(
address(order.takerToken) == ETH_TOKEN_ADDRESS,
"OtcOrdersFeature::fillOtcOrderWithEth/INVALID_TAKER_TOKEN"
);
if (!_isSenderValidTaker(order.taker)) {
bytes32 orderHash = getOtcOrderHash(order);
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
orderHash,
msg.sender,
order.taker
).rrevert();
}
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
_validateOtcOrder(
order,
orderInfo,
makerSignature,
msg.sender
);
(takerTokenFilledAmount, makerTokenFilledAmount) = _settleOtcOrder(
LibSignature.Signature memory nullSignature;
return _fillOtcOrderPrivate(
order,
makerSignature,
nullSignature,
msg.value.safeDowncastToUint128(),
address(this),
msg.sender
);
if (takerTokenFilledAmount < msg.value) {
uint256 refundAmount = msg.value - uint256(takerTokenFilledAmount);
if (order.takerToken == WETH) {
WETH.withdraw(refundAmount);
}
// Refund unused ETH
_transferEth(msg.sender, refundAmount);
}
emit OtcOrderFilled(
orderInfo.orderHash,
order.maker,
msg.sender,
address(order.makerToken),
address(order.takerToken),
makerTokenFilledAmount,
takerTokenFilledAmount
WethOptions.WrapEth
);
}
@@ -239,198 +164,51 @@ contract OtcOrdersFeature is
/// @param order The OTC order.
/// @param makerSignature The order signature from the maker.
/// @param takerSignature The order signature from the taker.
/// @param unwrapWeth Whether or not to unwrap bought WETH into ETH
/// before transferring it to the taker. Should be set to false
/// if the maker token is not WETH.
/// @return takerTokenFilledAmount How much taker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function fillTakerSignedOtcOrder(
LibNativeOrder.OtcOrder memory order,
LibSignature.Signature memory makerSignature,
LibSignature.Signature memory takerSignature
LibSignature.Signature memory takerSignature,
bool unwrapWeth
)
public
override
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
address taker = LibSignature.getSignerOfHash(orderInfo.orderHash, takerSignature);
_validateOtcOrder(
order,
orderInfo,
makerSignature,
taker
);
_settleOtcOrder(
return _fillOtcOrderPrivate(
order,
makerSignature,
takerSignature,
order.takerAmount,
taker,
taker
);
emit OtcOrderFilled(
orderInfo.orderHash,
order.maker,
taker,
address(order.makerToken),
address(order.takerToken),
order.makerAmount,
order.takerAmount
unwrapWeth ? WethOptions.UnwrapWeth : WethOptions.LeaveAsWeth
);
}
/// @dev Fully fill an OTC order. "Meta-transaction" variant,
/// requires order to be signed by both maker and taker.
/// Unwraps bought WETH into ETH. before sending it to
/// the taker.
/// @dev Fill an OTC order. Private variant.
/// @param order The OTC order.
/// @param makerSignature The order signature from the maker.
/// @param takerSignature The order signature from the taker.
function fillTakerSignedOtcOrderForEth(
LibNativeOrder.OtcOrder memory order,
LibSignature.Signature memory makerSignature,
LibSignature.Signature memory takerSignature
)
public
override
{
require(
order.makerToken == WETH,
"OtcOrdersFeature::fillTakerSignedOtcOrder/MAKER_TOKEN_NOT_WETH"
);
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
address taker = LibSignature.getSignerOfHash(orderInfo.orderHash, takerSignature);
_validateOtcOrder(
order,
orderInfo,
makerSignature,
taker
);
_settleOtcOrder(
order,
order.takerAmount,
taker,
address(this)
);
// Unwrap WETH
WETH.withdraw(order.makerAmount);
// Transfer ETH to taker
_transferEth(taker, order.makerAmount);
emit OtcOrderFilled(
orderInfo.orderHash,
order.maker,
taker,
address(order.makerToken),
address(order.takerToken),
order.makerAmount,
order.takerAmount
);
}
/// @dev Fills multiple taker-signed OTC orders.
/// @param orders Array of OTC orders.
/// @param makerSignatures Array of maker signatures for each order.
/// @param takerSignatures Array of taker signatures for each order.
/// @param unwrapWeth Array of booleans representing whether or not
/// to unwrap bought WETH into ETH for each order. Should be set
/// to false if the maker token is not WETH.
/// @return successes Array of booleans representing whether or not
/// each order in `orders` was filled successfully.
function batchFillTakerSignedOtcOrders(
LibNativeOrder.OtcOrder[] memory orders,
LibSignature.Signature[] memory makerSignatures,
LibSignature.Signature[] memory takerSignatures,
bool[] memory unwrapWeth
)
public
override
returns (bool[] memory successes)
{
require(
orders.length == makerSignatures.length &&
orders.length == takerSignatures.length &&
orders.length == unwrapWeth.length,
"OtcOrdersFeature::batchFillTakerSignedOtcOrders/MISMATCHED_ARRAY_LENGTHS"
);
successes = new bool[](orders.length);
for (uint256 i = 0; i != orders.length; i++) {
bytes4 fnSelector = unwrapWeth[i]
? this.fillTakerSignedOtcOrderForEth.selector
: this.fillTakerSignedOtcOrder.selector;
// Swallow reverts
(successes[i], ) = _implementation.delegatecall(
abi.encodeWithSelector(
fnSelector,
orders[i],
makerSignatures[i],
takerSignatures[i]
)
);
}
}
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
/// Internal variant.
/// @param order The OTC order.
/// @param makerSignature The order signature from the maker.
/// @param takerTokenFillAmount Maximum taker token amount to fill this
/// order with.
/// @param taker The address to fill the order in the context of.
/// @param useSelfBalance Whether to use the Exchange Proxy's balance
/// of input tokens.
/// @param recipient The recipient of the bought maker tokens.
/// Ignored if msg.sender == order.taker.
/// @param takerTokenFillAmount Maximum taker token amount to
/// fill this order with.
/// @return takerTokenFilledAmount How much taker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function _fillOtcOrder(
function _fillOtcOrderPrivate(
LibNativeOrder.OtcOrder memory order,
LibSignature.Signature memory makerSignature,
LibSignature.Signature memory takerSignature,
uint128 takerTokenFillAmount,
address taker,
bool useSelfBalance,
address recipient
WethOptions wethOptions
)
public
override
onlySelf
private
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
_validateOtcOrder(
order,
orderInfo,
makerSignature,
taker
);
(takerTokenFilledAmount, makerTokenFilledAmount) = _settleOtcOrder(
order,
takerTokenFillAmount,
useSelfBalance ? address(this) : taker,
recipient
);
emit OtcOrderFilled(
orderInfo.orderHash,
order.maker,
taker,
address(order.makerToken),
address(order.takerToken),
makerTokenFilledAmount,
takerTokenFilledAmount
);
}
/// @dev Validates an OTC order, reverting if the order cannot be
/// filled by the given taker.
/// @param order The OTC order.
/// @param orderInfo Info on the order.
/// @param makerSignature The order signature from the maker.
/// @param taker The order taker.
function _validateOtcOrder(
LibNativeOrder.OtcOrder memory order,
LibNativeOrder.OtcOrderInfo memory orderInfo,
LibSignature.Signature memory makerSignature,
address taker
)
private
view
{
// Must be fillable.
if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
LibNativeOrdersRichErrors.OrderNotFillableError(
@@ -439,57 +217,83 @@ contract OtcOrdersFeature is
).rrevert();
}
// Must be a valid taker for the order.
if (order.taker != address(0) && order.taker != taker) {
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
orderInfo.orderHash,
taker,
order.taker
).rrevert();
address taker = msg.sender;
{
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();
}
// Maker signature must be valid for the order.
address makerSigner = LibSignature.getSignerOfHash(orderInfo.orderHash, makerSignature);
if (
makerSigner != order.maker &&
!stor.orderSignerRegistry[order.maker][makerSigner]
) {
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
orderInfo.orderHash,
makerSigner,
order.maker
).rrevert();
}
// If msg.sender is not the taker, validate the taker signature.
if (!_isSenderValidTaker(order.taker)) {
address takerSigner = LibSignature.getSignerOfHash(orderInfo.orderHash, takerSignature);
if (
takerSigner != order.taker &&
!stor.orderSignerRegistry[order.taker][takerSigner]
) {
LibNativeOrdersRichErrors.OrderNotSignedByTakerError(
orderInfo.orderHash,
takerSigner,
order.taker
).rrevert();
}
taker = order.taker;
}
}
LibNativeOrdersStorage.Storage storage stor =
LibNativeOrdersStorage.getStorage();
// Settle between the maker and taker.
(takerTokenFilledAmount, makerTokenFilledAmount) = _settleOtcOrder(
order,
taker,
takerTokenFillAmount,
wethOptions
);
// 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();
}
// Maker signature must be valid for the order.
address makerSigner = LibSignature.getSignerOfHash(orderInfo.orderHash, makerSignature);
if (
makerSigner != order.maker &&
!stor.orderSignerRegistry[order.maker][makerSigner]
) {
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
orderInfo.orderHash,
makerSigner,
order.maker
).rrevert();
}
emit OtcOrderFilled(
orderInfo.orderHash,
order.maker,
taker,
address(order.makerToken),
address(order.takerToken),
takerTokenFilledAmount,
makerTokenFilledAmount
);
}
/// @dev Settle the trade between an OTC order's maker and taker.
/// @param order The OTC order.
/// @param takerTokenFillAmount Maximum taker token amount to fill this
/// order with.
/// @param payer The address holding the taker tokens.
/// @param recipient The recipient of the maker tokens.
/// @return takerTokenFilledAmount How much taker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function _settleOtcOrder(
LibNativeOrder.OtcOrder memory order,
address taker,
uint128 takerTokenFillAmount,
address payer,
address recipient
WethOptions wethOptions
)
private
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
@@ -522,34 +326,57 @@ contract OtcOrdersFeature is
));
}
if (payer == address(this)) {
if (address(order.takerToken) == ETH_TOKEN_ADDRESS) {
// Transfer ETH to the maker.
payable(order.maker).transfer(takerTokenFilledAmount);
} else {
// Transfer this -> maker.
_transferERC20Tokens(
order.takerToken,
order.maker,
takerTokenFilledAmount
);
if (wethOptions == WethOptions.WrapEth) {
require(
order.takerToken == WETH,
"OtcOrdersFeature/INVALID_WRAP_ETH"
);
// Wrap ETH
WETH.deposit{value: takerTokenFilledAmount}();
// Transfer WETH to maker
WETH.transfer(order.maker, takerTokenFilledAmount);
if (takerTokenFilledAmount < msg.value) {
// Refund unused ETH
_transferEth(
msg.sender,
msg.value - uint256(takerTokenFilledAmount)
);
}
} else {
// Transfer taker -> maker
_transferERC20TokensFrom(
order.takerToken,
payer,
taker,
order.maker,
takerTokenFilledAmount
);
}
// Transfer maker -> recipient.
_transferERC20TokensFrom(
order.makerToken,
order.maker,
recipient,
makerTokenFilledAmount
);
if (wethOptions == WethOptions.UnwrapWeth) {
require(
order.makerToken == WETH,
"OtcOrdersFeature/INVALID_UNWRAP_WETH"
);
// Transfer maker tokens in
_transferERC20TokensFrom(
order.makerToken,
order.maker,
address(this),
makerTokenFilledAmount
);
// Unwrap WETH
WETH.withdraw(makerTokenFilledAmount);
// Transfer ETH to taker
_transferEth(taker, makerTokenFilledAmount);
} else {
// Transfer maker -> taker.
_transferERC20TokensFrom(
order.makerToken,
order.maker,
taker,
makerTokenFilledAmount
);
}
}
/// @dev Get the order info for an OTC order.
@@ -634,4 +461,12 @@ contract OtcOrdersFeature is
revertData.rrevert();
}
}
function _isSenderValidTaker(address orderTaker)
private
view
returns (bool)
{
return orderTaker == address(0) || orderTaker == msg.sender;
}
}

View File

@@ -22,7 +22,6 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
import "../errors/LibTransformERC20RichErrors.sol";
@@ -52,14 +51,16 @@ contract TransformERC20Feature is
struct TransformERC20PrivateState {
IFlashWallet wallet;
address transformerDeployer;
uint256 recipientOutputTokenBalanceBefore;
uint256 recipientOutputTokenBalanceAfter;
uint256 takerOutputTokenBalanceBefore;
uint256 takerOutputTokenBalanceAfter;
}
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "TransformERC20";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 4, 0);
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 3, 1);
constructor() public {}
/// @dev Initialize and register this feature.
/// Should be delegatecalled by `Migrate.migrate()`.
@@ -75,7 +76,7 @@ contract TransformERC20Feature is
_registerFeatureFunction(this.setTransformerDeployer.selector);
_registerFeatureFunction(this.setQuoteSigner.selector);
_registerFeatureFunction(this.getQuoteSigner.selector);
_registerFeatureFunction(this.transformERC20Staging.selector);
_registerFeatureFunction(this.transformERC20.selector);
_registerFeatureFunction(this._transformERC20.selector);
if (this.getTransformWallet() == IFlashWallet(address(0))) {
// Create the transform wallet if it doesn't exist.
@@ -145,44 +146,6 @@ contract TransformERC20Feature is
LibTransformERC20Storage.getStorage().wallet = wallet;
}
/// @dev Wrapper for `transformERC20`. This selector will be temporarily
/// registered to the Exchange Proxy so that we can migrate 0x API
/// with no downtime. Once 0x API has been updated to point to this
/// function, we can safely re-register `transformERC20`, point
/// 0x API back to `transformERC20`, and deregister this function.
/// @param inputToken The token being provided by the sender.
/// If `0xeee...`, ETH is implied and should be provided with the call.`
/// @param outputToken The token to be acquired by the sender.
/// `0xeee...` implies ETH.
/// @param inputTokenAmount The amount of `inputToken` to take from the sender.
/// If set to `uint256(-1)`, the entire spendable balance of the taker
/// will be solt.
/// @param minOutputTokenAmount The minimum amount of `outputToken` the sender
/// must receive for the entire transformation to succeed. If set to zero,
/// the minimum output token transfer will not be asserted.
/// @param transformations The transformations to execute on the token balance(s)
/// in sequence.
/// @return outputTokenAmount The amount of `outputToken` received by the sender.
function transformERC20Staging(
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 minOutputTokenAmount,
Transformation[] memory transformations
)
public
payable
returns (uint256 outputTokenAmount)
{
return transformERC20(
inputToken,
outputToken,
inputTokenAmount,
minOutputTokenAmount,
transformations
);
}
/// @dev Executes a series of transformations to convert an ERC20 `inputToken`
/// to an ERC20 `outputToken`.
/// @param inputToken The token being provided by the sender.
@@ -217,9 +180,7 @@ contract TransformERC20Feature is
outputToken: outputToken,
inputTokenAmount: inputTokenAmount,
minOutputTokenAmount: minOutputTokenAmount,
transformations: transformations,
useSelfBalance: false,
recipient: msg.sender
transformations: transformations
})
);
}
@@ -247,7 +208,7 @@ contract TransformERC20Feature is
{
// If the input token amount is -1 and we are not selling ETH,
// transform the taker's entire spendable balance.
if (!args.useSelfBalance && args.inputTokenAmount == uint256(-1)) {
if (args.inputTokenAmount == uint256(-1)) {
if (LibERC20Transformer.isTokenETH(args.inputToken)) {
// We can't pull more ETH from the taker, so we just set the
// input token amount to the value attached to the call.
@@ -264,12 +225,17 @@ contract TransformERC20Feature is
state.wallet = getTransformWallet();
state.transformerDeployer = getTransformerDeployer();
// Remember the initial output token balance of the recipient.
state.recipientOutputTokenBalanceBefore =
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.recipient);
// Remember the initial output token balance of the taker.
state.takerOutputTokenBalanceBefore =
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker);
// Pull input tokens from the taker to the wallet and transfer attached ETH.
_transferInputTokensAndAttachedEth(args, address(state.wallet));
_transferInputTokensAndAttachedEth(
args.inputToken,
args.taker,
address(state.wallet),
args.inputTokenAmount
);
{
// Perform transformations.
@@ -278,29 +244,22 @@ contract TransformERC20Feature is
state.wallet,
args.transformations[i],
state.transformerDeployer,
args.recipient
args.taker
);
}
// Transfer output tokens from wallet to recipient
outputTokenAmount = _executeOutputTokenTransfer(
args.outputToken,
state.wallet,
args.recipient
);
}
// Compute how much output token has been transferred to the recipient.
state.recipientOutputTokenBalanceAfter =
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.recipient);
if (state.recipientOutputTokenBalanceAfter < state.recipientOutputTokenBalanceBefore) {
// Compute how much output token has been transferred to the taker.
state.takerOutputTokenBalanceAfter =
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker);
if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
LibTransformERC20RichErrors.NegativeTransformERC20OutputError(
address(args.outputToken),
state.recipientOutputTokenBalanceBefore - state.recipientOutputTokenBalanceAfter
state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter
).rrevert();
}
outputTokenAmount = LibSafeMathV06.min256(
outputTokenAmount,
state.recipientOutputTokenBalanceAfter.safeSub(state.recipientOutputTokenBalanceBefore)
outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
state.takerOutputTokenBalanceBefore
);
// Ensure enough output token has been sent to the taker.
if (outputTokenAmount < args.minOutputTokenAmount) {
@@ -333,49 +292,38 @@ contract TransformERC20Feature is
return LibTransformERC20Storage.getStorage().wallet;
}
/// @dev Transfer input tokens and any attached ETH to `to`
/// @param args A `TransformERC20Args` struct.
/// @dev Transfer input tokens from the taker and any attached ETH to `to`
/// @param inputToken The token to pull from the taker.
/// @param from The from (taker) address.
/// @param to The recipient of tokens and ETH.
/// @param amount Amount of `inputToken` tokens to transfer.
function _transferInputTokensAndAttachedEth(
TransformERC20Args memory args,
address payable to
IERC20TokenV06 inputToken,
address from,
address payable to,
uint256 amount
)
private
{
if (
LibERC20Transformer.isTokenETH(args.inputToken) &&
msg.value < args.inputTokenAmount
) {
// Token is ETH, so the caller must attach enough ETH to the call.
LibTransformERC20RichErrors.InsufficientEthAttachedError(
msg.value,
args.inputTokenAmount
).rrevert();
}
// Transfer any attached ETH.
if (msg.value != 0) {
to.transfer(msg.value);
}
// Transfer input tokens.
if (!LibERC20Transformer.isTokenETH(args.inputToken)) {
if (args.useSelfBalance) {
// Use EP balance input token.
_transferERC20Tokens(
args.inputToken,
to,
args.inputTokenAmount
);
} else {
// Pull ERC20 tokens from taker.
_transferERC20TokensFrom(
args.inputToken,
args.taker,
to,
args.inputTokenAmount
);
}
if (!LibERC20Transformer.isTokenETH(inputToken) && amount != 0) {
// Token is not ETH, so pull ERC20 tokens.
_transferERC20TokensFrom(
inputToken,
from,
to,
amount
);
} else if (msg.value < amount) {
// Token is ETH, so the caller must attach enough ETH to the call.
LibTransformERC20RichErrors.InsufficientEthAttachedError(
msg.value,
amount
).rrevert();
}
}
@@ -383,12 +331,12 @@ contract TransformERC20Feature is
/// @param wallet The wallet instance.
/// @param transformation The transformation.
/// @param transformerDeployer The address of the transformer deployer.
/// @param recipient The recipient address.
/// @param taker The taker address.
function _executeTransformation(
IFlashWallet wallet,
Transformation memory transformation,
address transformerDeployer,
address payable recipient
address payable taker
)
private
{
@@ -406,7 +354,7 @@ contract TransformERC20Feature is
IERC20Transformer.transform.selector,
IERC20Transformer.TransformContext({
sender: msg.sender,
recipient: recipient,
taker: taker,
data: transformation.data
})
)
@@ -422,52 +370,4 @@ contract TransformERC20Feature is
).rrevert();
}
}
function _executeOutputTokenTransfer(
IERC20TokenV06 outputToken,
IFlashWallet wallet,
address payable recipient
)
private
returns (uint256 transferAmount)
{
transferAmount =
LibERC20Transformer.getTokenBalanceOf(outputToken, address(wallet));
if (LibERC20Transformer.isTokenETH(outputToken)) {
wallet.executeCall(
recipient,
"",
transferAmount
);
} else {
bytes memory resultData = wallet.executeCall(
payable(address(outputToken)),
abi.encodeWithSelector(
IERC20TokenV06.transfer.selector,
recipient,
transferAmount
),
0
);
if (resultData.length == 0) {
// If we get back 0 returndata, this may be a non-standard ERC-20 that
// does not return a boolean. Check that it at least contains code.
uint256 size;
assembly { size := extcodesize(outputToken) }
require(size > 0, "invalid token address, contains no code");
} else if (resultData.length >= 32) {
// If we get back at least 32 bytes, we know the target address
// contains code, and we assume it is a token that returned a boolean
// success value, which must be true.
uint256 result = LibBytesV06.readUint256(resultData, 0);
if (result != 1) {
LibRichErrorsV06.rrevert(resultData);
}
} else {
// If 0 < returndatasize < 32, the target is a contract, but not a
// valid token.
LibRichErrorsV06.rrevert(resultData);
}
}
}
}

View File

@@ -40,7 +40,7 @@ contract UniswapV3Feature is
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "UniswapV3Feature";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 0);
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
/// @dev WETH contract.
IEtherTokenV06 private immutable WETH;
/// @dev UniswapV3 Factory contract address prepended with '0xff' and left-aligned.
@@ -88,7 +88,6 @@ contract UniswapV3Feature is
_registerFeatureFunction(this.sellEthForTokenToUniswapV3.selector);
_registerFeatureFunction(this.sellTokenForEthToUniswapV3.selector);
_registerFeatureFunction(this.sellTokenForTokenToUniswapV3.selector);
_registerFeatureFunction(this._sellHeldTokenForTokenToUniswapV3.selector);
_registerFeatureFunction(this.uniswapV3SwapCallback.selector);
return LibMigrate.MIGRATE_SUCCESS;
}
@@ -176,33 +175,6 @@ contract UniswapV3Feature is
);
}
/// @dev Sell a token for another token directly against uniswap v3.
/// Private variant, uses tokens held by `address(this)`.
/// @param encodedPath Uniswap-encoded path.
/// @param sellAmount amount of the first token in the path to sell.
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
/// @param recipient The recipient of the bought tokens. Can be zero for sender.
/// @return buyAmount Amount of the last token in the path bought.
function _sellHeldTokenForTokenToUniswapV3(
bytes memory encodedPath,
uint256 sellAmount,
uint256 minBuyAmount,
address recipient
)
public
override
onlySelf
returns (uint256 buyAmount)
{
buyAmount = _swap(
encodedPath,
sellAmount,
minBuyAmount,
address(this),
_normalizeRecipient(recipient)
);
}
/// @dev The UniswapV3 pool swap callback which pays the funds requested
/// by the caller/pool to the pool. Can only be called by a valid
/// UniswapV3 pool.

View File

@@ -24,21 +24,9 @@ import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
interface IMultiplexFeature {
// Identifies the type of subcall.
enum MultiplexSubcall {
Invalid,
RFQ,
OTC,
UniswapV2,
UniswapV3,
LiquidityProvider,
TransformERC20,
BatchSell,
MultiHopSell
}
// Parameters for a batch sell.
struct BatchSellParams {
// Parameters for `batchFill`.
struct BatchFillData {
// The token being sold.
IERC20TokenV06 inputToken;
// The token being bought.
@@ -46,182 +34,84 @@ interface IMultiplexFeature {
// The amount of `inputToken` to sell.
uint256 sellAmount;
// The nested calls to perform.
BatchSellSubcall[] calls;
// Whether to use the Exchange Proxy's balance
// of input tokens.
bool useSelfBalance;
// The recipient of the bought output tokens.
address recipient;
WrappedBatchCall[] calls;
}
// Represents a constituent call of a batch sell.
struct BatchSellSubcall {
// The function to call.
MultiplexSubcall id;
// Amount of input token to sell. If the highest bit is 1,
// this value represents a proportion of the total
// `sellAmount` of the batch sell. See `_normalizeSellAmount`
// for details.
// 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 a multi-hop sell.
struct MultiHopSellParams {
// 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.
MultiHopSellSubcall[] calls;
// Whether to use the Exchange Proxy's balance
// of input tokens.
bool useSelfBalance;
// The recipient of the bought output tokens.
address recipient;
WrappedMultiHopCall[] calls;
}
// Represents a constituent call of a multi-hop sell.
struct MultiHopSellSubcall {
// The function to call.
MultiplexSubcall id;
// 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;
}
struct BatchSellState {
// Tracks the amount of input token sold.
uint256 soldAmount;
// Tracks the amount of output token bought.
uint256 boughtAmount;
}
event LiquidityProviderSwap(
address inputToken,
address outputToken,
uint256 inputTokenAmount,
uint256 outputTokenAmount,
address provider,
address recipient
);
struct MultiHopSellState {
// This variable is used for the input and output amounts of
// each hop. After the final hop, this will contain the output
// amount of the multi-hop sell.
uint256 outputTokenAmount;
// For each hop in a multi-hop sell, `from` is the
// address that holds the input tokens of the hop,
// `to` is the address that receives the output tokens
// of the hop.
// See `_computeHopTarget` for details.
address from;
address to;
// The index of the current hop in the multi-hop chain.
uint256 hopIndex;
}
event ExpiredRfqOrder(
bytes32 orderHash,
address maker,
uint64 expiry
);
/// @dev Sells attached ETH for `outputToken` using the provided
/// calls.
/// @param outputToken The token to buy.
/// @param calls The calls to use to sell the attached ETH.
/// @param minBuyAmount The minimum amount of `outputToken` that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of `outputToken` bought.
function multiplexBatchSellEthForToken(
IERC20TokenV06 outputToken,
BatchSellSubcall[] calldata calls,
/// @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 boughtAmount);
returns (uint256 outputTokenAmount);
/// @dev Sells `sellAmount` of the given `inputToken` for ETH
/// using the provided calls.
/// @param inputToken The token to sell.
/// @param calls The calls to use to sell the input tokens.
/// @param sellAmount The amount of `inputToken` to sell.
/// @param minBuyAmount The minimum amount of ETH that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of ETH bought.
function multiplexBatchSellTokenForEth(
IERC20TokenV06 inputToken,
BatchSellSubcall[] calldata calls,
uint256 sellAmount,
uint256 minBuyAmount
)
external
returns (uint256 boughtAmount);
/// @dev Sells `sellAmount` of the given `inputToken` for
/// `outputToken` using the provided calls.
/// @param inputToken The token to sell.
/// @param outputToken The token to buy.
/// @param calls The calls to use to sell the input tokens.
/// @param sellAmount The amount of `inputToken` to sell.
/// @param minBuyAmount The minimum amount of `outputToken`
/// that must be bought for this function to not revert.
/// @return boughtAmount The amount of `outputToken` bought.
function multiplexBatchSellTokenForToken(
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
BatchSellSubcall[] calldata calls,
uint256 sellAmount,
uint256 minBuyAmount
)
external
returns (uint256 boughtAmount);
/// @dev Sells attached ETH via the given sequence of tokens
/// and calls. `tokens[0]` must be WETH.
/// The last token in `tokens` is the output token that
/// will ultimately be sent to `msg.sender`
/// @param tokens The sequence of tokens to use for the sell,
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
/// `calls[i]`.
/// @param calls The sequence of calls to use for the sell.
/// @param minBuyAmount The minimum amount of output tokens that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of output tokens bought.
function multiplexMultiHopSellEthForToken(
address[] calldata tokens,
MultiHopSellSubcall[] calldata calls,
/// @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 boughtAmount);
/// @dev Sells `sellAmount` of the input token (`tokens[0]`)
/// for ETH via the given sequence of tokens and calls.
/// The last token in `tokens` must be WETH.
/// @param tokens The sequence of tokens to use for the sell,
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
/// `calls[i]`.
/// @param calls The sequence of calls to use for the sell.
/// @param minBuyAmount The minimum amount of ETH that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of ETH bought.
function multiplexMultiHopSellTokenForEth(
address[] calldata tokens,
MultiHopSellSubcall[] calldata calls,
uint256 sellAmount,
uint256 minBuyAmount
)
external
returns (uint256 boughtAmount);
/// @dev Sells `sellAmount` of the input token (`tokens[0]`)
/// via the given sequence of tokens and calls.
/// The last token in `tokens` is the output token that
/// will ultimately be sent to `msg.sender`
/// @param tokens The sequence of tokens to use for the sell,
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
/// `calls[i]`.
/// @param calls The sequence of calls to use for the sell.
/// @param minBuyAmount The minimum amount of output tokens that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of output tokens bought.
function multiplexMultiHopSellTokenForToken(
address[] calldata tokens,
MultiHopSellSubcall[] calldata calls,
uint256 sellAmount,
uint256 minBuyAmount
)
external
returns (uint256 boughtAmount);
returns (uint256 outputTokenAmount);
}

View File

@@ -126,18 +126,13 @@ interface INativeOrdersFeature is
/// @param signature The order signature.
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
/// @param taker The order taker.
/// @param useSelfBalance Whether to use the ExchangeProxy's transient
/// balance of taker tokens to fill the order.
/// @param recipient The recipient of the maker tokens.
/// @return takerTokenFilledAmount How much maker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function _fillRfqOrder(
LibNativeOrder.RfqOrder calldata order,
LibSignature.Signature calldata signature,
uint128 takerTokenFillAmount,
address taker,
bool useSelfBalance,
address recipient
address taker
)
external
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);

View File

@@ -31,16 +31,16 @@ interface IOtcOrdersFeature {
/// @param orderHash The canonical hash of the order.
/// @param maker The maker of the order.
/// @param taker The taker of the order.
/// @param makerTokenFilledAmount How much maker token was filled.
/// @param takerTokenFilledAmount How much taker token was filled.
/// @param makerTokenFilledAmount How much maker token was filled.
event OtcOrderFilled(
bytes32 orderHash,
address maker,
address taker,
address makerToken,
address takerToken,
uint128 makerTokenFilledAmount,
uint128 takerTokenFilledAmount
uint128 takerTokenFilledAmount,
uint128 makerTokenFilledAmount
);
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
@@ -48,29 +48,15 @@ interface IOtcOrdersFeature {
/// @param makerSignature The order signature from the maker.
/// @param takerTokenFillAmount Maximum taker token amount to fill this
/// order with.
/// @param unwrapWeth Whether or not to unwrap bought WETH into ETH
/// before transferring it to the taker. Should be set to false
/// @return takerTokenFilledAmount How much taker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function fillOtcOrder(
LibNativeOrder.OtcOrder calldata order,
LibSignature.Signature calldata makerSignature,
uint128 takerTokenFillAmount
)
external
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
/// Unwraps bought WETH into ETH before sending it to
/// the taker.
/// @param order The OTC order.
/// @param makerSignature The order signature from the maker.
/// @param takerTokenFillAmount Maximum taker token amount to fill this
/// order with.
/// @return takerTokenFilledAmount How much taker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function fillOtcOrderForEth(
LibNativeOrder.OtcOrder calldata order,
LibSignature.Signature calldata makerSignature,
uint128 takerTokenFillAmount
uint128 takerTokenFillAmount,
bool unwrapWeth
)
external
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
@@ -94,64 +80,16 @@ interface IOtcOrdersFeature {
/// @param order The OTC order.
/// @param makerSignature The order signature from the maker.
/// @param takerSignature The order signature from the taker.
/// @param unwrapWeth Whether or not to unwrap bought WETH into ETH
/// before transferring it to the taker. Should be set to false
/// if the maker token is not WETH.
/// @return takerTokenFilledAmount How much taker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function fillTakerSignedOtcOrder(
LibNativeOrder.OtcOrder calldata order,
LibSignature.Signature calldata makerSignature,
LibSignature.Signature calldata takerSignature
)
external;
/// @dev Fully fill an OTC order. "Meta-transaction" variant,
/// requires order to be signed by both maker and taker.
/// Unwraps bought WETH into ETH before sending it to
/// the taker.
/// @param order The OTC order.
/// @param makerSignature The order signature from the maker.
/// @param takerSignature The order signature from the taker.
function fillTakerSignedOtcOrderForEth(
LibNativeOrder.OtcOrder calldata order,
LibSignature.Signature calldata makerSignature,
LibSignature.Signature calldata takerSignature
)
external;
/// @dev Fills multiple taker-signed OTC orders.
/// @param orders Array of OTC orders.
/// @param makerSignatures Array of maker signatures for each order.
/// @param takerSignatures Array of taker signatures for each order.
/// @param unwrapWeth Array of booleans representing whether or not
/// to unwrap bought WETH into ETH for each order. Should be set
/// to false if the maker token is not WETH.
/// @return successes Array of booleans representing whether or not
/// each order in `orders` was filled successfully.
function batchFillTakerSignedOtcOrders(
LibNativeOrder.OtcOrder[] calldata orders,
LibSignature.Signature[] calldata makerSignatures,
LibSignature.Signature[] calldata takerSignatures,
bool[] calldata unwrapWeth
)
external
returns (bool[] memory successes);
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
/// Internal variant.
/// @param order The OTC order.
/// @param makerSignature The order signature from the maker.
/// @param takerTokenFillAmount Maximum taker token amount to fill this
/// order with.
/// @param taker The address to fill the order in the context of.
/// @param useSelfBalance Whether to use the Exchange Proxy's balance
/// of input tokens.
/// @param recipient The recipient of the bought maker tokens.
/// @return takerTokenFilledAmount How much taker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function _fillOtcOrder(
LibNativeOrder.OtcOrder calldata order,
LibSignature.Signature calldata makerSignature,
uint128 takerTokenFillAmount,
address taker,
bool useSelfBalance,
address recipient
LibSignature.Signature calldata takerSignature,
bool unwrapWeth
)
external
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);

View File

@@ -59,10 +59,6 @@ interface ITransformERC20Feature {
// The transformations to execute on the token balance(s)
// in sequence.
Transformation[] transformations;
// Whether to use the Exchange Proxy's balance of `inputToken`.
bool useSelfBalance;
// The recipient of the bought `outputToken`.
address payable recipient;
}
/// @dev Raised upon a successful `transformERC20`.

View File

@@ -70,22 +70,6 @@ interface IUniswapV3Feature {
external
returns (uint256 buyAmount);
/// @dev Sell a token for another token directly against uniswap v3.
/// Private variant, uses tokens held by `address(this)`.
/// @param encodedPath Uniswap-encoded path.
/// @param sellAmount amount of the first token in the path to sell.
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
/// @param recipient The recipient of the bought tokens. Can be zero for sender.
/// @return buyAmount Amount of the last token in the path bought.
function _sellHeldTokenForTokenToUniswapV3(
bytes memory encodedPath,
uint256 sellAmount,
uint256 minBuyAmount,
address recipient
)
external
returns (uint256 buyAmount);
/// @dev The UniswapV3 pool swap callback which pays the funds requested
/// by the caller/pool to the pool. Can only be called by a valid
/// UniswapV3 pool.

View File

@@ -1,742 +0,0 @@
// 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/LibSafeMathV06.sol";
import "../../external/ILiquidityProviderSandbox.sol";
import "../../fixins/FixinCommon.sol";
import "../../fixins/FixinEIP712.sol";
import "../../migrations/LibMigrate.sol";
import "../interfaces/IFeature.sol";
import "../interfaces/IMultiplexFeature.sol";
import "./MultiplexLiquidityProvider.sol";
import "./MultiplexOtc.sol";
import "./MultiplexRfq.sol";
import "./MultiplexTransformERC20.sol";
import "./MultiplexUniswapV2.sol";
import "./MultiplexUniswapV3.sol";
/// @dev This feature enables efficient batch and multi-hop trades
/// using different liquidity sources.
contract MultiplexFeature is
IFeature,
IMultiplexFeature,
FixinCommon,
MultiplexLiquidityProvider,
MultiplexOtc,
MultiplexRfq,
MultiplexTransformERC20,
MultiplexUniswapV2,
MultiplexUniswapV3
{
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "MultiplexFeature";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(2, 0, 0);
/// @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 The WETH token contract.
IEtherTokenV06 private immutable WETH;
constructor(
address zeroExAddress,
IEtherTokenV06 weth,
ILiquidityProviderSandbox sandbox,
address uniswapFactory,
address sushiswapFactory,
bytes32 uniswapPairInitCodeHash,
bytes32 sushiswapPairInitCodeHash
)
public
FixinEIP712(zeroExAddress)
MultiplexLiquidityProvider(sandbox)
MultiplexUniswapV2(
uniswapFactory,
sushiswapFactory,
uniswapPairInitCodeHash,
sushiswapPairInitCodeHash
)
{
WETH = weth;
}
/// @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.multiplexBatchSellEthForToken.selector);
_registerFeatureFunction(this.multiplexBatchSellTokenForEth.selector);
_registerFeatureFunction(this.multiplexBatchSellTokenForToken.selector);
_registerFeatureFunction(this.multiplexMultiHopSellEthForToken.selector);
_registerFeatureFunction(this.multiplexMultiHopSellTokenForEth.selector);
_registerFeatureFunction(this.multiplexMultiHopSellTokenForToken.selector);
return LibMigrate.MIGRATE_SUCCESS;
}
/// @dev Sells attached ETH for `outputToken` using the provided
/// calls.
/// @param outputToken The token to buy.
/// @param calls The calls to use to sell the attached ETH.
/// @param minBuyAmount The minimum amount of `outputToken` that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of `outputToken` bought.
function multiplexBatchSellEthForToken(
IERC20TokenV06 outputToken,
BatchSellSubcall[] memory calls,
uint256 minBuyAmount
)
public
override
payable
returns (uint256 boughtAmount)
{
// Wrap ETH.
WETH.deposit{value: msg.value}();
// WETH is now held by this contract,
// so `useSelfBalance` is true.
return _multiplexBatchSell(
BatchSellParams({
inputToken: WETH,
outputToken: outputToken,
sellAmount: msg.value,
calls: calls,
useSelfBalance: true,
recipient: msg.sender
}),
minBuyAmount
);
}
/// @dev Sells `sellAmount` of the given `inputToken` for ETH
/// using the provided calls.
/// @param inputToken The token to sell.
/// @param calls The calls to use to sell the input tokens.
/// @param sellAmount The amount of `inputToken` to sell.
/// @param minBuyAmount The minimum amount of ETH that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of ETH bought.
function multiplexBatchSellTokenForEth(
IERC20TokenV06 inputToken,
BatchSellSubcall[] memory calls,
uint256 sellAmount,
uint256 minBuyAmount
)
public
override
returns (uint256 boughtAmount)
{
// The outputToken is implicitly WETH. The `recipient`
// of the WETH is set to this contract, since we
// must unwrap the WETH and transfer the resulting ETH.
boughtAmount = _multiplexBatchSell(
BatchSellParams({
inputToken: inputToken,
outputToken: WETH,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: address(this)
}),
minBuyAmount
);
// Unwrap WETH.
WETH.withdraw(boughtAmount);
// Transfer ETH to `msg.sender`.
_transferEth(msg.sender, boughtAmount);
}
/// @dev Sells `sellAmount` of the given `inputToken` for
/// `outputToken` using the provided calls.
/// @param inputToken The token to sell.
/// @param outputToken The token to buy.
/// @param calls The calls to use to sell the input tokens.
/// @param sellAmount The amount of `inputToken` to sell.
/// @param minBuyAmount The minimum amount of `outputToken`
/// that must be bought for this function to not revert.
/// @return boughtAmount The amount of `outputToken` bought.
function multiplexBatchSellTokenForToken(
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
BatchSellSubcall[] memory calls,
uint256 sellAmount,
uint256 minBuyAmount
)
public
override
returns (uint256 boughtAmount)
{
return _multiplexBatchSell(
BatchSellParams({
inputToken: inputToken,
outputToken: outputToken,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: msg.sender
}),
minBuyAmount
);
}
/// @dev Executes a batch sell and checks that at least
/// `minBuyAmount` of `outputToken` was bought.
/// @param params Batch sell parameters.
/// @param minBuyAmount The minimum amount of `outputToken` that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of `outputToken` bought.
function _multiplexBatchSell(
BatchSellParams memory params,
uint256 minBuyAmount
)
private
returns (uint256 boughtAmount)
{
// Cache the recipient's initial balance of the output token.
uint256 balanceBefore = params.outputToken.balanceOf(params.recipient);
// Execute the batch sell.
BatchSellState memory state = _executeBatchSell(params);
// Compute the change in balance of the output token.
uint256 balanceDelta = params.outputToken.balanceOf(params.recipient)
.safeSub(balanceBefore);
// Use the minimum of the balanceDelta and the returned bought
// amount in case of weird tokens and whatnot.
boughtAmount = LibSafeMathV06.min256(balanceDelta, state.boughtAmount);
// Enforce `minBuyAmount`.
require(
boughtAmount >= minBuyAmount,
"MultiplexFeature::_multiplexBatchSell/UNDERBOUGHT"
);
}
/// @dev Sells attached ETH via the given sequence of tokens
/// and calls. `tokens[0]` must be WETH.
/// The last token in `tokens` is the output token that
/// will ultimately be sent to `msg.sender`
/// @param tokens The sequence of tokens to use for the sell,
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
/// `calls[i]`.
/// @param calls The sequence of calls to use for the sell.
/// @param minBuyAmount The minimum amount of output tokens that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of output tokens bought.
function multiplexMultiHopSellEthForToken(
address[] memory tokens,
MultiHopSellSubcall[] memory calls,
uint256 minBuyAmount
)
public
override
payable
returns (uint256 boughtAmount)
{
// First token must be WETH.
require(
tokens[0] == address(WETH),
"MultiplexFeature::multiplexMultiHopSellEthForToken/NOT_WETH"
);
// Wrap ETH.
WETH.deposit{value: msg.value}();
// WETH is now held by this contract,
// so `useSelfBalance` is true.
return _multiplexMultiHopSell(
MultiHopSellParams({
tokens: tokens,
sellAmount: msg.value,
calls: calls,
useSelfBalance: true,
recipient: msg.sender
}),
minBuyAmount
);
}
/// @dev Sells `sellAmount` of the input token (`tokens[0]`)
/// for ETH via the given sequence of tokens and calls.
/// The last token in `tokens` must be WETH.
/// @param tokens The sequence of tokens to use for the sell,
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
/// `calls[i]`.
/// @param calls The sequence of calls to use for the sell.
/// @param sellAmount The amount of `inputToken` to sell.
/// @param minBuyAmount The minimum amount of ETH that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of ETH bought.
function multiplexMultiHopSellTokenForEth(
address[] memory tokens,
MultiHopSellSubcall[] memory calls,
uint256 sellAmount,
uint256 minBuyAmount
)
public
override
returns (uint256 boughtAmount)
{
// Last token must be WETH.
require(
tokens[tokens.length - 1] == address(WETH),
"MultiplexFeature::multiplexMultiHopSellTokenForEth/NOT_WETH"
);
// The `recipient of the WETH is set to this contract, since
// we must unwrap the WETH and transfer the resulting ETH.
boughtAmount = _multiplexMultiHopSell(
MultiHopSellParams({
tokens: tokens,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: address(this)
}),
minBuyAmount
);
// Unwrap WETH.
WETH.withdraw(boughtAmount);
// Transfer ETH to `msg.sender`.
_transferEth(msg.sender, boughtAmount);
}
/// @dev Sells `sellAmount` of the input token (`tokens[0]`)
/// via the given sequence of tokens and calls.
/// The last token in `tokens` is the output token that
/// will ultimately be sent to `msg.sender`
/// @param tokens The sequence of tokens to use for the sell,
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
/// `calls[i]`.
/// @param calls The sequence of calls to use for the sell.
/// @param sellAmount The amount of `inputToken` to sell.
/// @param minBuyAmount The minimum amount of output tokens that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of output tokens bought.
function multiplexMultiHopSellTokenForToken(
address[] memory tokens,
MultiHopSellSubcall[] memory calls,
uint256 sellAmount,
uint256 minBuyAmount
)
public
override
returns (uint256 boughtAmount)
{
return _multiplexMultiHopSell(
MultiHopSellParams({
tokens: tokens,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: msg.sender
}),
minBuyAmount
);
}
/// @dev Executes a multi-hop sell and checks that at least
/// `minBuyAmount` of output tokens were bought.
/// @param params Multi-hop sell parameters.
/// @param minBuyAmount The minimum amount of output tokens that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of output tokens bought.
function _multiplexMultiHopSell(
MultiHopSellParams memory params,
uint256 minBuyAmount
)
private
returns (uint256 boughtAmount)
{
// 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(
params.tokens.length == params.calls.length + 1,
"MultiplexFeature::_multiplexMultiHopSell/MISMATCHED_ARRAY_LENGTHS"
);
// The output token is the last token in the path.
IERC20TokenV06 outputToken = IERC20TokenV06(
params.tokens[params.tokens.length - 1]
);
// Cache the recipient's balance of the output token.
uint256 balanceBefore = outputToken.balanceOf(params.recipient);
// Execute the multi-hop sell.
MultiHopSellState memory state = _executeMultiHopSell(params);
// Compute the change in balance of the output token.
uint256 balanceDelta = outputToken.balanceOf(params.recipient)
.safeSub(balanceBefore);
// Use the minimum of the balanceDelta and the returned bought
// amount in case of weird tokens and whatnot.
boughtAmount = LibSafeMathV06.min256(balanceDelta, state.outputTokenAmount);
// Enforce `minBuyAmount`.
require(
boughtAmount >= minBuyAmount,
"MultiplexFeature::_multiplexMultiHopSell/UNDERBOUGHT"
);
}
/// @dev Iterates through the constituent calls of a batch
/// sell and executes each one, until the full amount
// has been sold.
/// @param params Batch sell parameters.
/// @return state A struct containing the amounts of `inputToken`
/// sold and `outputToken` bought.
function _executeBatchSell(BatchSellParams memory params)
private
returns (BatchSellState memory state)
{
// Iterate through the calls and execute each one
// until the full amount has been sold.
for (uint256 i = 0; i != params.calls.length; i++) {
// Check if we've hit our target.
if (state.soldAmount >= params.sellAmount) { break; }
BatchSellSubcall memory subcall = params.calls[i];
// Compute the input token amount.
uint256 inputTokenAmount = _normalizeSellAmount(
subcall.sellAmount,
params.sellAmount,
state.soldAmount
);
if (subcall.id == MultiplexSubcall.RFQ) {
_batchSellRfqOrder(
state,
params,
subcall.data,
inputTokenAmount
);
} else if (subcall.id == MultiplexSubcall.OTC) {
_batchSellOtcOrder(
state,
params,
subcall.data,
inputTokenAmount
);
} else if (subcall.id == MultiplexSubcall.UniswapV2) {
_batchSellUniswapV2(
state,
params,
subcall.data,
inputTokenAmount
);
} else if (subcall.id == MultiplexSubcall.UniswapV3) {
_batchSellUniswapV3(
state,
params,
subcall.data,
inputTokenAmount
);
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
_batchSellLiquidityProvider(
state,
params,
subcall.data,
inputTokenAmount
);
} else if (subcall.id == MultiplexSubcall.TransformERC20) {
_batchSellTransformERC20(
state,
params,
subcall.data,
inputTokenAmount
);
} else if (subcall.id == MultiplexSubcall.MultiHopSell) {
_nestedMultiHopSell(
state,
params,
subcall.data,
inputTokenAmount
);
} else {
revert("MultiplexFeature::_executeBatchSell/INVALID_SUBCALL");
}
}
require(
state.soldAmount == params.sellAmount,
"MultiplexFeature::_executeBatchSell/INCORRECT_AMOUNT_SOLD"
);
}
// This function executes a sequence of fills "hopping" through the
// path of tokens given by `params.tokens`.
function _executeMultiHopSell(MultiHopSellParams memory params)
private
returns (MultiHopSellState memory state)
{
// This variable is used for the input and output amounts of
// each hop. After the final hop, this will contain the output
// amount of the multi-hop fill.
state.outputTokenAmount = params.sellAmount;
// The first call may expect the input tokens to be held by
// `msg.sender`, `address(this)`, or some other address.
// Compute the expected address and transfer the input tokens
// there if necessary.
state.from = _computeHopTarget(params, 0);
// If the input tokens are currently held by `msg.sender` but
// the first hop expects them elsewhere, perform a `transferFrom`.
if (!params.useSelfBalance && state.from != msg.sender) {
_transferERC20TokensFrom(
IERC20TokenV06(params.tokens[0]),
msg.sender,
state.from,
params.sellAmount
);
}
// If the input tokens are currently held by `address(this)` but
// the first hop expects them elsewhere, perform a `transfer`.
if (params.useSelfBalance && state.from != address(this)) {
_transferERC20Tokens(
IERC20TokenV06(params.tokens[0]),
state.from,
params.sellAmount
);
}
// Iterate through the calls and execute each one.
for (state.hopIndex = 0; state.hopIndex != params.calls.length; state.hopIndex++) {
MultiHopSellSubcall memory subcall = params.calls[state.hopIndex];
// Compute the recipient of the tokens that will be
// bought by the current hop.
state.to = _computeHopTarget(params, state.hopIndex + 1);
if (subcall.id == MultiplexSubcall.UniswapV2) {
_multiHopSellUniswapV2(
state,
params,
subcall.data
);
} else if (subcall.id == MultiplexSubcall.UniswapV3) {
_multiHopSellUniswapV3(state, subcall.data);
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
_multiHopSellLiquidityProvider(
state,
params,
subcall.data
);
} else if (subcall.id == MultiplexSubcall.BatchSell) {
_nestedBatchSell(
state,
params,
subcall.data
);
} else {
revert("MultiplexFeature::_executeMultiHopSell/INVALID_SUBCALL");
}
// The recipient of the current hop will be the source
// of tokens for the next hop.
state.from = state.to;
}
}
function _nestedMultiHopSell(
IMultiplexFeature.BatchSellState memory state,
IMultiplexFeature.BatchSellParams memory params,
bytes memory data,
uint256 sellAmount
)
private
{
MultiHopSellParams memory multiHopParams;
// Decode the tokens and calls for the nested
// multi-hop sell.
(
multiHopParams.tokens,
multiHopParams.calls
) = abi.decode(
data,
(address[], MultiHopSellSubcall[])
);
multiHopParams.sellAmount = sellAmount;
// If the batch sell is using input tokens held by
// `address(this)`, then so should the nested
// multi-hop sell.
multiHopParams.useSelfBalance = params.useSelfBalance;
// Likewise, the recipient of the multi-hop sell is
// equal to the recipient of its containing batch sell.
multiHopParams.recipient = params.recipient;
// Execute the nested multi-hop sell.
uint256 outputTokenAmount =
_executeMultiHopSell(multiHopParams).outputTokenAmount;
// Increment the sold and bought amounts.
state.soldAmount = state.soldAmount.safeAdd(sellAmount);
state.boughtAmount = state.boughtAmount.safeAdd(outputTokenAmount);
}
function _nestedBatchSell(
IMultiplexFeature.MultiHopSellState memory state,
IMultiplexFeature.MultiHopSellParams memory params,
bytes memory data
)
private
{
BatchSellParams memory batchSellParams;
// Decode the calls for the nested batch sell.
batchSellParams.calls = abi.decode(
data,
(BatchSellSubcall[])
);
// The input and output tokens of the batch
// sell are the current and next tokens in
// `params.tokens`, respectively.
batchSellParams.inputToken = IERC20TokenV06(
params.tokens[state.hopIndex]
);
batchSellParams.outputToken = IERC20TokenV06(
params.tokens[state.hopIndex + 1]
);
// The `sellAmount` for the batch sell is the
// `outputTokenAmount` from the previous hop.
batchSellParams.sellAmount = state.outputTokenAmount;
// If the nested batch sell is the first hop
// and `useSelfBalance` for the containing multi-
// hop sell is false, the nested batch sell should
// pull tokens from `msg.sender` (so `batchSellParams.useSelfBalance`
// should be false). Otherwise `batchSellParams.useSelfBalance`
// should be true.
batchSellParams.useSelfBalance = state.hopIndex > 0 || params.useSelfBalance;
// `state.to` has been populated with the address
// that should receive the output tokens of the
// batch sell.
batchSellParams.recipient = state.to;
// Execute the nested batch sell.
state.outputTokenAmount =
_executeBatchSell(batchSellParams).boughtAmount;
}
// Transfers some amount of ETH to the given recipient and
// reverts if the transfer fails.
function _transferEth(address payable recipient, uint256 amount)
private
{
(bool success,) = recipient.call{value: amount}("");
require(success, "MultiplexFeature::_transferEth/TRANSFER_FAILED");
}
// This function computes the "target" address of hop index `i` within
// a multi-hop sell.
// If `i == 0`, the target is the address which should hold the input
// tokens prior to executing `calls[0]`. Otherwise, it is the address
// that should receive `tokens[i]` upon executing `calls[i-1]`.
function _computeHopTarget(
MultiHopSellParams memory params,
uint256 i
)
private
view
returns (address target)
{
if (i == params.calls.length) {
// The last call should send the output tokens to the
// multi-hop sell recipient.
target = params.recipient;
} else {
MultiHopSellSubcall memory subcall = params.calls[i];
if (subcall.id == MultiplexSubcall.UniswapV2) {
// UniswapV2 (and Sushiswap) allow tokens to be
// transferred into the pair contract before `swap`
// is called, so we compute the pair contract's address.
(address[] memory tokens, bool isSushi) = abi.decode(
subcall.data,
(address[], bool)
);
target = _computeUniswapPairAddress(
tokens[0],
tokens[1],
isSushi
);
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
// Similar to UniswapV2, LiquidityProvider contracts
// allow tokens to be transferred in before the swap
// is executed, so we the target is the address encoded
// in the subcall data.
(target,) = abi.decode(
subcall.data,
(address, bytes)
);
} else if (
subcall.id == MultiplexSubcall.UniswapV3 ||
subcall.id == MultiplexSubcall.BatchSell
) {
// UniswapV3 uses a callback to pull in the tokens being
// sold to it. The callback implemented in `UniswapV3Feature`
// can either:
// - call `transferFrom` to move tokens from `msg.sender` to the
// UniswapV3 pool, or
// - call `transfer` to move tokens from `address(this)` to the
// UniswapV3 pool.
// A nested batch sell is similar, in that it can either:
// - use tokens from `msg.sender`, or
// - use tokens held by `address(this)`.
// Suppose UniswapV3/BatchSell is the first call in the multi-hop
// path. The input tokens are either held by `msg.sender`,
// or in the case of `multiplexMultiHopSellEthForToken` WETH is
// held by `address(this)`. The target is set accordingly.
// If this is _not_ the first call in the multi-hop path, we
// are dealing with an "intermediate" token in the multi-hop path,
// which `msg.sender` may not have an allowance set for. Thus
// target must be set to `address(this)` for `i > 0`.
if (i == 0 && !params.useSelfBalance) {
target = msg.sender;
} else {
target = address(this);
}
} else {
revert("MultiplexFeature::_computeHopTarget/INVALID_SUBCALL");
}
}
require(
target != address(0),
"MultiplexFeature::_computeHopTarget/TARGET_IS_NULL"
);
}
// If `rawAmount` encodes a proportion of `totalSellAmount`, this function
// converts it to an absolute quantity. Caps the normalized amount to
// the remaining sell amount (`totalSellAmount - soldAmount`).
function _normalizeSellAmount(
uint256 rawAmount,
uint256 totalSellAmount,
uint256 soldAmount
)
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 `totalSellAmount`.
return LibSafeMathV06.min256(
totalSellAmount
* LibSafeMathV06.min256(rawAmount & LOWER_255_BITS, 1e18)
/ 1e18,
totalSellAmount.safeSub(soldAmount)
);
} else {
return LibSafeMathV06.min256(
rawAmount,
totalSellAmount.safeSub(soldAmount)
);
}
}
}

View File

@@ -1,202 +0,0 @@
// 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/LibERC20TokenV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
import "../../external/ILiquidityProviderSandbox.sol";
import "../../fixins/FixinCommon.sol";
import "../../fixins/FixinTokenSpender.sol";
import "../../vendor/ILiquidityProvider.sol";
import "../interfaces/IMultiplexFeature.sol";
abstract contract MultiplexLiquidityProvider is
FixinCommon,
FixinTokenSpender
{
using LibERC20TokenV06 for IERC20TokenV06;
using LibSafeMathV06 for uint256;
// Same event fired by LiquidityProviderFeature
event LiquidityProviderSwap(
address inputToken,
address outputToken,
uint256 inputTokenAmount,
uint256 outputTokenAmount,
address provider,
address recipient
);
/// @dev The sandbox contract address.
ILiquidityProviderSandbox private immutable SANDBOX;
constructor(ILiquidityProviderSandbox sandbox)
internal
{
SANDBOX = sandbox;
}
// A payable external function that we can delegatecall to
// swallow reverts and roll back the input token transfer.
function _batchSellLiquidityProviderExternal(
IMultiplexFeature.BatchSellParams calldata params,
bytes calldata wrappedCallData,
uint256 sellAmount
)
external
payable
returns (uint256 boughtAmount)
{
// Revert if not a delegatecall.
require(
address(this) != _implementation,
"MultiplexLiquidityProvider::_batchSellLiquidityProviderExternal/ONLY_DELEGATECALL"
);
// Decode the provider address and auxiliary data.
(address provider, bytes memory auxiliaryData) = abi.decode(
wrappedCallData,
(address, bytes)
);
if (params.useSelfBalance) {
// If `useSelfBalance` is true, use the input tokens
// held by `address(this)`.
_transferERC20Tokens(
params.inputToken,
provider,
sellAmount
);
} else {
// Otherwise, transfer the input tokens from `msg.sender`.
_transferERC20TokensFrom(
params.inputToken,
msg.sender,
provider,
sellAmount
);
}
// Cache the recipient's balance of the output token.
uint256 balanceBefore = params.outputToken
.balanceOf(params.recipient);
// Execute the swap.
SANDBOX.executeSellTokenForToken(
ILiquidityProvider(provider),
params.inputToken,
params.outputToken,
params.recipient,
0,
auxiliaryData
);
// Compute amount of output token received by the
// recipient.
boughtAmount = params.outputToken
.balanceOf(params.recipient)
.safeSub(balanceBefore);
emit LiquidityProviderSwap(
address(params.inputToken),
address(params.outputToken),
sellAmount,
boughtAmount,
provider,
params.recipient
);
}
function _batchSellLiquidityProvider(
IMultiplexFeature.BatchSellState memory state,
IMultiplexFeature.BatchSellParams memory params,
bytes memory wrappedCallData,
uint256 sellAmount
)
internal
{
// Swallow reverts
(bool success, bytes memory resultData) = _implementation.delegatecall(
abi.encodeWithSelector(
this._batchSellLiquidityProviderExternal.selector,
params,
wrappedCallData,
sellAmount
)
);
if (success) {
// Decode the output token amount on success.
uint256 boughtAmount = abi.decode(resultData, (uint256));
// Increment the sold and bought amounts.
state.soldAmount = state.soldAmount.safeAdd(sellAmount);
state.boughtAmount = state.boughtAmount.safeAdd(boughtAmount);
}
}
// This function is called after tokens have already been transferred
// into the liquidity provider contract (in the previous hop).
function _multiHopSellLiquidityProvider(
IMultiplexFeature.MultiHopSellState memory state,
IMultiplexFeature.MultiHopSellParams memory params,
bytes memory wrappedCallData
)
internal
{
IERC20TokenV06 inputToken = IERC20TokenV06(params.tokens[state.hopIndex]);
IERC20TokenV06 outputToken = IERC20TokenV06(params.tokens[state.hopIndex + 1]);
// Decode the provider address and auxiliary data.
(address provider, bytes memory auxiliaryData) = abi.decode(
wrappedCallData,
(address, bytes)
);
// Cache the recipient's balance of the output token.
uint256 balanceBefore = outputToken
.balanceOf(state.to);
// Execute the swap.
SANDBOX.executeSellTokenForToken(
ILiquidityProvider(provider),
inputToken,
outputToken,
state.to,
0,
auxiliaryData
);
// The previous `ouputTokenAmount` was effectively the
// input amount for this call. Cache the value before
// overwriting it with the new output token amount so
// that both the input and ouput amounts can be in the
// `LiquidityProviderSwap` event.
uint256 sellAmount = state.outputTokenAmount;
// Compute amount of output token received by the
// recipient.
state.outputTokenAmount = outputToken
.balanceOf(state.to)
.safeSub(balanceBefore);
emit LiquidityProviderSwap(
address(inputToken),
address(outputToken),
sellAmount,
state.outputTokenAmount,
provider,
state.to
);
}
}

View File

@@ -1,94 +0,0 @@
// 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/LibSafeMathV06.sol";
import "../../fixins/FixinEIP712.sol";
import "../interfaces/IMultiplexFeature.sol";
import "../interfaces/IOtcOrdersFeature.sol";
import "../libs/LibNativeOrder.sol";
abstract contract MultiplexOtc is
FixinEIP712
{
using LibSafeMathV06 for uint256;
event ExpiredOtcOrder(
bytes32 orderHash,
address maker,
uint64 expiry
);
function _batchSellOtcOrder(
IMultiplexFeature.BatchSellState memory state,
IMultiplexFeature.BatchSellParams memory params,
bytes memory wrappedCallData,
uint256 sellAmount
)
internal
{
// Decode the Otc order and signature.
(
LibNativeOrder.OtcOrder memory order,
LibSignature.Signature memory signature
) = abi.decode(
wrappedCallData,
(LibNativeOrder.OtcOrder, LibSignature.Signature)
);
// Validate tokens.
require(
order.takerToken == params.inputToken &&
order.makerToken == params.outputToken,
"MultiplexOtc::_batchSellOtcOrder/OTC_ORDER_INVALID_TOKENS"
);
// Pre-emptively check if the order is expired.
uint64 expiry = uint64(order.expiryAndNonce >> 192);
if (expiry <= uint64(block.timestamp)) {
bytes32 orderHash = _getEIP712Hash(
LibNativeOrder.getOtcOrderStructHash(order)
);
emit ExpiredOtcOrder(
orderHash,
order.maker,
expiry
);
return;
}
// Try filling the Otc order. Swallows reverts.
try
IOtcOrdersFeature(address(this))._fillOtcOrder
(
order,
signature,
sellAmount.safeDowncastToUint128(),
msg.sender,
params.useSelfBalance,
params.recipient
)
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
// Increment the sold and bought amounts.
state.soldAmount = state.soldAmount.safeAdd(takerTokenFilledAmount);
state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount);
} catch {}
}
}

View File

@@ -1,93 +0,0 @@
// 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/LibSafeMathV06.sol";
import "../../fixins/FixinEIP712.sol";
import "../interfaces/IMultiplexFeature.sol";
import "../interfaces/INativeOrdersFeature.sol";
import "../libs/LibNativeOrder.sol";
abstract contract MultiplexRfq is
FixinEIP712
{
using LibSafeMathV06 for uint256;
event ExpiredRfqOrder(
bytes32 orderHash,
address maker,
uint64 expiry
);
function _batchSellRfqOrder(
IMultiplexFeature.BatchSellState memory state,
IMultiplexFeature.BatchSellParams memory params,
bytes memory wrappedCallData,
uint256 sellAmount
)
internal
{
// Decode the RFQ order and signature.
(
LibNativeOrder.RfqOrder memory order,
LibSignature.Signature memory signature
) = abi.decode(
wrappedCallData,
(LibNativeOrder.RfqOrder, LibSignature.Signature)
);
// Pre-emptively check if the order is expired.
if (order.expiry <= uint64(block.timestamp)) {
bytes32 orderHash = _getEIP712Hash(
LibNativeOrder.getRfqOrderStructHash(order)
);
emit ExpiredRfqOrder(
orderHash,
order.maker,
order.expiry
);
return;
}
// Validate tokens.
require(
order.takerToken == params.inputToken &&
order.makerToken == params.outputToken,
"MultiplexRfq::_batchSellRfqOrder/RFQ_ORDER_INVALID_TOKENS"
);
// Try filling the RFQ order. Swallows reverts.
try
INativeOrdersFeature(address(this))._fillRfqOrder
(
order,
signature,
sellAmount.safeDowncastToUint128(),
msg.sender,
params.useSelfBalance,
params.recipient
)
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
// Increment the sold and bought amounts.
state.soldAmount = state.soldAmount.safeAdd(takerTokenFilledAmount);
state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount);
} catch {}
}
}

View File

@@ -1,64 +0,0 @@
// 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/LibSafeMathV06.sol";
import "../interfaces/IMultiplexFeature.sol";
import "../interfaces/ITransformERC20Feature.sol";
abstract contract MultiplexTransformERC20 {
using LibSafeMathV06 for uint256;
function _batchSellTransformERC20(
IMultiplexFeature.BatchSellState memory state,
IMultiplexFeature.BatchSellParams memory params,
bytes memory wrappedCallData,
uint256 sellAmount
)
internal
{
ITransformERC20Feature.TransformERC20Args memory args;
// We want the TransformedERC20 event to have
// `msg.sender` as the taker.
args.taker = msg.sender;
args.inputToken = params.inputToken;
args.outputToken = params.outputToken;
args.inputTokenAmount = sellAmount;
args.minOutputTokenAmount = 0;
args.useSelfBalance = params.useSelfBalance;
args.recipient = payable(params.recipient);
(args.transformations) = abi.decode(
wrappedCallData,
(ITransformERC20Feature.Transformation[])
);
// Execute the transformations and swallow reverts.
try ITransformERC20Feature(address(this))._transformERC20
(args)
returns (uint256 outputTokenAmount)
{
// Increment the sold and bought amounts.
state.soldAmount = state.soldAmount.safeAdd(sellAmount);
state.boughtAmount = state.boughtAmount.safeAdd(outputTokenAmount);
} catch {}
}
}

View File

@@ -1,290 +0,0 @@
// 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 "../../fixins/FixinCommon.sol";
import "../../fixins/FixinTokenSpender.sol";
import "../../vendor/IUniswapV2Pair.sol";
import "../interfaces/IMultiplexFeature.sol";
abstract contract MultiplexUniswapV2 is
FixinCommon,
FixinTokenSpender
{
using LibSafeMathV06 for uint256;
// address of the UniswapV2Factory contract.
address private immutable UNISWAP_FACTORY;
// address of the (Sushiswap) UniswapV2Factory contract.
address private immutable SUSHISWAP_FACTORY;
// Init code hash of the UniswapV2Pair contract.
bytes32 private immutable UNISWAP_PAIR_INIT_CODE_HASH;
// Init code hash of the (Sushiswap) UniswapV2Pair contract.
bytes32 private immutable SUSHISWAP_PAIR_INIT_CODE_HASH;
constructor(
address uniswapFactory,
address sushiswapFactory,
bytes32 uniswapPairInitCodeHash,
bytes32 sushiswapPairInitCodeHash
)
internal
{
UNISWAP_FACTORY = uniswapFactory;
SUSHISWAP_FACTORY = sushiswapFactory;
UNISWAP_PAIR_INIT_CODE_HASH = uniswapPairInitCodeHash;
SUSHISWAP_PAIR_INIT_CODE_HASH = sushiswapPairInitCodeHash;
}
// A payable external function that we can delegatecall to
// swallow reverts and roll back the input token transfer.
function _batchSellUniswapV2External(
IMultiplexFeature.BatchSellParams calldata params,
bytes calldata wrappedCallData,
uint256 sellAmount
)
external
payable
returns (uint256 boughtAmount)
{
// Revert is not a delegatecall.
require(
address(this) != _implementation,
"MultiplexLiquidityProvider::_batchSellUniswapV2External/ONLY_DELEGATECALL"
);
(address[] memory tokens, bool isSushi) = abi.decode(
wrappedCallData,
(address[], bool)
);
// Validate tokens
require(
tokens.length >= 2 &&
tokens[0] == address(params.inputToken) &&
tokens[tokens.length - 1] == address(params.outputToken),
"MultiplexUniswapV2::_batchSellUniswapV2/INVALID_TOKENS"
);
// Compute the address of the first Uniswap pair
// contract that will execute a swap.
address firstPairAddress = _computeUniswapPairAddress(
tokens[0],
tokens[1],
isSushi
);
// `_sellToUniswapV2` assumes the input tokens have been
// transferred into the pair contract before it is called,
// so we transfer the tokens in now (either from `msg.sender`
// or using the Exchange Proxy's balance).
if (params.useSelfBalance) {
_transferERC20Tokens(
IERC20TokenV06(tokens[0]),
firstPairAddress,
sellAmount
);
} else {
_transferERC20TokensFrom(
IERC20TokenV06(tokens[0]),
msg.sender,
firstPairAddress,
sellAmount
);
}
// Execute the Uniswap/Sushiswap trade.
return _sellToUniswapV2(
tokens,
sellAmount,
isSushi,
firstPairAddress,
params.recipient
);
}
function _batchSellUniswapV2(
IMultiplexFeature.BatchSellState memory state,
IMultiplexFeature.BatchSellParams memory params,
bytes memory wrappedCallData,
uint256 sellAmount
)
internal
{
// Swallow reverts
(bool success, bytes memory resultData) = _implementation.delegatecall(
abi.encodeWithSelector(
this._batchSellUniswapV2External.selector,
params,
wrappedCallData,
sellAmount
)
);
if (success) {
// Decode the output token amount on success.
uint256 boughtAmount = abi.decode(resultData, (uint256));
// Increment the sold and bought amounts.
state.soldAmount = state.soldAmount.safeAdd(sellAmount);
state.boughtAmount = state.boughtAmount.safeAdd(boughtAmount);
}
}
function _multiHopSellUniswapV2(
IMultiplexFeature.MultiHopSellState memory state,
IMultiplexFeature.MultiHopSellParams memory params,
bytes memory wrappedCallData
)
internal
{
(address[] memory tokens, bool isSushi) = abi.decode(
wrappedCallData,
(address[], bool)
);
// Validate the tokens
require(
tokens.length >= 2 &&
tokens[0] == params.tokens[state.hopIndex] &&
tokens[tokens.length - 1] == params.tokens[state.hopIndex + 1],
"MultiplexUniswapV2::_multiHopSellUniswapV2/INVALID_TOKENS"
);
// Execute the Uniswap/Sushiswap trade.
state.outputTokenAmount = _sellToUniswapV2(
tokens,
state.outputTokenAmount,
isSushi,
state.from,
state.to
);
}
function _sellToUniswapV2(
address[] memory tokens,
uint256 sellAmount,
bool isSushi,
address pairAddress,
address recipient
)
private
returns (uint256 outputTokenAmount)
{
// Iterate through `tokens` perform a swap against the Uniswap
// pair contract for each `(tokens[i], tokens[i+1])`.
for (uint256 i = 0; i < tokens.length - 1; i++) {
(address inputToken, address outputToken) = (tokens[i], tokens[i + 1]);
// Compute the output token amount
outputTokenAmount = _computeUniswapOutputAmount(
pairAddress,
inputToken,
outputToken,
sellAmount
);
(uint256 amount0Out, uint256 amount1Out) = inputToken < outputToken
? (uint256(0), outputTokenAmount)
: (outputTokenAmount, uint256(0));
// The Uniswap pair contract will transfer the output tokens to
// the next pair contract if there is one, otherwise transfer to
// `recipient`.
address to = i < tokens.length - 2
? _computeUniswapPairAddress(outputToken, tokens[i + 2], isSushi)
: recipient;
// Execute the swap.
IUniswapV2Pair(pairAddress).swap(
amount0Out,
amount1Out,
to,
new bytes(0)
);
// To avoid recomputing the pair address of the next pair, store
// `to` in `pairAddress`.
pairAddress = to;
// The outputTokenAmount
sellAmount = outputTokenAmount;
}
}
// Computes the Uniswap/Sushiswap pair contract address for the
// given tokens.
function _computeUniswapPairAddress(
address tokenA,
address tokenB,
bool isSushi
)
internal
view
returns (address pairAddress)
{
// Tokens are lexicographically sorted in the Uniswap contract.
(address token0, address token1) = tokenA < tokenB
? (tokenA, tokenB)
: (tokenB, tokenA);
if (isSushi) {
// Use the Sushiswap factory address and codehash
return address(uint256(keccak256(abi.encodePacked(
hex'ff',
SUSHISWAP_FACTORY,
keccak256(abi.encodePacked(token0, token1)),
SUSHISWAP_PAIR_INIT_CODE_HASH
))));
} else {
// Use the Uniswap factory address and codehash
return address(uint256(keccak256(abi.encodePacked(
hex'ff',
UNISWAP_FACTORY,
keccak256(abi.encodePacked(token0, token1)),
UNISWAP_PAIR_INIT_CODE_HASH
))));
}
}
// 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)
{
// Input amount should be non-zero.
require(
inputAmount > 0,
"MultiplexUniswapV2::_computeUniswapOutputAmount/INSUFFICIENT_INPUT_AMOUNT"
);
// Query the reserves of the pair contract.
(uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pairAddress).getReserves();
// Reserves must be non-zero.
require(
reserve0 > 0 && reserve1 > 0,
'MultiplexUniswapV2::_computeUniswapOutputAmount/INSUFFICIENT_LIQUIDITY'
);
// Tokens are lexicographically sorted in the Uniswap contract.
(uint256 inputReserve, uint256 outputReserve) = inputToken < outputToken
? (reserve0, reserve1)
: (reserve1, reserve0);
// Compute the output amount.
uint256 inputAmountWithFee = inputAmount.safeMul(997);
uint256 numerator = inputAmountWithFee.safeMul(outputReserve);
uint256 denominator = inputReserve.safeMul(1000).safeAdd(inputAmountWithFee);
return numerator / denominator;
}
}

View File

@@ -1,123 +0,0 @@
// 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 "../../fixins/FixinTokenSpender.sol";
import "../interfaces/IMultiplexFeature.sol";
import "../interfaces/IUniswapV3Feature.sol";
abstract contract MultiplexUniswapV3 is
FixinTokenSpender
{
using LibSafeMathV06 for uint256;
function _batchSellUniswapV3(
IMultiplexFeature.BatchSellState memory state,
IMultiplexFeature.BatchSellParams memory params,
bytes memory wrappedCallData,
uint256 sellAmount
)
internal
{
bool success;
bytes memory resultData;
if (params.useSelfBalance) {
// If the tokens are held by `address(this)`, we call
// the `onlySelf` variant `_sellHeldTokenForTokenToUniswapV3`,
// which uses the Exchange Proxy's balance of input token.
(success, resultData) = address(this).call(
abi.encodeWithSelector(
IUniswapV3Feature._sellHeldTokenForTokenToUniswapV3.selector,
wrappedCallData,
sellAmount,
0,
params.recipient
)
);
} else {
// Otherwise, we self-delegatecall the normal variant
// `sellTokenForTokenToUniswapV3`, which pulls the input token
// from `msg.sender`.
(success, resultData) = address(this).delegatecall(
abi.encodeWithSelector(
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
wrappedCallData,
sellAmount,
0,
params.recipient
)
);
}
if (success) {
// Decode the output token amount on success.
uint256 outputTokenAmount = abi.decode(resultData, (uint256));
// Increment the sold and bought amounts.
state.soldAmount = state.soldAmount.safeAdd(sellAmount);
state.boughtAmount = state.boughtAmount.safeAdd(outputTokenAmount);
}
}
function _multiHopSellUniswapV3(
IMultiplexFeature.MultiHopSellState memory state,
bytes memory wrappedCallData
)
internal
{
bool success;
bytes memory resultData;
if (state.from == address(this)) {
// If the tokens are held by `address(this)`, we call
// the `onlySelf` variant `_sellHeldTokenForTokenToUniswapV3`,
// which uses the Exchange Proxy's balance of input token.
(success, resultData) = address(this).call(
abi.encodeWithSelector(
IUniswapV3Feature._sellHeldTokenForTokenToUniswapV3.selector,
wrappedCallData,
state.outputTokenAmount,
0,
state.to
)
);
} else {
// Otherwise, we self-delegatecall the normal variant
// `sellTokenForTokenToUniswapV3`, which pulls the input token
// from `msg.sender`.
(success, resultData) = address(this).delegatecall(
abi.encodeWithSelector(
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
wrappedCallData,
state.outputTokenAmount,
0,
state.to
)
);
}
if (success) {
// Decode the output token amount on success.
state.outputTokenAmount = abi.decode(resultData, (uint256));
} else {
revert("MultiplexUniswapV3::_multiHopSellUniswapV3/SWAP_FAILED");
}
}
}

View File

@@ -52,10 +52,8 @@ abstract contract NativeOrdersSettlement is
bytes32 orderHash;
// Maker of the order.
address maker;
// The address holding the taker tokens.
address payer;
// Recipient of the maker tokens.
address recipient;
// Taker of the order.
address taker;
// Maker token.
IERC20TokenV06 makerToken;
// Taker token.
@@ -84,22 +82,6 @@ abstract contract NativeOrdersSettlement is
address sender;
}
/// @dev Params for `_fillRfqOrderPrivate()`
struct FillRfqOrderPrivateParams {
LibNativeOrder.RfqOrder order;
// The order signature.
LibSignature.Signature signature;
// Maximum taker token to fill this order with.
uint128 takerTokenFillAmount;
// The order taker.
address taker;
// Whether to use the Exchange Proxy's balance
// of taker tokens.
bool useSelfBalance;
// The recipient of the maker tokens.
address recipient;
}
// @dev Fill results returned by `_fillLimitOrderPrivate()` and
/// `_fillRfqOrderPrivate()`.
struct FillNativeOrderResults {
@@ -172,14 +154,12 @@ abstract contract NativeOrdersSettlement is
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
FillNativeOrderResults memory results =
_fillRfqOrderPrivate(FillRfqOrderPrivateParams({
order: order,
signature: signature,
takerTokenFillAmount: takerTokenFillAmount,
taker: msg.sender,
useSelfBalance: false,
recipient: msg.sender
}));
_fillRfqOrderPrivate(
order,
signature,
takerTokenFillAmount,
msg.sender
);
(takerTokenFilledAmount, makerTokenFilledAmount) = (
results.takerTokenFilledAmount,
results.makerTokenFilledAmount
@@ -240,14 +220,12 @@ abstract contract NativeOrdersSettlement is
returns (uint128 makerTokenFilledAmount)
{
FillNativeOrderResults memory results =
_fillRfqOrderPrivate(FillRfqOrderPrivateParams({
order: order,
signature: signature,
takerTokenFillAmount: takerTokenFillAmount,
taker: msg.sender,
useSelfBalance: false,
recipient: msg.sender
}));
_fillRfqOrderPrivate(
order,
signature,
takerTokenFillAmount,
msg.sender
);
// Must have filled exactly the amount requested.
if (results.takerTokenFilledAmount < takerTokenFillAmount) {
LibNativeOrdersRichErrors.FillOrKillFailedError(
@@ -282,36 +260,33 @@ abstract contract NativeOrdersSettlement is
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
FillNativeOrderResults memory results =
_fillLimitOrderPrivate(FillLimitOrderPrivateParams(
order,
signature,
takerTokenFillAmount,
taker,
sender
));
_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.
/// @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.
/// @param useSelfBalance Whether to use the ExchangeProxy's transient
/// balance of taker tokens to fill the order.
/// @param recipient The recipient of the maker tokens.
/// @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,
bool useSelfBalance,
address recipient
address taker
)
public
virtual
@@ -319,14 +294,12 @@ abstract contract NativeOrdersSettlement is
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
FillNativeOrderResults memory results =
_fillRfqOrderPrivate(FillRfqOrderPrivateParams(
_fillRfqOrderPrivate(
order,
signature,
takerTokenFillAmount,
taker,
useSelfBalance,
recipient
));
taker
);
(takerTokenFilledAmount, makerTokenFilledAmount) = (
results.takerTokenFilledAmount,
results.makerTokenFilledAmount
@@ -414,8 +387,7 @@ abstract contract NativeOrdersSettlement is
SettleOrderInfo({
orderHash: orderInfo.orderHash,
maker: params.order.maker,
payer: params.taker,
recipient: params.taker,
taker: params.taker,
makerToken: IERC20TokenV06(params.order.makerToken),
takerToken: IERC20TokenV06(params.order.takerToken),
makerAmount: params.order.makerAmount,
@@ -455,14 +427,22 @@ abstract contract NativeOrdersSettlement is
);
}
/// @dev Fill an RFQ order. Private variant.
/// @param params Function params.
/// @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(FillRfqOrderPrivateParams memory params)
function _fillRfqOrderPrivate(
LibNativeOrder.RfqOrder memory order,
LibSignature.Signature memory signature,
uint128 takerTokenFillAmount,
address taker
)
private
returns (FillNativeOrderResults memory results)
{
LibNativeOrder.OrderInfo memory orderInfo = getRfqOrderInfo(params.order);
LibNativeOrder.OrderInfo memory orderInfo = getRfqOrderInfo(order);
// Must be fillable.
if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
@@ -477,41 +457,32 @@ abstract contract NativeOrdersSettlement is
LibNativeOrdersStorage.getStorage();
// Must be fillable by the tx.origin.
if (
params.order.txOrigin != tx.origin &&
!stor.originRegistry[params.order.txOrigin][tx.origin]
) {
if (order.txOrigin != tx.origin && !stor.originRegistry[order.txOrigin][tx.origin]) {
LibNativeOrdersRichErrors.OrderNotFillableByOriginError(
orderInfo.orderHash,
tx.origin,
params.order.txOrigin
order.txOrigin
).rrevert();
}
}
// Must be fillable by the taker.
if (params.order.taker != address(0) && params.order.taker != params.taker) {
if (order.taker != address(0) && order.taker != taker) {
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
orderInfo.orderHash,
params.taker,
params.order.taker
taker,
order.taker
).rrevert();
}
// Signature must be valid for the order.
{
address signer = LibSignature.getSignerOfHash(
orderInfo.orderHash,
params.signature
);
if (
signer != params.order.maker &&
!isValidOrderSigner(params.order.maker, signer)
) {
address signer = LibSignature.getSignerOfHash(orderInfo.orderHash, signature);
if (signer != order.maker && !isValidOrderSigner(order.maker, signer)) {
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
orderInfo.orderHash,
signer,
params.order.maker
order.maker
).rrevert();
}
}
@@ -520,27 +491,26 @@ abstract contract NativeOrdersSettlement is
(results.takerTokenFilledAmount, results.makerTokenFilledAmount) = _settleOrder(
SettleOrderInfo({
orderHash: orderInfo.orderHash,
maker: params.order.maker,
payer: params.useSelfBalance ? address(this) : params.taker,
recipient: params.recipient,
makerToken: IERC20TokenV06(params.order.makerToken),
takerToken: IERC20TokenV06(params.order.takerToken),
makerAmount: params.order.makerAmount,
takerAmount: params.order.takerAmount,
takerTokenFillAmount: params.takerTokenFillAmount,
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,
params.order.maker,
params.taker,
address(params.order.makerToken),
address(params.order.takerToken),
order.maker,
taker,
address(order.makerToken),
address(order.takerToken),
results.takerTokenFilledAmount,
results.makerTokenFilledAmount,
params.order.pool
order.pool
);
}
@@ -579,28 +549,19 @@ abstract contract NativeOrdersSettlement is
// function if the order is cancelled.
settleInfo.takerTokenFilledAmount.safeAdd128(takerTokenFilledAmount);
if (settleInfo.payer == address(this)) {
// Transfer this -> maker.
_transferERC20Tokens(
settleInfo.takerToken,
settleInfo.maker,
takerTokenFilledAmount
);
} else {
// Transfer taker -> maker.
_transferERC20TokensFrom(
settleInfo.takerToken,
settleInfo.payer,
settleInfo.maker,
takerTokenFilledAmount
);
}
// Transfer taker -> maker.
_transferERC20TokensFrom(
settleInfo.takerToken,
settleInfo.taker,
settleInfo.maker,
takerTokenFilledAmount
);
// Transfer maker -> recipient.
// Transfer maker -> taker.
_transferERC20TokensFrom(
settleInfo.makerToken,
settleInfo.maker,
settleInfo.recipient,
settleInfo.taker,
makerTokenFilledAmount
);
}

View File

@@ -137,8 +137,8 @@ contract FillQuoteTransformer is
/// @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 transform recipient.
address private constant REFUND_RECEIVER_RECIPIENT = address(1);
/// protocol fees will be sent to the taker.
address private constant REFUND_RECEIVER_TAKER = address(1);
/// @dev If `refundReceiver` is set to this address, unpsent
/// protocol fees will be sent to the sender.
address private constant REFUND_RECEIVER_SENDER = address(2);
@@ -272,8 +272,8 @@ contract FillQuoteTransformer is
// Refund unspent protocol fees.
if (state.ethRemaining > 0 && data.refundReceiver != address(0)) {
bool transferSuccess;
if (data.refundReceiver == REFUND_RECEIVER_RECIPIENT) {
(transferSuccess,) = context.recipient.call{value: state.ethRemaining}("");
if (data.refundReceiver == REFUND_RECEIVER_TAKER) {
(transferSuccess,) = context.taker.call{value: state.ethRemaining}("");
} else if (data.refundReceiver == REFUND_RECEIVER_SENDER) {
(transferSuccess,) = context.sender.call{value: state.ethRemaining}("");
} else {

View File

@@ -30,9 +30,9 @@ interface IERC20Transformer {
struct TransformContext {
// The caller of `TransformERC20.transformERC20()`.
address payable sender;
// The recipient address, which may be distinct from `sender` e.g. in
// taker The taker address, which may be distinct from `sender` in the case
// meta-transactions.
address payable recipient;
address payable taker;
// Arbitrary data to pass to the transformer.
bytes data;
}

View File

@@ -41,7 +41,7 @@ contract LogMetadataTransformer is
override
returns (bytes4 success)
{
emit TransformerMetadata(context.sender, context.recipient, context.data);
emit TransformerMetadata(context.sender, context.taker, context.data);
return LibERC20Transformer.TRANSFORMER_SUCCESS;
}
}

View File

@@ -75,7 +75,7 @@ contract PayTakerTransformer is
amount = data.tokens[i].getTokenBalanceOf(address(this));
}
if (amount != 0) {
data.tokens[i].transformerTransfer(context.recipient, amount);
data.tokens[i].transformerTransfer(context.taker, amount);
}
}
return LibERC20Transformer.TRANSFORMER_SUCCESS;

View File

@@ -25,7 +25,7 @@ import "./BridgeProtocols.sol";
import "./mixins/MixinBalancer.sol";
import "./mixins/MixinBalancerV2.sol";
import "./mixins/MixinBancor.sol";
import "./mixins/MixinClipper.sol";
import "./mixins/MixinBooster.sol";
import "./mixins/MixinCoFiX.sol";
import "./mixins/MixinCurve.sol";
import "./mixins/MixinCurveV2.sol";
@@ -51,7 +51,7 @@ contract BridgeAdapter is
MixinBalancer,
MixinBalancerV2,
MixinBancor,
MixinClipper,
MixinBooster,
MixinCoFiX,
MixinCurve,
MixinCurveV2,
@@ -77,7 +77,7 @@ contract BridgeAdapter is
MixinBalancer()
MixinBalancerV2()
MixinBancor(weth)
MixinClipper(weth)
MixinBooster()
MixinCoFiX()
MixinCurve(weth)
MixinCurveV2()
@@ -248,8 +248,8 @@ contract BridgeAdapter is
sellAmount,
order.bridgeData
);
} else if (protocolId == BridgeProtocols.CLIPPER) {
boughtAmount = _tradeClipper(
} else if (protocolId == BridgeProtocols.BOOSTER) {
boughtAmount = _tradeBooster(
sellToken,
buyToken,
sellAmount,

View File

@@ -49,5 +49,5 @@ library BridgeProtocols {
uint128 internal constant KYBERDMM = 19;
uint128 internal constant CURVEV2 = 20;
uint128 internal constant LIDO = 21;
uint128 internal constant CLIPPER = 22;
uint128 internal constant BOOSTER = 22;
}

View File

@@ -39,6 +39,13 @@ interface IBancorNetwork {
external
payable
returns (uint256);
function conversionPath(address _sourceToken, address _targetToken) external view returns (address[] memory);
function rateByPath(address[] memory _path, uint256 _amount) external view returns (uint256);
}
interface IBancorRegistry {
function getAddress(bytes32 _contractName) external view returns (address);
function BANCOR_NETWORK() external view returns (bytes32);
}
@@ -62,6 +69,18 @@ contract MixinBancor {
)
internal
returns (uint256 boughtAmount)
{
return _tradeBancorInternal(WETH, buyToken, sellAmount, bridgeData);
}
function _tradeBancorInternal(
IEtherTokenV06 weth,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data.
IBancorNetwork bancorNetworkAddress;
@@ -79,7 +98,7 @@ contract MixinBancor {
require(path.length >= 2, "MixinBancor/PATH_LENGTH_MUST_BE_AT_LEAST_TWO");
require(
path[path.length - 1] == buyToken ||
(path[path.length - 1] == BANCOR_ETH_ADDRESS && buyToken == WETH),
(path[path.length - 1] == BANCOR_ETH_ADDRESS && buyToken == weth),
"MixinBancor/LAST_ELEMENT_OF_PATH_MUST_MATCH_OUTPUT_TOKEN"
);
@@ -88,7 +107,7 @@ contract MixinBancor {
// The Bancor path will have ETH as the 0xeee address
// Bancor expects to be paid in ETH not WETH
if (path[0] == BANCOR_ETH_ADDRESS) {
WETH.withdraw(sellAmount);
weth.withdraw(sellAmount);
payableAmount = sellAmount;
} else {
// Grant an allowance to the Bancor Network.
@@ -109,7 +128,7 @@ contract MixinBancor {
0 // affiliateFee; no fee paid
);
if (path[path.length - 1] == BANCOR_ETH_ADDRESS) {
WETH.deposit{value: boughtAmount}();
weth.deposit{value: boughtAmount}();
}
return boughtAmount;

View File

@@ -0,0 +1,68 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 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/LibERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "../IBridgeAdapter.sol";
interface IBooster {
function swap(
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
address recipient
)
external
returns (uint256);
}
contract MixinBooster {
using LibERC20TokenV06 for IERC20TokenV06;
function _tradeBooster(
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data.
(IBooster pool) = abi.decode(bridgeData, (IBooster));
// Grant the pool an allowance.
sellToken.approveIfBelow(address(pool), sellAmount);
// Convert the tokens
boughtAmount = pool.swap(
sellToken,
buyToken,
sellAmount,
address(this)
);
return boughtAmount;
}
}

View File

@@ -1,148 +0,0 @@
// 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/LibERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "../IBridgeAdapter.sol";
import "../../../vendor/ILiquidityProvider.sol";
contract MixinClipper {
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Mainnet address of the WETH contract.
IEtherTokenV06 private immutable WETH;
constructor(IEtherTokenV06 weth)
public
{
WETH = weth;
}
function _tradeClipper(
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// We can only use ETH with Clipper, no WETH available
(ILiquidityProvider clipper, bytes memory auxiliaryData) =
abi.decode(bridgeData, (ILiquidityProvider, bytes));
if (sellToken == WETH) {
boughtAmount = _executeSellEthForToken(
clipper,
buyToken,
sellAmount,
auxiliaryData
);
} else if (buyToken == WETH) {
boughtAmount = _executeSellTokenForEth(
clipper,
sellToken,
sellAmount,
auxiliaryData
);
} else {
boughtAmount = _executeSellTokenForToken(
clipper,
sellToken,
buyToken,
sellAmount,
auxiliaryData
);
}
return boughtAmount;
}
function _executeSellEthForToken(
ILiquidityProvider clipper,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory auxiliaryData
)
private
returns (uint256 boughtAmount)
{
// Clipper requires ETH and doesn't support WETH
WETH.withdraw(sellAmount);
boughtAmount = clipper.sellEthForToken{ value: sellAmount }(
buyToken,
address(this),
1,
auxiliaryData
);
}
function _executeSellTokenForEth(
ILiquidityProvider clipper,
IERC20TokenV06 sellToken,
uint256 sellAmount,
bytes memory auxiliaryData
)
private
returns (uint256 boughtAmount)
{
// Optimization: We can transfer the tokens into clipper rather than
// have an allowance updated
sellToken.compatTransfer(address(clipper), sellAmount);
boughtAmount = clipper.sellTokenForEth(
sellToken,
payable(address(this)),
1,
auxiliaryData
);
// we want WETH for possible future trades
WETH.deposit{ value: boughtAmount }();
}
function _executeSellTokenForToken(
ILiquidityProvider clipper,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory auxiliaryData
)
private
returns (uint256 boughtAmount)
{
// Optimization: We can transfer the tokens into clipper rather than
// have an allowance updated
sellToken.compatTransfer(address(clipper), sellAmount);
boughtAmount = clipper.sellTokenForToken(
sellToken,
buyToken,
address(this),
1,
auxiliaryData
);
}
}

View File

@@ -57,13 +57,26 @@ contract MixinCurve {
)
internal
returns (uint256 boughtAmount)
{
return _tradeCurveInternal(WETH, sellToken, buyToken, sellAmount, bridgeData);
}
function _tradeCurveInternal(
IEtherTokenV06 weth,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the Curve metadata.
CurveBridgeData memory data = abi.decode(bridgeData, (CurveBridgeData));
uint256 payableAmount;
if (sellToken == WETH) {
if (sellToken == weth) {
payableAmount = sellAmount;
WETH.withdraw(sellAmount);
weth.withdraw(sellAmount);
} else {
sellToken.approveIfBelow(data.curveAddress, sellAmount);
}
@@ -83,9 +96,9 @@ contract MixinCurve {
resultData.rrevert();
}
if (buyToken == WETH) {
if (buyToken == weth) {
boughtAmount = address(this).balance;
WETH.deposit{ value: boughtAmount }();
weth.deposit{ value: boughtAmount }();
}
return buyToken.balanceOf(address(this)).safeSub(beforeBalance);

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
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.

View File

@@ -52,6 +52,7 @@ interface IKyberNetworkProxy {
external
payable
returns (uint256 boughtAmount);
}
contract MixinKyber {
@@ -78,12 +79,26 @@ contract MixinKyber {
)
internal
returns (uint256 boughtAmount)
{
return _tradeKyberInternal(KYBER_ETH_ADDRESS, WETH, sellToken, buyToken, sellAmount, bridgeData);
}
function _tradeKyberInternal(
IERC20TokenV06 kyberEthAddress,
IEtherTokenV06 weth,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
(IKyberNetworkProxy kyber, bytes memory hint) =
abi.decode(bridgeData, (IKyberNetworkProxy, bytes));
uint256 payableAmount = 0;
if (sellToken != WETH) {
if (sellToken != weth) {
// If the input token is not WETH, grant an allowance to the exchange
// to spend them.
sellToken.approveIfBelow(
@@ -93,18 +108,18 @@ contract MixinKyber {
} else {
// If the input token is WETH, unwrap it and attach it to the call.
payableAmount = sellAmount;
WETH.withdraw(payableAmount);
weth.withdraw(payableAmount);
}
// Try to sell all of this contract's input token balance through
// `KyberNetworkProxy.trade()`.
boughtAmount = kyber.tradeWithHint{ value: payableAmount }(
// Input token.
sellToken == WETH ? KYBER_ETH_ADDRESS : sellToken,
sellToken == weth ? kyberEthAddress : sellToken,
// Sell amount.
sellAmount,
// Output token.
buyToken == WETH ? KYBER_ETH_ADDRESS : buyToken,
buyToken == weth ? kyberEthAddress : buyToken,
// Transfer to this contract
address(uint160(address(this))),
// Buy as much as possible.
@@ -116,8 +131,8 @@ contract MixinKyber {
hint
);
// If receving ETH, wrap it to WETH.
if (buyToken == WETH) {
WETH.deposit{ value: boughtAmount }();
if (buyToken == weth) {
weth.deposit{ value: boughtAmount }();
}
return boughtAmount;
}

View File

@@ -30,6 +30,8 @@ import "../IBridgeAdapter.sol";
*/
interface IKyberDmmRouter {
function factory() external view returns (address);
/// @dev Swaps an exact amount of input tokens for as many output tokens as possible, along the route determined by the path.
/// The first element of path is the input token, the last is the output token, and any intermediate elements represent
/// intermediate pairs to trade through (if, for example, a direct pair does not exist).

View File

@@ -58,10 +58,23 @@ contract MixinLido {
)
internal
returns (uint256 boughtAmount)
{
return _tradeLidoInternal(WETH, sellToken, buyToken, sellAmount, bridgeData);
}
function _tradeLidoInternal(
IEtherTokenV06 weth,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
(ILido lido) = abi.decode(bridgeData, (ILido));
if (address(sellToken) == address(WETH) && address(buyToken) == address(lido)) {
WETH.withdraw(sellAmount);
if (address(sellToken) == address(weth) && address(buyToken) == address(lido)) {
weth.withdraw(sellAmount);
boughtAmount = lido.getPooledEthByShares(lido.submit{ value: sellAmount}(address(0)));
} else {
revert("MixinLido/UNSUPPORTED_TOKEN_PAIR");

View File

@@ -66,12 +66,26 @@ contract MixinMooniswap {
internal
returns (uint256 boughtAmount)
{
return _tradeMooniswapInternal(WETH, sellToken, buyToken, sellAmount, bridgeData);
}
function _tradeMooniswapInternal(
IEtherTokenV06 weth,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
(IMooniswapPool pool) = abi.decode(bridgeData, (IMooniswapPool));
// Convert WETH to ETH.
uint256 ethValue = 0;
if (sellToken == WETH) {
WETH.withdraw(sellAmount);
if (sellToken == weth) {
weth.withdraw(sellAmount);
ethValue = sellAmount;
} else {
// Grant the pool an allowance.
@@ -82,16 +96,16 @@ contract MixinMooniswap {
}
boughtAmount = pool.swap{value: ethValue}(
sellToken == WETH ? IERC20TokenV06(0) : sellToken,
buyToken == WETH ? IERC20TokenV06(0) : buyToken,
sellToken == weth ? IERC20TokenV06(0) : sellToken,
buyToken == weth ? IERC20TokenV06(0) : buyToken,
sellAmount,
1,
address(0)
);
// Wrap ETH to WETH.
if (buyToken == WETH) {
WETH.deposit{value:boughtAmount}();
if (buyToken == weth) {
weth.deposit{value:boughtAmount}();
}
}
}

View File

@@ -124,21 +124,35 @@ contract MixinUniswap {
)
internal
returns (uint256 boughtAmount)
{
return _tradeUniswapInternal(WETH, sellToken, buyToken, sellAmount, bridgeData);
}
function _tradeUniswapInternal(
IEtherTokenV06 weth,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
IUniswapExchangeFactory exchangeFactory =
abi.decode(bridgeData, (IUniswapExchangeFactory));
// Get the exchange for the token pair.
IUniswapExchange exchange = _getUniswapExchangeForTokenPair(
weth,
exchangeFactory,
sellToken,
buyToken
);
// Convert from WETH to a token.
if (sellToken == WETH) {
if (sellToken == weth) {
// Unwrap the WETH.
WETH.withdraw(sellAmount);
weth.withdraw(sellAmount);
// Buy as much of `buyToken` token with ETH as possible
boughtAmount = exchange.ethToTokenTransferInput{ value: sellAmount }(
// Minimum buy amount.
@@ -150,7 +164,7 @@ contract MixinUniswap {
);
// Convert from a token to WETH.
} else if (buyToken == WETH) {
} else if (buyToken == weth) {
// Grant the exchange an allowance.
sellToken.approveIfBelow(
address(exchange),
@@ -166,7 +180,7 @@ contract MixinUniswap {
block.timestamp
);
// Wrap the ETH.
WETH.deposit{ value: boughtAmount }();
weth.deposit{ value: boughtAmount }();
// Convert from one token to another.
} else {
// Grant the exchange an allowance.
@@ -200,6 +214,7 @@ contract MixinUniswap {
/// @param buyToken The address of the token we are converting to.
/// @return exchange The uniswap exchange.
function _getUniswapExchangeForTokenPair(
IEtherTokenV06 weth,
IUniswapExchangeFactory exchangeFactory,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken
@@ -209,7 +224,7 @@ contract MixinUniswap {
returns (IUniswapExchange exchange)
{
// Whichever isn't WETH is the exchange token.
exchange = sellToken == WETH
exchange = sellToken == weth
? exchangeFactory.getExchange(buyToken)
: exchangeFactory.getExchange(sellToken);
require(address(exchange) != address(0), "MixinUniswap/NO_EXCHANGE");

View File

@@ -33,7 +33,7 @@ contract TestFillQuoteTransformerHost is
TestMintableERC20Token inputToken,
uint256 inputTokenAmount,
address payable sender,
address payable recipient,
address payable taker,
bytes calldata data
)
external
@@ -47,7 +47,7 @@ contract TestFillQuoteTransformerHost is
transformer,
IERC20Transformer.TransformContext({
sender: sender,
recipient: recipient,
taker: taker,
data: data
})
);

View File

@@ -46,6 +46,16 @@ contract TestLiquidityProvider {
uint256 inputTokenBalance
);
IERC20TokenV06 public immutable xAsset;
IERC20TokenV06 public immutable yAsset;
constructor(IERC20TokenV06 xAsset_, IERC20TokenV06 yAsset_)
public
{
xAsset = xAsset_;
yAsset = yAsset_;
}
receive() external payable {}
/// @dev Trades `inputToken` for `outputToken`. The amount of `inputToken`
@@ -73,8 +83,6 @@ contract TestLiquidityProvider {
minBuyAmount,
IERC20TokenV06(inputToken).balanceOf(address(this))
);
uint256 outputTokenBalance = IERC20TokenV06(outputToken).balanceOf(address(this));
IERC20TokenV06(outputToken).transfer(recipient, outputTokenBalance);
}
/// @dev Trades ETH for token. ETH must be sent to the contract prior to
@@ -98,8 +106,6 @@ contract TestLiquidityProvider {
minBuyAmount,
address(this).balance
);
uint256 outputTokenBalance = IERC20TokenV06(outputToken).balanceOf(address(this));
IERC20TokenV06(outputToken).transfer(recipient, outputTokenBalance);
}
/// @dev Trades token for ETH. The token must be sent to the contract prior
@@ -123,6 +129,5 @@ contract TestLiquidityProvider {
minBuyAmount,
IERC20TokenV06(inputToken).balanceOf(address(this))
);
recipient.transfer(address(this).balance);
}
}

View File

@@ -89,9 +89,7 @@ contract TestMetaTransactionsNativeOrdersFeature is
LibNativeOrder.RfqOrder memory order,
LibSignature.Signature memory signature,
uint128 takerTokenFillAmount,
address taker,
bool /* useSelfBalance */,
address /* recipient */
address taker
)
public
override

View File

@@ -57,7 +57,7 @@ contract TestMintTokenERC20Transformer is
address(this),
msg.sender,
context.sender,
context.recipient,
context.taker,
context.data,
LibERC20Transformer.isTokenETH(data.inputToken)
? address(this).balance
@@ -71,22 +71,15 @@ contract TestMintTokenERC20Transformer is
data.inputToken.transfer(address(0), data.burnAmount);
}
// Mint output tokens.
if (!LibERC20Transformer.isTokenETH(IERC20TokenV06(address(data.outputToken)))) {
if (data.feeAmount > data.mintAmount) {
data.outputToken.burn(
context.recipient,
data.feeAmount - data.mintAmount
);
} else {
data.outputToken.mint(
address(this),
data.mintAmount
);
data.outputToken.burn(
context.recipient,
data.feeAmount
);
}
if (LibERC20Transformer.isTokenETH(IERC20TokenV06(address(data.outputToken)))) {
context.taker.transfer(data.mintAmount);
} else {
data.outputToken.mint(
context.taker,
data.mintAmount
);
// Burn fees from output.
data.outputToken.burn(context.taker, data.feeAmount);
}
return LibERC20Transformer.TRANSFORMER_SUCCESS;
}

View File

@@ -22,12 +22,6 @@ pragma experimental ABIEncoderV2;
contract TestMintableERC20Token {
event Transfer(
address token,
address from,
address to,
uint256 value
);
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
@@ -87,7 +81,6 @@ contract TestMintableERC20Token {
require(balanceOf[from] >= amount, "TestMintableERC20Token/INSUFFICIENT_FUNDS");
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(address(this), from, to, amount);
return true;
}

View File

@@ -1,44 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "./TestUniswapV2Pool.sol";
contract TestUniswapV2Factory {
struct CreationParameters {
IERC20TokenV06 token0;
IERC20TokenV06 token1;
}
event PoolCreated(TestUniswapV2Pool pool);
bytes32 public immutable POOL_INIT_CODE_HASH;
mapping (IERC20TokenV06 => mapping (IERC20TokenV06 => TestUniswapV2Pool)) public getPool;
CreationParameters public creationParameters;
constructor() public {
POOL_INIT_CODE_HASH = keccak256(type(TestUniswapV2Pool).creationCode);
}
function createPool(IERC20TokenV06 tokenA, IERC20TokenV06 tokenB)
external
returns (TestUniswapV2Pool pool)
{
(IERC20TokenV06 token0, IERC20TokenV06 token1) = tokenA < tokenB
? (tokenA, tokenB)
: (tokenB, tokenA);
require(
getPool[token0][token1] == TestUniswapV2Pool(0),
"TestUniswapV2Factory/POOL_ALREADY_EXISTS"
);
creationParameters = CreationParameters({
token0: token0,
token1: token1
});
pool = new TestUniswapV2Pool
{ salt: keccak256(abi.encodePacked(token0, token1)) }();
getPool[token0][token1] = pool;
getPool[token1][token0] = pool;
emit PoolCreated(pool);
}
}

View File

@@ -1,67 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "../src/vendor/IUniswapV2Pair.sol";
interface IUniswapV2PoolDeployer {
struct CreationParameters {
IERC20TokenV06 token0;
IERC20TokenV06 token1;
}
function creationParameters() external view returns (CreationParameters memory);
}
contract TestUniswapV2Pool is IUniswapV2Pair {
IERC20TokenV06 public immutable token0;
IERC20TokenV06 public immutable token1;
uint112 reserve0;
uint112 reserve1;
uint32 blockTimestampLast;
constructor() public {
IUniswapV2PoolDeployer.CreationParameters memory params =
IUniswapV2PoolDeployer(msg.sender).creationParameters();
(token0, token1) = (params.token0, params.token1);
}
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata /* data */
)
external
override
{
if (amount0Out > 0) {
token0.transfer(to, amount0Out);
}
if (amount1Out > 0) {
token1.transfer(to, amount1Out);
}
}
function setReserves(
uint112 reserve0_,
uint112 reserve1_,
uint32 blockTimestampLast_
)
external
{
reserve0 = reserve0_;
reserve1 = reserve1_;
blockTimestampLast = blockTimestampLast_;
}
function getReserves()
external
override
view
returns (uint112, uint112, uint32)
{
return (reserve0, reserve1, blockTimestampLast);
}
}

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