Compare commits

...

7 Commits

Author SHA1 Message Date
Github Actions
d9a16ed1f9 Publish
- @0x/contracts-treasury@1.4.2
 - @0x/contracts-zero-ex@0.29.1
 - @0x/asset-swapper@16.29.1
 - @0x/migrations@8.1.8
 - @0x/protocol-utils@1.9.2
2021-10-04 19:01:10 +00:00
Github Actions
57a1120997 Updated CHANGELOGS & MD docs 2021-10-04 19:01:06 +00:00
Romain Butteaud
0945d4cef2 fix: removing Clipper custom integration (to add it later as a real PLP) (#335)
* fix: removing Clipper custom integration (to add it later as a real PLP)

* fix: update CHHANGELOG

* fix: keep Clipper as BridgeProtocols so we dont have to redeploy and comment this is not used

* fix: prettier
2021-10-04 11:39:07 -07:00
Github Actions
8f6f7ad453 Publish
- @0x/asset-swapper@16.29.0
2021-10-04 12:21:46 +00:00
Github Actions
bb4fad37fa Updated CHANGELOGS & MD docs 2021-10-04 12:21:43 +00:00
Kim Persson
d06daf2957 feat: initial integration of new router (#295)
* feat: integrate Rust router with asset-swapper WIP

* fix: produce outputFees in the format the Rust router expects

* fix: correct output fee calc and only use the rust router for sells

* fix: make sure numbers sent to the rust router are integers

* hack: try to debug why rust router output is being overestimated WIP

* refactor: clean up router debugging code

* fix: don't use negative output fees for sells

* feat: try VIP sources in isolation and compare with routing all sources

* fix: adjust for FQT overhead when choosing between VIP, all sources WIP

* fix: pass gasPrice to path_optimizer for EP overhead calculations

* feat: buy support with the Rust Router WIP

* chore: WIP commit trying to get buys working

* refactor: use samples instead of fills for the Rust router

* feat: add vip handling hack to sample based routing

* fix: revert to 200 samplings for rust router when using pure samples

* refactor: remove old hacky Path based Rust code, add back feature toggle

* fix: scale both fill output and adjustedOutput my same factor as input

* feat: initial plumbing for supporting RFQ/Limit orders

* fix: incorrect bump of input amount by one base unit before routing

* fix: add fake samples for rfq/limit orders to fulfill the 3 sample req

* fix pass rfq orders in the correct format to the rust router

* chore: remove debugging logs and clean up code & comments

* fix: use published version of @0x/neon-router

* hack: scale routed amounts to account for precision loss of number/f64

* refactor: clean up code and address initial review comments

* fix: only remove trailing 0 output samples before passing to the router

* refactor: consolidate eth to output token calc into ethToOutputAmount fn

* fix: interpolate input between samples on output amount instead of price

* fix: return no path when we have no samples, add sanity asserts

* refactor: fix interpolation comment wording

* fix: remove double adjusted source route input amount

* chore: update changelog for asset-swapper
2021-10-04 12:09:54 +02:00
Megan
34314960ef Updated docs for zero protocol fees (#337) 2021-10-01 13:04:25 +02:00
35 changed files with 619 additions and 297 deletions

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1633374058,
"version": "1.4.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1632957537,
"version": "1.4.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.4.2 - _October 4, 2021_
* Dependencies updated
## v1.4.1 - _September 29, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-treasury",
"version": "1.4.1",
"version": "1.4.2",
"engines": {
"node": ">=6.12"
},
@@ -73,7 +73,7 @@
},
"dependencies": {
"@0x/base-contract": "^6.4.2",
"@0x/protocol-utils": "^1.9.1",
"@0x/protocol-utils": "^1.9.2",
"@0x/subproviders": "^6.6.0",
"@0x/types": "^3.3.4",
"@0x/typescript-typings": "^5.2.1",

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1633374058,
"version": "0.29.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "0.29.0",
"changes": [

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v0.29.1 - _October 4, 2021_
* Dependencies updated
## v0.29.0 - _September 29, 2021_
* Export TransformERC20FeatureContract (#282)

View File

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

View File

@@ -49,5 +49,5 @@ library BridgeProtocols {
uint128 internal constant KYBERDMM = 19;
uint128 internal constant CURVEV2 = 20;
uint128 internal constant LIDO = 21;
uint128 internal constant CLIPPER = 22;
uint128 internal constant CLIPPER = 22; // Not used: Clipper is now using PLP interface
}

View File

@@ -1,148 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "../IBridgeAdapter.sol";
import "../../../vendor/ILiquidityProvider.sol";
contract MixinClipper {
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Mainnet address of the WETH contract.
IEtherTokenV06 private immutable WETH;
constructor(IEtherTokenV06 weth)
public
{
WETH = weth;
}
function _tradeClipper(
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// We can only use ETH with Clipper, no WETH available
(ILiquidityProvider clipper, bytes memory auxiliaryData) =
abi.decode(bridgeData, (ILiquidityProvider, bytes));
if (sellToken == WETH) {
boughtAmount = _executeSellEthForToken(
clipper,
buyToken,
sellAmount,
auxiliaryData
);
} else if (buyToken == WETH) {
boughtAmount = _executeSellTokenForEth(
clipper,
sellToken,
sellAmount,
auxiliaryData
);
} else {
boughtAmount = _executeSellTokenForToken(
clipper,
sellToken,
buyToken,
sellAmount,
auxiliaryData
);
}
return boughtAmount;
}
function _executeSellEthForToken(
ILiquidityProvider clipper,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory auxiliaryData
)
private
returns (uint256 boughtAmount)
{
// Clipper requires ETH and doesn't support WETH
WETH.withdraw(sellAmount);
boughtAmount = clipper.sellEthForToken{ value: sellAmount }(
buyToken,
address(this),
1,
auxiliaryData
);
}
function _executeSellTokenForEth(
ILiquidityProvider clipper,
IERC20TokenV06 sellToken,
uint256 sellAmount,
bytes memory auxiliaryData
)
private
returns (uint256 boughtAmount)
{
// Optimization: We can transfer the tokens into clipper rather than
// have an allowance updated
sellToken.compatTransfer(address(clipper), sellAmount);
boughtAmount = clipper.sellTokenForEth(
sellToken,
payable(address(this)),
1,
auxiliaryData
);
// we want WETH for possible future trades
WETH.deposit{ value: boughtAmount }();
}
function _executeSellTokenForToken(
ILiquidityProvider clipper,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory auxiliaryData
)
private
returns (uint256 boughtAmount)
{
// Optimization: We can transfer the tokens into clipper rather than
// have an allowance updated
sellToken.compatTransfer(address(clipper), sellAmount);
boughtAmount = clipper.sellTokenForToken(
sellToken,
buyToken,
address(this),
1,
auxiliaryData
);
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-zero-ex",
"version": "0.29.0",
"version": "0.29.1",
"engines": {
"node": ">=6.12"
},
@@ -43,7 +43,7 @@
"config": {
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinClipper|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
},
"repository": {
"type": "git",
@@ -83,7 +83,7 @@
},
"dependencies": {
"@0x/base-contract": "^6.4.2",
"@0x/protocol-utils": "^1.9.1",
"@0x/protocol-utils": "^1.9.2",
"@0x/subproviders": "^6.6.0",
"@0x/types": "^3.3.4",
"@0x/typescript-typings": "^5.2.1",

View File

@@ -84,7 +84,6 @@ import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransa
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
import * as MixinBalancerV2 from '../test/generated-artifacts/MixinBalancerV2.json';
import * as MixinBancor from '../test/generated-artifacts/MixinBancor.json';
import * as MixinClipper from '../test/generated-artifacts/MixinClipper.json';
import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json';
import * as MixinCryptoCom from '../test/generated-artifacts/MixinCryptoCom.json';
import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json';
@@ -276,7 +275,6 @@ export const artifacts = {
MixinBalancer: MixinBalancer as ContractArtifact,
MixinBalancerV2: MixinBalancerV2 as ContractArtifact,
MixinBancor: MixinBancor as ContractArtifact,
MixinClipper: MixinClipper as ContractArtifact,
MixinCoFiX: MixinCoFiX as ContractArtifact,
MixinCryptoCom: MixinCryptoCom as ContractArtifact,
MixinCurve: MixinCurve as ContractArtifact,

View File

@@ -82,7 +82,6 @@ export * from '../test/generated-wrappers/meta_transactions_feature';
export * from '../test/generated-wrappers/mixin_balancer';
export * from '../test/generated-wrappers/mixin_balancer_v2';
export * from '../test/generated-wrappers/mixin_bancor';
export * from '../test/generated-wrappers/mixin_clipper';
export * from '../test/generated-wrappers/mixin_co_fi_x';
export * from '../test/generated-wrappers/mixin_crypto_com';
export * from '../test/generated-wrappers/mixin_curve';

View File

@@ -115,7 +115,6 @@
"test/generated-artifacts/MixinBalancer.json",
"test/generated-artifacts/MixinBalancerV2.json",
"test/generated-artifacts/MixinBancor.json",
"test/generated-artifacts/MixinClipper.json",
"test/generated-artifacts/MixinCoFiX.json",
"test/generated-artifacts/MixinCryptoCom.json",
"test/generated-artifacts/MixinCurve.json",

View File

@@ -3,10 +3,14 @@ Protocol Fees
###############################
An ETH protocol fee is paid by the Taker each time a `Limit Order <./orders.html#limit-orders>`_ is `filled <./functions.html>`_.
The fee is proportional to the gas cost of filling an order and scales linearly with gas price. The cost is currently ``70k * tx.gasprice``.
The fee is proportional to the gas cost of filling an order and scales linearly with gas price. The cost is currently ``0 * tx.gasprice``.
At the end of every Staking Epoch, these fees are aggregated and distributed to the makers as a liquidity reward: the reward is proportional to the maker's collected fees and staked ZRX relative to other makers.
To learn more about protocol fees and liquidity incentives, see the `Official Spec <https://github.com/0xProject/0x-protocol-specification/blob/master/staking/staking-specification.md>`_.
.. note::
As of September 29, 2021, protocol fees have been removed for all order types in both Exchange V4 and V3 in accordance with `ZEIP-91 <https://0x.org/zrx/vote/zeip-91>`_.
.. note::
`RFQ Orders <./orders.html#rfq-orders>`_ are introduced in Exchange V4, and there is currently no protocol fee for filling this type of order.

View File

@@ -1,4 +1,24 @@
[
{
"version": "16.29.1",
"changes": [
{
"note": "Remove `Clipper` as a custom liquidity source",
"pr": 335
}
],
"timestamp": 1633374058
},
{
"version": "16.29.0",
"changes": [
{
"note": "Initial integration of neon-router (behind feature flag)",
"pr": 295
}
],
"timestamp": 1633350101
},
{
"version": "16.28.0",
"changes": [

View File

@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v16.29.1 - _October 4, 2021_
* Remove `Clipper` as a custom liquidity source (#335)
## v16.29.0 - _October 4, 2021_
* Initial integration of neon-router (behind feature flag) (#295)
## v16.28.0 - _September 29, 2021_
* Update ExchangeProxySwapQuoteConsumer for Multiplex V2 and friends (#282)

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/asset-swapper",
"version": "16.28.0",
"version": "16.29.1",
"engines": {
"node": ">=6.12"
},
@@ -63,10 +63,11 @@
"@0x/contract-addresses": "^6.7.0",
"@0x/contract-wrappers": "^13.18.0",
"@0x/contracts-erc20": "^3.3.20",
"@0x/contracts-zero-ex": "^0.29.0",
"@0x/contracts-zero-ex": "^0.29.1",
"@0x/dev-utils": "^4.2.9",
"@0x/json-schemas": "^6.3.0",
"@0x/protocol-utils": "^1.9.1",
"@0x/neon-router": "^0.1.3",
"@0x/protocol-utils": "^1.9.2",
"@0x/quote-server": "^6.0.6",
"@0x/types": "^3.3.4",
"@0x/typescript-typings": "^5.2.1",
@@ -100,7 +101,7 @@
"@0x/contracts-test-utils": "^5.4.11",
"@0x/contracts-utils": "^4.8.1",
"@0x/mesh-rpc-client": "^9.4.2",
"@0x/migrations": "^8.1.7",
"@0x/migrations": "^8.1.8",
"@0x/sol-compiler": "^4.7.5",
"@0x/subproviders": "^6.6.0",
"@0x/ts-doc-gen": "^0.0.28",

View File

@@ -363,6 +363,7 @@ export class SwapQuoter {
const cloneOpts = _.omit(opts, 'gasPrice') as GetMarketOrdersOpts;
const calcOpts: GetMarketOrdersOpts = {
...cloneOpts,
gasPrice,
feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData: FillData) =>
gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)),
),

View File

@@ -256,7 +256,7 @@ export interface RfqRequestOpts {
/**
* gasPrice: gas price to determine protocolFee amount, default to ethGasStation fast amount
*/
export interface SwapQuoteRequestOpts extends GetMarketOrdersOpts {
export interface SwapQuoteRequestOpts extends Omit<GetMarketOrdersOpts, 'gasPrice'> {
gasPrice?: BigNumber;
rfqt?: RfqRequestOpts;
}

View File

@@ -99,7 +99,6 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.UniswapV3,
ERC20BridgeSource.CurveV2,
ERC20BridgeSource.ShibaSwap,
ERC20BridgeSource.Clipper,
]),
[ChainId.Ropsten]: new SourceFilters([
ERC20BridgeSource.Kyber,
@@ -206,7 +205,6 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.UniswapV3,
ERC20BridgeSource.CurveV2,
ERC20BridgeSource.ShibaSwap,
ERC20BridgeSource.Clipper,
]),
[ChainId.Ropsten]: new SourceFilters([
ERC20BridgeSource.Kyber,
@@ -1482,22 +1480,6 @@ export const LIDO_INFO_BY_CHAIN = valueByChainId<LidoInfo>(
},
);
export const CLIPPER_INFO_BY_CHAIN = valueByChainId(
{
[ChainId.Mainnet]: {
poolAddress: '0xe82906b6b1b04f631d126c974af57a3a7b6a99d9',
tokens: [
MAINNET_TOKENS.WETH, // technically ETH but our sampler and mixin handle this
MAINNET_TOKENS.WBTC,
MAINNET_TOKENS.USDC,
MAINNET_TOKENS.USDT,
MAINNET_TOKENS.DAI,
],
},
},
{ poolAddress: NULL_ADDRESS, tokens: [] },
);
export const BALANCER_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer';
export const BALANCER_TOP_POOLS_FETCHED = 250;
export const BALANCER_MAX_POOLS_FETCHED = 3;
@@ -1637,6 +1619,23 @@ export const TRADER_JOE_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
NULL_ADDRESS,
);
export const VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID = valueByChainId<ERC20BridgeSource[]>(
{
[ChainId.Mainnet]: [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap, ERC20BridgeSource.UniswapV3],
[ChainId.BSC]: [
ERC20BridgeSource.PancakeSwap,
ERC20BridgeSource.PancakeSwapV2,
ERC20BridgeSource.BakerySwap,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.ApeSwap,
ERC20BridgeSource.CafeSwap,
ERC20BridgeSource.CheeseSwap,
ERC20BridgeSource.JulSwap,
],
},
[],
);
const uniswapV2CloneGasSchedule = (fillData?: FillData) => {
// TODO: Different base cost if to/from ETH.
let gas = 90e3;
@@ -1742,7 +1741,6 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
return gas;
},
[ERC20BridgeSource.Lido]: () => 226e3,
[ERC20BridgeSource.Clipper]: () => 170e3,
//
// BSC
@@ -1779,7 +1777,7 @@ export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(20000);
// tslint:enable:custom-no-magic-numbers
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
export const DEFAULT_GET_MARKET_ORDERS_OPTS: Omit<GetMarketOrdersOpts, 'gasPrice'> = {
// tslint:disable-next-line: custom-no-magic-numbers
runLimit: 2 ** 15,
excludedSources: [],

View File

@@ -71,7 +71,25 @@ function hasLiquidity(fills: Fill[]): boolean {
return true;
}
function nativeOrdersToFills(
export function ethToOutputAmount({
input,
output,
ethAmount,
inputAmountPerEth,
outputAmountPerEth,
}: {
input: BigNumber;
output: BigNumber;
inputAmountPerEth: BigNumber;
outputAmountPerEth: BigNumber;
ethAmount: BigNumber | number;
}): BigNumber {
return !outputAmountPerEth.isZero()
? outputAmountPerEth.times(ethAmount)
: inputAmountPerEth.times(ethAmount).times(output.dividedToIntegerBy(input));
}
export function nativeOrdersToFills(
side: MarketOperation,
orders: NativeOrderWithFillableAmounts[],
targetInput: BigNumber = POSITIVE_INF,
@@ -89,9 +107,13 @@ function nativeOrdersToFills(
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o);
const outputPenalty = !outputAmountPerEth.isZero()
? outputAmountPerEth.times(fee)
: inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input));
const outputPenalty = ethToOutputAmount({
input,
output,
inputAmountPerEth,
outputAmountPerEth,
ethAmount: fee,
});
// targetInput can be less than the order size
// whilst the penalty is constant, it affects the adjusted output
// only up until the target has been exhausted.
@@ -132,7 +154,7 @@ function nativeOrdersToFills(
return fills;
}
function dexSamplesToFills(
export function dexSamplesToFills(
side: MarketOperation,
samples: DexSample[],
outputAmountPerEth: BigNumber,
@@ -156,9 +178,13 @@ function dexSamplesToFills(
let penalty = ZERO_AMOUNT;
if (i === 0) {
// Only the first fill in a DEX path incurs a penalty.
penalty = !outputAmountPerEth.isZero()
? outputAmountPerEth.times(fee)
: inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input));
penalty = ethToOutputAmount({
input,
output,
inputAmountPerEth,
outputAmountPerEth,
ethAmount: fee,
});
}
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);

View File

@@ -39,7 +39,7 @@ import { createFills } from './fills';
import { getBestTwoHopQuote } from './multihop_utils';
import { createOrdersFromTwoHopSample } from './orders';
import { Path, PathPenaltyOpts } from './path';
import { fillsToSortedPaths, findOptimalPathAsync } from './path_optimizer';
import { fillsToSortedPaths, findOptimalPathJSAsync, findOptimalRustPathFromSamples } from './path_optimizer';
import { DexOrderSampler, getSampleAmounts } from './sampler';
import { SourceFilters } from './source_filters';
import {
@@ -48,7 +48,6 @@ import {
DexSample,
ERC20BridgeSource,
Fill,
FillData,
GenerateOptimizedOrdersOpts,
GetMarketOrdersOpts,
MarketSideLiquidity,
@@ -57,6 +56,8 @@ import {
OrderDomain,
} from './types';
const SHOULD_USE_RUST_ROUTER = process.env.RUST_ROUTER === 'true';
// tslint:disable:boolean-naming
export class MarketOperationUtils {
@@ -326,12 +327,12 @@ export class MarketOperationUtils {
public async getBatchMarketBuyOrdersAsync(
batchNativeOrders: SignedNativeOrder[][],
makerAmounts: BigNumber[],
opts?: Partial<GetMarketOrdersOpts>,
opts: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber },
): Promise<Array<OptimizerResult | undefined>> {
if (batchNativeOrders.length === 0) {
throw new Error(AggregationError.EmptyOrders);
}
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const _opts: GetMarketOrdersOpts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
const quoteSourceFilters = this._buySources.merge(requestFilters);
@@ -409,6 +410,7 @@ export class MarketOperationUtils {
excludedSources: _opts.excludedSources,
feeSchedule: _opts.feeSchedule,
allowFallback: _opts.allowFallback,
gasPrice: _opts.gasPrice,
},
);
return optimizerResult;
@@ -475,6 +477,7 @@ export class MarketOperationUtils {
outputAmountPerEth,
inputAmountPerEth,
exchangeProxyOverhead: opts.exchangeProxyOverhead || (() => ZERO_AMOUNT),
gasPrice: opts.gasPrice,
};
// NOTE: For sell quotes input is the taker asset and for buy quotes input is the maker asset
@@ -485,8 +488,22 @@ export class MarketOperationUtils {
const _unoptimizedPath = fillsToSortedPaths(fills, side, inputAmount, penaltyOpts)[0];
const unoptimizedPath = _unoptimizedPath ? _unoptimizedPath.collapse(orderOpts) : undefined;
// Find the optimal path
const optimalPath = await findOptimalPathAsync(side, fills, inputAmount, opts.runLimit, penaltyOpts);
// Find the optimal path using Rust router if enabled, otherwise fallback to JS Router
let optimalPath: Path | undefined;
if (SHOULD_USE_RUST_ROUTER) {
optimalPath = findOptimalRustPathFromSamples(
side,
dexQuotes,
[...nativeOrders, ...augmentedRfqtIndicativeQuotes],
inputAmount,
penaltyOpts,
opts.feeSchedule,
this._sampler.chainId,
);
} else {
optimalPath = await findOptimalPathJSAsync(side, fills, inputAmount, opts.runLimit, penaltyOpts);
}
const optimalPathRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
@@ -514,7 +531,7 @@ export class MarketOperationUtils {
}
// Generate a fallback path if required
await this._addOptionalFallbackAsync(side, inputAmount, optimalPath, fills, opts, penaltyOpts);
await this._addOptionalFallbackAsync(side, inputAmount, optimalPath, dexQuotes, fills, opts, penaltyOpts);
const collapsedPath = optimalPath.collapse(orderOpts);
return {
@@ -536,9 +553,9 @@ export class MarketOperationUtils {
nativeOrders: SignedNativeOrder[],
amount: BigNumber,
side: MarketOperation,
opts?: Partial<GetMarketOrdersOpts>,
opts: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber },
): Promise<OptimizerResultWithReport> {
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const _opts: GetMarketOrdersOpts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const optimizerOpts: GenerateOptimizedOrdersOpts = {
bridgeSlippage: _opts.bridgeSlippage,
maxFallbackSlippage: _opts.maxFallbackSlippage,
@@ -546,6 +563,7 @@ export class MarketOperationUtils {
feeSchedule: _opts.feeSchedule,
allowFallback: _opts.allowFallback,
exchangeProxyOverhead: _opts.exchangeProxyOverhead,
gasPrice: _opts.gasPrice,
};
if (nativeOrders.length === 0) {
@@ -711,7 +729,8 @@ export class MarketOperationUtils {
side: MarketOperation,
inputAmount: BigNumber,
optimalPath: Path,
fills: Array<Array<Fill<FillData>>>,
dexQuotes: DexSample[][],
fills: Fill[][],
opts: GenerateOptimizedOrdersOpts,
penaltyOpts: PathPenaltyOpts,
): Promise<void> {
@@ -725,13 +744,37 @@ export class MarketOperationUtils {
if (opts.allowFallback && fragileFills.length !== 0) {
// We create a fallback path that is exclusive of Native liquidity
// This is the optimal on-chain path for the entire input amount
const sturdyFills = fills.filter(p => p.length > 0 && !fragileSources.includes(p[0].source));
const sturdyOptimalPath = await findOptimalPathAsync(side, sturdyFills, inputAmount, opts.runLimit, {
const sturdyPenaltyOpts = {
...penaltyOpts,
exchangeProxyOverhead: (sourceFlags: bigint) =>
// tslint:disable-next-line: no-bitwise
penaltyOpts.exchangeProxyOverhead(sourceFlags | optimalPath.sourceFlags),
});
};
let sturdyOptimalPath: Path | undefined;
if (SHOULD_USE_RUST_ROUTER) {
const sturdySamples = dexQuotes.filter(
samples => samples.length > 0 && !fragileSources.includes(samples[0].source),
);
sturdyOptimalPath = findOptimalRustPathFromSamples(
side,
sturdySamples,
[],
inputAmount,
sturdyPenaltyOpts,
opts.feeSchedule,
this._sampler.chainId,
);
} else {
const sturdyFills = fills.filter(p => p.length > 0 && !fragileSources.includes(p[0].source));
sturdyOptimalPath = await findOptimalPathJSAsync(
side,
sturdyFills,
inputAmount,
opts.runLimit,
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 (

View File

@@ -3,7 +3,7 @@ import { AbiEncoder, BigNumber } from '@0x/utils';
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
import { MAX_UINT256, NULL_BYTES, ZERO_AMOUNT } from './constants';
import { MAX_UINT256, ZERO_AMOUNT } from './constants';
import {
AggregationError,
BalancerFillData,
@@ -180,8 +180,6 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'IronSwap');
case ERC20BridgeSource.ACryptos:
return encodeBridgeSourceId(BridgeProtocol.Curve, 'ACryptoS');
case ERC20BridgeSource.Clipper:
return encodeBridgeSourceId(BridgeProtocol.Clipper, 'Clipper');
case ERC20BridgeSource.Pangolin:
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Pangolin');
case ERC20BridgeSource.TraderJoe:
@@ -326,10 +324,6 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
const lidoFillData = (order as OptimizedMarketBridgeOrder<LidoFillData>).fillData;
bridgeData = encoder.encode([lidoFillData.stEthTokenAddress]);
break;
case ERC20BridgeSource.Clipper:
const clipperFillData = (order as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
bridgeData = encoder.encode([clipperFillData.poolAddress, NULL_BYTES]);
break;
default:
throw new Error(AggregationError.NoBridgeForSource);
}
@@ -489,10 +483,6 @@ export const BRIDGE_ENCODERS: {
]),
[ERC20BridgeSource.KyberDmm]: AbiEncoder.create('(address,address[],address[])'),
[ERC20BridgeSource.Lido]: AbiEncoder.create('(address)'),
[ERC20BridgeSource.Clipper]: AbiEncoder.create([
{ name: 'provider', type: 'address' },
{ name: 'data', type: 'bytes' },
]),
};
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {

View File

@@ -3,6 +3,7 @@ import { BigNumber } from '@0x/utils';
import { MarketOperation } from '../../types';
import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
import { ethToOutputAmount } from './fills';
import { createBridgeOrder, createNativeOptimizedOrder, CreateOrderFromPathOpts, getMakerTakerTokens } from './orders';
import { getCompleteRate, getRate } from './rate_utils';
import {
@@ -25,12 +26,14 @@ export interface PathPenaltyOpts {
outputAmountPerEth: BigNumber;
inputAmountPerEth: BigNumber;
exchangeProxyOverhead: ExchangeProxyOverhead;
gasPrice: BigNumber;
}
export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
outputAmountPerEth: ZERO_AMOUNT,
inputAmountPerEth: ZERO_AMOUNT,
exchangeProxyOverhead: () => ZERO_AMOUNT,
gasPrice: ZERO_AMOUNT,
};
export class Path {
@@ -143,9 +146,13 @@ export class Path {
const { input, output } = this._adjustedSize;
const { exchangeProxyOverhead, outputAmountPerEth, inputAmountPerEth } = this.pathPenaltyOpts;
const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
const pathPenalty = !outputAmountPerEth.isZero()
? outputAmountPerEth.times(gasOverhead)
: inputAmountPerEth.times(gasOverhead).times(output.dividedToIntegerBy(input));
const pathPenalty = ethToOutputAmount({
input,
output,
inputAmountPerEth,
outputAmountPerEth,
ethAmount: gasOverhead,
});
return {
input,
output: this.side === MarketOperation.Sell ? output.minus(pathPenalty) : output.plus(pathPenalty),

View File

@@ -1,20 +1,377 @@
import { assert } from '@0x/assert';
import { ChainId } from '@0x/contract-addresses';
import { OptimizerCapture, route, SerializedPath } from '@0x/neon-router';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { performance } from 'perf_hooks';
import { MarketOperation } from '../../types';
import { DEFAULT_INFO_LOGGER } from '../../constants';
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID } from '../market_operation_utils/constants';
import { dexSamplesToFills, ethToOutputAmount, nativeOrdersToFills } from './fills';
import { DEFAULT_PATH_PENALTY_OPTS, Path, PathPenaltyOpts } from './path';
import { ERC20BridgeSource, Fill } from './types';
import { getRate } from './rate_utils';
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillData } from './types';
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
const RUN_LIMIT_DECAY_FACTOR = 0.5;
const RUST_ROUTER_NUM_SAMPLES = 200;
const FILL_QUOTE_TRANSFORMER_GAS_OVERHEAD = new BigNumber(150e3);
// NOTE: The Rust router will panic with less than 3 samples
const MIN_NUM_SAMPLE_INPUTS = 3;
const isDexSample = (obj: DexSample | NativeOrderWithFillableAmounts): obj is DexSample => !!(obj as DexSample).source;
function nativeOrderToNormalizedAmounts(
side: MarketOperation,
nativeOrder: NativeOrderWithFillableAmounts,
): { input: BigNumber; output: BigNumber } {
const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount } = nativeOrder;
const makerAmount = fillableMakerAmount;
const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
return { input, output };
}
function calculateOuputFee(
side: MarketOperation,
sampleOrNativeOrder: DexSample | NativeOrderWithFillableAmounts,
outputAmountPerEth: BigNumber,
inputAmountPerEth: BigNumber,
fees: FeeSchedule,
): BigNumber {
if (isDexSample(sampleOrNativeOrder)) {
const { input, output, source, fillData } = sampleOrNativeOrder;
const fee = fees[source]?.(fillData) || 0;
const outputFee = ethToOutputAmount({
input,
output,
inputAmountPerEth,
outputAmountPerEth,
ethAmount: fee,
});
return outputFee;
} else {
const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
const fee = fees[ERC20BridgeSource.Native]?.(sampleOrNativeOrder) || 0;
const outputFee = ethToOutputAmount({
input,
output,
inputAmountPerEth,
outputAmountPerEth,
ethAmount: fee,
});
return outputFee;
}
}
// Use linear interpolation to approximate the output
// at a certain input somewhere between the two samples
// See https://en.wikipedia.org/wiki/Linear_interpolation
const interpolateOutputFromSamples = (
left: { input: BigNumber; output: BigNumber },
right: { input: BigNumber; output: BigNumber },
targetInput: BigNumber,
): BigNumber =>
left.output.plus(
right.output
.minus(left.output)
.dividedBy(right.input.minus(left.input))
.times(targetInput.minus(left.input)),
);
function findRoutesAndCreateOptimalPath(
side: MarketOperation,
samples: DexSample[][],
nativeOrders: NativeOrderWithFillableAmounts[],
input: BigNumber,
opts: PathPenaltyOpts,
fees: FeeSchedule,
): Path | undefined {
const createFill = (sample: DexSample) =>
dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, fees)[0];
// Track sample id's to integers (required by rust router)
const sampleIdLookup: { [key: string]: number } = {};
let sampleIdCounter = 0;
const sampleToId = (source: ERC20BridgeSource, index: number): number => {
const key = `${source}-${index}`;
if (sampleIdLookup[key]) {
return sampleIdLookup[key];
} else {
sampleIdLookup[key] = ++sampleIdCounter;
return sampleIdLookup[key];
}
};
const samplesAndNativeOrdersWithResults: Array<DexSample[] | NativeOrderWithFillableAmounts[]> = [];
const serializedPaths: SerializedPath[] = [];
for (const singleSourceSamples of samples) {
if (singleSourceSamples.length === 0) {
continue;
}
const singleSourceSamplesWithOutput = [...singleSourceSamples];
for (let i = singleSourceSamples.length - 1; i >= 0; i--) {
if (singleSourceSamples[i].output.isZero()) {
// Remove trailing 0 output samples
singleSourceSamplesWithOutput.pop();
} else {
break;
}
}
if (singleSourceSamplesWithOutput.length < MIN_NUM_SAMPLE_INPUTS) {
continue;
}
// TODO(kimpers): Do we need to handle 0 entries, from eg Kyber?
const serializedPath = singleSourceSamplesWithOutput.reduce<SerializedPath>(
(memo, sample, sampleIdx) => {
memo.ids.push(sampleToId(sample.source, sampleIdx));
memo.inputs.push(sample.input.integerValue().toNumber());
memo.outputs.push(sample.output.integerValue().toNumber());
memo.outputFees.push(
calculateOuputFee(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
.integerValue()
.toNumber(),
);
return memo;
},
{
ids: [],
inputs: [],
outputs: [],
outputFees: [],
},
);
samplesAndNativeOrdersWithResults.push(singleSourceSamplesWithOutput);
serializedPaths.push(serializedPath);
}
for (const [idx, nativeOrder] of nativeOrders.entries()) {
const { input: normalizedOrderInput, output: normalizedOrderOutput } = nativeOrderToNormalizedAmounts(
side,
nativeOrder,
);
// NOTE: skip dummy order created in swap_quoter
// TODO: remove dummy order and this logic once we don't need the JS router
if (normalizedOrderInput.isLessThanOrEqualTo(0) || normalizedOrderOutput.isLessThanOrEqualTo(0)) {
continue;
}
// HACK: the router requires at minimum 3 samples as a basis for interpolation
const inputs = [
0,
normalizedOrderInput
.dividedBy(2)
.integerValue()
.toNumber(),
normalizedOrderInput.integerValue().toNumber(),
];
const outputs = [
0,
normalizedOrderOutput
.dividedBy(2)
.integerValue()
.toNumber(),
normalizedOrderOutput.integerValue().toNumber(),
];
// NOTE: same fee no matter if full or partial fill
const fee = calculateOuputFee(side, nativeOrder, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
.integerValue()
.toNumber();
const outputFees = [fee, fee, fee];
// NOTE: ids can be the same for all fake samples
const id = sampleToId(ERC20BridgeSource.Native, idx);
const ids = [id, id, id];
const serializedPath: SerializedPath = {
ids,
inputs,
outputs,
outputFees,
};
samplesAndNativeOrdersWithResults.push([nativeOrder]);
serializedPaths.push(serializedPath);
}
if (serializedPaths.length === 0) {
return undefined;
}
const rustArgs: OptimizerCapture = {
side,
targetInput: input.toNumber(),
pathsIn: serializedPaths,
};
const before = performance.now();
const allSourcesRustRoute = route(rustArgs, RUST_ROUTER_NUM_SAMPLES);
DEFAULT_INFO_LOGGER(
{ router: 'neon-router', performanceMs: performance.now() - before, type: 'real' },
'Rust router real routing performance',
);
assert.assert(
rustArgs.pathsIn.length === allSourcesRustRoute.length,
'different number of sources in the Router output than the input',
);
const routesAndSamples = _.zip(allSourcesRustRoute, samplesAndNativeOrdersWithResults);
const adjustedFills: Fill[] = [];
const totalRoutedAmount = BigNumber.sum(...allSourcesRustRoute);
const scale = input.dividedBy(totalRoutedAmount);
for (const [routeInput, routeSamplesAndNativeOrders] of routesAndSamples) {
if (!routeInput || !routeSamplesAndNativeOrders) {
continue;
}
// TODO(kimpers): [TKR-241] amounts are sometimes clipped in the router due to precisions loss for number/f64
// we can work around it by scaling it and rounding up. However now we end up with a total amount of a couple base units too much
const rustInputAdjusted = BigNumber.min(
new BigNumber(routeInput).multipliedBy(scale).integerValue(BigNumber.ROUND_CEIL),
input,
);
const current = routeSamplesAndNativeOrders[routeSamplesAndNativeOrders.length - 1];
if (!isDexSample(current)) {
const nativeFill = nativeOrdersToFills(
side,
[current],
rustInputAdjusted,
opts.outputAmountPerEth,
opts.inputAmountPerEth,
fees,
)[0];
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
adjustedFills.push(nativeFill);
continue;
}
// NOTE: For DexSamples only
let fill = createFill(current);
const routeSamples = routeSamplesAndNativeOrders as Array<DexSample<FillData>>;
// Descend to approach a closer fill for fillData which may not be consistent
// throughout the path (UniswapV3) and for a closer guesstimate at
// gas used
assert.assert(routeSamples.length >= 1, 'Found no sample to use for source');
for (let k = routeSamples.length - 1; k >= 0; k--) {
if (k === 0) {
fill = createFill(routeSamples[0]);
}
if (rustInputAdjusted.isGreaterThan(routeSamples[k].input)) {
// Between here and the previous fill
// HACK: Use the midpoint between the two
const left = routeSamples[k];
const right = routeSamples[k + 1];
if (left && right) {
// Approximate how much output we get for the input with the surrounding samples
const interpolatedOutput = interpolateOutputFromSamples(
left,
right,
rustInputAdjusted,
).decimalPlaces(0, side === MarketOperation.Sell ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL);
fill = createFill({
...right, // default to the greater (for gas used)
input: rustInputAdjusted,
output: interpolatedOutput,
});
} else {
assert.assert(Boolean(left || right), 'No valid sample to use');
fill = createFill(left || right);
}
break;
}
}
const scaleOutput = (output: BigNumber) =>
output
.dividedBy(fill.input)
.times(rustInputAdjusted)
.decimalPlaces(0, side === MarketOperation.Sell ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL);
adjustedFills.push({
...fill,
input: rustInputAdjusted,
output: scaleOutput(fill.output),
adjustedOutput: scaleOutput(fill.adjustedOutput),
index: 0,
parent: undefined,
});
}
const pathFromRustInputs = Path.create(side, adjustedFills, input);
return pathFromRustInputs;
}
export function findOptimalRustPathFromSamples(
side: MarketOperation,
samples: DexSample[][],
nativeOrders: NativeOrderWithFillableAmounts[],
input: BigNumber,
opts: PathPenaltyOpts,
fees: FeeSchedule,
chainId: ChainId,
): Path | undefined {
const before = performance.now();
const logPerformance = () =>
DEFAULT_INFO_LOGGER(
{ router: 'neon-router', performanceMs: performance.now() - before, type: 'total' },
'Rust router total routing performance',
);
const allSourcesPath = findRoutesAndCreateOptimalPath(side, samples, nativeOrders, input, opts, fees);
if (!allSourcesPath) {
return undefined;
}
const vipSources = VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID[chainId];
// HACK(kimpers): The Rust router currently doesn't account for VIP sources correctly
// we need to try to route them in isolation and compare with the results all sources
if (vipSources.length > 0) {
const vipSourcesSet = new Set(vipSources);
const vipSourcesSamples = samples.filter(s => s[0] && vipSourcesSet.has(s[0].source));
if (vipSourcesSamples.length > 0) {
const vipSourcesPath = findRoutesAndCreateOptimalPath(side, vipSourcesSamples, [], input, opts, fees);
const { input: allSourcesInput, output: allSourcesOutput } = allSourcesPath.adjustedSize();
// NOTE: For sell quotes input is the taker asset and for buy quotes input is the maker asset
const gasCostInWei = FILL_QUOTE_TRANSFORMER_GAS_OVERHEAD.times(opts.gasPrice);
const fqtOverheadInOutputToken = gasCostInWei.times(opts.outputAmountPerEth);
const outputWithFqtOverhead =
side === MarketOperation.Sell
? allSourcesOutput.minus(fqtOverheadInOutputToken)
: allSourcesOutput.plus(fqtOverheadInOutputToken);
const allSourcesAdjustedRateWithFqtOverhead = getRate(side, allSourcesInput, outputWithFqtOverhead);
if (vipSourcesPath?.adjustedRate().isGreaterThan(allSourcesAdjustedRateWithFqtOverhead)) {
logPerformance();
return vipSourcesPath;
}
}
}
logPerformance();
return allSourcesPath;
}
/**
* Find the optimal mixture of fills that maximizes (for sells) or minimizes
* (for buys) output, while meeting the input requirement.
*/
export async function findOptimalPathAsync(
export async function findOptimalPathJSAsync(
side: MarketOperation,
fills: Fill[][],
targetInput: BigNumber,

View File

@@ -20,7 +20,6 @@ import {
import {
BALANCER_V2_VAULT_ADDRESS_BY_CHAIN,
BANCOR_REGISTRY_BY_CHAIN_ID,
CLIPPER_INFO_BY_CHAIN,
DODOV1_CONFIG_BY_CHAIN_ID,
DODOV2_FACTORIES_BY_CHAIN_ID,
KYBER_CONFIG_BY_CHAIN_ID,
@@ -1416,32 +1415,6 @@ export class SamplerOperations {
return this.getLidoSellQuotes(lidoInfo, makerToken, takerToken, takerFillAmounts);
}
case ERC20BridgeSource.Clipper:
const { poolAddress: clipperPoolAddress, tokens: clipperTokens } = CLIPPER_INFO_BY_CHAIN[
this.chainId
];
if (
clipperPoolAddress === NULL_ADDRESS ||
!clipperTokens.includes(makerToken) ||
!clipperTokens.includes(takerToken)
) {
return [];
}
// Clipper requires WETH to be represented as address(0)
const adjustedMakerToken =
makerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : makerToken;
const adjustedTakerToken =
takerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : takerToken;
// Supports the PLP interface
return this.getLiquidityProviderSellQuotes(
clipperPoolAddress,
adjustedMakerToken,
adjustedTakerToken,
takerFillAmounts,
// tslint:disable-next-line: custom-no-magic-numbers
0, // Not used for Clipper
ERC20BridgeSource.Clipper,
);
default:
throw new Error(`Unsupported sell sample source: ${source}`);
}
@@ -1706,32 +1679,6 @@ export class SamplerOperations {
return this.getLidoBuyQuotes(lidoInfo, makerToken, takerToken, makerFillAmounts);
}
case ERC20BridgeSource.Clipper:
const { poolAddress: clipperPoolAddress, tokens: clipperTokens } = CLIPPER_INFO_BY_CHAIN[
this.chainId
];
if (
clipperPoolAddress === NULL_ADDRESS ||
!clipperTokens.includes(makerToken) ||
!clipperTokens.includes(takerToken)
) {
return [];
}
// Clipper requires WETH to be represented as address(0)
const adjustedMakerToken =
makerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : makerToken;
const adjustedTakerToken =
takerToken === NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId] ? NULL_ADDRESS : takerToken;
// Supports the PLP interface
return this.getLiquidityProviderBuyQuotes(
clipperPoolAddress,
adjustedMakerToken,
adjustedTakerToken,
makerFillAmounts,
// tslint:disable-next-line: custom-no-magic-numbers
0, // Not used for Clipper
ERC20BridgeSource.Clipper,
);
default:
throw new Error(`Unsupported buy sample source: ${source}`);
}

View File

@@ -69,7 +69,6 @@ export enum ERC20BridgeSource {
CurveV2 = 'Curve_V2',
Lido = 'Lido',
ShibaSwap = 'ShibaSwap',
Clipper = 'Clipper',
// BSC only
PancakeSwap = 'PancakeSwap',
PancakeSwapV2 = 'PancakeSwap_V2',
@@ -455,6 +454,11 @@ export interface GetMarketOrdersOpts {
* hopping to. E.g DAI->USDC via an adjacent token WETH
*/
tokenAdjacencyGraph: TokenAdjacencyGraph;
/**
* Gas price to use for quote
*/
gasPrice: BigNumber;
}
/**
@@ -534,10 +538,11 @@ export interface GenerateOptimizedOrdersOpts {
bridgeSlippage?: number;
maxFallbackSlippage?: number;
excludedSources?: ERC20BridgeSource[];
feeSchedule?: FeeSchedule;
feeSchedule: FeeSchedule;
exchangeProxyOverhead?: ExchangeProxyOverhead;
allowFallback?: boolean;
shouldBatchBridgeOrders?: boolean;
gasPrice: BigNumber;
}
export interface ComparisonPrice {

View File

@@ -79,7 +79,7 @@ async function getMarketSellOrdersAsync(
utils: MarketOperationUtils,
nativeOrders: SignedNativeOrder[],
takerAmount: BigNumber,
opts?: Partial<GetMarketOrdersOpts>,
opts: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber },
): Promise<OptimizerResultWithReport> {
return utils.getOptimizerResultAsync(nativeOrders, takerAmount, MarketOperation.Sell, opts);
}
@@ -96,7 +96,7 @@ async function getMarketBuyOrdersAsync(
utils: MarketOperationUtils,
nativeOrders: SignedNativeOrder[],
makerAmount: BigNumber,
opts?: Partial<GetMarketOrdersOpts>,
opts: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber },
): Promise<OptimizerResultWithReport> {
return utils.getOptimizerResultAsync(nativeOrders, makerAmount, MarketOperation.Buy, opts);
}
@@ -459,7 +459,7 @@ describe('MarketOperationUtils tests', () => {
FILL_AMOUNT,
_.times(NUM_SAMPLES, i => DEFAULT_RATES[ERC20BridgeSource.Native][i]),
);
const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> = {
const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber } = {
numSamples: NUM_SAMPLES,
sampleDistributionBase: 1,
bridgeSlippage: 0,
@@ -468,6 +468,7 @@ describe('MarketOperationUtils tests', () => {
allowFallback: false,
gasSchedule: {},
feeSchedule: {},
gasPrice: new BigNumber(30e9),
};
beforeEach(() => {
@@ -1229,6 +1230,7 @@ describe('MarketOperationUtils tests', () => {
excludedSources: [],
numSamples: 4,
bridgeSlippage: 0,
gasPrice: new BigNumber(30e9),
},
);
const result = ordersAndReport.optimizedOrders;
@@ -1298,7 +1300,8 @@ describe('MarketOperationUtils tests', () => {
FILL_AMOUNT,
_.times(NUM_SAMPLES, () => DEFAULT_RATES[ERC20BridgeSource.Native][0]),
);
const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> = {
const GAS_PRICE = new BigNumber(100e9); // 100 gwei
const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber } = {
numSamples: NUM_SAMPLES,
sampleDistributionBase: 1,
bridgeSlippage: 0,
@@ -1307,6 +1310,7 @@ describe('MarketOperationUtils tests', () => {
allowFallback: false,
gasSchedule: {},
feeSchedule: {},
gasPrice: GAS_PRICE,
};
beforeEach(() => {
@@ -1626,11 +1630,10 @@ describe('MarketOperationUtils tests', () => {
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
});
const optimizer = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
const gasPrice = 100e9; // 100 gwei
const exchangeProxyOverhead = (sourceFlags: bigint) =>
sourceFlags === SOURCE_FLAGS.LiquidityProvider
? constants.ZERO_AMOUNT
: new BigNumber(1.3e5).times(gasPrice);
: new BigNumber(1.3e5).times(GAS_PRICE);
const improvedOrdersResponse = await optimizer.getOptimizerResultAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
FILL_AMOUNT,

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1633374058,
"version": "8.1.8",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1632957537,
"version": "8.1.7",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v8.1.8 - _October 4, 2021_
* Dependencies updated
## v8.1.7 - _September 29, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/migrations",
"version": "8.1.7",
"version": "8.1.8",
"engines": {
"node": ">=6.12"
},
@@ -81,7 +81,7 @@
"@0x/contracts-multisig": "^4.1.38",
"@0x/contracts-staking": "^2.0.45",
"@0x/contracts-utils": "^4.8.1",
"@0x/contracts-zero-ex": "^0.29.0",
"@0x/contracts-zero-ex": "^0.29.1",
"@0x/sol-compiler": "^4.7.5",
"@0x/subproviders": "^6.6.0",
"@0x/typescript-typings": "^5.2.1",

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1633374058,
"version": "1.9.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1632957537,
"version": "1.9.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.9.2 - _October 4, 2021_
* Dependencies updated
## v1.9.1 - _September 29, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/protocol-utils",
"version": "1.9.1",
"version": "1.9.2",
"engines": {
"node": ">=6.12"
},

View File

@@ -131,7 +131,7 @@ export enum BridgeProtocol {
KyberDmm,
CurveV2,
Lido,
Clipper,
Clipper, // Not used: Clipper is now using PLP interface
}
// tslint:enable: enum-naming

View File

@@ -959,6 +959,13 @@
typedoc "~0.16.11"
yargs "^10.0.3"
"@0x/neon-router@^0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@0x/neon-router/-/neon-router-0.1.3.tgz#70da4c17ca4b59dfe8b5e539673e364a70e62ebd"
integrity sha512-EfdrG829NalYjAK5/nMTD6YyJQgUzgssL2Hvyphu1ugWxWlZ3QMM9qpZsKt82hUiyZT/64I4JJ3hkerMhTaHeg==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.5"
"@0x/order-utils@^10.2.4", "@0x/order-utils@^10.4.28":
version "10.4.28"
resolved "https://registry.yarnpkg.com/@0x/order-utils/-/order-utils-10.4.28.tgz#c7b2f7d87a7f9834f9aa6186fbac68f32a05a81d"
@@ -2473,6 +2480,21 @@
semver "^7.3.4"
tar "^6.1.0"
"@mapbox/node-pre-gyp@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950"
integrity sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==
dependencies:
detect-libc "^1.0.3"
https-proxy-agent "^5.0.0"
make-dir "^3.1.0"
node-fetch "^2.6.1"
nopt "^5.0.0"
npmlog "^4.1.2"
rimraf "^3.0.2"
semver "^7.3.4"
tar "^6.1.0"
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"