Compare commits
19 Commits
protocol@b
...
protocol@e
Author | SHA1 | Date | |
---|---|---|---|
|
e77958425f | ||
|
6af4d71573 | ||
|
ee985240fb | ||
|
2aadbda527 | ||
|
297c73abcc | ||
|
4c9e1b21ec | ||
|
41685d1545 | ||
|
b9c25112ed | ||
|
f0738fc122 | ||
|
42baf504b7 | ||
|
56038d122f | ||
|
c4446b6c0e | ||
|
eaed2958c3 | ||
|
a045a3afb8 | ||
|
1cc59ab1ab | ||
|
2c6a714b71 | ||
|
d8c97d6720 | ||
|
d6bc702550 | ||
|
2838cb9420 |
18
CODEOWNERS
18
CODEOWNERS
@@ -1,18 +1,20 @@
|
||||
# See https://help.github.com/articles/about-codeowners/
|
||||
|
||||
# for more info about CODEOWNERS file
|
||||
|
||||
# It uses the same pattern rule for gitignore file
|
||||
|
||||
# https://git-scm.com/docs/gitignore#_pattern_format
|
||||
|
||||
# Website
|
||||
packages/asset-swapper/ @BMillman19 @fragosti @dave4506
|
||||
packages/instant/ @BMillman19 @fragosti @dave4506
|
||||
packages/asset-swapper/ @dekz @mzhu25 @dextracker @kh-chang
|
||||
|
||||
# Dev tools & setup
|
||||
.circleci/ @dorothy-zbornak
|
||||
packages/contract-addresses/ @abandeali1
|
||||
packages/contract-artifacts/ @abandeali1
|
||||
packages/order-utils/ @dorothy-zbornak
|
||||
|
||||
.circleci/ @dekz @mzhu25
|
||||
packages/contract-addresses/ @dekz @mzhu25 @dextracker @kh-chang
|
||||
packages/contract-artifacts/ @dekz @mzhu25
|
||||
packages/protocol-utils/ @dekz @mzhu25
|
||||
|
||||
# Protocol/smart contracts
|
||||
contracts/ @abandeali1 @hysz @dorothy-zbornak @mzhu25
|
||||
|
||||
contracts/ @dekz @mzhu25 @dextracker
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1655244958,
|
||||
"version": "3.3.32",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1654284040,
|
||||
"version": "3.3.31",
|
||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.3.32 - _June 14, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.31 - _June 3, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-erc20",
|
||||
"version": "3.3.31",
|
||||
"version": "3.3.32",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -53,8 +53,8 @@
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.8.0",
|
||||
"@0x/contracts-gen": "^2.0.46",
|
||||
"@0x/contracts-test-utils": "^5.4.22",
|
||||
"@0x/contracts-utils": "^4.8.12",
|
||||
"@0x/contracts-test-utils": "^5.4.23",
|
||||
"@0x/contracts-utils": "^4.8.13",
|
||||
"@0x/dev-utils": "^4.2.14",
|
||||
"@0x/sol-compiler": "^4.8.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1655244958,
|
||||
"version": "5.4.23",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1654284040,
|
||||
"version": "5.4.22",
|
||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v5.4.23 - _June 14, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.22 - _June 3, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-test-utils",
|
||||
"version": "5.4.22",
|
||||
"version": "5.4.23",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -44,7 +44,7 @@
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.34",
|
||||
"@0x/base-contract": "^6.5.0",
|
||||
"@0x/contract-addresses": "^6.15.0",
|
||||
"@0x/contract-addresses": "^6.16.0",
|
||||
"@0x/dev-utils": "^4.2.14",
|
||||
"@0x/json-schemas": "^6.4.4",
|
||||
"@0x/order-utils": "^10.4.28",
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1655244958,
|
||||
"version": "1.4.15",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1654284040,
|
||||
"version": "1.4.14",
|
||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.4.15 - _June 14, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.14 - _June 3, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-treasury",
|
||||
"version": "1.4.14",
|
||||
"version": "1.4.15",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -47,12 +47,12 @@
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/treasury",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.8.0",
|
||||
"@0x/contract-addresses": "^6.15.0",
|
||||
"@0x/contract-addresses": "^6.16.0",
|
||||
"@0x/contracts-asset-proxy": "^3.7.19",
|
||||
"@0x/contracts-erc20": "^3.3.31",
|
||||
"@0x/contracts-erc20": "^3.3.32",
|
||||
"@0x/contracts-gen": "^2.0.46",
|
||||
"@0x/contracts-staking": "^2.0.45",
|
||||
"@0x/contracts-test-utils": "^5.4.22",
|
||||
"@0x/contracts-test-utils": "^5.4.23",
|
||||
"@0x/sol-compiler": "^4.8.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.4",
|
||||
@@ -73,7 +73,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.5.0",
|
||||
"@0x/protocol-utils": "^11.14.0",
|
||||
"@0x/protocol-utils": "^11.15.0",
|
||||
"@0x/subproviders": "^6.6.5",
|
||||
"@0x/types": "^3.3.6",
|
||||
"@0x/typescript-typings": "^5.3.1",
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1655244958,
|
||||
"version": "4.8.13",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1654284040,
|
||||
"version": "4.8.12",
|
||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v4.8.13 - _June 14, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.12 - _June 3, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-utils",
|
||||
"version": "4.8.12",
|
||||
"version": "4.8.13",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,7 +52,7 @@
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.8.0",
|
||||
"@0x/contracts-gen": "^2.0.46",
|
||||
"@0x/contracts-test-utils": "^5.4.22",
|
||||
"@0x/contracts-test-utils": "^5.4.23",
|
||||
"@0x/dev-utils": "^4.2.14",
|
||||
"@0x/order-utils": "^10.4.28",
|
||||
"@0x/sol-compiler": "^4.8.1",
|
||||
|
@@ -1,4 +1,14 @@
|
||||
[
|
||||
{
|
||||
"version": "0.35.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Adds support for Velodrome OptimismBridgeAdapter",
|
||||
"pr": 494
|
||||
}
|
||||
],
|
||||
"timestamp": 1655244958
|
||||
},
|
||||
{
|
||||
"version": "0.34.0",
|
||||
"changes": [
|
||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v0.35.0 - _June 14, 2022_
|
||||
|
||||
* Adds support for Velodrome OptimismBridgeAdapter (#494)
|
||||
|
||||
## v0.34.0 - _June 3, 2022_
|
||||
|
||||
* Splits BridgeAdapter up by chain (#487)
|
||||
|
@@ -56,4 +56,5 @@ library BridgeProtocols {
|
||||
uint128 internal constant GMX = 26;
|
||||
uint128 internal constant PLATYPUS = 27;
|
||||
uint128 internal constant BANCORV3 = 28;
|
||||
uint128 internal constant VELODROME = 29;
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ import "./mixins/MixinCurve.sol";
|
||||
import "./mixins/MixinCurveV2.sol";
|
||||
import "./mixins/MixinNerve.sol";
|
||||
import "./mixins/MixinUniswapV3.sol";
|
||||
import "./mixins/MixinVelodrome.sol";
|
||||
import "./mixins/MixinZeroExBridge.sol";
|
||||
|
||||
contract OptimismBridgeAdapter is
|
||||
@@ -34,6 +35,7 @@ contract OptimismBridgeAdapter is
|
||||
MixinCurveV2,
|
||||
MixinNerve,
|
||||
MixinUniswapV3,
|
||||
MixinVelodrome,
|
||||
MixinZeroExBridge
|
||||
{
|
||||
constructor(IEtherTokenV06 weth)
|
||||
@@ -83,6 +85,14 @@ contract OptimismBridgeAdapter is
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else if (protocolId == BridgeProtocols.VELODROME) {
|
||||
if (dryRun) { return (0, true); }
|
||||
boughtAmount = _tradeVelodrome(
|
||||
sellToken,
|
||||
buyToken,
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else if (protocolId == BridgeProtocols.UNKNOWN) {
|
||||
if (dryRun) { return (0, true); }
|
||||
boughtAmount = _tradeZeroExBridge(
|
||||
|
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2022 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";
|
||||
|
||||
interface IVelodromeRouter {
|
||||
function swapExactTokensForTokensSimple(
|
||||
uint256 amountIn,
|
||||
uint256 amountOutMin,
|
||||
address tokenFrom,
|
||||
address tokenTo,
|
||||
bool stable,
|
||||
address to,
|
||||
uint256 deadline
|
||||
) external returns (uint256[] memory amounts);
|
||||
}
|
||||
|
||||
contract MixinVelodrome {
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
|
||||
function _tradeVelodrome(
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
|
||||
(IVelodromeRouter router, bool stable) = abi.decode(bridgeData, (IVelodromeRouter, bool));
|
||||
sellToken.approveIfBelow(address(router), sellAmount);
|
||||
|
||||
boughtAmount = router.swapExactTokensForTokensSimple(
|
||||
sellAmount,
|
||||
0,
|
||||
address(sellToken),
|
||||
address(buyToken),
|
||||
stable,
|
||||
address(this),
|
||||
block.timestamp + 1
|
||||
)[1];
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-zero-ex",
|
||||
"version": "0.34.0",
|
||||
"version": "0.35.0",
|
||||
"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,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|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/@(AbstractBridgeAdapter|AffiliateFeeTransformer|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinVelodrome|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|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",
|
||||
@@ -56,10 +56,10 @@
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/zero-ex",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.8.0",
|
||||
"@0x/contract-addresses": "^6.15.0",
|
||||
"@0x/contracts-erc20": "^3.3.31",
|
||||
"@0x/contract-addresses": "^6.16.0",
|
||||
"@0x/contracts-erc20": "^3.3.32",
|
||||
"@0x/contracts-gen": "^2.0.46",
|
||||
"@0x/contracts-test-utils": "^5.4.22",
|
||||
"@0x/contracts-test-utils": "^5.4.23",
|
||||
"@0x/dev-utils": "^4.2.14",
|
||||
"@0x/order-utils": "^10.4.28",
|
||||
"@0x/sol-compiler": "^4.8.1",
|
||||
@@ -83,7 +83,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.5.0",
|
||||
"@0x/protocol-utils": "^11.14.0",
|
||||
"@0x/protocol-utils": "^11.15.0",
|
||||
"@0x/subproviders": "^6.6.5",
|
||||
"@0x/types": "^3.3.6",
|
||||
"@0x/typescript-typings": "^5.3.1",
|
||||
|
@@ -127,6 +127,7 @@ import * as MixinShell from '../test/generated-artifacts/MixinShell.json';
|
||||
import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json';
|
||||
import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json';
|
||||
import * as MixinUniswapV3 from '../test/generated-artifacts/MixinUniswapV3.json';
|
||||
import * as MixinVelodrome from '../test/generated-artifacts/MixinVelodrome.json';
|
||||
import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json';
|
||||
import * as MooniswapLiquidityProvider from '../test/generated-artifacts/MooniswapLiquidityProvider.json';
|
||||
import * as MultiplexFeature from '../test/generated-artifacts/MultiplexFeature.json';
|
||||
@@ -349,6 +350,7 @@ export const artifacts = {
|
||||
MixinUniswap: MixinUniswap as ContractArtifact,
|
||||
MixinUniswapV2: MixinUniswapV2 as ContractArtifact,
|
||||
MixinUniswapV3: MixinUniswapV3 as ContractArtifact,
|
||||
MixinVelodrome: MixinVelodrome as ContractArtifact,
|
||||
MixinZeroExBridge: MixinZeroExBridge as ContractArtifact,
|
||||
IERC1155Token: IERC1155Token as ContractArtifact,
|
||||
IERC721Token: IERC721Token as ContractArtifact,
|
||||
|
@@ -125,6 +125,7 @@ export * from '../test/generated-wrappers/mixin_shell';
|
||||
export * from '../test/generated-wrappers/mixin_uniswap';
|
||||
export * from '../test/generated-wrappers/mixin_uniswap_v2';
|
||||
export * from '../test/generated-wrappers/mixin_uniswap_v3';
|
||||
export * from '../test/generated-wrappers/mixin_velodrome';
|
||||
export * from '../test/generated-wrappers/mixin_zero_ex_bridge';
|
||||
export * from '../test/generated-wrappers/mooniswap_liquidity_provider';
|
||||
export * from '../test/generated-wrappers/multiplex_feature';
|
||||
|
@@ -164,6 +164,7 @@
|
||||
"test/generated-artifacts/MixinUniswap.json",
|
||||
"test/generated-artifacts/MixinUniswapV2.json",
|
||||
"test/generated-artifacts/MixinUniswapV3.json",
|
||||
"test/generated-artifacts/MixinVelodrome.json",
|
||||
"test/generated-artifacts/MixinZeroExBridge.json",
|
||||
"test/generated-artifacts/MooniswapLiquidityProvider.json",
|
||||
"test/generated-artifacts/MultiplexFeature.json",
|
||||
|
@@ -1,4 +1,67 @@
|
||||
[
|
||||
{
|
||||
"version": "16.63.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Remove JS router",
|
||||
"pr": 480
|
||||
},
|
||||
{
|
||||
"note": "Removed Median price in favour of best gas adjusted price",
|
||||
"pr": 480
|
||||
}
|
||||
],
|
||||
"timestamp": 1656491792
|
||||
},
|
||||
{
|
||||
"version": "16.62.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Offboard Smoothy and ComethSwap",
|
||||
"pr": 509
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "16.62.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Remove nUSD from intermediate liquidity to save on sampler gas",
|
||||
"pr": 505
|
||||
}
|
||||
],
|
||||
"timestamp": 1655253622
|
||||
},
|
||||
{
|
||||
"version": "16.62.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add MDEX on BSC",
|
||||
"pr": 496
|
||||
},
|
||||
{
|
||||
"note": "Add KnightSwap on BSC",
|
||||
"pr": 498
|
||||
},
|
||||
{
|
||||
"note": "Add Velodrome support on Optimism",
|
||||
"pr": 494
|
||||
},
|
||||
{
|
||||
"note": "Do not send empty entries on Quote Report",
|
||||
"pr": 501
|
||||
},
|
||||
{
|
||||
"note": "KnightSwap/Mdex cosmetic change",
|
||||
"pr": 502
|
||||
},
|
||||
{
|
||||
"note": "Offboard JetSwap, CafeSwap, JulSwap, and PolyDex",
|
||||
"pr": 503
|
||||
}
|
||||
],
|
||||
"timestamp": 1655244958
|
||||
},
|
||||
{
|
||||
"version": "16.61.0",
|
||||
"changes": [
|
||||
|
@@ -5,6 +5,28 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v16.63.0 - _June 29, 2022_
|
||||
|
||||
* Remove JS router (#480)
|
||||
* Removed Median price in favour of best gas adjusted price (#480)
|
||||
|
||||
## v16.62.2 - _Invalid date_
|
||||
|
||||
* Offboard Smoothy and ComethSwap (#509)
|
||||
|
||||
## v16.62.1 - _June 15, 2022_
|
||||
|
||||
* Remove nUSD from intermediate liquidity to save on sampler gas (#505)
|
||||
|
||||
## v16.62.0 - _June 14, 2022_
|
||||
|
||||
* Add MDEX on BSC (#496)
|
||||
* Add KnightSwap on BSC (#498)
|
||||
* Add Velodrome support on Optimism (#494)
|
||||
* Do not send empty entries on Quote Report (#501)
|
||||
* KnightSwap/Mdex cosmetic change (#502)
|
||||
* Offboard JetSwap, CafeSwap, JulSwap, and PolyDex (#503)
|
||||
|
||||
## v16.61.0 - _June 3, 2022_
|
||||
|
||||
* Add stETH wrap/unwrap support (#476)
|
||||
|
@@ -39,11 +39,11 @@ import "./MooniswapSampler.sol";
|
||||
import "./NativeOrderSampler.sol";
|
||||
import "./PlatypusSampler.sol";
|
||||
import "./ShellSampler.sol";
|
||||
import "./SmoothySampler.sol";
|
||||
import "./TwoHopSampler.sol";
|
||||
import "./UniswapSampler.sol";
|
||||
import "./UniswapV2Sampler.sol";
|
||||
import "./UniswapV3Sampler.sol";
|
||||
import "./VelodromeSampler.sol";
|
||||
import "./UtilitySampler.sol";
|
||||
|
||||
|
||||
@@ -67,11 +67,11 @@ contract ERC20BridgeSampler is
|
||||
NativeOrderSampler,
|
||||
PlatypusSampler,
|
||||
ShellSampler,
|
||||
SmoothySampler,
|
||||
TwoHopSampler,
|
||||
UniswapSampler,
|
||||
UniswapV2Sampler,
|
||||
UniswapV3Sampler,
|
||||
VelodromeSampler,
|
||||
UtilitySampler
|
||||
{
|
||||
|
||||
|
@@ -1,156 +0,0 @@
|
||||
// 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
// import "./interfaces/ISmoothy.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
import "./interfaces/ISmoothy.sol";
|
||||
|
||||
contract SmoothySampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
/// @dev Information for sampling from smoothy sources.
|
||||
struct SmoothyInfo {
|
||||
address poolAddress;
|
||||
bytes4 sellQuoteFunctionSelector;
|
||||
bytes4 buyQuoteFunctionSelector;
|
||||
}
|
||||
|
||||
/// @dev Base gas limit for Smoothy calls.
|
||||
uint256 constant private SMOOTHY_CALL_GAS = 600e3;
|
||||
|
||||
/// @dev Sample sell quotes from Smoothy.
|
||||
/// @param smoothyInfo Smoothy information specific to this token pair.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromSmoothy(
|
||||
SmoothyInfo memory smoothyInfo,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
// Basically a Curve fork
|
||||
|
||||
// Smoothy only keep a percentage of its tokens available in reserve
|
||||
uint256 poolReserveMakerAmount = ISmoothy(smoothyInfo.poolAddress).getBalance(uint256(toTokenIdx)) -
|
||||
ISmoothy(smoothyInfo.poolAddress)._yBalances(uint256(toTokenIdx));
|
||||
(, , , uint256 decimals) = ISmoothy(smoothyInfo.poolAddress).getTokenStats(uint256(toTokenIdx));
|
||||
poolReserveMakerAmount = poolReserveMakerAmount/(10**(18-decimals));
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
smoothyInfo.poolAddress.staticcall.gas(SMOOTHY_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
smoothyInfo.sellQuoteFunctionSelector,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
|
||||
// Make sure the quoted buyAmount is available in the pool reserve
|
||||
if (buyAmount >= poolReserveMakerAmount) {
|
||||
// Assign pool reserve amount for all higher samples to break early
|
||||
for (uint256 j = i; j < numSamples; j++) {
|
||||
makerTokenAmounts[j] = poolReserveMakerAmount;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Smoothy.
|
||||
/// @param smoothyInfo Smoothy information specific to this token pair.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromSmoothy(
|
||||
SmoothyInfo memory smoothyInfo,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
// Buys not supported so approximate it.
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(toTokenIdx, smoothyInfo),
|
||||
takerTokenData: abi.encode(fromTokenIdx, smoothyInfo),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromSmoothy
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromSmoothy(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(int128 takerTokenIdx, SmoothyInfo memory smoothyInfo) =
|
||||
abi.decode(takerTokenData, (int128, SmoothyInfo));
|
||||
(int128 makerTokenIdx) =
|
||||
abi.decode(makerTokenData, (int128));
|
||||
(bool success, bytes memory resultData) =
|
||||
address(this).staticcall(abi.encodeWithSelector(
|
||||
this.sampleSellsFromSmoothy.selector,
|
||||
smoothyInfo,
|
||||
takerTokenIdx,
|
||||
makerTokenIdx,
|
||||
_toSingleValueArray(sellAmount)
|
||||
));
|
||||
if (!success) {
|
||||
return 0;
|
||||
}
|
||||
// solhint-disable-next-line indent
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
}
|
||||
}
|
134
packages/asset-swapper/contracts/src/VelodromeSampler.sol
Normal file
134
packages/asset-swapper/contracts/src/VelodromeSampler.sol
Normal file
@@ -0,0 +1,134 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2022 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 './ApproximateBuys.sol';
|
||||
import './SamplerUtils.sol';
|
||||
|
||||
struct VeloRoute {
|
||||
address from;
|
||||
address to;
|
||||
bool stable;
|
||||
}
|
||||
|
||||
interface IVelodromeRouter {
|
||||
function getAmountOut(
|
||||
uint256 amountIn,
|
||||
address tokenIn,
|
||||
address tokenOut
|
||||
) external view returns (uint256 amount, bool stable);
|
||||
|
||||
function getAmountsOut(uint256 amountIn, VeloRoute[] calldata routes)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory amounts);
|
||||
}
|
||||
|
||||
contract VelodromeSampler is SamplerUtils, ApproximateBuys {
|
||||
/// @dev Sample sell quotes from Velodrome
|
||||
/// @param router Address of Velodrome router.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample (sorted in ascending order).
|
||||
/// @return stable Whether the pool is a stable pool (vs volatile).
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
|
||||
function sampleSellsFromVelodrome(
|
||||
IVelodromeRouter router,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
) public view returns (bool stable, uint256[] memory makerTokenAmounts) {
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
// Sampling should not mix stable and volatile pools.
|
||||
// Find the most liquid pool based on max(takerTokenAmounts) and stick with it.
|
||||
stable = _isMostLiquidPoolStablePool(router, takerToken, makerToken, takerTokenAmounts);
|
||||
VeloRoute[] memory routes = new VeloRoute[](1);
|
||||
routes[0] = VeloRoute({ from: takerToken, to: makerToken, stable: stable });
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] = router.getAmountsOut(takerTokenAmounts[i], routes)[1];
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Velodrome.
|
||||
/// @param router Address of Velodrome router.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return stable Whether the pool is a stable pool (vs volatile).
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
|
||||
function sampleBuysFromVelodrome(
|
||||
IVelodromeRouter router,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
) public view returns (bool stable, uint256[] memory takerTokenAmounts) {
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
|
||||
// Sampling should not mix stable and volatile pools.
|
||||
// Find the most liquid pool based on the reverse swap (maker -> taker) and stick with it.
|
||||
stable = _isMostLiquidPoolStablePool(router, makerToken, takerToken, makerTokenAmounts);
|
||||
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
takerTokenData: abi.encode(router, VeloRoute({ from: takerToken, to: makerToken, stable: stable })),
|
||||
makerTokenData: abi.encode(router, VeloRoute({ from: makerToken, to: takerToken, stable: stable })),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromVelodrome
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromVelodrome(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory, /* makerTokenData */
|
||||
uint256 sellAmount
|
||||
) internal view returns (uint256) {
|
||||
(IVelodromeRouter router, VeloRoute memory route) = abi.decode(takerTokenData, (IVelodromeRouter, VeloRoute));
|
||||
|
||||
VeloRoute[] memory routes = new VeloRoute[](1);
|
||||
routes[0] = route;
|
||||
return router.getAmountsOut(sellAmount, routes)[1];
|
||||
}
|
||||
|
||||
/// @dev Returns whether the most liquid pool is a stable pool.
|
||||
/// @param router Address of Velodrome router.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token buy amount for each sample (sorted in ascending order)
|
||||
/// @return stable Whether the pool is a stable pool (vs volatile).
|
||||
function _isMostLiquidPoolStablePool(
|
||||
IVelodromeRouter router,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
) internal view returns (bool stable) {
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
(, stable) = router.getAmountOut(takerTokenAmounts[numSamples - 1], takerToken, makerToken);
|
||||
}
|
||||
}
|
@@ -1,45 +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;
|
||||
|
||||
|
||||
interface ISmoothy {
|
||||
|
||||
function getBalance (
|
||||
uint256 tid
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 balance);
|
||||
|
||||
function _yBalances (
|
||||
uint256 tid
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 balance);
|
||||
|
||||
function getTokenStats (
|
||||
uint256 tid
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 softWeight, uint256 hardWeight, uint256 balance, uint256 decimals);
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/asset-swapper",
|
||||
"version": "16.61.0",
|
||||
"version": "16.63.0",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -40,7 +40,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|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|SmoothySampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json",
|
||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler|VelodromeSampler).json",
|
||||
"postpublish": {
|
||||
"assets": []
|
||||
}
|
||||
@@ -61,14 +61,14 @@
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.34",
|
||||
"@0x/base-contract": "^6.5.0",
|
||||
"@0x/contract-addresses": "^6.15.0",
|
||||
"@0x/contract-wrappers": "^13.20.3",
|
||||
"@0x/contracts-erc20": "^3.3.31",
|
||||
"@0x/contracts-zero-ex": "^0.34.0",
|
||||
"@0x/contract-addresses": "^6.16.0",
|
||||
"@0x/contract-wrappers": "^13.20.4",
|
||||
"@0x/contracts-erc20": "^3.3.32",
|
||||
"@0x/contracts-zero-ex": "^0.35.0",
|
||||
"@0x/dev-utils": "^4.2.14",
|
||||
"@0x/json-schemas": "^6.4.4",
|
||||
"@0x/neon-router": "^0.3.5",
|
||||
"@0x/protocol-utils": "^11.14.0",
|
||||
"@0x/protocol-utils": "^11.15.0",
|
||||
"@0x/quote-server": "^6.0.6",
|
||||
"@0x/types": "^3.3.6",
|
||||
"@0x/typescript-typings": "^5.3.1",
|
||||
@@ -100,8 +100,8 @@
|
||||
"@0x/contracts-exchange": "^3.2.38",
|
||||
"@0x/contracts-exchange-libs": "^4.3.37",
|
||||
"@0x/contracts-gen": "^2.0.46",
|
||||
"@0x/contracts-test-utils": "^5.4.22",
|
||||
"@0x/contracts-utils": "^4.8.12",
|
||||
"@0x/contracts-test-utils": "^5.4.23",
|
||||
"@0x/contracts-utils": "^4.8.13",
|
||||
"@0x/mesh-rpc-client": "^9.4.2",
|
||||
"@0x/sol-compiler": "^4.8.1",
|
||||
"@0x/subproviders": "^6.6.5",
|
||||
|
@@ -4,7 +4,7 @@ export {
|
||||
ContractTxFunctionObj,
|
||||
SendTransactionOpts,
|
||||
} from '@0x/base-contract';
|
||||
export { ContractAddresses } from '@0x/contract-addresses';
|
||||
export { ContractAddresses, ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
export {
|
||||
V4RFQFirmQuote,
|
||||
V4RFQIndicativeQuote,
|
||||
@@ -132,6 +132,7 @@ export {
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
||||
ZERO_AMOUNT,
|
||||
} from './utils/market_operation_utils/constants';
|
||||
export {
|
||||
Parameters,
|
||||
@@ -141,7 +142,6 @@ export {
|
||||
export {
|
||||
BalancerFillData,
|
||||
BancorFillData,
|
||||
CollapsedFill,
|
||||
CurveFillData,
|
||||
CurveFunctionSelectors,
|
||||
CurveInfo,
|
||||
@@ -150,7 +150,9 @@ export {
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
FeeSchedule,
|
||||
GasSchedule,
|
||||
Fill,
|
||||
FillAdjustor,
|
||||
FillData,
|
||||
GetMarketOrdersRfqOpts,
|
||||
LiquidityProviderFillData,
|
||||
@@ -159,7 +161,6 @@ export {
|
||||
MarketDepthSide,
|
||||
MooniswapFillData,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
NativeRfqOrderFillData,
|
||||
NativeLimitOrderFillData,
|
||||
NativeFillData,
|
||||
@@ -168,6 +169,7 @@ export {
|
||||
TokenAdjacencyGraph,
|
||||
UniswapV2FillData,
|
||||
} from './utils/market_operation_utils/types';
|
||||
export { IdentityFillAdjustor } from './utils/market_operation_utils/identity_fill_adjustor';
|
||||
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||
export {
|
||||
BridgeQuoteReportEntry,
|
||||
@@ -191,3 +193,5 @@ export type Native = ERC20BridgeSource.Native;
|
||||
export type MultiHop = ERC20BridgeSource.MultiHop;
|
||||
|
||||
export { rfqtMocker, RfqtQuoteEndpoint } from './utils/rfqt_mocker';
|
||||
|
||||
export { adjustOutput } from './utils/market_operation_utils/fills';
|
||||
|
@@ -75,9 +75,7 @@ const PANCAKE_SWAP_FORKS = [
|
||||
ERC20BridgeSource.BakerySwap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.CafeSwap,
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
ERC20BridgeSource.JulSwap,
|
||||
];
|
||||
const FAKE_PROVIDER: any = {
|
||||
sendAsync(): void {
|
||||
@@ -222,9 +220,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
ERC20BridgeSource.BakerySwap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.CafeSwap,
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
ERC20BridgeSource.JulSwap,
|
||||
])
|
||||
) {
|
||||
const source = slippedOrders[0].source;
|
||||
@@ -286,7 +282,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
// ETH buy/sell is supported
|
||||
![sellToken, buyToken].includes(NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet])
|
||||
) {
|
||||
const fillData = slippedOrders[0].fills[0].fillData as CurveFillData;
|
||||
const fillData = slippedOrders[0].fillData as CurveFillData;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToLiquidityProvider(
|
||||
@@ -315,7 +311,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
this.chainId === ChainId.Mainnet &&
|
||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Mooniswap])
|
||||
) {
|
||||
const fillData = slippedOrders[0].fills[0].fillData as MooniswapFillData;
|
||||
const fillData = slippedOrders[0].fillData as MooniswapFillData;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToLiquidityProvider(
|
||||
|
@@ -33,8 +33,8 @@ import { DexOrderSampler } from './utils/market_operation_utils/sampler';
|
||||
import { SourceFilters } from './utils/market_operation_utils/source_filters';
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
FeeSchedule,
|
||||
FillData,
|
||||
GasSchedule,
|
||||
GetMarketOrdersOpts,
|
||||
MarketDepth,
|
||||
MarketDepthSide,
|
||||
@@ -366,9 +366,11 @@ export class SwapQuoter {
|
||||
const calcOpts: GetMarketOrdersOpts = {
|
||||
...cloneOpts,
|
||||
gasPrice,
|
||||
feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData: FillData) =>
|
||||
gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)),
|
||||
),
|
||||
feeSchedule: _.mapValues(opts.gasSchedule, gasCost => (fillData: FillData) => {
|
||||
const gas = gasCost ? gasCost(fillData) : 0;
|
||||
const fee = gasPrice.times(gas);
|
||||
return { gas, fee };
|
||||
}),
|
||||
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
|
||||
};
|
||||
// pass the QuoteRequestor on if rfqt enabled
|
||||
@@ -502,7 +504,7 @@ function createSwapQuote(
|
||||
operation: MarketOperation,
|
||||
assetFillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
gasSchedule: FeeSchedule,
|
||||
gasSchedule: GasSchedule,
|
||||
slippage: number,
|
||||
): SwapQuote {
|
||||
const {
|
||||
@@ -562,7 +564,7 @@ function calculateQuoteInfo(
|
||||
operation: MarketOperation,
|
||||
assetFillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
gasSchedule: FeeSchedule,
|
||||
gasSchedule: GasSchedule,
|
||||
slippage: number,
|
||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
||||
const bestCaseFillResult = simulateBestCaseFill({
|
||||
@@ -591,25 +593,23 @@ function calculateQuoteInfo(
|
||||
function calculateTwoHopQuoteInfo(
|
||||
optimizedOrders: OptimizedMarketOrder[],
|
||||
operation: MarketOperation,
|
||||
gasSchedule: FeeSchedule,
|
||||
gasSchedule: GasSchedule,
|
||||
slippage: number,
|
||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
||||
const [firstHopOrder, secondHopOrder] = optimizedOrders;
|
||||
const [firstHopFill] = firstHopOrder.fills;
|
||||
const [secondHopFill] = secondHopOrder.fills;
|
||||
const gas = new BigNumber(
|
||||
gasSchedule[ERC20BridgeSource.MultiHop]!({
|
||||
firstHopSource: _.pick(firstHopFill, 'source', 'fillData'),
|
||||
secondHopSource: _.pick(secondHopFill, 'source', 'fillData'),
|
||||
firstHopSource: _.pick(firstHopOrder, 'source', 'fillData'),
|
||||
secondHopSource: _.pick(secondHopOrder, 'source', 'fillData'),
|
||||
}),
|
||||
).toNumber();
|
||||
const isSell = operation === MarketOperation.Sell;
|
||||
|
||||
return {
|
||||
bestCaseQuoteInfo: {
|
||||
makerAmount: isSell ? secondHopFill.output : secondHopFill.input,
|
||||
takerAmount: isSell ? firstHopFill.input : firstHopFill.output,
|
||||
totalTakerAmount: isSell ? firstHopFill.input : firstHopFill.output,
|
||||
makerAmount: isSell ? secondHopOrder.fill.output : secondHopOrder.fill.input,
|
||||
takerAmount: isSell ? firstHopOrder.fill.input : firstHopOrder.fill.output,
|
||||
totalTakerAmount: isSell ? firstHopOrder.fill.input : firstHopOrder.fill.output,
|
||||
feeTakerTokenAmount: constants.ZERO_AMOUNT,
|
||||
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
||||
gas,
|
||||
@@ -635,7 +635,7 @@ function calculateTwoHopQuoteInfo(
|
||||
[ERC20BridgeSource.MultiHop]: {
|
||||
proportion: new BigNumber(1),
|
||||
intermediateToken: secondHopOrder.takerToken,
|
||||
hops: [firstHopFill.source, secondHopFill.source],
|
||||
hops: [firstHopOrder.source, secondHopOrder.source],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -7,9 +7,7 @@ import {
|
||||
BAKERYSWAP_ROUTER_BY_CHAIN_ID,
|
||||
BELT_BSC_INFOS,
|
||||
BISWAP_ROUTER_BY_CHAIN_ID,
|
||||
CAFESWAP_ROUTER_BY_CHAIN_ID,
|
||||
CHEESESWAP_ROUTER_BY_CHAIN_ID,
|
||||
COMETHSWAP_ROUTER_BY_CHAIN_ID,
|
||||
COMPONENT_POOLS_BY_CHAIN_ID,
|
||||
CRYPTO_COM_ROUTER_BY_CHAIN_ID,
|
||||
CURVE_AVALANCHE_INFOS,
|
||||
@@ -26,9 +24,9 @@ import {
|
||||
FIREBIRDONESWAP_BSC_INFOS,
|
||||
FIREBIRDONESWAP_POLYGON_INFOS,
|
||||
IRONSWAP_POLYGON_INFOS,
|
||||
JETSWAP_ROUTER_BY_CHAIN_ID,
|
||||
JULSWAP_ROUTER_BY_CHAIN_ID,
|
||||
KNIGHTSWAP_ROUTER_BY_CHAIN_ID,
|
||||
MAX_DODOV2_POOLS_QUERIED,
|
||||
MDEX_ROUTER_BY_CHAIN_ID,
|
||||
MESHSWAP_ROUTER_BY_CHAIN_ID,
|
||||
MOBIUSMONEY_CELO_INFOS,
|
||||
MORPHEUSSWAP_ROUTER_BY_CHAIN_ID,
|
||||
@@ -39,13 +37,10 @@ import {
|
||||
PANCAKESWAPV2_ROUTER_BY_CHAIN_ID,
|
||||
PANGOLIN_ROUTER_BY_CHAIN_ID,
|
||||
PLATYPUS_AVALANCHE_INFOS,
|
||||
POLYDEX_ROUTER_BY_CHAIN_ID,
|
||||
QUICKSWAP_ROUTER_BY_CHAIN_ID,
|
||||
SADDLE_MAINNET_INFOS,
|
||||
SHELL_POOLS_BY_CHAIN_ID,
|
||||
SHIBASWAP_ROUTER_BY_CHAIN_ID,
|
||||
SMOOTHY_BSC_INFOS,
|
||||
SMOOTHY_MAINNET_INFOS,
|
||||
SPIRITSWAP_ROUTER_BY_CHAIN_ID,
|
||||
SPOOKYSWAP_ROUTER_BY_CHAIN_ID,
|
||||
SUSHISWAP_ROUTER_BY_CHAIN_ID,
|
||||
@@ -327,30 +322,6 @@ export function getEllipsisInfosForPair(chainId: ChainId, takerToken: string, ma
|
||||
);
|
||||
}
|
||||
|
||||
export function getSmoothyInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
if (chainId === ChainId.BSC) {
|
||||
return Object.values(SMOOTHY_BSC_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),
|
||||
),
|
||||
);
|
||||
} else if (chainId === ChainId.Mainnet) {
|
||||
return Object.values(SMOOTHY_MAINNET_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),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function getSaddleInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
if (chainId !== ChainId.Mainnet) {
|
||||
return [];
|
||||
@@ -458,7 +429,6 @@ export function getCurveLikeInfosForPair(
|
||||
| ERC20BridgeSource.Synapse
|
||||
| ERC20BridgeSource.Belt
|
||||
| ERC20BridgeSource.Ellipsis
|
||||
| ERC20BridgeSource.Smoothy
|
||||
| ERC20BridgeSource.Saddle
|
||||
| ERC20BridgeSource.IronSwap
|
||||
| ERC20BridgeSource.XSigma
|
||||
@@ -486,9 +456,6 @@ export function getCurveLikeInfosForPair(
|
||||
case ERC20BridgeSource.Ellipsis:
|
||||
pools = getEllipsisInfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
case ERC20BridgeSource.Smoothy:
|
||||
pools = getSmoothyInfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
case ERC20BridgeSource.Saddle:
|
||||
pools = getSaddleInfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
@@ -527,16 +494,11 @@ export function uniswapV2LikeRouterAddress(
|
||||
| ERC20BridgeSource.PancakeSwapV2
|
||||
| ERC20BridgeSource.BakerySwap
|
||||
| ERC20BridgeSource.ApeSwap
|
||||
| ERC20BridgeSource.CafeSwap
|
||||
| ERC20BridgeSource.CheeseSwap
|
||||
| ERC20BridgeSource.JulSwap
|
||||
| ERC20BridgeSource.QuickSwap
|
||||
| ERC20BridgeSource.ComethSwap
|
||||
| ERC20BridgeSource.Dfyn
|
||||
| ERC20BridgeSource.WaultSwap
|
||||
| ERC20BridgeSource.Polydex
|
||||
| ERC20BridgeSource.ShibaSwap
|
||||
| ERC20BridgeSource.JetSwap
|
||||
| ERC20BridgeSource.TraderJoe
|
||||
| ERC20BridgeSource.Pangolin
|
||||
| ERC20BridgeSource.UbeSwap
|
||||
@@ -545,6 +507,8 @@ export function uniswapV2LikeRouterAddress(
|
||||
| ERC20BridgeSource.SpiritSwap
|
||||
| ERC20BridgeSource.BiSwap
|
||||
| ERC20BridgeSource.Yoshi
|
||||
| ERC20BridgeSource.MDex
|
||||
| ERC20BridgeSource.KnightSwap
|
||||
| ERC20BridgeSource.MeshSwap,
|
||||
): string {
|
||||
switch (source) {
|
||||
@@ -562,26 +526,16 @@ export function uniswapV2LikeRouterAddress(
|
||||
return BAKERYSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.ApeSwap:
|
||||
return APESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.CafeSwap:
|
||||
return CAFESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.CheeseSwap:
|
||||
return CHEESESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.JulSwap:
|
||||
return JULSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.QuickSwap:
|
||||
return QUICKSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.ComethSwap:
|
||||
return COMETHSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.Dfyn:
|
||||
return DFYN_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.WaultSwap:
|
||||
return WAULTSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.Polydex:
|
||||
return POLYDEX_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
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:
|
||||
@@ -600,6 +554,10 @@ export function uniswapV2LikeRouterAddress(
|
||||
return YOSHI_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.MeshSwap:
|
||||
return MESHSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.MDex:
|
||||
return MDEX_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.KnightSwap:
|
||||
return KNIGHTSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
default:
|
||||
throw new Error(`Unknown UniswapV2 like source ${source}`);
|
||||
}
|
||||
|
@@ -48,7 +48,7 @@ export function getComparisonPrices(
|
||||
} else {
|
||||
try {
|
||||
const fillFeeInEth = new BigNumber(
|
||||
(feeSchedule[ERC20BridgeSource.Native] as FeeEstimate)({ type: FillQuoteTransformerOrderType.Rfq }),
|
||||
(feeSchedule[ERC20BridgeSource.Native] as FeeEstimate)({ type: FillQuoteTransformerOrderType.Rfq }).fee,
|
||||
);
|
||||
const exchangeProxyOverheadInEth = new BigNumber(exchangeProxyOverhead(SOURCE_FLAGS.RfqOrder));
|
||||
feeInEth = fillFeeInEth.plus(exchangeProxyOverheadInEth);
|
||||
|
@@ -5,6 +5,7 @@ import { formatBytes32String } from '@ethersproject/strings';
|
||||
|
||||
import { TokenAdjacencyGraphBuilder } from '../token_adjacency_graph_builder';
|
||||
|
||||
import { IdentityFillAdjustor } from './identity_fill_adjustor';
|
||||
import { SourceFilters } from './source_filters';
|
||||
import {
|
||||
AaveV2FillData,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
FeeSchedule,
|
||||
FillData,
|
||||
FinalUniswapV3FillData,
|
||||
GasSchedule,
|
||||
GeistFillData,
|
||||
GetMarketOrdersOpts,
|
||||
isFinalUniswapV3FillData,
|
||||
@@ -98,7 +100,6 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Lido,
|
||||
ERC20BridgeSource.MakerPsm,
|
||||
ERC20BridgeSource.KyberDmm,
|
||||
ERC20BridgeSource.Smoothy,
|
||||
ERC20BridgeSource.Component,
|
||||
ERC20BridgeSource.Saddle,
|
||||
ERC20BridgeSource.XSigma,
|
||||
@@ -135,23 +136,20 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.PancakeSwap,
|
||||
ERC20BridgeSource.PancakeSwapV2,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.Smoothy,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.CafeSwap,
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
ERC20BridgeSource.JulSwap,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
ERC20BridgeSource.WaultSwap,
|
||||
ERC20BridgeSource.FirebirdOneSwap,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.ACryptos,
|
||||
ERC20BridgeSource.KyberDmm,
|
||||
ERC20BridgeSource.BiSwap,
|
||||
ERC20BridgeSource.MDex,
|
||||
ERC20BridgeSource.KnightSwap,
|
||||
]),
|
||||
[ChainId.Polygon]: new SourceFilters([
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.QuickSwap,
|
||||
ERC20BridgeSource.ComethSwap,
|
||||
ERC20BridgeSource.Dfyn,
|
||||
ERC20BridgeSource.MStable,
|
||||
ERC20BridgeSource.Curve,
|
||||
@@ -159,14 +157,12 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Dodo,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.WaultSwap,
|
||||
ERC20BridgeSource.Polydex,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.FirebirdOneSwap,
|
||||
ERC20BridgeSource.BalancerV2,
|
||||
ERC20BridgeSource.KyberDmm,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
ERC20BridgeSource.MultiHop,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.IronSwap,
|
||||
ERC20BridgeSource.AaveV2,
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
@@ -192,7 +188,6 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.Geist,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.MorpheusSwap,
|
||||
ERC20BridgeSource.SpiritSwap,
|
||||
ERC20BridgeSource.SpookySwap,
|
||||
@@ -212,6 +207,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.MultiHop,
|
||||
ERC20BridgeSource.Velodrome,
|
||||
]),
|
||||
},
|
||||
new SourceFilters([]),
|
||||
@@ -244,7 +240,6 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.CryptoCom,
|
||||
ERC20BridgeSource.MakerPsm,
|
||||
ERC20BridgeSource.KyberDmm,
|
||||
ERC20BridgeSource.Smoothy,
|
||||
ERC20BridgeSource.Component,
|
||||
ERC20BridgeSource.Saddle,
|
||||
ERC20BridgeSource.XSigma,
|
||||
@@ -281,23 +276,20 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.PancakeSwap,
|
||||
ERC20BridgeSource.PancakeSwapV2,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.Smoothy,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.CafeSwap,
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
ERC20BridgeSource.JulSwap,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
ERC20BridgeSource.WaultSwap,
|
||||
ERC20BridgeSource.FirebirdOneSwap,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.ACryptos,
|
||||
ERC20BridgeSource.KyberDmm,
|
||||
ERC20BridgeSource.BiSwap,
|
||||
ERC20BridgeSource.MDex,
|
||||
ERC20BridgeSource.KnightSwap,
|
||||
]),
|
||||
[ChainId.Polygon]: new SourceFilters([
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.QuickSwap,
|
||||
ERC20BridgeSource.ComethSwap,
|
||||
ERC20BridgeSource.Dfyn,
|
||||
ERC20BridgeSource.MStable,
|
||||
ERC20BridgeSource.Curve,
|
||||
@@ -305,14 +297,12 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Dodo,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.WaultSwap,
|
||||
ERC20BridgeSource.Polydex,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.FirebirdOneSwap,
|
||||
ERC20BridgeSource.BalancerV2,
|
||||
ERC20BridgeSource.KyberDmm,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
ERC20BridgeSource.MultiHop,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.IronSwap,
|
||||
ERC20BridgeSource.AaveV2,
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
@@ -338,7 +328,6 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.Geist,
|
||||
ERC20BridgeSource.JetSwap,
|
||||
ERC20BridgeSource.MorpheusSwap,
|
||||
ERC20BridgeSource.SpiritSwap,
|
||||
ERC20BridgeSource.SpookySwap,
|
||||
@@ -358,6 +347,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.CurveV2,
|
||||
ERC20BridgeSource.MultiHop,
|
||||
ERC20BridgeSource.Velodrome,
|
||||
]),
|
||||
},
|
||||
new SourceFilters([]),
|
||||
@@ -750,10 +740,6 @@ export const CURVE_OPTIMISM_POOLS = {
|
||||
tri: '0x1337bedc9d22ecbe766df105c9623922a27963ec',
|
||||
};
|
||||
|
||||
export const SMOOTHY_POOLS = {
|
||||
syUSD: '0xe5859f4efc09027a9b718781dcb2c6910cac6e91',
|
||||
};
|
||||
|
||||
export const SADDLE_POOLS = {
|
||||
stablesV2: '0xaCb83E0633d6605c5001e2Ab59EF3C745547C8C7',
|
||||
bitcoinsV2: '0xdf3309771d2BF82cb2B6C56F9f5365C8bD97c4f2',
|
||||
@@ -873,7 +859,6 @@ export const DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID = valueByChainId<string[]>(
|
||||
POLYGON_TOKENS.DAI,
|
||||
POLYGON_TOKENS.USDT,
|
||||
POLYGON_TOKENS.WBTC,
|
||||
POLYGON_TOKENS.nUSD,
|
||||
],
|
||||
[ChainId.Avalanche]: [
|
||||
AVALANCHE_TOKENS.WAVAX,
|
||||
@@ -1596,39 +1581,6 @@ export const IRONSWAP_POLYGON_INFOS: { [name: string]: CurveInfo } = {
|
||||
},
|
||||
};
|
||||
|
||||
export const SMOOTHY_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
||||
[SMOOTHY_POOLS.syUSD]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.swap_uint256,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_swap_amount,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
poolAddress: SMOOTHY_POOLS.syUSD,
|
||||
tokens: [
|
||||
MAINNET_TOKENS.USDT,
|
||||
MAINNET_TOKENS.USDC,
|
||||
MAINNET_TOKENS.DAI,
|
||||
MAINNET_TOKENS.TUSD,
|
||||
MAINNET_TOKENS.sUSD,
|
||||
MAINNET_TOKENS.BUSD,
|
||||
MAINNET_TOKENS.PAX,
|
||||
MAINNET_TOKENS.GUSD,
|
||||
],
|
||||
metaTokens: undefined,
|
||||
gasSchedule: 190e3,
|
||||
},
|
||||
};
|
||||
|
||||
export const SMOOTHY_BSC_INFOS: { [name: string]: CurveInfo } = {
|
||||
[SMOOTHY_POOLS.syUSD]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.swap_uint256,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_swap_amount,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
poolAddress: SMOOTHY_POOLS.syUSD,
|
||||
tokens: [BSC_TOKENS.BUSD, BSC_TOKENS.USDT, BSC_TOKENS.USDC, BSC_TOKENS.DAI, BSC_TOKENS.PAX, BSC_TOKENS.UST],
|
||||
metaTokens: undefined,
|
||||
gasSchedule: 90e3,
|
||||
},
|
||||
};
|
||||
|
||||
export const NERVE_BSC_INFOS: { [name: string]: CurveInfo } = {
|
||||
[NERVE_POOLS.threePool]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.swap,
|
||||
@@ -1973,6 +1925,20 @@ export const BISWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const MDEX_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.BSC]: '0x7dae51bd3e3376b8c7c4900e9107f12be3af1ba8',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const KNIGHTSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.BSC]: '0x05e61e0cdcd2170a76f9568a110cee3afdd6c46f',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const MOONISWAP_REGISTRIES_BY_CHAIN_ID = valueByChainId(
|
||||
{
|
||||
[ChainId.Mainnet]: ['0xbaf9a5d4b0052359326a6cdab54babaa3a3a9643'],
|
||||
@@ -2252,13 +2218,6 @@ export const APESWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const CAFESWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.BSC]: '0x933daea3a5995fb94b14a7696a5f3ffd7b1e385a',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const CHEESESWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.BSC]: '0x3047799262d8d2ef41ed2a222205968bc9b0d895',
|
||||
@@ -2266,13 +2225,6 @@ export const CHEESESWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const JULSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.BSC]: '0xbd67d157502a23309db761c41965600c2ec788b2',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
//
|
||||
// Polygon
|
||||
//
|
||||
@@ -2283,13 +2235,6 @@ export const QUICKSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const COMETHSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Polygon]: '0x93bcdc45f7e62f89a8e901dc4a0e2c6c427d9f25',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const DFYN_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Polygon]: '0xa102072a4c07f06ec3b4900fdc4c7b80b6c57429',
|
||||
@@ -2305,13 +2250,6 @@ export const WAULTSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const POLYDEX_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Polygon]: '0xe5c67ba380fb2f70a47b489e94bced486bb8fb74',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const MESHSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Polygon]: '0x10f4a785f458bc144e3706575924889954946639',
|
||||
@@ -2319,15 +2257,6 @@ export const MESHSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
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',
|
||||
@@ -2405,6 +2334,13 @@ export const YOSHI_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const VELODROME_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Optimism]: '0xa132dab612db5cb9fc9ac426a0cc215a3423f9c9',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID = valueByChainId<ERC20BridgeSource[]>(
|
||||
{
|
||||
[ChainId.Mainnet]: [
|
||||
@@ -2421,9 +2357,7 @@ export const VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID = valueByChainId<ERC20BridgeSo
|
||||
ERC20BridgeSource.BakerySwap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.CafeSwap,
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
ERC20BridgeSource.JulSwap,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
ERC20BridgeSource.Native,
|
||||
],
|
||||
@@ -2449,7 +2383,7 @@ const uniswapV2CloneGasSchedule = (fillData?: FillData) => {
|
||||
* the ethereum transaction cost (21k)
|
||||
*/
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
export const DEFAULT_GAS_SCHEDULE: Required<GasSchedule> = {
|
||||
[ERC20BridgeSource.Native]: fillData => {
|
||||
// TODO jacob re-order imports so there is no circular rependency with SignedNativeOrder
|
||||
const nativeFillData = fillData as { type: FillQuoteTransformerOrderType };
|
||||
@@ -2468,7 +2402,6 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
[ERC20BridgeSource.Synapse]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.Belt]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.Ellipsis]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.Smoothy]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.Saddle]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.IronSwap]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.XSigma]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
@@ -2480,6 +2413,8 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
[ERC20BridgeSource.CryptoCom]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.ShibaSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.BiSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.MDex]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.KnightSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Balancer]: () => 120e3,
|
||||
[ERC20BridgeSource.BalancerV2]: (fillData?: FillData) => {
|
||||
return 100e3 + ((fillData as BalancerV2BatchSwapFillData).swapSteps.length - 1) * 50e3;
|
||||
@@ -2597,9 +2532,7 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
[ERC20BridgeSource.PancakeSwapV2]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.BakerySwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.ApeSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.CafeSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.CheeseSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.JulSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.WaultSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.ACryptos]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
|
||||
@@ -2607,10 +2540,7 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
// Polygon
|
||||
//
|
||||
[ERC20BridgeSource.QuickSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.ComethSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Dfyn]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Polydex]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.JetSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.MeshSwap]: uniswapV2CloneGasSchedule,
|
||||
|
||||
//
|
||||
@@ -2634,12 +2564,28 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
[ERC20BridgeSource.SpookySwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Yoshi]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Beethovenx]: () => 100e3,
|
||||
|
||||
//
|
||||
// Optimism
|
||||
//
|
||||
[ERC20BridgeSource.Velodrome]: () => 160e3,
|
||||
};
|
||||
|
||||
export const DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = { ...DEFAULT_GAS_SCHEDULE };
|
||||
export const DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = Object.keys(DEFAULT_GAS_SCHEDULE).reduce((acc, key) => {
|
||||
acc[key as ERC20BridgeSource] = (fillData: FillData) => {
|
||||
return {
|
||||
gas: DEFAULT_GAS_SCHEDULE[key as ERC20BridgeSource](fillData),
|
||||
fee: ZERO_AMOUNT,
|
||||
};
|
||||
};
|
||||
return acc;
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
}, {} as Required<FeeSchedule>);
|
||||
|
||||
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(20000);
|
||||
|
||||
export const DEFAULT_FEE_ESTIMATE = { gas: 0, fee: ZERO_AMOUNT };
|
||||
|
||||
// tslint:enable:custom-no-magic-numbers
|
||||
|
||||
export const DEFAULT_GET_MARKET_ORDERS_OPTS: Omit<GetMarketOrdersOpts, 'gasPrice'> = {
|
||||
@@ -2660,4 +2606,5 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: Omit<GetMarketOrdersOpts, 'gasPrice
|
||||
shouldIncludePriceComparisonsReport: false,
|
||||
tokenAdjacencyGraph: { default: [] },
|
||||
neonRouterNumSamples: 14,
|
||||
fillAdjustor: new IdentityFillAdjustor(),
|
||||
};
|
||||
|
@@ -3,74 +3,17 @@ import { BigNumber, hexUtils } from '@0x/utils';
|
||||
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||
|
||||
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
||||
import { DEFAULT_FEE_ESTIMATE, POSITIVE_INF, SOURCE_FLAGS } from './constants';
|
||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||
|
||||
/**
|
||||
* Create `Fill` objects from orders and dex quotes.
|
||||
* Converts the ETH value to an amount in output tokens.
|
||||
*
|
||||
* By default this prefers the outputAmountPerEth, but if this value
|
||||
* is zero it will utilize the inputAmountPerEth and input.
|
||||
*/
|
||||
export function createFills(opts: {
|
||||
side: MarketOperation;
|
||||
orders?: NativeOrderWithFillableAmounts[];
|
||||
dexQuotes?: DexSample[][];
|
||||
targetInput?: BigNumber;
|
||||
outputAmountPerEth?: BigNumber;
|
||||
inputAmountPerEth?: BigNumber;
|
||||
excludedSources?: ERC20BridgeSource[];
|
||||
feeSchedule?: FeeSchedule;
|
||||
}): Fill[][] {
|
||||
const { side } = opts;
|
||||
const excludedSources = opts.excludedSources || [];
|
||||
const feeSchedule = opts.feeSchedule || {};
|
||||
const orders = opts.orders || [];
|
||||
const dexQuotes = opts.dexQuotes || [];
|
||||
const outputAmountPerEth = opts.outputAmountPerEth || ZERO_AMOUNT;
|
||||
const inputAmountPerEth = opts.inputAmountPerEth || ZERO_AMOUNT;
|
||||
// Create native fills.
|
||||
const nativeFills = nativeOrdersToFills(
|
||||
side,
|
||||
orders.filter(o => o.fillableTakerAmount.isGreaterThan(0)),
|
||||
opts.targetInput,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
feeSchedule,
|
||||
);
|
||||
// Create DEX fills.
|
||||
const dexFills = dexQuotes.map(singleSourceSamples =>
|
||||
dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, feeSchedule),
|
||||
);
|
||||
return [...dexFills, nativeFills]
|
||||
.map(p => clipFillsToInput(p, opts.targetInput))
|
||||
.filter(fills => hasLiquidity(fills) && !excludedSources.includes(fills[0].source));
|
||||
}
|
||||
|
||||
function clipFillsToInput(fills: Fill[], targetInput: BigNumber = POSITIVE_INF): Fill[] {
|
||||
const clipped: Fill[] = [];
|
||||
let input = ZERO_AMOUNT;
|
||||
for (const fill of fills) {
|
||||
if (input.gte(targetInput)) {
|
||||
break;
|
||||
}
|
||||
input = input.plus(fill.input);
|
||||
clipped.push(fill);
|
||||
}
|
||||
return clipped;
|
||||
}
|
||||
|
||||
function hasLiquidity(fills: Fill[]): boolean {
|
||||
if (fills.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const totalInput = BigNumber.sum(...fills.map(fill => fill.input));
|
||||
const totalOutput = BigNumber.sum(...fills.map(fill => fill.output));
|
||||
if (totalInput.isZero() || totalOutput.isZero()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
@@ -85,122 +28,106 @@ export function ethToOutputAmount({
|
||||
ethAmount: BigNumber | number;
|
||||
}): BigNumber {
|
||||
return !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(ethAmount)
|
||||
? outputAmountPerEth.times(ethAmount).integerValue()
|
||||
: inputAmountPerEth.times(ethAmount).times(output.dividedToIntegerBy(input));
|
||||
}
|
||||
|
||||
export function nativeOrdersToFills(
|
||||
export function nativeOrderToFill(
|
||||
side: MarketOperation,
|
||||
orders: NativeOrderWithFillableAmounts[],
|
||||
order: NativeOrderWithFillableAmounts,
|
||||
targetInput: BigNumber = POSITIVE_INF,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
filterNegativeAdjustedRateOrders: boolean = true,
|
||||
): Fill[] {
|
||||
): Fill | undefined {
|
||||
const sourcePathId = hexUtils.random();
|
||||
// Create a single path from all orders.
|
||||
let fills: Array<Fill & { adjustedRate: BigNumber }> = [];
|
||||
for (const o of orders) {
|
||||
const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = o;
|
||||
const makerAmount = fillableMakerAmount;
|
||||
const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
|
||||
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 = 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.
|
||||
// A large order and an order at the exact target should be penalized
|
||||
// the same.
|
||||
const clippedInput = BigNumber.min(targetInput, input);
|
||||
// scale the clipped output inline with the input
|
||||
const clippedOutput = clippedInput.dividedBy(input).times(output);
|
||||
const adjustedOutput =
|
||||
side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty);
|
||||
const adjustedRate =
|
||||
side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput);
|
||||
// Optionally skip orders with rates that are <= 0.
|
||||
if (filterNegativeAdjustedRateOrders && adjustedRate.lte(0)) {
|
||||
continue;
|
||||
}
|
||||
fills.push({
|
||||
sourcePathId,
|
||||
adjustedRate,
|
||||
adjustedOutput,
|
||||
input: clippedInput,
|
||||
output: clippedOutput,
|
||||
flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
||||
index: 0, // TBD
|
||||
parent: undefined, // TBD
|
||||
source: ERC20BridgeSource.Native,
|
||||
type,
|
||||
fillData: { ...o },
|
||||
});
|
||||
const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = order;
|
||||
const makerAmount = fillableMakerAmount;
|
||||
const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
|
||||
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
||||
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
||||
const { fee, gas } =
|
||||
fees[ERC20BridgeSource.Native] === undefined ? DEFAULT_FEE_ESTIMATE : fees[ERC20BridgeSource.Native]!(order);
|
||||
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.
|
||||
// A large order and an order at the exact target should be penalized
|
||||
// the same.
|
||||
const clippedInput = BigNumber.min(targetInput, input);
|
||||
// scale the clipped output inline with the input
|
||||
const clippedOutput = clippedInput.dividedBy(input).times(output);
|
||||
const adjustedOutput =
|
||||
side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty);
|
||||
const adjustedRate =
|
||||
side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput);
|
||||
// Optionally skip orders with rates that are <= 0.
|
||||
if (filterNegativeAdjustedRateOrders && adjustedRate.lte(0)) {
|
||||
return undefined;
|
||||
}
|
||||
// Sort by descending adjusted rate.
|
||||
fills = fills.sort((a, b) => b.adjustedRate.comparedTo(a.adjustedRate));
|
||||
// Re-index fills.
|
||||
for (let i = 0; i < fills.length; ++i) {
|
||||
fills[i].parent = i === 0 ? undefined : fills[i - 1];
|
||||
fills[i].index = i;
|
||||
}
|
||||
return fills;
|
||||
|
||||
return {
|
||||
sourcePathId,
|
||||
adjustedOutput,
|
||||
input: clippedInput,
|
||||
output: clippedOutput,
|
||||
flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
||||
source: ERC20BridgeSource.Native,
|
||||
type,
|
||||
fillData: { ...order },
|
||||
gas,
|
||||
};
|
||||
}
|
||||
|
||||
export function dexSamplesToFills(
|
||||
export function dexSampleToFill(
|
||||
side: MarketOperation,
|
||||
samples: DexSample[],
|
||||
sample: DexSample,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
): Fill[] {
|
||||
): Fill {
|
||||
const sourcePathId = hexUtils.random();
|
||||
const fills: Fill[] = [];
|
||||
// Drop any non-zero entries. This can occur if the any fills on Kyber were UniswapReserves
|
||||
// We need not worry about Kyber fills going to UniswapReserve as the input amount
|
||||
// we fill is the same as we sampled. I.e we received [0,20,30] output from [1,2,3] input
|
||||
// and we only fill [2,3] on Kyber (as 1 returns 0 output)
|
||||
const nonzeroSamples = samples.filter(q => !q.output.isZero());
|
||||
for (let i = 0; i < nonzeroSamples.length; i++) {
|
||||
const sample = nonzeroSamples[i];
|
||||
const prevSample = i === 0 ? undefined : nonzeroSamples[i - 1];
|
||||
const { source, fillData } = sample;
|
||||
const input = sample.input.minus(prevSample ? prevSample.input : 0);
|
||||
const output = sample.output.minus(prevSample ? prevSample.output : 0);
|
||||
let penalty = ZERO_AMOUNT;
|
||||
if (i === 0) {
|
||||
const fee = fees[source] === undefined ? 0 : fees[source]!(sample.fillData) || 0;
|
||||
// Only the first fill in a DEX path incurs a penalty.
|
||||
penalty = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
}
|
||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||
const { source, fillData } = sample;
|
||||
const input = sample.input;
|
||||
const output = sample.output;
|
||||
const { fee, gas } =
|
||||
fees[source] === undefined ? DEFAULT_FEE_ESTIMATE : fees[source]!(sample.fillData) || DEFAULT_FEE_ESTIMATE;
|
||||
|
||||
fills.push({
|
||||
sourcePathId,
|
||||
input,
|
||||
output,
|
||||
adjustedOutput,
|
||||
source,
|
||||
fillData,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
index: i,
|
||||
parent: i !== 0 ? fills[fills.length - 1] : undefined,
|
||||
flags: SOURCE_FLAGS[source],
|
||||
});
|
||||
}
|
||||
return fills;
|
||||
const penalty = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
|
||||
return {
|
||||
sourcePathId,
|
||||
input,
|
||||
output,
|
||||
adjustedOutput: adjustOutput(side, output, penalty),
|
||||
source,
|
||||
fillData,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
flags: SOURCE_FLAGS[source],
|
||||
gas,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the output depending on whether this is a buy or a sell.
|
||||
*
|
||||
* If it is a sell, than output is lowered by the adjustment.
|
||||
* If it is a buy, than output is increased by adjustment.
|
||||
*/
|
||||
export function adjustOutput(side: MarketOperation, output: BigNumber, penalty: BigNumber): BigNumber {
|
||||
return side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||
}
|
||||
|
@@ -0,0 +1,13 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { MarketOperation } from '../../types';
|
||||
|
||||
import { Fill, FillAdjustor } from './types';
|
||||
|
||||
// tslint:disable:prefer-function-over-method
|
||||
|
||||
export class IdentityFillAdjustor implements FillAdjustor {
|
||||
public adjustFills(side: MarketOperation, fills: Fill[], amount: BigNumber): Fill[] {
|
||||
return fills;
|
||||
}
|
||||
}
|
@@ -41,19 +41,17 @@ import {
|
||||
SOURCE_FLAGS,
|
||||
ZERO_AMOUNT,
|
||||
} from './constants';
|
||||
import { createFills } from './fills';
|
||||
import { IdentityFillAdjustor } from './identity_fill_adjustor';
|
||||
import { getBestTwoHopQuote } from './multihop_utils';
|
||||
import { createOrdersFromTwoHopSample } from './orders';
|
||||
import { Path, PathPenaltyOpts } from './path';
|
||||
import { findOptimalPathJSAsync, findOptimalRustPathFromSamples } from './path_optimizer';
|
||||
import { findOptimalPathFromSamples } from './path_optimizer';
|
||||
import { DexOrderSampler, getSampleAmounts } from './sampler';
|
||||
import { SourceFilters } from './source_filters';
|
||||
import {
|
||||
AggregationError,
|
||||
CollapsedFill,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
GenerateOptimizedOrdersOpts,
|
||||
GetMarketOrdersOpts,
|
||||
MarketSideLiquidity,
|
||||
@@ -62,8 +60,6 @@ import {
|
||||
OrderDomain,
|
||||
} from './types';
|
||||
|
||||
const SHOULD_USE_RUST_ROUTER = process.env.RUST_ROUTER === 'true';
|
||||
|
||||
// tslint:disable:boolean-naming
|
||||
|
||||
export class MarketOperationUtils {
|
||||
@@ -167,18 +163,20 @@ export class MarketOperationUtils {
|
||||
// Get native order fillable amounts.
|
||||
this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||
// Get ETH -> maker token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
this._sampler.getBestNativeTokenSellRate(
|
||||
feeSourceFilters.sources,
|
||||
makerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
_opts.feeSchedule,
|
||||
),
|
||||
// Get ETH -> taker token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
this._sampler.getBestNativeTokenSellRate(
|
||||
feeSourceFilters.sources,
|
||||
takerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
_opts.feeSchedule,
|
||||
),
|
||||
// Get sell quotes for taker -> maker.
|
||||
this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||
@@ -278,18 +276,20 @@ export class MarketOperationUtils {
|
||||
// Get native order fillable amounts.
|
||||
this._sampler.getLimitOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||
// Get ETH -> makerToken token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
this._sampler.getBestNativeTokenSellRate(
|
||||
feeSourceFilters.sources,
|
||||
makerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
_opts.feeSchedule,
|
||||
),
|
||||
// Get ETH -> taker token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
this._sampler.getBestNativeTokenSellRate(
|
||||
feeSourceFilters.sources,
|
||||
takerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
_opts.feeSchedule,
|
||||
),
|
||||
// Get buy quotes for taker -> maker.
|
||||
this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||
@@ -384,11 +384,12 @@ export class MarketOperationUtils {
|
||||
this._sampler.getLimitOrderFillableMakerAmounts(orders, this.contractAddresses.exchangeProxy),
|
||||
),
|
||||
...batchNativeOrders.map(orders =>
|
||||
this._sampler.getMedianSellRate(
|
||||
this._sampler.getBestNativeTokenSellRate(
|
||||
feeSourceFilters.sources,
|
||||
orders[0].order.takerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
_opts.feeSchedule,
|
||||
),
|
||||
),
|
||||
...batchNativeOrders.map((orders, i) =>
|
||||
@@ -455,6 +456,7 @@ export class MarketOperationUtils {
|
||||
allowFallback: _opts.allowFallback,
|
||||
gasPrice: _opts.gasPrice,
|
||||
neonRouterNumSamples: _opts.neonRouterNumSamples,
|
||||
fillAdjustor: _opts.fillAdjustor,
|
||||
},
|
||||
);
|
||||
return optimizerResult;
|
||||
@@ -516,60 +518,38 @@ export class MarketOperationUtils {
|
||||
const takerAmountPerEth = side === MarketOperation.Sell ? inputAmountPerEth : outputAmountPerEth;
|
||||
const makerAmountPerEth = side === MarketOperation.Sell ? outputAmountPerEth : inputAmountPerEth;
|
||||
|
||||
let fills: Fill[][];
|
||||
// Find the optimal path using Rust router if enabled, otherwise fallback to JS Router
|
||||
let optimalPath: Path | undefined;
|
||||
if (SHOULD_USE_RUST_ROUTER) {
|
||||
fills = [[]];
|
||||
optimalPath = findOptimalRustPathFromSamples(
|
||||
side,
|
||||
dexQuotes,
|
||||
[...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
||||
inputAmount,
|
||||
penaltyOpts,
|
||||
opts.feeSchedule,
|
||||
this._sampler.chainId,
|
||||
opts.neonRouterNumSamples,
|
||||
opts.samplerMetrics,
|
||||
);
|
||||
} else {
|
||||
// Convert native orders and dex quotes into `Fill` objects.
|
||||
fills = createFills({
|
||||
side,
|
||||
orders: [...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
||||
dexQuotes,
|
||||
targetInput: inputAmount,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
excludedSources: opts.excludedSources,
|
||||
feeSchedule: opts.feeSchedule,
|
||||
});
|
||||
optimalPath = findOptimalPathFromSamples(
|
||||
side,
|
||||
dexQuotes,
|
||||
[...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
||||
inputAmount,
|
||||
penaltyOpts,
|
||||
opts.feeSchedule,
|
||||
this._sampler.chainId,
|
||||
opts.neonRouterNumSamples,
|
||||
opts.fillAdjustor,
|
||||
opts.samplerMetrics,
|
||||
);
|
||||
|
||||
optimalPath = await findOptimalPathJSAsync(
|
||||
side,
|
||||
fills,
|
||||
inputAmount,
|
||||
opts.runLimit,
|
||||
opts.samplerMetrics,
|
||||
penaltyOpts,
|
||||
);
|
||||
}
|
||||
const optimalPathAdjustedRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
|
||||
|
||||
const optimalPathRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
|
||||
|
||||
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
||||
const { adjustedRate: bestTwoHopAdjustedRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
||||
marketSideLiquidity,
|
||||
opts.feeSchedule,
|
||||
opts.exchangeProxyOverhead,
|
||||
opts.fillAdjustor,
|
||||
);
|
||||
if (bestTwoHopQuote && bestTwoHopRate.isGreaterThan(optimalPathRate)) {
|
||||
|
||||
if (bestTwoHopQuote && bestTwoHopAdjustedRate.isGreaterThan(optimalPathAdjustedRate)) {
|
||||
const twoHopOrders = createOrdersFromTwoHopSample(bestTwoHopQuote, orderOpts);
|
||||
return {
|
||||
optimizedOrders: twoHopOrders,
|
||||
liquidityDelivered: bestTwoHopQuote,
|
||||
sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop],
|
||||
marketSideLiquidity,
|
||||
adjustedRate: bestTwoHopRate,
|
||||
adjustedRate: bestTwoHopAdjustedRate,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
};
|
||||
@@ -580,19 +560,14 @@ export class MarketOperationUtils {
|
||||
throw new Error(AggregationError.NoOptimalPath);
|
||||
}
|
||||
|
||||
// Generate a fallback path if required
|
||||
// TODO(kimpers): Will experiment with disabling this and see how it affects revert rate
|
||||
// to avoid yet another router roundtrip
|
||||
// TODO: clean this up if we don't need it
|
||||
// await this._addOptionalFallbackAsync(side, inputAmount, optimalPath, dexQuotes, fills, opts, penaltyOpts);
|
||||
const collapsedPath = optimalPath.collapse(orderOpts);
|
||||
const finalizedPath = optimalPath.finalize(orderOpts);
|
||||
|
||||
return {
|
||||
optimizedOrders: collapsedPath.orders,
|
||||
liquidityDelivered: collapsedPath.collapsedFills as CollapsedFill[],
|
||||
sourceFlags: collapsedPath.sourceFlags,
|
||||
optimizedOrders: finalizedPath.orders,
|
||||
liquidityDelivered: finalizedPath.fills,
|
||||
sourceFlags: finalizedPath.sourceFlags,
|
||||
marketSideLiquidity,
|
||||
adjustedRate: optimalPathRate,
|
||||
adjustedRate: optimalPathAdjustedRate,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
};
|
||||
@@ -618,6 +593,7 @@ export class MarketOperationUtils {
|
||||
gasPrice: _opts.gasPrice,
|
||||
neonRouterNumSamples: _opts.neonRouterNumSamples,
|
||||
samplerMetrics: _opts.samplerMetrics,
|
||||
fillAdjustor: _opts.fillAdjustor,
|
||||
};
|
||||
|
||||
if (nativeOrders.length === 0) {
|
||||
@@ -630,9 +606,15 @@ export class MarketOperationUtils {
|
||||
? this.getMarketSellLiquidityAsync.bind(this)
|
||||
: this.getMarketBuyLiquidityAsync.bind(this);
|
||||
const marketSideLiquidity: MarketSideLiquidity = await marketLiquidityFnAsync(nativeOrders, amount, _opts);
|
||||
|
||||
// Phase 1 Routing
|
||||
// We find an optimized path for ALL the DEX and open-orderbook liquidity
|
||||
let optimizerResult: OptimizerResult | undefined;
|
||||
try {
|
||||
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, optimizerOpts);
|
||||
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, {
|
||||
...optimizerOpts,
|
||||
fillAdjustor: new IdentityFillAdjustor(),
|
||||
});
|
||||
} catch (e) {
|
||||
// If no on-chain or off-chain Open Orderbook orders are present, a `NoOptimalPath` will be thrown.
|
||||
// If this happens at this stage, there is still a chance that an RFQ order is fillable, therefore
|
||||
@@ -656,6 +638,17 @@ export class MarketOperationUtils {
|
||||
}
|
||||
|
||||
// If RFQ liquidity is enabled, make a request to check RFQ liquidity against the first optimizer result
|
||||
|
||||
// Phase 2 Routing
|
||||
// Mix in any off-chain RFQ quotes
|
||||
// Apply any fill adjustments i
|
||||
const phaseTwoOptimizerOpts = {
|
||||
...optimizerOpts,
|
||||
// Pass in the FillAdjustor for Phase 2 adjustment, in the future we may perform this adjustment
|
||||
// in Phase 1.
|
||||
fillAdjustor: _opts.fillAdjustor,
|
||||
};
|
||||
|
||||
const { rfqt } = _opts;
|
||||
if (
|
||||
marketSideLiquidity.isRfqSupported &&
|
||||
@@ -716,8 +709,28 @@ export class MarketOperationUtils {
|
||||
});
|
||||
// Re-run optimizer with the new indicative quote
|
||||
if (indicativeQuotes.length > 0) {
|
||||
// Attach the indicative quotes to the market side liquidity
|
||||
marketSideLiquidity.quotes.rfqtIndicativeQuotes = indicativeQuotes;
|
||||
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, optimizerOpts);
|
||||
|
||||
// Phase 2 Routing
|
||||
const phase1OptimalSources = optimizerResult
|
||||
? optimizerResult.optimizedOrders.map(o => o.source)
|
||||
: [];
|
||||
const phase2MarketSideLiquidity: MarketSideLiquidity = {
|
||||
...marketSideLiquidity,
|
||||
quotes: {
|
||||
...marketSideLiquidity.quotes,
|
||||
// Select only the quotes that were chosen in Phase 1
|
||||
dexQuotes: marketSideLiquidity.quotes.dexQuotes.filter(
|
||||
q => q.length > 0 && phase1OptimalSources.includes(q[0].source),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
optimizerResult = await this._generateOptimizedOrdersAsync(
|
||||
phase2MarketSideLiquidity,
|
||||
phaseTwoOptimizerOpts,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// A firm quote is being requested, and firm quotes price-aware enabled.
|
||||
@@ -775,6 +788,8 @@ export class MarketOperationUtils {
|
||||
fillableTakerFeeAmount: ZERO_AMOUNT,
|
||||
}),
|
||||
);
|
||||
|
||||
// Attach the firm RFQt quotes to the market side liquidity
|
||||
marketSideLiquidity.quotes.nativeOrders = [
|
||||
...quotesWithOrderFillableAmounts,
|
||||
...marketSideLiquidity.quotes.nativeOrders,
|
||||
@@ -783,7 +798,27 @@ export class MarketOperationUtils {
|
||||
// Re-run optimizer with the new firm quote. This is the second and last time
|
||||
// we run the optimized in a block of code. In this case, we don't catch a potential `NoOptimalPath` exception
|
||||
// and we let it bubble up if it happens.
|
||||
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, optimizerOpts);
|
||||
|
||||
// Phase 2 Routing
|
||||
// Optimization: Filter by what is already currently in the Phase1 output as it doesn't
|
||||
// seem possible that inclusion of RFQT could impact the sources chosen from Phase 1.
|
||||
const phase1OptimalSources = optimizerResult
|
||||
? optimizerResult.optimizedOrders.map(o => o.source)
|
||||
: [];
|
||||
const phase2MarketSideLiquidity: MarketSideLiquidity = {
|
||||
...marketSideLiquidity,
|
||||
quotes: {
|
||||
...marketSideLiquidity.quotes,
|
||||
// Select only the quotes that were chosen in Phase 1
|
||||
dexQuotes: marketSideLiquidity.quotes.dexQuotes.filter(
|
||||
q => q.length > 0 && phase1OptimalSources.includes(q[0].source),
|
||||
),
|
||||
},
|
||||
};
|
||||
optimizerResult = await this._generateOptimizedOrdersAsync(
|
||||
phase2MarketSideLiquidity,
|
||||
phaseTwoOptimizerOpts,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -836,75 +871,6 @@ export class MarketOperationUtils {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO(kimpers): Remove this when we know that it's safe to drop the fallbacks on native orders
|
||||
// tslint:disable-next-line: prefer-function-over-method
|
||||
private async _addOptionalFallbackAsync(
|
||||
side: MarketOperation,
|
||||
inputAmount: BigNumber,
|
||||
optimalPath: Path,
|
||||
dexQuotes: DexSample[][],
|
||||
fills: Fill[][],
|
||||
opts: GenerateOptimizedOrdersOpts,
|
||||
penaltyOpts: PathPenaltyOpts,
|
||||
): Promise<void> {
|
||||
const maxFallbackSlippage = opts.maxFallbackSlippage || 0;
|
||||
const optimalPathRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
|
||||
// Generate a fallback path if sources requiring a fallback (fragile) are in the optimal path.
|
||||
// Native is relatively fragile (limit order collision, expiry, or lack of available maker balance)
|
||||
// LiquidityProvider is relatively fragile (collision)
|
||||
const fragileSources = [ERC20BridgeSource.Native, ERC20BridgeSource.LiquidityProvider];
|
||||
const fragileFills = optimalPath.fills.filter(f => fragileSources.includes(f.source));
|
||||
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 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 (
|
||||
sturdyOptimalPath !== undefined &&
|
||||
(fragileFills.length === optimalPath.fills.length ||
|
||||
sturdyOptimalPath.adjustedSlippage(optimalPathRate) <= maxFallbackSlippage)
|
||||
) {
|
||||
optimalPath.addFallback(sturdyOptimalPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// tslint:disable: max-file-line-count
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
DexSample,
|
||||
ExchangeProxyOverhead,
|
||||
FeeSchedule,
|
||||
FillAdjustor,
|
||||
MarketSideLiquidity,
|
||||
MultiHopFillData,
|
||||
TokenAdjacencyGraph,
|
||||
@@ -38,6 +39,7 @@ export function getBestTwoHopQuote(
|
||||
marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>,
|
||||
feeSchedule?: FeeSchedule,
|
||||
exchangeProxyOverhead?: ExchangeProxyOverhead,
|
||||
fillAdjustor?: FillAdjustor,
|
||||
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
|
||||
const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity;
|
||||
const { twoHopQuotes } = quotes;
|
||||
@@ -57,7 +59,15 @@ export function getBestTwoHopQuote(
|
||||
}
|
||||
const best = filteredQuotes
|
||||
.map(quote =>
|
||||
getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, feeSchedule, exchangeProxyOverhead),
|
||||
getTwoHopAdjustedRate(
|
||||
side,
|
||||
quote,
|
||||
inputAmount,
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
exchangeProxyOverhead,
|
||||
fillAdjustor,
|
||||
),
|
||||
)
|
||||
.reduce(
|
||||
(prev, curr, i) =>
|
||||
@@ -70,6 +80,7 @@ export function getBestTwoHopQuote(
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
exchangeProxyOverhead,
|
||||
fillAdjustor,
|
||||
),
|
||||
quote: filteredQuotes[0],
|
||||
},
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { BridgeProtocol, encodeBridgeSourceId, FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
import _ = require('lodash');
|
||||
|
||||
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
|
||||
|
||||
@@ -11,12 +12,12 @@ import {
|
||||
BalancerV2BatchSwapFillData,
|
||||
BalancerV2FillData,
|
||||
BancorFillData,
|
||||
CollapsedFill,
|
||||
CompoundFillData,
|
||||
CurveFillData,
|
||||
DexSample,
|
||||
DODOFillData,
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
FillData,
|
||||
FinalUniswapV3FillData,
|
||||
GeistFillData,
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
MakerPsmFillData,
|
||||
MooniswapFillData,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
NativeFillData,
|
||||
NativeLimitOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
OptimizedMarketBridgeOrder,
|
||||
@@ -40,6 +41,7 @@ import {
|
||||
UniswapV2FillData,
|
||||
UniswapV3FillData,
|
||||
UniswapV3PathAmount,
|
||||
VelodromeFillData,
|
||||
} from './types';
|
||||
|
||||
// tslint:disable completed-docs
|
||||
@@ -59,23 +61,27 @@ export function createOrdersFromTwoHopSample(
|
||||
): OptimizedMarketOrder[] {
|
||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
||||
const { firstHopSource, secondHopSource, intermediateToken } = sample.fillData;
|
||||
const firstHopFill: CollapsedFill = {
|
||||
const firstHopFill: Fill = {
|
||||
sourcePathId: '',
|
||||
source: firstHopSource.source,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
input: opts.side === MarketOperation.Sell ? sample.input : ZERO_AMOUNT,
|
||||
output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
|
||||
subFills: [],
|
||||
adjustedOutput: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
|
||||
fillData: firstHopSource.fillData,
|
||||
flags: BigInt(0),
|
||||
gas: 1,
|
||||
};
|
||||
const secondHopFill: CollapsedFill = {
|
||||
const secondHopFill: Fill = {
|
||||
sourcePathId: '',
|
||||
source: secondHopSource.source,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
input: opts.side === MarketOperation.Sell ? MAX_UINT256 : sample.input,
|
||||
output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
|
||||
subFills: [],
|
||||
adjustedOutput: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
|
||||
fillData: secondHopSource.fillData,
|
||||
flags: BigInt(0),
|
||||
gas: 1,
|
||||
};
|
||||
return [
|
||||
createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts.side),
|
||||
@@ -134,44 +140,32 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'Ellipsis');
|
||||
case ERC20BridgeSource.Component:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Shell, 'Component');
|
||||
case ERC20BridgeSource.Smoothy:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'Smoothy');
|
||||
case ERC20BridgeSource.Saddle:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'Saddle');
|
||||
case ERC20BridgeSource.XSigma:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'xSigma');
|
||||
case ERC20BridgeSource.ApeSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'ApeSwap');
|
||||
case ERC20BridgeSource.CafeSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'CafeSwap');
|
||||
case ERC20BridgeSource.CheeseSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'CheeseSwap');
|
||||
case ERC20BridgeSource.JulSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'JulSwap');
|
||||
case ERC20BridgeSource.UniswapV3:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV3, 'UniswapV3');
|
||||
case ERC20BridgeSource.KyberDmm:
|
||||
return encodeBridgeSourceId(BridgeProtocol.KyberDmm, 'KyberDmm');
|
||||
case ERC20BridgeSource.QuickSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'QuickSwap');
|
||||
case ERC20BridgeSource.ComethSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'ComethSwap');
|
||||
case ERC20BridgeSource.Dfyn:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Dfyn');
|
||||
case ERC20BridgeSource.CurveV2:
|
||||
return encodeBridgeSourceId(BridgeProtocol.CurveV2, 'CurveV2');
|
||||
case ERC20BridgeSource.WaultSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'WaultSwap');
|
||||
case ERC20BridgeSource.Polydex:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Polydex');
|
||||
case ERC20BridgeSource.FirebirdOneSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'FirebirdOneSwap');
|
||||
case ERC20BridgeSource.Lido:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Lido, 'Lido');
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'ShibaSwap');
|
||||
case ERC20BridgeSource.JetSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'JetSwap');
|
||||
case ERC20BridgeSource.IronSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'IronSwap');
|
||||
case ERC20BridgeSource.ACryptos:
|
||||
@@ -202,6 +196,10 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'MobiusMoney');
|
||||
case ERC20BridgeSource.BiSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'BiSwap');
|
||||
case ERC20BridgeSource.MDex:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MDex');
|
||||
case ERC20BridgeSource.KnightSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'KnightSwap');
|
||||
case ERC20BridgeSource.GMX:
|
||||
return encodeBridgeSourceId(BridgeProtocol.GMX, 'GMX');
|
||||
case ERC20BridgeSource.Platypus:
|
||||
@@ -210,6 +208,8 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MeshSwap');
|
||||
case ERC20BridgeSource.BancorV3:
|
||||
return encodeBridgeSourceId(BridgeProtocol.BancorV3, 'BancorV3');
|
||||
case ERC20BridgeSource.Velodrome:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Velodrome, 'Velodrome');
|
||||
default:
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
@@ -237,7 +237,6 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
||||
case ERC20BridgeSource.Synapse:
|
||||
case ERC20BridgeSource.Belt:
|
||||
case ERC20BridgeSource.Ellipsis:
|
||||
case ERC20BridgeSource.Smoothy:
|
||||
case ERC20BridgeSource.Saddle:
|
||||
case ERC20BridgeSource.XSigma:
|
||||
case ERC20BridgeSource.FirebirdOneSwap:
|
||||
@@ -283,16 +282,11 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
||||
case ERC20BridgeSource.PancakeSwapV2:
|
||||
case ERC20BridgeSource.BakerySwap:
|
||||
case ERC20BridgeSource.ApeSwap:
|
||||
case ERC20BridgeSource.CafeSwap:
|
||||
case ERC20BridgeSource.CheeseSwap:
|
||||
case ERC20BridgeSource.JulSwap:
|
||||
case ERC20BridgeSource.QuickSwap:
|
||||
case ERC20BridgeSource.ComethSwap:
|
||||
case ERC20BridgeSource.Dfyn:
|
||||
case ERC20BridgeSource.WaultSwap:
|
||||
case ERC20BridgeSource.Polydex:
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
case ERC20BridgeSource.JetSwap:
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
@@ -300,6 +294,8 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
||||
case ERC20BridgeSource.SpookySwap:
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
case ERC20BridgeSource.BiSwap:
|
||||
case ERC20BridgeSource.MDex:
|
||||
case ERC20BridgeSource.KnightSwap:
|
||||
case ERC20BridgeSource.Yoshi:
|
||||
case ERC20BridgeSource.MeshSwap:
|
||||
const uniswapV2FillData = (order as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
|
||||
@@ -391,74 +387,16 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
||||
const bancorV3FillData = (order as OptimizedMarketBridgeOrder<BancorFillData>).fillData;
|
||||
bridgeData = encoder.encode([bancorV3FillData.networkAddress, bancorV3FillData.path]);
|
||||
break;
|
||||
case ERC20BridgeSource.Velodrome:
|
||||
const velodromeFillData = (order as OptimizedMarketBridgeOrder<VelodromeFillData>).fillData;
|
||||
bridgeData = encoder.encode([velodromeFillData.router, velodromeFillData.stable]);
|
||||
break;
|
||||
default:
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
return bridgeData;
|
||||
}
|
||||
|
||||
export function createBridgeOrder(
|
||||
fill: CollapsedFill,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
side: MarketOperation,
|
||||
): OptimizedMarketBridgeOrder {
|
||||
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
||||
return {
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerAmount,
|
||||
takerAmount,
|
||||
fillData: createFinalBridgeOrderFillDataFromCollapsedFill(fill),
|
||||
source: fill.source,
|
||||
sourcePathId: fill.sourcePathId,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
fills: [fill],
|
||||
};
|
||||
}
|
||||
|
||||
function createFinalBridgeOrderFillDataFromCollapsedFill(fill: CollapsedFill): FillData {
|
||||
switch (fill.source) {
|
||||
case ERC20BridgeSource.UniswapV3: {
|
||||
const fd = fill.fillData as UniswapV3FillData;
|
||||
const { uniswapPath, gasUsed } = getBestUniswapV3PathAmountForInputAmount(fd, fill.input);
|
||||
const finalFillData: FinalUniswapV3FillData = {
|
||||
router: fd.router,
|
||||
tokenAddressPath: fd.tokenAddressPath,
|
||||
uniswapPath,
|
||||
gasUsed,
|
||||
};
|
||||
return finalFillData;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return fill.fillData;
|
||||
}
|
||||
|
||||
function getBestUniswapV3PathAmountForInputAmount(
|
||||
fillData: UniswapV3FillData,
|
||||
inputAmount: BigNumber,
|
||||
): UniswapV3PathAmount {
|
||||
if (fillData.pathAmounts.length === 0) {
|
||||
throw new Error(`No Uniswap V3 paths`);
|
||||
}
|
||||
// Find the best path that can satisfy `inputAmount`.
|
||||
// Assumes `fillData.pathAmounts` is sorted ascending.
|
||||
for (const pathAmount of fillData.pathAmounts) {
|
||||
if (pathAmount.inputAmount.gte(inputAmount)) {
|
||||
return pathAmount;
|
||||
}
|
||||
}
|
||||
return fillData.pathAmounts[fillData.pathAmounts.length - 1];
|
||||
}
|
||||
|
||||
export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] {
|
||||
const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
|
||||
const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;
|
||||
return [makerToken, takerToken];
|
||||
}
|
||||
|
||||
export const poolEncoder = AbiEncoder.create([{ name: 'poolAddress', type: 'address' }]);
|
||||
const curveEncoder = AbiEncoder.create([
|
||||
{ name: 'curveAddress', type: 'address' },
|
||||
@@ -505,7 +443,6 @@ export const BRIDGE_ENCODERS: {
|
||||
[ERC20BridgeSource.Synapse]: curveEncoder,
|
||||
[ERC20BridgeSource.Belt]: curveEncoder,
|
||||
[ERC20BridgeSource.Ellipsis]: curveEncoder,
|
||||
[ERC20BridgeSource.Smoothy]: curveEncoder,
|
||||
[ERC20BridgeSource.Saddle]: curveEncoder,
|
||||
[ERC20BridgeSource.XSigma]: curveEncoder,
|
||||
[ERC20BridgeSource.FirebirdOneSwap]: curveEncoder,
|
||||
@@ -525,6 +462,8 @@ export const BRIDGE_ENCODERS: {
|
||||
[ERC20BridgeSource.SpookySwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.MorpheusSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.BiSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.MDex]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.KnightSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.Yoshi]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.MeshSwap]: routerAddressPathEncoder,
|
||||
// Avalanche
|
||||
@@ -537,16 +476,11 @@ export const BRIDGE_ENCODERS: {
|
||||
[ERC20BridgeSource.PancakeSwapV2]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.BakerySwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.ApeSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.CafeSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.CheeseSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.JulSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.WaultSwap]: routerAddressPathEncoder,
|
||||
// Polygon
|
||||
[ERC20BridgeSource.QuickSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.ComethSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.Dfyn]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.Polydex]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.JetSwap]: routerAddressPathEncoder,
|
||||
// Generic pools
|
||||
[ERC20BridgeSource.Shell]: poolEncoder,
|
||||
[ERC20BridgeSource.Component]: poolEncoder,
|
||||
@@ -582,9 +516,10 @@ export const BRIDGE_ENCODERS: {
|
||||
[ERC20BridgeSource.AaveV2]: AbiEncoder.create('(address,address)'),
|
||||
[ERC20BridgeSource.Compound]: AbiEncoder.create('(address)'),
|
||||
[ERC20BridgeSource.Geist]: AbiEncoder.create('(address,address)'),
|
||||
[ERC20BridgeSource.Velodrome]: AbiEncoder.create('(address,bool)'),
|
||||
};
|
||||
|
||||
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {
|
||||
function getFillTokenAmounts(fill: Fill, side: MarketOperation): [BigNumber, BigNumber] {
|
||||
return [
|
||||
// Maker asset amount.
|
||||
side === MarketOperation.Sell ? fill.output.integerValue(BigNumber.ROUND_DOWN) : fill.input,
|
||||
@@ -594,7 +529,7 @@ function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNu
|
||||
}
|
||||
|
||||
export function createNativeOptimizedOrder(
|
||||
fill: NativeCollapsedFill,
|
||||
fill: Fill<NativeFillData>,
|
||||
side: MarketOperation,
|
||||
): OptimizedMarketOrderBase<NativeLimitOrderFillData> | OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
||||
const fillData = fill.fillData;
|
||||
@@ -606,10 +541,76 @@ export function createNativeOptimizedOrder(
|
||||
takerToken: fillData.order.takerToken,
|
||||
makerAmount,
|
||||
takerAmount,
|
||||
fills: [fill],
|
||||
fillData,
|
||||
fill: cleanFillForExport(fill),
|
||||
};
|
||||
return fill.type === FillQuoteTransformerOrderType.Rfq
|
||||
? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
|
||||
: { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
|
||||
}
|
||||
|
||||
export function createBridgeOrder(
|
||||
fill: Fill,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
side: MarketOperation,
|
||||
): OptimizedMarketBridgeOrder {
|
||||
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
||||
return {
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
source: fill.source,
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerAmount,
|
||||
takerAmount,
|
||||
fillData: createFinalBridgeOrderFillDataFromCollapsedFill(fill),
|
||||
fill: cleanFillForExport(fill),
|
||||
sourcePathId: fill.sourcePathId,
|
||||
};
|
||||
}
|
||||
|
||||
function cleanFillForExport(fill: Fill): Fill {
|
||||
return _.omit(fill, ['flags', 'fillData', 'sourcePathId', 'source', 'type']) as Fill;
|
||||
}
|
||||
|
||||
function createFinalBridgeOrderFillDataFromCollapsedFill(fill: Fill): FillData {
|
||||
switch (fill.source) {
|
||||
case ERC20BridgeSource.UniswapV3: {
|
||||
const fd = fill.fillData as UniswapV3FillData;
|
||||
const { uniswapPath, gasUsed } = getBestUniswapV3PathAmountForInputAmount(fd, fill.input);
|
||||
const finalFillData: FinalUniswapV3FillData = {
|
||||
router: fd.router,
|
||||
tokenAddressPath: fd.tokenAddressPath,
|
||||
uniswapPath,
|
||||
gasUsed,
|
||||
};
|
||||
return finalFillData;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return fill.fillData;
|
||||
}
|
||||
|
||||
function getBestUniswapV3PathAmountForInputAmount(
|
||||
fillData: UniswapV3FillData,
|
||||
inputAmount: BigNumber,
|
||||
): UniswapV3PathAmount {
|
||||
if (fillData.pathAmounts.length === 0) {
|
||||
throw new Error(`No Uniswap V3 paths`);
|
||||
}
|
||||
// Find the best path that can satisfy `inputAmount`.
|
||||
// Assumes `fillData.pathAmounts` is sorted ascending.
|
||||
for (const pathAmount of fillData.pathAmounts) {
|
||||
if (pathAmount.inputAmount.gte(inputAmount)) {
|
||||
return pathAmount;
|
||||
}
|
||||
}
|
||||
return fillData.pathAmounts[fillData.pathAmounts.length - 1];
|
||||
}
|
||||
|
||||
export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] {
|
||||
const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
|
||||
const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;
|
||||
return [makerToken, takerToken];
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import _ = require('lodash');
|
||||
|
||||
import { MarketOperation } from '../../types';
|
||||
|
||||
@@ -6,14 +7,7 @@ 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 {
|
||||
CollapsedFill,
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
Fill,
|
||||
NativeCollapsedFill,
|
||||
OptimizedMarketOrder,
|
||||
} from './types';
|
||||
import { ERC20BridgeSource, ExchangeProxyOverhead, Fill, NativeFillData, OptimizedMarketOrder } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||
|
||||
@@ -37,7 +31,6 @@ export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
|
||||
};
|
||||
|
||||
export class Path {
|
||||
public collapsedFills?: ReadonlyArray<CollapsedFill>;
|
||||
public orders?: OptimizedMarketOrder[];
|
||||
public sourceFlags: bigint = BigInt(0);
|
||||
protected _size: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
||||
@@ -57,16 +50,6 @@ export class Path {
|
||||
return path;
|
||||
}
|
||||
|
||||
public static clone(base: Path): Path {
|
||||
const clonedPath = new Path(base.side, base.fills.slice(), base.targetInput, base.pathPenaltyOpts);
|
||||
clonedPath.sourceFlags = base.sourceFlags;
|
||||
clonedPath._size = { ...base._size };
|
||||
clonedPath._adjustedSize = { ...base._adjustedSize };
|
||||
clonedPath.collapsedFills = base.collapsedFills === undefined ? undefined : base.collapsedFills.slice();
|
||||
clonedPath.orders = base.orders === undefined ? undefined : base.orders.slice();
|
||||
return clonedPath;
|
||||
}
|
||||
|
||||
protected constructor(
|
||||
protected readonly side: MarketOperation,
|
||||
public fills: ReadonlyArray<Fill>,
|
||||
@@ -74,68 +57,33 @@ export class Path {
|
||||
public readonly pathPenaltyOpts: PathPenaltyOpts,
|
||||
) {}
|
||||
|
||||
public append(fill: Fill): this {
|
||||
(this.fills as Fill[]).push(fill);
|
||||
this.sourceFlags |= fill.flags;
|
||||
this._addFillSize(fill);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a fallback path to the current path
|
||||
* Fallback must contain exclusive fills that are
|
||||
* not present in this path
|
||||
* Finalizes this path, creating fillable orders with the information required
|
||||
* for settlement
|
||||
*/
|
||||
public addFallback(fallback: Path): this {
|
||||
// We pre-pend the sources which have a higher probability of failure
|
||||
// This allows us to continue on to the remaining fills
|
||||
// If the "flakey" sources like Native were at the end, we may have a failure
|
||||
// as the last fill and then either revert, or go back to a source we previously
|
||||
// filled against
|
||||
const nativeFills = this.fills.filter(f => f.source === ERC20BridgeSource.Native);
|
||||
const otherFills = this.fills.filter(f => f.source !== ERC20BridgeSource.Native);
|
||||
|
||||
// Map to the unique source id and the index to represent a unique fill
|
||||
const fillToFillId = (fill: Fill) => `${fill.sourcePathId}${fill.index}`;
|
||||
const otherFillIds = otherFills.map(f => fillToFillId(f));
|
||||
|
||||
this.fills = [
|
||||
// Append all of the native fills first
|
||||
...nativeFills,
|
||||
// Add the other fills that are not native in the optimal path
|
||||
...otherFills,
|
||||
// Add the fills to the end that aren't already included
|
||||
...fallback.fills.filter(f => !otherFillIds.includes(fillToFillId(f))),
|
||||
];
|
||||
// Recompute the source flags
|
||||
this.sourceFlags = this.fills.reduce((flags, fill) => flags | fill.flags, BigInt(0));
|
||||
return this;
|
||||
}
|
||||
|
||||
public collapse(opts: CreateOrderFromPathOpts): CollapsedPath {
|
||||
public finalize(opts: CreateOrderFromPathOpts): FinalizedPath {
|
||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
||||
const collapsedFills = this.collapsedFills === undefined ? this._collapseFills() : this.collapsedFills;
|
||||
this.orders = [];
|
||||
for (let i = 0; i < collapsedFills.length; ) {
|
||||
if (collapsedFills[i].source === ERC20BridgeSource.Native) {
|
||||
this.orders.push(createNativeOptimizedOrder(collapsedFills[i] as NativeCollapsedFill, opts.side));
|
||||
++i;
|
||||
continue;
|
||||
for (const fill of this.fills) {
|
||||
// internal BigInt flag field is not supported JSON and is tricky
|
||||
// to remove upstream. Since it's not needed in a FinalizedPath we just drop it.
|
||||
const normalizedFill = _.omit(fill, 'flags') as Fill;
|
||||
if (fill.source === ERC20BridgeSource.Native) {
|
||||
this.orders.push(createNativeOptimizedOrder(normalizedFill as Fill<NativeFillData>, opts.side));
|
||||
} else {
|
||||
this.orders.push(createBridgeOrder(normalizedFill, makerToken, takerToken, opts.side));
|
||||
}
|
||||
|
||||
this.orders.push(createBridgeOrder(collapsedFills[i], makerToken, takerToken, opts.side));
|
||||
i += 1;
|
||||
}
|
||||
return this as CollapsedPath;
|
||||
}
|
||||
|
||||
public size(): PathSize {
|
||||
return this._size;
|
||||
return this as FinalizedPath;
|
||||
}
|
||||
|
||||
public adjustedSize(): PathSize {
|
||||
// Adjusted input/output has been adjusted by the cost of the DEX, but not by any
|
||||
// overhead added by the exchange proxy.
|
||||
const { input, output } = this._adjustedSize;
|
||||
const { exchangeProxyOverhead, outputAmountPerEth, inputAmountPerEth } = this.pathPenaltyOpts;
|
||||
// Calculate the additional penalty from the ways this path can be filled
|
||||
// by the exchange proxy, e.g VIPs (small) or FillQuoteTransformer (large)
|
||||
const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
|
||||
const pathPenalty = ethToOutputAmount({
|
||||
input,
|
||||
@@ -155,6 +103,10 @@ export class Path {
|
||||
return getCompleteRate(this.side, input, output, this.targetInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the rate of this path, where the output has been
|
||||
* adjusted for penalties (e.g cost)
|
||||
*/
|
||||
public adjustedRate(): BigNumber {
|
||||
const { input, output } = this.adjustedSize();
|
||||
return getRate(this.side, input, output);
|
||||
@@ -171,16 +123,11 @@ export class Path {
|
||||
return best;
|
||||
}
|
||||
|
||||
public adjustedSlippage(maxRate: BigNumber): number {
|
||||
if (maxRate.eq(0)) {
|
||||
return 0;
|
||||
}
|
||||
const totalRate = this.adjustedRate();
|
||||
const rateChange = maxRate.minus(totalRate);
|
||||
return rateChange.div(maxRate).toNumber();
|
||||
}
|
||||
|
||||
public isBetterThan(other: Path): boolean {
|
||||
/**
|
||||
* Compares two paths returning if this adjusted path
|
||||
* is better than the other adjusted path
|
||||
*/
|
||||
public isAdjustedBetterThan(other: Path): boolean {
|
||||
if (!this.targetInput.isEqualTo(other.targetInput)) {
|
||||
throw new Error(`Target input mismatch: ${this.targetInput} !== ${other.targetInput}`);
|
||||
}
|
||||
@@ -192,78 +139,6 @@ export class Path {
|
||||
} else {
|
||||
return this.adjustedCompleteRate().isGreaterThan(other.adjustedCompleteRate());
|
||||
}
|
||||
// if (otherInput.isLessThan(targetInput)) {
|
||||
// return input.isGreaterThan(otherInput);
|
||||
// } else if (input.isGreaterThanOrEqualTo(targetInput)) {
|
||||
// return this.adjustedCompleteRate().isGreaterThan(other.adjustedCompleteRate());
|
||||
// }
|
||||
// return false;
|
||||
}
|
||||
|
||||
public isComplete(): boolean {
|
||||
const { input } = this._size;
|
||||
return input.gte(this.targetInput);
|
||||
}
|
||||
|
||||
public isValid(skipDuplicateCheck: boolean = false): boolean {
|
||||
for (let i = 0; i < this.fills.length; ++i) {
|
||||
// Fill must immediately follow its parent.
|
||||
if (this.fills[i].parent) {
|
||||
if (i === 0 || this.fills[i - 1] !== this.fills[i].parent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!skipDuplicateCheck) {
|
||||
// Fill must not be duplicated.
|
||||
for (let j = 0; j < i; ++j) {
|
||||
if (this.fills[i] === this.fills[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public isValidNextFill(fill: Fill): boolean {
|
||||
if (this.fills.length === 0) {
|
||||
return !fill.parent;
|
||||
}
|
||||
if (this.fills[this.fills.length - 1] === fill.parent) {
|
||||
return true;
|
||||
}
|
||||
if (fill.parent) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _collapseFills(): ReadonlyArray<CollapsedFill> {
|
||||
this.collapsedFills = [];
|
||||
for (const fill of this.fills) {
|
||||
const source = fill.source;
|
||||
if (this.collapsedFills.length !== 0 && source !== ERC20BridgeSource.Native) {
|
||||
const prevFill = this.collapsedFills[this.collapsedFills.length - 1];
|
||||
// If the last fill is from the same source, merge them.
|
||||
if (prevFill.sourcePathId === fill.sourcePathId) {
|
||||
prevFill.input = prevFill.input.plus(fill.input);
|
||||
prevFill.output = prevFill.output.plus(fill.output);
|
||||
prevFill.fillData = fill.fillData;
|
||||
prevFill.subFills.push(fill);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
(this.collapsedFills as CollapsedFill[]).push({
|
||||
sourcePathId: fill.sourcePathId,
|
||||
source: fill.source,
|
||||
type: fill.type,
|
||||
fillData: fill.fillData,
|
||||
input: fill.input,
|
||||
output: fill.output,
|
||||
subFills: [fill],
|
||||
});
|
||||
}
|
||||
return this.collapsedFills;
|
||||
}
|
||||
|
||||
private _addFillSize(fill: Fill): void {
|
||||
@@ -285,7 +160,6 @@ export class Path {
|
||||
}
|
||||
}
|
||||
|
||||
export interface CollapsedPath extends Path {
|
||||
readonly collapsedFills: ReadonlyArray<CollapsedFill>;
|
||||
export interface FinalizedPath extends Path {
|
||||
readonly orders: OptimizedMarketOrder[];
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { assert } from '@0x/assert';
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { OptimizerCapture, route, SerializedPath } from '@0x/neon-router';
|
||||
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
import { performance } from 'perf_hooks';
|
||||
@@ -9,13 +10,12 @@ import { DEFAULT_WARNING_LOGGER } from '../../constants';
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||
|
||||
import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID, ZERO_AMOUNT } from './constants';
|
||||
import { dexSamplesToFills, ethToOutputAmount, nativeOrdersToFills } from './fills';
|
||||
import { DEFAULT_PATH_PENALTY_OPTS, Path, PathPenaltyOpts } from './path';
|
||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillData, SamplerMetrics } from './types';
|
||||
import { dexSampleToFill, ethToOutputAmount, nativeOrderToFill } from './fills';
|
||||
import { Path, PathPenaltyOpts } from './path';
|
||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillAdjustor, FillData, SamplerMetrics } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
|
||||
|
||||
const RUN_LIMIT_DECAY_FACTOR = 0.5;
|
||||
// NOTE: The Rust router will panic with less than 3 samples
|
||||
const MIN_NUM_SAMPLE_INPUTS = 3;
|
||||
|
||||
@@ -45,7 +45,7 @@ function calculateOuputFee(
|
||||
): BigNumber {
|
||||
if (isDexSample(sampleOrNativeOrder)) {
|
||||
const { input, output, source, fillData } = sampleOrNativeOrder;
|
||||
const fee = fees[source]?.(fillData) || 0;
|
||||
const fee = fees[source]?.(fillData).fee || ZERO_AMOUNT;
|
||||
const outputFee = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
@@ -56,7 +56,7 @@ function calculateOuputFee(
|
||||
return outputFee;
|
||||
} else {
|
||||
const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
|
||||
const fee = fees[ERC20BridgeSource.Native]?.(sampleOrNativeOrder) || 0;
|
||||
const fee = fees[ERC20BridgeSource.Native]?.(sampleOrNativeOrder).fee || ZERO_AMOUNT;
|
||||
const outputFee = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
@@ -77,6 +77,7 @@ function findRoutesAndCreateOptimalPath(
|
||||
fees: FeeSchedule,
|
||||
neonRouterNumSamples: number,
|
||||
vipSourcesSet: Set<ERC20BridgeSource>,
|
||||
fillAdjustor: FillAdjustor,
|
||||
): { allSourcesPath: Path | undefined; vipSourcesPath: Path | undefined } | undefined {
|
||||
// Currently the rust router is unable to handle 1 base unit sized quotes and will error out
|
||||
// To avoid flooding the logs with these errors we just return an insufficient liquidity error
|
||||
@@ -85,31 +86,44 @@ function findRoutesAndCreateOptimalPath(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const createFill = (sample: DexSample): Fill | undefined => {
|
||||
const fills = dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, fees);
|
||||
// NOTE: If the sample has 0 output dexSamplesToFills will return [] because no fill can be created
|
||||
if (fills.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return fills[0];
|
||||
// Create a `Fill` from a dex sample and adjust it with any passed in
|
||||
// adjustor
|
||||
const createFillFromDexSample = (sample: DexSample): Fill => {
|
||||
const fill = dexSampleToFill(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, fees);
|
||||
const adjustedFills = fillAdjustor.adjustFills(side, [fill], input);
|
||||
return adjustedFills[0];
|
||||
};
|
||||
|
||||
const createPathFromStrategy = (sourcesRustRoute: Float64Array, sourcesOutputAmounts: Float64Array) => {
|
||||
const createPathFromStrategy = (optimalRouteInputs: Float64Array, optimalRouteOutputs: Float64Array) => {
|
||||
/**
|
||||
* inputs are the amounts to fill at each source index
|
||||
* e.g fill 2076 at index 4
|
||||
* [ 0, 0, 0, 0, 2076, 464, 230,
|
||||
* 230, 0, 0, 0 ]
|
||||
* the sum represents the total input amount
|
||||
*
|
||||
* outputs are the amounts we expect out at each source index
|
||||
* [ 0, 0, 0, 0, 42216, 9359, 4677,
|
||||
* 4674, 0, 0, 0 ]
|
||||
* the sum represents the total expected output amount
|
||||
*/
|
||||
|
||||
const routesAndSamplesAndOutputs = _.zip(
|
||||
sourcesRustRoute,
|
||||
optimalRouteInputs,
|
||||
optimalRouteOutputs,
|
||||
samplesAndNativeOrdersWithResults,
|
||||
sourcesOutputAmounts,
|
||||
sampleSourcePathIds,
|
||||
);
|
||||
const adjustedFills: Fill[] = [];
|
||||
const totalRoutedAmount = BigNumber.sum(...sourcesRustRoute);
|
||||
const totalRoutedAmount = BigNumber.sum(...optimalRouteInputs);
|
||||
|
||||
// Due to precision errors we can end up with a totalRoutedAmount that is not exactly equal to the input
|
||||
const precisionErrorScalar = input.dividedBy(totalRoutedAmount);
|
||||
|
||||
const scale = input.dividedBy(totalRoutedAmount);
|
||||
for (const [
|
||||
routeInput,
|
||||
routeSamplesAndNativeOrders,
|
||||
outputAmount,
|
||||
routeSamplesAndNativeOrders,
|
||||
sourcePathId,
|
||||
] of routesAndSamplesAndOutputs) {
|
||||
if (!Number.isFinite(outputAmount)) {
|
||||
@@ -119,26 +133,27 @@ function findRoutesAndCreateOptimalPath(
|
||||
if (!routeInput || !routeSamplesAndNativeOrders || !outputAmount) {
|
||||
continue;
|
||||
}
|
||||
// TODO(kimpers): [TKR-241] amounts are sometimes clipped in the router due to precision loss for number/f64
|
||||
// TODO: [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),
|
||||
const routeInputCorrected = BigNumber.min(
|
||||
precisionErrorScalar.multipliedBy(routeInput).integerValue(BigNumber.ROUND_CEIL),
|
||||
input,
|
||||
);
|
||||
|
||||
const current = routeSamplesAndNativeOrders[routeSamplesAndNativeOrders.length - 1];
|
||||
// If it is a native single order we only have one Input/output
|
||||
// we want to convert this to an array of samples
|
||||
if (!isDexSample(current)) {
|
||||
const nativeFill = nativeOrdersToFills(
|
||||
const nativeFill = nativeOrderToFill(
|
||||
side,
|
||||
[current],
|
||||
rustInputAdjusted,
|
||||
current,
|
||||
routeInputCorrected,
|
||||
opts.outputAmountPerEth,
|
||||
opts.inputAmountPerEth,
|
||||
fees,
|
||||
false,
|
||||
)[0] as Fill | undefined;
|
||||
// Note: If the order has an adjusted rate of less than or equal to 0 it will be skipped
|
||||
// and nativeFill will be `undefined`
|
||||
);
|
||||
// Note: If the order has an adjusted rate of less than or equal to 0 it will be undefined
|
||||
if (nativeFill) {
|
||||
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
|
||||
adjustedFills.push({ ...nativeFill, sourcePathId: sourcePathId ?? hexUtils.random() });
|
||||
@@ -147,62 +162,54 @@ function findRoutesAndCreateOptimalPath(
|
||||
}
|
||||
|
||||
// NOTE: For DexSamples only
|
||||
let fill = createFill(current);
|
||||
let fill = createFillFromDexSample(current);
|
||||
if (!fill) {
|
||||
continue;
|
||||
}
|
||||
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
|
||||
|
||||
// From the output of the router, find the closest Sample in terms of input.
|
||||
// The Router may have chosen an amount to fill that we do not have a measured sample of
|
||||
// Choosing this accurately is required in some sources where the `FillData` may change depending
|
||||
// on the size of the trade. For example, UniswapV3 has variable gas cost
|
||||
// which increases with input.
|
||||
assert.assert(routeSamples.length >= 1, 'Found no sample to use for source');
|
||||
for (let k = routeSamples.length - 1; k >= 0; k--) {
|
||||
// If we're at the last remaining sample that's all we have left to use
|
||||
if (k === 0) {
|
||||
fill = createFill(routeSamples[0]) ?? fill;
|
||||
fill = createFillFromDexSample(routeSamples[0]) ?? fill;
|
||||
}
|
||||
if (rustInputAdjusted.isGreaterThan(routeSamples[k].input)) {
|
||||
if (routeInputCorrected.isGreaterThan(routeSamples[k].input)) {
|
||||
const left = routeSamples[k];
|
||||
const right = routeSamples[k + 1];
|
||||
if (left && right) {
|
||||
fill =
|
||||
createFill({
|
||||
createFillFromDexSample({
|
||||
...right, // default to the greater (for gas used)
|
||||
input: rustInputAdjusted,
|
||||
output: new BigNumber(outputAmount),
|
||||
input: routeInputCorrected,
|
||||
output: new BigNumber(outputAmount).integerValue(),
|
||||
}) ?? fill;
|
||||
} else {
|
||||
assert.assert(Boolean(left || right), 'No valid sample to use');
|
||||
fill = createFill(left || right) ?? fill;
|
||||
fill = createFillFromDexSample(left || right) ?? fill;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(kimpers): remove once we have solved the rounding/precision loss issues in the Rust router
|
||||
const maxSampledOutput = BigNumber.max(...routeSamples.map(s => s.output));
|
||||
// TODO: remove once we have solved the rounding/precision loss issues in the Rust router
|
||||
const maxSampledOutput = BigNumber.max(...routeSamples.map(s => s.output)).integerValue();
|
||||
// Scale output by scale factor but never go above the largest sample in sell quotes (unknown liquidity) or below 1 base unit (unfillable)
|
||||
const scaleOutput = (output: BigNumber) => {
|
||||
// Don't try to scale 0 output as it will be clamped to 1
|
||||
if (output.eq(ZERO_AMOUNT)) {
|
||||
return output;
|
||||
}
|
||||
|
||||
const scaled = output
|
||||
.times(scale)
|
||||
.decimalPlaces(0, side === MarketOperation.Sell ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL);
|
||||
const capped = MarketOperation.Sell ? BigNumber.min(scaled, maxSampledOutput) : scaled;
|
||||
|
||||
const capped = BigNumber.min(output.integerValue(), maxSampledOutput);
|
||||
return BigNumber.max(capped, 1);
|
||||
};
|
||||
|
||||
adjustedFills.push({
|
||||
...fill,
|
||||
input: rustInputAdjusted,
|
||||
input: routeInputCorrected,
|
||||
output: scaleOutput(fill.output),
|
||||
adjustedOutput: scaleOutput(fill.adjustedOutput),
|
||||
index: 0,
|
||||
parent: undefined,
|
||||
sourcePathId: sourcePathId ?? hexUtils.random(),
|
||||
});
|
||||
}
|
||||
@@ -224,7 +231,6 @@ function findRoutesAndCreateOptimalPath(
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourcePathId = hexUtils.random();
|
||||
const singleSourceSamplesWithOutput = [...singleSourceSamples];
|
||||
for (let i = singleSourceSamples.length - 1; i >= 0; i--) {
|
||||
const currentOutput = singleSourceSamples[i].output;
|
||||
@@ -240,17 +246,23 @@ function findRoutesAndCreateOptimalPath(
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(kimpers): Do we need to handle 0 entries, from eg Kyber?
|
||||
// TODO: 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(),
|
||||
);
|
||||
// Use the fill from createFillFromDexSample to apply
|
||||
// any user supplied adjustments
|
||||
const f = createFillFromDexSample(sample);
|
||||
memo.ids.push(`${f.source}-${serializedPaths.length}-${sampleIdx}`);
|
||||
memo.inputs.push(f.input.integerValue().toNumber());
|
||||
memo.outputs.push(f.output.integerValue().toNumber());
|
||||
// Calculate the penalty of this sample as the diff between the
|
||||
// output and the adjusted output
|
||||
const outputFee = f.output
|
||||
.minus(f.adjustedOutput)
|
||||
.absoluteValue()
|
||||
.integerValue()
|
||||
.toNumber();
|
||||
memo.outputFees.push(outputFee);
|
||||
|
||||
return memo;
|
||||
},
|
||||
@@ -265,6 +277,8 @@ function findRoutesAndCreateOptimalPath(
|
||||
|
||||
samplesAndNativeOrdersWithResults.push(singleSourceSamplesWithOutput);
|
||||
serializedPaths.push(serializedPath);
|
||||
|
||||
const sourcePathId = hexUtils.random();
|
||||
sampleSourcePathIds.push(sourcePathId);
|
||||
}
|
||||
|
||||
@@ -306,19 +320,22 @@ function findRoutesAndCreateOptimalPath(
|
||||
normalizedOrderOutput.times(scaleToInput).times(fraction),
|
||||
normalizedOrderOutput,
|
||||
);
|
||||
const id = `${ERC20BridgeSource.Native}-${serializedPaths.length}-${idx}-${i}`;
|
||||
const id = `${ERC20BridgeSource.Native}-${nativeOrder.type}-${serializedPaths.length}-${idx}-${i}`;
|
||||
inputs.push(currentInput.integerValue().toNumber());
|
||||
outputs.push(currentOutput.integerValue().toNumber());
|
||||
outputFees.push(fee);
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
// We have a VIP for the Rfq order type, Limit order currently goes through FQT
|
||||
const isVip = nativeOrder.type !== FillQuoteTransformerOrderType.Limit;
|
||||
|
||||
const serializedPath: SerializedPath = {
|
||||
ids,
|
||||
inputs,
|
||||
outputs,
|
||||
outputFees,
|
||||
isVip: true,
|
||||
isVip,
|
||||
};
|
||||
|
||||
samplesAndNativeOrdersWithResults.push([nativeOrder]);
|
||||
@@ -375,7 +392,7 @@ function findRoutesAndCreateOptimalPath(
|
||||
};
|
||||
}
|
||||
|
||||
export function findOptimalRustPathFromSamples(
|
||||
export function findOptimalPathFromSamples(
|
||||
side: MarketOperation,
|
||||
samples: DexSample[][],
|
||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||
@@ -384,6 +401,7 @@ export function findOptimalRustPathFromSamples(
|
||||
fees: FeeSchedule,
|
||||
chainId: ChainId,
|
||||
neonRouterNumSamples: number,
|
||||
fillAdjustor: FillAdjustor,
|
||||
samplerMetrics?: SamplerMetrics,
|
||||
): Path | undefined {
|
||||
const beforeTimeMs = performance.now();
|
||||
@@ -406,6 +424,7 @@ export function findOptimalRustPathFromSamples(
|
||||
fees,
|
||||
neonRouterNumSamples,
|
||||
vipSourcesSet,
|
||||
fillAdjustor,
|
||||
);
|
||||
|
||||
if (!paths) {
|
||||
@@ -415,7 +434,7 @@ export function findOptimalRustPathFromSamples(
|
||||
|
||||
const { allSourcesPath, vipSourcesPath } = paths;
|
||||
|
||||
if (!allSourcesPath || vipSourcesPath?.isBetterThan(allSourcesPath)) {
|
||||
if (!allSourcesPath || vipSourcesPath?.isAdjustedBetterThan(allSourcesPath)) {
|
||||
sendMetrics();
|
||||
return vipSourcesPath;
|
||||
}
|
||||
@@ -423,143 +442,3 @@ export function findOptimalRustPathFromSamples(
|
||||
sendMetrics();
|
||||
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 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);
|
||||
if (sortedPaths.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const rates = rateBySourcePathId(sortedPaths);
|
||||
let optimalPath = sortedPaths[0];
|
||||
for (const [i, path] of sortedPaths.slice(1).entries()) {
|
||||
optimalPath = mixPaths(side, optimalPath, path, targetInput, runLimit * RUN_LIMIT_DECAY_FACTOR ** i, rates);
|
||||
// Yield to event loop.
|
||||
await Promise.resolve();
|
||||
}
|
||||
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.
|
||||
export function fillsToSortedPaths(
|
||||
fills: Fill[][],
|
||||
side: MarketOperation,
|
||||
targetInput: BigNumber,
|
||||
opts: PathPenaltyOpts,
|
||||
): Path[] {
|
||||
const paths = fills.map(singleSourceFills => Path.create(side, singleSourceFills, targetInput, opts));
|
||||
const sortedPaths = paths.sort((a, b) => {
|
||||
const aRate = a.adjustedCompleteRate();
|
||||
const bRate = b.adjustedCompleteRate();
|
||||
// There is a case where the adjusted completed rate isn't sufficient for the desired amount
|
||||
// resulting in a NaN div by 0 (output)
|
||||
if (bRate.isNaN()) {
|
||||
return -1;
|
||||
}
|
||||
if (aRate.isNaN()) {
|
||||
return 1;
|
||||
}
|
||||
return bRate.comparedTo(aRate);
|
||||
});
|
||||
return sortedPaths;
|
||||
}
|
||||
|
||||
// Remove paths which have no impact on the optimal path
|
||||
export function reducePaths(sortedPaths: Path[], side: MarketOperation): Path[] {
|
||||
// Any path which has a min rate that is less than the best adjusted completed rate has no chance of improving
|
||||
// the overall route.
|
||||
const bestNonNativeCompletePath = sortedPaths.filter(
|
||||
p => p.isComplete() && p.fills[0].source !== ERC20BridgeSource.Native,
|
||||
)[0];
|
||||
|
||||
// If there is no complete path then just go ahead with the sorted paths
|
||||
// I.e if the token only exists on sources which cannot sell to infinity
|
||||
// or buys where X is greater than all the tokens available in the pools
|
||||
if (!bestNonNativeCompletePath) {
|
||||
return sortedPaths;
|
||||
}
|
||||
const bestNonNativeCompletePathAdjustedRate = bestNonNativeCompletePath.adjustedCompleteRate();
|
||||
if (!bestNonNativeCompletePathAdjustedRate.isGreaterThan(0)) {
|
||||
return sortedPaths;
|
||||
}
|
||||
|
||||
const filteredPaths = sortedPaths.filter(p =>
|
||||
p.bestRate().isGreaterThanOrEqualTo(bestNonNativeCompletePathAdjustedRate),
|
||||
);
|
||||
return filteredPaths;
|
||||
}
|
||||
|
||||
function mixPaths(
|
||||
side: MarketOperation,
|
||||
pathA: Path,
|
||||
pathB: Path,
|
||||
targetInput: BigNumber,
|
||||
maxSteps: number,
|
||||
rates: { [id: string]: BigNumber },
|
||||
): Path {
|
||||
const _maxSteps = Math.max(maxSteps, 32);
|
||||
let steps = 0;
|
||||
// We assume pathA is the better of the two initially.
|
||||
let bestPath: Path = pathA;
|
||||
|
||||
const _walk = (path: Path, remainingFills: Fill[]) => {
|
||||
steps += 1;
|
||||
if (path.isBetterThan(bestPath)) {
|
||||
bestPath = path;
|
||||
}
|
||||
const remainingInput = targetInput.minus(path.size().input);
|
||||
if (remainingInput.isGreaterThan(0)) {
|
||||
for (let i = 0; i < remainingFills.length && steps < _maxSteps; ++i) {
|
||||
const fill = remainingFills[i];
|
||||
// Only walk valid paths.
|
||||
if (!path.isValidNextFill(fill)) {
|
||||
continue;
|
||||
}
|
||||
// Remove this fill from the next list of candidate fills.
|
||||
const nextRemainingFills = remainingFills.slice();
|
||||
nextRemainingFills.splice(i, 1);
|
||||
// Recurse.
|
||||
_walk(Path.clone(path).append(fill), nextRemainingFills);
|
||||
}
|
||||
}
|
||||
};
|
||||
const allFills = [...pathA.fills, ...pathB.fills];
|
||||
// Sort subpaths by rate and keep fills contiguous to improve our
|
||||
// chances of walking ideal, valid paths first.
|
||||
const sortedFills = allFills.sort((a, b) => {
|
||||
if (a.sourcePathId !== b.sourcePathId) {
|
||||
return rates[b.sourcePathId].comparedTo(rates[a.sourcePathId]);
|
||||
}
|
||||
return a.index - b.index;
|
||||
});
|
||||
_walk(Path.create(side, [], targetInput, pathA.pathPenaltyOpts), sortedFills);
|
||||
if (!bestPath.isValid()) {
|
||||
throw new Error('nooope');
|
||||
}
|
||||
return bestPath;
|
||||
}
|
||||
|
||||
function rateBySourcePathId(paths: Path[]): { [id: string]: BigNumber } {
|
||||
return _.fromPairs(paths.map(p => [p.fills[0].sourcePathId, p.adjustedRate()]));
|
||||
}
|
||||
|
@@ -1,9 +1,20 @@
|
||||
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { MarketOperation } from '../../types';
|
||||
|
||||
import { SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
||||
import { DexSample, ERC20BridgeSource, ExchangeProxyOverhead, FeeSchedule, MultiHopFillData } from './types';
|
||||
import { adjustOutput } from './fills';
|
||||
import { IdentityFillAdjustor } from './identity_fill_adjustor';
|
||||
import {
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
FeeSchedule,
|
||||
Fill,
|
||||
FillAdjustor,
|
||||
MultiHopFillData,
|
||||
} from './types';
|
||||
|
||||
// tslint:disable:no-bitwise
|
||||
|
||||
@@ -18,20 +29,55 @@ export function getTwoHopAdjustedRate(
|
||||
outputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule = {},
|
||||
exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT,
|
||||
fillAdjustor: FillAdjustor = new IdentityFillAdjustor(),
|
||||
): BigNumber {
|
||||
const { output, input, fillData } = twoHopQuote;
|
||||
if (input.isLessThan(targetInput) || output.isZero()) {
|
||||
return ZERO_AMOUNT;
|
||||
}
|
||||
const penalty = outputAmountPerEth.times(
|
||||
exchangeProxyOverhead(
|
||||
SOURCE_FLAGS.MultiHop |
|
||||
SOURCE_FLAGS[fillData.firstHopSource.source] |
|
||||
SOURCE_FLAGS[fillData.secondHopSource.source],
|
||||
).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)),
|
||||
);
|
||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||
return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput);
|
||||
|
||||
// Flags to indicate which sources are used
|
||||
const flags =
|
||||
SOURCE_FLAGS.MultiHop |
|
||||
SOURCE_FLAGS[fillData.firstHopSource.source] |
|
||||
SOURCE_FLAGS[fillData.secondHopSource.source];
|
||||
|
||||
// Penalty of going to those sources in terms of output
|
||||
const sourcePenalty = outputAmountPerEth.times(fees[ERC20BridgeSource.MultiHop]!(fillData).fee).integerValue();
|
||||
|
||||
// Create a Fill so it can be adjusted by the `FillAdjustor`
|
||||
const fill: Fill = {
|
||||
...twoHopQuote,
|
||||
flags,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
adjustedOutput: adjustOutput(side, twoHopQuote.output, sourcePenalty),
|
||||
sourcePathId: `${ERC20BridgeSource.MultiHop}-${fillData.firstHopSource.source}-${fillData.secondHopSource.source}`,
|
||||
// We don't have this information at this stage
|
||||
gas: 0,
|
||||
};
|
||||
// Adjust the individual Fill
|
||||
// HACK: Chose the worst of slippage between the two sources in multihop
|
||||
const adjustedOutputLeft = fillAdjustor.adjustFills(
|
||||
side,
|
||||
[{ ...fill, source: fillData.firstHopSource.source }],
|
||||
targetInput,
|
||||
)[0].adjustedOutput;
|
||||
const adjustedOutputRight = fillAdjustor.adjustFills(
|
||||
side,
|
||||
[{ ...fill, source: fillData.secondHopSource.source }],
|
||||
targetInput,
|
||||
)[0].adjustedOutput;
|
||||
// In Sells, output smaller is worse (you're getting less out)
|
||||
// In Buys, output larger is worse (it's costing you more)
|
||||
const fillAdjustedOutput =
|
||||
side === MarketOperation.Sell
|
||||
? BigNumber.min(adjustedOutputLeft, adjustedOutputRight)
|
||||
: BigNumber.max(adjustedOutputLeft, adjustedOutputRight);
|
||||
|
||||
const pathPenalty = outputAmountPerEth.times(exchangeProxyOverhead(flags)).integerValue();
|
||||
const pathAdjustedOutput = adjustOutput(side, fillAdjustedOutput, pathPenalty);
|
||||
|
||||
return getRate(side, input, pathAdjustedOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,6 +105,8 @@ export function getCompleteRate(
|
||||
|
||||
/**
|
||||
* Computes the rate given the input/output of a path.
|
||||
*
|
||||
* If it is a sell, output/input. If it is a buy, input/output.
|
||||
*/
|
||||
export function getRate(side: MarketOperation, input: BigNumber, output: BigNumber): BigNumber {
|
||||
if (input.eq(0) || output.eq(0)) {
|
||||
|
@@ -48,6 +48,7 @@ import {
|
||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
UNISWAPV1_ROUTER_BY_CHAIN_ID,
|
||||
UNISWAPV3_CONFIG_BY_CHAIN_ID,
|
||||
VELODROME_ROUTER_BY_CHAIN_ID,
|
||||
ZERO_AMOUNT,
|
||||
} from './constants';
|
||||
import { getGeistInfoForPair } from './geist_utils';
|
||||
@@ -74,6 +75,7 @@ import {
|
||||
DexSample,
|
||||
DODOFillData,
|
||||
ERC20BridgeSource,
|
||||
FeeSchedule,
|
||||
GeistFillData,
|
||||
GeistInfo,
|
||||
GenericRouterFillData,
|
||||
@@ -95,6 +97,7 @@ import {
|
||||
TokenAdjacencyGraph,
|
||||
UniswapV2FillData,
|
||||
UniswapV3FillData,
|
||||
VelodromeFillData,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
@@ -473,62 +476,6 @@ export class SamplerOperations {
|
||||
});
|
||||
}
|
||||
|
||||
public getSmoothySellQuotes(
|
||||
pool: CurveInfo,
|
||||
fromTokenIdx: number,
|
||||
toTokenIdx: number,
|
||||
takerFillAmounts: BigNumber[],
|
||||
): SourceQuoteOperation<CurveFillData> {
|
||||
return new SamplerContractOperation({
|
||||
source: ERC20BridgeSource.Smoothy,
|
||||
fillData: {
|
||||
pool,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
},
|
||||
contract: this._samplerContract,
|
||||
function: this._samplerContract.sampleSellsFromSmoothy,
|
||||
params: [
|
||||
{
|
||||
poolAddress: pool.poolAddress,
|
||||
sellQuoteFunctionSelector: pool.sellQuoteFunctionSelector,
|
||||
buyQuoteFunctionSelector: pool.buyQuoteFunctionSelector,
|
||||
},
|
||||
new BigNumber(fromTokenIdx),
|
||||
new BigNumber(toTokenIdx),
|
||||
takerFillAmounts,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
public getSmoothyBuyQuotes(
|
||||
pool: CurveInfo,
|
||||
fromTokenIdx: number,
|
||||
toTokenIdx: number,
|
||||
makerFillAmounts: BigNumber[],
|
||||
): SourceQuoteOperation<CurveFillData> {
|
||||
return new SamplerContractOperation({
|
||||
source: ERC20BridgeSource.Smoothy,
|
||||
fillData: {
|
||||
pool,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
},
|
||||
contract: this._samplerContract,
|
||||
function: this._samplerContract.sampleBuysFromSmoothy,
|
||||
params: [
|
||||
{
|
||||
poolAddress: pool.poolAddress,
|
||||
sellQuoteFunctionSelector: pool.sellQuoteFunctionSelector,
|
||||
buyQuoteFunctionSelector: pool.buyQuoteFunctionSelector,
|
||||
},
|
||||
new BigNumber(fromTokenIdx),
|
||||
new BigNumber(toTokenIdx),
|
||||
makerFillAmounts,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
public getBalancerV2MultihopSellQuotes(
|
||||
vault: string,
|
||||
quoteSwaps: BalancerSwapInfo, // Should always be sell swap steps.
|
||||
@@ -1301,6 +1248,7 @@ export class SamplerOperations {
|
||||
params: [pool[0], tokenAddressPath, takerFillAmounts],
|
||||
});
|
||||
}
|
||||
|
||||
public getPlatypusBuyQuotes(
|
||||
router: string,
|
||||
pool: string[],
|
||||
@@ -1316,16 +1264,68 @@ export class SamplerOperations {
|
||||
});
|
||||
}
|
||||
|
||||
public getMedianSellRate(
|
||||
public getVelodromeSellQuotes(
|
||||
router: string,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
): SourceQuoteOperation<VelodromeFillData> {
|
||||
return new SamplerContractOperation({
|
||||
source: ERC20BridgeSource.Velodrome,
|
||||
contract: this._samplerContract,
|
||||
function: this._samplerContract.sampleSellsFromVelodrome,
|
||||
params: [router, takerToken, makerToken, takerFillAmounts],
|
||||
callback: (callResults: string, fillData: VelodromeFillData): BigNumber[] => {
|
||||
const [isStable, samples] = this._samplerContract.getABIDecodedReturnData<[boolean, BigNumber[]]>(
|
||||
'sampleSellsFromVelodrome',
|
||||
callResults,
|
||||
);
|
||||
fillData.router = router;
|
||||
fillData.stable = isStable;
|
||||
return samples;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public getVelodromeBuyQuotes(
|
||||
router: string,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
): SourceQuoteOperation<VelodromeFillData> {
|
||||
return new SamplerContractOperation({
|
||||
source: ERC20BridgeSource.Velodrome,
|
||||
contract: this._samplerContract,
|
||||
function: this._samplerContract.sampleBuysFromVelodrome,
|
||||
params: [router, takerToken, makerToken, makerFillAmounts],
|
||||
callback: (callResults: string, fillData: VelodromeFillData): BigNumber[] => {
|
||||
const [isStable, samples] = this._samplerContract.getABIDecodedReturnData<[boolean, BigNumber[]]>(
|
||||
'sampleBuysFromVelodrome',
|
||||
callResults,
|
||||
);
|
||||
fillData.router = router;
|
||||
fillData.stable = isStable;
|
||||
return samples;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best price for the native token
|
||||
* Best is calculated according to the fee schedule, so the price of the
|
||||
* best source, fee adjusted, will be returned.
|
||||
*/
|
||||
public getBestNativeTokenSellRate(
|
||||
sources: ERC20BridgeSource[],
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmount: BigNumber,
|
||||
nativeToken: string,
|
||||
nativeFillAmount: BigNumber,
|
||||
feeSchedule: FeeSchedule,
|
||||
): BatchedOperation<BigNumber> {
|
||||
if (makerToken.toLowerCase() === takerToken.toLowerCase()) {
|
||||
if (makerToken.toLowerCase() === nativeToken.toLowerCase()) {
|
||||
return SamplerOperations.constant(new BigNumber(1));
|
||||
}
|
||||
const subOps = this._getSellQuoteOperations(sources, makerToken, takerToken, [takerFillAmount], {
|
||||
const subOps = this._getSellQuoteOperations(sources, makerToken, nativeToken, [nativeFillAmount], {
|
||||
default: [],
|
||||
});
|
||||
return this._createBatch(
|
||||
@@ -1334,15 +1334,35 @@ export class SamplerOperations {
|
||||
if (samples.length === 0) {
|
||||
return ZERO_AMOUNT;
|
||||
}
|
||||
const flatSortedSamples = samples
|
||||
.reduce((acc, v) => acc.concat(...v))
|
||||
.filter(v => !v.isZero())
|
||||
.sort((a, b) => a.comparedTo(b));
|
||||
if (flatSortedSamples.length === 0) {
|
||||
return ZERO_AMOUNT;
|
||||
}
|
||||
const medianSample = flatSortedSamples[Math.floor(flatSortedSamples.length / 2)];
|
||||
return medianSample.div(takerFillAmount);
|
||||
|
||||
const adjustedPrices = subOps.map((s, i) => {
|
||||
// If the source gave us nothing, skip it and return a default
|
||||
if (samples[i].length === 0 || samples[i][0].isZero()) {
|
||||
return { adjustedPrice: ZERO_AMOUNT, source: s.source, price: ZERO_AMOUNT };
|
||||
}
|
||||
const v = samples[i][0];
|
||||
const price = v.dividedBy(nativeFillAmount);
|
||||
// Create an adjusted price to avoid selecting the following:
|
||||
// * a source that is too expensive to arbitrage given the gas environment
|
||||
// * when a number of sources are poorly priced or liquidity is low
|
||||
|
||||
// Fee is already gas * gasPrice
|
||||
const fee = feeSchedule[subOps[i].source]
|
||||
? feeSchedule[subOps[i].source]!(subOps[i].fillData).fee
|
||||
: ZERO_AMOUNT;
|
||||
const adjustedNativeAmount = nativeFillAmount.plus(fee);
|
||||
const adjustedPrice = v.div(adjustedNativeAmount);
|
||||
return {
|
||||
adjustedPrice,
|
||||
source: subOps[i].source,
|
||||
price,
|
||||
};
|
||||
});
|
||||
|
||||
const sortedPrices = adjustedPrices.sort((a, b) => a.adjustedPrice.comparedTo(b.adjustedPrice));
|
||||
const selectedPrice = sortedPrices[sortedPrices.length - 1].price;
|
||||
|
||||
return selectedPrice;
|
||||
},
|
||||
() => ZERO_AMOUNT,
|
||||
);
|
||||
@@ -1430,16 +1450,11 @@ export class SamplerOperations {
|
||||
case ERC20BridgeSource.PancakeSwapV2:
|
||||
case ERC20BridgeSource.BakerySwap:
|
||||
case ERC20BridgeSource.ApeSwap:
|
||||
case ERC20BridgeSource.CafeSwap:
|
||||
case ERC20BridgeSource.CheeseSwap:
|
||||
case ERC20BridgeSource.JulSwap:
|
||||
case ERC20BridgeSource.QuickSwap:
|
||||
case ERC20BridgeSource.ComethSwap:
|
||||
case ERC20BridgeSource.Dfyn:
|
||||
case ERC20BridgeSource.WaultSwap:
|
||||
case ERC20BridgeSource.Polydex:
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
case ERC20BridgeSource.JetSwap:
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
@@ -1448,6 +1463,8 @@ export class SamplerOperations {
|
||||
case ERC20BridgeSource.Yoshi:
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
case ERC20BridgeSource.BiSwap:
|
||||
case ERC20BridgeSource.MDex:
|
||||
case ERC20BridgeSource.KnightSwap:
|
||||
case ERC20BridgeSource.MeshSwap:
|
||||
const uniLikeRouter = uniswapV2LikeRouterAddress(this.chainId, source);
|
||||
if (!isValidAddress(uniLikeRouter)) {
|
||||
@@ -1484,15 +1501,6 @@ export class SamplerOperations {
|
||||
source,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.Smoothy:
|
||||
return getCurveLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool =>
|
||||
this.getSmoothySellQuotes(
|
||||
pool,
|
||||
pool.tokens.indexOf(takerToken),
|
||||
pool.tokens.indexOf(makerToken),
|
||||
takerFillAmounts,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.Shell:
|
||||
case ERC20BridgeSource.Component:
|
||||
return getShellLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool =>
|
||||
@@ -1717,6 +1725,14 @@ export class SamplerOperations {
|
||||
takerFillAmounts,
|
||||
);
|
||||
}
|
||||
case ERC20BridgeSource.Velodrome: {
|
||||
return this.getVelodromeSellQuotes(
|
||||
VELODROME_ROUTER_BY_CHAIN_ID[this.chainId],
|
||||
takerToken,
|
||||
makerToken,
|
||||
takerFillAmounts,
|
||||
);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported sell sample source: ${source}`);
|
||||
}
|
||||
@@ -1772,16 +1788,11 @@ export class SamplerOperations {
|
||||
case ERC20BridgeSource.PancakeSwapV2:
|
||||
case ERC20BridgeSource.BakerySwap:
|
||||
case ERC20BridgeSource.ApeSwap:
|
||||
case ERC20BridgeSource.CafeSwap:
|
||||
case ERC20BridgeSource.CheeseSwap:
|
||||
case ERC20BridgeSource.JulSwap:
|
||||
case ERC20BridgeSource.QuickSwap:
|
||||
case ERC20BridgeSource.ComethSwap:
|
||||
case ERC20BridgeSource.Dfyn:
|
||||
case ERC20BridgeSource.WaultSwap:
|
||||
case ERC20BridgeSource.Polydex:
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
case ERC20BridgeSource.JetSwap:
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
@@ -1790,6 +1801,8 @@ export class SamplerOperations {
|
||||
case ERC20BridgeSource.Yoshi:
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
case ERC20BridgeSource.BiSwap:
|
||||
case ERC20BridgeSource.MDex:
|
||||
case ERC20BridgeSource.KnightSwap:
|
||||
case ERC20BridgeSource.MeshSwap:
|
||||
const uniLikeRouter = uniswapV2LikeRouterAddress(this.chainId, source);
|
||||
if (!isValidAddress(uniLikeRouter)) {
|
||||
@@ -1826,15 +1839,6 @@ export class SamplerOperations {
|
||||
source,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.Smoothy:
|
||||
return getCurveLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool =>
|
||||
this.getSmoothyBuyQuotes(
|
||||
pool,
|
||||
pool.tokens.indexOf(takerToken),
|
||||
pool.tokens.indexOf(makerToken),
|
||||
makerFillAmounts,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.Shell:
|
||||
case ERC20BridgeSource.Component:
|
||||
return getShellLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool =>
|
||||
@@ -2053,6 +2057,14 @@ export class SamplerOperations {
|
||||
makerFillAmounts,
|
||||
);
|
||||
}
|
||||
case ERC20BridgeSource.Velodrome: {
|
||||
return this.getVelodromeBuyQuotes(
|
||||
VELODROME_ROUTER_BY_CHAIN_ID[this.chainId],
|
||||
takerToken,
|
||||
makerToken,
|
||||
makerFillAmounts,
|
||||
);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported buy sample source: ${source}`);
|
||||
}
|
||||
|
@@ -55,7 +55,6 @@ export enum ERC20BridgeSource {
|
||||
DodoV2 = 'DODO_V2',
|
||||
CryptoCom = 'CryptoCom',
|
||||
KyberDmm = 'KyberDMM',
|
||||
Smoothy = 'Smoothy',
|
||||
Component = 'Component',
|
||||
Saddle = 'Saddle',
|
||||
XSigma = 'xSigma',
|
||||
@@ -71,23 +70,20 @@ export enum ERC20BridgeSource {
|
||||
PancakeSwap = 'PancakeSwap',
|
||||
PancakeSwapV2 = 'PancakeSwap_V2',
|
||||
BiSwap = 'BiSwap',
|
||||
MDex = 'MDex',
|
||||
KnightSwap = 'KnightSwap',
|
||||
BakerySwap = 'BakerySwap',
|
||||
Nerve = 'Nerve',
|
||||
Belt = 'Belt',
|
||||
Ellipsis = 'Ellipsis',
|
||||
ApeSwap = 'ApeSwap',
|
||||
CafeSwap = 'CafeSwap',
|
||||
CheeseSwap = 'CheeseSwap',
|
||||
JulSwap = 'JulSwap',
|
||||
ACryptos = 'ACryptoS',
|
||||
// Polygon only
|
||||
QuickSwap = 'QuickSwap',
|
||||
ComethSwap = 'ComethSwap',
|
||||
Dfyn = 'Dfyn',
|
||||
WaultSwap = 'WaultSwap',
|
||||
Polydex = 'Polydex',
|
||||
FirebirdOneSwap = 'FirebirdOneSwap',
|
||||
JetSwap = 'JetSwap',
|
||||
IronSwap = 'IronSwap',
|
||||
MeshSwap = 'MeshSwap',
|
||||
// Avalanche
|
||||
@@ -106,6 +102,8 @@ export enum ERC20BridgeSource {
|
||||
MorpheusSwap = 'MorpheusSwap',
|
||||
Yoshi = 'Yoshi',
|
||||
Geist = 'Geist',
|
||||
// Optimism
|
||||
Velodrome = 'Velodrome',
|
||||
}
|
||||
export type SourcesWithPoolsCache =
|
||||
| ERC20BridgeSource.Balancer
|
||||
@@ -132,7 +130,7 @@ export enum CurveFunctionSelectors {
|
||||
exchange_underlying_v2 = '0x65b2489b',
|
||||
get_dy_v2 = '0x556d6e9f',
|
||||
get_dy_underlying_v2 = '0x85f11d1e',
|
||||
// Smoothy
|
||||
// Smoothy(deprecated)
|
||||
swap_uint256 = '0x5673b02d', // swap(uint256,uint256,uint256,uint256)
|
||||
get_swap_amount = '0x45cf2ef6', // getSwapAmount(uint256,uint256,uint256)
|
||||
// Nerve BSC, Saddle Mainnet, Synapse
|
||||
@@ -376,6 +374,12 @@ export interface PlatypusFillData extends FillData {
|
||||
pool: string[];
|
||||
tokenAddressPath: string[];
|
||||
}
|
||||
|
||||
export interface VelodromeFillData extends FillData {
|
||||
router: string;
|
||||
stable: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a node on a fill path.
|
||||
*/
|
||||
@@ -397,45 +401,10 @@ export interface Fill<TFillData extends FillData = FillData> {
|
||||
output: BigNumber;
|
||||
// The output fill amount, adjusted by fees.
|
||||
adjustedOutput: BigNumber;
|
||||
// Fill that must precede this one. This enforces certain fills to be contiguous.
|
||||
parent?: Fill;
|
||||
// The index of the fill in the original path.
|
||||
index: number;
|
||||
// The expected gas cost of this fill
|
||||
gas: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents continguous fills on a path that have been merged together.
|
||||
*/
|
||||
export interface CollapsedFill<TFillData extends FillData = FillData> {
|
||||
source: ERC20BridgeSource;
|
||||
type: FillQuoteTransformerOrderType; // should correspond with TFillData
|
||||
fillData: TFillData;
|
||||
// Unique ID of the original source path this fill belongs to.
|
||||
// This is generated when the path is generated and is useful to distinguish
|
||||
// paths that have the same `source` IDs but are distinct (e.g., Curves).
|
||||
sourcePathId: string;
|
||||
/**
|
||||
* Total input amount (sum of `subFill`s)
|
||||
*/
|
||||
input: BigNumber;
|
||||
/**
|
||||
* Total output amount (sum of `subFill`s)
|
||||
*/
|
||||
output: BigNumber;
|
||||
/**
|
||||
* Quantities of all the fills that were collapsed.
|
||||
*/
|
||||
subFills: Array<{
|
||||
input: BigNumber;
|
||||
output: BigNumber;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `CollapsedFill` wrapping a native order.
|
||||
*/
|
||||
export interface NativeCollapsedFill extends CollapsedFill<NativeFillData> {}
|
||||
|
||||
export interface OptimizedMarketOrderBase<TFillData extends FillData = FillData> {
|
||||
source: ERC20BridgeSource;
|
||||
fillData: TFillData;
|
||||
@@ -444,24 +413,21 @@ export interface OptimizedMarketOrderBase<TFillData extends FillData = FillData>
|
||||
takerToken: string;
|
||||
makerAmount: BigNumber; // The amount we wish to buy from this order, e.g inclusive of any previous partial fill
|
||||
takerAmount: BigNumber; // The amount we wish to fill this for, e.g inclusive of any previous partial fill
|
||||
fills: CollapsedFill[];
|
||||
fill: Omit<Fill, 'flags' | 'fillData' | 'sourcePathId' | 'source' | 'type'>; // Remove duplicates which have been brought into the OrderBase interface
|
||||
}
|
||||
|
||||
export interface OptimizedMarketBridgeOrder<TFillData extends FillData = FillData>
|
||||
extends OptimizedMarketOrderBase<TFillData> {
|
||||
type: FillQuoteTransformerOrderType.Bridge;
|
||||
fillData: TFillData;
|
||||
sourcePathId: string;
|
||||
}
|
||||
|
||||
export interface OptimizedLimitOrder extends OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
||||
type: FillQuoteTransformerOrderType.Limit;
|
||||
fillData: NativeLimitOrderFillData;
|
||||
}
|
||||
|
||||
export interface OptimizedRfqOrder extends OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
||||
type: FillQuoteTransformerOrderType.Rfq;
|
||||
fillData: NativeRfqOrderFillData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -478,8 +444,12 @@ export interface GetMarketOrdersRfqOpts extends RfqRequestOpts {
|
||||
firmQuoteValidator?: RfqFirmQuoteValidator;
|
||||
}
|
||||
|
||||
export type FeeEstimate = (fillData: FillData) => number | BigNumber;
|
||||
export type FeeEstimate = (fillData: FillData) => { gas: number; fee: BigNumber };
|
||||
export type FeeSchedule = Partial<{ [key in ERC20BridgeSource]: FeeEstimate }>;
|
||||
|
||||
export type GasEstimate = (fillData: FillData) => number;
|
||||
export type GasSchedule = Partial<{ [key in ERC20BridgeSource]: GasEstimate }>;
|
||||
|
||||
export type ExchangeProxyOverhead = (sourceFlags: bigint) => BigNumber;
|
||||
|
||||
/**
|
||||
@@ -543,7 +513,7 @@ export interface GetMarketOrdersOpts {
|
||||
/**
|
||||
* Estimated gas consumed by each liquidity source.
|
||||
*/
|
||||
gasSchedule: FeeSchedule;
|
||||
gasSchedule: GasSchedule;
|
||||
exchangeProxyOverhead: ExchangeProxyOverhead;
|
||||
/**
|
||||
* Whether to pad the quote with a redundant fallback quote using different
|
||||
@@ -578,6 +548,11 @@ export interface GetMarketOrdersOpts {
|
||||
* Sampler metrics for recording data on the sampler service and operations
|
||||
*/
|
||||
samplerMetrics?: SamplerMetrics;
|
||||
|
||||
/**
|
||||
* Adjusts fills individual fills based on caller supplied criteria
|
||||
*/
|
||||
fillAdjustor: FillAdjustor;
|
||||
}
|
||||
|
||||
export interface SamplerMetrics {
|
||||
@@ -623,7 +598,7 @@ export interface SourceQuoteOperation<TFillData extends FillData = FillData> ext
|
||||
export interface OptimizerResult {
|
||||
optimizedOrders: OptimizedMarketOrder[];
|
||||
sourceFlags: bigint;
|
||||
liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
|
||||
liquidityDelivered: Readonly<Fill[] | DexSample<MultiHopFillData>>;
|
||||
marketSideLiquidity: MarketSideLiquidity;
|
||||
adjustedRate: BigNumber;
|
||||
takerAmountPerEth: BigNumber;
|
||||
@@ -691,8 +666,13 @@ export interface GenerateOptimizedOrdersOpts {
|
||||
gasPrice: BigNumber;
|
||||
neonRouterNumSamples: number;
|
||||
samplerMetrics?: SamplerMetrics;
|
||||
fillAdjustor: FillAdjustor;
|
||||
}
|
||||
|
||||
export interface ComparisonPrice {
|
||||
wholeOrder: BigNumber | undefined;
|
||||
}
|
||||
|
||||
export interface FillAdjustor {
|
||||
adjustFills: (side: MarketOperation, fills: Fill[], amount: BigNumber) => Fill[];
|
||||
}
|
||||
|
@@ -5,12 +5,11 @@ import _ = require('lodash');
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../types';
|
||||
|
||||
import {
|
||||
CollapsedFill,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
FillData,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
NativeFillData,
|
||||
NativeLimitOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
@@ -123,7 +122,7 @@ export interface PriceComparisonsReport {
|
||||
export function generateQuoteReport(
|
||||
marketOperation: MarketOperation,
|
||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||
liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
|
||||
liquidityDelivered: ReadonlyArray<Fill> | DexSample<MultiHopFillData>,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
quoteRequestor?: QuoteRequestor,
|
||||
): QuoteReport {
|
||||
@@ -174,7 +173,7 @@ export function generateQuoteReport(
|
||||
export function generateExtendedQuoteReportSources(
|
||||
marketOperation: MarketOperation,
|
||||
quotes: RawQuotes,
|
||||
liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
|
||||
liquidityDelivered: ReadonlyArray<Fill> | DexSample<MultiHopFillData>,
|
||||
amount: BigNumber,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
quoteRequestor?: QuoteRequestor,
|
||||
@@ -207,7 +206,7 @@ export function generateExtendedQuoteReportSources(
|
||||
..._.flatten(
|
||||
quotes.dexQuotes.map(dex =>
|
||||
dex
|
||||
.filter(quote => isDexSampleForTotalAmount(quote, amount))
|
||||
.filter(quote => isDexSampleFilter(quote, amount))
|
||||
.map(quote => dexSampleToReportSource(quote, marketOperation)),
|
||||
),
|
||||
),
|
||||
@@ -306,8 +305,9 @@ 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, amount: BigNumber): boolean {
|
||||
return ds.input.eq(amount);
|
||||
function isDexSampleFilter(ds: DexSample, amount: BigNumber): boolean {
|
||||
// The entry is for the total amont, not a sampler entry && there was liquidity in the source
|
||||
return ds.input.eq(amount) && ds.output.isGreaterThan(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,7 +342,7 @@ export function multiHopSampleToReportSource(
|
||||
}
|
||||
}
|
||||
|
||||
function _isNativeOrderFromCollapsedFill(cf: CollapsedFill): cf is NativeCollapsedFill {
|
||||
function _isNativeOrderFromCollapsedFill(cf: Fill): cf is Fill<NativeFillData> {
|
||||
const { type } = cf;
|
||||
return type === FillQuoteTransformerOrderType.Limit || type === FillQuoteTransformerOrderType.Rfq;
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import { BigNumber } from '@0x/utils';
|
||||
import { constants } from '../constants';
|
||||
import { MarketOperation } from '../types';
|
||||
|
||||
import { FeeSchedule, NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types';
|
||||
import { GasSchedule, NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types';
|
||||
import { getNativeAdjustedTakerFeeAmount } from './utils';
|
||||
|
||||
const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants;
|
||||
@@ -72,7 +72,7 @@ export interface QuoteFillInfo {
|
||||
}
|
||||
|
||||
export interface QuoteFillInfoOpts {
|
||||
gasSchedule: FeeSchedule;
|
||||
gasSchedule: GasSchedule;
|
||||
protocolFeeMultiplier: BigNumber;
|
||||
slippage: number;
|
||||
}
|
||||
@@ -140,7 +140,7 @@ export function fillQuoteOrders(
|
||||
fillOrders: QuoteFillOrderCall[],
|
||||
inputAmount: BigNumber,
|
||||
protocolFeePerFillOrder: BigNumber,
|
||||
gasSchedule: FeeSchedule,
|
||||
gasSchedule: GasSchedule,
|
||||
): IntermediateQuoteFillResult {
|
||||
const result: IntermediateQuoteFillResult = {
|
||||
...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT,
|
||||
@@ -151,39 +151,27 @@ export function fillQuoteOrders(
|
||||
if (remainingInput.lte(0)) {
|
||||
break;
|
||||
}
|
||||
for (const fill of fo.order.fills) {
|
||||
if (remainingInput.lte(0)) {
|
||||
break;
|
||||
}
|
||||
const { source, fillData } = fill;
|
||||
const gas = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(fillData);
|
||||
result.gas += new BigNumber(gas).toNumber();
|
||||
result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT;
|
||||
const { source, fillData } = fo.order;
|
||||
const gas = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(fillData);
|
||||
result.gas += new BigNumber(gas).toNumber();
|
||||
result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT;
|
||||
|
||||
// Actual rates are rarely linear, so fill subfills individually to
|
||||
// get a better approximation of fill size.
|
||||
for (const subFill of fill.subFills) {
|
||||
if (remainingInput.lte(0)) {
|
||||
break;
|
||||
}
|
||||
const filledInput = solveForInputFillAmount(
|
||||
remainingInput,
|
||||
subFill.input,
|
||||
fo.totalOrderInput,
|
||||
fo.totalOrderInputFee,
|
||||
);
|
||||
const filledOutput = subFill.output.times(filledInput.div(subFill.input));
|
||||
const filledInputFee = filledInput.div(fo.totalOrderInput).times(fo.totalOrderInputFee);
|
||||
const filledOutputFee = filledOutput.div(fo.totalOrderOutput).times(fo.totalOrderOutputFee);
|
||||
const filledInput = solveForInputFillAmount(
|
||||
remainingInput,
|
||||
fo.order.fill.input,
|
||||
fo.totalOrderInput,
|
||||
fo.totalOrderInputFee,
|
||||
);
|
||||
const filledOutput = fo.order.fill.output.times(filledInput.div(fo.order.fill.input));
|
||||
const filledInputFee = filledInput.div(fo.totalOrderInput).times(fo.totalOrderInputFee);
|
||||
const filledOutputFee = filledOutput.div(fo.totalOrderOutput).times(fo.totalOrderOutputFee);
|
||||
|
||||
result.inputBySource[source] = result.inputBySource[source].plus(filledInput);
|
||||
result.input = result.input.plus(filledInput);
|
||||
result.output = result.output.plus(filledOutput);
|
||||
result.inputFee = result.inputFee.plus(filledInputFee);
|
||||
result.outputFee = result.outputFee.plus(filledOutputFee);
|
||||
remainingInput = remainingInput.minus(filledInput.plus(filledInputFee));
|
||||
}
|
||||
}
|
||||
result.inputBySource[source] = result.inputBySource[source].plus(filledInput);
|
||||
result.input = result.input.plus(filledInput);
|
||||
result.output = result.output.plus(filledOutput);
|
||||
result.inputFee = result.inputFee.plus(filledInputFee);
|
||||
result.outputFee = result.outputFee.plus(filledOutputFee);
|
||||
remainingInput = remainingInput.minus(filledInput.plus(filledInputFee));
|
||||
// NOTE: V4 Limit orders have Protocol fees
|
||||
const protocolFee = hasProtocolFee(fo.order) ? protocolFeePerFillOrder : ZERO_AMOUNT;
|
||||
result.protocolFee = result.protocolFee.plus(protocolFee);
|
||||
@@ -314,7 +302,7 @@ function fromIntermediateQuoteFillResult(ir: IntermediateQuoteFillResult, quoteI
|
||||
};
|
||||
}
|
||||
|
||||
function getTotalGasUsedByFills(fills: OptimizedMarketOrder[], gasSchedule: FeeSchedule): number {
|
||||
function getTotalGasUsedByFills(fills: OptimizedMarketOrder[], gasSchedule: GasSchedule): number {
|
||||
let gasUsed = 0;
|
||||
for (const f of fills) {
|
||||
const fee = gasSchedule[f.source] === undefined ? 0 : gasSchedule[f.source]!(f.fillData);
|
||||
|
@@ -31,7 +31,6 @@ import * as IMStable from '../test/generated-artifacts/IMStable.json';
|
||||
import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json';
|
||||
import * as IPlatypus from '../test/generated-artifacts/IPlatypus.json';
|
||||
import * as IShell from '../test/generated-artifacts/IShell.json';
|
||||
import * as ISmoothy from '../test/generated-artifacts/ISmoothy.json';
|
||||
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
|
||||
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
|
||||
import * as KyberDmmSampler from '../test/generated-artifacts/KyberDmmSampler.json';
|
||||
@@ -44,13 +43,13 @@ import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSamp
|
||||
import * as PlatypusSampler from '../test/generated-artifacts/PlatypusSampler.json';
|
||||
import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json';
|
||||
import * as ShellSampler from '../test/generated-artifacts/ShellSampler.json';
|
||||
import * as SmoothySampler from '../test/generated-artifacts/SmoothySampler.json';
|
||||
import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json';
|
||||
import * as TwoHopSampler from '../test/generated-artifacts/TwoHopSampler.json';
|
||||
import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json';
|
||||
import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json';
|
||||
import * as UniswapV3Sampler from '../test/generated-artifacts/UniswapV3Sampler.json';
|
||||
import * as UtilitySampler from '../test/generated-artifacts/UtilitySampler.json';
|
||||
import * as VelodromeSampler from '../test/generated-artifacts/VelodromeSampler.json';
|
||||
export const artifacts = {
|
||||
ApproximateBuys: ApproximateBuys as ContractArtifact,
|
||||
BalanceChecker: BalanceChecker as ContractArtifact,
|
||||
@@ -77,12 +76,12 @@ export const artifacts = {
|
||||
PlatypusSampler: PlatypusSampler as ContractArtifact,
|
||||
SamplerUtils: SamplerUtils as ContractArtifact,
|
||||
ShellSampler: ShellSampler as ContractArtifact,
|
||||
SmoothySampler: SmoothySampler as ContractArtifact,
|
||||
TwoHopSampler: TwoHopSampler as ContractArtifact,
|
||||
UniswapSampler: UniswapSampler as ContractArtifact,
|
||||
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,
|
||||
UniswapV3Sampler: UniswapV3Sampler as ContractArtifact,
|
||||
UtilitySampler: UtilitySampler as ContractArtifact,
|
||||
VelodromeSampler: VelodromeSampler as ContractArtifact,
|
||||
IBalancer: IBalancer as ContractArtifact,
|
||||
IBalancerV2Vault: IBalancerV2Vault as ContractArtifact,
|
||||
IBancor: IBancor as ContractArtifact,
|
||||
@@ -94,7 +93,6 @@ export const artifacts = {
|
||||
IMultiBridge: IMultiBridge as ContractArtifact,
|
||||
IPlatypus: IPlatypus as ContractArtifact,
|
||||
IShell: IShell as ContractArtifact,
|
||||
ISmoothy: ISmoothy as ContractArtifact,
|
||||
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
|
||||
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
|
||||
TestNativeOrderSampler: TestNativeOrderSampler as ContractArtifact,
|
||||
|
@@ -18,7 +18,7 @@ const expect = chai.expect;
|
||||
const DAI_TOKEN = '0x6b175474e89094c44da98b954eedeac495271d0f';
|
||||
const ETH_TOKEN = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
||||
const GAS_PRICE = new BigNumber(50e9); // 50 gwei
|
||||
const NATIVE_ORDER_FEE = new BigNumber(220e3); // 220K gas
|
||||
const NATIVE_ORDER_GAS = 220e3; // 220K gas
|
||||
|
||||
// DEX samples to fill in MarketSideLiquidity
|
||||
const curveSample: DexSample = {
|
||||
@@ -36,7 +36,10 @@ const uniswapSample1: DexSample = {
|
||||
const dexQuotes: DexSample[] = [curveSample, uniswapSample1];
|
||||
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Native]: _.constant(GAS_PRICE.times(NATIVE_ORDER_FEE)),
|
||||
[ERC20BridgeSource.Native]: _.constant({
|
||||
gas: NATIVE_ORDER_GAS,
|
||||
fee: GAS_PRICE.times(NATIVE_ORDER_GAS),
|
||||
}),
|
||||
};
|
||||
|
||||
const exchangeProxyOverhead = (sourceFlags: bigint) => {
|
||||
|
@@ -23,6 +23,8 @@ import { ExchangeProxySwapQuoteConsumer } from '../src/quote_consumers/exchange_
|
||||
import { AffiliateFeeType, MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types';
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
NativeFillData,
|
||||
OptimizedLimitOrder,
|
||||
OptimizedMarketOrder,
|
||||
} from '../src/utils/market_operation_utils/types';
|
||||
@@ -100,7 +102,8 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
takerToken: order.takerToken,
|
||||
makerAmount: order.makerAmount,
|
||||
takerAmount: order.takerAmount,
|
||||
fills: [],
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
fill: {} as Fill<NativeFillData>,
|
||||
...optimizerFields,
|
||||
};
|
||||
}
|
||||
|
@@ -16,16 +16,14 @@ import * as _ from 'lodash';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
|
||||
import { MarketOperation, QuoteRequestor, RfqRequestOpts, SignedNativeOrder } from '../src';
|
||||
import { Integrator, NativeOrderWithFillableAmounts } from '../src/types';
|
||||
import { Integrator } 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';
|
||||
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
||||
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
|
||||
@@ -39,7 +37,6 @@ import {
|
||||
GetMarketOrdersOpts,
|
||||
LiquidityProviderFillData,
|
||||
MarketSideLiquidity,
|
||||
NativeFillData,
|
||||
OptimizedMarketBridgeOrder,
|
||||
OptimizerResultWithReport,
|
||||
TokenAdjacencyGraph,
|
||||
@@ -272,7 +269,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
};
|
||||
}
|
||||
|
||||
type GetMedianRateOperation = (
|
||||
type GetBestNativeTokenSellRateOperation = (
|
||||
sources: ERC20BridgeSource[],
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
@@ -281,7 +278,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
liquidityProviderAddress?: string,
|
||||
) => BigNumber;
|
||||
|
||||
function createGetMedianSellRate(rate: Numberish): GetMedianRateOperation {
|
||||
function createGetBestNativeSellRate(rate: Numberish): GetBestNativeTokenSellRateOperation {
|
||||
return (
|
||||
_sources: ERC20BridgeSource[],
|
||||
_makerToken: string,
|
||||
@@ -348,17 +345,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
fromTokenIdx: 0,
|
||||
toTokenIdx: 1,
|
||||
},
|
||||
[ERC20BridgeSource.Smoothy]: {
|
||||
pool: {
|
||||
poolAddress: randomAddress(),
|
||||
tokens: [TAKER_TOKEN, MAKER_TOKEN],
|
||||
exchangeFunctionSelector: hexUtils.random(4),
|
||||
sellQuoteFunctionSelector: hexUtils.random(4),
|
||||
buyQuoteFunctionSelector: hexUtils.random(4),
|
||||
},
|
||||
fromTokenIdx: 0,
|
||||
toTokenIdx: 1,
|
||||
},
|
||||
[ERC20BridgeSource.Saddle]: {
|
||||
pool: {
|
||||
poolAddress: randomAddress(),
|
||||
@@ -399,7 +385,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
},
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
|
||||
getMedianSellRate: createGetMedianSellRate(1),
|
||||
getBestNativeTokenSellRate: createGetBestNativeSellRate(1),
|
||||
getTwoHopSellQuotes: (..._params: any[]) => [],
|
||||
getTwoHopBuyQuotes: (..._params: any[]) => [],
|
||||
isAddressContract: (..._params: any[]) => false,
|
||||
@@ -632,7 +618,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
|
||||
// to get a comparisonPrice, you need a feeschedule for a native order
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Native]: _.constant(new BigNumber(1)),
|
||||
[ERC20BridgeSource.Native]: _.constant({ gas: 1, fee: new BigNumber(1) }),
|
||||
};
|
||||
mockedQuoteRequestor
|
||||
.setup(mqr => mqr.getMakerUriForSignature(TypeMoq.It.isValue(SIGNATURE)))
|
||||
@@ -1022,7 +1008,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
expect(improvedOrders).to.not.be.length(0);
|
||||
for (const order of improvedOrders) {
|
||||
const expectedMakerAmount = order.fills[0].output;
|
||||
const expectedMakerAmount = order.fill.output;
|
||||
const slippage = new BigNumber(1).minus(order.makerAmount.div(expectedMakerAmount.plus(1)));
|
||||
assertRoughlyEquals(slippage, bridgeSlippage, 1);
|
||||
}
|
||||
@@ -1044,7 +1030,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
@@ -1067,15 +1053,16 @@ describe('MarketOperationUtils tests', () => {
|
||||
[ERC20BridgeSource.SushiSwap]: [0.95, 0.1, 0.1, 0.1],
|
||||
};
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Native]: _.constant(
|
||||
FILL_AMOUNT.div(4)
|
||||
[ERC20BridgeSource.Native]: _.constant({
|
||||
gas: 1,
|
||||
fee: FILL_AMOUNT.div(4)
|
||||
.times(nativeFeeRate)
|
||||
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
|
||||
),
|
||||
}),
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_MAKER_RATE),
|
||||
});
|
||||
const improvedOrdersResponse = await getMarketSellOrdersAsync(
|
||||
marketOperationUtils,
|
||||
@@ -1084,7 +1071,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
@@ -1104,15 +1091,16 @@ describe('MarketOperationUtils tests', () => {
|
||||
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
|
||||
};
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Uniswap]: _.constant(
|
||||
FILL_AMOUNT.div(4)
|
||||
[ERC20BridgeSource.Uniswap]: _.constant({
|
||||
gas: 1,
|
||||
fee: FILL_AMOUNT.div(4)
|
||||
.times(uniswapFeeRate)
|
||||
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
|
||||
),
|
||||
}),
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_MAKER_RATE),
|
||||
});
|
||||
const improvedOrdersResponse = await getMarketSellOrdersAsync(
|
||||
marketOperationUtils,
|
||||
@@ -1121,7 +1109,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
@@ -1139,7 +1127,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_MAKER_RATE),
|
||||
});
|
||||
const improvedOrdersResponse = await getMarketSellOrdersAsync(
|
||||
marketOperationUtils,
|
||||
@@ -1148,7 +1136,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
@@ -1175,7 +1163,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
|
||||
const secondSources: ERC20BridgeSource[] = [];
|
||||
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
|
||||
@@ -1248,7 +1236,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_MAKER_RATE),
|
||||
});
|
||||
const optimizer = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
|
||||
const gasPrice = 100e9; // 100 gwei
|
||||
@@ -1273,7 +1261,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
},
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const expectedSources = [ERC20BridgeSource.LiquidityProvider];
|
||||
expect(orderSources).to.deep.eq(expectedSources);
|
||||
});
|
||||
@@ -1469,7 +1457,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
expect(improvedOrders).to.not.be.length(0);
|
||||
for (const order of improvedOrders) {
|
||||
const expectedTakerAmount = order.fills[0].output;
|
||||
const expectedTakerAmount = order.fill.output;
|
||||
const slippage = order.takerAmount.div(expectedTakerAmount.plus(1)).minus(1);
|
||||
assertRoughlyEquals(slippage, bridgeSlippage, 1);
|
||||
}
|
||||
@@ -1491,7 +1479,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
@@ -1516,15 +1504,16 @@ describe('MarketOperationUtils tests', () => {
|
||||
[ERC20BridgeSource.Curve]: [0.1, 0.1, 0.1, 0.1],
|
||||
};
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Native]: _.constant(
|
||||
FILL_AMOUNT.div(4)
|
||||
[ERC20BridgeSource.Native]: _.constant({
|
||||
gas: 1,
|
||||
fee: FILL_AMOUNT.div(4)
|
||||
.times(nativeFeeRate)
|
||||
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
|
||||
),
|
||||
}),
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_TAKER_RATE),
|
||||
});
|
||||
const improvedOrdersResponse = await getMarketBuyOrdersAsync(
|
||||
marketOperationUtils,
|
||||
@@ -1533,7 +1522,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
@@ -1555,15 +1544,16 @@ describe('MarketOperationUtils tests', () => {
|
||||
[ERC20BridgeSource.SushiSwap]: [0.92, 0.1, 0.1, 0.1],
|
||||
};
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Uniswap]: _.constant(
|
||||
FILL_AMOUNT.div(4)
|
||||
[ERC20BridgeSource.Uniswap]: _.constant({
|
||||
gas: 1,
|
||||
fee: FILL_AMOUNT.div(4)
|
||||
.times(uniswapFeeRate)
|
||||
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
|
||||
),
|
||||
}),
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_TAKER_RATE),
|
||||
});
|
||||
const improvedOrdersResponse = await getMarketBuyOrdersAsync(
|
||||
marketOperationUtils,
|
||||
@@ -1572,7 +1562,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
@@ -1598,7 +1588,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
|
||||
const secondSources: ERC20BridgeSource[] = [];
|
||||
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
|
||||
@@ -1619,7 +1609,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_TAKER_RATE),
|
||||
});
|
||||
const optimizer = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
|
||||
const exchangeProxyOverhead = (sourceFlags: bigint) =>
|
||||
@@ -1643,77 +1633,11 @@ describe('MarketOperationUtils tests', () => {
|
||||
},
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const orderSources = improvedOrders.map(o => o.source);
|
||||
const expectedSources = [ERC20BridgeSource.LiquidityProvider];
|
||||
expect(orderSources).to.deep.eq(expectedSources);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFills', () => {
|
||||
const takerAmount = new BigNumber(5000000);
|
||||
const outputAmountPerEth = new BigNumber(0.5);
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
const smallOrder: NativeOrderWithFillableAmounts = {
|
||||
order: {
|
||||
...new LimitOrder({
|
||||
chainId: 1,
|
||||
maker: 'SMALL_ORDER',
|
||||
takerAmount,
|
||||
makerAmount: takerAmount.times(2),
|
||||
}),
|
||||
},
|
||||
fillableMakerAmount: takerAmount.times(2),
|
||||
fillableTakerAmount: takerAmount,
|
||||
fillableTakerFeeAmount: new BigNumber(0),
|
||||
type: FillQuoteTransformerOrderType.Limit,
|
||||
signature: SIGNATURE,
|
||||
};
|
||||
const largeOrder: NativeOrderWithFillableAmounts = {
|
||||
order: {
|
||||
...new LimitOrder({
|
||||
chainId: 1,
|
||||
maker: 'LARGE_ORDER',
|
||||
takerAmount: smallOrder.order.takerAmount.times(2),
|
||||
makerAmount: smallOrder.order.makerAmount.times(2),
|
||||
}),
|
||||
},
|
||||
fillableTakerAmount: smallOrder.fillableTakerAmount.times(2),
|
||||
fillableMakerAmount: smallOrder.fillableMakerAmount.times(2),
|
||||
fillableTakerFeeAmount: new BigNumber(0),
|
||||
type: FillQuoteTransformerOrderType.Limit,
|
||||
signature: SIGNATURE,
|
||||
};
|
||||
const orders = [smallOrder, largeOrder];
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Native]: _.constant(2e5),
|
||||
};
|
||||
|
||||
it('penalizes native fill based on target amount when target is smaller', () => {
|
||||
const path = createFills({
|
||||
side: MarketOperation.Sell,
|
||||
orders,
|
||||
dexQuotes: [],
|
||||
targetInput: takerAmount.minus(1),
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
});
|
||||
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker);
|
||||
expect(path[0][0].input).to.be.bignumber.eq(takerAmount.minus(1));
|
||||
});
|
||||
|
||||
it('penalizes native fill based on available amount when target is larger', () => {
|
||||
const path = createFills({
|
||||
side: MarketOperation.Sell,
|
||||
orders,
|
||||
dexQuotes: [],
|
||||
targetInput: POSITIVE_INF,
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
});
|
||||
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(largeOrder.order.maker);
|
||||
expect((path[0][1].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:disable-next-line: max-file-line-count
|
||||
|
@@ -1,90 +0,0 @@
|
||||
import { expect } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { MarketOperation } from '../src/types';
|
||||
import { Path } from '../src/utils/market_operation_utils/path';
|
||||
import { ERC20BridgeSource, Fill } from '../src/utils/market_operation_utils/types';
|
||||
|
||||
const createFill = (
|
||||
source: ERC20BridgeSource,
|
||||
index: number = 0,
|
||||
input: BigNumber = new BigNumber(100),
|
||||
output: BigNumber = new BigNumber(100),
|
||||
): Fill =>
|
||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
||||
({
|
||||
source,
|
||||
input,
|
||||
output,
|
||||
adjustedOutput: output,
|
||||
flags: BigInt(0),
|
||||
sourcePathId: source,
|
||||
index,
|
||||
} as Fill);
|
||||
|
||||
describe('Path', () => {
|
||||
it('Adds a fallback', () => {
|
||||
const targetInput = new BigNumber(100);
|
||||
const path = Path.create(
|
||||
MarketOperation.Sell,
|
||||
[createFill(ERC20BridgeSource.Native), createFill(ERC20BridgeSource.Native)],
|
||||
targetInput,
|
||||
);
|
||||
const fallback = Path.create(MarketOperation.Sell, [createFill(ERC20BridgeSource.Uniswap)], targetInput);
|
||||
path.addFallback(fallback);
|
||||
const sources = path.fills.map(f => f.source);
|
||||
expect(sources).to.deep.eq([ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap]);
|
||||
});
|
||||
|
||||
it('Adds a fallback with LiquidityProvider', () => {
|
||||
const targetInput = new BigNumber(100);
|
||||
const path = Path.create(
|
||||
MarketOperation.Sell,
|
||||
[createFill(ERC20BridgeSource.Native), createFill(ERC20BridgeSource.LiquidityProvider)],
|
||||
targetInput,
|
||||
);
|
||||
const fallback = Path.create(MarketOperation.Sell, [createFill(ERC20BridgeSource.Uniswap)], targetInput);
|
||||
path.addFallback(fallback);
|
||||
const sources = path.fills.map(f => f.source);
|
||||
expect(sources).to.deep.eq([
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
]);
|
||||
});
|
||||
|
||||
it('Handles duplicates', () => {
|
||||
const targetInput = new BigNumber(100);
|
||||
const path = Path.create(
|
||||
MarketOperation.Sell,
|
||||
[createFill(ERC20BridgeSource.Uniswap), createFill(ERC20BridgeSource.LiquidityProvider)],
|
||||
targetInput,
|
||||
);
|
||||
const fallback = Path.create(MarketOperation.Sell, [createFill(ERC20BridgeSource.Uniswap)], targetInput);
|
||||
path.addFallback(fallback);
|
||||
const sources = path.fills.map(f => f.source);
|
||||
expect(sources).to.deep.eq([ERC20BridgeSource.Uniswap, ERC20BridgeSource.LiquidityProvider]);
|
||||
});
|
||||
it('Moves Native orders to the front and appends with unused fills', () => {
|
||||
const targetInput = new BigNumber(100);
|
||||
const path = Path.create(
|
||||
MarketOperation.Sell,
|
||||
[
|
||||
createFill(ERC20BridgeSource.Uniswap, 0, new BigNumber(50)),
|
||||
createFill(ERC20BridgeSource.Native, 0, new BigNumber(50)),
|
||||
],
|
||||
targetInput,
|
||||
);
|
||||
const fallback = Path.create(
|
||||
MarketOperation.Sell,
|
||||
[
|
||||
createFill(ERC20BridgeSource.Uniswap, 0, new BigNumber(50)),
|
||||
createFill(ERC20BridgeSource.Uniswap, 1, new BigNumber(50)),
|
||||
],
|
||||
targetInput,
|
||||
);
|
||||
path.addFallback(fallback);
|
||||
const sources = path.fills.map(f => f.source);
|
||||
expect(sources).to.deep.eq([ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap, ERC20BridgeSource.Uniswap]);
|
||||
});
|
||||
});
|
@@ -9,11 +9,10 @@ import * as TypeMoq from 'typemoq';
|
||||
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../src/types';
|
||||
import {
|
||||
CollapsedFill,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
NativeFillData,
|
||||
NativeLimitOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
@@ -34,7 +33,7 @@ import { getRandomAmount, getRandomSignature } from './utils/utils';
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
function collapsedFillFromNativeOrder(order: NativeOrderWithFillableAmounts): NativeCollapsedFill {
|
||||
function fillFromNativeOrder(order: NativeOrderWithFillableAmounts): Fill<NativeFillData> {
|
||||
const fillData = {
|
||||
order: order.order,
|
||||
signature: order.signature,
|
||||
@@ -50,7 +49,9 @@ function collapsedFillFromNativeOrder(order: NativeOrderWithFillableAmounts): Na
|
||||
order.type === FillQuoteTransformerOrderType.Limit
|
||||
? (fillData as NativeLimitOrderFillData)
|
||||
: (fillData as NativeRfqOrderFillData),
|
||||
subFills: [],
|
||||
adjustedOutput: order.order.makerAmount,
|
||||
flags: BigInt(0),
|
||||
gas: 1,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -111,21 +112,25 @@ describe('generateQuoteReport', async () => {
|
||||
];
|
||||
|
||||
// generate path
|
||||
const uniswap2Fill: CollapsedFill = {
|
||||
const uniswap2Fill: Fill = {
|
||||
...uniswapSample2,
|
||||
subFills: [],
|
||||
sourcePathId: hexUtils.random(),
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
adjustedOutput: uniswapSample2.output,
|
||||
flags: BigInt(0),
|
||||
gas: 1,
|
||||
};
|
||||
const balancer2Fill: CollapsedFill = {
|
||||
const balancer2Fill: Fill = {
|
||||
...balancerSample2,
|
||||
subFills: [],
|
||||
sourcePathId: hexUtils.random(),
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
adjustedOutput: balancerSample2.output,
|
||||
flags: BigInt(0),
|
||||
gas: 1,
|
||||
};
|
||||
const orderbookOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder2);
|
||||
const rfqtOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(rfqtOrder2);
|
||||
const pathGenerated: CollapsedFill[] = [rfqtOrder2Fill, orderbookOrder2Fill, uniswap2Fill, balancer2Fill];
|
||||
const orderbookOrder2Fill: Fill = fillFromNativeOrder(orderbookOrder2);
|
||||
const rfqtOrder2Fill: Fill = fillFromNativeOrder(rfqtOrder2);
|
||||
const pathGenerated: Fill[] = [rfqtOrder2Fill, orderbookOrder2Fill, uniswap2Fill, balancer2Fill];
|
||||
|
||||
// quote generator mock
|
||||
const quoteRequestor = TypeMoq.Mock.ofType<QuoteRequestor>();
|
||||
@@ -241,20 +246,24 @@ describe('generateQuoteReport', async () => {
|
||||
const nativeOrders = [orderbookOrder1, orderbookOrder2];
|
||||
|
||||
// generate path
|
||||
const orderbookOrder1Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder1);
|
||||
const uniswap1Fill: CollapsedFill = {
|
||||
const orderbookOrder1Fill: Fill = fillFromNativeOrder(orderbookOrder1);
|
||||
const uniswap1Fill: Fill = {
|
||||
...uniswapSample1,
|
||||
subFills: [],
|
||||
sourcePathId: hexUtils.random(),
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
adjustedOutput: uniswapSample1.output,
|
||||
flags: BigInt(0),
|
||||
gas: 1,
|
||||
};
|
||||
const balancer1Fill: CollapsedFill = {
|
||||
const balancer1Fill: Fill = {
|
||||
...balancerSample1,
|
||||
subFills: [],
|
||||
sourcePathId: hexUtils.random(),
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
adjustedOutput: balancerSample1.output,
|
||||
flags: BigInt(0),
|
||||
gas: 1,
|
||||
};
|
||||
const pathGenerated: CollapsedFill[] = [orderbookOrder1Fill, uniswap1Fill, balancer1Fill];
|
||||
const pathGenerated: Fill[] = [orderbookOrder1Fill, uniswap1Fill, balancer1Fill];
|
||||
|
||||
const orderReport = generateQuoteReport(marketOperation, nativeOrders, pathGenerated);
|
||||
|
||||
|
@@ -5,8 +5,8 @@ import * as _ from 'lodash';
|
||||
|
||||
import { MarketOperation } from '../src/types';
|
||||
import {
|
||||
CollapsedFill,
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
NativeLimitOrderFillData,
|
||||
OptimizedMarketOrder,
|
||||
OptimizedMarketOrderBase,
|
||||
@@ -45,18 +45,16 @@ describe('quote_simulation tests', async () => {
|
||||
inputFeeRate: number;
|
||||
outputFeeRate: number;
|
||||
count: number;
|
||||
fillsCount: number;
|
||||
side: MarketOperation;
|
||||
type?: FillQuoteTransformerOrderType;
|
||||
}> = {},
|
||||
): QuoteFillOrderCall[] {
|
||||
const { fillableInput, fillableOutput, inputFeeRate, outputFeeRate, count, fillsCount, side, type } = {
|
||||
const { fillableInput, fillableOutput, inputFeeRate, outputFeeRate, count, side, type } = {
|
||||
fillableInput: getRandomOrderSize(),
|
||||
fillableOutput: getRandomOrderSize(),
|
||||
inputFeeRate: 0,
|
||||
outputFeeRate: 0,
|
||||
count: 3,
|
||||
fillsCount: 3,
|
||||
side: MarketOperation.Sell,
|
||||
...opts,
|
||||
};
|
||||
@@ -83,7 +81,6 @@ describe('quote_simulation tests', async () => {
|
||||
return {
|
||||
order: createQuoteFillOrderOrder(totalInputs[i], totalOutputs[i], {
|
||||
side,
|
||||
fillsCount,
|
||||
filledInput: filledInputs[i],
|
||||
takerInputFee: inputFees[i].abs(),
|
||||
takerOutputFee: outputFees[i].abs(),
|
||||
@@ -102,19 +99,17 @@ describe('quote_simulation tests', async () => {
|
||||
output: BigNumber,
|
||||
opts: Partial<{
|
||||
filledInput: BigNumber;
|
||||
fillsCount: number;
|
||||
side: MarketOperation;
|
||||
takerInputFee: BigNumber;
|
||||
takerOutputFee: BigNumber;
|
||||
type: FillQuoteTransformerOrderType;
|
||||
}> = {},
|
||||
): OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
||||
const { filledInput, fillsCount, side, takerInputFee, takerOutputFee, type } = _.merge(
|
||||
const { filledInput, side, takerInputFee, takerOutputFee, type } = _.merge(
|
||||
{},
|
||||
{
|
||||
side: MarketOperation.Sell,
|
||||
filledInput: ZERO,
|
||||
fillsCount: 3,
|
||||
takerInputFee: ZERO,
|
||||
takerOutputFee: ZERO,
|
||||
type: FillQuoteTransformerOrderType.Limit,
|
||||
@@ -160,46 +155,23 @@ describe('quote_simulation tests', async () => {
|
||||
maxTakerTokenFillAmount: fillableTakerAmount,
|
||||
},
|
||||
type,
|
||||
fills: createOrderCollapsedFills(fillableInput, fillableOutput, fillsCount),
|
||||
fill: createOrderFill(fillableInput, fillableOutput),
|
||||
};
|
||||
return order;
|
||||
}
|
||||
const nativeSourcePathId = hexUtils.random();
|
||||
function createOrderCollapsedFills(input: BigNumber, output: BigNumber, count: number): CollapsedFill[] {
|
||||
const inputs = subdivideAmount(input, count);
|
||||
const outputs = subdivideAmount(output, count);
|
||||
return _.times(count, i => {
|
||||
const subFillInputs = subdivideAmount(inputs[i], count);
|
||||
const subFillOutputs = subdivideAmount(outputs[i], count);
|
||||
return {
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
sourcePathId: nativeSourcePathId,
|
||||
source: ERC20BridgeSource.Uniswap,
|
||||
fillData: {},
|
||||
input: inputs[i],
|
||||
output: outputs[i],
|
||||
subFills: _.times(count, j => ({
|
||||
input: subFillInputs[j],
|
||||
output: subFillOutputs[j],
|
||||
})),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function countCollapsedFills(fillOrders: QuoteFillOrderCall[] | OptimizedMarketOrder[]): number {
|
||||
let count = 0;
|
||||
if ((fillOrders as any)[0].fills) {
|
||||
const orders = (fillOrders as any) as OptimizedMarketOrder[];
|
||||
for (const o of orders) {
|
||||
count += o.fills.length;
|
||||
}
|
||||
} else {
|
||||
const orders = (fillOrders as any) as QuoteFillOrderCall[];
|
||||
for (const fo of orders) {
|
||||
count += fo.order.fills.length;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
function createOrderFill(input: BigNumber, output: BigNumber): Fill {
|
||||
return {
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
sourcePathId: nativeSourcePathId,
|
||||
source: ERC20BridgeSource.Uniswap,
|
||||
fillData: {},
|
||||
input,
|
||||
output,
|
||||
flags: BigInt(0),
|
||||
adjustedOutput: output,
|
||||
gas: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function randomSide(): MarketOperation {
|
||||
@@ -237,14 +209,12 @@ describe('quote_simulation tests', async () => {
|
||||
describe('single order', () => {
|
||||
it('can exactly fill one order', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = _.random(1, 3);
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const fillOrders = createQuoteFillOrders({
|
||||
fillableInput,
|
||||
fillableOutput,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
||||
@@ -253,19 +223,16 @@ describe('quote_simulation tests', async () => {
|
||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||
expect(result.protocolFee).to.bignumber.eq(1);
|
||||
expect(result.gas).to.eq(fillsCount);
|
||||
});
|
||||
|
||||
it('can partially fill one simple order', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = 1;
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const fillOrders = createQuoteFillOrders({
|
||||
fillableInput,
|
||||
fillableOutput,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
||||
@@ -279,19 +246,16 @@ describe('quote_simulation tests', async () => {
|
||||
.integerValue();
|
||||
assertRoughlyEquals(totalFilledOutput, expectedOutputFilledAmount);
|
||||
expect(result.protocolFee).to.bignumber.eq(1);
|
||||
expect(result.gas).to.eq(1);
|
||||
});
|
||||
|
||||
it('can partially fill one batched order', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = 3;
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const fillOrders = createQuoteFillOrders({
|
||||
fillableInput,
|
||||
fillableOutput,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
||||
@@ -301,20 +265,16 @@ describe('quote_simulation tests', async () => {
|
||||
expect(totalFilledInput).to.bignumber.eq(inputFillAmount);
|
||||
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
||||
expect(result.protocolFee).to.bignumber.eq(1);
|
||||
expect(result.gas).to.gte(1);
|
||||
expect(result.gas).to.lte(fillsCount);
|
||||
});
|
||||
|
||||
it('does not over fill one order', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = _.random(1, 3);
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const fillOrders = createQuoteFillOrders({
|
||||
fillableInput,
|
||||
fillableOutput,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
||||
@@ -324,12 +284,10 @@ describe('quote_simulation tests', async () => {
|
||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||
expect(result.protocolFee).to.bignumber.eq(1);
|
||||
expect(result.gas).to.eq(fillsCount);
|
||||
});
|
||||
|
||||
it('can exactly fill one order with input fees', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = _.random(1, 3);
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const inputFeeRate = getRandomFeeRate();
|
||||
@@ -338,7 +296,6 @@ describe('quote_simulation tests', async () => {
|
||||
fillableOutput,
|
||||
inputFeeRate,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||
@@ -350,12 +307,10 @@ describe('quote_simulation tests', async () => {
|
||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.eq(1);
|
||||
expect(result.gas).to.eq(fillsCount);
|
||||
});
|
||||
|
||||
it('can partially fill one order with input fees', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = _.random(1, 3);
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const inputFeeRate = getRandomFeeRate();
|
||||
@@ -364,7 +319,6 @@ describe('quote_simulation tests', async () => {
|
||||
fillableOutput,
|
||||
inputFeeRate,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||
@@ -377,12 +331,10 @@ describe('quote_simulation tests', async () => {
|
||||
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.eq(1);
|
||||
expect(result.gas).to.lte(fillsCount);
|
||||
});
|
||||
|
||||
it('does not over fill one order with input fees', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = _.random(1, 3);
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const inputFeeRate = getRandomFeeRate();
|
||||
@@ -391,7 +343,6 @@ describe('quote_simulation tests', async () => {
|
||||
fillableOutput,
|
||||
inputFeeRate,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||
@@ -404,12 +355,10 @@ describe('quote_simulation tests', async () => {
|
||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.eq(1);
|
||||
expect(result.gas).to.eq(fillsCount);
|
||||
});
|
||||
|
||||
it('can exactly fill one order with output fees', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = _.random(1, 3);
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const outputFeeRate = getRandomFeeRate();
|
||||
@@ -418,7 +367,6 @@ describe('quote_simulation tests', async () => {
|
||||
fillableOutput,
|
||||
outputFeeRate,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||
@@ -430,12 +378,10 @@ describe('quote_simulation tests', async () => {
|
||||
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.eq(1);
|
||||
expect(result.gas).to.eq(fillsCount);
|
||||
});
|
||||
|
||||
it('can partial fill one order with output fees', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = _.random(1, 3);
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const outputFeeRate = getRandomFeeRate();
|
||||
@@ -444,7 +390,6 @@ describe('quote_simulation tests', async () => {
|
||||
fillableOutput,
|
||||
outputFeeRate,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||
@@ -457,12 +402,10 @@ describe('quote_simulation tests', async () => {
|
||||
expect(totalFilledOutput).to.bignumber.lt(totalFillableOutput);
|
||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.eq(1);
|
||||
expect(result.gas).to.lte(fillsCount);
|
||||
});
|
||||
|
||||
it('does not over fill one order with output fees', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = _.random(1, 3);
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const outputFeeRate = getRandomFeeRate();
|
||||
@@ -471,7 +414,6 @@ describe('quote_simulation tests', async () => {
|
||||
fillableOutput,
|
||||
outputFeeRate,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||
@@ -484,19 +426,16 @@ describe('quote_simulation tests', async () => {
|
||||
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.eq(1);
|
||||
expect(result.gas).to.eq(fillsCount);
|
||||
});
|
||||
|
||||
it('does not charge a protocol fee for rfq orders', () => {
|
||||
const side = randomSide();
|
||||
const fillsCount = _.random(1, 3);
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const fillOrders = createQuoteFillOrders({
|
||||
fillableInput,
|
||||
fillableOutput,
|
||||
side,
|
||||
fillsCount,
|
||||
count: 1,
|
||||
type: FillQuoteTransformerOrderType.Rfq,
|
||||
});
|
||||
@@ -506,7 +445,6 @@ describe('quote_simulation tests', async () => {
|
||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||
expect(result.protocolFee).to.bignumber.eq(0);
|
||||
expect(result.gas).to.eq(fillsCount);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -522,7 +460,6 @@ describe('quote_simulation tests', async () => {
|
||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||
expect(totalFilledOutput).to.bignumber.eq(fillableOutput);
|
||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
||||
});
|
||||
|
||||
it('can partial fill orders', () => {
|
||||
@@ -551,7 +488,6 @@ describe('quote_simulation tests', async () => {
|
||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||
expect(totalFilledOutput).to.bignumber.eq(fillableOutput);
|
||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
||||
});
|
||||
|
||||
it('can exactly fill orders with input fees', () => {
|
||||
@@ -574,7 +510,6 @@ describe('quote_simulation tests', async () => {
|
||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
||||
});
|
||||
|
||||
it('can partial fill orders with input fees', () => {
|
||||
@@ -598,7 +533,6 @@ describe('quote_simulation tests', async () => {
|
||||
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.lte(fillOrders.length);
|
||||
expect(result.gas).to.lte(countCollapsedFills(fillOrders));
|
||||
});
|
||||
|
||||
it('does not over fill orders with input fees', () => {
|
||||
@@ -622,7 +556,6 @@ describe('quote_simulation tests', async () => {
|
||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
||||
});
|
||||
|
||||
it('can exactly fill orders with output fees', () => {
|
||||
@@ -645,7 +578,6 @@ describe('quote_simulation tests', async () => {
|
||||
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
||||
});
|
||||
|
||||
it('can partial fill orders with output fees', () => {
|
||||
@@ -669,7 +601,6 @@ describe('quote_simulation tests', async () => {
|
||||
expect(totalFilledOutput).to.bignumber.lt(totalFillableOutput);
|
||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.lte(fillOrders.length);
|
||||
expect(result.gas).to.lte(countCollapsedFills(fillOrders));
|
||||
});
|
||||
|
||||
it('does not over fill orders with output fees', () => {
|
||||
@@ -693,7 +624,6 @@ describe('quote_simulation tests', async () => {
|
||||
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -771,7 +701,6 @@ describe('quote_simulation tests', async () => {
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||
});
|
||||
expect(result.gas).to.eq(countCollapsedFills(orders));
|
||||
expect(result.protocolFeeAmount).to.bignumber.eq(orders.length);
|
||||
expect(result.takerFeeTakerAssetAmount).to.bignumber.eq(0);
|
||||
expect(result.takerFeeMakerAssetAmount).to.bignumber.eq(0);
|
||||
@@ -895,7 +824,6 @@ describe('quote_simulation tests', async () => {
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||
});
|
||||
expect(result.gas).to.eq(countCollapsedFills(orders));
|
||||
expect(result.protocolFeeAmount).to.bignumber.eq(orders.length);
|
||||
|
||||
assertRoughlyEquals(result.makerAssetAmount, fillableInput);
|
||||
|
@@ -28,7 +28,6 @@ export * from '../test/generated-wrappers/i_mooniswap';
|
||||
export * from '../test/generated-wrappers/i_multi_bridge';
|
||||
export * from '../test/generated-wrappers/i_platypus';
|
||||
export * from '../test/generated-wrappers/i_shell';
|
||||
export * from '../test/generated-wrappers/i_smoothy';
|
||||
export * from '../test/generated-wrappers/i_uniswap_exchange_quotes';
|
||||
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
|
||||
export * from '../test/generated-wrappers/igmx';
|
||||
@@ -42,10 +41,10 @@ export * from '../test/generated-wrappers/native_order_sampler';
|
||||
export * from '../test/generated-wrappers/platypus_sampler';
|
||||
export * from '../test/generated-wrappers/sampler_utils';
|
||||
export * from '../test/generated-wrappers/shell_sampler';
|
||||
export * from '../test/generated-wrappers/smoothy_sampler';
|
||||
export * from '../test/generated-wrappers/test_native_order_sampler';
|
||||
export * from '../test/generated-wrappers/two_hop_sampler';
|
||||
export * from '../test/generated-wrappers/uniswap_sampler';
|
||||
export * from '../test/generated-wrappers/uniswap_v2_sampler';
|
||||
export * from '../test/generated-wrappers/uniswap_v3_sampler';
|
||||
export * from '../test/generated-wrappers/utility_sampler';
|
||||
export * from '../test/generated-wrappers/velodrome_sampler';
|
||||
|
@@ -32,7 +32,6 @@
|
||||
"test/generated-artifacts/IMultiBridge.json",
|
||||
"test/generated-artifacts/IPlatypus.json",
|
||||
"test/generated-artifacts/IShell.json",
|
||||
"test/generated-artifacts/ISmoothy.json",
|
||||
"test/generated-artifacts/IUniswapExchangeQuotes.json",
|
||||
"test/generated-artifacts/IUniswapV2Router01.json",
|
||||
"test/generated-artifacts/KyberDmmSampler.json",
|
||||
@@ -45,12 +44,12 @@
|
||||
"test/generated-artifacts/PlatypusSampler.json",
|
||||
"test/generated-artifacts/SamplerUtils.json",
|
||||
"test/generated-artifacts/ShellSampler.json",
|
||||
"test/generated-artifacts/SmoothySampler.json",
|
||||
"test/generated-artifacts/TestNativeOrderSampler.json",
|
||||
"test/generated-artifacts/TwoHopSampler.json",
|
||||
"test/generated-artifacts/UniswapSampler.json",
|
||||
"test/generated-artifacts/UniswapV2Sampler.json",
|
||||
"test/generated-artifacts/UniswapV3Sampler.json",
|
||||
"test/generated-artifacts/UtilitySampler.json"
|
||||
"test/generated-artifacts/UtilitySampler.json",
|
||||
"test/generated-artifacts/VelodromeSampler.json"
|
||||
]
|
||||
}
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "6.16.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Redeploy FQT on Mainnet and Optimism"
|
||||
}
|
||||
],
|
||||
"timestamp": 1655244958
|
||||
},
|
||||
{
|
||||
"version": "6.15.0",
|
||||
"changes": [
|
||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v6.16.0 - _June 14, 2022_
|
||||
|
||||
* Redeploy FQT on Mainnet and Optimism
|
||||
|
||||
## v6.15.0 - _June 3, 2022_
|
||||
|
||||
* Redeploy FQT on Mainnet
|
||||
|
@@ -37,7 +37,7 @@
|
||||
"wethTransformer": "0xb2bc06a4efb20fc6553a69dbfa49b7be938034a7",
|
||||
"payTakerTransformer": "0x4638a7ebe75b911b995d0ec73a81e4f85f41f24e",
|
||||
"affiliateFeeTransformer": "0xda6d9fc5998f550a094585cf9171f0e8ee3ac59f",
|
||||
"fillQuoteTransformer": "0xa651c18efcf50409f3d0f5cdbf615d3016b828c8",
|
||||
"fillQuoteTransformer": "0x26b2d9ea76f24206805d17565a5e0efcf787e0ae",
|
||||
"positiveSlippageFeeTransformer": "0xa9416ce1dbde8d331210c07b5c253d94ee4cc3fd"
|
||||
}
|
||||
},
|
||||
@@ -499,7 +499,7 @@
|
||||
"wethTransformer": "0x02ce7af6520e2862f961f5d7eda746642865179c",
|
||||
"payTakerTransformer": "0x085d10a34f14f6a631ea8ff7d016782ee3ffaa11",
|
||||
"affiliateFeeTransformer": "0x55cf1d7535250db75bf0190493f55781ee583553",
|
||||
"fillQuoteTransformer": "0x3543ef833d28b7e983c293856561f21a7f089f1d",
|
||||
"fillQuoteTransformer": "0xfae0ce3841afbf5625a15f0c73e03ba6660e575f",
|
||||
"positiveSlippageFeeTransformer": "0xb11e14565dfbeb702dea9bc0cb47f1a8b32f4783"
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contract-addresses",
|
||||
"version": "6.15.0",
|
||||
"version": "6.16.0",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1655244958,
|
||||
"version": "13.20.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1654284040,
|
||||
"version": "13.20.3",
|
||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v13.20.4 - _June 14, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v13.20.3 - _June 3, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contract-wrappers",
|
||||
"version": "13.20.3",
|
||||
"version": "13.20.4",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -57,7 +57,7 @@
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.34",
|
||||
"@0x/base-contract": "^6.5.0",
|
||||
"@0x/contract-addresses": "^6.15.0",
|
||||
"@0x/contract-addresses": "^6.16.0",
|
||||
"@0x/json-schemas": "^6.4.4",
|
||||
"@0x/types": "^3.3.6",
|
||||
"@0x/utils": "^6.5.3",
|
||||
|
@@ -1,4 +1,14 @@
|
||||
[
|
||||
{
|
||||
"version": "11.15.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add Velodrome support",
|
||||
"pr": 494
|
||||
}
|
||||
],
|
||||
"timestamp": 1655244958
|
||||
},
|
||||
{
|
||||
"version": "11.14.0",
|
||||
"changes": [
|
||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v11.15.0 - _June 14, 2022_
|
||||
|
||||
* Add Velodrome support (#494)
|
||||
|
||||
## v11.14.0 - _June 3, 2022_
|
||||
|
||||
* Adds Support for BancorV3 on Ethereum (#492)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/protocol-utils",
|
||||
"version": "11.14.0",
|
||||
"version": "11.15.0",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -63,8 +63,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.34",
|
||||
"@0x/contract-addresses": "^6.15.0",
|
||||
"@0x/contract-wrappers": "^13.20.3",
|
||||
"@0x/contract-addresses": "^6.16.0",
|
||||
"@0x/contract-wrappers": "^13.20.4",
|
||||
"@0x/json-schemas": "^6.4.4",
|
||||
"@0x/subproviders": "^6.6.5",
|
||||
"@0x/utils": "^6.5.3",
|
||||
|
@@ -139,6 +139,7 @@ export enum BridgeProtocol {
|
||||
GMX,
|
||||
Platypus,
|
||||
BancorV3,
|
||||
Velodrome,
|
||||
}
|
||||
// tslint:enable: enum-naming
|
||||
|
||||
|
Reference in New Issue
Block a user