Compare commits
122 Commits
protocol@e
...
protocol@1
Author | SHA1 | Date | |
---|---|---|---|
|
11dfea47a6 | ||
|
55e9dd39a2 | ||
|
1993929bed | ||
|
e1d81de517 | ||
|
a6b3a21635 | ||
|
fd59cdc2db | ||
|
98e11b5189 | ||
|
3bebc7cd62 | ||
|
56dab6ae8c | ||
|
285f98e9e9 | ||
|
8ae9f59f20 | ||
|
4c341c5ca3 | ||
|
a3c912c2af | ||
|
5e72eb9af9 | ||
|
9b08b73c06 | ||
|
76c7eb7c3e | ||
|
9b2e5a3adb | ||
|
813d703d12 | ||
|
83005d0f3d | ||
|
d07ffd2688 | ||
|
4170f970d0 | ||
|
dcde12dd70 | ||
|
84a60ec982 | ||
|
9615570dc6 | ||
|
880a9c3da0 | ||
|
c44bd9d42d | ||
|
90b441330b | ||
|
e12ed1eddf | ||
|
ac94023ab3 | ||
|
281e6acca5 | ||
|
2f1b520409 | ||
|
21b4eb3d26 | ||
|
644f6c7d28 | ||
|
0647b9e4f8 | ||
|
82d42eeede | ||
|
6ef4d95043 | ||
|
6044140f86 | ||
|
4da1ef0f56 | ||
|
8c668a3918 | ||
|
f1ff1cde39 | ||
|
1668a24e31 | ||
|
4b7c376d96 | ||
|
bb4a9c656c | ||
|
6ab07b6304 | ||
|
8903f1ab01 | ||
|
415535612a | ||
|
5248a135c3 | ||
|
602290925c | ||
|
01813746e8 | ||
|
7d668d8801 | ||
|
797a00a33a | ||
|
4b3d98f43c | ||
|
9ff77c1cd5 | ||
|
18a8351671 | ||
|
9a994dfcd3 | ||
|
b7adc5a889 | ||
|
10b0d7f363 | ||
|
e2c905a15f | ||
|
8589ba728c | ||
|
43512fd07a | ||
|
c090608d99 | ||
|
89817428ed | ||
|
fce3664258 | ||
|
aa522fe49b | ||
|
c03653ebd7 | ||
|
ae08f77381 | ||
|
73a07e512d | ||
|
7cff09f40a | ||
|
a6d690f10a | ||
|
d7cff52e75 | ||
|
cf1f29a37d | ||
|
9af22110b4 | ||
|
2c187c7e85 | ||
|
d46756ae2e | ||
|
d9a16ed1f9 | ||
|
57a1120997 | ||
|
0945d4cef2 | ||
|
8f6f7ad453 | ||
|
bb4fad37fa | ||
|
d06daf2957 | ||
|
34314960ef | ||
|
832ba737ec | ||
|
6950fdbebb | ||
|
1849b1bb9a | ||
|
a883139220 | ||
|
bb2c26cb94 | ||
|
ba09a0b2bf | ||
|
b2d54f0238 | ||
|
4a3096495b | ||
|
23f6e9e53c | ||
|
d7dbc0576d | ||
|
15fb00e958 | ||
|
1d9295cc94 | ||
|
79f36cf6fb | ||
|
6ce4458a5d | ||
|
fad6e65c07 | ||
|
840c85373e | ||
|
0479bb5fe1 | ||
|
9ecc31ed54 | ||
|
eb29abd36c | ||
|
d202e01522 | ||
|
e838a6801b | ||
|
7439871aa0 | ||
|
317f2138c5 | ||
|
e5834f1901 | ||
|
5063446f93 | ||
|
0caf495a1a | ||
|
a20de0fc69 | ||
|
9aa0065d2d | ||
|
c24855e627 | ||
|
6bb72dd775 | ||
|
77d1ed257c | ||
|
5d265360c4 | ||
|
c9097f6e8b | ||
|
d3df985a42 | ||
|
7267420874 | ||
|
17e81432f1 | ||
|
57c767c3b1 | ||
|
dbb1c88ad9 | ||
|
254b850a8b | ||
|
e2242e5955 | ||
|
7a0255fee7 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -75,8 +75,9 @@ generated_docs/
|
||||
|
||||
TODO.md
|
||||
|
||||
# VSCode file
|
||||
# IDE file
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# generated contract artifacts/
|
||||
contracts/broker/generated-artifacts/
|
||||
|
@@ -1,4 +1,76 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1640364306,
|
||||
"version": "3.3.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1638390144,
|
||||
"version": "3.3.24",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1637102971,
|
||||
"version": "3.3.23",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "3.3.22",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1634668033,
|
||||
"version": "3.3.21",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1631710679,
|
||||
"version": "3.3.20",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1631120757,
|
||||
"version": "3.3.19",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1630459879,
|
||||
"version": "3.3.18",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1629353596,
|
||||
"version": "3.3.17",
|
||||
|
@@ -5,6 +5,38 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.3.25 - _December 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.24 - _December 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.23 - _November 16, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.22 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.21 - _October 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.20 - _September 15, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.19 - _September 8, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.18 - _September 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.17 - _August 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-erc20",
|
||||
"version": "3.3.17",
|
||||
"version": "3.3.25",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -51,18 +51,18 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/tokens",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.6.0",
|
||||
"@0x/contracts-gen": "^2.0.38",
|
||||
"@0x/contracts-test-utils": "^5.4.9",
|
||||
"@0x/contracts-utils": "^4.7.17",
|
||||
"@0x/dev-utils": "^4.2.7",
|
||||
"@0x/sol-compiler": "^4.7.3",
|
||||
"@0x/abi-gen": "^5.6.2",
|
||||
"@0x/contracts-gen": "^2.0.40",
|
||||
"@0x/contracts-test-utils": "^5.4.16",
|
||||
"@0x/contracts-utils": "^4.8.6",
|
||||
"@0x/dev-utils": "^4.2.9",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.4",
|
||||
"@0x/types": "^3.3.3",
|
||||
"@0x/typescript-typings": "^5.2.0",
|
||||
"@0x/utils": "^6.4.3",
|
||||
"@0x/web3-wrapper": "^7.5.3",
|
||||
"@0x/types": "^3.3.4",
|
||||
"@0x/typescript-typings": "^5.2.1",
|
||||
"@0x/utils": "^6.4.4",
|
||||
"@0x/web3-wrapper": "^7.6.0",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "12.12.54",
|
||||
@@ -70,7 +70,7 @@
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-bignumber": "^3.0.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"ethereum-types": "^3.5.0",
|
||||
"ethereum-types": "^3.6.0",
|
||||
"lodash": "^4.17.11",
|
||||
"make-promises-safe": "^1.1.0",
|
||||
"mocha": "^6.2.0",
|
||||
@@ -82,7 +82,7 @@
|
||||
"typescript": "4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.4.0",
|
||||
"@0x/base-contract": "^6.4.2",
|
||||
"ethers": "~4.0.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
@@ -1,4 +1,67 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1640364306,
|
||||
"version": "5.4.16",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1638390144,
|
||||
"version": "5.4.15",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1637102971,
|
||||
"version": "5.4.14",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "5.4.13",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1634668033,
|
||||
"version": "5.4.12",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1631710679,
|
||||
"version": "5.4.11",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1630459879,
|
||||
"version": "5.4.10",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1629353596,
|
||||
"version": "5.4.9",
|
||||
|
@@ -5,6 +5,34 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v5.4.16 - _December 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.15 - _December 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.14 - _November 16, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.13 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.12 - _October 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.11 - _September 15, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.10 - _September 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.9 - _August 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-test-utils",
|
||||
"version": "5.4.9",
|
||||
"version": "5.4.16",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/test-utils",
|
||||
"devDependencies": {
|
||||
"@0x/sol-compiler": "^4.7.3",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
"@0x/tslint-config": "^4.1.4",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"shx": "^0.2.2",
|
||||
@@ -42,20 +42,20 @@
|
||||
"typescript": "4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.27",
|
||||
"@0x/base-contract": "^6.4.0",
|
||||
"@0x/contract-addresses": "^6.6.1",
|
||||
"@0x/dev-utils": "^4.2.7",
|
||||
"@0x/json-schemas": "^6.1.3",
|
||||
"@0x/assert": "^3.0.29",
|
||||
"@0x/base-contract": "^6.4.2",
|
||||
"@0x/contract-addresses": "^6.11.0",
|
||||
"@0x/dev-utils": "^4.2.9",
|
||||
"@0x/json-schemas": "^6.3.0",
|
||||
"@0x/order-utils": "^10.4.28",
|
||||
"@0x/sol-coverage": "^4.0.37",
|
||||
"@0x/sol-profiler": "^4.1.27",
|
||||
"@0x/sol-trace": "^3.0.37",
|
||||
"@0x/subproviders": "^6.5.3",
|
||||
"@0x/types": "^3.3.3",
|
||||
"@0x/typescript-typings": "^5.2.0",
|
||||
"@0x/utils": "^6.4.3",
|
||||
"@0x/web3-wrapper": "^7.5.3",
|
||||
"@0x/sol-coverage": "^4.0.39",
|
||||
"@0x/sol-profiler": "^4.1.29",
|
||||
"@0x/sol-trace": "^3.0.39",
|
||||
"@0x/subproviders": "^6.6.0",
|
||||
"@0x/types": "^3.3.4",
|
||||
"@0x/typescript-typings": "^5.2.1",
|
||||
"@0x/utils": "^6.4.4",
|
||||
"@0x/web3-wrapper": "^7.6.0",
|
||||
"@types/bn.js": "^4.11.0",
|
||||
"@types/js-combinatorics": "^0.5.29",
|
||||
"@types/lodash": "4.14.104",
|
||||
@@ -67,7 +67,7 @@
|
||||
"chai-bignumber": "^3.0.0",
|
||||
"decimal.js": "^10.2.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"ethereum-types": "^3.5.0",
|
||||
"ethereum-types": "^3.6.0",
|
||||
"ethereumjs-util": "^7.0.10",
|
||||
"ethers": "~4.0.4",
|
||||
"js-combinatorics": "^0.5.3",
|
||||
|
@@ -1,4 +1,103 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1640364306,
|
||||
"version": "1.4.8",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1638390144,
|
||||
"version": "1.4.7",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1637102971,
|
||||
"version": "1.4.6",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "1.4.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1634668033,
|
||||
"version": "1.4.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1634147078,
|
||||
"version": "1.4.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1633374058,
|
||||
"version": "1.4.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1632957537,
|
||||
"version": "1.4.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.4.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Support cast vote by signature in Treasury"
|
||||
}
|
||||
],
|
||||
"timestamp": 1631710679
|
||||
},
|
||||
{
|
||||
"timestamp": 1631120757,
|
||||
"version": "1.3.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1630459879,
|
||||
"version": "1.3.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1629353596,
|
||||
"version": "1.3.3",
|
||||
|
@@ -5,6 +5,50 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.4.8 - _December 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.7 - _December 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.6 - _November 16, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.5 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.4 - _October 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.3 - _October 13, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.2 - _October 4, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.1 - _September 29, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.0 - _September 15, 2021_
|
||||
|
||||
* Support cast vote by signature in Treasury
|
||||
|
||||
## v1.3.5 - _September 8, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.3.4 - _September 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.3.3 - _August 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
60
contracts/treasury/contracts/external/ISablier.sol
vendored
Normal file
60
contracts/treasury/contracts/external/ISablier.sol
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
/**
|
||||
* @title ISablier
|
||||
* @author Sablier
|
||||
*/
|
||||
interface ISablier {
|
||||
/**
|
||||
* @notice Emits when a stream is successfully created.
|
||||
*/
|
||||
event CreateStream(
|
||||
uint256 indexed streamId,
|
||||
address indexed sender,
|
||||
address indexed recipient,
|
||||
uint256 deposit,
|
||||
address tokenAddress,
|
||||
uint256 startTime,
|
||||
uint256 stopTime
|
||||
);
|
||||
|
||||
/**
|
||||
* @notice Emits when the recipient of a stream withdraws a portion or all their pro rata share of the stream.
|
||||
*/
|
||||
event WithdrawFromStream(uint256 indexed streamId, address indexed recipient, uint256 amount);
|
||||
|
||||
/**
|
||||
* @notice Emits when a stream is successfully cancelled and tokens are transferred back on a pro rata basis.
|
||||
*/
|
||||
event CancelStream(
|
||||
uint256 indexed streamId,
|
||||
address indexed sender,
|
||||
address indexed recipient,
|
||||
uint256 senderBalance,
|
||||
uint256 recipientBalance
|
||||
);
|
||||
|
||||
function balanceOf(uint256 streamId, address who) external view returns (uint256 balance);
|
||||
|
||||
function getStream(uint256 streamId)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
address sender,
|
||||
address recipient,
|
||||
uint256 deposit,
|
||||
address token,
|
||||
uint256 startTime,
|
||||
uint256 stopTime,
|
||||
uint256 remainingBalance,
|
||||
uint256 ratePerSecond
|
||||
);
|
||||
|
||||
function createStream(address recipient, uint256 deposit, address tokenAddress, uint256 startTime, uint256 stopTime)
|
||||
external
|
||||
returns (uint256 streamId);
|
||||
|
||||
function withdrawFromStream(uint256 streamId, uint256 funds) external returns (bool);
|
||||
|
||||
function cancelStream(uint256 streamId) external returns (bool);
|
||||
}
|
@@ -136,8 +136,9 @@ interface IZrxTreasury {
|
||||
returns (uint256 proposalId);
|
||||
|
||||
/// @dev Casts a vote for the given proposal. Only callable
|
||||
/// during the voting period for that proposal. See
|
||||
/// `getVotingPower` for how voting power is computed.
|
||||
/// during the voting period for that proposal.
|
||||
/// One address can only vote once.
|
||||
/// See `getVotingPower` for how voting power is computed.
|
||||
/// @param proposalId The ID of the proposal to vote on.
|
||||
/// @param support Whether to support the proposal or not.
|
||||
/// @param operatedPoolIds The pools operated by `msg.sender`. The
|
||||
@@ -150,6 +151,28 @@ interface IZrxTreasury {
|
||||
)
|
||||
external;
|
||||
|
||||
/// @dev Casts a vote for the given proposal, by signature.
|
||||
/// Only callable during the voting period for that proposal.
|
||||
/// One address/voter can only vote once.
|
||||
/// See `getVotingPower` for how voting power is computed.
|
||||
/// @param proposalId The ID of the proposal to vote on.
|
||||
/// @param support Whether to support the proposal or not.
|
||||
/// @param operatedPoolIds The pools operated by the signer. The
|
||||
/// ZRX currently delegated to those pools will be accounted
|
||||
/// for in the voting power.
|
||||
/// @param v the v field of the signature
|
||||
/// @param r the r field of the signature
|
||||
/// @param s the s field of the signature
|
||||
function castVoteBySignature(
|
||||
uint256 proposalId,
|
||||
bool support,
|
||||
bytes32[] memory operatedPoolIds,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
)
|
||||
external;
|
||||
|
||||
/// @dev Executes a proposal that has passed and is
|
||||
/// currently executable.
|
||||
/// @param proposalId The ID of the proposal to execute.
|
||||
|
@@ -34,11 +34,25 @@ contract ZrxTreasury is
|
||||
using LibRichErrorsV06 for bytes;
|
||||
using LibBytesV06 for bytes;
|
||||
|
||||
/// Contract name
|
||||
string private constant CONTRACT_NAME = "Zrx Treasury";
|
||||
|
||||
/// Contract version
|
||||
string private constant CONTRACT_VERSION = "1.0.0";
|
||||
|
||||
/// The EIP-712 typehash for the contract's domain
|
||||
bytes32 private constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
|
||||
|
||||
/// The EIP-712 typehash for the vote struct
|
||||
bytes32 private constant VOTE_TYPEHASH = keccak256("TreasuryVote(uint256 proposalId,bool support,bytes32[] operatedPoolIds)");
|
||||
|
||||
// Immutables
|
||||
IStaking public immutable override stakingProxy;
|
||||
DefaultPoolOperator public immutable override defaultPoolOperator;
|
||||
bytes32 public immutable override defaultPoolId;
|
||||
uint256 public immutable override votingPeriod;
|
||||
bytes32 immutable domainSeparator;
|
||||
|
||||
uint256 public override proposalThreshold;
|
||||
uint256 public override quorumThreshold;
|
||||
|
||||
@@ -67,6 +81,15 @@ contract ZrxTreasury is
|
||||
defaultPoolId = params.defaultPoolId;
|
||||
IStaking.Pool memory defaultPool = stakingProxy_.getStakingPool(params.defaultPoolId);
|
||||
defaultPoolOperator = DefaultPoolOperator(defaultPool.operator);
|
||||
domainSeparator = keccak256(
|
||||
abi.encode(
|
||||
DOMAIN_TYPEHASH,
|
||||
keccak256(bytes(CONTRACT_NAME)),
|
||||
_getChainId(),
|
||||
keccak256(bytes(CONTRACT_VERSION)),
|
||||
address(this)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// solhint-disable
|
||||
@@ -105,7 +128,7 @@ contract ZrxTreasury is
|
||||
/// be executed if it passes. Must be at least two epochs
|
||||
/// from the current epoch.
|
||||
/// @param description A text description for the proposal.
|
||||
/// @param operatedPoolIds The pools operated by `msg.sender`. The
|
||||
/// @param operatedPoolIds The pools operated by the signer. The
|
||||
/// ZRX currently delegated to those pools will be accounted
|
||||
/// for in the voting power.
|
||||
/// @return proposalId The ID of the newly created proposal.
|
||||
@@ -150,8 +173,9 @@ contract ZrxTreasury is
|
||||
}
|
||||
|
||||
/// @dev Casts a vote for the given proposal. Only callable
|
||||
/// during the voting period for that proposal. See
|
||||
/// `getVotingPower` for how voting power is computed.
|
||||
/// during the voting period for that proposal.
|
||||
/// One address can only vote once.
|
||||
/// See `getVotingPower` for how voting power is computed.
|
||||
/// @param proposalId The ID of the proposal to vote on.
|
||||
/// @param support Whether to support the proposal or not.
|
||||
/// @param operatedPoolIds The pools operated by `msg.sender`. The
|
||||
@@ -165,43 +189,39 @@ contract ZrxTreasury is
|
||||
public
|
||||
override
|
||||
{
|
||||
if (proposalId >= proposalCount()) {
|
||||
revert("castVote/INVALID_PROPOSAL_ID");
|
||||
}
|
||||
if (hasVoted[proposalId][msg.sender]) {
|
||||
revert("castVote/ALREADY_VOTED");
|
||||
}
|
||||
return _castVote(msg.sender, proposalId, support, operatedPoolIds);
|
||||
}
|
||||
|
||||
Proposal memory proposal = proposals[proposalId];
|
||||
if (
|
||||
proposal.voteEpoch != stakingProxy.currentEpoch() ||
|
||||
_hasVoteEnded(proposal.voteEpoch)
|
||||
) {
|
||||
revert("castVote/VOTING_IS_CLOSED");
|
||||
}
|
||||
|
||||
uint256 votingPower = getVotingPower(msg.sender, operatedPoolIds);
|
||||
if (votingPower == 0) {
|
||||
revert("castVote/NO_VOTING_POWER");
|
||||
}
|
||||
|
||||
if (support) {
|
||||
proposals[proposalId].votesFor = proposals[proposalId].votesFor
|
||||
.safeAdd(votingPower);
|
||||
hasVoted[proposalId][msg.sender] = true;
|
||||
} else {
|
||||
proposals[proposalId].votesAgainst = proposals[proposalId].votesAgainst
|
||||
.safeAdd(votingPower);
|
||||
hasVoted[proposalId][msg.sender] = true;
|
||||
}
|
||||
|
||||
emit VoteCast(
|
||||
msg.sender,
|
||||
operatedPoolIds,
|
||||
proposalId,
|
||||
support,
|
||||
votingPower
|
||||
/// @dev Casts a vote for the given proposal, by signature.
|
||||
/// Only callable during the voting period for that proposal.
|
||||
/// One address/voter can only vote once.
|
||||
/// See `getVotingPower` for how voting power is computed.
|
||||
/// @param proposalId The ID of the proposal to vote on.
|
||||
/// @param support Whether to support the proposal or not.
|
||||
/// @param operatedPoolIds The pools operated by voter. The
|
||||
/// ZRX currently delegated to those pools will be accounted
|
||||
/// for in the voting power.
|
||||
/// @param v the v field of the signature
|
||||
/// @param r the r field of the signature
|
||||
/// @param s the s field of the signature
|
||||
function castVoteBySignature(
|
||||
uint256 proposalId,
|
||||
bool support,
|
||||
bytes32[] memory operatedPoolIds,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
)
|
||||
public
|
||||
override
|
||||
{
|
||||
bytes32 structHash = keccak256(
|
||||
abi.encode(VOTE_TYPEHASH, proposalId, support, keccak256(abi.encodePacked(operatedPoolIds)))
|
||||
);
|
||||
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
|
||||
address signatory = ecrecover(digest, v, r, s);
|
||||
|
||||
return _castVote(signatory, proposalId, support, operatedPoolIds);
|
||||
}
|
||||
|
||||
/// @dev Executes a proposal that has passed and is
|
||||
@@ -373,4 +393,60 @@ contract ZrxTreasury is
|
||||
.safeAdd(votingPeriod);
|
||||
return block.timestamp > voteEndTime;
|
||||
}
|
||||
|
||||
/// @dev Casts a vote for the given proposal. Only callable
|
||||
/// during the voting period for that proposal. See
|
||||
/// `getVotingPower` for how voting power is computed.
|
||||
function _castVote(
|
||||
address voter,
|
||||
uint256 proposalId,
|
||||
bool support,
|
||||
bytes32[] memory operatedPoolIds
|
||||
)
|
||||
private
|
||||
{
|
||||
if (proposalId >= proposalCount()) {
|
||||
revert("_castVote/INVALID_PROPOSAL_ID");
|
||||
}
|
||||
if (hasVoted[proposalId][voter]) {
|
||||
revert("_castVote/ALREADY_VOTED");
|
||||
}
|
||||
|
||||
Proposal memory proposal = proposals[proposalId];
|
||||
if (
|
||||
proposal.voteEpoch != stakingProxy.currentEpoch() ||
|
||||
_hasVoteEnded(proposal.voteEpoch)
|
||||
) {
|
||||
revert("_castVote/VOTING_IS_CLOSED");
|
||||
}
|
||||
|
||||
uint256 votingPower = getVotingPower(voter, operatedPoolIds);
|
||||
if (votingPower == 0) {
|
||||
revert("_castVote/NO_VOTING_POWER");
|
||||
}
|
||||
|
||||
if (support) {
|
||||
proposals[proposalId].votesFor = proposals[proposalId].votesFor
|
||||
.safeAdd(votingPower);
|
||||
} else {
|
||||
proposals[proposalId].votesAgainst = proposals[proposalId].votesAgainst
|
||||
.safeAdd(votingPower);
|
||||
}
|
||||
hasVoted[proposalId][voter] = true;
|
||||
|
||||
emit VoteCast(
|
||||
voter,
|
||||
operatedPoolIds,
|
||||
proposalId,
|
||||
support,
|
||||
votingPower
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Gets the Ethereum chain id
|
||||
function _getChainId() private pure returns (uint256) {
|
||||
uint256 chainId;
|
||||
assembly { chainId := chainid() }
|
||||
return chainId;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-treasury",
|
||||
"version": "1.3.3",
|
||||
"version": "1.4.8",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -32,9 +32,9 @@
|
||||
"publish:private": "yarn build && gitpkg publish"
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ZrxTreasury,DefaultPoolOperator",
|
||||
"publicInterfaceContracts": "ZrxTreasury,DefaultPoolOperator,ISablier",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(DefaultPoolOperator|IStaking|IZrxTreasury|ZrxTreasury).json"
|
||||
"abis": "./test/generated-artifacts/@(DefaultPoolOperator|ISablier|IStaking|IZrxTreasury|ZrxTreasury).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -46,14 +46,14 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/treasury",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.6.0",
|
||||
"@0x/contract-addresses": "^6.6.1",
|
||||
"@0x/abi-gen": "^5.6.2",
|
||||
"@0x/contract-addresses": "^6.11.0",
|
||||
"@0x/contracts-asset-proxy": "^3.7.19",
|
||||
"@0x/contracts-erc20": "^3.3.17",
|
||||
"@0x/contracts-gen": "^2.0.38",
|
||||
"@0x/contracts-erc20": "^3.3.25",
|
||||
"@0x/contracts-gen": "^2.0.40",
|
||||
"@0x/contracts-staking": "^2.0.45",
|
||||
"@0x/contracts-test-utils": "^5.4.9",
|
||||
"@0x/sol-compiler": "^4.7.3",
|
||||
"@0x/contracts-test-utils": "^5.4.16",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.4",
|
||||
"@types/isomorphic-fetch": "^0.0.35",
|
||||
@@ -72,14 +72,14 @@
|
||||
"typescript": "4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.4.0",
|
||||
"@0x/protocol-utils": "^1.8.3",
|
||||
"@0x/subproviders": "^6.5.3",
|
||||
"@0x/types": "^3.3.3",
|
||||
"@0x/typescript-typings": "^5.2.0",
|
||||
"@0x/utils": "^6.4.3",
|
||||
"@0x/web3-wrapper": "^7.5.3",
|
||||
"ethereum-types": "^3.5.0",
|
||||
"@0x/base-contract": "^6.4.2",
|
||||
"@0x/protocol-utils": "^1.10.1",
|
||||
"@0x/subproviders": "^6.6.0",
|
||||
"@0x/types": "^3.3.4",
|
||||
"@0x/typescript-typings": "^5.2.1",
|
||||
"@0x/utils": "^6.4.4",
|
||||
"@0x/web3-wrapper": "^7.6.0",
|
||||
"ethereum-types": "^3.6.0",
|
||||
"ethereumjs-util": "^7.0.10"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
@@ -6,8 +6,10 @@
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as DefaultPoolOperator from '../generated-artifacts/DefaultPoolOperator.json';
|
||||
import * as ISablier from '../generated-artifacts/ISablier.json';
|
||||
import * as ZrxTreasury from '../generated-artifacts/ZrxTreasury.json';
|
||||
export const artifacts = {
|
||||
ZrxTreasury: ZrxTreasury as ContractArtifact,
|
||||
DefaultPoolOperator: DefaultPoolOperator as ContractArtifact,
|
||||
ISablier: ISablier as ContractArtifact,
|
||||
};
|
||||
|
@@ -3,6 +3,8 @@ import { ERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { Web3ProviderEngine } from '@0x/subproviders';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { ISablierContract } from './wrappers';
|
||||
|
||||
interface ProposedAction {
|
||||
target: string;
|
||||
data: string;
|
||||
@@ -17,8 +19,14 @@ interface Proposal {
|
||||
|
||||
const { zrxToken } = getContractAddressesForChainOrThrow(1);
|
||||
const zrx = new ERC20TokenContract(zrxToken, new Web3ProviderEngine());
|
||||
const maticToken = '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0';
|
||||
const matic = new ERC20TokenContract(maticToken, new Web3ProviderEngine());
|
||||
const maticToken = new ERC20TokenContract('0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0', new Web3ProviderEngine());
|
||||
const sablier = new ISablierContract('0xcd18eaa163733da39c232722cbc4e8940b1d8888', new Web3ProviderEngine());
|
||||
|
||||
const ONE_YEAR_IN_SECONDS = new BigNumber('31536000');
|
||||
const PROPOSAL_2_ZRX_AMOUNT = new BigNumber('485392999999999970448000');
|
||||
const PROPOSAL_2_MATIC_AMOUNT = new BigNumber('378035999999999992944000');
|
||||
const PROPOSAL_2_STREAM_START_TIME = new BigNumber('1635188400');
|
||||
const PROPOSAL_2_RECIPIENT = '0x976378445d31d81b15576811450a7b9797206807';
|
||||
|
||||
export const proposals: Proposal[] = [
|
||||
{
|
||||
@@ -44,8 +52,8 @@ export const proposals: Proposal[] = [
|
||||
value: new BigNumber(0),
|
||||
},
|
||||
{
|
||||
target: maticToken,
|
||||
data: matic
|
||||
target: maticToken.address,
|
||||
data: maticToken
|
||||
.transfer('0xab66cc8fd10457ebc9d13b9760c835f0a4cbc487', new BigNumber('420000e18'))
|
||||
.getABIEncodedTransactionData(),
|
||||
value: new BigNumber(0),
|
||||
@@ -54,4 +62,46 @@ export const proposals: Proposal[] = [
|
||||
description:
|
||||
'# Z-2 0x/Polygon Grant Budget for 0xEVE\n\n## Summary\n\nWe propose to transfer 10% of the new Treasury allocation from the recently announced 0x/Polygon initiative to the 0x Ecosystem Value Experiment (0xEVE). The purpose is not so much to increase the budget as it is to enable access to the MATIC that was allocated to the Treasury after 0xEVE was established and to expand the original goals to include use cases on the Polygon Network. A snapshot vote was held to gauge community sentiment with 100% in favor https://snapshot.org/#/0xgov.eth/proposal/QmdcZAAcmNgM3R6CVY586G4sSoPwm6T3CS39DCQ4gPDPB4.\n\n## Background\n\nA proposal to establish the 0x Ecosystem Value Experiment (0xEVE) was passed in early June with a budget of 400K ZRX, which was then transferred to the 0xEVE multisig to fund operations. Shortly afterwards, 0xLabs and 0xPolygon allocated 3.3M ZRX and 4.2M MATIC to the 0xDAO Treasury with the shared goal of bringing 1M new users to the Polygon Network via 0x-powered applications.<br/><br/>\nSince that time, 0xEVE has established a grant program and published a framework for projects seeking support from the 0xDAO. However, because 0xEVE has no access to these new funds, it will be extremely difficult given the current market conditions (and the commensurate devaluation of our operating budget in USD terms) for us to incorporate this new goal into the grant program in any meaningful way, particularly because we are not able to spend any of the MATIC in the Treasury.\n\n## Request for Approval\n\nIn keeping with the original structure of the budget where ~10% of the Treasury was allocated to 0xEVE to fund opportunities such as grants, we propose that 10% of the new funding (ZRX and MATIC) be allocated to 0xEVE to fund activities associated with this new initiative. A separate multisig has been set up to manage and track these expenditures.<br/><br/>\nCompensation will remain as authorized in the original budget, and the new funding will be allocated 100% to grants and other operational activities specific to Polygon. In accordance with the grant program framework, this will enable 0xEVE to fast track grants under $50k using its own budget, while larger grants will require an onchain community vote and will be awarded from Treasury funds.<br/><br/>\nAdditionally, as 0x protocol deploys to additional chains, for any future allocations from similar joint initiatives, we recommend that they be structured the same way (90/10 split between the Treasury and 0xEVE) so that 0xEVE can actively participate in evaluating, distributing, and managing grants and other associated efforts designed to accelerate adoption and ecosystem value capture on those networks.<br/><br/>\nAs stipulated in Z-1, 0xEVE is a limited-duration experiment (26 weeks) and any funds not used will be returned to the Treasury when the experiment concludes.\n\n## Action Required\n\nTransfer 330,813 ZRX and 420,000 MATIC to 0xEVE gnosis safe multisig 0xAB66CC8FD10457ebC9D13B9760C835F0a4CbC487',
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
target: zrxToken,
|
||||
data: zrx.approve(sablier.address, PROPOSAL_2_ZRX_AMOUNT).getABIEncodedTransactionData(),
|
||||
value: new BigNumber(0),
|
||||
},
|
||||
{
|
||||
target: maticToken.address,
|
||||
data: maticToken.approve(sablier.address, PROPOSAL_2_MATIC_AMOUNT).getABIEncodedTransactionData(),
|
||||
value: new BigNumber(0),
|
||||
},
|
||||
{
|
||||
target: sablier.address,
|
||||
data: sablier
|
||||
.createStream(
|
||||
PROPOSAL_2_RECIPIENT,
|
||||
PROPOSAL_2_ZRX_AMOUNT,
|
||||
zrxToken,
|
||||
PROPOSAL_2_STREAM_START_TIME,
|
||||
PROPOSAL_2_STREAM_START_TIME.plus(ONE_YEAR_IN_SECONDS),
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: new BigNumber(0),
|
||||
},
|
||||
{
|
||||
target: sablier.address,
|
||||
data: sablier
|
||||
.createStream(
|
||||
PROPOSAL_2_RECIPIENT,
|
||||
PROPOSAL_2_MATIC_AMOUNT,
|
||||
maticToken.address,
|
||||
PROPOSAL_2_STREAM_START_TIME,
|
||||
PROPOSAL_2_STREAM_START_TIME.plus(ONE_YEAR_IN_SECONDS),
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: new BigNumber(0),
|
||||
},
|
||||
],
|
||||
description:
|
||||
'# Z-3 Trader.xyz Grant\n\n## Summary\n\nThis proposal seeks authorization of a $950k grant from the treasury to trader.xyz. The community has discussed the merits of the proposal in the governance forum and signaled strong support for moving forward in a snapshot poll:\n\n1. https://gov.0x.org/t/grant-proposal-trader-xyz/1005/\n2. https://snapshot.org/#/0xgov.eth/proposal/Qmcf2C3KmQ1W1XBGownLWsA8yX9hpzY6peLUGKNJnPzN9y\n\n## Grant Details\n\n### What category best describes your grant request?\n\n1. 0x orderbook\n2. 0x protocol feature development\n\n### Grant amount requested\n\n**Amount**: $950k split 50/50 between $ZRX and $MATIC (note: an upfront payment of $50k in $ZRX and $MATIC is being made from the 0xEVE grant budget)\n\n**Price reference**:\n1. [https://www.tradingview.com/symbols/ZRXUSD/technicals/](https://www.tradingview.com/symbols/ZRXUSD/technicals/) ($ZRX 30-day EMA as of 10/4/2021 = 0.97859)\n2. [https://www.tradingview.com/symbols/MATICUSD/technicals/](https://www.tradingview.com/symbols/MATICUSD/technicals/) ($MATIC 30-day EMA as of 10/4/2021 = 1.2564959)\n\n**Payment details**: $950k streamed from Sablier over 365 days (485,393 ZRX + 378,036 MATIC)\n\n**Receiving address**: 0x976378445D31D81b15576811450A7b9797206807 (Gnosis Safe)\n\n### Team background\n\nCore team is comprised of two former 0x core team members (Patryk Adas - former Matcha lead designer, and John Johnson - former Matcha lead engineer)\n\n### Project background\n\nTrader.xyz is a dapp that provides a user-focused trading experience with the goal of becoming the flagship, 0x-powered application for discovering and trading NFTs\n\n### Description of work to be funded\n\nSee detailed explanation at https://gov.0x.org/t/grant-proposal-trader-xyz/1005\n\n### Budget breakdown\n\nSee detailed explanation at https://gov.0x.org/t/grant-proposal-trader-xyz/1005\n\n### Benefit to 0x ecosystem\n\n1. Become the gold standard for exchanging NFTs with 0x protocol\n2. Enable 0xDAO to build organic development capabilities\n3. Improve protocol documentation and developer resources\n4. Add OSS API protocol features\n\n### Risk/Reward factors\n\nRisks include competition, mindshare, flywheel of liquidity, etc. We believe our product and team have the potential to mitigate these risks and bring to market several features and capabilities that will be market-leading.\n\n### Additional info\n\nOur team has a track record of delivering high quality projects, and in order for us to continue our work, we need capital and support. We prefer not to go the venture capital route and instead work directly with the 0xDAO for the best synergies and to align value with 0x. We proved out an initial brand, design, and engineering concept via OTC orders, and it is already the highest quality OTC swap on the market. We would like to work with 0xDAO directly to make sure 0x has a foothold in the NFT market as it continues to evolve and develop. We want to do what Matcha did for DEX ERC20 trading.\n\n## Action Required\n\nStream 485,393 ZRX and 378,036 MATIC to Gnosis Safe 0x976378445D31D81b15576811450A7b9797206807 over 365 days',
|
||||
},
|
||||
];
|
||||
|
@@ -4,4 +4,5 @@
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../generated-wrappers/default_pool_operator';
|
||||
export * from '../generated-wrappers/i_sablier';
|
||||
export * from '../generated-wrappers/zrx_treasury';
|
||||
|
@@ -6,10 +6,12 @@
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as DefaultPoolOperator from '../test/generated-artifacts/DefaultPoolOperator.json';
|
||||
import * as ISablier from '../test/generated-artifacts/ISablier.json';
|
||||
import * as IStaking from '../test/generated-artifacts/IStaking.json';
|
||||
import * as IZrxTreasury from '../test/generated-artifacts/IZrxTreasury.json';
|
||||
import * as ZrxTreasury from '../test/generated-artifacts/ZrxTreasury.json';
|
||||
export const artifacts = {
|
||||
ISablier: ISablier as ContractArtifact,
|
||||
DefaultPoolOperator: DefaultPoolOperator as ContractArtifact,
|
||||
IStaking: IStaking as ContractArtifact,
|
||||
IZrxTreasury: IZrxTreasury as ContractArtifact,
|
||||
|
@@ -7,7 +7,7 @@ import * as _ from 'lodash';
|
||||
import { proposals } from '../src/proposals';
|
||||
|
||||
import { artifacts } from './artifacts';
|
||||
import { ZrxTreasuryContract, ZrxTreasuryEvents } from './wrappers';
|
||||
import { ISablierEvents, ZrxTreasuryContract, ZrxTreasuryEvents } from './wrappers';
|
||||
|
||||
const SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/mzhu25/zeroex-staking';
|
||||
const STAKING_PROXY_ADDRESS = '0xa26e80e7dea86279c6d778d702cc413e6cffa777';
|
||||
@@ -15,9 +15,11 @@ const TREASURY_ADDRESS = '0x0bb1810061c2f5b2088054ee184e6c79e1591101';
|
||||
const PROPOSER = process.env.PROPOSER || constants.NULL_ADDRESS;
|
||||
const VOTER = '0xba4f44e774158408e2dc6c5cb65bc995f0a89180';
|
||||
const VOTER_OPERATED_POOLS = ['0x0000000000000000000000000000000000000000000000000000000000000017'];
|
||||
const VOTER_2 = '0x9a4eb1101c0c053505bd71d2ffa27ed902dead85';
|
||||
const VOTER_2_OPERATED_POOLS = ['0x0000000000000000000000000000000000000000000000000000000000000029'];
|
||||
blockchainTests.configure({
|
||||
fork: {
|
||||
unlockedAccounts: [PROPOSER, VOTER],
|
||||
unlockedAccounts: [PROPOSER, VOTER, VOTER_2],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -219,4 +221,80 @@ blockchainTests.fork.skip('Treasury proposal mainnet fork tests', env => {
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('Proposal 2', () => {
|
||||
it('works', async () => {
|
||||
const proposal = proposals[2];
|
||||
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 treasury
|
||||
.castVote(proposalId, true, VOTER_2_OPERATED_POOLS)
|
||||
.awaitTransactionSuccessAsync({ from: VOTER_2 });
|
||||
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,
|
||||
);
|
||||
|
||||
verifyEventsFromLogs(
|
||||
executeTx.logs,
|
||||
[
|
||||
{
|
||||
recipient: '0x976378445D31D81b15576811450A7b9797206807',
|
||||
deposit: new BigNumber('485392999999999970448000'),
|
||||
tokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
|
||||
startTime: new BigNumber(1635188400),
|
||||
stopTime: new BigNumber(1666724400),
|
||||
},
|
||||
{
|
||||
recipient: '0x976378445D31D81b15576811450A7b9797206807',
|
||||
deposit: new BigNumber('378035999999999992944000'),
|
||||
tokenAddress: '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0',
|
||||
startTime: new BigNumber(1635188400),
|
||||
stopTime: new BigNumber(1666724400),
|
||||
},
|
||||
],
|
||||
ISablierEvents.CreateStream,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -17,8 +17,9 @@ import {
|
||||
randomAddress,
|
||||
verifyEventsFromLogs,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
import { TreasuryVote } from '@0x/protocol-utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
|
||||
import { artifacts } from './artifacts';
|
||||
import { DefaultPoolOperatorContract, ZrxTreasuryContract, ZrxTreasuryEvents } from './wrappers';
|
||||
@@ -55,6 +56,8 @@ blockchainTests.resets('Treasury governance', env => {
|
||||
let nonDefaultPoolId: string;
|
||||
let poolOperator: string;
|
||||
let delegator: string;
|
||||
let relayer: string;
|
||||
let delegatorPrivateKey: string;
|
||||
let actions: ProposedAction[];
|
||||
|
||||
async function deployStakingAsync(): Promise<void> {
|
||||
@@ -105,7 +108,10 @@ blockchainTests.resets('Treasury governance', env => {
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
[admin, poolOperator, delegator] = await env.getAccountAddressesAsync();
|
||||
const accounts = await env.getAccountAddressesAsync();
|
||||
[admin, poolOperator, delegator, relayer] = accounts;
|
||||
delegatorPrivateKey = hexUtils.toHex(constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(delegator)]);
|
||||
|
||||
zrx = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
erc20Artifacts.DummyERC20Token,
|
||||
env.provider,
|
||||
@@ -399,7 +405,7 @@ blockchainTests.resets('Treasury governance', env => {
|
||||
expect(await treasury.proposalCount().callAsync()).to.bignumber.equal(1);
|
||||
});
|
||||
});
|
||||
describe('castVote()', () => {
|
||||
describe('castVote() and castVoteBySignature()', () => {
|
||||
const VOTE_PROPOSAL_ID = new BigNumber(0);
|
||||
const DELEGATOR_VOTING_POWER = new BigNumber(420);
|
||||
|
||||
@@ -418,17 +424,18 @@ blockchainTests.resets('Treasury governance', env => {
|
||||
.propose(actions, currentEpoch.plus(2), PROPOSAL_DESCRIPTION, [])
|
||||
.awaitTransactionSuccessAsync({ from: delegator });
|
||||
});
|
||||
// castVote()
|
||||
it('Cannot vote on invalid proposalId', async () => {
|
||||
await fastForwardToNextEpochAsync();
|
||||
await fastForwardToNextEpochAsync();
|
||||
const tx = treasury
|
||||
.castVote(INVALID_PROPOSAL_ID, true, [])
|
||||
.awaitTransactionSuccessAsync({ from: delegator });
|
||||
return expect(tx).to.revertWith('castVote/INVALID_PROPOSAL_ID');
|
||||
return expect(tx).to.revertWith('_castVote/INVALID_PROPOSAL_ID');
|
||||
});
|
||||
it('Cannot vote before voting period starts', async () => {
|
||||
const tx = treasury.castVote(VOTE_PROPOSAL_ID, true, []).awaitTransactionSuccessAsync({ from: delegator });
|
||||
return expect(tx).to.revertWith('castVote/VOTING_IS_CLOSED');
|
||||
return expect(tx).to.revertWith('_castVote/VOTING_IS_CLOSED');
|
||||
});
|
||||
it('Cannot vote after voting period ends', async () => {
|
||||
await fastForwardToNextEpochAsync();
|
||||
@@ -436,14 +443,14 @@ blockchainTests.resets('Treasury governance', env => {
|
||||
await env.web3Wrapper.increaseTimeAsync(TREASURY_PARAMS.votingPeriod.plus(1).toNumber());
|
||||
await env.web3Wrapper.mineBlockAsync();
|
||||
const tx = treasury.castVote(VOTE_PROPOSAL_ID, true, []).awaitTransactionSuccessAsync({ from: delegator });
|
||||
return expect(tx).to.revertWith('castVote/VOTING_IS_CLOSED');
|
||||
return expect(tx).to.revertWith('_castVote/VOTING_IS_CLOSED');
|
||||
});
|
||||
it('Cannot vote twice on same proposal', async () => {
|
||||
await fastForwardToNextEpochAsync();
|
||||
await fastForwardToNextEpochAsync();
|
||||
await treasury.castVote(VOTE_PROPOSAL_ID, true, []).awaitTransactionSuccessAsync({ from: delegator });
|
||||
const tx = treasury.castVote(VOTE_PROPOSAL_ID, false, []).awaitTransactionSuccessAsync({ from: delegator });
|
||||
return expect(tx).to.revertWith('castVote/ALREADY_VOTED');
|
||||
return expect(tx).to.revertWith('_castVote/ALREADY_VOTED');
|
||||
});
|
||||
it('Can cast a valid vote', async () => {
|
||||
await fastForwardToNextEpochAsync();
|
||||
@@ -465,6 +472,109 @@ blockchainTests.resets('Treasury governance', env => {
|
||||
ZrxTreasuryEvents.VoteCast,
|
||||
);
|
||||
});
|
||||
// castVoteBySignature()
|
||||
it('Cannot vote by signature on invalid proposalId', async () => {
|
||||
await fastForwardToNextEpochAsync();
|
||||
await fastForwardToNextEpochAsync();
|
||||
const vote = new TreasuryVote({
|
||||
proposalId: INVALID_PROPOSAL_ID,
|
||||
verifyingContract: admin,
|
||||
});
|
||||
const signature = vote.getSignatureWithKey(delegatorPrivateKey);
|
||||
const tx = treasury
|
||||
.castVoteBySignature(INVALID_PROPOSAL_ID, true, [], signature.v, signature.r, signature.s)
|
||||
.awaitTransactionSuccessAsync({ from: relayer });
|
||||
return expect(tx).to.revertWith('_castVote/INVALID_PROPOSAL_ID');
|
||||
});
|
||||
it('Cannot vote by signature before voting period starts', async () => {
|
||||
const vote = new TreasuryVote({
|
||||
proposalId: VOTE_PROPOSAL_ID,
|
||||
verifyingContract: admin,
|
||||
});
|
||||
const signature = vote.getSignatureWithKey(delegatorPrivateKey);
|
||||
const tx = treasury
|
||||
.castVoteBySignature(VOTE_PROPOSAL_ID, true, [], signature.v, signature.r, signature.s)
|
||||
.awaitTransactionSuccessAsync({ from: relayer });
|
||||
return expect(tx).to.revertWith('_castVote/VOTING_IS_CLOSED');
|
||||
});
|
||||
it('Cannot vote by signature after voting period ends', async () => {
|
||||
await fastForwardToNextEpochAsync();
|
||||
await fastForwardToNextEpochAsync();
|
||||
await env.web3Wrapper.increaseTimeAsync(TREASURY_PARAMS.votingPeriod.plus(1).toNumber());
|
||||
await env.web3Wrapper.mineBlockAsync();
|
||||
|
||||
const vote = new TreasuryVote({
|
||||
proposalId: VOTE_PROPOSAL_ID,
|
||||
verifyingContract: admin,
|
||||
});
|
||||
const signature = vote.getSignatureWithKey(delegatorPrivateKey);
|
||||
const tx = treasury
|
||||
.castVoteBySignature(VOTE_PROPOSAL_ID, true, [], signature.v, signature.r, signature.s)
|
||||
.awaitTransactionSuccessAsync({ from: relayer });
|
||||
return expect(tx).to.revertWith('_castVote/VOTING_IS_CLOSED');
|
||||
});
|
||||
it('Can recover the address from signature correctly', async () => {
|
||||
const vote = new TreasuryVote({
|
||||
proposalId: VOTE_PROPOSAL_ID,
|
||||
verifyingContract: admin,
|
||||
});
|
||||
const signature = vote.getSignatureWithKey(delegatorPrivateKey);
|
||||
const publicKey = ethUtil.ecrecover(
|
||||
ethUtil.toBuffer(vote.getEIP712Hash()),
|
||||
signature.v,
|
||||
ethUtil.toBuffer(signature.r),
|
||||
ethUtil.toBuffer(signature.s),
|
||||
);
|
||||
const address = ethUtil.publicToAddress(publicKey);
|
||||
|
||||
expect(ethUtil.bufferToHex(address)).to.be.equal(delegator);
|
||||
});
|
||||
it('Can cast a valid vote by signature', async () => {
|
||||
await fastForwardToNextEpochAsync();
|
||||
await fastForwardToNextEpochAsync();
|
||||
|
||||
const vote = new TreasuryVote({
|
||||
proposalId: VOTE_PROPOSAL_ID,
|
||||
verifyingContract: treasury.address,
|
||||
chainId: 1337,
|
||||
support: false,
|
||||
});
|
||||
const signature = vote.getSignatureWithKey(delegatorPrivateKey);
|
||||
const tx = await treasury
|
||||
.castVoteBySignature(VOTE_PROPOSAL_ID, false, [], signature.v, signature.r, signature.s)
|
||||
.awaitTransactionSuccessAsync({ from: relayer });
|
||||
|
||||
verifyEventsFromLogs(
|
||||
tx.logs,
|
||||
[
|
||||
{
|
||||
voter: delegator,
|
||||
operatedPoolIds: [],
|
||||
proposalId: VOTE_PROPOSAL_ID,
|
||||
support: vote.support,
|
||||
votingPower: DELEGATOR_VOTING_POWER,
|
||||
},
|
||||
],
|
||||
ZrxTreasuryEvents.VoteCast,
|
||||
);
|
||||
});
|
||||
it('Cannot vote by signature twice on same proposal', async () => {
|
||||
await fastForwardToNextEpochAsync();
|
||||
await fastForwardToNextEpochAsync();
|
||||
await treasury.castVote(VOTE_PROPOSAL_ID, true, []).awaitTransactionSuccessAsync({ from: delegator });
|
||||
|
||||
const secondVote = new TreasuryVote({
|
||||
proposalId: VOTE_PROPOSAL_ID,
|
||||
verifyingContract: treasury.address,
|
||||
chainId: 1337,
|
||||
support: false,
|
||||
});
|
||||
const signature = secondVote.getSignatureWithKey(delegatorPrivateKey);
|
||||
const secondVoteTx = treasury
|
||||
.castVoteBySignature(VOTE_PROPOSAL_ID, false, [], signature.v, signature.r, signature.s)
|
||||
.awaitTransactionSuccessAsync({ from: relayer });
|
||||
return expect(secondVoteTx).to.revertWith('_castVote/ALREADY_VOTED');
|
||||
});
|
||||
});
|
||||
describe('execute()', () => {
|
||||
let passedProposalId: BigNumber;
|
||||
@@ -473,7 +583,7 @@ blockchainTests.resets('Treasury governance', env => {
|
||||
let ongoingVoteProposalId: BigNumber;
|
||||
|
||||
before(async () => {
|
||||
// OPerator has enough ZRX to create and pass a proposal
|
||||
// Operator has enough ZRX to create and pass a proposal
|
||||
await staking.stake(TREASURY_PARAMS.quorumThreshold).awaitTransactionSuccessAsync({ from: poolOperator });
|
||||
await staking
|
||||
.moveStake(
|
||||
@@ -549,7 +659,7 @@ blockchainTests.resets('Treasury governance', env => {
|
||||
});
|
||||
it('Cannot execute before or after the execution epoch', async () => {
|
||||
const tooEarly = treasury.execute(passedProposalId, actions).awaitTransactionSuccessAsync();
|
||||
expect(tooEarly).to.revertWith('_assertProposalExecutable/CANNOT_EXECUTE_THIS_EPOCH');
|
||||
await expect(tooEarly).to.revertWith('_assertProposalExecutable/CANNOT_EXECUTE_THIS_EPOCH');
|
||||
await fastForwardToNextEpochAsync();
|
||||
// Proposal 0 is executable here
|
||||
await fastForwardToNextEpochAsync();
|
||||
|
@@ -4,6 +4,7 @@
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../test/generated-wrappers/default_pool_operator';
|
||||
export * from '../test/generated-wrappers/i_sablier';
|
||||
export * from '../test/generated-wrappers/i_staking';
|
||||
export * from '../test/generated-wrappers/i_zrx_treasury';
|
||||
export * from '../test/generated-wrappers/zrx_treasury';
|
||||
|
@@ -4,8 +4,10 @@
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||
"files": [
|
||||
"generated-artifacts/DefaultPoolOperator.json",
|
||||
"generated-artifacts/ISablier.json",
|
||||
"generated-artifacts/ZrxTreasury.json",
|
||||
"test/generated-artifacts/DefaultPoolOperator.json",
|
||||
"test/generated-artifacts/ISablier.json",
|
||||
"test/generated-artifacts/IStaking.json",
|
||||
"test/generated-artifacts/IZrxTreasury.json",
|
||||
"test/generated-artifacts/ZrxTreasury.json"
|
||||
|
@@ -1,4 +1,77 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1640364306,
|
||||
"version": "4.8.6",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1638390144,
|
||||
"version": "4.8.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1637102971,
|
||||
"version": "4.8.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "4.8.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1634668033,
|
||||
"version": "4.8.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1631710679,
|
||||
"version": "4.8.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "4.8.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Added FundRecoveryFeature to the 0x EP",
|
||||
"pr": 306
|
||||
}
|
||||
],
|
||||
"timestamp": 1631120757
|
||||
},
|
||||
{
|
||||
"timestamp": 1630459879,
|
||||
"version": "4.7.18",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1629353596,
|
||||
"version": "4.7.17",
|
||||
|
@@ -5,6 +5,38 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v4.8.6 - _December 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.5 - _December 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.4 - _November 16, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.3 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.2 - _October 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.1 - _September 15, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.0 - _September 8, 2021_
|
||||
|
||||
* Added FundRecoveryFeature to the 0x EP (#306)
|
||||
|
||||
## v4.7.18 - _September 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.7.17 - _August 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-utils",
|
||||
"version": "4.7.17",
|
||||
"version": "4.8.6",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -50,15 +50,15 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/utils",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.6.0",
|
||||
"@0x/contracts-gen": "^2.0.38",
|
||||
"@0x/contracts-test-utils": "^5.4.9",
|
||||
"@0x/dev-utils": "^4.2.7",
|
||||
"@0x/abi-gen": "^5.6.2",
|
||||
"@0x/contracts-gen": "^2.0.40",
|
||||
"@0x/contracts-test-utils": "^5.4.16",
|
||||
"@0x/dev-utils": "^4.2.9",
|
||||
"@0x/order-utils": "^10.4.28",
|
||||
"@0x/sol-compiler": "^4.7.3",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
"@0x/tslint-config": "^4.1.4",
|
||||
"@0x/types": "^3.3.3",
|
||||
"@0x/web3-wrapper": "^7.5.3",
|
||||
"@0x/types": "^3.3.4",
|
||||
"@0x/web3-wrapper": "^7.6.0",
|
||||
"@types/bn.js": "^4.11.0",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
@@ -79,11 +79,11 @@
|
||||
"typescript": "4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.4.0",
|
||||
"@0x/typescript-typings": "^5.2.0",
|
||||
"@0x/utils": "^6.4.3",
|
||||
"@0x/base-contract": "^6.4.2",
|
||||
"@0x/typescript-typings": "^5.2.1",
|
||||
"@0x/utils": "^6.4.4",
|
||||
"bn.js": "^4.11.8",
|
||||
"ethereum-types": "^3.5.0"
|
||||
"ethereum-types": "^3.6.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@@ -1,4 +1,111 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1640364306,
|
||||
"version": "0.30.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.30.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `AaveV2` and `Compound` deposit/withdrawal liquidity source",
|
||||
"pr": 321
|
||||
}
|
||||
],
|
||||
"timestamp": 1638390144
|
||||
},
|
||||
{
|
||||
"timestamp": 1637102971,
|
||||
"version": "0.29.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.29.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Prevent EP ETH balance from reducing when executin mtxs",
|
||||
"pr": 365
|
||||
}
|
||||
],
|
||||
"timestamp": 1637065617
|
||||
},
|
||||
{
|
||||
"version": "0.29.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Register transformERC20() and remove transformERC20Staging()",
|
||||
"pr": 355
|
||||
},
|
||||
{
|
||||
"note": "Add OtcOrders to FullMigration",
|
||||
"pr": 350
|
||||
}
|
||||
],
|
||||
"timestamp": 1635903615
|
||||
},
|
||||
{
|
||||
"timestamp": 1634668033,
|
||||
"version": "0.29.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1633374058,
|
||||
"version": "0.29.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.29.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Export TransformERC20FeatureContract",
|
||||
"pr": 282
|
||||
}
|
||||
],
|
||||
"timestamp": 1632957537
|
||||
},
|
||||
{
|
||||
"timestamp": 1631710679,
|
||||
"version": "0.28.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1631120757,
|
||||
"version": "0.28.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1630459879,
|
||||
"version": "0.28.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.28.2",
|
||||
"changes": [
|
||||
|
@@ -5,6 +5,51 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v0.30.1 - _December 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.30.0 - _December 1, 2021_
|
||||
|
||||
* Add `AaveV2` and `Compound` deposit/withdrawal liquidity source (#321)
|
||||
|
||||
## v0.29.5 - _November 16, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.29.4 - _November 16, 2021_
|
||||
|
||||
* Prevent EP ETH balance from reducing when executin mtxs (#365)
|
||||
|
||||
## v0.29.3 - _November 3, 2021_
|
||||
|
||||
* Register transformERC20() and remove transformERC20Staging() (#355)
|
||||
* Add OtcOrders to FullMigration (#350)
|
||||
|
||||
## v0.29.2 - _October 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.29.1 - _October 4, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.29.0 - _September 29, 2021_
|
||||
|
||||
* Export TransformERC20FeatureContract (#282)
|
||||
|
||||
## v0.28.5 - _September 15, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.28.4 - _September 8, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.28.3 - _September 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.28.2 - _August 19, 2021_
|
||||
|
||||
* Add ethers as an explicit dependency (#310)
|
||||
|
@@ -33,6 +33,7 @@ import "./features/interfaces/INativeOrdersFeature.sol";
|
||||
import "./features/interfaces/IBatchFillNativeOrdersFeature.sol";
|
||||
import "./features/interfaces/IMultiplexFeature.sol";
|
||||
import "./features/interfaces/IOtcOrdersFeature.sol";
|
||||
import "./features/interfaces/IFundRecoveryFeature.sol";
|
||||
|
||||
|
||||
/// @dev Interface for a fully featured Exchange Proxy.
|
||||
@@ -48,7 +49,8 @@ interface IZeroEx is
|
||||
INativeOrdersFeature,
|
||||
IBatchFillNativeOrdersFeature,
|
||||
IMultiplexFeature,
|
||||
IOtcOrdersFeature
|
||||
IOtcOrdersFeature,
|
||||
IFundRecoveryFeature
|
||||
{
|
||||
// solhint-disable state-visibility
|
||||
|
||||
|
@@ -0,0 +1,66 @@
|
||||
// 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 "../migrations/LibMigrate.sol";
|
||||
import "../fixins/FixinCommon.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/IFundRecoveryFeature.sol";
|
||||
import "../transformers/LibERC20Transformer.sol";
|
||||
|
||||
contract FundRecoveryFeature is
|
||||
IFeature,
|
||||
IFundRecoveryFeature,
|
||||
FixinCommon
|
||||
{
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "FundRecoveryFeature";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||
|
||||
/// @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.transferTrappedTokensTo.selector);
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
}
|
||||
|
||||
/// @dev Recovers ERC20 tokens or ETH from the 0x Exchange Proxy contract
|
||||
/// @param erc20 ERC20 Token Address. (You can also pass in `0xeeeee...` to indicate ETH)
|
||||
/// @param amountOut Amount of tokens to withdraw.
|
||||
/// @param recipientWallet Recipient wallet address.
|
||||
function transferTrappedTokensTo(
|
||||
IERC20TokenV06 erc20,
|
||||
uint256 amountOut,
|
||||
address payable recipientWallet
|
||||
)
|
||||
external
|
||||
override
|
||||
onlyOwner
|
||||
{
|
||||
if(amountOut == uint256(-1)) {
|
||||
amountOut = LibERC20Transformer.getTokenBalanceOf(erc20, address(this));
|
||||
}
|
||||
LibERC20Transformer.transformerTransfer(erc20, recipientWallet, amountOut);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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, 2, 1);
|
||||
/// @dev EIP712 typehash of the `MetaTransactionData` struct.
|
||||
bytes32 public immutable MTX_EIP712_TYPEHASH = keccak256(
|
||||
"MetaTransactionData("
|
||||
@@ -105,6 +105,17 @@ contract MetaTransactionsFeature is
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Ensures that the ETH balance of `this` does not go below the
|
||||
/// initial ETH balance before the call (excluding ETH attached to the call).
|
||||
modifier doesNotReduceEthBalance() {
|
||||
uint256 initialBalance = address(this).balance - msg.value;
|
||||
_;
|
||||
require(
|
||||
initialBalance <= address(this).balance,
|
||||
"MetaTransactionsFeature/ETH_LEAK"
|
||||
);
|
||||
}
|
||||
|
||||
constructor(address zeroExAddress)
|
||||
public
|
||||
FixinCommon()
|
||||
@@ -140,6 +151,7 @@ contract MetaTransactionsFeature is
|
||||
payable
|
||||
override
|
||||
nonReentrant(REENTRANCY_MTX)
|
||||
doesNotReduceEthBalance
|
||||
refundsAttachedEth
|
||||
returns (bytes memory returnResult)
|
||||
{
|
||||
@@ -164,6 +176,7 @@ contract MetaTransactionsFeature is
|
||||
payable
|
||||
override
|
||||
nonReentrant(REENTRANCY_MTX)
|
||||
doesNotReduceEthBalance
|
||||
refundsAttachedEth
|
||||
returns (bytes[] memory returnResults)
|
||||
{
|
||||
|
@@ -75,7 +75,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 +145,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.
|
||||
@@ -283,8 +245,8 @@ contract TransformERC20Feature is
|
||||
}
|
||||
// Transfer output tokens from wallet to recipient
|
||||
outputTokenAmount = _executeOutputTokenTransfer(
|
||||
args.outputToken,
|
||||
state.wallet,
|
||||
args.outputToken,
|
||||
state.wallet,
|
||||
args.recipient
|
||||
);
|
||||
}
|
||||
|
@@ -0,0 +1,35 @@
|
||||
// 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/IERC20TokenV06.sol";
|
||||
|
||||
|
||||
/// @dev Exchange Proxy Recovery Functions
|
||||
interface IFundRecoveryFeature {
|
||||
|
||||
/// @dev calledFrom FundRecoveryFeature.transferTrappedTokensTo() This will be delegatecalled
|
||||
/// in the context of the Exchange Proxy instance being used.
|
||||
/// @param erc20 ERC20 Token Address.
|
||||
/// @param amountOut Amount of tokens to withdraw.
|
||||
/// @param recipientWallet Recipient wallet address.
|
||||
function transferTrappedTokensTo(
|
||||
IERC20TokenV06 erc20,
|
||||
uint256 amountOut,
|
||||
address payable recipientWallet
|
||||
)
|
||||
external;
|
||||
}
|
@@ -25,6 +25,7 @@ import "../features/interfaces/IOwnableFeature.sol";
|
||||
import "../features/TransformERC20Feature.sol";
|
||||
import "../features/MetaTransactionsFeature.sol";
|
||||
import "../features/NativeOrdersFeature.sol";
|
||||
import "../features/OtcOrdersFeature.sol";
|
||||
import "./InitialMigration.sol";
|
||||
|
||||
|
||||
@@ -40,6 +41,7 @@ contract FullMigration {
|
||||
TransformERC20Feature transformERC20;
|
||||
MetaTransactionsFeature metaTransactions;
|
||||
NativeOrdersFeature nativeOrders;
|
||||
OtcOrdersFeature otcOrders;
|
||||
}
|
||||
|
||||
/// @dev Parameters needed to initialize features.
|
||||
@@ -173,5 +175,16 @@ contract FullMigration {
|
||||
address(this)
|
||||
);
|
||||
}
|
||||
// OtcOrdersFeature
|
||||
{
|
||||
// Register the feature.
|
||||
ownable.migrate(
|
||||
address(features.otcOrders),
|
||||
abi.encodeWithSelector(
|
||||
OtcOrdersFeature.migrate.selector
|
||||
),
|
||||
address(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -22,11 +22,12 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./IBridgeAdapter.sol";
|
||||
import "./BridgeProtocols.sol";
|
||||
import "./mixins/MixinAaveV2.sol";
|
||||
import "./mixins/MixinBalancer.sol";
|
||||
import "./mixins/MixinBalancerV2.sol";
|
||||
import "./mixins/MixinBancor.sol";
|
||||
import "./mixins/MixinClipper.sol";
|
||||
import "./mixins/MixinCoFiX.sol";
|
||||
import "./mixins/MixinCompound.sol";
|
||||
import "./mixins/MixinCurve.sol";
|
||||
import "./mixins/MixinCurveV2.sol";
|
||||
import "./mixins/MixinCryptoCom.sol";
|
||||
@@ -48,11 +49,12 @@ import "./mixins/MixinZeroExBridge.sol";
|
||||
|
||||
contract BridgeAdapter is
|
||||
IBridgeAdapter,
|
||||
MixinAaveV2,
|
||||
MixinBalancer,
|
||||
MixinBalancerV2,
|
||||
MixinBancor,
|
||||
MixinClipper,
|
||||
MixinCoFiX,
|
||||
MixinCompound,
|
||||
MixinCurve,
|
||||
MixinCurveV2,
|
||||
MixinCryptoCom,
|
||||
@@ -74,11 +76,12 @@ contract BridgeAdapter is
|
||||
{
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
MixinAaveV2()
|
||||
MixinBalancer()
|
||||
MixinBalancerV2()
|
||||
MixinBancor(weth)
|
||||
MixinClipper(weth)
|
||||
MixinCoFiX()
|
||||
MixinCompound(weth)
|
||||
MixinCurve(weth)
|
||||
MixinCurveV2()
|
||||
MixinCryptoCom()
|
||||
@@ -248,8 +251,15 @@ contract BridgeAdapter is
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else if (protocolId == BridgeProtocols.CLIPPER) {
|
||||
boughtAmount = _tradeClipper(
|
||||
} else if (protocolId == BridgeProtocols.AAVEV2) {
|
||||
boughtAmount = _tradeAaveV2(
|
||||
sellToken,
|
||||
buyToken,
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else if (protocolId == BridgeProtocols.COMPOUND) {
|
||||
boughtAmount = _tradeCompound(
|
||||
sellToken,
|
||||
buyToken,
|
||||
sellAmount,
|
||||
|
@@ -49,5 +49,7 @@ 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 CLIPPER = 22; // Not used: Clipper is now using PLP interface
|
||||
uint128 internal constant AAVEV2 = 23;
|
||||
uint128 internal constant COMPOUND = 24;
|
||||
}
|
||||
|
@@ -0,0 +1,93 @@
|
||||
// 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";
|
||||
|
||||
// Minimal Aave V2 LendingPool interface
|
||||
interface ILendingPool {
|
||||
/**
|
||||
* @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
|
||||
* - E.g. User deposits 100 USDC and gets in return 100 aUSDC
|
||||
* @param asset The address of the underlying asset to deposit
|
||||
* @param amount The amount to be deposited
|
||||
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
|
||||
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
|
||||
* is a different wallet
|
||||
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
|
||||
* 0 if the action is executed directly by the user, without any middle-man
|
||||
**/
|
||||
function deposit(
|
||||
address asset,
|
||||
uint256 amount,
|
||||
address onBehalfOf,
|
||||
uint16 referralCode
|
||||
) external;
|
||||
|
||||
/**
|
||||
* @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
|
||||
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
|
||||
* @param asset The address of the underlying asset to withdraw
|
||||
* @param amount The underlying amount to be withdrawn
|
||||
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
|
||||
* @param to Address that will receive the underlying, same as msg.sender if the user
|
||||
* wants to receive it on his own wallet, or a different address if the beneficiary is a
|
||||
* different wallet
|
||||
* @return The final amount withdrawn
|
||||
**/
|
||||
function withdraw(
|
||||
address asset,
|
||||
uint256 amount,
|
||||
address to
|
||||
) external returns (uint256);
|
||||
}
|
||||
|
||||
contract MixinAaveV2 {
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
|
||||
function _tradeAaveV2(
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256)
|
||||
{
|
||||
(ILendingPool lendingPool, address aToken) = abi.decode(bridgeData, (ILendingPool, address));
|
||||
|
||||
sellToken.approveIfBelow(
|
||||
address(lendingPool),
|
||||
sellAmount
|
||||
);
|
||||
|
||||
if (address(buyToken) == aToken) {
|
||||
lendingPool.deposit(address(sellToken), sellAmount, address(this), 0);
|
||||
// 1:1 mapping token -> aToken and have the same number of decimals as the underlying token
|
||||
return sellAmount;
|
||||
} else if (address(sellToken) == aToken) {
|
||||
return lendingPool.withdraw(address(buyToken), sellAmount, address(this));
|
||||
}
|
||||
|
||||
revert("MixinAaveV2/UNSUPPORTED_TOKEN_PAIR");
|
||||
}
|
||||
}
|
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
// 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 "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
|
||||
|
||||
/// @dev Minimal CToken interface
|
||||
interface ICToken {
|
||||
/// @dev deposits specified amount underlying tokens and mints cToken for the sender
|
||||
/// @param mintAmountInUnderlying amount of underlying tokens to deposit to mint cTokens
|
||||
/// @return status code of whether the mint was successful or not
|
||||
function mint(uint256 mintAmountInUnderlying) external returns (uint256);
|
||||
/// @dev redeems specified amount of cTokens and returns the underlying token to the sender
|
||||
/// @param redeemTokensInCtokens amount of cTokens to redeem for underlying collateral
|
||||
/// @return status code of whether the redemption was successful or not
|
||||
function redeem(uint256 redeemTokensInCtokens) external returns (uint256);
|
||||
}
|
||||
/// @dev Minimal CEther interface
|
||||
interface ICEther {
|
||||
/// @dev deposits the amount of Ether sent as value and return mints cEther for the sender
|
||||
function mint() payable external;
|
||||
/// @dev redeems specified amount of cETH and returns the underlying ether to the sender
|
||||
/// @dev redeemTokensInCEther amount of cETH to redeem for underlying ether
|
||||
/// @return status code of whether the redemption was successful or not
|
||||
function redeem(uint256 redeemTokensInCEther) external returns (uint256);
|
||||
}
|
||||
|
||||
contract MixinCompound {
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
using LibSafeMathV06 for uint256;
|
||||
|
||||
IEtherTokenV06 private immutable WETH;
|
||||
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
{
|
||||
WETH = weth;
|
||||
}
|
||||
|
||||
uint256 constant private COMPOUND_SUCCESS_CODE = 0;
|
||||
|
||||
function _tradeCompound(
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256)
|
||||
{
|
||||
(address cTokenAddress) = abi.decode(bridgeData, (address));
|
||||
uint256 beforeBalance = buyToken.balanceOf(address(this));
|
||||
|
||||
if (address(buyToken) == cTokenAddress) {
|
||||
if (address(sellToken) == address(WETH)) {
|
||||
// ETH/WETH -> cETH
|
||||
ICEther cETH = ICEther(cTokenAddress);
|
||||
// Compound expects ETH to be sent with mint call
|
||||
WETH.withdraw(sellAmount);
|
||||
// NOTE: cETH mint will revert on failure instead of returning a status code
|
||||
cETH.mint{value: sellAmount}();
|
||||
} else {
|
||||
sellToken.approveIfBelow(
|
||||
cTokenAddress,
|
||||
sellAmount
|
||||
);
|
||||
// Token -> cToken
|
||||
ICToken cToken = ICToken(cTokenAddress);
|
||||
require(cToken.mint(sellAmount) == COMPOUND_SUCCESS_CODE, "MixinCompound/FAILED_TO_MINT_CTOKEN");
|
||||
}
|
||||
} else if (address(sellToken) == cTokenAddress) {
|
||||
if (address(buyToken) == address(WETH)) {
|
||||
// cETH -> ETH/WETH
|
||||
uint256 etherBalanceBefore = address(this).balance;
|
||||
ICEther cETH = ICEther(cTokenAddress);
|
||||
require(cETH.redeem(sellAmount) == COMPOUND_SUCCESS_CODE, "MixinCompound/FAILED_TO_REDEEM_CETHER");
|
||||
uint256 etherBalanceAfter = address(this).balance;
|
||||
uint256 receivedEtherBalance = etherBalanceAfter.safeSub(etherBalanceBefore);
|
||||
WETH.deposit{value: receivedEtherBalance}();
|
||||
} else {
|
||||
ICToken cToken = ICToken(cTokenAddress);
|
||||
require(cToken.redeem(sellAmount) == COMPOUND_SUCCESS_CODE, "MixinCompound/FAILED_TO_REDEEM_CTOKEN");
|
||||
}
|
||||
}
|
||||
|
||||
return buyToken.balanceOf(address(this)).safeSub(beforeBalance);
|
||||
}
|
||||
}
|
@@ -46,6 +46,10 @@ contract TestMetaTransactionsTransformERC20Feature is
|
||||
payable
|
||||
returns (uint256 outputTokenAmount)
|
||||
{
|
||||
if (msg.value == 555) {
|
||||
tx.origin.transfer(1);
|
||||
}
|
||||
|
||||
if (msg.value == 666) {
|
||||
revert('FAIL');
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-zero-ex",
|
||||
"version": "0.28.2",
|
||||
"version": "0.30.1",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -43,7 +43,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinClipper|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -55,14 +55,14 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/zero-ex",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.6.0",
|
||||
"@0x/contract-addresses": "^6.6.1",
|
||||
"@0x/contracts-erc20": "^3.3.17",
|
||||
"@0x/contracts-gen": "^2.0.38",
|
||||
"@0x/contracts-test-utils": "^5.4.9",
|
||||
"@0x/dev-utils": "^4.2.7",
|
||||
"@0x/abi-gen": "^5.6.2",
|
||||
"@0x/contract-addresses": "^6.11.0",
|
||||
"@0x/contracts-erc20": "^3.3.25",
|
||||
"@0x/contracts-gen": "^2.0.40",
|
||||
"@0x/contracts-test-utils": "^5.4.16",
|
||||
"@0x/dev-utils": "^4.2.9",
|
||||
"@0x/order-utils": "^10.4.28",
|
||||
"@0x/sol-compiler": "^4.7.3",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.4",
|
||||
"@types/isomorphic-fetch": "^0.0.35",
|
||||
@@ -82,14 +82,14 @@
|
||||
"typescript": "4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.4.0",
|
||||
"@0x/protocol-utils": "^1.8.3",
|
||||
"@0x/subproviders": "^6.5.3",
|
||||
"@0x/types": "^3.3.3",
|
||||
"@0x/typescript-typings": "^5.2.0",
|
||||
"@0x/utils": "^6.4.3",
|
||||
"@0x/web3-wrapper": "^7.5.3",
|
||||
"ethereum-types": "^3.5.0",
|
||||
"@0x/base-contract": "^6.4.2",
|
||||
"@0x/protocol-utils": "^1.10.1",
|
||||
"@0x/subproviders": "^6.6.0",
|
||||
"@0x/types": "^3.3.4",
|
||||
"@0x/typescript-typings": "^5.2.1",
|
||||
"@0x/utils": "^6.4.4",
|
||||
"@0x/web3-wrapper": "^7.6.0",
|
||||
"ethereum-types": "^3.6.0",
|
||||
"ethereumjs-util": "^7.0.10",
|
||||
"ethers": "~4.0.4"
|
||||
},
|
||||
|
@@ -47,6 +47,7 @@ export {
|
||||
MultiplexFeatureContract,
|
||||
PayTakerTransformerContract,
|
||||
PositiveSlippageFeeTransformerContract,
|
||||
TransformERC20FeatureContract,
|
||||
WethTransformerContract,
|
||||
ZeroExContract,
|
||||
} from './wrappers';
|
||||
|
@@ -12,6 +12,7 @@ import {
|
||||
IZeroExContract,
|
||||
MetaTransactionsFeatureContract,
|
||||
NativeOrdersFeatureContract,
|
||||
OtcOrdersFeatureContract,
|
||||
OwnableFeatureContract,
|
||||
SimpleFunctionRegistryFeatureContract,
|
||||
TransformERC20FeatureContract,
|
||||
@@ -113,6 +114,7 @@ export interface FullFeatures extends BootstrapFeatures {
|
||||
transformERC20: string;
|
||||
metaTransactions: string;
|
||||
nativeOrders: string;
|
||||
otcOrders: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,6 +125,7 @@ export interface FullFeatureArtifacts extends BootstrapFeatureArtifacts {
|
||||
metaTransactions: SimpleContractArtifact;
|
||||
nativeOrders: SimpleContractArtifact;
|
||||
feeCollectorController: SimpleContractArtifact;
|
||||
otcOrders: SimpleContractArtifact;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,6 +158,7 @@ const DEFAULT_FULL_FEATURES_ARTIFACTS = {
|
||||
metaTransactions: artifacts.MetaTransactionsFeature,
|
||||
nativeOrders: artifacts.NativeOrdersFeature,
|
||||
feeCollectorController: artifacts.FeeCollectorController,
|
||||
otcOrders: artifacts.OtcOrdersFeature,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -222,6 +226,18 @@ export async function deployFullFeaturesAsync(
|
||||
_config.protocolFeeMultiplier,
|
||||
)
|
||||
).address,
|
||||
otcOrders:
|
||||
features.otcOrders ||
|
||||
(
|
||||
await OtcOrdersFeatureContract.deployFrom0xArtifactAsync(
|
||||
_featureArtifacts.otcOrders,
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
_config.zeroExAddress,
|
||||
_config.wethAddress,
|
||||
)
|
||||
).address,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -21,6 +21,7 @@ import * as FixinReentrancyGuard from '../test/generated-artifacts/FixinReentran
|
||||
import * as FixinTokenSpender from '../test/generated-artifacts/FixinTokenSpender.json';
|
||||
import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json';
|
||||
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
|
||||
import * as FundRecoveryFeature from '../test/generated-artifacts/FundRecoveryFeature.json';
|
||||
import * as IBatchFillNativeOrdersFeature from '../test/generated-artifacts/IBatchFillNativeOrdersFeature.json';
|
||||
import * as IBootstrapFeature from '../test/generated-artifacts/IBootstrapFeature.json';
|
||||
import * as IBridgeAdapter from '../test/generated-artifacts/IBridgeAdapter.json';
|
||||
@@ -28,6 +29,7 @@ import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json';
|
||||
import * as IERC20Transformer from '../test/generated-artifacts/IERC20Transformer.json';
|
||||
import * as IFeature from '../test/generated-artifacts/IFeature.json';
|
||||
import * as IFlashWallet from '../test/generated-artifacts/IFlashWallet.json';
|
||||
import * as IFundRecoveryFeature from '../test/generated-artifacts/IFundRecoveryFeature.json';
|
||||
import * as ILiquidityProvider from '../test/generated-artifacts/ILiquidityProvider.json';
|
||||
import * as ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidityProviderFeature.json';
|
||||
import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json';
|
||||
@@ -79,11 +81,12 @@ import * as LiquidityProviderFeature from '../test/generated-artifacts/Liquidity
|
||||
import * as LiquidityProviderSandbox from '../test/generated-artifacts/LiquidityProviderSandbox.json';
|
||||
import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
|
||||
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
|
||||
import * as MixinAaveV2 from '../test/generated-artifacts/MixinAaveV2.json';
|
||||
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
|
||||
import * as MixinBalancerV2 from '../test/generated-artifacts/MixinBalancerV2.json';
|
||||
import * as MixinBancor from '../test/generated-artifacts/MixinBancor.json';
|
||||
import * as MixinClipper from '../test/generated-artifacts/MixinClipper.json';
|
||||
import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json';
|
||||
import * as MixinCompound from '../test/generated-artifacts/MixinCompound.json';
|
||||
import * as MixinCryptoCom from '../test/generated-artifacts/MixinCryptoCom.json';
|
||||
import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json';
|
||||
import * as MixinCurveV2 from '../test/generated-artifacts/MixinCurveV2.json';
|
||||
@@ -198,6 +201,7 @@ export const artifacts = {
|
||||
TransformerDeployer: TransformerDeployer as ContractArtifact,
|
||||
BatchFillNativeOrdersFeature: BatchFillNativeOrdersFeature as ContractArtifact,
|
||||
BootstrapFeature: BootstrapFeature as ContractArtifact,
|
||||
FundRecoveryFeature: FundRecoveryFeature as ContractArtifact,
|
||||
LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
|
||||
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
|
||||
NativeOrdersFeature: NativeOrdersFeature as ContractArtifact,
|
||||
@@ -211,6 +215,7 @@ export const artifacts = {
|
||||
IBatchFillNativeOrdersFeature: IBatchFillNativeOrdersFeature as ContractArtifact,
|
||||
IBootstrapFeature: IBootstrapFeature as ContractArtifact,
|
||||
IFeature: IFeature as ContractArtifact,
|
||||
IFundRecoveryFeature: IFundRecoveryFeature as ContractArtifact,
|
||||
ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact,
|
||||
IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact,
|
||||
IMultiplexFeature: IMultiplexFeature as ContractArtifact,
|
||||
@@ -269,11 +274,12 @@ export const artifacts = {
|
||||
BridgeAdapter: BridgeAdapter as ContractArtifact,
|
||||
BridgeProtocols: BridgeProtocols as ContractArtifact,
|
||||
IBridgeAdapter: IBridgeAdapter as ContractArtifact,
|
||||
MixinAaveV2: MixinAaveV2 as ContractArtifact,
|
||||
MixinBalancer: MixinBalancer as ContractArtifact,
|
||||
MixinBalancerV2: MixinBalancerV2 as ContractArtifact,
|
||||
MixinBancor: MixinBancor as ContractArtifact,
|
||||
MixinClipper: MixinClipper as ContractArtifact,
|
||||
MixinCoFiX: MixinCoFiX as ContractArtifact,
|
||||
MixinCompound: MixinCompound as ContractArtifact,
|
||||
MixinCryptoCom: MixinCryptoCom as ContractArtifact,
|
||||
MixinCurve: MixinCurve as ContractArtifact,
|
||||
MixinCurveV2: MixinCurveV2 as ContractArtifact,
|
||||
|
96
contracts/zero-ex/test/features/fund_recovery_tests.ts
Normal file
96
contracts/zero-ex/test/features/fund_recovery_tests.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { blockchainTests, constants, expect, randomAddress } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, OwnableRevertErrors } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
|
||||
import { IOwnableFeatureContract, IZeroExContract } from '../../src/wrappers';
|
||||
import { artifacts } from '../artifacts';
|
||||
import { FundRecoveryFeatureContract } from '../generated-wrappers/fund_recovery_feature';
|
||||
import { abis } from '../utils/abis';
|
||||
import { fullMigrateAsync } from '../utils/migration';
|
||||
import { TestMintableERC20TokenContract } from '../wrappers';
|
||||
|
||||
blockchainTests('FundRecovery', async env => {
|
||||
let owner: string;
|
||||
let zeroEx: IZeroExContract;
|
||||
let token: TestMintableERC20TokenContract;
|
||||
before(async () => {
|
||||
const INITIAL_ERC20_BALANCE = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18);
|
||||
[owner] = await env.getAccountAddressesAsync();
|
||||
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {});
|
||||
token = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMintableERC20Token,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
{},
|
||||
);
|
||||
await token.mint(zeroEx.address, INITIAL_ERC20_BALANCE).awaitTransactionSuccessAsync();
|
||||
const featureImpl = await FundRecoveryFeatureContract.deployFrom0xArtifactAsync(
|
||||
artifacts.FundRecoveryFeature,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
await new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults, abis)
|
||||
.migrate(featureImpl.address, featureImpl.migrate().getABIEncodedTransactionData(), owner)
|
||||
.awaitTransactionSuccessAsync({ from: owner });
|
||||
});
|
||||
blockchainTests.resets('Should delegatecall `transferTrappedTokensTo` from the exchange proxy', () => {
|
||||
const ETH_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
||||
const recipientAddress = randomAddress();
|
||||
it('Tranfers an arbitrary ERC-20 Token', async () => {
|
||||
const amountOut = Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18);
|
||||
await zeroEx
|
||||
.transferTrappedTokensTo(token.address, amountOut, recipientAddress)
|
||||
.awaitTransactionSuccessAsync({ from: owner });
|
||||
const recipientAddressBalanceAferTransfer = await token.balanceOf(recipientAddress).callAsync();
|
||||
return expect(recipientAddressBalanceAferTransfer).to.bignumber.equal(amountOut);
|
||||
});
|
||||
it('Amount -1 transfers entire balance of ERC-20', async () => {
|
||||
const balanceOwner = await token.balanceOf(zeroEx.address).callAsync();
|
||||
await zeroEx
|
||||
.transferTrappedTokensTo(token.address, constants.MAX_UINT256, recipientAddress)
|
||||
.awaitTransactionSuccessAsync({ from: owner });
|
||||
const recipientAddressBalanceAferTransfer = await token.balanceOf(recipientAddress).callAsync();
|
||||
return expect(recipientAddressBalanceAferTransfer).to.bignumber.equal(balanceOwner);
|
||||
});
|
||||
it('Amount -1 transfers entire balance of ETH', async () => {
|
||||
const amountOut = new BigNumber(20);
|
||||
await env.web3Wrapper.awaitTransactionMinedAsync(
|
||||
await env.web3Wrapper.sendTransactionAsync({
|
||||
from: owner,
|
||||
to: zeroEx.address,
|
||||
value: amountOut,
|
||||
}),
|
||||
);
|
||||
const balanceOwner = await env.web3Wrapper.getBalanceInWeiAsync(zeroEx.address);
|
||||
await zeroEx
|
||||
.transferTrappedTokensTo(ETH_TOKEN_ADDRESS, constants.MAX_UINT256, recipientAddress)
|
||||
.awaitTransactionSuccessAsync({ from: owner });
|
||||
const recipientAddressBalanceAferTransfer = await env.web3Wrapper.getBalanceInWeiAsync(recipientAddress);
|
||||
return expect(recipientAddressBalanceAferTransfer).to.bignumber.equal(balanceOwner);
|
||||
});
|
||||
it('Transfers ETH ', async () => {
|
||||
const amountOut = new BigNumber(20);
|
||||
await env.web3Wrapper.awaitTransactionMinedAsync(
|
||||
await env.web3Wrapper.sendTransactionAsync({
|
||||
from: owner,
|
||||
to: zeroEx.address,
|
||||
value: amountOut,
|
||||
}),
|
||||
);
|
||||
await zeroEx
|
||||
.transferTrappedTokensTo(ETH_TOKEN_ADDRESS, amountOut.minus(1), recipientAddress)
|
||||
.awaitTransactionSuccessAsync({ from: owner });
|
||||
const recipientAddressBalance = await env.web3Wrapper.getBalanceInWeiAsync(recipientAddress);
|
||||
return expect(recipientAddressBalance).to.bignumber.be.equal(amountOut.minus(1));
|
||||
});
|
||||
it('Feature `transferTrappedTokensTo` can only be called by owner', async () => {
|
||||
const notOwner = randomAddress();
|
||||
return expect(
|
||||
zeroEx
|
||||
.transferTrappedTokensTo(ETH_TOKEN_ADDRESS, constants.MAX_UINT256, recipientAddress)
|
||||
.awaitTransactionSuccessAsync({ from: notOwner }),
|
||||
).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner, owner));
|
||||
});
|
||||
});
|
||||
});
|
@@ -38,6 +38,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
let nativeOrdersFeature: TestMetaTransactionsNativeOrdersFeatureContract;
|
||||
|
||||
const MAX_FEE_AMOUNT = new BigNumber('1e18');
|
||||
const TRANSFORM_ERC20_ONE_WEI_VALUE = new BigNumber(555);
|
||||
const TRANSFORM_ERC20_FAILING_VALUE = new BigNumber(666);
|
||||
const TRANSFORM_ERC20_REENTER_VALUE = new BigNumber(777);
|
||||
const TRANSFORM_ERC20_BATCH_REENTER_VALUE = new BigNumber(888);
|
||||
@@ -597,7 +598,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot reenter `executeMetaTransaction()`', async () => {
|
||||
it('cannot reduce initial ETH balance', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
@@ -609,58 +610,23 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_REENTER_VALUE,
|
||||
value: TRANSFORM_ERC20_ONE_WEI_VALUE,
|
||||
});
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(
|
||||
mtxHash,
|
||||
undefined,
|
||||
new ZeroExRevertErrors.Common.IllegalReentrancyError(
|
||||
feature.getSelector('executeMetaTransaction'),
|
||||
REENTRANCY_FLAG_MTX,
|
||||
).encode(),
|
||||
),
|
||||
// Send pre-existing ETH to the EP.
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await env.web3Wrapper.sendTransactionAsync({
|
||||
from: owner,
|
||||
to: zeroEx.address,
|
||||
value: new BigNumber(1),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot reenter `batchExecuteMetaTransactions()`', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_BATCH_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(
|
||||
mtxHash,
|
||||
undefined,
|
||||
new ZeroExRevertErrors.Common.IllegalReentrancyError(
|
||||
feature.getSelector('batchExecuteMetaTransactions'),
|
||||
REENTRANCY_FLAG_MTX,
|
||||
).encode(),
|
||||
),
|
||||
);
|
||||
return expect(tx).to.revertWith('MetaTransactionsFeature/ETH_LEAK');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -817,6 +783,37 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot reduce initial ETH balance', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_ONE_WEI_VALUE,
|
||||
});
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
// Send pre-existing ETH to the EP.
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await env.web3Wrapper.sendTransactionAsync({
|
||||
from: owner,
|
||||
to: zeroEx.address,
|
||||
value: new BigNumber(1),
|
||||
}),
|
||||
);
|
||||
const tx = feature.batchExecuteMetaTransactions([mtx], [signature]).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith('MetaTransactionsFeature/ETH_LEAK');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMetaTransactionExecutedBlock()', () => {
|
||||
|
@@ -19,6 +19,7 @@ export * from '../test/generated-wrappers/fixin_reentrancy_guard';
|
||||
export * from '../test/generated-wrappers/fixin_token_spender';
|
||||
export * from '../test/generated-wrappers/flash_wallet';
|
||||
export * from '../test/generated-wrappers/full_migration';
|
||||
export * from '../test/generated-wrappers/fund_recovery_feature';
|
||||
export * from '../test/generated-wrappers/i_batch_fill_native_orders_feature';
|
||||
export * from '../test/generated-wrappers/i_bootstrap_feature';
|
||||
export * from '../test/generated-wrappers/i_bridge_adapter';
|
||||
@@ -26,6 +27,7 @@ export * from '../test/generated-wrappers/i_erc20_bridge';
|
||||
export * from '../test/generated-wrappers/i_erc20_transformer';
|
||||
export * from '../test/generated-wrappers/i_feature';
|
||||
export * from '../test/generated-wrappers/i_flash_wallet';
|
||||
export * from '../test/generated-wrappers/i_fund_recovery_feature';
|
||||
export * from '../test/generated-wrappers/i_liquidity_provider';
|
||||
export * from '../test/generated-wrappers/i_liquidity_provider_feature';
|
||||
export * from '../test/generated-wrappers/i_liquidity_provider_sandbox';
|
||||
@@ -77,11 +79,12 @@ export * from '../test/generated-wrappers/liquidity_provider_feature';
|
||||
export * from '../test/generated-wrappers/liquidity_provider_sandbox';
|
||||
export * from '../test/generated-wrappers/log_metadata_transformer';
|
||||
export * from '../test/generated-wrappers/meta_transactions_feature';
|
||||
export * from '../test/generated-wrappers/mixin_aave_v2';
|
||||
export * from '../test/generated-wrappers/mixin_balancer';
|
||||
export * from '../test/generated-wrappers/mixin_balancer_v2';
|
||||
export * from '../test/generated-wrappers/mixin_bancor';
|
||||
export * from '../test/generated-wrappers/mixin_clipper';
|
||||
export * from '../test/generated-wrappers/mixin_co_fi_x';
|
||||
export * from '../test/generated-wrappers/mixin_compound';
|
||||
export * from '../test/generated-wrappers/mixin_crypto_com';
|
||||
export * from '../test/generated-wrappers/mixin_curve';
|
||||
export * from '../test/generated-wrappers/mixin_curve_v2';
|
||||
|
@@ -52,6 +52,7 @@
|
||||
"test/generated-artifacts/FixinTokenSpender.json",
|
||||
"test/generated-artifacts/FlashWallet.json",
|
||||
"test/generated-artifacts/FullMigration.json",
|
||||
"test/generated-artifacts/FundRecoveryFeature.json",
|
||||
"test/generated-artifacts/IBatchFillNativeOrdersFeature.json",
|
||||
"test/generated-artifacts/IBootstrapFeature.json",
|
||||
"test/generated-artifacts/IBridgeAdapter.json",
|
||||
@@ -59,6 +60,7 @@
|
||||
"test/generated-artifacts/IERC20Transformer.json",
|
||||
"test/generated-artifacts/IFeature.json",
|
||||
"test/generated-artifacts/IFlashWallet.json",
|
||||
"test/generated-artifacts/IFundRecoveryFeature.json",
|
||||
"test/generated-artifacts/ILiquidityProvider.json",
|
||||
"test/generated-artifacts/ILiquidityProviderFeature.json",
|
||||
"test/generated-artifacts/ILiquidityProviderSandbox.json",
|
||||
@@ -110,11 +112,12 @@
|
||||
"test/generated-artifacts/LiquidityProviderSandbox.json",
|
||||
"test/generated-artifacts/LogMetadataTransformer.json",
|
||||
"test/generated-artifacts/MetaTransactionsFeature.json",
|
||||
"test/generated-artifacts/MixinAaveV2.json",
|
||||
"test/generated-artifacts/MixinBalancer.json",
|
||||
"test/generated-artifacts/MixinBalancerV2.json",
|
||||
"test/generated-artifacts/MixinBancor.json",
|
||||
"test/generated-artifacts/MixinClipper.json",
|
||||
"test/generated-artifacts/MixinCoFiX.json",
|
||||
"test/generated-artifacts/MixinCompound.json",
|
||||
"test/generated-artifacts/MixinCryptoCom.json",
|
||||
"test/generated-artifacts/MixinCurve.json",
|
||||
"test/generated-artifacts/MixinCurveV2.json",
|
||||
|
@@ -3,10 +3,14 @@ Protocol Fees
|
||||
###############################
|
||||
|
||||
An ETH protocol fee is paid by the Taker each time a `Limit Order <./orders.html#limit-orders>`_ is `filled <./functions.html>`_.
|
||||
The fee is proportional to the gas cost of filling an order and scales linearly with gas price. The cost is currently ``70k * tx.gasprice``.
|
||||
The fee is proportional to the gas cost of filling an order and scales linearly with gas price. The cost is currently ``0 * tx.gasprice``.
|
||||
At the end of every Staking Epoch, these fees are aggregated and distributed to the makers as a liquidity reward: the reward is proportional to the maker's collected fees and staked ZRX relative to other makers.
|
||||
To learn more about protocol fees and liquidity incentives, see the `Official Spec <https://github.com/0xProject/0x-protocol-specification/blob/master/staking/staking-specification.md>`_.
|
||||
|
||||
.. note::
|
||||
|
||||
As of September 29, 2021, protocol fees have been removed for all order types in both Exchange V4 and V3 in accordance with `ZEIP-91 <https://0x.org/zrx/vote/zeip-91>`_.
|
||||
|
||||
.. note::
|
||||
|
||||
`RFQ Orders <./orders.html#rfq-orders>`_ are introduced in Exchange V4, and there is currently no protocol fee for filling this type of order.
|
||||
|
@@ -1,2 +1,3 @@
|
||||
six
|
||||
sphinx-markdown-tables
|
||||
sphinx==3.5.4
|
||||
sphinx-markdown-tables
|
||||
|
@@ -46,12 +46,12 @@
|
||||
"test:generate_docs:circleci": "for i in ${npm_package_config_packagesWithDocPages}; do yarn generate_doc --package $i || break -1; done;",
|
||||
"bundlewatch": "bundlewatch",
|
||||
"lint": "wsrun --fast-exit --parallel --exclude-missing -p $PKG -c lint",
|
||||
"upgrade_org_deps": "node node_modules/@0x/monorepo-scripts/lib/upgrade_deps.js -p '@0x|ethereum-types'",
|
||||
"upgrade_org_deps": "node node_modules/@0x/monorepo-scripts/lib/upgrade_deps.js -p '@0x/|ethereum-types'",
|
||||
"upgrade_deps": "node node_modules/@0x/monorepo-scripts/lib/upgrade_deps.js",
|
||||
"verdaccio": "docker run --rm -i -p 4873:4873 0xorg/verdaccio"
|
||||
},
|
||||
"config": {
|
||||
"contractsPackages": "@0x/contracts-erc20 @0x/contracts-test-utils @0x/contracts-utils @0x/contracts-zero-ex @0x/contracts-treasury",
|
||||
"contractsPackages": "@0x/contracts-erc20 @0x/contracts-test-utils @0x/contracts-utils @0x/contracts-zero-ex @0x/contracts-treasury",
|
||||
"nonContractPackages": "@0x/migrations @0x/contract-wrappers @0x/contract-addresses @0x/contract-artifacts @0x/contract-wrappers-test @0x/asset-swapper",
|
||||
"ignoreTestsForPackages": "",
|
||||
"mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic",
|
||||
|
@@ -1,4 +1,296 @@
|
||||
[
|
||||
{
|
||||
"version": "16.45.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update `Celo` intermediate tokens",
|
||||
"pr": 390
|
||||
}
|
||||
],
|
||||
"timestamp": 1641359319
|
||||
},
|
||||
{
|
||||
"version": "16.45.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Capture router timings",
|
||||
"pr": 388
|
||||
}
|
||||
],
|
||||
"timestamp": 1641308410
|
||||
},
|
||||
{
|
||||
"version": "16.44.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update neon-router and use router estimated output amount",
|
||||
"pr": 354
|
||||
}
|
||||
],
|
||||
"timestamp": 1640778328
|
||||
},
|
||||
{
|
||||
"version": "16.43.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "`UniswapV3` support for `Optimism`",
|
||||
"pr": 385
|
||||
}
|
||||
],
|
||||
"timestamp": 1640364306
|
||||
},
|
||||
{
|
||||
"version": "16.42.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "`UniswapV3` support for `Polygon`",
|
||||
"pr": 382
|
||||
},
|
||||
{
|
||||
"note": "Update `Beethoven` Graphql url",
|
||||
"pr": 383
|
||||
}
|
||||
],
|
||||
"timestamp": 1640124159
|
||||
},
|
||||
{
|
||||
"version": "16.41.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update mcusd contract address, and made celo native asset",
|
||||
"pr": 376
|
||||
}
|
||||
],
|
||||
"timestamp": 1638827302
|
||||
},
|
||||
{
|
||||
"version": "16.40.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `AaveV2` and `Compound` deposit/withdrawal liquidity source",
|
||||
"pr": 321
|
||||
}
|
||||
],
|
||||
"timestamp": 1638390144
|
||||
},
|
||||
{
|
||||
"version": "16.39.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Curve ETH/CRV pool",
|
||||
"pr": 378
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "16.38.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Capture sampler metrics",
|
||||
"pr": 374
|
||||
}
|
||||
],
|
||||
"timestamp": 1638228231
|
||||
},
|
||||
{
|
||||
"version": "16.37.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Changed Sushiswap router address",
|
||||
"pr": 373
|
||||
}
|
||||
],
|
||||
"timestamp": 1637349338
|
||||
},
|
||||
{
|
||||
"version": "16.36.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Specify liquid routes for FEI/TRIBE FXS/FRAX and OHM/FRAX",
|
||||
"pr": 371
|
||||
}
|
||||
],
|
||||
"timestamp": 1637290768
|
||||
},
|
||||
{
|
||||
"version": "16.35.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add Beethoven X, MorpheusSwap and JetSwap to Fantom",
|
||||
"pr": 370
|
||||
}
|
||||
],
|
||||
"timestamp": 1637206290
|
||||
},
|
||||
{
|
||||
"version": "16.34.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add support Celo",
|
||||
"pr": 367
|
||||
}
|
||||
],
|
||||
"timestamp": 1637102971
|
||||
},
|
||||
{
|
||||
"version": "16.33.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add support for Uniswap V3 1 bps pools",
|
||||
"pr": 366
|
||||
}
|
||||
],
|
||||
"timestamp": 1637065617
|
||||
},
|
||||
{
|
||||
"version": "16.32.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Extended Quote Report",
|
||||
"pr": 361
|
||||
}
|
||||
],
|
||||
"timestamp": 1636480845
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "16.31.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Added `Curve`, `Curve_V2` and `KyberDmm` to Avalanche",
|
||||
"pr": 363
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "16.30.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "16.30.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Fantom deployment",
|
||||
"pr": 347
|
||||
}
|
||||
],
|
||||
"timestamp": 1634668033
|
||||
},
|
||||
{
|
||||
"version": "16.29.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update neon-router version and address breaking changes",
|
||||
"pr": 344
|
||||
}
|
||||
],
|
||||
"timestamp": 1634553393
|
||||
},
|
||||
{
|
||||
"version": "16.29.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Check MAX_IN_RATIO in sampleBuysFromBalancer",
|
||||
"pr": 338
|
||||
},
|
||||
{
|
||||
"note": "Go back to using transformERC20 (instead of transformERC20Staging)",
|
||||
"pr": 343
|
||||
}
|
||||
],
|
||||
"timestamp": 1634147078
|
||||
},
|
||||
{
|
||||
"version": "16.29.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Remove `Clipper` as a custom liquidity source",
|
||||
"pr": 335
|
||||
}
|
||||
],
|
||||
"timestamp": 1633374058
|
||||
},
|
||||
{
|
||||
"version": "16.29.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Initial integration of neon-router (behind feature flag)",
|
||||
"pr": 295
|
||||
}
|
||||
],
|
||||
"timestamp": 1633350101
|
||||
},
|
||||
{
|
||||
"version": "16.28.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update ExchangeProxySwapQuoteConsumer for Multiplex V2 and friends",
|
||||
"pr": 282
|
||||
}
|
||||
],
|
||||
"timestamp": 1632957537
|
||||
},
|
||||
{
|
||||
"version": "16.27.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Remove protocol fees by setting `PROTOCOL_FEE_MULTIPLIER` to 0",
|
||||
"pr": 333
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1631710679,
|
||||
"version": "16.27.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1631646242,
|
||||
"version": "16.27.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1631639620,
|
||||
"version": "16.27.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "16.27.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Fix ApproximateBuys sampler to terminate if the buy amount is not met",
|
||||
"pr": 319
|
||||
}
|
||||
],
|
||||
"timestamp": 1631120757
|
||||
},
|
||||
{
|
||||
"version": "16.27.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Avalanche deployment",
|
||||
"pr": 312
|
||||
}
|
||||
],
|
||||
"timestamp": 1630459879
|
||||
},
|
||||
{
|
||||
"version": "16.26.2",
|
||||
"changes": [
|
||||
|
@@ -5,6 +5,124 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v16.45.1 - _January 5, 2022_
|
||||
|
||||
* Update `Celo` intermediate tokens (#390)
|
||||
|
||||
## v16.45.0 - _January 4, 2022_
|
||||
|
||||
* Capture router timings (#388)
|
||||
|
||||
## v16.44.0 - _December 29, 2021_
|
||||
|
||||
* Update neon-router and use router estimated output amount (#354)
|
||||
|
||||
## v16.43.0 - _December 24, 2021_
|
||||
|
||||
* `UniswapV3` support for `Optimism` (#385)
|
||||
|
||||
## v16.42.0 - _December 21, 2021_
|
||||
|
||||
* `UniswapV3` support for `Polygon` (#382)
|
||||
* Update `Beethoven` Graphql url (#383)
|
||||
|
||||
## v16.41.0 - _December 6, 2021_
|
||||
|
||||
* Update mcusd contract address, and made celo native asset (#376)
|
||||
|
||||
## v16.40.0 - _December 1, 2021_
|
||||
|
||||
* Add `AaveV2` and `Compound` deposit/withdrawal liquidity source (#321)
|
||||
|
||||
## v16.39.0 - _Invalid date_
|
||||
|
||||
* Curve ETH/CRV pool (#378)
|
||||
|
||||
## v16.38.0 - _November 29, 2021_
|
||||
|
||||
* Capture sampler metrics (#374)
|
||||
|
||||
## v16.37.0 - _November 19, 2021_
|
||||
|
||||
* Changed Sushiswap router address (#373)
|
||||
|
||||
## v16.36.0 - _November 19, 2021_
|
||||
|
||||
* Specify liquid routes for FEI/TRIBE FXS/FRAX and OHM/FRAX (#371)
|
||||
|
||||
## v16.35.0 - _November 18, 2021_
|
||||
|
||||
* Add Beethoven X, MorpheusSwap and JetSwap to Fantom (#370)
|
||||
|
||||
## v16.34.0 - _November 16, 2021_
|
||||
|
||||
* Add support Celo (#367)
|
||||
|
||||
## v16.33.0 - _November 16, 2021_
|
||||
|
||||
* Add support for Uniswap V3 1 bps pools (#366)
|
||||
|
||||
## v16.32.0 - _November 9, 2021_
|
||||
|
||||
* Extended Quote Report (#361)
|
||||
|
||||
## v16.31.0 - _November 3, 2021_
|
||||
|
||||
* Added `Curve`, `Curve_V2` and `KyberDmm` to Avalanche (#363)
|
||||
|
||||
## v16.30.1 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.30.0 - _October 19, 2021_
|
||||
|
||||
* Fantom deployment (#347)
|
||||
|
||||
## v16.29.3 - _October 18, 2021_
|
||||
|
||||
* Update neon-router version and address breaking changes (#344)
|
||||
|
||||
## v16.29.2 - _October 13, 2021_
|
||||
|
||||
* Check MAX_IN_RATIO in sampleBuysFromBalancer (#338)
|
||||
* Go back to using transformERC20 (instead of transformERC20Staging) (#343)
|
||||
|
||||
## v16.29.1 - _October 4, 2021_
|
||||
|
||||
* Remove `Clipper` as a custom liquidity source (#335)
|
||||
|
||||
## v16.29.0 - _October 4, 2021_
|
||||
|
||||
* Initial integration of neon-router (behind feature flag) (#295)
|
||||
|
||||
## v16.28.0 - _September 29, 2021_
|
||||
|
||||
* Update ExchangeProxySwapQuoteConsumer for Multiplex V2 and friends (#282)
|
||||
|
||||
## v16.27.5 - _Invalid date_
|
||||
|
||||
* Remove protocol fees by setting `PROTOCOL_FEE_MULTIPLIER` to 0 (#333)
|
||||
|
||||
## v16.27.4 - _September 15, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.27.3 - _September 14, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.27.2 - _September 14, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.27.1 - _September 8, 2021_
|
||||
|
||||
* Fix ApproximateBuys sampler to terminate if the buy amount is not met (#319)
|
||||
|
||||
## v16.27.0 - _September 1, 2021_
|
||||
|
||||
* Avalanche deployment (#312)
|
||||
|
||||
## v16.26.2 - _August 31, 2021_
|
||||
|
||||
* chore: Curve new pools (CVX-CRX, MIM, atricrypto3)
|
||||
|
@@ -77,6 +77,7 @@ contract ApproximateBuys {
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
|
||||
uint256 eps = 0;
|
||||
for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) {
|
||||
// adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER
|
||||
sellAmount = _safeGetPartialAmountCeil(
|
||||
@@ -108,7 +109,7 @@ contract ApproximateBuys {
|
||||
buyAmount = _buyAmount;
|
||||
// If we've reached our goal, exit early
|
||||
if (buyAmount >= makerTokenAmounts[i]) {
|
||||
uint256 eps =
|
||||
eps =
|
||||
(buyAmount - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS /
|
||||
makerTokenAmounts[i];
|
||||
if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
|
||||
@@ -116,6 +117,9 @@ contract ApproximateBuys {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (eps == 0 || eps > APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
|
||||
break;
|
||||
}
|
||||
// We do our best to close in on the requested amount, but we can either over buy or under buy and exit
|
||||
// if we hit a max iteration limit
|
||||
// We scale the sell amount to get the approximate target
|
||||
|
@@ -154,6 +154,12 @@ contract BalancerSampler {
|
||||
)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
// Handles this revert scenario:
|
||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443
|
||||
if (amount > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) {
|
||||
break;
|
||||
}
|
||||
|
||||
takerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
|
96
packages/asset-swapper/contracts/src/CompoundSampler.sol
Normal file
96
packages/asset-swapper/contracts/src/CompoundSampler.sol
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
|
||||
// Minimal CToken interface
|
||||
interface ICToken {
|
||||
function mint(uint mintAmount) external returns (uint);
|
||||
function redeem(uint redeemTokens) external returns (uint);
|
||||
function redeemUnderlying(uint redeemAmount) external returns (uint);
|
||||
function exchangeRateStored() external view returns (uint);
|
||||
function decimals() external view returns (uint8);
|
||||
}
|
||||
|
||||
contract CompoundSampler is SamplerUtils {
|
||||
uint256 constant private EXCHANGE_RATE_SCALE = 1e10;
|
||||
|
||||
function sampleSellsFromCompound(
|
||||
ICToken cToken,
|
||||
IERC20TokenV06 takerToken,
|
||||
IERC20TokenV06 makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
// Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals
|
||||
uint256 exchangeRate = cToken.exchangeRateStored();
|
||||
uint256 cTokenDecimals = uint256(cToken.decimals());
|
||||
|
||||
if (address(makerToken) == address(cToken)) {
|
||||
// mint
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] = (takerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals) / exchangeRate;
|
||||
}
|
||||
|
||||
} else if (address(takerToken) == address(cToken)) {
|
||||
// redeem
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] = (takerTokenAmounts[i] * exchangeRate) / (EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sampleBuysFromCompound(
|
||||
ICToken cToken,
|
||||
IERC20TokenV06 takerToken,
|
||||
IERC20TokenV06 makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
// Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals
|
||||
uint256 exchangeRate = cToken.exchangeRateStored();
|
||||
uint256 cTokenDecimals = uint256(cToken.decimals());
|
||||
|
||||
if (address(makerToken) == address(cToken)) {
|
||||
// mint
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
takerTokenAmounts[i] = makerTokenAmounts[i] * exchangeRate / (EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals);
|
||||
}
|
||||
} else if (address(takerToken) == address(cToken)) {
|
||||
// redeem
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
takerTokenAmounts[i] = (makerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals)/exchangeRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -23,6 +23,7 @@ pragma experimental ABIEncoderV2;
|
||||
import "./BalancerSampler.sol";
|
||||
import "./BalancerV2Sampler.sol";
|
||||
import "./BancorSampler.sol";
|
||||
import "./CompoundSampler.sol";
|
||||
import "./CurveSampler.sol";
|
||||
import "./DODOSampler.sol";
|
||||
import "./DODOV2Sampler.sol";
|
||||
@@ -48,6 +49,7 @@ contract ERC20BridgeSampler is
|
||||
BalancerSampler,
|
||||
BalancerV2Sampler,
|
||||
BancorSampler,
|
||||
CompoundSampler,
|
||||
CurveSampler,
|
||||
DODOSampler,
|
||||
DODOV2Sampler,
|
||||
|
@@ -159,8 +159,11 @@ contract KyberDmmSampler
|
||||
(path[i], path[i + 1])
|
||||
returns (address[] memory allPools)
|
||||
{
|
||||
if (allPools.length == 0) {
|
||||
return new address[](0);
|
||||
}
|
||||
|
||||
uint256 maxSupply = 0;
|
||||
require(allPools.length >= 1, "KyberDMMSampler/NO_POOLS_FOUND");
|
||||
for (uint256 j = 0; j < allPools.length; j++) {
|
||||
uint256 totalSupply = IKyberDmmPool(allPools[j]).totalSupply();
|
||||
if (totalSupply > maxSupply) {
|
||||
|
@@ -51,7 +51,7 @@ interface IUniswapV3Pool {
|
||||
contract UniswapV3Sampler
|
||||
{
|
||||
/// @dev Gas limit for UniswapV3 calls. This is 100% a guess.
|
||||
uint256 constant private QUOTE_GAS = 300e3;
|
||||
uint256 constant private QUOTE_GAS = 600e3;
|
||||
|
||||
/// @dev Sample sell quotes from UniswapV3.
|
||||
/// @param quoter UniswapV3 Quoter contract.
|
||||
@@ -174,8 +174,9 @@ contract UniswapV3Sampler
|
||||
tokenPath.length - startIndex >= 2,
|
||||
"UniswapV3Sampler/tokenPath too short"
|
||||
);
|
||||
uint24[3] memory validPoolFees = [
|
||||
uint24[4] memory validPoolFees = [
|
||||
// The launch pool fees. Could get hairier if they add more.
|
||||
uint24(0.0001e6),
|
||||
uint24(0.0005e6),
|
||||
uint24(0.003e6),
|
||||
uint24(0.01e6)
|
||||
|
@@ -77,4 +77,19 @@ contract UtilitySampler {
|
||||
assembly { size := extcodesize(account) }
|
||||
return size > 0;
|
||||
}
|
||||
|
||||
function getGasLeft()
|
||||
public
|
||||
returns (uint256)
|
||||
{
|
||||
return gasleft();
|
||||
}
|
||||
|
||||
function getBlockNumber()
|
||||
public
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return block.number;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/asset-swapper",
|
||||
"version": "16.26.2",
|
||||
"version": "16.45.1",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -39,7 +39,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|FakeTaker|IBalancer|IBancor|ICurve|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json",
|
||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|FakeTaker|IBalancer|IBancor|ICurve|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json",
|
||||
"postpublish": {
|
||||
"assets": []
|
||||
}
|
||||
@@ -58,20 +58,21 @@
|
||||
"registry": "git@github.com:0xProject/gitpkg-registry.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.27",
|
||||
"@0x/base-contract": "^6.4.0",
|
||||
"@0x/contract-addresses": "^6.6.1",
|
||||
"@0x/contract-wrappers": "^13.17.5",
|
||||
"@0x/contracts-erc20": "^3.3.17",
|
||||
"@0x/contracts-zero-ex": "^0.27.0",
|
||||
"@0x/dev-utils": "^4.2.7",
|
||||
"@0x/json-schemas": "^6.1.3",
|
||||
"@0x/protocol-utils": "^1.8.3",
|
||||
"@0x/quote-server": "^6.0.2",
|
||||
"@0x/types": "^3.3.3",
|
||||
"@0x/typescript-typings": "^5.2.0",
|
||||
"@0x/utils": "^6.4.3",
|
||||
"@0x/web3-wrapper": "^7.5.3",
|
||||
"@0x/assert": "^3.0.29",
|
||||
"@0x/base-contract": "^6.4.2",
|
||||
"@0x/contract-addresses": "^6.11.0",
|
||||
"@0x/contract-wrappers": "^13.18.5",
|
||||
"@0x/contracts-erc20": "^3.3.25",
|
||||
"@0x/contracts-zero-ex": "^0.30.1",
|
||||
"@0x/dev-utils": "^4.2.9",
|
||||
"@0x/json-schemas": "^6.3.0",
|
||||
"@0x/neon-router": "^0.3.1",
|
||||
"@0x/protocol-utils": "^1.10.1",
|
||||
"@0x/quote-server": "^6.0.6",
|
||||
"@0x/types": "^3.3.4",
|
||||
"@0x/typescript-typings": "^5.2.1",
|
||||
"@0x/utils": "^6.4.4",
|
||||
"@0x/web3-wrapper": "^7.6.0",
|
||||
"@balancer-labs/sor": "0.3.2",
|
||||
"@bancor/sdk": "0.2.9",
|
||||
"@ethersproject/abi": "^5.0.1",
|
||||
@@ -83,7 +84,7 @@
|
||||
"axios-mock-adapter": "^1.19.0",
|
||||
"cream-sor": "^0.3.3",
|
||||
"decimal.js": "^10.2.0",
|
||||
"ethereum-types": "^3.5.0",
|
||||
"ethereum-types": "^3.6.0",
|
||||
"ethereumjs-util": "^7.0.10",
|
||||
"fast-abi": "^0.0.2",
|
||||
"graphql": "^15.4.0",
|
||||
@@ -92,20 +93,20 @@
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.6.0",
|
||||
"@0x/abi-gen": "^5.6.2",
|
||||
"@0x/contracts-asset-proxy": "^3.7.19",
|
||||
"@0x/contracts-exchange": "^3.2.38",
|
||||
"@0x/contracts-exchange-libs": "^4.3.37",
|
||||
"@0x/contracts-gen": "^2.0.38",
|
||||
"@0x/contracts-test-utils": "^5.4.9",
|
||||
"@0x/contracts-utils": "^4.7.17",
|
||||
"@0x/contracts-gen": "^2.0.40",
|
||||
"@0x/contracts-test-utils": "^5.4.16",
|
||||
"@0x/contracts-utils": "^4.8.6",
|
||||
"@0x/mesh-rpc-client": "^9.4.2",
|
||||
"@0x/migrations": "^8.1.3",
|
||||
"@0x/sol-compiler": "^4.7.3",
|
||||
"@0x/subproviders": "^6.5.3",
|
||||
"@0x/migrations": "^8.1.14",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
"@0x/subproviders": "^6.6.0",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.4",
|
||||
"@0x/types": "^3.3.3",
|
||||
"@0x/types": "^3.3.4",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "12.12.54",
|
||||
|
@@ -28,7 +28,6 @@ const ONE_SECOND_MS = 1000;
|
||||
const ONE_MINUTE_SECS = 60;
|
||||
const ONE_MINUTE_MS = ONE_SECOND_MS * ONE_MINUTE_SECS;
|
||||
const DEFAULT_PER_PAGE = 1000;
|
||||
const ZERO_AMOUNT = new BigNumber(0);
|
||||
const ALT_MM_IMPUTED_INDICATIVE_EXPIRY_SECONDS = 180;
|
||||
|
||||
const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = {
|
||||
@@ -38,11 +37,12 @@ const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = {
|
||||
|
||||
// 6 seconds polling interval
|
||||
const PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS = 6000;
|
||||
const PROTOCOL_FEE_MULTIPLIER = new BigNumber(70000);
|
||||
const PROTOCOL_FEE_MULTIPLIER = new BigNumber(0);
|
||||
|
||||
// default 50% buffer for selecting native orders to be aggregated with other sources
|
||||
const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5;
|
||||
|
||||
export const ZERO_AMOUNT = new BigNumber(0);
|
||||
const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||
chainId: ChainId.Mainnet,
|
||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
||||
@@ -50,7 +50,7 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||
samplerGasLimit: 500e6,
|
||||
ethGasStationUrl: ETH_GAS_STATION_API_URL,
|
||||
rfqt: {
|
||||
takerApiKeyWhitelist: [],
|
||||
integratorsWhitelist: [],
|
||||
makerAssetOfferings: {},
|
||||
txOriginBlacklist: new Set(),
|
||||
},
|
||||
|
@@ -88,6 +88,7 @@ export {
|
||||
ExchangeProxyContractOpts,
|
||||
ExchangeProxyRefundReceiver,
|
||||
GetExtensionContractTypeOpts,
|
||||
Integrator,
|
||||
LogFunction,
|
||||
MarketBuySwapQuote,
|
||||
MarketOperation,
|
||||
@@ -112,6 +113,7 @@ export {
|
||||
SwapQuoterError,
|
||||
SwapQuoterOpts,
|
||||
SwapQuoterRfqOpts,
|
||||
SamplerMetrics,
|
||||
} from './types';
|
||||
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
||||
export {
|
||||
@@ -161,14 +163,20 @@ export {
|
||||
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||
export {
|
||||
BridgeQuoteReportEntry,
|
||||
jsonifyFillData,
|
||||
MultiHopQuoteReportEntry,
|
||||
NativeLimitOrderQuoteReportEntry,
|
||||
NativeRfqOrderQuoteReportEntry,
|
||||
QuoteReport,
|
||||
QuoteReportEntry,
|
||||
ExtendedQuoteReport,
|
||||
ExtendedQuoteReportSources,
|
||||
ExtendedQuoteReportEntry,
|
||||
ExtendedQuoteReportIndexedEntry,
|
||||
ExtendedQuoteReportIndexedEntryOutbound,
|
||||
PriceComparisonsReport,
|
||||
} from './utils/quote_report_generator';
|
||||
export { QuoteRequestor } from './utils/quote_requestor';
|
||||
export { QuoteRequestor, V4RFQIndicativeQuoteMM } from './utils/quote_requestor';
|
||||
export { ERC20BridgeSamplerContract, BalanceCheckerContract, FakeTakerContract } from './wrappers';
|
||||
import { ERC20BridgeSource } from './utils/market_operation_utils/types';
|
||||
export type Native = ERC20BridgeSource.Native;
|
||||
|
57
packages/asset-swapper/src/noop_samplers/AaveV2Sampler.ts
Normal file
57
packages/asset-swapper/src/noop_samplers/AaveV2Sampler.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { ZERO_AMOUNT } from '../constants';
|
||||
export interface AaveInfo {
|
||||
lendingPool: string;
|
||||
aToken: string;
|
||||
underlyingToken: string;
|
||||
}
|
||||
// tslint:disable-next-line:no-unnecessary-class
|
||||
export class AaveV2Sampler {
|
||||
public static sampleSellsFromAaveV2(
|
||||
aaveInfo: AaveInfo,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerTokenAmounts: BigNumber[],
|
||||
): BigNumber[] {
|
||||
// Deposit/Withdrawal underlying <-> aToken is always 1:1
|
||||
if (
|
||||
(takerToken.toLowerCase() === aaveInfo.aToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase()) ||
|
||||
(takerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === aaveInfo.aToken.toLowerCase())
|
||||
) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
// Not matching the reserve return 0 results
|
||||
const numSamples = takerTokenAmounts.length;
|
||||
|
||||
const makerTokenAmounts = new Array(numSamples);
|
||||
makerTokenAmounts.fill(ZERO_AMOUNT);
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
public static sampleBuysFromAaveV2(
|
||||
aaveInfo: AaveInfo,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerTokenAmounts: BigNumber[],
|
||||
): BigNumber[] {
|
||||
// Deposit/Withdrawal underlying <-> aToken is always 1:1
|
||||
if (
|
||||
(takerToken.toLowerCase() === aaveInfo.aToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase()) ||
|
||||
(takerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === aaveInfo.aToken.toLowerCase())
|
||||
) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
// Not matching the reserve return 0 results
|
||||
const numSamples = makerTokenAmounts.length;
|
||||
const takerTokenAmounts = new Array(numSamples);
|
||||
takerTokenAmounts.fill(ZERO_AMOUNT);
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
import { ChainId, ContractAddresses } from '@0x/contract-addresses';
|
||||
import { IZeroExContract, WETH9Contract } from '@0x/contract-wrappers';
|
||||
import { MultiplexFeatureContract } from '@0x/contracts-zero-ex';
|
||||
import { IZeroExContract } from '@0x/contract-wrappers';
|
||||
import {
|
||||
encodeAffiliateFeeTransformerData,
|
||||
encodeCurveLiquidityProviderData,
|
||||
@@ -51,6 +50,7 @@ import {
|
||||
import {
|
||||
multiplexPlpEncoder,
|
||||
multiplexRfqEncoder,
|
||||
MultiplexSubcall,
|
||||
multiplexTransformERC20Encoder,
|
||||
multiplexUniswapEncoder,
|
||||
} from './multiplex_encoders';
|
||||
@@ -82,7 +82,6 @@ const FAKE_PROVIDER: any = {
|
||||
return;
|
||||
},
|
||||
};
|
||||
const DUMMY_WETH_CONTRACT = new WETH9Contract(NULL_ADDRESS, FAKE_PROVIDER);
|
||||
|
||||
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
public readonly chainId: ChainId;
|
||||
@@ -95,7 +94,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
};
|
||||
|
||||
private readonly _exchangeProxy: IZeroExContract;
|
||||
private readonly _multiplex: MultiplexFeatureContract;
|
||||
|
||||
constructor(public readonly contractAddresses: ContractAddresses, options: Partial<SwapQuoteConsumerOpts> = {}) {
|
||||
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
||||
@@ -103,7 +101,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
this.chainId = chainId;
|
||||
this.contractAddresses = contractAddresses;
|
||||
this._exchangeProxy = new IZeroExContract(contractAddresses.exchangeProxy, FAKE_PROVIDER);
|
||||
this._multiplex = new MultiplexFeatureContract(contractAddresses.exchangeProxy, FAKE_PROVIDER);
|
||||
this.transformerNonces = {
|
||||
wethTransformer: findTransformerNonce(
|
||||
contractAddresses.transformers.wethTransformer,
|
||||
@@ -338,7 +335,10 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
|
||||
if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) {
|
||||
return {
|
||||
calldataHexString: this._encodeMultiplexBatchFillCalldata({ ...quote, orders: slippedOrders }),
|
||||
calldataHexString: this._encodeMultiplexBatchFillCalldata(
|
||||
{ ...quote, orders: slippedOrders },
|
||||
optsWithDefaults,
|
||||
),
|
||||
ethAmount,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
@@ -360,8 +360,9 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
|
||||
// Build up the transforms.
|
||||
const transforms = [];
|
||||
if (isFromETH) {
|
||||
// Create a WETH wrapper if coming from ETH.
|
||||
// Create a WETH wrapper if coming from ETH.
|
||||
// Dont add the wethTransformer to CELO. There is no wrap/unwrap logic for CELO.
|
||||
if (isFromETH && this.chainId !== ChainId.Celo) {
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.wethTransformer,
|
||||
data: encodeWethTransformerData({
|
||||
@@ -413,9 +414,9 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (isToETH) {
|
||||
// Create a WETH unwrapper if going to ETH.
|
||||
// Create a WETH unwrapper if going to ETH.
|
||||
// Dont add the wethTransformer on CELO. There is no wrap/unwrap logic for CELO.
|
||||
if (isToETH && this.chainId !== ChainId.Celo) {
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.wethTransformer,
|
||||
data: encodeWethTransformerData({
|
||||
@@ -473,19 +474,30 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Return any unspent sell tokens.
|
||||
const payTakerTokens = [sellToken];
|
||||
// Return any unspent intermediate tokens for two-hop swaps.
|
||||
if (quote.isTwoHop) {
|
||||
payTakerTokens.push(intermediateToken);
|
||||
}
|
||||
// Return any unspent ETH. If ETH is the buy token, it will
|
||||
// be returned in TransformERC20Feature rather than PayTakerTransformer.
|
||||
if (!isToETH) {
|
||||
payTakerTokens.push(ETH_TOKEN_ADDRESS);
|
||||
}
|
||||
// The final transformer will send all funds to the taker.
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.payTakerTransformer,
|
||||
data: encodePayTakerTransformerData({
|
||||
tokens: [sellToken, buyToken, ETH_TOKEN_ADDRESS].concat(quote.isTwoHop ? intermediateToken : []),
|
||||
tokens: payTakerTokens,
|
||||
amounts: [],
|
||||
}),
|
||||
});
|
||||
|
||||
const TO_ETH_ADDRESS = this.chainId === ChainId.Celo ? this.contractAddresses.etherToken : ETH_TOKEN_ADDRESS;
|
||||
const calldataHexString = this._exchangeProxy
|
||||
.transformERC20(
|
||||
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
|
||||
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
isToETH ? TO_ETH_ADDRESS : buyToken,
|
||||
shouldSellEntireBalance ? MAX_UINT256 : sellAmount,
|
||||
minBuyAmount,
|
||||
transforms,
|
||||
@@ -509,8 +521,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
throw new Error('Execution not supported for Exchange Proxy quotes');
|
||||
}
|
||||
|
||||
private _encodeMultiplexBatchFillCalldata(quote: SwapQuote): string {
|
||||
const wrappedBatchCalls = [];
|
||||
private _encodeMultiplexBatchFillCalldata(quote: SwapQuote, opts: ExchangeProxyContractOpts): string {
|
||||
const subcalls = [];
|
||||
for_loop: for (const [i, order] of quote.orders.entries()) {
|
||||
switch_statement: switch (order.source) {
|
||||
case ERC20BridgeSource.Native:
|
||||
@@ -519,8 +531,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
// before calling this function.
|
||||
throw new Error('Multiplex batch fill only supported for RFQ native orders');
|
||||
}
|
||||
wrappedBatchCalls.push({
|
||||
selector: this._exchangeProxy.getSelector('_fillRfqOrder'),
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.Rfq,
|
||||
sellAmount: order.takerAmount,
|
||||
data: multiplexRfqEncoder.encode({
|
||||
order: order.fillData.order,
|
||||
@@ -530,8 +542,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
break switch_statement;
|
||||
case ERC20BridgeSource.UniswapV2:
|
||||
case ERC20BridgeSource.SushiSwap:
|
||||
wrappedBatchCalls.push({
|
||||
selector: this._multiplex.getSelector('_sellToUniswap'),
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.UniswapV2,
|
||||
sellAmount: order.takerAmount,
|
||||
data: multiplexUniswapEncoder.encode({
|
||||
tokens: (order.fillData as UniswapV2FillData).tokenAddressPath,
|
||||
@@ -540,8 +552,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
});
|
||||
break switch_statement;
|
||||
case ERC20BridgeSource.LiquidityProvider:
|
||||
wrappedBatchCalls.push({
|
||||
selector: this._multiplex.getSelector('_sellToLiquidityProvider'),
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.LiquidityProvider,
|
||||
sellAmount: order.takerAmount,
|
||||
data: multiplexPlpEncoder.encode({
|
||||
provider: (order.fillData as LiquidityProviderFillData).poolAddress,
|
||||
@@ -551,8 +563,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
break switch_statement;
|
||||
case ERC20BridgeSource.UniswapV3:
|
||||
const fillData = (order as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
|
||||
wrappedBatchCalls.push({
|
||||
selector: this._exchangeProxy.getSelector('sellTokenForTokenToUniswapV3'),
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.UniswapV3,
|
||||
sellAmount: order.takerAmount,
|
||||
data: fillData.uniswapPath,
|
||||
});
|
||||
@@ -571,54 +583,59 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
{
|
||||
deploymentNonce: this.transformerNonces.payTakerTransformer,
|
||||
data: encodePayTakerTransformerData({
|
||||
tokens: [quote.takerToken, quote.makerToken],
|
||||
tokens: [quote.takerToken],
|
||||
amounts: [],
|
||||
}),
|
||||
},
|
||||
];
|
||||
wrappedBatchCalls.push({
|
||||
selector: this._exchangeProxy.getSelector('_transformERC20'),
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.TransformERC20,
|
||||
sellAmount: BigNumber.sum(...quote.orders.slice(i).map(o => o.takerAmount)),
|
||||
data: multiplexTransformERC20Encoder.encode({
|
||||
transformations,
|
||||
ethValue: constants.ZERO_AMOUNT,
|
||||
}),
|
||||
});
|
||||
break for_loop;
|
||||
}
|
||||
}
|
||||
return this._exchangeProxy
|
||||
.batchFill(
|
||||
{
|
||||
inputToken: quote.takerToken,
|
||||
outputToken: quote.makerToken,
|
||||
sellAmount: quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
calls: wrappedBatchCalls,
|
||||
},
|
||||
quote.worstCaseQuoteInfo.makerAmount,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
if (opts.isFromETH) {
|
||||
return this._exchangeProxy
|
||||
.multiplexBatchSellEthForToken(quote.makerToken, subcalls, quote.worstCaseQuoteInfo.makerAmount)
|
||||
.getABIEncodedTransactionData();
|
||||
} else if (opts.isToETH) {
|
||||
return this._exchangeProxy
|
||||
.multiplexBatchSellTokenForEth(
|
||||
quote.takerToken,
|
||||
subcalls,
|
||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
quote.worstCaseQuoteInfo.makerAmount,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
} else {
|
||||
return this._exchangeProxy
|
||||
.multiplexBatchSellTokenForToken(
|
||||
quote.takerToken,
|
||||
quote.makerToken,
|
||||
subcalls,
|
||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
quote.worstCaseQuoteInfo.makerAmount,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
}
|
||||
}
|
||||
|
||||
private _encodeMultiplexMultiHopFillCalldata(quote: SwapQuote, opts: ExchangeProxyContractOpts): string {
|
||||
const wrappedMultiHopCalls = [];
|
||||
const tokens: string[] = [];
|
||||
if (opts.isFromETH) {
|
||||
wrappedMultiHopCalls.push({
|
||||
selector: DUMMY_WETH_CONTRACT.getSelector('deposit'),
|
||||
data: NULL_BYTES,
|
||||
});
|
||||
tokens.push(ETH_TOKEN_ADDRESS);
|
||||
}
|
||||
const subcalls = [];
|
||||
const [firstHopOrder, secondHopOrder] = quote.orders;
|
||||
const intermediateToken = firstHopOrder.makerToken;
|
||||
tokens.push(quote.takerToken, intermediateToken, quote.makerToken);
|
||||
const tokens = [quote.takerToken, intermediateToken, quote.makerToken];
|
||||
|
||||
for (const order of [firstHopOrder, secondHopOrder]) {
|
||||
switch (order.source) {
|
||||
case ERC20BridgeSource.UniswapV2:
|
||||
case ERC20BridgeSource.SushiSwap:
|
||||
wrappedMultiHopCalls.push({
|
||||
selector: this._multiplex.getSelector('_sellToUniswap'),
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.UniswapV2,
|
||||
data: multiplexUniswapEncoder.encode({
|
||||
tokens: (order.fillData as UniswapV2FillData).tokenAddressPath,
|
||||
isSushi: order.source === ERC20BridgeSource.SushiSwap,
|
||||
@@ -626,39 +643,49 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
});
|
||||
break;
|
||||
case ERC20BridgeSource.LiquidityProvider:
|
||||
wrappedMultiHopCalls.push({
|
||||
selector: this._multiplex.getSelector('_sellToLiquidityProvider'),
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.LiquidityProvider,
|
||||
data: multiplexPlpEncoder.encode({
|
||||
provider: (order.fillData as LiquidityProviderFillData).poolAddress,
|
||||
auxiliaryData: NULL_BYTES,
|
||||
}),
|
||||
});
|
||||
break;
|
||||
case ERC20BridgeSource.UniswapV3:
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.UniswapV3,
|
||||
data: (order.fillData as FinalUniswapV3FillData).uniswapPath,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// Note: we'll need to redeploy TransformERC20Feature before we can
|
||||
// use other sources
|
||||
// Should never happen because we check `isMultiplexMultiHopFillCompatible`
|
||||
// before calling this function.
|
||||
throw new Error(`Multiplex multi-hop unsupported source: ${order.source}`);
|
||||
}
|
||||
}
|
||||
if (opts.isToETH) {
|
||||
wrappedMultiHopCalls.push({
|
||||
selector: DUMMY_WETH_CONTRACT.getSelector('withdraw'),
|
||||
data: NULL_BYTES,
|
||||
});
|
||||
tokens.push(ETH_TOKEN_ADDRESS);
|
||||
}
|
||||
return this._exchangeProxy
|
||||
.multiHopFill(
|
||||
{
|
||||
if (opts.isFromETH) {
|
||||
return this._exchangeProxy
|
||||
.multiplexMultiHopSellEthForToken(tokens, subcalls, quote.worstCaseQuoteInfo.makerAmount)
|
||||
.getABIEncodedTransactionData();
|
||||
} else if (opts.isToETH) {
|
||||
return this._exchangeProxy
|
||||
.multiplexMultiHopSellTokenForEth(
|
||||
tokens,
|
||||
sellAmount: quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
calls: wrappedMultiHopCalls,
|
||||
},
|
||||
quote.worstCaseQuoteInfo.makerAmount,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
subcalls,
|
||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
quote.worstCaseQuoteInfo.makerAmount,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
} else {
|
||||
return this._exchangeProxy
|
||||
.multiplexMultiHopSellTokenForToken(
|
||||
tokens,
|
||||
subcalls,
|
||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
quote.worstCaseQuoteInfo.makerAmount,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,17 @@
|
||||
import { RfqOrder, SIGNATURE_ABI } from '@0x/protocol-utils';
|
||||
import { AbiEncoder } from '@0x/utils';
|
||||
|
||||
export enum MultiplexSubcall {
|
||||
Invalid,
|
||||
Rfq,
|
||||
Otc,
|
||||
UniswapV2,
|
||||
UniswapV3,
|
||||
LiquidityProvider,
|
||||
TransformERC20,
|
||||
BatchSell,
|
||||
MultiHopSell,
|
||||
}
|
||||
export const multiplexTransformERC20Encoder = AbiEncoder.create([
|
||||
{
|
||||
name: 'transformations',
|
||||
@@ -10,7 +21,6 @@ export const multiplexTransformERC20Encoder = AbiEncoder.create([
|
||||
{ name: 'data', type: 'bytes' },
|
||||
],
|
||||
},
|
||||
{ name: 'ethValue', type: 'uint256' },
|
||||
]);
|
||||
export const multiplexRfqEncoder = AbiEncoder.create([
|
||||
{ name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI },
|
||||
|
@@ -32,10 +32,6 @@ export function isMultiplexBatchFillCompatible(quote: SwapQuote, opts: ExchangeP
|
||||
if (quote.isTwoHop) {
|
||||
return false;
|
||||
}
|
||||
// batchFill does not support WETH wrapping/unwrapping at the moment
|
||||
if (opts.isFromETH || opts.isToETH) {
|
||||
return false;
|
||||
}
|
||||
if (quote.orders.map(o => o.type).includes(FillQuoteTransformerOrderType.Limit)) {
|
||||
return false;
|
||||
}
|
||||
@@ -49,6 +45,7 @@ const MULTIPLEX_MULTIHOP_FILL_SOURCES = [
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@@ -75,6 +75,7 @@ export class SwapQuoter {
|
||||
private readonly _marketOperationUtils: MarketOperationUtils;
|
||||
private readonly _rfqtOptions?: SwapQuoterRfqOpts;
|
||||
private readonly _quoteRequestorHttpClient: AxiosInstance;
|
||||
private readonly _integratorIdsSet: Set<string>;
|
||||
|
||||
/**
|
||||
* Instantiates a new SwapQuoter instance
|
||||
@@ -164,6 +165,9 @@ export class SwapQuoter {
|
||||
httpsAgent: new HttpsAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }),
|
||||
...(rfqt ? rfqt.axiosInstanceOpts : {}),
|
||||
});
|
||||
|
||||
const integratorIds = this._rfqtOptions?.integratorsWhitelist.map(integrator => integrator.integratorId) || [];
|
||||
this._integratorIdsSet = new Set(integratorIds);
|
||||
}
|
||||
|
||||
public async getBatchMarketBuySwapQuoteAsync(
|
||||
@@ -359,6 +363,7 @@ export class SwapQuoter {
|
||||
const cloneOpts = _.omit(opts, 'gasPrice') as GetMarketOrdersOpts;
|
||||
const calcOpts: GetMarketOrdersOpts = {
|
||||
...cloneOpts,
|
||||
gasPrice,
|
||||
feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData: FillData) =>
|
||||
gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)),
|
||||
),
|
||||
@@ -414,12 +419,11 @@ export class SwapQuoter {
|
||||
return isOpenOrder && !willOrderExpire && isFeeTypeAllowed;
|
||||
}; // tslint:disable-line:semicolon
|
||||
|
||||
private _isApiKeyWhitelisted(apiKey: string | undefined): boolean {
|
||||
if (!apiKey) {
|
||||
private _isIntegratorIdWhitelisted(integratorId: string | undefined): boolean {
|
||||
if (!integratorId) {
|
||||
return false;
|
||||
}
|
||||
const whitelistedApiKeys = this._rfqtOptions ? this._rfqtOptions.takerApiKeyWhitelist : [];
|
||||
return whitelistedApiKeys.includes(apiKey);
|
||||
return this._integratorIdsSet.has(integratorId);
|
||||
}
|
||||
|
||||
private _isTxOriginBlacklisted(txOrigin: string | undefined): boolean {
|
||||
@@ -438,19 +442,19 @@ export class SwapQuoter {
|
||||
return rfqt;
|
||||
}
|
||||
// tslint:disable-next-line: boolean-naming
|
||||
const { apiKey, nativeExclusivelyRFQ, intentOnFilling, txOrigin } = rfqt;
|
||||
const { integrator, nativeExclusivelyRFQ, intentOnFilling, txOrigin } = rfqt;
|
||||
// If RFQ-T is enabled and `nativeExclusivelyRFQ` is set, then `ERC20BridgeSource.Native` should
|
||||
// never be excluded.
|
||||
if (nativeExclusivelyRFQ === true && !sourceFilters.isAllowed(ERC20BridgeSource.Native)) {
|
||||
throw new Error('Native liquidity cannot be excluded if "rfqt.nativeExclusivelyRFQ" is set');
|
||||
}
|
||||
|
||||
// If an API key was provided, but the key is not whitelisted, raise a warning and disable RFQ
|
||||
if (!this._isApiKeyWhitelisted(apiKey)) {
|
||||
// If an integrator ID was provided, but the ID is not whitelisted, raise a warning and disable RFQ
|
||||
if (!this._isIntegratorIdWhitelisted(integrator.integratorId)) {
|
||||
if (this._rfqtOptions && this._rfqtOptions.warningLogger) {
|
||||
this._rfqtOptions.warningLogger(
|
||||
{
|
||||
apiKey,
|
||||
...integrator,
|
||||
},
|
||||
'Attempt at using an RFQ API key that is not whitelisted. Disabling RFQ for the request lifetime.',
|
||||
);
|
||||
@@ -474,7 +478,7 @@ export class SwapQuoter {
|
||||
// Otherwise check other RFQ options
|
||||
if (
|
||||
intentOnFilling && // The requestor is asking for a firm quote
|
||||
this._isApiKeyWhitelisted(apiKey) && // A valid API key was provided
|
||||
this._isIntegratorIdWhitelisted(integrator.integratorId) && // A valid API key was provided
|
||||
sourceFilters.isAllowed(ERC20BridgeSource.Native) // Native liquidity is not excluded
|
||||
) {
|
||||
if (!txOrigin || txOrigin === constants.NULL_ADDRESS) {
|
||||
@@ -501,6 +505,7 @@ function createSwapQuote(
|
||||
const {
|
||||
optimizedOrders,
|
||||
quoteReport,
|
||||
extendedQuoteReportSources,
|
||||
sourceFlags,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
@@ -528,6 +533,7 @@ function createSwapQuote(
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
quoteReport,
|
||||
extendedQuoteReportSources,
|
||||
isTwoHop,
|
||||
priceComparisonsReport,
|
||||
};
|
||||
|
@@ -19,7 +19,8 @@ import {
|
||||
OptimizedMarketOrder,
|
||||
TokenAdjacencyGraph,
|
||||
} from './utils/market_operation_utils/types';
|
||||
import { PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
|
||||
export { SamplerMetrics } from './utils/market_operation_utils/types';
|
||||
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
|
||||
import { MetricsProxy } from './utils/quote_requestor';
|
||||
|
||||
/**
|
||||
@@ -171,6 +172,7 @@ export interface SwapQuoteBase {
|
||||
worstCaseQuoteInfo: SwapQuoteInfo;
|
||||
sourceBreakdown: SwapQuoteOrdersBreakdown;
|
||||
quoteReport?: QuoteReport;
|
||||
extendedQuoteReportSources?: ExtendedQuoteReportSources;
|
||||
priceComparisonsReport?: PriceComparisonsReport;
|
||||
isTwoHop: boolean;
|
||||
makerTokenDecimals: number;
|
||||
@@ -243,7 +245,7 @@ export interface RfqmRequestOptions extends RfqRequestOpts {
|
||||
export interface RfqRequestOpts {
|
||||
takerAddress: string;
|
||||
txOrigin: string;
|
||||
apiKey: string;
|
||||
integrator: Integrator;
|
||||
intentOnFilling: boolean;
|
||||
isIndicative?: boolean;
|
||||
makerEndpointMaxResponseTimeMs?: number;
|
||||
@@ -256,7 +258,7 @@ export interface RfqRequestOpts {
|
||||
/**
|
||||
* gasPrice: gas price to determine protocolFee amount, default to ethGasStation fast amount
|
||||
*/
|
||||
export interface SwapQuoteRequestOpts extends GetMarketOrdersOpts {
|
||||
export interface SwapQuoteRequestOpts extends Omit<GetMarketOrdersOpts, 'gasPrice'> {
|
||||
gasPrice?: BigNumber;
|
||||
rfqt?: RfqRequestOpts;
|
||||
}
|
||||
@@ -293,8 +295,14 @@ export interface RfqFirmQuoteValidator {
|
||||
getRfqtTakerFillableAmountsAsync(quotes: RfqOrder[]): Promise<BigNumber[]>;
|
||||
}
|
||||
|
||||
export interface Integrator {
|
||||
integratorId: string;
|
||||
label: string;
|
||||
whitelistIntegratorUrls?: string[];
|
||||
}
|
||||
|
||||
export interface SwapQuoterRfqOpts {
|
||||
takerApiKeyWhitelist: string[];
|
||||
integratorsWhitelist: Integrator[];
|
||||
makerAssetOfferings: RfqMakerAssetOfferings;
|
||||
txOriginBlacklist: Set<string>;
|
||||
altRfqCreds?: {
|
||||
|
@@ -223,7 +223,17 @@ export async function returnQuoteFromAltMMAsync<ResponseT>(
|
||||
cancelToken,
|
||||
})
|
||||
.catch(err => {
|
||||
warningLogger(err, `Alt RFQ MM request failed`);
|
||||
if (err.response) {
|
||||
// request was made and market maker responded
|
||||
warningLogger(
|
||||
{ data: err.response.data, status: err.response.status, headers: err.response.headers },
|
||||
`Alt RFQ MM request failed`,
|
||||
);
|
||||
} else if (err.request) {
|
||||
warningLogger({}, 'Alt RFQ MM no response received');
|
||||
} else {
|
||||
warningLogger({ err: err.message }, 'Failed to construct Alt RFQ MM request');
|
||||
}
|
||||
throw new Error(`Alt RFQ MM request failed`);
|
||||
});
|
||||
|
||||
|
@@ -0,0 +1,106 @@
|
||||
import { logUtils } from '@0x/utils';
|
||||
import { gql, request } from 'graphql-request';
|
||||
|
||||
import { constants } from '../../constants';
|
||||
|
||||
const RESERVES_GQL_QUERY = gql`
|
||||
{
|
||||
reserves(
|
||||
first: 300
|
||||
where: { isActive: true, isFrozen: false }
|
||||
orderBy: totalLiquidity
|
||||
orderDirection: desc
|
||||
) {
|
||||
id
|
||||
underlyingAsset
|
||||
aToken {
|
||||
id
|
||||
}
|
||||
pool {
|
||||
id
|
||||
lendingPool
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface AaveReserve {
|
||||
id: string;
|
||||
underlyingAsset: string;
|
||||
aToken: {
|
||||
id: string;
|
||||
};
|
||||
pool: {
|
||||
id: string;
|
||||
lendingPool: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Cache {
|
||||
[key: string]: AaveReserve[];
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const RESERVES_REFRESH_INTERVAL_MS = 30 * constants.ONE_MINUTE_MS;
|
||||
|
||||
/**
|
||||
* Fetches Aave V2 reserve information from the official subgraph(s).
|
||||
* The reserve information is updated every 30 minutes and cached
|
||||
* so that it can be accessed with the underlying token's address
|
||||
*/
|
||||
export class AaveV2ReservesCache {
|
||||
private _cache: Cache = {};
|
||||
constructor(private readonly _subgraphUrl: string) {
|
||||
const resfreshReserves = async () => this.fetchAndUpdateReservesAsync();
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
resfreshReserves();
|
||||
setInterval(resfreshReserves, RESERVES_REFRESH_INTERVAL_MS);
|
||||
}
|
||||
/**
|
||||
* Fetches Aave V2 reserves from the subgraph and updates the cache
|
||||
*/
|
||||
public async fetchAndUpdateReservesAsync(): Promise<void> {
|
||||
try {
|
||||
const { reserves } = await request<{ reserves: AaveReserve[] }>(this._subgraphUrl, RESERVES_GQL_QUERY);
|
||||
const newCache = reserves.reduce<Cache>((memo, reserve) => {
|
||||
const underlyingAsset = reserve.underlyingAsset.toLowerCase();
|
||||
if (!memo[underlyingAsset]) {
|
||||
memo[underlyingAsset] = [];
|
||||
}
|
||||
|
||||
memo[underlyingAsset].push(reserve);
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
this._cache = newCache;
|
||||
} catch (err) {
|
||||
logUtils.warn(`Failed to update Aave V2 reserves cache: ${err.message}`);
|
||||
// Empty cache just to be safe
|
||||
this._cache = {};
|
||||
}
|
||||
}
|
||||
public get(takerToken: string, makerToken: string): AaveReserve | undefined {
|
||||
// Deposit takerToken into reserve
|
||||
if (this._cache[takerToken.toLowerCase()]) {
|
||||
const matchingReserve = this._cache[takerToken.toLowerCase()].find(
|
||||
r => r.aToken.id === makerToken.toLowerCase(),
|
||||
);
|
||||
if (matchingReserve) {
|
||||
return matchingReserve;
|
||||
}
|
||||
}
|
||||
|
||||
// Withdraw makerToken from reserve
|
||||
if (this._cache[makerToken.toLowerCase()]) {
|
||||
const matchingReserve = this._cache[makerToken.toLowerCase()].find(
|
||||
r => r.aToken.id === takerToken.toLowerCase(),
|
||||
);
|
||||
if (matchingReserve) {
|
||||
return matchingReserve;
|
||||
}
|
||||
}
|
||||
|
||||
// No match
|
||||
return undefined;
|
||||
}
|
||||
}
|
@@ -11,8 +11,12 @@ import {
|
||||
COMETHSWAP_ROUTER_BY_CHAIN_ID,
|
||||
COMPONENT_POOLS_BY_CHAIN_ID,
|
||||
CRYPTO_COM_ROUTER_BY_CHAIN_ID,
|
||||
CURVE_AVALANCHE_INFOS,
|
||||
CURVE_FANTOM_INFOS,
|
||||
CURVE_MAINNET_INFOS,
|
||||
CURVE_POLYGON_INFOS,
|
||||
CURVE_V2_AVALANCHE_INFOS,
|
||||
CURVE_V2_FANTOM_INFOS,
|
||||
CURVE_V2_MAINNET_INFOS,
|
||||
CURVE_V2_POLYGON_INFOS,
|
||||
DFYN_ROUTER_BY_CHAIN_ID,
|
||||
@@ -26,11 +30,13 @@ import {
|
||||
KYBER_BRIDGED_LIQUIDITY_PREFIX,
|
||||
MAX_DODOV2_POOLS_QUERIED,
|
||||
MAX_KYBER_RESERVES_QUERIED,
|
||||
MORPHEUSSWAP_ROUTER_BY_CHAIN_ID,
|
||||
MSTABLE_POOLS_BY_CHAIN_ID,
|
||||
NERVE_BSC_INFOS,
|
||||
NULL_ADDRESS,
|
||||
PANCAKESWAP_ROUTER_BY_CHAIN_ID,
|
||||
PANCAKESWAPV2_ROUTER_BY_CHAIN_ID,
|
||||
PANGOLIN_ROUTER_BY_CHAIN_ID,
|
||||
POLYDEX_ROUTER_BY_CHAIN_ID,
|
||||
QUICKSWAP_ROUTER_BY_CHAIN_ID,
|
||||
SADDLE_MAINNET_INFOS,
|
||||
@@ -39,8 +45,12 @@ import {
|
||||
SMOOTHY_BSC_INFOS,
|
||||
SMOOTHY_MAINNET_INFOS,
|
||||
SNOWSWAP_MAINNET_INFOS,
|
||||
SPIRITSWAP_ROUTER_BY_CHAIN_ID,
|
||||
SPOOKYSWAP_ROUTER_BY_CHAIN_ID,
|
||||
SUSHISWAP_ROUTER_BY_CHAIN_ID,
|
||||
SWERVE_MAINNET_INFOS,
|
||||
TRADER_JOE_ROUTER_BY_CHAIN_ID,
|
||||
UBESWAP_ROUTER_BY_CHAIN_ID,
|
||||
UNISWAPV2_ROUTER_BY_CHAIN_ID,
|
||||
WAULTSWAP_ROUTER_BY_CHAIN_ID,
|
||||
XSIGMA_MAINNET_INFOS,
|
||||
@@ -131,6 +141,24 @@ export function getCurveInfosForPair(chainId: ChainId, takerToken: string, maker
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
case ChainId.Fantom:
|
||||
return Object.values(CURVE_FANTOM_INFOS).filter(c =>
|
||||
[makerToken, takerToken].every(
|
||||
t =>
|
||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
||||
(c.tokens.includes(t) &&
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
case ChainId.Avalanche:
|
||||
return Object.values(CURVE_AVALANCHE_INFOS).filter(c =>
|
||||
[makerToken, takerToken].every(
|
||||
t =>
|
||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
||||
(c.tokens.includes(t) &&
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
@@ -157,6 +185,24 @@ export function getCurveV2InfosForPair(chainId: ChainId, takerToken: string, mak
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
case ChainId.Fantom:
|
||||
return Object.values(CURVE_V2_FANTOM_INFOS).filter(c =>
|
||||
[makerToken, takerToken].every(
|
||||
t =>
|
||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
||||
(c.tokens.includes(t) &&
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
case ChainId.Avalanche:
|
||||
return Object.values(CURVE_V2_AVALANCHE_INFOS).filter(c =>
|
||||
[makerToken, takerToken].every(
|
||||
t =>
|
||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
||||
(c.tokens.includes(t) &&
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
@@ -439,7 +485,13 @@ export function uniswapV2LikeRouterAddress(
|
||||
| ERC20BridgeSource.WaultSwap
|
||||
| ERC20BridgeSource.Polydex
|
||||
| ERC20BridgeSource.ShibaSwap
|
||||
| ERC20BridgeSource.JetSwap,
|
||||
| ERC20BridgeSource.JetSwap
|
||||
| ERC20BridgeSource.TraderJoe
|
||||
| ERC20BridgeSource.Pangolin
|
||||
| ERC20BridgeSource.UbeSwap
|
||||
| ERC20BridgeSource.MorpheusSwap
|
||||
| ERC20BridgeSource.SpookySwap
|
||||
| ERC20BridgeSource.SpiritSwap,
|
||||
): string {
|
||||
switch (source) {
|
||||
case ERC20BridgeSource.UniswapV2:
|
||||
@@ -476,6 +528,18 @@ export function uniswapV2LikeRouterAddress(
|
||||
return SHIBASWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.JetSwap:
|
||||
return JETSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
return PANGOLIN_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
return TRADER_JOE_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
return UBESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
return MORPHEUSSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.SpookySwap:
|
||||
return SPOOKYSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.SpiritSwap:
|
||||
return SPIRITSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
default:
|
||||
throw new Error(`Unknown UniswapV2 like source ${source}`);
|
||||
}
|
||||
|
@@ -0,0 +1,78 @@
|
||||
import { logUtils } from '@0x/utils';
|
||||
import axios from 'axios';
|
||||
|
||||
import { constants } from '../../constants';
|
||||
|
||||
export interface CToken {
|
||||
tokenAddress: string;
|
||||
underlyingAddress: string;
|
||||
}
|
||||
|
||||
interface CTokenApiResponse {
|
||||
cToken: Array<{
|
||||
token_address: string;
|
||||
underlying_address: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface Cache {
|
||||
[key: string]: CToken;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const CTOKEN_REFRESH_INTERVAL_MS = 30 * constants.ONE_MINUTE_MS;
|
||||
|
||||
/**
|
||||
* Fetches a list of CTokens from Compound's official API.
|
||||
* The token information is updated every 30 minutes and cached
|
||||
* so that it can be accessed with the underlying token's address.
|
||||
*/
|
||||
export class CompoundCTokenCache {
|
||||
private _cache: Cache = {};
|
||||
constructor(private readonly _apiUrl: string, private readonly _wethAddress: string) {
|
||||
const refreshCTokenCache = async () => this.fetchAndUpdateCTokensAsync();
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
refreshCTokenCache();
|
||||
setInterval(refreshCTokenCache, CTOKEN_REFRESH_INTERVAL_MS);
|
||||
}
|
||||
|
||||
public async fetchAndUpdateCTokensAsync(): Promise<void> {
|
||||
try {
|
||||
const { data } = await axios.get<CTokenApiResponse>(`${this._apiUrl}/ctoken`);
|
||||
const newCache = data?.cToken.reduce<Cache>((memo, cToken) => {
|
||||
// NOTE: Re-map cETH with null underlying token address to WETH address (we only handle WETH internally)
|
||||
const underlyingAddressClean = cToken.underlying_address
|
||||
? cToken.underlying_address.toLowerCase()
|
||||
: this._wethAddress;
|
||||
|
||||
const tokenData: CToken = {
|
||||
tokenAddress: cToken.token_address.toLowerCase(),
|
||||
underlyingAddress: underlyingAddressClean,
|
||||
};
|
||||
memo[underlyingAddressClean] = tokenData;
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
this._cache = newCache;
|
||||
} catch (err) {
|
||||
logUtils.warn(`Failed to update Compound cToken cache: ${err.message}`);
|
||||
// NOTE: Safe to keep already cached data as tokens should only be added to the list
|
||||
}
|
||||
}
|
||||
public get(takerToken: string, makerToken: string): CToken | undefined {
|
||||
// mint cToken
|
||||
let cToken = this._cache[takerToken.toLowerCase()];
|
||||
if (cToken && makerToken.toLowerCase() === cToken.tokenAddress.toLowerCase()) {
|
||||
return cToken;
|
||||
}
|
||||
|
||||
// redeem cToken
|
||||
cToken = this._cache[makerToken.toLowerCase()];
|
||||
if (cToken && takerToken.toLowerCase() === cToken.tokenAddress.toLowerCase()) {
|
||||
return cToken;
|
||||
}
|
||||
|
||||
// No match
|
||||
return undefined;
|
||||
}
|
||||
}
|
@@ -7,7 +7,9 @@ import { TokenAdjacencyGraphBuilder } from '../token_adjacency_graph_builder';
|
||||
|
||||
import { SourceFilters } from './source_filters';
|
||||
import {
|
||||
AaveV2FillData,
|
||||
BancorFillData,
|
||||
CompoundFillData,
|
||||
CurveFillData,
|
||||
CurveFunctionSelectors,
|
||||
CurveInfo,
|
||||
@@ -56,6 +58,10 @@ function valueByChainId<T>(rest: Partial<{ [key in ChainId]: T }>, defaultValue:
|
||||
[ChainId.BSC]: defaultValue,
|
||||
[ChainId.Polygon]: defaultValue,
|
||||
[ChainId.PolygonMumbai]: defaultValue,
|
||||
[ChainId.Avalanche]: defaultValue,
|
||||
[ChainId.Fantom]: defaultValue,
|
||||
[ChainId.Celo]: defaultValue,
|
||||
[ChainId.Optimism]: defaultValue,
|
||||
...(rest || {}),
|
||||
};
|
||||
}
|
||||
@@ -98,7 +104,9 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.ShibaSwap,
|
||||
ERC20BridgeSource.Clipper,
|
||||
// TODO: enable after FQT has been redeployed on Ethereum mainnet
|
||||
// ERC20BridgeSource.AaveV2,
|
||||
// ERC20BridgeSource.Compound,
|
||||
]),
|
||||
[ChainId.Ropsten]: new SourceFilters([
|
||||
ERC20BridgeSource.Kyber,
|
||||
@@ -135,6 +143,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.FirebirdOneSwap,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.ACryptos,
|
||||
ERC20BridgeSource.KyberDmm,
|
||||
]),
|
||||
[ChainId.Polygon]: new SourceFilters([
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
@@ -156,7 +165,36 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.MultiHop,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.IronSwap,
|
||||
ERC20BridgeSource.AaveV2,
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
]),
|
||||
[ChainId.Avalanche]: new SourceFilters([
|
||||
ERC20BridgeSource.MultiHop,
|
||||
ERC20BridgeSource.Pangolin,
|
||||
ERC20BridgeSource.TraderJoe,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.KyberDmm,
|
||||
ERC20BridgeSource.AaveV2,
|
||||
]),
|
||||
[ChainId.Fantom]: new SourceFilters([
|
||||
ERC20BridgeSource.MultiHop,
|
||||
ERC20BridgeSource.Beethovenx,
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.MorpheusSwap,
|
||||
ERC20BridgeSource.SpiritSwap,
|
||||
ERC20BridgeSource.SpookySwap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
]),
|
||||
[ChainId.Celo]: new SourceFilters([
|
||||
ERC20BridgeSource.UbeSwap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.MultiHop,
|
||||
]),
|
||||
[ChainId.Optimism]: new SourceFilters([ERC20BridgeSource.UniswapV3]),
|
||||
},
|
||||
new SourceFilters([]),
|
||||
);
|
||||
@@ -199,7 +237,9 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.ShibaSwap,
|
||||
ERC20BridgeSource.Clipper,
|
||||
// TODO: enable after FQT has been redeployed on Ethereum mainnet
|
||||
// ERC20BridgeSource.AaveV2,
|
||||
// ERC20BridgeSource.Compound,
|
||||
]),
|
||||
[ChainId.Ropsten]: new SourceFilters([
|
||||
ERC20BridgeSource.Kyber,
|
||||
@@ -236,6 +276,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.FirebirdOneSwap,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.ACryptos,
|
||||
ERC20BridgeSource.KyberDmm,
|
||||
]),
|
||||
[ChainId.Polygon]: new SourceFilters([
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
@@ -257,7 +298,36 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.MultiHop,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.IronSwap,
|
||||
ERC20BridgeSource.AaveV2,
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
]),
|
||||
[ChainId.Avalanche]: new SourceFilters([
|
||||
ERC20BridgeSource.MultiHop,
|
||||
ERC20BridgeSource.Pangolin,
|
||||
ERC20BridgeSource.TraderJoe,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.KyberDmm,
|
||||
ERC20BridgeSource.AaveV2,
|
||||
]),
|
||||
[ChainId.Fantom]: new SourceFilters([
|
||||
ERC20BridgeSource.MultiHop,
|
||||
ERC20BridgeSource.Beethovenx,
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.MorpheusSwap,
|
||||
ERC20BridgeSource.SpiritSwap,
|
||||
ERC20BridgeSource.SpookySwap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
]),
|
||||
[ChainId.Celo]: new SourceFilters([
|
||||
ERC20BridgeSource.UbeSwap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.MultiHop,
|
||||
]),
|
||||
[ChainId.Optimism]: new SourceFilters([ERC20BridgeSource.UniswapV3]),
|
||||
},
|
||||
new SourceFilters([]),
|
||||
);
|
||||
@@ -265,7 +335,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
/**
|
||||
* 0x Protocol Fee Multiplier
|
||||
*/
|
||||
export const PROTOCOL_FEE_MULTIPLIER = new BigNumber(70000);
|
||||
export const PROTOCOL_FEE_MULTIPLIER = new BigNumber(0);
|
||||
|
||||
/**
|
||||
* Sources to poll for ETH fee price estimates.
|
||||
@@ -275,7 +345,11 @@ export const FEE_QUOTE_SOURCES_BY_CHAIN_ID = valueByChainId<ERC20BridgeSource[]>
|
||||
[ChainId.Mainnet]: [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap, ERC20BridgeSource.UniswapV3],
|
||||
[ChainId.BSC]: [ERC20BridgeSource.PancakeSwap, ERC20BridgeSource.Mooniswap, ERC20BridgeSource.SushiSwap],
|
||||
[ChainId.Ropsten]: [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap],
|
||||
[ChainId.Polygon]: [ERC20BridgeSource.QuickSwap, ERC20BridgeSource.SushiSwap],
|
||||
[ChainId.Polygon]: [ERC20BridgeSource.QuickSwap, ERC20BridgeSource.SushiSwap, ERC20BridgeSource.UniswapV3],
|
||||
[ChainId.Avalanche]: [ERC20BridgeSource.Pangolin, ERC20BridgeSource.TraderJoe, ERC20BridgeSource.SushiSwap],
|
||||
[ChainId.Fantom]: [ERC20BridgeSource.SpiritSwap, ERC20BridgeSource.SpookySwap, ERC20BridgeSource.SushiSwap],
|
||||
[ChainId.Celo]: [ERC20BridgeSource.UbeSwap, ERC20BridgeSource.SushiSwap],
|
||||
[ChainId.Optimism]: [ERC20BridgeSource.UniswapV3],
|
||||
},
|
||||
[],
|
||||
);
|
||||
@@ -372,9 +446,16 @@ export const MAINNET_TOKENS = {
|
||||
// StableSwap "open pools" (crv.finance)
|
||||
STABLEx: '0xcd91538b91b4ba7797d39a2f66e63810b50a33d0',
|
||||
alUSD: '0xbc6da0fe9ad5f3b0d58160288917aa56653660e9',
|
||||
// Frax ecosystem
|
||||
FRAX: '0x853d955acef822db058eb8505911ed77f175b99e',
|
||||
FXS: '0x3432b6a60d23ca0dfca7761b7ab56459d9c964d0',
|
||||
OHM: '0x383518188c0c6d7730d91b2c03a03c837814a899',
|
||||
//
|
||||
LUSD: '0x5f98805a4e8be255a32880fdec7f6728c6568ba0',
|
||||
// Fei Ecosystem
|
||||
FEI: '0x956f47f50a910163d8bf957cf5846d573e7f87ca',
|
||||
TRIBE: '0xc7283b66eb1eb5fb86327f08e1b5816b0720212b',
|
||||
//
|
||||
DSU: '0x605d26fbd5be761089281d5cec2ce86eea667109',
|
||||
ESS: '0x24ae124c4cc33d6791f8e8b63520ed7107ac8b3e',
|
||||
cvxCRV: '0x62b9c7356a2dc64a1969e19c23e4f579f9810aa7',
|
||||
@@ -416,6 +497,56 @@ export const POLYGON_TOKENS = {
|
||||
WEXPOLY: '0x4c4bf319237d98a30a929a96112effa8da3510eb',
|
||||
};
|
||||
|
||||
export const AVALANCHE_TOKENS = {
|
||||
WAVAX: '0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7',
|
||||
WETH: '0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab',
|
||||
WBTC: '0x50b7545627a5162f82a992c33b87adc75187b218',
|
||||
DAI: '0xd586e7f844cea2f87f50152665bcbc2c279d8d70',
|
||||
USDC: '0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664',
|
||||
USDT: '0xc7198437980c041c805a1edcba50c1ce5db95118',
|
||||
aDAI: '0x47afa96cdc9fab46904a55a6ad4bf6660b53c38a',
|
||||
aUSDC: '0x46a51127c3ce23fb7ab1de06226147f446e4a857',
|
||||
aUSDT: '0x532e6537fea298397212f09a61e03311686f548e',
|
||||
};
|
||||
|
||||
export const CELO_TOKENS = {
|
||||
WCELO: '0x471ece3750da237f93b8e339c536989b8978a438',
|
||||
// Some of these tokens are Optics bridge? tokens which
|
||||
// had an issue and migrated from v1 to v2
|
||||
WETHv1: '0xe919f65739c26a42616b7b8eedc6b5524d1e3ac4',
|
||||
WETH: '0x122013fd7df1c6f636a5bb8f03108e876548b455',
|
||||
WBTC: '0xbaab46e28388d2779e6e31fd00cf0e5ad95e327b',
|
||||
cUSD: '0x765de816845861e75a25fca122bb6898b8b1282a',
|
||||
// ??
|
||||
WBTCv1: '0xd629eb00deced2a080b7ec630ef6ac117e614f1b',
|
||||
cETH: '0x2def4285787d58a2f811af24755a8150622f4361',
|
||||
UBE: '0x00be915b9dcf56a3cbe739d9b9c202ca692409ec',
|
||||
// Moolah
|
||||
mCELO: '0x7d00cd74ff385c955ea3d79e47bf06bd7386387d',
|
||||
mCUSD: '0x918146359264c492bd6934071c6bd31c854edbc3',
|
||||
mCEUR: '0xe273ad7ee11dcfaa87383ad5977ee1504ac07568',
|
||||
amCUSD: '0x64defa3544c695db8c535d289d843a189aa26b98',
|
||||
MOO: '0x17700282592d6917f6a73d0bf8accf4d578c131e',
|
||||
};
|
||||
|
||||
export const FANTOM_TOKENS = {
|
||||
WFTM: '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83',
|
||||
WETH: '0x74b23882a30290451a17c44f4f05243b6b58c76d',
|
||||
USDC: '0x04068da6c83afcfa0e13ba15a6696662335d5b75',
|
||||
DAI: '0x8d11ec38a3eb5e956b052f67da8bdc9bef8abf3e',
|
||||
fUSDT: '0x049d68029688eabf473097a2fc38ef61633a3c7a',
|
||||
WBTC: '0x321162cd933e2be498cd2267a90534a804051b11',
|
||||
renBTC: '0xdbf31df14b66535af65aac99c32e9ea844e14501',
|
||||
};
|
||||
|
||||
export const OPTIMISM_TOKENS = {
|
||||
WETH: '0x4200000000000000000000000000000000000006',
|
||||
USDC: '0x7f5c764cbc14f9669b88837ca1490cca17c31607',
|
||||
USDT: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58',
|
||||
DAI: '0xda10009cbd5d07dd0cecc66161fc93d7c9000da1',
|
||||
WBTC: '0x68f180fcce6836688e9084f035309e29bf0a2095',
|
||||
};
|
||||
|
||||
export const CURVE_POOLS = {
|
||||
compound: '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56', // 0.Compound
|
||||
// 1.USDT is dead
|
||||
@@ -460,6 +591,7 @@ export const CURVE_POOLS = {
|
||||
cvxcrv: '0x9d0464996170c6b9e75eed71c68b99ddedf279e8',
|
||||
mim: '0x5a6a4d54456819380173272a5e8e9b9904bdf41b',
|
||||
eurt: '0xfd5db7463a3ab53fd211b4af195c5bccc1a03890',
|
||||
ethcrv: '0x8301ae4fc9c624d1d396cbdaa1ed877821d7c511',
|
||||
};
|
||||
|
||||
export const CURVE_V2_POOLS = {
|
||||
@@ -477,6 +609,24 @@ export const CURVE_V2_POLYGON_POOLS = {
|
||||
atricrypto3: '0x1d8b86e3d88cdb2d34688e87e72f388cb541b7c8',
|
||||
};
|
||||
|
||||
export const CURVE_AVALANCHE_POOLS = {
|
||||
aave: '0x7f90122bf0700f9e7e1f688fe926940e8839f353',
|
||||
};
|
||||
|
||||
export const CURVE_V2_AVALANCHE_POOLS = {
|
||||
atricrypto: '0x58e57ca18b7a47112b877e31929798cd3d703b0f',
|
||||
};
|
||||
|
||||
export const CURVE_FANTOM_POOLS = {
|
||||
fUSDT: '0x92d5ebf3593a92888c25c0abef126583d4b5312e',
|
||||
twoPool: '0x27e611fd27b276acbd5ffd632e5eaebec9761e40',
|
||||
ren: '0x3ef6a01a0f81d6046290f3e2a8c5b843e738e604',
|
||||
};
|
||||
|
||||
export const CURVE_V2_FANTOM_POOLS = {
|
||||
tricrypto: '0x3a1659ddcf2339be3aea159ca010979fb49155ff',
|
||||
};
|
||||
|
||||
export const SWERVE_POOLS = {
|
||||
y: '0x329239599afb305da0a2ec69c58f8a6697f9f88d',
|
||||
};
|
||||
@@ -569,6 +719,16 @@ export const DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID = valueByChainId<string[]>(
|
||||
POLYGON_TOKENS.USDT,
|
||||
POLYGON_TOKENS.WBTC,
|
||||
],
|
||||
[ChainId.Avalanche]: [
|
||||
AVALANCHE_TOKENS.WAVAX,
|
||||
AVALANCHE_TOKENS.WETH,
|
||||
AVALANCHE_TOKENS.DAI,
|
||||
AVALANCHE_TOKENS.USDT,
|
||||
AVALANCHE_TOKENS.USDC,
|
||||
],
|
||||
[ChainId.Fantom]: [FANTOM_TOKENS.WFTM, FANTOM_TOKENS.WETH, FANTOM_TOKENS.DAI, FANTOM_TOKENS.USDC],
|
||||
[ChainId.Celo]: [CELO_TOKENS.WCELO, CELO_TOKENS.mCUSD, CELO_TOKENS.WETH, CELO_TOKENS.amCUSD, CELO_TOKENS.WBTC],
|
||||
[ChainId.Optimism]: [OPTIMISM_TOKENS.WETH, OPTIMISM_TOKENS.DAI, OPTIMISM_TOKENS.USDC],
|
||||
},
|
||||
[],
|
||||
);
|
||||
@@ -585,6 +745,11 @@ export const DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID = valueByChainId<TokenAdj
|
||||
builder.add(MAINNET_TOKENS.MIR, MAINNET_TOKENS.UST);
|
||||
// Convex and Curve
|
||||
builder.add(MAINNET_TOKENS.cvxCRV, MAINNET_TOKENS.CRV).add(MAINNET_TOKENS.CRV, MAINNET_TOKENS.cvxCRV);
|
||||
// FEI TRIBE liquid in UniV2
|
||||
builder.add(MAINNET_TOKENS.FEI, MAINNET_TOKENS.TRIBE).add(MAINNET_TOKENS.TRIBE, MAINNET_TOKENS.FEI);
|
||||
// FRAX ecosystem
|
||||
builder.add(MAINNET_TOKENS.FRAX, MAINNET_TOKENS.FXS).add(MAINNET_TOKENS.FXS, MAINNET_TOKENS.FRAX);
|
||||
builder.add(MAINNET_TOKENS.FRAX, MAINNET_TOKENS.OHM).add(MAINNET_TOKENS.OHM, MAINNET_TOKENS.FRAX);
|
||||
})
|
||||
// Build
|
||||
.build(),
|
||||
@@ -594,6 +759,18 @@ export const DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID = valueByChainId<TokenAdj
|
||||
[ChainId.Polygon]: new TokenAdjacencyGraphBuilder({
|
||||
default: DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID[ChainId.Polygon],
|
||||
}).build(),
|
||||
[ChainId.Avalanche]: new TokenAdjacencyGraphBuilder({
|
||||
default: DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID[ChainId.Avalanche],
|
||||
}).build(),
|
||||
[ChainId.Fantom]: new TokenAdjacencyGraphBuilder({
|
||||
default: DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID[ChainId.Fantom],
|
||||
}).build(),
|
||||
[ChainId.Celo]: new TokenAdjacencyGraphBuilder({
|
||||
default: DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID[ChainId.Celo],
|
||||
}).build(),
|
||||
[ChainId.Optimism]: new TokenAdjacencyGraphBuilder({
|
||||
default: DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID[ChainId.Optimism],
|
||||
}).build(),
|
||||
},
|
||||
new TokenAdjacencyGraphBuilder({ default: [] }).build(),
|
||||
);
|
||||
@@ -607,6 +784,10 @@ export const NATIVE_FEE_TOKEN_BY_CHAIN_ID = valueByChainId<string>(
|
||||
[ChainId.Rinkeby]: getContractAddressesForChainOrThrow(ChainId.Rinkeby).etherToken,
|
||||
[ChainId.Kovan]: getContractAddressesForChainOrThrow(ChainId.Kovan).etherToken,
|
||||
[ChainId.Polygon]: getContractAddressesForChainOrThrow(ChainId.Polygon).etherToken,
|
||||
[ChainId.Avalanche]: getContractAddressesForChainOrThrow(ChainId.Avalanche).etherToken,
|
||||
[ChainId.Fantom]: getContractAddressesForChainOrThrow(ChainId.Fantom).etherToken,
|
||||
[ChainId.Celo]: getContractAddressesForChainOrThrow(ChainId.Celo).etherToken,
|
||||
[ChainId.Optimism]: getContractAddressesForChainOrThrow(ChainId.Optimism).etherToken,
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
@@ -881,6 +1062,17 @@ export const CURVE_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
||||
pool: CURVE_POOLS.cvxcrv,
|
||||
gasSchedule: 105e3,
|
||||
}),
|
||||
[CURVE_POOLS.ethcrv]: {
|
||||
...createCurveExchangePool({
|
||||
// This pool uses ETH
|
||||
tokens: [MAINNET_TOKENS.WETH, MAINNET_TOKENS.CRV],
|
||||
pool: CURVE_POOLS.ethcrv,
|
||||
gasSchedule: 350e3,
|
||||
}),
|
||||
// This pool has a custom get_dy and exchange selector with uint256
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_uint256,
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying_uint256,
|
||||
},
|
||||
};
|
||||
|
||||
export const CURVE_V2_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
||||
@@ -927,6 +1119,64 @@ export const CURVE_V2_POLYGON_INFOS: { [name: string]: CurveInfo } = {
|
||||
}),
|
||||
};
|
||||
|
||||
export const CURVE_AVALANCHE_INFOS: { [name: string]: CurveInfo } = {
|
||||
['aave_exchangeunderlying']: createCurveExchangeUnderlyingPool({
|
||||
tokens: [AVALANCHE_TOKENS.DAI, AVALANCHE_TOKENS.USDC, AVALANCHE_TOKENS.USDT],
|
||||
pool: CURVE_AVALANCHE_POOLS.aave,
|
||||
gasSchedule: 850e3,
|
||||
}),
|
||||
['aave_exchange']: createCurveExchangePool({
|
||||
tokens: [AVALANCHE_TOKENS.aDAI, AVALANCHE_TOKENS.aUSDC, AVALANCHE_TOKENS.aUSDT],
|
||||
pool: CURVE_AVALANCHE_POOLS.aave,
|
||||
gasSchedule: 150e3,
|
||||
}),
|
||||
};
|
||||
|
||||
export const CURVE_V2_AVALANCHE_INFOS: { [name: string]: CurveInfo } = {
|
||||
[CURVE_V2_AVALANCHE_POOLS.atricrypto]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying_v2,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying_v2,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
tokens: [
|
||||
AVALANCHE_TOKENS.DAI,
|
||||
AVALANCHE_TOKENS.USDC,
|
||||
AVALANCHE_TOKENS.USDT,
|
||||
AVALANCHE_TOKENS.WBTC,
|
||||
AVALANCHE_TOKENS.WETH,
|
||||
],
|
||||
metaTokens: undefined,
|
||||
poolAddress: CURVE_V2_AVALANCHE_POOLS.atricrypto,
|
||||
gasSchedule: 1300e3,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: modify gasSchedule
|
||||
export const CURVE_FANTOM_INFOS: { [name: string]: CurveInfo } = {
|
||||
[CURVE_FANTOM_POOLS.ren]: createCurveExchangePool({
|
||||
tokens: [FANTOM_TOKENS.WBTC, FANTOM_TOKENS.renBTC],
|
||||
pool: CURVE_FANTOM_POOLS.ren,
|
||||
gasSchedule: 171e3,
|
||||
}),
|
||||
[CURVE_FANTOM_POOLS.twoPool]: createCurveExchangePool({
|
||||
tokens: [FANTOM_TOKENS.DAI, FANTOM_TOKENS.USDC],
|
||||
pool: CURVE_FANTOM_POOLS.twoPool,
|
||||
gasSchedule: 176e3,
|
||||
}),
|
||||
[CURVE_FANTOM_POOLS.fUSDT]: createCurveExchangeUnderlyingPool({
|
||||
tokens: [FANTOM_TOKENS.fUSDT, FANTOM_TOKENS.DAI, FANTOM_TOKENS.USDC],
|
||||
pool: CURVE_FANTOM_POOLS.fUSDT,
|
||||
gasSchedule: 587e3,
|
||||
}),
|
||||
};
|
||||
|
||||
export const CURVE_V2_FANTOM_INFOS: { [name: string]: CurveInfo } = {
|
||||
[CURVE_V2_FANTOM_POOLS.tricrypto]: createCurveExchangeV2Pool({
|
||||
tokens: [FANTOM_TOKENS.fUSDT, FANTOM_TOKENS.WBTC, FANTOM_TOKENS.WETH],
|
||||
pool: CURVE_V2_FANTOM_POOLS.tricrypto,
|
||||
gasSchedule: 300e3,
|
||||
}),
|
||||
};
|
||||
|
||||
export const SWERVE_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
||||
[SWERVE_POOLS.y]: createCurveExchangePool({
|
||||
tokens: [MAINNET_TOKENS.DAI, MAINNET_TOKENS.USDC, MAINNET_TOKENS.USDT, MAINNET_TOKENS.TUSD],
|
||||
@@ -1219,6 +1469,9 @@ export const SUSHISWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
[ChainId.BSC]: '0x1b02da8cb0d097eb8d57a175b88c7d8b47997506',
|
||||
[ChainId.Ropsten]: '0x1b02da8cb0d097eb8d57a175b88c7d8b47997506',
|
||||
[ChainId.Polygon]: '0x1b02da8cb0d097eb8d57a175b88c7d8b47997506',
|
||||
[ChainId.Avalanche]: '0x1b02da8cb0d097eb8d57a175b88c7d8b47997506',
|
||||
[ChainId.Fantom]: '0x1b02da8cb0d097eb8d57a175b88c7d8b47997506',
|
||||
[ChainId.Celo]: '0x1421bde4b10e8dd459b3bcb598810b1337d56842',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
@@ -1288,6 +1541,9 @@ export const KYBER_DMM_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Mainnet]: '0x1c87257f5e8609940bc751a07bb085bb7f8cdbe6',
|
||||
[ChainId.Polygon]: '0x546c79662e028b661dfb4767664d0273184e4dd1',
|
||||
[ChainId.BSC]: '0x78df70615ffc8066cc0887917f2cd72092c86409',
|
||||
[ChainId.Avalanche]: '0x8efa5a9ad6d594cf76830267077b78ce0bc5a5f8',
|
||||
[ChainId.Fantom]: '0x5d5a5a0a465129848c2549669e12cdc2f8de039a',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
@@ -1437,6 +1693,13 @@ export const BALANCER_V2_VAULT_ADDRESS_BY_CHAIN = valueByChainId<string>(
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Fantom]: '0x20dd72ed959b6147912c2e529f0a0c651c33c9ce',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const LIDO_INFO_BY_CHAIN = valueByChainId<LidoInfo>(
|
||||
{
|
||||
[ChainId.Mainnet]: {
|
||||
@@ -1450,22 +1713,6 @@ export const LIDO_INFO_BY_CHAIN = valueByChainId<LidoInfo>(
|
||||
},
|
||||
);
|
||||
|
||||
export const CLIPPER_INFO_BY_CHAIN = valueByChainId(
|
||||
{
|
||||
[ChainId.Mainnet]: {
|
||||
poolAddress: '0xe82906b6b1b04f631d126c974af57a3a7b6a99d9',
|
||||
tokens: [
|
||||
MAINNET_TOKENS.WETH, // technically ETH but our sampler and mixin handle this
|
||||
MAINNET_TOKENS.WBTC,
|
||||
MAINNET_TOKENS.USDC,
|
||||
MAINNET_TOKENS.USDT,
|
||||
MAINNET_TOKENS.DAI,
|
||||
],
|
||||
},
|
||||
},
|
||||
{ poolAddress: NULL_ADDRESS, tokens: [] },
|
||||
);
|
||||
|
||||
export const BALANCER_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer';
|
||||
export const BALANCER_TOP_POOLS_FETCHED = 250;
|
||||
export const BALANCER_MAX_POOLS_FETCHED = 3;
|
||||
@@ -1477,6 +1724,13 @@ export const BALANCER_V2_SUBGRAPH_URL_BY_CHAIN = valueByChainId<string>(
|
||||
'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2',
|
||||
);
|
||||
|
||||
export const BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Fantom]: 'https://graph-node.beets-ftm-node.com/subgraphs/name/beethovenx',
|
||||
},
|
||||
'https://graph-node.beets-ftm-node.com/subgraphs/name/beethovenx',
|
||||
);
|
||||
|
||||
export const UNISWAPV3_CONFIG_BY_CHAIN_ID = valueByChainId(
|
||||
{
|
||||
[ChainId.Mainnet]: {
|
||||
@@ -1487,10 +1741,36 @@ export const UNISWAPV3_CONFIG_BY_CHAIN_ID = valueByChainId(
|
||||
quoter: '0x2f9e608fd881861b8916257b76613cb22ee0652c',
|
||||
router: '0x03782388516e94fcd4c18666303601a12aa729ea',
|
||||
},
|
||||
[ChainId.Polygon]: {
|
||||
quoter: '0xb27308f9f90d607463bb33ea1bebb41c27ce5ab6',
|
||||
router: '0xe592427a0aece92de3edee1f18e0157c05861564',
|
||||
},
|
||||
[ChainId.Optimism]: {
|
||||
quoter: '0xb27308f9f90d607463bb33ea1bebb41c27ce5ab6',
|
||||
router: '0xe592427a0aece92de3edee1f18e0157c05861564',
|
||||
},
|
||||
},
|
||||
{ quoter: NULL_ADDRESS, router: NULL_ADDRESS },
|
||||
);
|
||||
|
||||
export const AAVE_V2_SUBGRAPH_URL_BY_CHAIN_ID = valueByChainId(
|
||||
{
|
||||
// TODO: enable after FQT has been redeployed on Ethereum mainnet
|
||||
// [ChainId.Mainnet]: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v2',
|
||||
[ChainId.Polygon]: 'https://api.thegraph.com/subgraphs/name/aave/aave-v2-matic',
|
||||
[ChainId.Avalanche]: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v2-avalanche',
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
export const COMPOUND_API_URL_BY_CHAIN_ID = valueByChainId(
|
||||
{
|
||||
// TODO: enable after FQT has been redeployed on Ethereum mainnet
|
||||
// [ChainId.Mainnet]: 'https://api.compound.finance/api/v2',
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
//
|
||||
// BSC
|
||||
//
|
||||
@@ -1587,10 +1867,70 @@ export const JETSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.BSC]: '0xbe65b8f75b9f20f4c522e0067a3887fada714800',
|
||||
[ChainId.Polygon]: '0x5c6ec38fb0e2609672bdf628b1fd605a523e5923',
|
||||
[ChainId.Fantom]: '0x845e76a8691423fbc4ecb8dd77556cb61c09ee25',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const PANGOLIN_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Avalanche]: '0xe54ca86531e17ef3616d22ca28b0d458b6c89106',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const TRADER_JOE_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Avalanche]: '0x60ae616a2155ee3d9a68541ba4544862310933d4',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const UBESWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Celo]: '0x7d28570135a2b1930f331c507f65039d4937f66c',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const MORPHEUSSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Fantom]: '0x8ac868293d97761a1fed6d4a01e9ff17c5594aa3',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const SPIRITSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Fantom]: '0x16327e3fbdaca3bcf7e38f5af2599d2ddc33ae52',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const SPOOKYSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Fantom]: '0xf491e7b69e4244ad4002bc14e878a34207e38c29',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID = valueByChainId<ERC20BridgeSource[]>(
|
||||
{
|
||||
[ChainId.Mainnet]: [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap, ERC20BridgeSource.UniswapV3],
|
||||
[ChainId.BSC]: [
|
||||
ERC20BridgeSource.PancakeSwap,
|
||||
ERC20BridgeSource.PancakeSwapV2,
|
||||
ERC20BridgeSource.BakerySwap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.CafeSwap,
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
ERC20BridgeSource.JulSwap,
|
||||
],
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const uniswapV2CloneGasSchedule = (fillData?: FillData) => {
|
||||
// TODO: Different base cost if to/from ETH.
|
||||
let gas = 90e3;
|
||||
@@ -1696,7 +2036,21 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
return gas;
|
||||
},
|
||||
[ERC20BridgeSource.Lido]: () => 226e3,
|
||||
[ERC20BridgeSource.Clipper]: () => 170e3,
|
||||
[ERC20BridgeSource.AaveV2]: (fillData?: FillData) => {
|
||||
const aaveFillData = fillData as AaveV2FillData;
|
||||
// NOTE: The Aave deposit method is more expensive than the withdraw
|
||||
return aaveFillData.takerToken === aaveFillData.underlyingToken ? 400e3 : 300e3;
|
||||
},
|
||||
[ERC20BridgeSource.Compound]: (fillData?: FillData) => {
|
||||
// NOTE: cETH is handled differently than other cTokens
|
||||
const wethAddress = NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet];
|
||||
const compoundFillData = fillData as CompoundFillData;
|
||||
if (compoundFillData.takerToken === compoundFillData.cToken) {
|
||||
return compoundFillData.makerToken === wethAddress ? 120e3 : 150e3;
|
||||
} else {
|
||||
return compoundFillData.takerToken === wethAddress ? 210e3 : 250e3;
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// BSC
|
||||
@@ -1719,6 +2073,25 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
[ERC20BridgeSource.Dfyn]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Polydex]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.JetSwap]: uniswapV2CloneGasSchedule,
|
||||
|
||||
//
|
||||
// Avalanche
|
||||
//
|
||||
[ERC20BridgeSource.Pangolin]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.TraderJoe]: uniswapV2CloneGasSchedule,
|
||||
|
||||
//
|
||||
// Celo
|
||||
//
|
||||
[ERC20BridgeSource.UbeSwap]: uniswapV2CloneGasSchedule,
|
||||
|
||||
//
|
||||
// Fantom
|
||||
//
|
||||
[ERC20BridgeSource.MorpheusSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.SpiritSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.SpookySwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Beethovenx]: () => 100e3,
|
||||
};
|
||||
|
||||
export const DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = { ...DEFAULT_GAS_SCHEDULE };
|
||||
@@ -1727,7 +2100,7 @@ export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(20000);
|
||||
|
||||
// tslint:enable:custom-no-magic-numbers
|
||||
|
||||
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
||||
export const DEFAULT_GET_MARKET_ORDERS_OPTS: Omit<GetMarketOrdersOpts, 'gasPrice'> = {
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
runLimit: 2 ** 15,
|
||||
excludedSources: [],
|
||||
@@ -1744,4 +2117,5 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
||||
shouldGenerateQuoteReport: true,
|
||||
shouldIncludePriceComparisonsReport: false,
|
||||
tokenAdjacencyGraph: { default: [] },
|
||||
neonRouterNumSamples: 14,
|
||||
};
|
||||
|
@@ -71,7 +71,25 @@ function hasLiquidity(fills: Fill[]): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
function nativeOrdersToFills(
|
||||
export function ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
ethAmount,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
}: {
|
||||
input: BigNumber;
|
||||
output: BigNumber;
|
||||
inputAmountPerEth: BigNumber;
|
||||
outputAmountPerEth: BigNumber;
|
||||
ethAmount: BigNumber | number;
|
||||
}): BigNumber {
|
||||
return !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(ethAmount)
|
||||
: inputAmountPerEth.times(ethAmount).times(output.dividedToIntegerBy(input));
|
||||
}
|
||||
|
||||
export function nativeOrdersToFills(
|
||||
side: MarketOperation,
|
||||
orders: NativeOrderWithFillableAmounts[],
|
||||
targetInput: BigNumber = POSITIVE_INF,
|
||||
@@ -89,9 +107,13 @@ function nativeOrdersToFills(
|
||||
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
||||
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
||||
const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o);
|
||||
const outputPenalty = !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(fee)
|
||||
: inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input));
|
||||
const outputPenalty = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
// targetInput can be less than the order size
|
||||
// whilst the penalty is constant, it affects the adjusted output
|
||||
// only up until the target has been exhausted.
|
||||
@@ -132,7 +154,7 @@ function nativeOrdersToFills(
|
||||
return fills;
|
||||
}
|
||||
|
||||
function dexSamplesToFills(
|
||||
export function dexSamplesToFills(
|
||||
side: MarketOperation,
|
||||
samples: DexSample[],
|
||||
outputAmountPerEth: BigNumber,
|
||||
@@ -156,9 +178,13 @@ function dexSamplesToFills(
|
||||
let penalty = ZERO_AMOUNT;
|
||||
if (i === 0) {
|
||||
// Only the first fill in a DEX path incurs a penalty.
|
||||
penalty = !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(fee)
|
||||
: inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input));
|
||||
penalty = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
}
|
||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||
|
||||
|
@@ -18,12 +18,15 @@ import {
|
||||
|
||||
import {
|
||||
dexSampleToReportSource,
|
||||
ExtendedQuoteReportSources,
|
||||
generateExtendedQuoteReportSources,
|
||||
generateQuoteReport,
|
||||
multiHopSampleToReportSource,
|
||||
nativeOrderToReportEntry,
|
||||
PriceComparisonsReport,
|
||||
QuoteReport,
|
||||
} from './../quote_report_generator';
|
||||
|
||||
import { getComparisonPrices } from './comparison_price';
|
||||
import {
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
@@ -39,7 +42,7 @@ import { createFills } from './fills';
|
||||
import { getBestTwoHopQuote } from './multihop_utils';
|
||||
import { createOrdersFromTwoHopSample } from './orders';
|
||||
import { Path, PathPenaltyOpts } from './path';
|
||||
import { fillsToSortedPaths, findOptimalPathAsync } from './path_optimizer';
|
||||
import { fillsToSortedPaths, findOptimalPathJSAsync, findOptimalRustPathFromSamples } from './path_optimizer';
|
||||
import { DexOrderSampler, getSampleAmounts } from './sampler';
|
||||
import { SourceFilters } from './source_filters';
|
||||
import {
|
||||
@@ -48,7 +51,6 @@ import {
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
FillData,
|
||||
GenerateOptimizedOrdersOpts,
|
||||
GetMarketOrdersOpts,
|
||||
MarketSideLiquidity,
|
||||
@@ -57,6 +59,8 @@ import {
|
||||
OrderDomain,
|
||||
} from './types';
|
||||
|
||||
const SHOULD_USE_RUST_ROUTER = process.env.RUST_ROUTER === 'true';
|
||||
|
||||
// tslint:disable:boolean-naming
|
||||
|
||||
export class MarketOperationUtils {
|
||||
@@ -77,6 +81,25 @@ export class MarketOperationUtils {
|
||||
return generateQuoteReport(side, quotes.nativeOrders, liquidityDelivered, comparisonPrice, quoteRequestor);
|
||||
}
|
||||
|
||||
private static _computeExtendedQuoteReportSources(
|
||||
quoteRequestor: QuoteRequestor | undefined,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
amount: BigNumber,
|
||||
optimizerResult: OptimizerResult,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
): ExtendedQuoteReportSources {
|
||||
const { side, quotes } = marketSideLiquidity;
|
||||
const { liquidityDelivered } = optimizerResult;
|
||||
return generateExtendedQuoteReportSources(
|
||||
side,
|
||||
quotes,
|
||||
liquidityDelivered,
|
||||
amount,
|
||||
comparisonPrice,
|
||||
quoteRequestor,
|
||||
);
|
||||
}
|
||||
|
||||
private static _computePriceComparisonsReport(
|
||||
quoteRequestor: QuoteRequestor | undefined,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
@@ -135,6 +158,8 @@ export class MarketOperationUtils {
|
||||
|
||||
// Call the sampler contract.
|
||||
const samplerPromise = this._sampler.executeAsync(
|
||||
this._sampler.getBlockNumber(),
|
||||
this._sampler.getGasLeft(),
|
||||
this._sampler.getTokenDecimals([makerToken, takerToken]),
|
||||
// Get native order fillable amounts.
|
||||
this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||
@@ -161,6 +186,7 @@ export class MarketOperationUtils {
|
||||
takerAmount,
|
||||
),
|
||||
this._sampler.isAddressContract(txOrigin),
|
||||
this._sampler.getGasLeft(),
|
||||
);
|
||||
|
||||
// Refresh the cached pools asynchronously if required
|
||||
@@ -168,6 +194,8 @@ export class MarketOperationUtils {
|
||||
|
||||
const [
|
||||
[
|
||||
blockNumber,
|
||||
gasBefore,
|
||||
tokenDecimals,
|
||||
orderFillableTakerAmounts,
|
||||
outputAmountPerEth,
|
||||
@@ -175,9 +203,14 @@ export class MarketOperationUtils {
|
||||
dexQuotes,
|
||||
rawTwoHopQuotes,
|
||||
isTxOriginContract,
|
||||
gasAfter,
|
||||
],
|
||||
] = await Promise.all([samplerPromise]);
|
||||
|
||||
// Log the gas metrics
|
||||
_opts.samplerMetrics?.logGasDetails({ gasBefore, gasAfter });
|
||||
_opts.samplerMetrics?.logBlockNumber(blockNumber);
|
||||
|
||||
// Filter out any invalid two hop quotes where we couldn't find a route
|
||||
const twoHopQuotes = rawTwoHopQuotes.filter(
|
||||
q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource,
|
||||
@@ -326,12 +359,12 @@ export class MarketOperationUtils {
|
||||
public async getBatchMarketBuyOrdersAsync(
|
||||
batchNativeOrders: SignedNativeOrder[][],
|
||||
makerAmounts: BigNumber[],
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
opts: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber },
|
||||
): Promise<Array<OptimizerResult | undefined>> {
|
||||
if (batchNativeOrders.length === 0) {
|
||||
throw new Error(AggregationError.EmptyOrders);
|
||||
}
|
||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
const _opts: GetMarketOrdersOpts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
|
||||
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
|
||||
const quoteSourceFilters = this._buySources.merge(requestFilters);
|
||||
@@ -409,6 +442,8 @@ export class MarketOperationUtils {
|
||||
excludedSources: _opts.excludedSources,
|
||||
feeSchedule: _opts.feeSchedule,
|
||||
allowFallback: _opts.allowFallback,
|
||||
gasPrice: _opts.gasPrice,
|
||||
neonRouterNumSamples: _opts.neonRouterNumSamples,
|
||||
},
|
||||
);
|
||||
return optimizerResult;
|
||||
@@ -475,6 +510,7 @@ export class MarketOperationUtils {
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
exchangeProxyOverhead: opts.exchangeProxyOverhead || (() => ZERO_AMOUNT),
|
||||
gasPrice: opts.gasPrice,
|
||||
};
|
||||
|
||||
// NOTE: For sell quotes input is the taker asset and for buy quotes input is the maker asset
|
||||
@@ -485,8 +521,31 @@ export class MarketOperationUtils {
|
||||
const _unoptimizedPath = fillsToSortedPaths(fills, side, inputAmount, penaltyOpts)[0];
|
||||
const unoptimizedPath = _unoptimizedPath ? _unoptimizedPath.collapse(orderOpts) : undefined;
|
||||
|
||||
// Find the optimal path
|
||||
const optimalPath = await findOptimalPathAsync(side, fills, inputAmount, opts.runLimit, penaltyOpts);
|
||||
// Find the optimal path using Rust router if enabled, otherwise fallback to JS Router
|
||||
let optimalPath: Path | undefined;
|
||||
if (SHOULD_USE_RUST_ROUTER) {
|
||||
optimalPath = findOptimalRustPathFromSamples(
|
||||
side,
|
||||
dexQuotes,
|
||||
[...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
||||
inputAmount,
|
||||
penaltyOpts,
|
||||
opts.feeSchedule,
|
||||
this._sampler.chainId,
|
||||
opts.neonRouterNumSamples,
|
||||
opts.samplerMetrics,
|
||||
);
|
||||
} else {
|
||||
optimalPath = await findOptimalPathJSAsync(
|
||||
side,
|
||||
fills,
|
||||
inputAmount,
|
||||
opts.runLimit,
|
||||
opts.samplerMetrics,
|
||||
penaltyOpts,
|
||||
);
|
||||
}
|
||||
|
||||
const optimalPathRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
|
||||
|
||||
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
||||
@@ -514,7 +573,7 @@ export class MarketOperationUtils {
|
||||
}
|
||||
|
||||
// Generate a fallback path if required
|
||||
await this._addOptionalFallbackAsync(side, inputAmount, optimalPath, fills, opts, penaltyOpts);
|
||||
await this._addOptionalFallbackAsync(side, inputAmount, optimalPath, dexQuotes, fills, opts, penaltyOpts);
|
||||
const collapsedPath = optimalPath.collapse(orderOpts);
|
||||
|
||||
return {
|
||||
@@ -536,9 +595,9 @@ export class MarketOperationUtils {
|
||||
nativeOrders: SignedNativeOrder[],
|
||||
amount: BigNumber,
|
||||
side: MarketOperation,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
opts: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber },
|
||||
): Promise<OptimizerResultWithReport> {
|
||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
const _opts: GetMarketOrdersOpts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
const optimizerOpts: GenerateOptimizedOrdersOpts = {
|
||||
bridgeSlippage: _opts.bridgeSlippage,
|
||||
maxFallbackSlippage: _opts.maxFallbackSlippage,
|
||||
@@ -546,6 +605,9 @@ export class MarketOperationUtils {
|
||||
feeSchedule: _opts.feeSchedule,
|
||||
allowFallback: _opts.allowFallback,
|
||||
exchangeProxyOverhead: _opts.exchangeProxyOverhead,
|
||||
gasPrice: _opts.gasPrice,
|
||||
neonRouterNumSamples: _opts.neonRouterNumSamples,
|
||||
samplerMetrics: _opts.samplerMetrics,
|
||||
};
|
||||
|
||||
if (nativeOrders.length === 0) {
|
||||
@@ -684,6 +746,16 @@ export class MarketOperationUtils {
|
||||
);
|
||||
}
|
||||
|
||||
// Always compute the Extended Quote Report
|
||||
let extendedQuoteReportSources: ExtendedQuoteReportSources | undefined;
|
||||
extendedQuoteReportSources = MarketOperationUtils._computeExtendedQuoteReportSources(
|
||||
_opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
|
||||
marketSideLiquidity,
|
||||
amount,
|
||||
optimizerResult,
|
||||
wholeOrderPrice,
|
||||
);
|
||||
|
||||
let priceComparisonsReport: PriceComparisonsReport | undefined;
|
||||
if (_opts.shouldIncludePriceComparisonsReport) {
|
||||
priceComparisonsReport = MarketOperationUtils._computePriceComparisonsReport(
|
||||
@@ -692,7 +764,7 @@ export class MarketOperationUtils {
|
||||
wholeOrderPrice,
|
||||
);
|
||||
}
|
||||
return { ...optimizerResult, quoteReport, priceComparisonsReport };
|
||||
return { ...optimizerResult, quoteReport, extendedQuoteReportSources, priceComparisonsReport };
|
||||
}
|
||||
|
||||
private async _refreshPoolCacheIfRequiredAsync(takerToken: string, makerToken: string): Promise<void> {
|
||||
@@ -711,7 +783,8 @@ export class MarketOperationUtils {
|
||||
side: MarketOperation,
|
||||
inputAmount: BigNumber,
|
||||
optimalPath: Path,
|
||||
fills: Array<Array<Fill<FillData>>>,
|
||||
dexQuotes: DexSample[][],
|
||||
fills: Fill[][],
|
||||
opts: GenerateOptimizedOrdersOpts,
|
||||
penaltyOpts: PathPenaltyOpts,
|
||||
): Promise<void> {
|
||||
@@ -725,13 +798,40 @@ export class MarketOperationUtils {
|
||||
if (opts.allowFallback && fragileFills.length !== 0) {
|
||||
// We create a fallback path that is exclusive of Native liquidity
|
||||
// This is the optimal on-chain path for the entire input amount
|
||||
const sturdyFills = fills.filter(p => p.length > 0 && !fragileSources.includes(p[0].source));
|
||||
const sturdyOptimalPath = await findOptimalPathAsync(side, sturdyFills, inputAmount, opts.runLimit, {
|
||||
const sturdyPenaltyOpts = {
|
||||
...penaltyOpts,
|
||||
exchangeProxyOverhead: (sourceFlags: bigint) =>
|
||||
// tslint:disable-next-line: no-bitwise
|
||||
penaltyOpts.exchangeProxyOverhead(sourceFlags | optimalPath.sourceFlags),
|
||||
});
|
||||
};
|
||||
|
||||
let sturdyOptimalPath: Path | undefined;
|
||||
if (SHOULD_USE_RUST_ROUTER) {
|
||||
const sturdySamples = dexQuotes.filter(
|
||||
samples => samples.length > 0 && !fragileSources.includes(samples[0].source),
|
||||
);
|
||||
sturdyOptimalPath = findOptimalRustPathFromSamples(
|
||||
side,
|
||||
sturdySamples,
|
||||
[],
|
||||
inputAmount,
|
||||
sturdyPenaltyOpts,
|
||||
opts.feeSchedule,
|
||||
this._sampler.chainId,
|
||||
opts.neonRouterNumSamples,
|
||||
undefined, // hack: set sampler metrics to undefined to avoid fallback timings
|
||||
);
|
||||
} else {
|
||||
const sturdyFills = fills.filter(p => p.length > 0 && !fragileSources.includes(p[0].source));
|
||||
sturdyOptimalPath = await findOptimalPathJSAsync(
|
||||
side,
|
||||
sturdyFills,
|
||||
inputAmount,
|
||||
opts.runLimit,
|
||||
undefined, // hack: set sampler metrics to undefined to avoid fallback timings
|
||||
sturdyPenaltyOpts,
|
||||
);
|
||||
}
|
||||
// Calculate the slippage of on-chain sources compared to the most optimal path
|
||||
// if within an acceptable threshold we enable a fallback to prevent reverts
|
||||
if (
|
||||
|
@@ -3,13 +3,15 @@ import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
|
||||
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
|
||||
|
||||
import { MAX_UINT256, NULL_BYTES, ZERO_AMOUNT } from './constants';
|
||||
import { MAX_UINT256, ZERO_AMOUNT } from './constants';
|
||||
import {
|
||||
AaveV2FillData,
|
||||
AggregationError,
|
||||
BalancerFillData,
|
||||
BalancerV2FillData,
|
||||
BancorFillData,
|
||||
CollapsedFill,
|
||||
CompoundFillData,
|
||||
CurveFillData,
|
||||
DexSample,
|
||||
DODOFillData,
|
||||
@@ -180,8 +182,24 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'IronSwap');
|
||||
case ERC20BridgeSource.ACryptos:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'ACryptoS');
|
||||
case ERC20BridgeSource.Clipper:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Clipper, 'Clipper');
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Pangolin');
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'TraderJoe');
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'UbeSwap');
|
||||
case ERC20BridgeSource.Beethovenx:
|
||||
return encodeBridgeSourceId(BridgeProtocol.BalancerV2, 'Beethovenx');
|
||||
case ERC20BridgeSource.SpiritSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'SpiritSwap');
|
||||
case ERC20BridgeSource.SpookySwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'SpookySwap');
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MorpheusSwap');
|
||||
case ERC20BridgeSource.AaveV2:
|
||||
return encodeBridgeSourceId(BridgeProtocol.AaveV2, 'AaveV2');
|
||||
case ERC20BridgeSource.Compound:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Compound, 'Compound');
|
||||
default:
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
@@ -230,6 +248,7 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
||||
bridgeData = encoder.encode([balancerFillData.poolAddress]);
|
||||
break;
|
||||
case ERC20BridgeSource.BalancerV2:
|
||||
case ERC20BridgeSource.Beethovenx:
|
||||
const balancerV2FillData = (order as OptimizedMarketBridgeOrder<BalancerV2FillData>).fillData;
|
||||
const { vault, poolId } = balancerV2FillData;
|
||||
bridgeData = encoder.encode([vault, poolId]);
|
||||
@@ -256,6 +275,12 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
||||
case ERC20BridgeSource.Polydex:
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
case ERC20BridgeSource.JetSwap:
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
case ERC20BridgeSource.SpiritSwap:
|
||||
case ERC20BridgeSource.SpookySwap:
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
const uniswapV2FillData = (order as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
|
||||
bridgeData = encoder.encode([uniswapV2FillData.router, uniswapV2FillData.tokenAddressPath]);
|
||||
break;
|
||||
@@ -320,10 +345,15 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
||||
const lidoFillData = (order as OptimizedMarketBridgeOrder<LidoFillData>).fillData;
|
||||
bridgeData = encoder.encode([lidoFillData.stEthTokenAddress]);
|
||||
break;
|
||||
case ERC20BridgeSource.Clipper:
|
||||
const clipperFillData = (order as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
|
||||
bridgeData = encoder.encode([clipperFillData.poolAddress, NULL_BYTES]);
|
||||
case ERC20BridgeSource.AaveV2:
|
||||
const aaveFillData = (order as OptimizedMarketBridgeOrder<AaveV2FillData>).fillData;
|
||||
bridgeData = encoder.encode([aaveFillData.lendingPool, aaveFillData.aToken]);
|
||||
break;
|
||||
case ERC20BridgeSource.Compound:
|
||||
const compoundFillData = (order as OptimizedMarketBridgeOrder<CompoundFillData>).fillData;
|
||||
bridgeData = encoder.encode([compoundFillData.cToken]);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
@@ -448,6 +478,13 @@ export const BRIDGE_ENCODERS: {
|
||||
[ERC20BridgeSource.CryptoCom]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.Linkswap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.ShibaSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.Pangolin]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.TraderJoe]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.SpiritSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.SpookySwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.MorpheusSwap]: routerAddressPathEncoder,
|
||||
// Celo
|
||||
[ERC20BridgeSource.UbeSwap]: routerAddressPathEncoder,
|
||||
// BSC
|
||||
[ERC20BridgeSource.PancakeSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.PancakeSwapV2]: routerAddressPathEncoder,
|
||||
@@ -475,16 +512,15 @@ export const BRIDGE_ENCODERS: {
|
||||
// Custom integrations
|
||||
[ERC20BridgeSource.MakerPsm]: makerPsmEncoder,
|
||||
[ERC20BridgeSource.BalancerV2]: balancerV2Encoder,
|
||||
[ERC20BridgeSource.Beethovenx]: balancerV2Encoder,
|
||||
[ERC20BridgeSource.UniswapV3]: AbiEncoder.create([
|
||||
{ name: 'router', type: 'address' },
|
||||
{ name: 'path', type: 'bytes' },
|
||||
]),
|
||||
[ERC20BridgeSource.KyberDmm]: AbiEncoder.create('(address,address[],address[])'),
|
||||
[ERC20BridgeSource.Lido]: AbiEncoder.create('(address)'),
|
||||
[ERC20BridgeSource.Clipper]: AbiEncoder.create([
|
||||
{ name: 'provider', type: 'address' },
|
||||
{ name: 'data', type: 'bytes' },
|
||||
]),
|
||||
[ERC20BridgeSource.AaveV2]: AbiEncoder.create('(address,address)'),
|
||||
[ERC20BridgeSource.Compound]: AbiEncoder.create('(address)'),
|
||||
};
|
||||
|
||||
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {
|
||||
|
@@ -3,6 +3,7 @@ import { BigNumber } from '@0x/utils';
|
||||
import { MarketOperation } from '../../types';
|
||||
|
||||
import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
|
||||
import { ethToOutputAmount } from './fills';
|
||||
import { createBridgeOrder, createNativeOptimizedOrder, CreateOrderFromPathOpts, getMakerTakerTokens } from './orders';
|
||||
import { getCompleteRate, getRate } from './rate_utils';
|
||||
import {
|
||||
@@ -25,12 +26,14 @@ export interface PathPenaltyOpts {
|
||||
outputAmountPerEth: BigNumber;
|
||||
inputAmountPerEth: BigNumber;
|
||||
exchangeProxyOverhead: ExchangeProxyOverhead;
|
||||
gasPrice: BigNumber;
|
||||
}
|
||||
|
||||
export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
|
||||
outputAmountPerEth: ZERO_AMOUNT,
|
||||
inputAmountPerEth: ZERO_AMOUNT,
|
||||
exchangeProxyOverhead: () => ZERO_AMOUNT,
|
||||
gasPrice: ZERO_AMOUNT,
|
||||
};
|
||||
|
||||
export class Path {
|
||||
@@ -143,9 +146,13 @@ export class Path {
|
||||
const { input, output } = this._adjustedSize;
|
||||
const { exchangeProxyOverhead, outputAmountPerEth, inputAmountPerEth } = this.pathPenaltyOpts;
|
||||
const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
|
||||
const pathPenalty = !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(gasOverhead)
|
||||
: inputAmountPerEth.times(gasOverhead).times(output.dividedToIntegerBy(input));
|
||||
const pathPenalty = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: gasOverhead,
|
||||
});
|
||||
return {
|
||||
input,
|
||||
output: this.side === MarketOperation.Sell ? output.minus(pathPenalty) : output.plus(pathPenalty),
|
||||
|
@@ -1,26 +1,389 @@
|
||||
import { assert } from '@0x/assert';
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { OptimizerCapture, route, SerializedPath } from '@0x/neon-router';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
import { performance } from 'perf_hooks';
|
||||
|
||||
import { MarketOperation } from '../../types';
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||
import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID } from '../market_operation_utils/constants';
|
||||
|
||||
import { dexSamplesToFills, ethToOutputAmount, nativeOrdersToFills } from './fills';
|
||||
import { DEFAULT_PATH_PENALTY_OPTS, Path, PathPenaltyOpts } from './path';
|
||||
import { ERC20BridgeSource, Fill } from './types';
|
||||
import { getRate } from './rate_utils';
|
||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillData, SamplerMetrics } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
|
||||
|
||||
const RUN_LIMIT_DECAY_FACTOR = 0.5;
|
||||
const FILL_QUOTE_TRANSFORMER_GAS_OVERHEAD = new BigNumber(150e3);
|
||||
// NOTE: The Rust router will panic with less than 3 samples
|
||||
const MIN_NUM_SAMPLE_INPUTS = 3;
|
||||
|
||||
const isDexSample = (obj: DexSample | NativeOrderWithFillableAmounts): obj is DexSample => !!(obj as DexSample).source;
|
||||
|
||||
function nativeOrderToNormalizedAmounts(
|
||||
side: MarketOperation,
|
||||
nativeOrder: NativeOrderWithFillableAmounts,
|
||||
): { input: BigNumber; output: BigNumber } {
|
||||
const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount } = nativeOrder;
|
||||
const makerAmount = fillableMakerAmount;
|
||||
const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
|
||||
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
||||
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
||||
|
||||
return { input, output };
|
||||
}
|
||||
|
||||
function calculateOuputFee(
|
||||
side: MarketOperation,
|
||||
sampleOrNativeOrder: DexSample | NativeOrderWithFillableAmounts,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
): BigNumber {
|
||||
if (isDexSample(sampleOrNativeOrder)) {
|
||||
const { input, output, source, fillData } = sampleOrNativeOrder;
|
||||
const fee = fees[source]?.(fillData) || 0;
|
||||
const outputFee = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
return outputFee;
|
||||
} else {
|
||||
const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
|
||||
const fee = fees[ERC20BridgeSource.Native]?.(sampleOrNativeOrder) || 0;
|
||||
const outputFee = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
return outputFee;
|
||||
}
|
||||
}
|
||||
|
||||
function findRoutesAndCreateOptimalPath(
|
||||
side: MarketOperation,
|
||||
samples: DexSample[][],
|
||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||
input: BigNumber,
|
||||
opts: PathPenaltyOpts,
|
||||
fees: FeeSchedule,
|
||||
neonRouterNumSamples: number,
|
||||
): Path | undefined {
|
||||
const createFill = (sample: DexSample) =>
|
||||
dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, fees)[0];
|
||||
|
||||
const samplesAndNativeOrdersWithResults: Array<DexSample[] | NativeOrderWithFillableAmounts[]> = [];
|
||||
const serializedPaths: SerializedPath[] = [];
|
||||
for (const singleSourceSamples of samples) {
|
||||
if (singleSourceSamples.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const singleSourceSamplesWithOutput = [...singleSourceSamples];
|
||||
for (let i = singleSourceSamples.length - 1; i >= 0; i--) {
|
||||
if (singleSourceSamples[i].output.isZero()) {
|
||||
// Remove trailing 0 output samples
|
||||
singleSourceSamplesWithOutput.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (singleSourceSamplesWithOutput.length < MIN_NUM_SAMPLE_INPUTS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(kimpers): Do we need to handle 0 entries, from eg Kyber?
|
||||
const serializedPath = singleSourceSamplesWithOutput.reduce<SerializedPath>(
|
||||
(memo, sample, sampleIdx) => {
|
||||
memo.ids.push(`${sample.source}-${serializedPaths.length}-${sampleIdx}`);
|
||||
memo.inputs.push(sample.input.integerValue().toNumber());
|
||||
memo.outputs.push(sample.output.integerValue().toNumber());
|
||||
memo.outputFees.push(
|
||||
calculateOuputFee(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
|
||||
.integerValue()
|
||||
.toNumber(),
|
||||
);
|
||||
|
||||
return memo;
|
||||
},
|
||||
{
|
||||
ids: [],
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
outputFees: [],
|
||||
},
|
||||
);
|
||||
|
||||
samplesAndNativeOrdersWithResults.push(singleSourceSamplesWithOutput);
|
||||
serializedPaths.push(serializedPath);
|
||||
}
|
||||
|
||||
for (const [idx, nativeOrder] of nativeOrders.entries()) {
|
||||
const { input: normalizedOrderInput, output: normalizedOrderOutput } = nativeOrderToNormalizedAmounts(
|
||||
side,
|
||||
nativeOrder,
|
||||
);
|
||||
// NOTE: skip dummy order created in swap_quoter
|
||||
// TODO: remove dummy order and this logic once we don't need the JS router
|
||||
if (normalizedOrderInput.isLessThanOrEqualTo(0) || normalizedOrderOutput.isLessThanOrEqualTo(0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// HACK: the router requires at minimum 3 samples as a basis for interpolation
|
||||
const inputs = [
|
||||
0,
|
||||
normalizedOrderInput
|
||||
.dividedBy(2)
|
||||
.integerValue()
|
||||
.toNumber(),
|
||||
normalizedOrderInput.integerValue().toNumber(),
|
||||
];
|
||||
const outputs = [
|
||||
0,
|
||||
normalizedOrderOutput
|
||||
.dividedBy(2)
|
||||
.integerValue()
|
||||
.toNumber(),
|
||||
normalizedOrderOutput.integerValue().toNumber(),
|
||||
];
|
||||
// NOTE: same fee no matter if full or partial fill
|
||||
const fee = calculateOuputFee(side, nativeOrder, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
|
||||
.integerValue()
|
||||
.toNumber();
|
||||
const outputFees = [fee, fee, fee];
|
||||
// NOTE: ids can be the same for all fake samples
|
||||
const id = `${ERC20BridgeSource.Native}-${serializedPaths.length}-${idx}`;
|
||||
const ids = [id, id, id];
|
||||
|
||||
const serializedPath: SerializedPath = {
|
||||
ids,
|
||||
inputs,
|
||||
outputs,
|
||||
outputFees,
|
||||
};
|
||||
|
||||
samplesAndNativeOrdersWithResults.push([nativeOrder]);
|
||||
serializedPaths.push(serializedPath);
|
||||
}
|
||||
|
||||
if (serializedPaths.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const rustArgs: OptimizerCapture = {
|
||||
side,
|
||||
targetInput: input.toNumber(),
|
||||
pathsIn: serializedPaths,
|
||||
};
|
||||
|
||||
const allSourcesRustRoute = new Float64Array(rustArgs.pathsIn.length);
|
||||
const strategySourcesOutputAmounts = new Float64Array(rustArgs.pathsIn.length);
|
||||
route(rustArgs, allSourcesRustRoute, strategySourcesOutputAmounts, neonRouterNumSamples);
|
||||
assert.assert(
|
||||
rustArgs.pathsIn.length === allSourcesRustRoute.length,
|
||||
'different number of sources in the Router output than the input',
|
||||
);
|
||||
assert.assert(
|
||||
rustArgs.pathsIn.length === strategySourcesOutputAmounts.length,
|
||||
'different number of sources in the Router output amounts results than the input',
|
||||
);
|
||||
|
||||
const routesAndSamplesAndOutputs = _.zip(
|
||||
allSourcesRustRoute,
|
||||
samplesAndNativeOrdersWithResults,
|
||||
strategySourcesOutputAmounts,
|
||||
);
|
||||
const adjustedFills: Fill[] = [];
|
||||
const totalRoutedAmount = BigNumber.sum(...allSourcesRustRoute);
|
||||
|
||||
const scale = input.dividedBy(totalRoutedAmount);
|
||||
for (const [routeInput, routeSamplesAndNativeOrders, outputAmount] of routesAndSamplesAndOutputs) {
|
||||
if (!routeInput || !routeSamplesAndNativeOrders || !outputAmount || !Number.isFinite(outputAmount)) {
|
||||
continue;
|
||||
}
|
||||
// TODO(kimpers): [TKR-241] amounts are sometimes clipped in the router due to precision loss for number/f64
|
||||
// we can work around it by scaling it and rounding up. However now we end up with a total amount of a couple base units too much
|
||||
const rustInputAdjusted = BigNumber.min(
|
||||
new BigNumber(routeInput).multipliedBy(scale).integerValue(BigNumber.ROUND_CEIL),
|
||||
input,
|
||||
);
|
||||
|
||||
const current = routeSamplesAndNativeOrders[routeSamplesAndNativeOrders.length - 1];
|
||||
if (!isDexSample(current)) {
|
||||
const nativeFill = nativeOrdersToFills(
|
||||
side,
|
||||
[current],
|
||||
rustInputAdjusted,
|
||||
opts.outputAmountPerEth,
|
||||
opts.inputAmountPerEth,
|
||||
fees,
|
||||
)[0];
|
||||
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
|
||||
adjustedFills.push(nativeFill);
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: For DexSamples only
|
||||
let fill = createFill(current);
|
||||
const routeSamples = routeSamplesAndNativeOrders as Array<DexSample<FillData>>;
|
||||
// Descend to approach a closer fill for fillData which may not be consistent
|
||||
// throughout the path (UniswapV3) and for a closer guesstimate at
|
||||
// gas used
|
||||
|
||||
assert.assert(routeSamples.length >= 1, 'Found no sample to use for source');
|
||||
for (let k = routeSamples.length - 1; k >= 0; k--) {
|
||||
if (k === 0) {
|
||||
fill = createFill(routeSamples[0]);
|
||||
}
|
||||
if (rustInputAdjusted.isGreaterThan(routeSamples[k].input)) {
|
||||
const left = routeSamples[k];
|
||||
const right = routeSamples[k + 1];
|
||||
if (left && right) {
|
||||
fill = createFill({
|
||||
...right, // default to the greater (for gas used)
|
||||
input: rustInputAdjusted,
|
||||
output: new BigNumber(outputAmount),
|
||||
});
|
||||
} else {
|
||||
assert.assert(Boolean(left || right), 'No valid sample to use');
|
||||
fill = createFill(left || right);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(kimpers): remove once we have solved the rounding/precision loss issues in the Rust router
|
||||
const scaleOutput = (output: BigNumber) =>
|
||||
output
|
||||
.dividedBy(fill.input)
|
||||
.times(rustInputAdjusted)
|
||||
.decimalPlaces(0, side === MarketOperation.Sell ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL);
|
||||
adjustedFills.push({
|
||||
...fill,
|
||||
input: rustInputAdjusted,
|
||||
output: scaleOutput(fill.output),
|
||||
adjustedOutput: scaleOutput(fill.adjustedOutput),
|
||||
index: 0,
|
||||
parent: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (adjustedFills.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const pathFromRustInputs = Path.create(side, adjustedFills, input);
|
||||
|
||||
return pathFromRustInputs;
|
||||
}
|
||||
|
||||
export function findOptimalRustPathFromSamples(
|
||||
side: MarketOperation,
|
||||
samples: DexSample[][],
|
||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||
input: BigNumber,
|
||||
opts: PathPenaltyOpts,
|
||||
fees: FeeSchedule,
|
||||
chainId: ChainId,
|
||||
neonRouterNumSamples: number,
|
||||
samplerMetrics?: SamplerMetrics,
|
||||
): Path | undefined {
|
||||
const beforeAllTimeMs = performance.now();
|
||||
let beforeTimeMs = performance.now();
|
||||
const allSourcesPath = findRoutesAndCreateOptimalPath(
|
||||
side,
|
||||
samples,
|
||||
nativeOrders,
|
||||
input,
|
||||
opts,
|
||||
fees,
|
||||
neonRouterNumSamples,
|
||||
);
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
samplerMetrics &&
|
||||
samplerMetrics.logRouterDetails({
|
||||
router: 'neon-router',
|
||||
type: 'all',
|
||||
timingMs: performance.now() - beforeTimeMs,
|
||||
});
|
||||
if (!allSourcesPath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const vipSources = VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID[chainId];
|
||||
|
||||
// HACK(kimpers): The Rust router currently doesn't account for VIP sources correctly
|
||||
// we need to try to route them in isolation and compare with the results all sources
|
||||
if (vipSources.length > 0) {
|
||||
beforeTimeMs = performance.now();
|
||||
const vipSourcesSet = new Set(vipSources);
|
||||
const vipSourcesSamples = samples.filter(s => s[0] && vipSourcesSet.has(s[0].source));
|
||||
|
||||
if (vipSourcesSamples.length > 0) {
|
||||
const vipSourcesPath = findRoutesAndCreateOptimalPath(
|
||||
side,
|
||||
vipSourcesSamples,
|
||||
[],
|
||||
input,
|
||||
opts,
|
||||
fees,
|
||||
neonRouterNumSamples,
|
||||
);
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
samplerMetrics &&
|
||||
samplerMetrics.logRouterDetails({
|
||||
router: 'neon-router',
|
||||
type: 'vip',
|
||||
timingMs: performance.now() - beforeTimeMs,
|
||||
});
|
||||
|
||||
const { input: allSourcesInput, output: allSourcesOutput } = allSourcesPath.adjustedSize();
|
||||
// NOTE: For sell quotes input is the taker asset and for buy quotes input is the maker asset
|
||||
const gasCostInWei = FILL_QUOTE_TRANSFORMER_GAS_OVERHEAD.times(opts.gasPrice);
|
||||
const fqtOverheadInOutputToken = gasCostInWei.times(opts.outputAmountPerEth);
|
||||
const outputWithFqtOverhead =
|
||||
side === MarketOperation.Sell
|
||||
? allSourcesOutput.minus(fqtOverheadInOutputToken)
|
||||
: allSourcesOutput.plus(fqtOverheadInOutputToken);
|
||||
const allSourcesAdjustedRateWithFqtOverhead = getRate(side, allSourcesInput, outputWithFqtOverhead);
|
||||
|
||||
if (vipSourcesPath?.adjustedRate().isGreaterThan(allSourcesAdjustedRateWithFqtOverhead)) {
|
||||
return vipSourcesPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
samplerMetrics &&
|
||||
samplerMetrics.logRouterDetails({
|
||||
router: 'neon-router',
|
||||
type: 'total',
|
||||
timingMs: performance.now() - beforeAllTimeMs,
|
||||
});
|
||||
|
||||
return allSourcesPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the optimal mixture of fills that maximizes (for sells) or minimizes
|
||||
* (for buys) output, while meeting the input requirement.
|
||||
*/
|
||||
export async function findOptimalPathAsync(
|
||||
export async function findOptimalPathJSAsync(
|
||||
side: MarketOperation,
|
||||
fills: Fill[][],
|
||||
targetInput: BigNumber,
|
||||
runLimit: number = 2 ** 8,
|
||||
samplerMetrics?: SamplerMetrics,
|
||||
opts: PathPenaltyOpts = DEFAULT_PATH_PENALTY_OPTS,
|
||||
): Promise<Path | undefined> {
|
||||
const beforeTimeMs = performance.now();
|
||||
// Sort fill arrays by descending adjusted completed rate.
|
||||
// Remove any paths which cannot impact the optimal path
|
||||
const sortedPaths = reducePaths(fillsToSortedPaths(fills, side, targetInput, opts), side);
|
||||
@@ -34,7 +397,15 @@ export async function findOptimalPathAsync(
|
||||
// Yield to event loop.
|
||||
await Promise.resolve();
|
||||
}
|
||||
return optimalPath.isComplete() ? optimalPath : undefined;
|
||||
const finalPath = optimalPath.isComplete() ? optimalPath : undefined;
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
samplerMetrics &&
|
||||
samplerMetrics.logRouterDetails({
|
||||
router: 'js',
|
||||
type: 'total',
|
||||
timingMs: performance.now() - beforeTimeMs,
|
||||
});
|
||||
return finalPath;
|
||||
}
|
||||
|
||||
// Sort fill arrays by descending adjusted completed rate.
|
||||
|
@@ -14,7 +14,8 @@ import { BatchedOperation, ERC20BridgeSource, LiquidityProviderRegistry, TokenAd
|
||||
*/
|
||||
export function getSampleAmounts(maxFillAmount: BigNumber, numSamples: number, expBase: number = 1): BigNumber[] {
|
||||
const distribution = [...Array<BigNumber>(numSamples)].map((_v, i) => new BigNumber(expBase).pow(i));
|
||||
const stepSizes = distribution.map(d => d.div(BigNumber.sum(...distribution)));
|
||||
const distributionSum = BigNumber.sum(...distribution);
|
||||
const stepSizes = distribution.map(d => d.div(distributionSum));
|
||||
const amounts = stepSizes.map((_s, i) => {
|
||||
if (i === numSamples - 1) {
|
||||
return maxFillAmount;
|
||||
@@ -130,6 +131,37 @@ export class DexOrderSampler extends SamplerOperations {
|
||||
BatchedOperationResult<T8>
|
||||
]>;
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1, T2, T3, T4, T5, T6, T7, T8, T9
|
||||
>(...ops: [T1, T2, T3, T4, T5, T6, T7, T8, T9]): Promise<[
|
||||
BatchedOperationResult<T1>,
|
||||
BatchedOperationResult<T2>,
|
||||
BatchedOperationResult<T3>,
|
||||
BatchedOperationResult<T4>,
|
||||
BatchedOperationResult<T5>,
|
||||
BatchedOperationResult<T6>,
|
||||
BatchedOperationResult<T7>,
|
||||
BatchedOperationResult<T8>,
|
||||
BatchedOperationResult<T9>
|
||||
]>;
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
|
||||
>(...ops: [T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]): Promise<[
|
||||
BatchedOperationResult<T1>,
|
||||
BatchedOperationResult<T2>,
|
||||
BatchedOperationResult<T3>,
|
||||
BatchedOperationResult<T4>,
|
||||
BatchedOperationResult<T5>,
|
||||
BatchedOperationResult<T6>,
|
||||
BatchedOperationResult<T7>,
|
||||
BatchedOperationResult<T8>,
|
||||
BatchedOperationResult<T9>,
|
||||
BatchedOperationResult<T10>,
|
||||
]>;
|
||||
|
||||
/**
|
||||
* Run a series of operations from `DexOrderSampler.ops` in a single transaction.
|
||||
*/
|
||||
|
@@ -0,0 +1,36 @@
|
||||
import { BigNumber, logUtils, NULL_BYTES } from '@0x/utils';
|
||||
|
||||
import { ERC20BridgeSource, FillData, SourceQuoteOperation } from './types';
|
||||
|
||||
interface SamplerNoOperationCall {
|
||||
callback: () => BigNumber[];
|
||||
}
|
||||
|
||||
/**
|
||||
* SamplerNoOperation can be used for sources where we already have all the necessary information
|
||||
* required to perform the sample operations, without needing access to any on-chain data. Using a noop sample
|
||||
* you can skip the eth_call, and just calculate the results directly in typescript land.
|
||||
*/
|
||||
export class SamplerNoOperation<TFillData extends FillData = FillData> implements SourceQuoteOperation<TFillData> {
|
||||
public readonly source: ERC20BridgeSource;
|
||||
public fillData: TFillData;
|
||||
private readonly _callback: () => BigNumber[];
|
||||
|
||||
constructor(opts: { source: ERC20BridgeSource; fillData?: TFillData } & SamplerNoOperationCall) {
|
||||
this.source = opts.source;
|
||||
this.fillData = opts.fillData || ({} as TFillData); // tslint:disable-line:no-object-literal-type-assertion
|
||||
this._callback = opts.callback;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
public encodeCall(): string {
|
||||
return NULL_BYTES;
|
||||
}
|
||||
public handleCallResults(_callResults: string): BigNumber[] {
|
||||
return this._callback();
|
||||
}
|
||||
public handleRevert(_callResults: string): BigNumber[] {
|
||||
logUtils.warn(`SamplerNoOperation: ${this.source} reverted`);
|
||||
return [];
|
||||
}
|
||||
}
|
@@ -3,9 +3,11 @@ import { LimitOrderFields } from '@0x/protocol-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { AaveV2Sampler } from '../../noop_samplers/AaveV2Sampler';
|
||||
import { SamplerCallResult, SignedNativeOrder } from '../../types';
|
||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||
|
||||
import { AaveV2ReservesCache } from './aave_reserves_cache';
|
||||
import { BancorService } from './bancor_service';
|
||||
import {
|
||||
getCurveLikeInfosForPair,
|
||||
@@ -17,10 +19,14 @@ import {
|
||||
isValidAddress,
|
||||
uniswapV2LikeRouterAddress,
|
||||
} from './bridge_source_utils';
|
||||
import { CompoundCTokenCache } from './compound_ctoken_cache';
|
||||
import {
|
||||
AAVE_V2_SUBGRAPH_URL_BY_CHAIN_ID,
|
||||
BALANCER_V2_VAULT_ADDRESS_BY_CHAIN,
|
||||
BANCOR_REGISTRY_BY_CHAIN_ID,
|
||||
CLIPPER_INFO_BY_CHAIN,
|
||||
BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN,
|
||||
BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN,
|
||||
COMPOUND_API_URL_BY_CHAIN_ID,
|
||||
DODOV1_CONFIG_BY_CHAIN_ID,
|
||||
DODOV2_FACTORIES_BY_CHAIN_ID,
|
||||
KYBER_CONFIG_BY_CHAIN_ID,
|
||||
@@ -44,13 +50,17 @@ import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
|
||||
import { getIntermediateTokens } from './multihop_utils';
|
||||
import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache, PoolsCache } from './pools_cache';
|
||||
import { SamplerContractOperation } from './sampler_contract_operation';
|
||||
import { SamplerNoOperation } from './sampler_no_operation';
|
||||
import { SourceFilters } from './source_filters';
|
||||
import {
|
||||
AaveV2FillData,
|
||||
AaveV2Info,
|
||||
BalancerFillData,
|
||||
BalancerV2FillData,
|
||||
BalancerV2PoolInfo,
|
||||
BancorFillData,
|
||||
BatchedOperation,
|
||||
CompoundFillData,
|
||||
CurveFillData,
|
||||
CurveInfo,
|
||||
DexSample,
|
||||
@@ -98,6 +108,8 @@ export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSour
|
||||
export class SamplerOperations {
|
||||
public readonly liquidityProviderRegistry: LiquidityProviderRegistry;
|
||||
public readonly poolsCaches: { [key in SourcesWithPoolsCache]: PoolsCache };
|
||||
public readonly aaveReservesCache: AaveV2ReservesCache | undefined;
|
||||
public readonly compoundCTokenCache: CompoundCTokenCache | undefined;
|
||||
protected _bancorService?: BancorService;
|
||||
public static constant<T>(result: T): BatchedOperation<T> {
|
||||
return {
|
||||
@@ -123,9 +135,26 @@ export class SamplerOperations {
|
||||
? poolsCaches
|
||||
: {
|
||||
[ERC20BridgeSource.BalancerV2]: new BalancerV2PoolsCache(chainId),
|
||||
[ERC20BridgeSource.Beethovenx]: new BalancerV2PoolsCache(
|
||||
chainId,
|
||||
BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN[chainId],
|
||||
),
|
||||
[ERC20BridgeSource.Balancer]: new BalancerPoolsCache(),
|
||||
[ERC20BridgeSource.Cream]: new CreamPoolsCache(),
|
||||
};
|
||||
|
||||
const aaveSubgraphUrl = AAVE_V2_SUBGRAPH_URL_BY_CHAIN_ID[chainId];
|
||||
if (aaveSubgraphUrl) {
|
||||
this.aaveReservesCache = new AaveV2ReservesCache(aaveSubgraphUrl);
|
||||
}
|
||||
|
||||
const compoundApiUrl = COMPOUND_API_URL_BY_CHAIN_ID[chainId];
|
||||
if (compoundApiUrl) {
|
||||
this.compoundCTokenCache = new CompoundCTokenCache(
|
||||
compoundApiUrl,
|
||||
NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId],
|
||||
);
|
||||
}
|
||||
// Initialize the Bancor service, fetching paths in the background
|
||||
bancorServiceFn()
|
||||
.then(service => (this._bancorService = service))
|
||||
@@ -153,6 +182,30 @@ export class SamplerOperations {
|
||||
};
|
||||
}
|
||||
|
||||
public getGasLeft(): BatchedOperation<BigNumber> {
|
||||
return {
|
||||
encodeCall: () => this._samplerContract.getGasLeft().getABIEncodedTransactionData(),
|
||||
handleCallResults: (callResults: string) =>
|
||||
this._samplerContract.getABIDecodedReturnData<BigNumber>('getGasLeft', callResults),
|
||||
handleRevert: () => {
|
||||
/* should never happen */
|
||||
throw new Error('Invalid result for getGasLeft');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public getBlockNumber(): BatchedOperation<BigNumber> {
|
||||
return {
|
||||
encodeCall: () => this._samplerContract.getBlockNumber().getABIEncodedTransactionData(),
|
||||
handleCallResults: (callResults: string) =>
|
||||
this._samplerContract.getABIDecodedReturnData<BigNumber>('getBlockNumber', callResults),
|
||||
handleRevert: () => {
|
||||
/* should never happen */
|
||||
throw new Error('Invalid result for getBlockNumber');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public getLimitOrderFillableTakerAmounts(
|
||||
orders: SignedNativeOrder[],
|
||||
exchangeAddress: string,
|
||||
@@ -1070,6 +1123,64 @@ export class SamplerOperations {
|
||||
});
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
public getAaveV2SellQuotes(
|
||||
aaveInfo: AaveV2Info,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
): SourceQuoteOperation<AaveV2FillData> {
|
||||
return new SamplerNoOperation({
|
||||
source: ERC20BridgeSource.AaveV2,
|
||||
fillData: { ...aaveInfo, takerToken },
|
||||
callback: () => AaveV2Sampler.sampleSellsFromAaveV2(aaveInfo, takerToken, makerToken, takerFillAmounts),
|
||||
});
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
public getAaveV2BuyQuotes(
|
||||
aaveInfo: AaveV2Info,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
): SourceQuoteOperation<AaveV2FillData> {
|
||||
return new SamplerNoOperation({
|
||||
source: ERC20BridgeSource.AaveV2,
|
||||
fillData: { ...aaveInfo, takerToken },
|
||||
callback: () => AaveV2Sampler.sampleBuysFromAaveV2(aaveInfo, takerToken, makerToken, makerFillAmounts),
|
||||
});
|
||||
}
|
||||
|
||||
public getCompoundSellQuotes(
|
||||
cToken: string,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
): SourceQuoteOperation<CompoundFillData> {
|
||||
return new SamplerContractOperation({
|
||||
source: ERC20BridgeSource.Compound,
|
||||
fillData: { cToken, takerToken, makerToken },
|
||||
contract: this._samplerContract,
|
||||
function: this._samplerContract.sampleSellsFromCompound,
|
||||
params: [cToken, takerToken, makerToken, takerFillAmounts],
|
||||
});
|
||||
}
|
||||
|
||||
public getCompoundBuyQuotes(
|
||||
cToken: string,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
): SourceQuoteOperation<CompoundFillData> {
|
||||
return new SamplerContractOperation({
|
||||
source: ERC20BridgeSource.Compound,
|
||||
fillData: { cToken, takerToken, makerToken },
|
||||
contract: this._samplerContract,
|
||||
function: this._samplerContract.sampleBuysFromCompound,
|
||||
params: [cToken, takerToken, makerToken, makerFillAmounts],
|
||||
});
|
||||
}
|
||||
|
||||
public getMedianSellRate(
|
||||
sources: ERC20BridgeSource[],
|
||||
makerToken: string,
|
||||
@@ -1196,6 +1307,12 @@ export class SamplerOperations {
|
||||
case ERC20BridgeSource.Polydex:
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
case ERC20BridgeSource.JetSwap:
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
case ERC20BridgeSource.SpiritSwap:
|
||||
case ERC20BridgeSource.SpookySwap:
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
const uniLikeRouter = uniswapV2LikeRouterAddress(this.chainId, source);
|
||||
if (!isValidAddress(uniLikeRouter)) {
|
||||
return [];
|
||||
@@ -1297,13 +1414,14 @@ export class SamplerOperations {
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.BalancerV2:
|
||||
case ERC20BridgeSource.Beethovenx:
|
||||
const poolIds =
|
||||
this.poolsCaches[ERC20BridgeSource.BalancerV2].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || [];
|
||||
this.poolsCaches[source].getCachedPoolAddressesForPair(takerToken, makerToken) || [];
|
||||
|
||||
const vault = BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
||||
const vault =
|
||||
source === ERC20BridgeSource.BalancerV2
|
||||
? BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId]
|
||||
: BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
||||
if (vault === NULL_ADDRESS) {
|
||||
return [];
|
||||
}
|
||||
@@ -1313,10 +1431,9 @@ export class SamplerOperations {
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerFillAmounts,
|
||||
ERC20BridgeSource.BalancerV2,
|
||||
source,
|
||||
),
|
||||
);
|
||||
|
||||
case ERC20BridgeSource.Cream:
|
||||
return (
|
||||
this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair(
|
||||
@@ -1414,32 +1531,38 @@ export class SamplerOperations {
|
||||
|
||||
return this.getLidoSellQuotes(lidoInfo, makerToken, takerToken, takerFillAmounts);
|
||||
}
|
||||
case ERC20BridgeSource.Clipper:
|
||||
const { poolAddress: clipperPoolAddress, tokens: clipperTokens } = CLIPPER_INFO_BY_CHAIN[
|
||||
this.chainId
|
||||
];
|
||||
if (
|
||||
clipperPoolAddress === NULL_ADDRESS ||
|
||||
!clipperTokens.includes(makerToken) ||
|
||||
!clipperTokens.includes(takerToken)
|
||||
) {
|
||||
case ERC20BridgeSource.AaveV2: {
|
||||
if (!this.aaveReservesCache) {
|
||||
return [];
|
||||
}
|
||||
// Clipper requires WETH to be represented as address(0)
|
||||
const adjustedMakerToken =
|
||||
makerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : makerToken;
|
||||
const adjustedTakerToken =
|
||||
takerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : takerToken;
|
||||
// Supports the PLP interface
|
||||
return this.getLiquidityProviderSellQuotes(
|
||||
clipperPoolAddress,
|
||||
adjustedMakerToken,
|
||||
adjustedTakerToken,
|
||||
const reserve = this.aaveReservesCache.get(takerToken, makerToken);
|
||||
if (!reserve) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const info: AaveV2Info = {
|
||||
lendingPool: reserve.pool.lendingPool,
|
||||
aToken: reserve.aToken.id,
|
||||
underlyingToken: reserve.underlyingAsset,
|
||||
};
|
||||
return this.getAaveV2SellQuotes(info, makerToken, takerToken, takerFillAmounts);
|
||||
}
|
||||
case ERC20BridgeSource.Compound: {
|
||||
if (!this.compoundCTokenCache) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const cToken = this.compoundCTokenCache.get(takerToken, makerToken);
|
||||
if (!cToken) {
|
||||
return [];
|
||||
}
|
||||
return this.getCompoundSellQuotes(
|
||||
cToken.tokenAddress,
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerFillAmounts,
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
0, // Not used for Clipper
|
||||
ERC20BridgeSource.Clipper,
|
||||
);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported sell sample source: ${source}`);
|
||||
}
|
||||
@@ -1489,6 +1612,12 @@ export class SamplerOperations {
|
||||
case ERC20BridgeSource.Polydex:
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
case ERC20BridgeSource.JetSwap:
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
case ERC20BridgeSource.SpiritSwap:
|
||||
case ERC20BridgeSource.SpookySwap:
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
const uniLikeRouter = uniswapV2LikeRouterAddress(this.chainId, source);
|
||||
if (!isValidAddress(uniLikeRouter)) {
|
||||
return [];
|
||||
@@ -1590,13 +1719,14 @@ export class SamplerOperations {
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.BalancerV2:
|
||||
case ERC20BridgeSource.Beethovenx:
|
||||
const poolIds =
|
||||
this.poolsCaches[ERC20BridgeSource.BalancerV2].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || [];
|
||||
this.poolsCaches[source].getCachedPoolAddressesForPair(takerToken, makerToken) || [];
|
||||
|
||||
const vault = BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
||||
const vault =
|
||||
source === ERC20BridgeSource.BalancerV2
|
||||
? BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId]
|
||||
: BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
||||
if (vault === NULL_ADDRESS) {
|
||||
return [];
|
||||
}
|
||||
@@ -1606,7 +1736,7 @@ export class SamplerOperations {
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerFillAmounts,
|
||||
ERC20BridgeSource.BalancerV2,
|
||||
source,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.Cream:
|
||||
@@ -1702,32 +1832,32 @@ export class SamplerOperations {
|
||||
|
||||
return this.getLidoBuyQuotes(lidoInfo, makerToken, takerToken, makerFillAmounts);
|
||||
}
|
||||
case ERC20BridgeSource.Clipper:
|
||||
const { poolAddress: clipperPoolAddress, tokens: clipperTokens } = CLIPPER_INFO_BY_CHAIN[
|
||||
this.chainId
|
||||
];
|
||||
if (
|
||||
clipperPoolAddress === NULL_ADDRESS ||
|
||||
!clipperTokens.includes(makerToken) ||
|
||||
!clipperTokens.includes(takerToken)
|
||||
) {
|
||||
case ERC20BridgeSource.AaveV2: {
|
||||
if (!this.aaveReservesCache) {
|
||||
return [];
|
||||
}
|
||||
// Clipper requires WETH to be represented as address(0)
|
||||
const adjustedMakerToken =
|
||||
makerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : makerToken;
|
||||
const adjustedTakerToken =
|
||||
takerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : takerToken;
|
||||
// Supports the PLP interface
|
||||
return this.getLiquidityProviderBuyQuotes(
|
||||
clipperPoolAddress,
|
||||
adjustedMakerToken,
|
||||
adjustedTakerToken,
|
||||
makerFillAmounts,
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
0, // Not used for Clipper
|
||||
ERC20BridgeSource.Clipper,
|
||||
);
|
||||
const reserve = this.aaveReservesCache.get(takerToken, makerToken);
|
||||
if (!reserve) {
|
||||
return [];
|
||||
}
|
||||
const info: AaveV2Info = {
|
||||
lendingPool: reserve.pool.lendingPool,
|
||||
aToken: reserve.aToken.id,
|
||||
underlyingToken: reserve.underlyingAsset,
|
||||
};
|
||||
return this.getAaveV2BuyQuotes(info, makerToken, takerToken, makerFillAmounts);
|
||||
}
|
||||
case ERC20BridgeSource.Compound: {
|
||||
if (!this.compoundCTokenCache) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const cToken = this.compoundCTokenCache.get(takerToken, makerToken);
|
||||
if (!cToken) {
|
||||
return [];
|
||||
}
|
||||
return this.getCompoundBuyQuotes(cToken.tokenAddress, makerToken, takerToken, makerFillAmounts);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported buy sample source: ${source}`);
|
||||
}
|
||||
|
@@ -3,13 +3,12 @@ import {
|
||||
FillQuoteTransformerOrderType,
|
||||
FillQuoteTransformerRfqOrderInfo,
|
||||
} from '@0x/protocol-utils';
|
||||
import { V4RFQIndicativeQuote } from '@0x/quote-server';
|
||||
import { MarketOperation } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { NativeOrderWithFillableAmounts, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types';
|
||||
import { QuoteRequestor } from '../../utils/quote_requestor';
|
||||
import { PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
|
||||
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from '../../utils/quote_requestor';
|
||||
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
|
||||
|
||||
import { CollapsedPath } from './path';
|
||||
import { SourceFilters } from './source_filters';
|
||||
@@ -69,7 +68,8 @@ export enum ERC20BridgeSource {
|
||||
CurveV2 = 'Curve_V2',
|
||||
Lido = 'Lido',
|
||||
ShibaSwap = 'ShibaSwap',
|
||||
Clipper = 'Clipper',
|
||||
AaveV2 = 'Aave_V2',
|
||||
Compound = 'Compound',
|
||||
// BSC only
|
||||
PancakeSwap = 'PancakeSwap',
|
||||
PancakeSwapV2 = 'PancakeSwap_V2',
|
||||
@@ -91,8 +91,22 @@ export enum ERC20BridgeSource {
|
||||
FirebirdOneSwap = 'FirebirdOneSwap',
|
||||
JetSwap = 'JetSwap',
|
||||
IronSwap = 'IronSwap',
|
||||
// Avalanche
|
||||
Pangolin = 'Pangolin',
|
||||
TraderJoe = 'TraderJoe',
|
||||
// Celo only
|
||||
UbeSwap = 'UbeSwap',
|
||||
// Fantom
|
||||
SpiritSwap = 'SpiritSwap',
|
||||
SpookySwap = 'SpookySwap',
|
||||
Beethovenx = 'Beethovenx',
|
||||
MorpheusSwap = 'MorpheusSwap',
|
||||
}
|
||||
export type SourcesWithPoolsCache = ERC20BridgeSource.Balancer | ERC20BridgeSource.BalancerV2 | ERC20BridgeSource.Cream;
|
||||
export type SourcesWithPoolsCache =
|
||||
| ERC20BridgeSource.Balancer
|
||||
| ERC20BridgeSource.BalancerV2
|
||||
| ERC20BridgeSource.Beethovenx
|
||||
| ERC20BridgeSource.Cream;
|
||||
|
||||
// tslint:disable: enum-naming
|
||||
/**
|
||||
@@ -104,8 +118,10 @@ export enum CurveFunctionSelectors {
|
||||
exchange_underlying = '0xa6417ed6',
|
||||
get_dy_underlying = '0x07211ef7',
|
||||
get_dx_underlying = '0x0e71d1b9',
|
||||
get_dy = '0x5e0d443f',
|
||||
get_dy = '0x5e0d443f', // get_dy(int128,int128,uint256)
|
||||
get_dx = '0x67df02ca',
|
||||
get_dy_uint256 = '0x556d6e9f', // get_dy(uint256,uint256,uint256)
|
||||
exchange_underlying_uint256 = '0x65b2489b', // exchange_underlying(uint256,uint256,uint256,uint256)
|
||||
// Curve V2
|
||||
exchange_v2 = '0x5b41b908',
|
||||
exchange_underlying_v2 = '0x65b2489b',
|
||||
@@ -158,6 +174,12 @@ export interface BalancerV2PoolInfo {
|
||||
vault: string;
|
||||
}
|
||||
|
||||
export interface AaveV2Info {
|
||||
lendingPool: string;
|
||||
aToken: string;
|
||||
underlyingToken: string;
|
||||
}
|
||||
|
||||
// Internal `fillData` field for `Fill` objects.
|
||||
export interface FillData {}
|
||||
|
||||
@@ -265,6 +287,19 @@ export interface LidoFillData extends FillData {
|
||||
takerToken: string;
|
||||
}
|
||||
|
||||
export interface AaveV2FillData extends FillData {
|
||||
lendingPool: string;
|
||||
aToken: string;
|
||||
underlyingToken: string;
|
||||
takerToken: string;
|
||||
}
|
||||
|
||||
export interface CompoundFillData extends FillData {
|
||||
cToken: string;
|
||||
takerToken: string;
|
||||
makerToken: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a node on a fill path.
|
||||
*/
|
||||
@@ -420,6 +455,10 @@ export interface GetMarketOrdersOpts {
|
||||
* Default: 1.25.
|
||||
*/
|
||||
sampleDistributionBase: number;
|
||||
/**
|
||||
* Number of samples to use when creating fill curves with neon-router
|
||||
*/
|
||||
neonRouterNumSamples: number;
|
||||
/**
|
||||
* Fees for each liquidity source, expressed in gas.
|
||||
*/
|
||||
@@ -452,6 +491,42 @@ export interface GetMarketOrdersOpts {
|
||||
* hopping to. E.g DAI->USDC via an adjacent token WETH
|
||||
*/
|
||||
tokenAdjacencyGraph: TokenAdjacencyGraph;
|
||||
|
||||
/**
|
||||
* Gas price to use for quote
|
||||
*/
|
||||
gasPrice: BigNumber;
|
||||
|
||||
/**
|
||||
* Sampler metrics for recording data on the sampler service and operations
|
||||
*/
|
||||
samplerMetrics?: SamplerMetrics;
|
||||
}
|
||||
|
||||
export interface SamplerMetrics {
|
||||
/**
|
||||
* Logs the gas information performed during a sampler call.
|
||||
*
|
||||
* @param data.gasBefore The gas remaining measured before any operations have been performed
|
||||
* @param data.gasAfter The gas remaining measured after all operations have been performed
|
||||
*/
|
||||
logGasDetails(data: { gasBefore: BigNumber; gasAfter: BigNumber }): void;
|
||||
|
||||
/**
|
||||
* Logs the block number
|
||||
*
|
||||
* @param blockNumber block number of the sampler call
|
||||
*/
|
||||
logBlockNumber(blockNumber: BigNumber): void;
|
||||
|
||||
/**
|
||||
* Logs the routing timings
|
||||
*
|
||||
* @param data.router The router type (neon-router or js)
|
||||
* @param data.type The type of timing being recorded (e.g total timing, all sources timing or vip timing)
|
||||
* @param data.timingMs The timing in milliseconds
|
||||
*/
|
||||
logRouterDetails(data: { router: 'neon-router' | 'js'; type: 'all' | 'vip' | 'total'; timingMs: number }): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -481,6 +556,7 @@ export interface OptimizerResult {
|
||||
|
||||
export interface OptimizerResultWithReport extends OptimizerResult {
|
||||
quoteReport?: QuoteReport;
|
||||
extendedQuoteReportSources?: ExtendedQuoteReportSources;
|
||||
priceComparisonsReport?: PriceComparisonsReport;
|
||||
}
|
||||
|
||||
@@ -509,7 +585,7 @@ export interface MarketSideLiquidity {
|
||||
|
||||
export interface RawQuotes {
|
||||
nativeOrders: NativeOrderWithFillableAmounts[];
|
||||
rfqtIndicativeQuotes: V4RFQIndicativeQuote[];
|
||||
rfqtIndicativeQuotes: V4RFQIndicativeQuoteMM[];
|
||||
twoHopQuotes: Array<DexSample<MultiHopFillData>>;
|
||||
dexQuotes: Array<Array<DexSample<FillData>>>;
|
||||
}
|
||||
@@ -531,10 +607,13 @@ export interface GenerateOptimizedOrdersOpts {
|
||||
bridgeSlippage?: number;
|
||||
maxFallbackSlippage?: number;
|
||||
excludedSources?: ERC20BridgeSource[];
|
||||
feeSchedule?: FeeSchedule;
|
||||
feeSchedule: FeeSchedule;
|
||||
exchangeProxyOverhead?: ExchangeProxyOverhead;
|
||||
allowFallback?: boolean;
|
||||
shouldBatchBridgeOrders?: boolean;
|
||||
gasPrice: BigNumber;
|
||||
neonRouterNumSamples: number;
|
||||
samplerMetrics?: SamplerMetrics;
|
||||
}
|
||||
|
||||
export interface ComparisonPrice {
|
||||
|
@@ -14,8 +14,9 @@ import {
|
||||
NativeFillData,
|
||||
NativeLimitOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
RawQuotes,
|
||||
} from './market_operation_utils/types';
|
||||
import { QuoteRequestor } from './quote_requestor';
|
||||
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from './quote_requestor';
|
||||
|
||||
export interface QuoteReportEntryBase {
|
||||
liquiditySource: ERC20BridgeSource;
|
||||
@@ -36,30 +37,77 @@ export interface NativeLimitOrderQuoteReportEntry extends QuoteReportEntryBase {
|
||||
liquiditySource: ERC20BridgeSource.Native;
|
||||
fillData: NativeFillData;
|
||||
fillableTakerAmount: BigNumber;
|
||||
isRfqt: false;
|
||||
isRFQ: false;
|
||||
}
|
||||
|
||||
export interface NativeRfqOrderQuoteReportEntry extends QuoteReportEntryBase {
|
||||
liquiditySource: ERC20BridgeSource.Native;
|
||||
fillData: NativeFillData;
|
||||
fillableTakerAmount: BigNumber;
|
||||
isRfqt: true;
|
||||
isRFQ: true;
|
||||
nativeOrder: RfqOrderFields;
|
||||
makerUri: string;
|
||||
comparisonPrice?: number;
|
||||
}
|
||||
|
||||
export interface IndicativeRfqOrderQuoteReportEntry extends QuoteReportEntryBase {
|
||||
liquiditySource: ERC20BridgeSource.Native;
|
||||
fillableTakerAmount: BigNumber;
|
||||
isRFQ: true;
|
||||
makerUri?: string;
|
||||
comparisonPrice?: number;
|
||||
}
|
||||
|
||||
export type QuoteReportEntry =
|
||||
| BridgeQuoteReportEntry
|
||||
| MultiHopQuoteReportEntry
|
||||
| NativeLimitOrderQuoteReportEntry
|
||||
| NativeRfqOrderQuoteReportEntry;
|
||||
|
||||
export type ExtendedQuoteReportEntry =
|
||||
| BridgeQuoteReportEntry
|
||||
| MultiHopQuoteReportEntry
|
||||
| NativeLimitOrderQuoteReportEntry
|
||||
| NativeRfqOrderQuoteReportEntry
|
||||
| IndicativeRfqOrderQuoteReportEntry;
|
||||
|
||||
export type ExtendedQuoteReportIndexedEntry = ExtendedQuoteReportEntry & {
|
||||
quoteEntryIndex: number;
|
||||
isDelivered: boolean;
|
||||
};
|
||||
|
||||
export type ExtendedQuoteReportIndexedEntryOutbound = Omit<ExtendedQuoteReportIndexedEntry, 'fillData'> & {
|
||||
fillData?: string;
|
||||
};
|
||||
|
||||
export interface QuoteReport {
|
||||
sourcesConsidered: QuoteReportEntry[];
|
||||
sourcesDelivered: QuoteReportEntry[];
|
||||
}
|
||||
|
||||
export interface ExtendedQuoteReportSources {
|
||||
sourcesConsidered: ExtendedQuoteReportIndexedEntry[];
|
||||
sourcesDelivered: ExtendedQuoteReportIndexedEntry[] | undefined;
|
||||
}
|
||||
|
||||
export interface ExtendedQuoteReport {
|
||||
quoteId?: string;
|
||||
taker?: string;
|
||||
timestamp: number;
|
||||
firmQuoteReport: boolean;
|
||||
submissionBy: 'taker' | 'metaTxn' | 'rfqm';
|
||||
buyAmount?: string;
|
||||
sellAmount?: string;
|
||||
buyTokenAddress: string;
|
||||
sellTokenAddress: string;
|
||||
integratorId?: string;
|
||||
slippageBips?: number;
|
||||
zeroExTransactionHash?: string;
|
||||
decodedUniqueId?: string;
|
||||
sourcesConsidered: ExtendedQuoteReportIndexedEntryOutbound[];
|
||||
sourcesDelivered: ExtendedQuoteReportIndexedEntryOutbound[] | undefined;
|
||||
}
|
||||
|
||||
export interface PriceComparisonsReport {
|
||||
dexSources: BridgeQuoteReportEntry[];
|
||||
multiHopSources: MultiHopQuoteReportEntry[];
|
||||
@@ -80,7 +128,7 @@ export function generateQuoteReport(
|
||||
const nativeOrderSourcesConsidered = nativeOrders.map(order =>
|
||||
nativeOrderToReportEntry(order.type, order as any, order.fillableTakerAmount, comparisonPrice, quoteRequestor),
|
||||
);
|
||||
const sourcesConsidered = [...nativeOrderSourcesConsidered.filter(order => order.isRfqt)];
|
||||
const sourcesConsidered = [...nativeOrderSourcesConsidered.filter(order => order.isRFQ)];
|
||||
|
||||
let sourcesDelivered;
|
||||
if (Array.isArray(liquidityDelivered)) {
|
||||
@@ -116,6 +164,105 @@ export function generateQuoteReport(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a report of sources considered while computing the optimized
|
||||
* swap quote, the sources ultimately included in the computed quote. This
|
||||
* extende version incudes all considered quotes, not only native liquidity.
|
||||
*/
|
||||
export function generateExtendedQuoteReportSources(
|
||||
marketOperation: MarketOperation,
|
||||
quotes: RawQuotes,
|
||||
liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
|
||||
amount: BigNumber,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
quoteRequestor?: QuoteRequestor,
|
||||
): ExtendedQuoteReportSources {
|
||||
const sourcesConsidered: ExtendedQuoteReportEntry[] = [];
|
||||
|
||||
// NativeOrders
|
||||
sourcesConsidered.push(
|
||||
...quotes.nativeOrders.map(order =>
|
||||
nativeOrderToReportEntry(
|
||||
order.type,
|
||||
order as any,
|
||||
order.fillableTakerAmount,
|
||||
comparisonPrice,
|
||||
quoteRequestor,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// IndicativeQuotes
|
||||
sourcesConsidered.push(
|
||||
...quotes.rfqtIndicativeQuotes.map(order => indicativeQuoteToReportEntry(order, comparisonPrice)),
|
||||
);
|
||||
|
||||
// MultiHop
|
||||
sourcesConsidered.push(...quotes.twoHopQuotes.map(quote => multiHopSampleToReportSource(quote, marketOperation)));
|
||||
|
||||
// Dex Quotes
|
||||
sourcesConsidered.push(
|
||||
..._.flatten(
|
||||
quotes.dexQuotes.map(dex =>
|
||||
dex
|
||||
.filter(quote => isDexSampleForTotalAmount(quote, marketOperation, amount))
|
||||
.map(quote => dexSampleToReportSource(quote, marketOperation)),
|
||||
),
|
||||
),
|
||||
);
|
||||
const sourcesConsideredIndexed = sourcesConsidered.map(
|
||||
(quote, index): ExtendedQuoteReportIndexedEntry => {
|
||||
return {
|
||||
...quote,
|
||||
quoteEntryIndex: index,
|
||||
isDelivered: false,
|
||||
};
|
||||
},
|
||||
);
|
||||
let sourcesDelivered;
|
||||
if (Array.isArray(liquidityDelivered)) {
|
||||
// create easy way to look up fillable amounts
|
||||
const nativeOrderSignaturesToFillableAmounts = _.fromPairs(
|
||||
quotes.nativeOrders.map(o => {
|
||||
return [_nativeDataToId(o), o.fillableTakerAmount];
|
||||
}),
|
||||
);
|
||||
// map sources delivered
|
||||
sourcesDelivered = liquidityDelivered.map(collapsedFill => {
|
||||
if (_isNativeOrderFromCollapsedFill(collapsedFill)) {
|
||||
return nativeOrderToReportEntry(
|
||||
collapsedFill.type,
|
||||
collapsedFill.fillData,
|
||||
nativeOrderSignaturesToFillableAmounts[_nativeDataToId(collapsedFill.fillData)],
|
||||
comparisonPrice,
|
||||
quoteRequestor,
|
||||
);
|
||||
} else {
|
||||
return dexSampleToReportSource(collapsedFill, marketOperation);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sourcesDelivered = [
|
||||
// tslint:disable-next-line: no-unnecessary-type-assertion
|
||||
multiHopSampleToReportSource(liquidityDelivered as DexSample<MultiHopFillData>, marketOperation),
|
||||
];
|
||||
}
|
||||
const sourcesDeliveredIndexed = sourcesDelivered.map(
|
||||
(quote, index): ExtendedQuoteReportIndexedEntry => {
|
||||
return {
|
||||
...quote,
|
||||
quoteEntryIndex: index,
|
||||
isDelivered: false,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
sourcesConsidered: sourcesConsideredIndexed,
|
||||
sourcesDelivered: sourcesDeliveredIndexed,
|
||||
};
|
||||
}
|
||||
|
||||
function _nativeDataToId(data: { signature: Signature }): string {
|
||||
const { v, r, s } = data.signature;
|
||||
return `${v}${r}${s}`;
|
||||
@@ -153,6 +300,22 @@ export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a DEX sample is the one that represents the whole amount requested by taker
|
||||
* NOTE: this is used for the QuoteReport to filter samples
|
||||
*/
|
||||
function isDexSampleForTotalAmount(ds: DexSample, marketOperation: MarketOperation, amount: BigNumber): boolean {
|
||||
// input and output map to different values
|
||||
// based on the market operation
|
||||
if (marketOperation === MarketOperation.Buy) {
|
||||
return ds.input === amount;
|
||||
} else if (marketOperation === MarketOperation.Sell) {
|
||||
return ds.output === amount;
|
||||
} else {
|
||||
throw new Error(`Unexpected marketOperation ${marketOperation}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a report sample for a MultiHop source
|
||||
* NOTE: this is used for the QuoteReport and quote price comparison data
|
||||
@@ -208,17 +371,17 @@ export function nativeOrderToReportEntry(
|
||||
};
|
||||
|
||||
// if we find this is an rfqt order, label it as such and associate makerUri
|
||||
const isRfqt = type === FillQuoteTransformerOrderType.Rfq;
|
||||
const isRFQ = type === FillQuoteTransformerOrderType.Rfq;
|
||||
const rfqtMakerUri =
|
||||
isRfqt && quoteRequestor ? quoteRequestor.getMakerUriForSignature(fillData.signature) : undefined;
|
||||
isRFQ && quoteRequestor ? quoteRequestor.getMakerUriForSignature(fillData.signature) : undefined;
|
||||
|
||||
if (isRfqt) {
|
||||
if (isRFQ) {
|
||||
const nativeOrder = fillData.order as RfqOrderFields;
|
||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
||||
return {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
...nativeOrderBase,
|
||||
isRfqt: true,
|
||||
isRFQ: true,
|
||||
makerUri: rfqtMakerUri || '',
|
||||
...(comparisonPrice ? { comparisonPrice: comparisonPrice.toNumber() } : {}),
|
||||
nativeOrder,
|
||||
@@ -229,8 +392,49 @@ export function nativeOrderToReportEntry(
|
||||
return {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
...nativeOrderBase,
|
||||
isRfqt: false,
|
||||
isRFQ: false,
|
||||
fillData,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a report entry for an indicative RFQ Quote
|
||||
* NOTE: this is used for the QuoteReport and quote price comparison data
|
||||
*/
|
||||
export function indicativeQuoteToReportEntry(
|
||||
order: V4RFQIndicativeQuoteMM,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
): IndicativeRfqOrderQuoteReportEntry {
|
||||
const nativeOrderBase = {
|
||||
makerAmount: order.makerAmount,
|
||||
takerAmount: order.takerAmount,
|
||||
fillableTakerAmount: order.takerAmount,
|
||||
};
|
||||
|
||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
||||
return {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
...nativeOrderBase,
|
||||
isRFQ: true,
|
||||
makerUri: order.makerUri,
|
||||
fillData: {},
|
||||
...(comparisonPrice ? { comparisonPrice: comparisonPrice.toNumber() } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* For the extended quote report, we output the filldata as JSON
|
||||
*/
|
||||
export function jsonifyFillData(source: ExtendedQuoteReportIndexedEntry): ExtendedQuoteReportIndexedEntryOutbound {
|
||||
return {
|
||||
...source,
|
||||
fillData: JSON.stringify(source.fillData, (key: string, value: any) => {
|
||||
if (key === '_samplerContract') {
|
||||
return {};
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ import { constants } from '../constants';
|
||||
import {
|
||||
AltQuoteModel,
|
||||
AltRfqMakerAssetOfferings,
|
||||
Integrator,
|
||||
LogFunction,
|
||||
MarketOperation,
|
||||
RfqMakerAssetOfferings,
|
||||
@@ -38,6 +39,10 @@ interface RfqQuote<T> {
|
||||
makerUri: string;
|
||||
}
|
||||
|
||||
export interface V4RFQIndicativeQuoteMM extends V4RFQIndicativeQuote {
|
||||
makerUri: string;
|
||||
}
|
||||
|
||||
export interface MetricsProxy {
|
||||
/**
|
||||
* Increments a counter that is tracking valid Firm Quotes that are dropped due to low expiration.
|
||||
@@ -61,6 +66,31 @@ export interface MetricsProxy {
|
||||
* @param expirationTimeSeconds the expiration time in seconds
|
||||
*/
|
||||
incrementFillRatioWarningCounter(isLastLook: boolean, maker: string): void;
|
||||
|
||||
/**
|
||||
* Logs the outcome of a network (HTTP) interaction with a market maker.
|
||||
*
|
||||
* @param interaction.isLastLook true if the request is RFQM
|
||||
* @param interaction.integrator the integrator that is requesting the RFQ quote
|
||||
* @param interaction.url the URL of the market maker
|
||||
* @param interaction.quoteType indicative or firm quote
|
||||
* @param interaction.statusCode the statusCode returned by a market maker
|
||||
* @param interaction.latencyMs the latency of the HTTP request (in ms)
|
||||
* @param interaction.included if a firm quote that was returned got included in the next step of processing.
|
||||
* NOTE: this does not mean that the request returned a valid fillable order. It just
|
||||
* means that the network response was successful.
|
||||
*/
|
||||
logRfqMakerNetworkInteraction(interaction: {
|
||||
isLastLook: boolean;
|
||||
integrator: Integrator;
|
||||
url: string;
|
||||
quoteType: 'firm' | 'indicative';
|
||||
statusCode: number | undefined;
|
||||
latencyMs: number;
|
||||
included: boolean;
|
||||
sellTokenAddress: string;
|
||||
buyTokenAddress: string;
|
||||
}): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,6 +208,42 @@ export class QuoteRequestor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets both standard RFQ makers and "alternative" RFQ makers and combines them together
|
||||
* in a single configuration map. If an integration key whitelist is present, it will be used
|
||||
* to filter a specific makers.
|
||||
*
|
||||
* @param options the RfqmRequestOptions passed in
|
||||
* @param assetOfferings the RFQM or RFQT maker offerings
|
||||
* @returns a list of TypedMakerUrl instances
|
||||
*/
|
||||
public static getTypedMakerUrlsAndWhitelist(
|
||||
options: Pick<RfqmRequestOptions, 'integrator' | 'altRfqAssetOfferings'>,
|
||||
assetOfferings: RfqMakerAssetOfferings,
|
||||
): TypedMakerUrl[] {
|
||||
const standardUrls = Object.keys(assetOfferings).map(
|
||||
(mm: string): TypedMakerUrl => {
|
||||
return { pairType: RfqPairType.Standard, url: mm };
|
||||
},
|
||||
);
|
||||
const altUrls = options.altRfqAssetOfferings
|
||||
? Object.keys(options.altRfqAssetOfferings).map(
|
||||
(mm: string): TypedMakerUrl => {
|
||||
return { pairType: RfqPairType.Alt, url: mm };
|
||||
},
|
||||
)
|
||||
: [];
|
||||
|
||||
let typedMakerUrls = standardUrls.concat(altUrls);
|
||||
|
||||
// If there is a whitelist, only allow approved maker URLs
|
||||
if (options.integrator.whitelistIntegratorUrls !== undefined) {
|
||||
const whitelist = new Set(options.integrator.whitelistIntegratorUrls.map(key => key.toLowerCase()));
|
||||
typedMakerUrls = typedMakerUrls.filter(makerUrl => whitelist.has(makerUrl.url.toLowerCase()));
|
||||
}
|
||||
return typedMakerUrls;
|
||||
}
|
||||
|
||||
public static getDurationUntilExpirationMs(expirationTimeSeconds: BigNumber): BigNumber {
|
||||
const expirationTimeMs = expirationTimeSeconds.times(constants.ONE_SECOND_MS);
|
||||
const currentTimeMs = new BigNumber(Date.now());
|
||||
@@ -281,7 +347,7 @@ export class QuoteRequestor {
|
||||
marketOperation: MarketOperation,
|
||||
comparisonPrice: BigNumber | undefined,
|
||||
options: RfqmRequestOptions,
|
||||
): Promise<V4RFQIndicativeQuote[]> {
|
||||
): Promise<V4RFQIndicativeQuoteMM[]> {
|
||||
const _opts: RfqRequestOpts = {
|
||||
...constants.DEFAULT_RFQT_REQUEST_OPTS,
|
||||
...options,
|
||||
@@ -305,7 +371,7 @@ export class QuoteRequestor {
|
||||
marketOperation: MarketOperation,
|
||||
comparisonPrice: BigNumber | undefined,
|
||||
options: RfqRequestOpts,
|
||||
): Promise<V4RFQIndicativeQuote[]> {
|
||||
): Promise<V4RFQIndicativeQuoteMM[]> {
|
||||
const _opts: RfqRequestOpts = { ...constants.DEFAULT_RFQT_REQUEST_OPTS, ...options };
|
||||
// Originally a takerAddress was required for indicative quotes, but
|
||||
// now we've eliminated that requirement. @0x/quote-server, however,
|
||||
@@ -336,8 +402,8 @@ export class QuoteRequestor {
|
||||
return this._orderSignatureToMakerUri[nativeDataToId({ signature })];
|
||||
}
|
||||
|
||||
private _isValidRfqtIndicativeQuoteResponse(response: V4RFQIndicativeQuote): boolean {
|
||||
const requiredKeys: Array<keyof V4RFQIndicativeQuote> = [
|
||||
private _isValidRfqtIndicativeQuoteResponse(response: V4RFQIndicativeQuoteMM): boolean {
|
||||
const requiredKeys: Array<keyof V4RFQIndicativeQuoteMM> = [
|
||||
'makerAmount',
|
||||
'takerAmount',
|
||||
'makerToken',
|
||||
@@ -401,21 +467,6 @@ export class QuoteRequestor {
|
||||
}
|
||||
})();
|
||||
|
||||
const standardUrls = Object.keys(assetOfferings).map(
|
||||
(mm: string): TypedMakerUrl => {
|
||||
return { pairType: RfqPairType.Standard, url: mm };
|
||||
},
|
||||
);
|
||||
const altUrls = options.altRfqAssetOfferings
|
||||
? Object.keys(options.altRfqAssetOfferings).map(
|
||||
(mm: string): TypedMakerUrl => {
|
||||
return { pairType: RfqPairType.Alt, url: mm };
|
||||
},
|
||||
)
|
||||
: [];
|
||||
|
||||
const typedMakerUrls = standardUrls.concat(altUrls);
|
||||
|
||||
const timeoutMs =
|
||||
options.makerEndpointMaxResponseTimeMs ||
|
||||
constants.DEFAULT_RFQT_REQUEST_OPTS.makerEndpointMaxResponseTimeMs!;
|
||||
@@ -427,11 +478,25 @@ export class QuoteRequestor {
|
||||
cancelTokenSource.cancel('timeout via cancel token');
|
||||
}, timeoutMs + bufferMs);
|
||||
|
||||
const typedMakerUrls = QuoteRequestor.getTypedMakerUrlsAndWhitelist(options, assetOfferings);
|
||||
const quotePromises = typedMakerUrls.map(async typedMakerUrl => {
|
||||
// filter out requests to skip
|
||||
const isBlacklisted = rfqMakerBlacklist.isMakerBlacklisted(typedMakerUrl.url);
|
||||
const partialLogEntry = { url: typedMakerUrl.url, quoteType, requestParams, isBlacklisted };
|
||||
const { isLastLook, integrator } = options;
|
||||
const { sellTokenAddress, buyTokenAddress } = requestParams;
|
||||
if (isBlacklisted) {
|
||||
this._metrics?.logRfqMakerNetworkInteraction({
|
||||
isLastLook: false,
|
||||
url: typedMakerUrl.url,
|
||||
quoteType,
|
||||
statusCode: undefined,
|
||||
sellTokenAddress,
|
||||
buyTokenAddress,
|
||||
latencyMs: 0,
|
||||
included: false,
|
||||
integrator,
|
||||
});
|
||||
this._infoLogger({ rfqtMakerInteraction: { ...partialLogEntry } });
|
||||
return;
|
||||
} else if (
|
||||
@@ -450,18 +515,32 @@ export class QuoteRequestor {
|
||||
try {
|
||||
if (typedMakerUrl.pairType === RfqPairType.Standard) {
|
||||
const response = await this._quoteRequestorHttpClient.get(`${typedMakerUrl.url}/${quotePath}`, {
|
||||
headers: { '0x-api-key': options.apiKey },
|
||||
headers: {
|
||||
'0x-api-key': options.integrator.integratorId,
|
||||
'0x-integrator-id': options.integrator.integratorId,
|
||||
},
|
||||
params: requestParams,
|
||||
timeout: timeoutMs,
|
||||
cancelToken: cancelTokenSource.token,
|
||||
});
|
||||
const latencyMs = Date.now() - timeBeforeAwait;
|
||||
this._metrics?.logRfqMakerNetworkInteraction({
|
||||
isLastLook: isLastLook || false,
|
||||
url: typedMakerUrl.url,
|
||||
quoteType,
|
||||
statusCode: response.status,
|
||||
sellTokenAddress,
|
||||
buyTokenAddress,
|
||||
latencyMs,
|
||||
included: true,
|
||||
integrator,
|
||||
});
|
||||
this._infoLogger({
|
||||
rfqtMakerInteraction: {
|
||||
...partialLogEntry,
|
||||
response: {
|
||||
included: true,
|
||||
apiKey: options.apiKey,
|
||||
apiKey: options.integrator.integratorId,
|
||||
takerAddress: requestParams.takerAddress,
|
||||
txOrigin: requestParams.txOrigin,
|
||||
statusCode: response.status,
|
||||
@@ -470,7 +549,10 @@ export class QuoteRequestor {
|
||||
},
|
||||
});
|
||||
rfqMakerBlacklist.logTimeoutOrLackThereof(typedMakerUrl.url, latencyMs >= timeoutMs);
|
||||
return { response: response.data, makerUri: typedMakerUrl.url };
|
||||
return {
|
||||
response: { ...response.data, makerUri: typedMakerUrl.url },
|
||||
makerUri: typedMakerUrl.url,
|
||||
};
|
||||
} else {
|
||||
if (this._altRfqCreds === undefined) {
|
||||
throw new Error(`don't have credentials for alt MM`);
|
||||
@@ -479,7 +561,7 @@ export class QuoteRequestor {
|
||||
typedMakerUrl.url,
|
||||
this._altRfqCreds.altRfqApiKey,
|
||||
this._altRfqCreds.altRfqProfile,
|
||||
options.apiKey,
|
||||
options.integrator.integratorId,
|
||||
quoteType === 'firm' ? AltQuoteModel.Firm : AltQuoteModel.Indicative,
|
||||
makerToken,
|
||||
takerToken,
|
||||
@@ -492,12 +574,23 @@ export class QuoteRequestor {
|
||||
);
|
||||
|
||||
const latencyMs = Date.now() - timeBeforeAwait;
|
||||
this._metrics?.logRfqMakerNetworkInteraction({
|
||||
isLastLook: isLastLook || false,
|
||||
url: typedMakerUrl.url,
|
||||
quoteType,
|
||||
statusCode: quote.status,
|
||||
sellTokenAddress,
|
||||
buyTokenAddress,
|
||||
latencyMs,
|
||||
included: true,
|
||||
integrator,
|
||||
});
|
||||
this._infoLogger({
|
||||
rfqtMakerInteraction: {
|
||||
...partialLogEntry,
|
||||
response: {
|
||||
included: true,
|
||||
apiKey: options.apiKey,
|
||||
apiKey: options.integrator.integratorId,
|
||||
takerAddress: requestParams.takerAddress,
|
||||
txOrigin: requestParams.txOrigin,
|
||||
statusCode: quote.status,
|
||||
@@ -511,12 +604,23 @@ export class QuoteRequestor {
|
||||
} catch (err) {
|
||||
// log error if any
|
||||
const latencyMs = Date.now() - timeBeforeAwait;
|
||||
this._metrics?.logRfqMakerNetworkInteraction({
|
||||
isLastLook: isLastLook || false,
|
||||
url: typedMakerUrl.url,
|
||||
quoteType,
|
||||
statusCode: err.response?.status,
|
||||
sellTokenAddress,
|
||||
buyTokenAddress,
|
||||
latencyMs,
|
||||
included: false,
|
||||
integrator,
|
||||
});
|
||||
this._infoLogger({
|
||||
rfqtMakerInteraction: {
|
||||
...partialLogEntry,
|
||||
response: {
|
||||
included: false,
|
||||
apiKey: options.apiKey,
|
||||
apiKey: options.integrator.integratorId,
|
||||
takerAddress: requestParams.takerAddress,
|
||||
txOrigin: requestParams.txOrigin,
|
||||
statusCode: err.response ? err.response.status : undefined,
|
||||
@@ -527,7 +631,7 @@ export class QuoteRequestor {
|
||||
rfqMakerBlacklist.logTimeoutOrLackThereof(typedMakerUrl.url, latencyMs >= timeoutMs);
|
||||
this._warningLogger(
|
||||
convertIfAxiosError(err),
|
||||
`Failed to get RFQ-T ${quoteType} quote from market maker endpoint ${typedMakerUrl.url} for API key ${options.apiKey} for taker address ${options.takerAddress} and tx origin ${options.txOrigin}`,
|
||||
`Failed to get RFQ-T ${quoteType} quote from market maker endpoint ${typedMakerUrl.url} for integrator ${options.integrator.integratorId} (${options.integrator.label}) for taker address ${options.takerAddress} and tx origin ${options.txOrigin}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -597,7 +701,6 @@ export class QuoteRequestor {
|
||||
} else {
|
||||
const secondsRemaining = msRemainingUntilExpiration.div(ONE_SECOND_MS);
|
||||
this._metrics?.measureExpirationForValidOrder(isLastLook, order.maker, secondsRemaining);
|
||||
|
||||
const takerAmount = new BigNumber(order.takerAmount);
|
||||
const fillRatio = takerAmount.div(assetFillAmount);
|
||||
if (fillRatio.lt(1) && fillRatio.gte(FILL_RATIO_WARNING_LEVEL)) {
|
||||
@@ -647,9 +750,9 @@ export class QuoteRequestor {
|
||||
comparisonPrice: BigNumber | undefined,
|
||||
options: RfqRequestOpts,
|
||||
assetOfferings: RfqMakerAssetOfferings,
|
||||
): Promise<V4RFQIndicativeQuote[]> {
|
||||
): Promise<V4RFQIndicativeQuoteMM[]> {
|
||||
// fetch quotes
|
||||
const rawQuotes = await this._getQuotesAsync<V4RFQIndicativeQuote>(
|
||||
const rawQuotes = await this._getQuotesAsync<V4RFQIndicativeQuoteMM>(
|
||||
makerToken,
|
||||
takerToken,
|
||||
assetFillAmount,
|
||||
@@ -661,7 +764,7 @@ export class QuoteRequestor {
|
||||
);
|
||||
|
||||
// validate
|
||||
const validationFunction = (o: V4RFQIndicativeQuote) => this._isValidRfqtIndicativeQuoteResponse(o);
|
||||
const validationFunction = (o: V4RFQIndicativeQuoteMM) => this._isValidRfqtIndicativeQuoteResponse(o);
|
||||
const validQuotes = rawQuotes.filter(result => {
|
||||
const order = result.response;
|
||||
if (!validationFunction(order)) {
|
||||
|
@@ -28,7 +28,11 @@ export const rfqtMocker = {
|
||||
// Mock out RFQT responses
|
||||
for (const mockedResponse of mockedResponses) {
|
||||
const { endpoint, requestApiKey, requestParams, responseData, responseCode } = mockedResponse;
|
||||
const requestHeaders = { Accept: 'application/json, text/plain, */*', '0x-api-key': requestApiKey };
|
||||
const requestHeaders = {
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'0x-api-key': requestApiKey,
|
||||
'0x-integrator-id': requestApiKey,
|
||||
};
|
||||
mockedAxios
|
||||
.onGet(`${endpoint}/${quoteType}`, { params: requestParams }, requestHeaders)
|
||||
.replyOnce(responseCode, responseData);
|
||||
|
@@ -10,6 +10,7 @@ import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json
|
||||
import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json';
|
||||
import * as BalancerV2Sampler from '../test/generated-artifacts/BalancerV2Sampler.json';
|
||||
import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json';
|
||||
import * as CompoundSampler from '../test/generated-artifacts/CompoundSampler.json';
|
||||
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
|
||||
import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json';
|
||||
import * as DODOV2Sampler from '../test/generated-artifacts/DODOV2Sampler.json';
|
||||
@@ -52,6 +53,7 @@ export const artifacts = {
|
||||
BalancerSampler: BalancerSampler as ContractArtifact,
|
||||
BalancerV2Sampler: BalancerV2Sampler as ContractArtifact,
|
||||
BancorSampler: BancorSampler as ContractArtifact,
|
||||
CompoundSampler: CompoundSampler as ContractArtifact,
|
||||
CurveSampler: CurveSampler as ContractArtifact,
|
||||
DODOSampler: DODOSampler as ContractArtifact,
|
||||
DODOV2Sampler: DODOV2Sampler as ContractArtifact,
|
||||
|
@@ -18,7 +18,9 @@ import { DummyLiquidityProviderContract, TestERC20BridgeSamplerContract } from '
|
||||
// tslint:disable: custom-no-magic-numbers
|
||||
|
||||
const { NULL_ADDRESS } = constants;
|
||||
blockchainTests('erc20-bridge-sampler', env => {
|
||||
// HACK(dorothy-zbornak): Disabled because these tests are flakey and all this logic is moving to
|
||||
// the sampler service anyway.
|
||||
blockchainTests.skip('erc20-bridge-sampler', env => {
|
||||
let testContract: TestERC20BridgeSamplerContract;
|
||||
const RATE_DENOMINATOR = constants.ONE_ETHER;
|
||||
const MIN_RATE = new BigNumber('0.01');
|
||||
|
@@ -261,7 +261,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
expect(fillQuoteTransformerData.buyToken).to.eq(MAKER_TOKEN);
|
||||
const payTakerTransformerData = decodePayTakerTransformerData(callArgs.transformations[1].data);
|
||||
expect(payTakerTransformerData.amounts).to.deep.eq([]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, MAKER_TOKEN, ETH_TOKEN_ADDRESS]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, ETH_TOKEN_ADDRESS]);
|
||||
});
|
||||
|
||||
it('can produce a buy quote', async () => {
|
||||
@@ -292,7 +292,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
expect(fillQuoteTransformerData.buyToken).to.eq(MAKER_TOKEN);
|
||||
const payTakerTransformerData = decodePayTakerTransformerData(callArgs.transformations[1].data);
|
||||
expect(payTakerTransformerData.amounts).to.deep.eq([]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, MAKER_TOKEN, ETH_TOKEN_ADDRESS]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, ETH_TOKEN_ADDRESS]);
|
||||
});
|
||||
|
||||
it('ERC20 -> ERC20 does not have a WETH transformer', async () => {
|
||||
@@ -437,12 +437,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
expect(secondHopFillQuoteTransformerData.buyToken).to.eq(MAKER_TOKEN);
|
||||
const payTakerTransformerData = decodePayTakerTransformerData(callArgs.transformations[2].data);
|
||||
expect(payTakerTransformerData.amounts).to.deep.eq([]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([
|
||||
TAKER_TOKEN,
|
||||
MAKER_TOKEN,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
INTERMEDIATE_TOKEN,
|
||||
]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, INTERMEDIATE_TOKEN, ETH_TOKEN_ADDRESS]);
|
||||
});
|
||||
// it.skip('Uses the `LiquidityProviderFeature` if given a single LiquidityProvider order', async () => {
|
||||
// const quote = {
|
||||
@@ -504,7 +499,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
expect(fillQuoteTransformerData.buyToken).to.eq(MAKER_TOKEN);
|
||||
const payTakerTransformerData = decodePayTakerTransformerData(callArgs.transformations[1].data);
|
||||
expect(payTakerTransformerData.amounts).to.deep.eq([]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, MAKER_TOKEN, ETH_TOKEN_ADDRESS]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, ETH_TOKEN_ADDRESS]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -16,13 +16,14 @@ import * as _ from 'lodash';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
|
||||
import { MarketOperation, QuoteRequestor, RfqRequestOpts, SignedNativeOrder } from '../src';
|
||||
import { NativeOrderWithFillableAmounts } from '../src/types';
|
||||
import { Integrator, NativeOrderWithFillableAmounts } from '../src/types';
|
||||
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
||||
import {
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
POSITIVE_INF,
|
||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
SOURCE_FLAGS,
|
||||
ZERO_AMOUNT,
|
||||
} from '../src/utils/market_operation_utils/constants';
|
||||
import { createFills } from '../src/utils/market_operation_utils/fills';
|
||||
import { PoolsCache } from '../src/utils/market_operation_utils/pools_cache';
|
||||
@@ -62,6 +63,10 @@ const SELL_SOURCES = SELL_SOURCE_FILTER_BY_CHAIN_ID[ChainId.Mainnet].sources;
|
||||
const TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = { default: [] };
|
||||
|
||||
const SIGNATURE = { v: 1, r: NULL_BYTES, s: NULL_BYTES, signatureType: SignatureType.EthSign };
|
||||
const FOO_INTEGRATOR: Integrator = {
|
||||
integratorId: 'foo',
|
||||
label: 'foo',
|
||||
};
|
||||
|
||||
/**
|
||||
* gets the orders required for a market sell operation by (potentially) merging native orders with
|
||||
@@ -75,7 +80,7 @@ async function getMarketSellOrdersAsync(
|
||||
utils: MarketOperationUtils,
|
||||
nativeOrders: SignedNativeOrder[],
|
||||
takerAmount: BigNumber,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
opts: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber },
|
||||
): Promise<OptimizerResultWithReport> {
|
||||
return utils.getOptimizerResultAsync(nativeOrders, takerAmount, MarketOperation.Sell, opts);
|
||||
}
|
||||
@@ -92,7 +97,7 @@ async function getMarketBuyOrdersAsync(
|
||||
utils: MarketOperationUtils,
|
||||
nativeOrders: SignedNativeOrder[],
|
||||
makerAmount: BigNumber,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
opts: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber },
|
||||
): Promise<OptimizerResultWithReport> {
|
||||
return utils.getOptimizerResultAsync(nativeOrders, makerAmount, MarketOperation.Buy, opts);
|
||||
}
|
||||
@@ -155,7 +160,11 @@ describe('MarketOperationUtils tests', () => {
|
||||
} else {
|
||||
requestor
|
||||
.setup(r => r.requestRfqtIndicativeQuotesAsync(...args))
|
||||
.returns(async () => results.map(r => r.order))
|
||||
.returns(async () =>
|
||||
results.map(r => {
|
||||
return { ...r.order, makerUri: 'https://foo.bar/' };
|
||||
}),
|
||||
)
|
||||
.verifiable(verifiable);
|
||||
}
|
||||
return requestor;
|
||||
@@ -419,6 +428,8 @@ describe('MarketOperationUtils tests', () => {
|
||||
getTwoHopSellQuotes: (..._params: any[]) => [],
|
||||
getTwoHopBuyQuotes: (..._params: any[]) => [],
|
||||
isAddressContract: (..._params: any[]) => false,
|
||||
getGasLeft: () => ZERO_AMOUNT,
|
||||
getBlockNumber: () => ZERO_AMOUNT,
|
||||
};
|
||||
|
||||
const MOCK_SAMPLER = ({
|
||||
@@ -455,7 +466,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
FILL_AMOUNT,
|
||||
_.times(NUM_SAMPLES, i => DEFAULT_RATES[ERC20BridgeSource.Native][i]),
|
||||
);
|
||||
const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> = {
|
||||
const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber } = {
|
||||
numSamples: NUM_SAMPLES,
|
||||
sampleDistributionBase: 1,
|
||||
bridgeSlippage: 0,
|
||||
@@ -464,6 +475,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
allowFallback: false,
|
||||
gasSchedule: {},
|
||||
feeSchedule: {},
|
||||
gasPrice: new BigNumber(30e9),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -745,7 +757,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
feeSchedule,
|
||||
rfqt: {
|
||||
isIndicative: false,
|
||||
apiKey: 'foo',
|
||||
integrator: FOO_INTEGRATOR,
|
||||
takerAddress: randomAddress(),
|
||||
txOrigin: randomAddress(),
|
||||
intentOnFilling: true,
|
||||
@@ -790,7 +802,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
...DEFAULT_OPTS,
|
||||
rfqt: {
|
||||
isIndicative: false,
|
||||
apiKey: 'foo',
|
||||
integrator: FOO_INTEGRATOR,
|
||||
takerAddress: randomAddress(),
|
||||
intentOnFilling: true,
|
||||
txOrigin: randomAddress(),
|
||||
@@ -837,7 +849,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
...DEFAULT_OPTS,
|
||||
rfqt: {
|
||||
isIndicative: true,
|
||||
apiKey: 'foo',
|
||||
integrator: FOO_INTEGRATOR,
|
||||
takerAddress: randomAddress(),
|
||||
txOrigin: randomAddress(),
|
||||
intentOnFilling: true,
|
||||
@@ -896,7 +908,10 @@ describe('MarketOperationUtils tests', () => {
|
||||
...DEFAULT_OPTS,
|
||||
rfqt: {
|
||||
isIndicative: false,
|
||||
apiKey: 'foo',
|
||||
integrator: {
|
||||
integratorId: 'foo',
|
||||
label: 'foo',
|
||||
},
|
||||
takerAddress: randomAddress(),
|
||||
intentOnFilling: true,
|
||||
txOrigin: randomAddress(),
|
||||
@@ -954,7 +969,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
...DEFAULT_OPTS,
|
||||
rfqt: {
|
||||
isIndicative: false,
|
||||
apiKey: 'foo',
|
||||
integrator: FOO_INTEGRATOR,
|
||||
takerAddress: randomAddress(),
|
||||
txOrigin: randomAddress(),
|
||||
intentOnFilling: true,
|
||||
@@ -1222,6 +1237,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
excludedSources: [],
|
||||
numSamples: 4,
|
||||
bridgeSlippage: 0,
|
||||
gasPrice: new BigNumber(30e9),
|
||||
},
|
||||
);
|
||||
const result = ordersAndReport.optimizedOrders;
|
||||
@@ -1291,7 +1307,8 @@ describe('MarketOperationUtils tests', () => {
|
||||
FILL_AMOUNT,
|
||||
_.times(NUM_SAMPLES, () => DEFAULT_RATES[ERC20BridgeSource.Native][0]),
|
||||
);
|
||||
const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> = {
|
||||
const GAS_PRICE = new BigNumber(100e9); // 100 gwei
|
||||
const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber } = {
|
||||
numSamples: NUM_SAMPLES,
|
||||
sampleDistributionBase: 1,
|
||||
bridgeSlippage: 0,
|
||||
@@ -1300,6 +1317,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
allowFallback: false,
|
||||
gasSchedule: {},
|
||||
feeSchedule: {},
|
||||
gasPrice: GAS_PRICE,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -1619,11 +1637,10 @@ describe('MarketOperationUtils tests', () => {
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||
});
|
||||
const optimizer = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
|
||||
const gasPrice = 100e9; // 100 gwei
|
||||
const exchangeProxyOverhead = (sourceFlags: bigint) =>
|
||||
sourceFlags === SOURCE_FLAGS.LiquidityProvider
|
||||
? constants.ZERO_AMOUNT
|
||||
: new BigNumber(1.3e5).times(gasPrice);
|
||||
: new BigNumber(1.3e5).times(GAS_PRICE);
|
||||
const improvedOrdersResponse = await optimizer.getOptimizerResultAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
FILL_AMOUNT,
|
||||
|
@@ -155,7 +155,7 @@ describe('generateQuoteReport', async () => {
|
||||
makerAmount: rfqtOrder1.order.makerAmount,
|
||||
takerAmount: rfqtOrder1.order.takerAmount,
|
||||
fillableTakerAmount: rfqtOrder1.fillableTakerAmount,
|
||||
isRfqt: true,
|
||||
isRFQ: true,
|
||||
makerUri: 'https://rfqt1.provider.club',
|
||||
nativeOrder: rfqtOrder1.order,
|
||||
fillData: {
|
||||
@@ -167,7 +167,7 @@ describe('generateQuoteReport', async () => {
|
||||
makerAmount: rfqtOrder2.order.makerAmount,
|
||||
takerAmount: rfqtOrder2.order.takerAmount,
|
||||
fillableTakerAmount: rfqtOrder2.fillableTakerAmount,
|
||||
isRfqt: true,
|
||||
isRFQ: true,
|
||||
makerUri: 'https://rfqt2.provider.club',
|
||||
nativeOrder: rfqtOrder2.order,
|
||||
fillData: {
|
||||
@@ -179,7 +179,7 @@ describe('generateQuoteReport', async () => {
|
||||
makerAmount: orderbookOrder2.order.makerAmount,
|
||||
takerAmount: orderbookOrder2.order.takerAmount,
|
||||
fillableTakerAmount: orderbookOrder2.fillableTakerAmount,
|
||||
isRfqt: false,
|
||||
isRFQ: false,
|
||||
fillData: {
|
||||
order: orderbookOrder2.order,
|
||||
} as NativeLimitOrderFillData,
|
||||
@@ -263,7 +263,7 @@ describe('generateQuoteReport', async () => {
|
||||
makerAmount: orderbookOrder1.order.makerAmount,
|
||||
takerAmount: orderbookOrder1.order.takerAmount,
|
||||
fillableTakerAmount: orderbookOrder1.fillableTakerAmount,
|
||||
isRfqt: false,
|
||||
isRFQ: false,
|
||||
fillData: {
|
||||
order: orderbookOrder1.order,
|
||||
} as NativeLimitOrderFillData,
|
||||
|
@@ -240,7 +240,10 @@ describe('QuoteRequestor', async () => {
|
||||
MarketOperation.Sell,
|
||||
undefined,
|
||||
{
|
||||
apiKey,
|
||||
integrator: {
|
||||
integratorId: apiKey,
|
||||
label: 'foo',
|
||||
},
|
||||
takerAddress,
|
||||
txOrigin: takerAddress,
|
||||
intentOnFilling: true,
|
||||
@@ -435,7 +438,10 @@ describe('QuoteRequestor', async () => {
|
||||
MarketOperation.Sell,
|
||||
undefined,
|
||||
{
|
||||
apiKey,
|
||||
integrator: {
|
||||
integratorId: apiKey,
|
||||
label: 'foo',
|
||||
},
|
||||
takerAddress,
|
||||
txOrigin: takerAddress,
|
||||
intentOnFilling: true,
|
||||
@@ -488,15 +494,18 @@ describe('QuoteRequestor', async () => {
|
||||
expiry: makeThreeMinuteExpiry(),
|
||||
};
|
||||
|
||||
const goodMMUri1 = 'https://1337.0.0.1';
|
||||
const goodMMUri2 = 'https://37.0.0.1';
|
||||
|
||||
mockedRequests.push({
|
||||
...mockedDefaults,
|
||||
endpoint: 'https://1337.0.0.1',
|
||||
endpoint: goodMMUri1,
|
||||
responseData: successfulQuote1,
|
||||
});
|
||||
// [GOOD] Another Successful response
|
||||
mockedRequests.push({
|
||||
...mockedDefaults,
|
||||
endpoint: 'https://37.0.0.1',
|
||||
endpoint: goodMMUri2,
|
||||
responseData: successfulQuote1,
|
||||
});
|
||||
|
||||
@@ -526,6 +535,16 @@ describe('QuoteRequestor', async () => {
|
||||
responseData: { ...successfulQuote1, takerToken: otherToken1 },
|
||||
});
|
||||
|
||||
const assetOfferings: { [k: string]: [[string, string]] } = {
|
||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||
'https://422.0.0.1': [[makerToken, takerToken]],
|
||||
'https://423.0.0.1': [[makerToken, takerToken]],
|
||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||
};
|
||||
assetOfferings[goodMMUri1] = [[makerToken, takerToken]];
|
||||
assetOfferings[goodMMUri2] = [[makerToken, takerToken]];
|
||||
|
||||
return testHelpers.withMockedRfqQuotes(
|
||||
mockedRequests,
|
||||
[],
|
||||
@@ -533,15 +552,7 @@ describe('QuoteRequestor', async () => {
|
||||
async () => {
|
||||
const qr = new QuoteRequestor(
|
||||
{}, // No RFQ-T asset offerings
|
||||
{
|
||||
'https://1337.0.0.1': [[makerToken, takerToken]],
|
||||
'https://37.0.0.1': [[makerToken, takerToken]],
|
||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||
'https://422.0.0.1': [[makerToken, takerToken]],
|
||||
'https://423.0.0.1': [[makerToken, takerToken]],
|
||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||
},
|
||||
assetOfferings,
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
const resp = await qr.requestRfqmIndicativeQuotesAsync(
|
||||
@@ -551,7 +562,10 @@ describe('QuoteRequestor', async () => {
|
||||
MarketOperation.Sell,
|
||||
undefined,
|
||||
{
|
||||
apiKey,
|
||||
integrator: {
|
||||
integratorId: apiKey,
|
||||
label: 'foo',
|
||||
},
|
||||
takerAddress,
|
||||
txOrigin: takerAddress,
|
||||
intentOnFilling: true,
|
||||
@@ -563,7 +577,12 @@ describe('QuoteRequestor', async () => {
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(resp.sort()).to.eql([successfulQuote1, successfulQuote1].sort());
|
||||
expect(resp.sort()).to.eql(
|
||||
[
|
||||
{ ...successfulQuote1, makerUri: goodMMUri1 },
|
||||
{ ...successfulQuote1, makerUri: goodMMUri2 },
|
||||
].sort(),
|
||||
);
|
||||
},
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
@@ -613,9 +632,12 @@ describe('QuoteRequestor', async () => {
|
||||
expiry: makeThreeMinuteExpiry(),
|
||||
};
|
||||
|
||||
const goodMMUri1 = 'https://1337.0.0.1';
|
||||
const goodMMUri2 = 'https://37.0.0.1';
|
||||
|
||||
mockedRequests.push({
|
||||
...mockedDefaults,
|
||||
endpoint: 'https://1337.0.0.1',
|
||||
endpoint: goodMMUri1,
|
||||
responseData: successfulQuote1,
|
||||
});
|
||||
// Test out a bad response code, ensure it doesnt cause throw
|
||||
@@ -646,28 +668,26 @@ describe('QuoteRequestor', async () => {
|
||||
// Another Successful response
|
||||
mockedRequests.push({
|
||||
...mockedDefaults,
|
||||
endpoint: 'https://37.0.0.1',
|
||||
endpoint: goodMMUri2,
|
||||
responseData: successfulQuote1,
|
||||
});
|
||||
|
||||
const assetOfferings: { [k: string]: [[string, string]] } = {
|
||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||
'https://422.0.0.1': [[makerToken, takerToken]],
|
||||
'https://423.0.0.1': [[makerToken, takerToken]],
|
||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||
};
|
||||
assetOfferings[goodMMUri1] = [[makerToken, takerToken]];
|
||||
assetOfferings[goodMMUri2] = [[makerToken, takerToken]];
|
||||
|
||||
return testHelpers.withMockedRfqQuotes(
|
||||
mockedRequests,
|
||||
[],
|
||||
RfqQuoteEndpoint.Indicative,
|
||||
async () => {
|
||||
const qr = new QuoteRequestor(
|
||||
{
|
||||
'https://1337.0.0.1': [[makerToken, takerToken]],
|
||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||
'https://422.0.0.1': [[makerToken, takerToken]],
|
||||
'https://423.0.0.1': [[makerToken, takerToken]],
|
||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||
'https://37.0.0.1': [[makerToken, takerToken]],
|
||||
},
|
||||
{},
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
const qr = new QuoteRequestor(assetOfferings, {}, quoteRequestorHttpClient);
|
||||
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
||||
makerToken,
|
||||
takerToken,
|
||||
@@ -675,13 +695,21 @@ describe('QuoteRequestor', async () => {
|
||||
MarketOperation.Sell,
|
||||
undefined,
|
||||
{
|
||||
apiKey,
|
||||
integrator: {
|
||||
integratorId: apiKey,
|
||||
label: 'foo',
|
||||
},
|
||||
takerAddress,
|
||||
txOrigin: takerAddress,
|
||||
intentOnFilling: true,
|
||||
},
|
||||
);
|
||||
expect(resp.sort()).to.eql([successfulQuote1, successfulQuote1].sort());
|
||||
expect(resp.sort()).to.eql(
|
||||
[
|
||||
{ ...successfulQuote1, makerUri: goodMMUri1 },
|
||||
{ ...successfulQuote1, makerUri: goodMMUri2 },
|
||||
].sort(),
|
||||
);
|
||||
},
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
@@ -762,14 +790,17 @@ describe('QuoteRequestor', async () => {
|
||||
MarketOperation.Sell,
|
||||
undefined,
|
||||
{
|
||||
apiKey,
|
||||
integrator: {
|
||||
integratorId: apiKey,
|
||||
label: 'foo',
|
||||
},
|
||||
takerAddress,
|
||||
txOrigin: takerAddress,
|
||||
intentOnFilling: true,
|
||||
makerEndpointMaxResponseTimeMs: maxTimeoutMs,
|
||||
},
|
||||
);
|
||||
expect(resp.sort()).to.eql([successfulQuote1].sort()); // notice only one result, despite two requests made
|
||||
expect(resp.sort()).to.eql([{ ...successfulQuote1, makerUri: 'https://1337.0.0.1' }].sort()); // notice only one result, despite two requests made
|
||||
},
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
@@ -823,17 +854,57 @@ describe('QuoteRequestor', async () => {
|
||||
MarketOperation.Buy,
|
||||
undefined,
|
||||
{
|
||||
apiKey,
|
||||
integrator: {
|
||||
integratorId: apiKey,
|
||||
label: 'foo',
|
||||
},
|
||||
takerAddress,
|
||||
txOrigin: takerAddress,
|
||||
intentOnFilling: true,
|
||||
},
|
||||
);
|
||||
expect(resp.sort()).to.eql([successfulQuote1].sort());
|
||||
expect(resp.sort()).to.eql([{ ...successfulQuote1, makerUri: 'https://1337.0.0.1' }].sort());
|
||||
},
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
});
|
||||
it('should be able to handle and filter RFQ offerings', () => {
|
||||
const tests: Array<[string[] | undefined, string[]]> = [
|
||||
[['https://top.maker'], []],
|
||||
[undefined, ['https://foo.bar/', 'https://lorem.ipsum/']],
|
||||
[['https://lorem.ipsum/'], ['https://lorem.ipsum/']],
|
||||
];
|
||||
for (const test of tests) {
|
||||
const [apiKeyWhitelist, results] = test;
|
||||
const response = QuoteRequestor.getTypedMakerUrlsAndWhitelist(
|
||||
{
|
||||
integrator: {
|
||||
integratorId: 'foo',
|
||||
label: 'bar',
|
||||
whitelistIntegratorUrls: apiKeyWhitelist,
|
||||
},
|
||||
altRfqAssetOfferings: {},
|
||||
},
|
||||
{
|
||||
'https://foo.bar/': [
|
||||
[
|
||||
'0xA6cD4cb8c62aCDe44739E3Ed0F1d13E0e31f2d94',
|
||||
'0xF45107c0200a04A8aB9C600cc52A3C89AE5D0489',
|
||||
],
|
||||
],
|
||||
'https://lorem.ipsum/': [
|
||||
[
|
||||
'0xA6cD4cb8c62aCDe44739E3Ed0F1d13E0e31f2d94',
|
||||
'0xF45107c0200a04A8aB9C600cc52A3C89AE5D0489',
|
||||
],
|
||||
],
|
||||
},
|
||||
);
|
||||
const typedUrls = response.map(typed => typed.url);
|
||||
expect(typedUrls).to.eql(results);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return successful alt indicative quotes', async () => {
|
||||
const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
|
||||
const txOrigin = '0xf209925defc99488e3afff1174e48b4fa628302a';
|
||||
@@ -1055,7 +1126,10 @@ describe('QuoteRequestor', async () => {
|
||||
altScenario.requestedOperation,
|
||||
undefined,
|
||||
{
|
||||
apiKey,
|
||||
integrator: {
|
||||
integratorId: apiKey,
|
||||
label: 'foo',
|
||||
},
|
||||
takerAddress,
|
||||
txOrigin,
|
||||
intentOnFilling: true,
|
||||
|
@@ -744,7 +744,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||
});
|
||||
if (side === MarketOperation.Sell) {
|
||||
expect(result.totalMakerAssetAmount).to.be.bignumber.eq(fillableOutput);
|
||||
@@ -769,10 +769,10 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||
});
|
||||
expect(result.gas).to.eq(countCollapsedFills(orders));
|
||||
expect(result.protocolFeeAmount).to.bignumber.gt(orders.length);
|
||||
expect(result.protocolFeeAmount).to.bignumber.eq(orders.length);
|
||||
expect(result.takerFeeTakerAssetAmount).to.bignumber.eq(0);
|
||||
expect(result.takerFeeMakerAssetAmount).to.bignumber.eq(0);
|
||||
expect(result.makerAssetAmount).to.bignumber.eq(result.totalMakerAssetAmount);
|
||||
@@ -801,7 +801,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: inputFillAmount,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||
});
|
||||
expect(result.gas).to.gt(0);
|
||||
expect(result.protocolFeeAmount).to.bignumber.gt(0);
|
||||
@@ -835,7 +835,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: totalFillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||
});
|
||||
|
||||
assertRoughlyEquals(result.takerAssetAmount, fillableInput);
|
||||
@@ -865,7 +865,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: inputFillAmount,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||
});
|
||||
expect(result.gas).to.gt(0);
|
||||
expect(result.protocolFeeAmount).to.bignumber.gt(0);
|
||||
@@ -893,10 +893,10 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||
});
|
||||
expect(result.gas).to.eq(countCollapsedFills(orders));
|
||||
expect(result.protocolFeeAmount).to.bignumber.gt(orders.length);
|
||||
expect(result.protocolFeeAmount).to.bignumber.eq(orders.length);
|
||||
|
||||
assertRoughlyEquals(result.makerAssetAmount, fillableInput);
|
||||
assertRoughlyEquals(result.totalMakerAssetAmount, fillableInput);
|
||||
@@ -923,7 +923,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: inputFillAmount,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||
});
|
||||
expect(result.gas).to.gt(0);
|
||||
expect(result.protocolFeeAmount).to.bignumber.gt(0);
|
||||
@@ -951,7 +951,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE, slippage },
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE, slippage },
|
||||
});
|
||||
if (side === MarketOperation.Sell) {
|
||||
const slippedOutput = fillableOutput.times(1 - slippage).integerValue();
|
||||
@@ -982,7 +982,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||
});
|
||||
const worstCase = simulateWorstCaseFill({
|
||||
orders,
|
||||
|
@@ -48,7 +48,11 @@ export const testHelpers = {
|
||||
// Mock out Standard RFQ-T/M responses
|
||||
for (const mockedResponse of standardMockedResponses) {
|
||||
const { endpoint, requestApiKey, requestParams, responseData, responseCode } = mockedResponse;
|
||||
const requestHeaders = { Accept: 'application/json, text/plain, */*', '0x-api-key': requestApiKey };
|
||||
const requestHeaders = {
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'0x-api-key': requestApiKey,
|
||||
'0x-integrator-id': requestApiKey,
|
||||
};
|
||||
if (mockedResponse.callback !== undefined) {
|
||||
mockedAxios
|
||||
.onGet(`${endpoint}/${quoteType}`, { params: requestParams }, requestHeaders)
|
||||
|
@@ -8,6 +8,7 @@ export * from '../test/generated-wrappers/balance_checker';
|
||||
export * from '../test/generated-wrappers/balancer_sampler';
|
||||
export * from '../test/generated-wrappers/balancer_v2_sampler';
|
||||
export * from '../test/generated-wrappers/bancor_sampler';
|
||||
export * from '../test/generated-wrappers/compound_sampler';
|
||||
export * from '../test/generated-wrappers/curve_sampler';
|
||||
export * from '../test/generated-wrappers/d_o_d_o_sampler';
|
||||
export * from '../test/generated-wrappers/d_o_d_o_v2_sampler';
|
||||
|
@@ -11,6 +11,7 @@
|
||||
"test/generated-artifacts/BalancerSampler.json",
|
||||
"test/generated-artifacts/BalancerV2Sampler.json",
|
||||
"test/generated-artifacts/BancorSampler.json",
|
||||
"test/generated-artifacts/CompoundSampler.json",
|
||||
"test/generated-artifacts/CurveSampler.json",
|
||||
"test/generated-artifacts/DODOSampler.json",
|
||||
"test/generated-artifacts/DODOV2Sampler.json",
|
||||
|
@@ -1,4 +1,63 @@
|
||||
[
|
||||
{
|
||||
"version": "6.11.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add Optimism addresses",
|
||||
"pr": 385
|
||||
}
|
||||
],
|
||||
"timestamp": 1640364306
|
||||
},
|
||||
{
|
||||
"version": "6.10.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add Aave supported FQT addresses for Polygon, Avalanche",
|
||||
"pr": 321
|
||||
}
|
||||
],
|
||||
"timestamp": 1638390144
|
||||
},
|
||||
{
|
||||
"version": "6.9.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add Celo addresses",
|
||||
"pr": 368
|
||||
}
|
||||
],
|
||||
"timestamp": 1637102971
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "6.8.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "6.8.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Fantom deployment",
|
||||
"pr": 347
|
||||
}
|
||||
],
|
||||
"timestamp": 1634668033
|
||||
},
|
||||
{
|
||||
"version": "6.7.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Avalanche deployment",
|
||||
"pr": 312
|
||||
}
|
||||
],
|
||||
"timestamp": 1630459879
|
||||
},
|
||||
{
|
||||
"timestamp": 1629353596,
|
||||
"version": "6.6.1",
|
||||
|
@@ -5,6 +5,30 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v6.11.0 - _December 24, 2021_
|
||||
|
||||
* Add Optimism addresses (#385)
|
||||
|
||||
## v6.10.0 - _December 1, 2021_
|
||||
|
||||
* Add Aave supported FQT addresses for Polygon, Avalanche (#321)
|
||||
|
||||
## v6.9.0 - _November 16, 2021_
|
||||
|
||||
* Add Celo addresses (#368)
|
||||
|
||||
## v6.8.1 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v6.8.0 - _October 19, 2021_
|
||||
|
||||
* Fantom deployment (#347)
|
||||
|
||||
## v6.7.0 - _September 1, 2021_
|
||||
|
||||
* Avalanche deployment (#312)
|
||||
|
||||
## v6.6.1 - _August 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -244,11 +244,11 @@
|
||||
"exchangeProxyLiquidityProviderSandbox": "0x0000000000000000000000000000000000000000",
|
||||
"zrxTreasury": "0x0000000000000000000000000000000000000000",
|
||||
"transformers": {
|
||||
"wethTransformer": "0xc6b0d3c45a6b5092808196cb00df5c357d55e1d5",
|
||||
"payTakerTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",
|
||||
"affiliateFeeTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a",
|
||||
"fillQuoteTransformer": "0x99356167edba8fbdc36959e3f5d0c43d1ba9c6db",
|
||||
"positiveSlippageFeeTransformer": "0x45b3a72221e571017c0f0ec42189e11d149d0ace"
|
||||
"wethTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",
|
||||
"payTakerTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a",
|
||||
"affiliateFeeTransformer": "0x99356167edba8fbdc36959e3f5d0c43d1ba9c6db",
|
||||
"fillQuoteTransformer": "0x45b3a72221e571017c0f0ec42189e11d149d0ace",
|
||||
"positiveSlippageFeeTransformer": "0xdd66c23e07b4d6925b6089b5fe6fc9e62941afe8"
|
||||
}
|
||||
},
|
||||
"137": {
|
||||
@@ -289,7 +289,7 @@
|
||||
"wethTransformer": "0xe309d011cc6f189a3e8dcba85922715a019fed38",
|
||||
"payTakerTransformer": "0x5ba7b9be86cda01cfbf56e0fb97184783be9dda1",
|
||||
"affiliateFeeTransformer": "0xbed27284b42e5684e987169cf1da09c5d6c49fa8",
|
||||
"fillQuoteTransformer": "0xf708d512b8a82e2862543a630403327174410baf",
|
||||
"fillQuoteTransformer": "0xd3afdf4a8ea9183e76c9c2306cda03ea4afffea5",
|
||||
"positiveSlippageFeeTransformer": "0x4cd8f1c0df4d40fcc1e073845d5f6f4ed5cc8dab"
|
||||
}
|
||||
},
|
||||
@@ -334,5 +334,173 @@
|
||||
"fillQuoteTransformer": "0x750cb81ee6d64e29e1e358ba155925000bf044d4",
|
||||
"positiveSlippageFeeTransformer": "0x30aebc4c68effa70e21612b39b94299a8778d0cb"
|
||||
}
|
||||
},
|
||||
"43114": {
|
||||
"erc20Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc721Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"zrxToken": "0x0000000000000000000000000000000000000000",
|
||||
"etherToken": "0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7",
|
||||
"exchangeV2": "0x0000000000000000000000000000000000000000",
|
||||
"exchange": "0x0000000000000000000000000000000000000000",
|
||||
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
|
||||
"zeroExGovernor": "0x0000000000000000000000000000000000000000",
|
||||
"forwarder": "0x0000000000000000000000000000000000000000",
|
||||
"coordinatorRegistry": "0x0000000000000000000000000000000000000000",
|
||||
"coordinator": "0x0000000000000000000000000000000000000000",
|
||||
"multiAssetProxy": "0x0000000000000000000000000000000000000000",
|
||||
"staticCallProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc1155Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"devUtils": "0x0000000000000000000000000000000000000000",
|
||||
"zrxVault": "0x0000000000000000000000000000000000000000",
|
||||
"staking": "0x0000000000000000000000000000000000000000",
|
||||
"stakingProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeSampler": "0x0000000000000000000000000000000000000000",
|
||||
"chaiBridge": "0x0000000000000000000000000000000000000000",
|
||||
"dydxBridge": "0x0000000000000000000000000000000000000000",
|
||||
"godsUnchainedValidator": "0x0000000000000000000000000000000000000000",
|
||||
"broker": "0x0000000000000000000000000000000000000000",
|
||||
"chainlinkStopLimit": "0x0000000000000000000000000000000000000000",
|
||||
"maximumGasPrice": "0x0000000000000000000000000000000000000000",
|
||||
"dexForwarderBridge": "0x0000000000000000000000000000000000000000",
|
||||
"exchangeProxyGovernor": "0xca7bab1b2d1ec7d81710b7f9e2ab4e6788930588",
|
||||
"exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
|
||||
"exchangeProxyTransformerDeployer": "0xa60b57833dce6260f4f2411c811755dd980bc0a7",
|
||||
"exchangeProxyFlashWallet": "0xdb6f1920a889355780af7570773609bd8cb1f498",
|
||||
"exchangeProxyLiquidityProviderSandbox": "0x8953c63d0858d286cc407cd6f8e26b9cbd02a511",
|
||||
"zrxTreasury": "0x0000000000000000000000000000000000000000",
|
||||
"transformers": {
|
||||
"wethTransformer": "0x9b8b52391071d71cd4ad1e61d7f273268fa34c6c",
|
||||
"payTakerTransformer": "0x898c6fde239d646c73f0a57e3570b6f86a3d62a3",
|
||||
"affiliateFeeTransformer": "0x34617b855411e52fbc05899435f44cbd0503022c",
|
||||
"fillQuoteTransformer": "0xd421f50b3ae27f223aa35a04944236d257235412",
|
||||
"positiveSlippageFeeTransformer": "0x470ba89da18a6db6e8a0567b3c9214b960861857"
|
||||
}
|
||||
},
|
||||
"250": {
|
||||
"erc20Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc721Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"zrxToken": "0x0000000000000000000000000000000000000000",
|
||||
"etherToken": "0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83",
|
||||
"exchangeV2": "0x0000000000000000000000000000000000000000",
|
||||
"exchange": "0x0000000000000000000000000000000000000000",
|
||||
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
|
||||
"zeroExGovernor": "0x0000000000000000000000000000000000000000",
|
||||
"forwarder": "0x0000000000000000000000000000000000000000",
|
||||
"coordinatorRegistry": "0x0000000000000000000000000000000000000000",
|
||||
"coordinator": "0x0000000000000000000000000000000000000000",
|
||||
"multiAssetProxy": "0x0000000000000000000000000000000000000000",
|
||||
"staticCallProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc1155Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"devUtils": "0x0000000000000000000000000000000000000000",
|
||||
"zrxVault": "0x0000000000000000000000000000000000000000",
|
||||
"staking": "0x0000000000000000000000000000000000000000",
|
||||
"stakingProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeSampler": "0x0000000000000000000000000000000000000000",
|
||||
"chaiBridge": "0x0000000000000000000000000000000000000000",
|
||||
"dydxBridge": "0x0000000000000000000000000000000000000000",
|
||||
"godsUnchainedValidator": "0x0000000000000000000000000000000000000000",
|
||||
"broker": "0x0000000000000000000000000000000000000000",
|
||||
"chainlinkStopLimit": "0x0000000000000000000000000000000000000000",
|
||||
"maximumGasPrice": "0x0000000000000000000000000000000000000000",
|
||||
"dexForwarderBridge": "0x0000000000000000000000000000000000000000",
|
||||
"exchangeProxyGovernor": "0xf760c5b88d970d6f97e64e264dac5a3767dafd74",
|
||||
"exchangeProxy": "0xdef189deaef76e379df891899eb5a00a94cbc250",
|
||||
"exchangeProxyTransformerDeployer": "0x47f01db18a38261e4cb153bae6db7d3743acb33c",
|
||||
"exchangeProxyFlashWallet": "0xb4d961671cadfed687e040b076eee29840c142e5",
|
||||
"exchangeProxyLiquidityProviderSandbox": "0xca64d4225804f2ae069760cb5ff2f1d8bac1c2f9",
|
||||
"zrxTreasury": "0x0000000000000000000000000000000000000000",
|
||||
"transformers": {
|
||||
"wethTransformer": "0x9b6aa8f26a92108e7d1f66373d757bb955112703",
|
||||
"payTakerTransformer": "0x32df54951d33d7460e15fa59b1fcc262183ce4c2",
|
||||
"affiliateFeeTransformer": "0x67efa679a4b56c38713d478e649c88247f4f8e88",
|
||||
"fillQuoteTransformer": "0x71de60a1b160094a3f6c7e1b883ff9337d639131",
|
||||
"positiveSlippageFeeTransformer": "0xe87d69b285005cc82b53b844322652c49ed64600"
|
||||
}
|
||||
},
|
||||
"42220": {
|
||||
"erc20Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc721Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"zrxToken": "0x0000000000000000000000000000000000000000",
|
||||
"etherToken": "0x471ece3750da237f93b8e339c536989b8978a438",
|
||||
"exchangeV2": "0x0000000000000000000000000000000000000000",
|
||||
"exchange": "0x0000000000000000000000000000000000000000",
|
||||
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
|
||||
"zeroExGovernor": "0x0000000000000000000000000000000000000000",
|
||||
"forwarder": "0x0000000000000000000000000000000000000000",
|
||||
"coordinatorRegistry": "0x0000000000000000000000000000000000000000",
|
||||
"coordinator": "0x0000000000000000000000000000000000000000",
|
||||
"multiAssetProxy": "0x0000000000000000000000000000000000000000",
|
||||
"staticCallProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc1155Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"devUtils": "0x0000000000000000000000000000000000000000",
|
||||
"zrxVault": "0x0000000000000000000000000000000000000000",
|
||||
"staking": "0x0000000000000000000000000000000000000000",
|
||||
"stakingProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeSampler": "0x0000000000000000000000000000000000000000",
|
||||
"chaiBridge": "0x0000000000000000000000000000000000000000",
|
||||
"dydxBridge": "0x0000000000000000000000000000000000000000",
|
||||
"godsUnchainedValidator": "0x0000000000000000000000000000000000000000",
|
||||
"broker": "0x0000000000000000000000000000000000000000",
|
||||
"chainlinkStopLimit": "0x0000000000000000000000000000000000000000",
|
||||
"maximumGasPrice": "0x0000000000000000000000000000000000000000",
|
||||
"dexForwarderBridge": "0x0000000000000000000000000000000000000000",
|
||||
"exchangeProxyGovernor": "0x92115010fd9b170d4918b102efc86b1b7bebdc7f",
|
||||
"exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
|
||||
"exchangeProxyTransformerDeployer": "0x1fe80d5ad9464dba2d60b88e449305f184823f8a",
|
||||
"exchangeProxyFlashWallet": "0xdb6f1920a889355780af7570773609bd8cb1f498",
|
||||
"exchangeProxyLiquidityProviderSandbox": "0x0000000000000000000000000000000000000000",
|
||||
"zrxTreasury": "0x0000000000000000000000000000000000000000",
|
||||
"transformers": {
|
||||
"wethTransformer": "0x948e03e708b4c62c63f89157a3aa76b986c110ed",
|
||||
"payTakerTransformer": "0x90fb6c638ece8f3e4bfda1c6d6425626b53148b0",
|
||||
"affiliateFeeTransformer": "0xc93913692ed073cb0cb37d4a760afd7916e9cb01",
|
||||
"fillQuoteTransformer": "0xa825d4d3c4d2820c52da69fcccf269b4081871f2",
|
||||
"positiveSlippageFeeTransformer": "0x9ffc7a79133ed5242777e40764777a6d5aab282c"
|
||||
}
|
||||
},
|
||||
"10": {
|
||||
"erc20Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc721Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"zrxToken": "0x0000000000000000000000000000000000000000",
|
||||
"etherToken": "0x4200000000000000000000000000000000000006",
|
||||
"exchangeV2": "0x0000000000000000000000000000000000000000",
|
||||
"exchange": "0x0000000000000000000000000000000000000000",
|
||||
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
|
||||
"zeroExGovernor": "0x0000000000000000000000000000000000000000",
|
||||
"forwarder": "0x0000000000000000000000000000000000000000",
|
||||
"coordinatorRegistry": "0x0000000000000000000000000000000000000000",
|
||||
"coordinator": "0x0000000000000000000000000000000000000000",
|
||||
"multiAssetProxy": "0x0000000000000000000000000000000000000000",
|
||||
"staticCallProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc1155Proxy": "0x0000000000000000000000000000000000000000",
|
||||
"devUtils": "0x0000000000000000000000000000000000000000",
|
||||
"zrxVault": "0x0000000000000000000000000000000000000000",
|
||||
"staking": "0x0000000000000000000000000000000000000000",
|
||||
"stakingProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeProxy": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeSampler": "0x0000000000000000000000000000000000000000",
|
||||
"chaiBridge": "0x0000000000000000000000000000000000000000",
|
||||
"dydxBridge": "0x0000000000000000000000000000000000000000",
|
||||
"godsUnchainedValidator": "0x0000000000000000000000000000000000000000",
|
||||
"broker": "0x0000000000000000000000000000000000000000",
|
||||
"chainlinkStopLimit": "0x0000000000000000000000000000000000000000",
|
||||
"maximumGasPrice": "0x0000000000000000000000000000000000000000",
|
||||
"dexForwarderBridge": "0x0000000000000000000000000000000000000000",
|
||||
"exchangeProxyGovernor": "0x6d506b2847df0c6f04d2628da1adaf4d8fb2e81b",
|
||||
"exchangeProxy": "0xdef1abe32c034e558cdd535791643c58a13acc10",
|
||||
"exchangeProxyTransformerDeployer": "0x3a539ed6bd42de8fbaf3899fb490c792e153d647",
|
||||
"exchangeProxyFlashWallet": "0xa3128d9b7cca7d5af29780a56abeec12b05a6740",
|
||||
"exchangeProxyLiquidityProviderSandbox": "0x0000000000000000000000000000000000000000",
|
||||
"zrxTreasury": "0x0000000000000000000000000000000000000000",
|
||||
"transformers": {
|
||||
"wethTransformer": "0x02ce7af6520e2862f961f5d7eda746642865179c",
|
||||
"payTakerTransformer": "0x085d10a34f14f6a631ea8ff7d016782ee3ffaa11",
|
||||
"affiliateFeeTransformer": "0x55cf1d7535250db75bf0190493f55781ee583553",
|
||||
"fillQuoteTransformer": "0x3543ef833d28b7e983c293856561f21a7f089f1d",
|
||||
"positiveSlippageFeeTransformer": "0xb11e14565dfbeb702dea9bc0cb47f1a8b32f4783"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contract-addresses",
|
||||
"version": "6.6.1",
|
||||
"version": "6.11.0",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
|
@@ -52,6 +52,10 @@ export enum ChainId {
|
||||
BSC = 56,
|
||||
Polygon = 137,
|
||||
PolygonMumbai = 80001,
|
||||
Avalanche = 43114,
|
||||
Fantom = 250,
|
||||
Celo = 42220,
|
||||
Optimism = 10,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,4 +1,23 @@
|
||||
[
|
||||
{
|
||||
"version": "3.16.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update IZeroEx and ITransformERC20 artifacts",
|
||||
"pr": 282
|
||||
}
|
||||
],
|
||||
"timestamp": 1632957537
|
||||
},
|
||||
{
|
||||
"timestamp": 1631710679,
|
||||
"version": "3.15.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "3.15.0",
|
||||
"changes": [
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user