Compare commits

...

15 Commits

Author SHA1 Message Date
Github Actions
9a1df67d6b Publish
- @0x/asset-swapper@16.51.0
2022-03-10 04:58:07 +00:00
Github Actions
4b91411faf Updated CHANGELOGS & MD docs 2022-03-10 04:58:04 +00:00
Jacob Evans
622a542d57 feat: Curve YFI-ETH (#444)
* feat: Curve YFI-ETH

* CHANGELOG
2022-03-10 14:36:59 +10:00
Github Actions
cba53a9a50 Publish
- @0x/asset-swapper@16.50.3
2022-03-09 14:59:25 +00:00
Github Actions
e186f27f63 Updated CHANGELOGS & MD docs 2022-03-09 14:59:22 +00:00
Kim Persson
4cd767ecb8 feat: VIP routing in the router, don't create fallback orders for native [TKR-243] (#440)
* feat: calculate all routes and VIP only routes in a single router call

* fix: Try to disable using fallback orders for quotes with native orders

* fix: create VIP sources set once per routing call

* chore: use private publish version of neon-router to test

* chore(lint): comment out unused fn _addOptionalFallbackAsync

* chore: update to latest private publish of neon-router

* refactor: fix router metrics beforeTimeMs naming

* feat: don't recompute isVip for ever sample of a source

* chore: update neon-router to real published version

* fix: merge conflict resolution issue

* chore: add asset-swapper changelog entry
2022-03-09 15:39:47 +01:00
Shawn
f6e85aedf1 Fix/routing glue optimization (#439)
* fix: only create fills if using js router, remove `unoptimizedPath`

* minor optimizations

* remove the cap of route output in buys

* change PR number

* fix: Update Ropsten UniswapV3 Router address (#441)

* fix: Update Ropsten UniswapV3 Router address

* Update CHANGELOG

* Updated CHANGELOGS & MD docs

* Publish

 - @0x/asset-swapper@16.50.2

* change PR number

Co-authored-by: Kim Persson <kimpersson88@gmail.com>
Co-authored-by: Jacob Evans <jacob@dekz.net>
Co-authored-by: Github Actions <github-actions@github.com>
2022-03-08 16:26:22 -08:00
Github Actions
b3ee294ba5 Publish
- @0x/asset-swapper@16.50.2
2022-03-07 01:37:11 +00:00
Github Actions
1c242def93 Updated CHANGELOGS & MD docs 2022-03-07 01:37:07 +00:00
Jacob Evans
f0fe6f2f69 fix: Update Ropsten UniswapV3 Router address (#441)
* fix: Update Ropsten UniswapV3 Router address

* Update CHANGELOG
2022-03-07 11:18:25 +10:00
Github Actions
f86d555e49 Publish
- @0x/asset-swapper@16.50.1
2022-03-03 13:04:04 +00:00
Github Actions
b0f2c40463 Updated CHANGELOGS & MD docs 2022-03-03 13:04:00 +00:00
Kim Persson
87be6fbb8a fix: lower UniswapV3Sampler quote gas allowance to 700k (#438)
* fix: lower UniswapV3Sampler quote gas allowance to 700k

* chore: add asset-swapper changelog entry
2022-03-03 13:45:20 +01:00
Noah Khamliche
9141a9d2c8 feat: BTRFLY/WETH factory pool (#437)
* added curve support for BTRFLY/WETH factory pool

* prettier and lint

* remove timestamp

* fixed gas schedule for btrflyweth pool

* chore: update Node16 (#384)


* Node 16

* update yarn.lock

* Update ganache-cli and merkle-patricia-tree

* [TKR-275] Add Geist on Fantom (#398)

* Add Geist on Fantom

* nvm there is not subgraph for it

* finish giest utils

* Address pr comments

* lowercase gtoken addresses

* return undefined instead of error for unsupported pairs

* another lower case

* Update fantom fillQuoteTransformer address

* more const clean up

* feat: Improve Uniswap V3 gas schedule redux (#424)

* Revert "fix: Revert Improve Uniswap V3 gas schedule (#397) (#419)"

This reverts commit df0e0866e4.

* fix: UniswapV3Sampler return token amounts as the last value in return tuple

* fix: bump Uniswap V3 quote max gas because QuoterV2 is more expensive

* fix: don't try to rout 0 sellAmount/buyAmount quotes

* fix: Linting issue

* fix: use median gas usage instead of mean in UniV3 gas schedule

* chore: add asset-swapper changelog entry

* fix: remove contract-addresses changelog empty row failing linting

* Updated CHANGELOGS & MD docs

* Publish

 - @0x/contracts-erc20@3.3.27
 - @0x/contracts-test-utils@5.4.18
 - @0x/contracts-treasury@1.4.10
 - @0x/contracts-utils@4.8.8
 - @0x/contracts-zero-ex@0.31.1
 - @0x/asset-swapper@16.50.0
 - @0x/contract-addresses@6.12.0
 - @0x/contract-wrappers@13.19.1
 - @0x/migrations@8.1.16
 - @0x/protocol-utils@1.11.1

* Add v4 NFT Audits (#435)

* prettier and lint

Co-authored-by: Jacob Evans <jacob@dekz.net>
Co-authored-by: Cece Z <cece@0xproject.com>
Co-authored-by: Kim Persson <kimpers@users.noreply.github.com>
Co-authored-by: Github Actions <github-actions@github.com>
Co-authored-by: Jessica Lin <jlin2700@gmail.com>
2022-03-02 21:18:40 -05:00
Jessica Lin
7f75de347e Add v4 NFT Audits (#435) 2022-03-02 10:46:00 -08:00
12 changed files with 319 additions and 223 deletions

View File

@@ -4,23 +4,28 @@ Audits
Below are links to our third-party audit reports. Below are links to our third-party audit reports.
+------------------+---------------------------------------------------------------------------------------------------------------+ +----------------------+---------------------------------------------------------------------------------------------------------------------------+
| **Release** | **Reports** | | **Release** | **Reports** |
+------------------+---------------------------------------------------------------------------------------------------------------+ +----------------------+---------------------------------------------------------------------------------------------------------------------------+
| Exchange V4 | * `Consensys Diligence (December 2020) <https://consensys.net/diligence/audits/2020/12/0x-exchange-v4/>`__ | | ERC721OrdersFeature | * `ABDK Consulting <https://s3.us-east-2.amazonaws.com/zeips.0x.org/audits/abdk-consulting/ABDK_0x_Solidity_v_1_0.pdf>`__ |
+------------------+---------------------------------------------------------------------------------------------------------------+ | | |
| Exchange V3 | * `Trail of Bits <http://zeips.0x.org.s3-website.us-east-2.amazonaws.com/audits/56/trail-of-bits/audit.pdf>`_ | | | |
| | * `Consensys Diligence (Exchange) <https://diligence.consensys.net/audits/2019/09/0x-v3-exchange/>`__ | | ERC1155OrdersFeature | |
| | * `Consensys Diligence (Staking) <https://diligence.consensys.net/audits/2019/10/0x-v3-staking/>`__ | +----------------------+---------------------------------------------------------------------------------------------------------------------------+
+------------------+---------------------------------------------------------------------------------------------------------------+ | Exchange V4 | * `Consensys Diligence (December 2020) <https://consensys.net/diligence/audits/2020/12/0x-exchange-v4/>`__ |
| Exchange V2.1 | * `First <https://docs.google.com/document/d/1jYv6V21MfCSwCS5fxD6ZyaLWGzkpRSUO0lZpST94XsA/edit>`_ | +----------------------+---------------------------------------------------------------------------------------------------------------------------+
| | * `Consensys Diligence <https://github.com/ConsenSys/0x_audit_report_2018-07-23>`_ | | Exchange V3 | * `Trail of Bits <http://zeips.0x.org.s3-website.us-east-2.amazonaws.com/audits/56/trail-of-bits/audit.pdf>`__ |
+------------------+---------------------------------------------------------------------------------------------------------------+ | | * `Consensys Diligence (Exchange) <https://diligence.consensys.net/audits/2019/09/0x-v3-exchange/>`__ |
| MultiAssetProxy | * `Consensys Diligence <https://github.com/ConsenSys/0x-audit-report-2018-12>`__ | | | * `Consensys Diligence (Staking) <https://diligence.consensys.net/audits/2019/10/0x-v3-staking/>`__ |
+------------------+---------------------------------------------------------------------------------------------------------------+ +----------------------+---------------------------------------------------------------------------------------------------------------------------+
| ERC1155Proxy | * `Consensys Diligence <https://github.com/ConsenSys/0x-audit-report-2019-05>`__ | | Exchange V2.1 | * `First <https://docs.google.com/document/d/1jYv6V21MfCSwCS5fxD6ZyaLWGzkpRSUO0lZpST94XsA/edit>`_ |
+------------------+---------------------------------------------------------------------------------------------------------------+ | | * `Consensys Diligence <https://github.com/ConsenSys/0x_audit_report_2018-07-23>`_ |
| StaticCallProxy | * No third-party audit. | +----------------------+---------------------------------------------------------------------------------------------------------------------------+
+------------------+---------------------------------------------------------------------------------------------------------------+ | MultiAssetProxy | * `Consensys Diligence <https://github.com/ConsenSys/0x-audit-report-2018-12>`__ |
| ERC20BridgeProxy | * No third-party audit. | +----------------------+---------------------------------------------------------------------------------------------------------------------------+
+------------------+---------------------------------------------------------------------------------------------------------------+ | ERC1155Proxy | * `Consensys Diligence <https://github.com/ConsenSys/0x-audit-report-2019-05>`__ |
+----------------------+---------------------------------------------------------------------------------------------------------------------------+
| StaticCallProxy | * No third-party audit. |
+----------------------+---------------------------------------------------------------------------------------------------------------------------+
| ERC20BridgeProxy | * No third-party audit. |
+----------------------+---------------------------------------------------------------------------------------------------------------------------+

View File

@@ -1,4 +1,52 @@
[ [
{
"version": "16.51.0",
"changes": [
{
"note": "Added `Curve` `YFI-ETH` pool",
"pr": 444
}
],
"timestamp": 1646888282
},
{
"version": "16.50.3",
"changes": [
{
"note": "Routing glue optimization",
"pr": 439
},
{
"note": "Move VIP source routing into neon-router & disable fallback orders for native/plp",
"pr": 440
}
],
"timestamp": 1646837959
},
{
"version": "16.50.2",
"changes": [
{
"note": "Update `Uniswap_V3` address on `Ropsten`",
"pr": 441
}
],
"timestamp": 1646617024
},
{
"version": "16.50.1",
"changes": [
{
"note": "Add BTRFLY/WETH Curve pool on mainnet",
"pr": 437
},
{
"note": "Lower Uniswap V3 Sampler gas allowance",
"pr": 438
}
],
"timestamp": 1646312638
},
{ {
"version": "16.50.0", "version": "16.50.0",
"changes": [ "changes": [

View File

@@ -5,6 +5,24 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG CHANGELOG
## v16.51.0 - _March 10, 2022_
* Added `Curve` `YFI-ETH` pool (#444)
## v16.50.3 - _March 9, 2022_
* Routing glue optimization (#439)
* Move VIP source routing into neon-router & disable fallback orders for native/plp (#440)
## v16.50.2 - _March 7, 2022_
* Update `Uniswap_V3` address on `Ropsten` (#441)
## v16.50.1 - _March 3, 2022_
* Add BTRFLY/WETH Curve pool on mainnet (#437)
* Lower Uniswap V3 Sampler gas allowance (#438)
## v16.50.0 - _March 2, 2022_ ## v16.50.0 - _March 2, 2022_
* Adding support for Geist on `Fantom` (#398) * Adding support for Geist on `Fantom` (#398)

View File

@@ -77,7 +77,7 @@ interface IUniswapV3Pool {
contract UniswapV3Sampler contract UniswapV3Sampler
{ {
/// @dev Gas limit for UniswapV3 calls. This is 100% a guess. /// @dev Gas limit for UniswapV3 calls. This is 100% a guess.
uint256 constant private QUOTE_GAS = 900e3; uint256 constant private QUOTE_GAS = 700e3;
/// @dev Sample sell quotes from UniswapV3. /// @dev Sample sell quotes from UniswapV3.
/// @param quoter UniswapV3 Quoter contract. /// @param quoter UniswapV3 Quoter contract.

View File

@@ -1,6 +1,6 @@
{ {
"name": "@0x/asset-swapper", "name": "@0x/asset-swapper",
"version": "16.50.0", "version": "16.51.0",
"engines": { "engines": {
"node": ">=6.12" "node": ">=6.12"
}, },
@@ -66,7 +66,7 @@
"@0x/contracts-zero-ex": "^0.31.1", "@0x/contracts-zero-ex": "^0.31.1",
"@0x/dev-utils": "^4.2.11", "@0x/dev-utils": "^4.2.11",
"@0x/json-schemas": "^6.4.1", "@0x/json-schemas": "^6.4.1",
"@0x/neon-router": "^0.3.3", "@0x/neon-router": "^0.3.5",
"@0x/protocol-utils": "^1.11.1", "@0x/protocol-utils": "^1.11.1",
"@0x/quote-server": "^6.0.6", "@0x/quote-server": "^6.0.6",
"@0x/types": "^3.3.4", "@0x/types": "^3.3.4",

View File

@@ -500,6 +500,7 @@ export const MAINNET_TOKENS = {
OUSD: '0x2a8e1e676ec238d8a992307b495b45b3feaa5e86', OUSD: '0x2a8e1e676ec238d8a992307b495b45b3feaa5e86',
agEUR: '0x1a7e4e63778b4f12a199c062f3efdd288afcbce8', agEUR: '0x1a7e4e63778b4f12a199c062f3efdd288afcbce8',
ibEUR: '0x96e61422b6a9ba0e068b6c5add4ffabc6a4aae27', ibEUR: '0x96e61422b6a9ba0e068b6c5add4ffabc6a4aae27',
YFI: '0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e',
}; };
export const BSC_TOKENS = { export const BSC_TOKENS = {
@@ -646,6 +647,7 @@ export const CURVE_POOLS = {
USDP: '0x42d7025938bec20b69cbae5a77421082407f053a', // usdp USDP: '0x42d7025938bec20b69cbae5a77421082407f053a', // usdp
ib: '0x2dded6da1bf5dbdf597c45fcfaa3194e53ecfeaf', // iron bank ib: '0x2dded6da1bf5dbdf597c45fcfaa3194e53ecfeaf', // iron bank
link: '0xf178c0b5bb7e7abf4e12a4838c7b7c5ba2c623c0', // link link: '0xf178c0b5bb7e7abf4e12a4838c7b7c5ba2c623c0', // link
btrflyweth: '0xf43b15ab692fde1f9c24a9fce700adcc809d5391', // redacted cartel
// StableSwap "open pools" (crv.finance) // StableSwap "open pools" (crv.finance)
TUSD: '0xecd5e75afb02efa118af914515d6521aabd189f1', TUSD: '0xecd5e75afb02efa118af914515d6521aabd189f1',
STABLEx: '0x3252efd4ea2d6c78091a1f43982ee2c3659cc3d1', STABLEx: '0x3252efd4ea2d6c78091a1f43982ee2c3659cc3d1',
@@ -668,6 +670,7 @@ export const CURVE_POOLS = {
d3pool: '0xbaaa1f5dba42c3389bdbc2c9d2de134f5cd0dc89', d3pool: '0xbaaa1f5dba42c3389bdbc2c9d2de134f5cd0dc89',
triEURpool: '0xb9446c4ef5ebe66268da6700d26f96273de3d571', triEURpool: '0xb9446c4ef5ebe66268da6700d26f96273de3d571',
ibEURsEUR: '0x19b080fe1ffa0553469d20ca36219f17fcf03859', ibEURsEUR: '0x19b080fe1ffa0553469d20ca36219f17fcf03859',
wethyfi: '0xc26b89a667578ec7b3f11b2f98d6fd15c07c54ba',
}; };
export const CURVE_V2_POOLS = { export const CURVE_V2_POOLS = {
@@ -1019,6 +1022,16 @@ const createCurveV2MetaTriPool = (info: { tokens: string[]; pool: string; gasSch
gasSchedule: info.gasSchedule, gasSchedule: info.gasSchedule,
}); });
const createCurveFactoryCryptoExchangePool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({
exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying_uint256,
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_uint256,
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
tokens: info.tokens,
metaTokens: undefined,
poolAddress: info.pool,
gasSchedule: info.gasSchedule,
});
/** /**
* Mainnet Curve configuration * Mainnet Curve configuration
* The tokens are in order of their index, which each curve defines * The tokens are in order of their index, which each curve defines
@@ -1285,6 +1298,16 @@ export const CURVE_MAINNET_INFOS: { [name: string]: CurveInfo } = {
pool: CURVE_POOLS.ibEURsEUR, pool: CURVE_POOLS.ibEURsEUR,
gasSchedule: 176e3, gasSchedule: 176e3,
}), }),
[CURVE_POOLS.btrflyweth]: createCurveFactoryCryptoExchangePool({
tokens: [MAINNET_TOKENS.WETH, MAINNET_TOKENS.BTRFLY],
pool: CURVE_POOLS.btrflyweth,
gasSchedule: 250e3,
}),
[CURVE_POOLS.wethyfi]: createCurveFactoryCryptoExchangePool({
tokens: [MAINNET_TOKENS.WETH, MAINNET_TOKENS.YFI],
pool: CURVE_POOLS.wethyfi,
gasSchedule: 250e3,
}),
}; };
export const CURVE_V2_MAINNET_INFOS: { [name: string]: CurveInfo } = { export const CURVE_V2_MAINNET_INFOS: { [name: string]: CurveInfo } = {
@@ -2072,7 +2095,7 @@ export const UNISWAPV3_CONFIG_BY_CHAIN_ID = valueByChainId(
}, },
[ChainId.Ropsten]: { [ChainId.Ropsten]: {
quoter: '0x61ffe014ba17989e743c5f6cb21bf9697530b21e', quoter: '0x61ffe014ba17989e743c5f6cb21bf9697530b21e',
router: '0x03782388516e94fcd4c18666303601a12aa729ea', router: '0xe592427a0aece92de3edee1f18e0157c05861564',
}, },
[ChainId.Polygon]: { [ChainId.Polygon]: {
quoter: '0x61ffe014ba17989e743c5f6cb21bf9697530b21e', quoter: '0x61ffe014ba17989e743c5f6cb21bf9697530b21e',

View File

@@ -175,9 +175,9 @@ export function dexSamplesToFills(
const { source, fillData } = sample; const { source, fillData } = sample;
const input = sample.input.minus(prevSample ? prevSample.input : 0); const input = sample.input.minus(prevSample ? prevSample.input : 0);
const output = sample.output.minus(prevSample ? prevSample.output : 0); const output = sample.output.minus(prevSample ? prevSample.output : 0);
const fee = fees[source] === undefined ? 0 : fees[source]!(sample.fillData) || 0;
let penalty = ZERO_AMOUNT; let penalty = ZERO_AMOUNT;
if (i === 0) { 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. // Only the first fill in a DEX path incurs a penalty.
penalty = ethToOutputAmount({ penalty = ethToOutputAmount({
input, input,

View File

@@ -42,7 +42,7 @@ import { createFills } from './fills';
import { getBestTwoHopQuote } from './multihop_utils'; import { getBestTwoHopQuote } from './multihop_utils';
import { createOrdersFromTwoHopSample } from './orders'; import { createOrdersFromTwoHopSample } from './orders';
import { Path, PathPenaltyOpts } from './path'; import { Path, PathPenaltyOpts } from './path';
import { fillsToSortedPaths, findOptimalPathJSAsync, findOptimalRustPathFromSamples } from './path_optimizer'; import { findOptimalPathJSAsync, findOptimalRustPathFromSamples } from './path_optimizer';
import { DexOrderSampler, getSampleAmounts } from './sampler'; import { DexOrderSampler, getSampleAmounts } from './sampler';
import { SourceFilters } from './source_filters'; import { SourceFilters } from './source_filters';
import { import {
@@ -493,18 +493,6 @@ export class MarketOperationUtils {
} as NativeOrderWithFillableAmounts), } as NativeOrderWithFillableAmounts),
); );
// Convert native orders and dex quotes into `Fill` objects.
const fills = createFills({
side,
orders: [...nativeOrders, ...augmentedRfqtIndicativeQuotes],
dexQuotes,
targetInput: inputAmount,
outputAmountPerEth,
inputAmountPerEth,
excludedSources: opts.excludedSources,
feeSchedule: opts.feeSchedule,
});
// Find the optimal path. // Find the optimal path.
const penaltyOpts: PathPenaltyOpts = { const penaltyOpts: PathPenaltyOpts = {
outputAmountPerEth, outputAmountPerEth,
@@ -517,13 +505,11 @@ export class MarketOperationUtils {
const takerAmountPerEth = side === MarketOperation.Sell ? inputAmountPerEth : outputAmountPerEth; const takerAmountPerEth = side === MarketOperation.Sell ? inputAmountPerEth : outputAmountPerEth;
const makerAmountPerEth = side === MarketOperation.Sell ? outputAmountPerEth : inputAmountPerEth; const makerAmountPerEth = side === MarketOperation.Sell ? outputAmountPerEth : inputAmountPerEth;
// Find the unoptimized best rate to calculate savings from optimizer let fills: Fill[][];
const _unoptimizedPath = fillsToSortedPaths(fills, side, inputAmount, penaltyOpts)[0];
const unoptimizedPath = _unoptimizedPath ? _unoptimizedPath.collapse(orderOpts) : undefined;
// Find the optimal path using Rust router if enabled, otherwise fallback to JS Router // Find the optimal path using Rust router if enabled, otherwise fallback to JS Router
let optimalPath: Path | undefined; let optimalPath: Path | undefined;
if (SHOULD_USE_RUST_ROUTER) { if (SHOULD_USE_RUST_ROUTER) {
fills = [[]];
optimalPath = findOptimalRustPathFromSamples( optimalPath = findOptimalRustPathFromSamples(
side, side,
dexQuotes, dexQuotes,
@@ -536,6 +522,18 @@ export class MarketOperationUtils {
opts.samplerMetrics, opts.samplerMetrics,
); );
} else { } 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 = await findOptimalPathJSAsync( optimalPath = await findOptimalPathJSAsync(
side, side,
fills, fills,
@@ -561,7 +559,6 @@ export class MarketOperationUtils {
sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop], sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop],
marketSideLiquidity, marketSideLiquidity,
adjustedRate: bestTwoHopRate, adjustedRate: bestTwoHopRate,
unoptimizedPath,
takerAmountPerEth, takerAmountPerEth,
makerAmountPerEth, makerAmountPerEth,
}; };
@@ -573,7 +570,10 @@ export class MarketOperationUtils {
} }
// Generate a fallback path if required // Generate a fallback path if required
await this._addOptionalFallbackAsync(side, inputAmount, optimalPath, dexQuotes, fills, opts, penaltyOpts); // 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 collapsedPath = optimalPath.collapse(orderOpts);
return { return {
@@ -582,7 +582,6 @@ export class MarketOperationUtils {
sourceFlags: collapsedPath.sourceFlags, sourceFlags: collapsedPath.sourceFlags,
marketSideLiquidity, marketSideLiquidity,
adjustedRate: optimalPathRate, adjustedRate: optimalPathRate,
unoptimizedPath,
takerAmountPerEth, takerAmountPerEth,
makerAmountPerEth, makerAmountPerEth,
}; };
@@ -778,6 +777,8 @@ 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 // tslint:disable-next-line: prefer-function-over-method
private async _addOptionalFallbackAsync( private async _addOptionalFallbackAsync(
side: MarketOperation, side: MarketOperation,
@@ -843,6 +844,7 @@ export class MarketOperationUtils {
} }
} }
} }
*/
} }
// tslint:disable: max-file-line-count // tslint:disable: max-file-line-count

View File

@@ -122,17 +122,8 @@ export class Path {
++i; ++i;
continue; continue;
} }
// If there are contiguous bridge orders, we can batch them together.
// TODO jacob pretty sure this is from DFB and we can remove
const contiguousBridgeFills = [collapsedFills[i]];
for (let j = i + 1; j < collapsedFills.length; ++j) {
if (collapsedFills[j].source === ERC20BridgeSource.Native) {
break;
}
contiguousBridgeFills.push(collapsedFills[j]);
}
this.orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts.side)); this.orders.push(createBridgeOrder(collapsedFills[i], makerToken, takerToken, opts.side));
i += 1; i += 1;
} }
return this as CollapsedPath; return this as CollapsedPath;

View File

@@ -76,7 +76,8 @@ function findRoutesAndCreateOptimalPath(
opts: PathPenaltyOpts, opts: PathPenaltyOpts,
fees: FeeSchedule, fees: FeeSchedule,
neonRouterNumSamples: number, neonRouterNumSamples: number,
): Path | undefined { vipSourcesSet: Set<ERC20BridgeSource>,
): { allSourcesPath: Path | undefined; vipSourcesPath: Path | undefined } | undefined {
// Currently the rust router is unable to handle 1 base unit sized quotes and will error out // 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 // To avoid flooding the logs with these errors we just return an insufficient liquidity error
// which is how the JS router handles these quotes today // which is how the JS router handles these quotes today
@@ -94,6 +95,127 @@ function findRoutesAndCreateOptimalPath(
return fills[0]; return fills[0];
}; };
const createPathFromStrategy = (sourcesRustRoute: Float64Array, sourcesOutputAmounts: Float64Array) => {
const routesAndSamplesAndOutputs = _.zip(
sourcesRustRoute,
samplesAndNativeOrdersWithResults,
sourcesOutputAmounts,
sampleSourcePathIds,
);
const adjustedFills: Fill[] = [];
const totalRoutedAmount = BigNumber.sum(...sourcesRustRoute);
const scale = input.dividedBy(totalRoutedAmount);
for (const [
routeInput,
routeSamplesAndNativeOrders,
outputAmount,
sourcePathId,
] of routesAndSamplesAndOutputs) {
if (!Number.isFinite(outputAmount)) {
DEFAULT_WARNING_LOGGER(rustArgs, `neon-router: invalid route outputAmount ${outputAmount}`);
return undefined;
}
if (!routeInput || !routeSamplesAndNativeOrders || !outputAmount) {
continue;
}
// TODO(kimpers): [TKR-241] amounts are sometimes clipped in the router due to precision loss for number/f64
// we can work around it by scaling it and rounding up. However now we end up with a total amount of a couple base units too much
const rustInputAdjusted = BigNumber.min(
new BigNumber(routeInput).multipliedBy(scale).integerValue(BigNumber.ROUND_CEIL),
input,
);
const current = routeSamplesAndNativeOrders[routeSamplesAndNativeOrders.length - 1];
if (!isDexSample(current)) {
const nativeFill = nativeOrdersToFills(
side,
[current],
rustInputAdjusted,
opts.outputAmountPerEth,
opts.inputAmountPerEth,
fees,
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`
if (nativeFill) {
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
adjustedFills.push({ ...nativeFill, sourcePathId: sourcePathId ?? hexUtils.random() });
}
continue;
}
// NOTE: For DexSamples only
let fill = createFill(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
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]) ?? fill;
}
if (rustInputAdjusted.isGreaterThan(routeSamples[k].input)) {
const left = routeSamples[k];
const right = routeSamples[k + 1];
if (left && right) {
fill =
createFill({
...right, // default to the greater (for gas used)
input: rustInputAdjusted,
output: new BigNumber(outputAmount),
}) ?? fill;
} else {
assert.assert(Boolean(left || right), 'No valid sample to use');
fill = createFill(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));
// 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;
return BigNumber.max(capped, 1);
};
adjustedFills.push({
...fill,
input: rustInputAdjusted,
output: scaleOutput(fill.output),
adjustedOutput: scaleOutput(fill.adjustedOutput),
index: 0,
parent: undefined,
sourcePathId: sourcePathId ?? hexUtils.random(),
});
}
if (adjustedFills.length === 0) {
return undefined;
}
const pathFromRustInputs = Path.create(side, adjustedFills, input, opts);
return pathFromRustInputs;
};
const samplesAndNativeOrdersWithResults: Array<DexSample[] | NativeOrderWithFillableAmounts[]> = []; const samplesAndNativeOrdersWithResults: Array<DexSample[] | NativeOrderWithFillableAmounts[]> = [];
const serializedPaths: SerializedPath[] = []; const serializedPaths: SerializedPath[] = [];
const sampleSourcePathIds: string[] = []; const sampleSourcePathIds: string[] = [];
@@ -136,6 +258,7 @@ function findRoutesAndCreateOptimalPath(
inputs: [], inputs: [],
outputs: [], outputs: [],
outputFees: [], outputFees: [],
isVip: vipSourcesSet.has(singleSourceSamplesWithOutput[0]?.source),
}, },
); );
@@ -194,6 +317,7 @@ function findRoutesAndCreateOptimalPath(
inputs, inputs,
outputs, outputs,
outputFees, outputFees,
isVip: true,
}; };
samplesAndNativeOrdersWithResults.push([nativeOrder]); samplesAndNativeOrdersWithResults.push([nativeOrder]);
@@ -212,129 +336,42 @@ function findRoutesAndCreateOptimalPath(
}; };
const allSourcesRustRoute = new Float64Array(rustArgs.pathsIn.length); const allSourcesRustRoute = new Float64Array(rustArgs.pathsIn.length);
const strategySourcesOutputAmounts = new Float64Array(rustArgs.pathsIn.length); const allSourcesOutputAmounts = new Float64Array(rustArgs.pathsIn.length);
route(rustArgs, allSourcesRustRoute, strategySourcesOutputAmounts, neonRouterNumSamples); const vipSourcesRustRoute = new Float64Array(rustArgs.pathsIn.length);
const vipSourcesOutputAmounts = new Float64Array(rustArgs.pathsIn.length);
route(
rustArgs,
allSourcesRustRoute,
allSourcesOutputAmounts,
vipSourcesRustRoute,
vipSourcesOutputAmounts,
neonRouterNumSamples,
);
assert.assert( assert.assert(
rustArgs.pathsIn.length === allSourcesRustRoute.length, rustArgs.pathsIn.length === allSourcesRustRoute.length,
'different number of sources in the Router output than the input', 'different number of sources in the Router output than the input',
); );
assert.assert( assert.assert(
rustArgs.pathsIn.length === strategySourcesOutputAmounts.length, rustArgs.pathsIn.length === allSourcesOutputAmounts.length,
'different number of sources in the Router output amounts results than the input',
);
assert.assert(
rustArgs.pathsIn.length === vipSourcesRustRoute.length,
'different number of sources in the Router output than the input',
);
assert.assert(
rustArgs.pathsIn.length === vipSourcesOutputAmounts.length,
'different number of sources in the Router output amounts results than the input', 'different number of sources in the Router output amounts results than the input',
); );
const routesAndSamplesAndOutputs = _.zip( const allSourcesPath = createPathFromStrategy(allSourcesRustRoute, allSourcesOutputAmounts);
allSourcesRustRoute, const vipSourcesPath = createPathFromStrategy(vipSourcesRustRoute, vipSourcesOutputAmounts);
samplesAndNativeOrdersWithResults,
strategySourcesOutputAmounts,
sampleSourcePathIds,
);
const adjustedFills: Fill[] = [];
const totalRoutedAmount = BigNumber.sum(...allSourcesRustRoute);
const scale = input.dividedBy(totalRoutedAmount); return {
for (const [routeInput, routeSamplesAndNativeOrders, outputAmount, sourcePathId] of routesAndSamplesAndOutputs) { allSourcesPath,
if (!Number.isFinite(outputAmount)) { vipSourcesPath,
DEFAULT_WARNING_LOGGER(rustArgs, `neon-router: invalid route outputAmount ${outputAmount}`); };
return undefined;
}
if (!routeInput || !routeSamplesAndNativeOrders || !outputAmount) {
continue;
}
// TODO(kimpers): [TKR-241] amounts are sometimes clipped in the router due to precision loss for number/f64
// we can work around it by scaling it and rounding up. However now we end up with a total amount of a couple base units too much
const rustInputAdjusted = BigNumber.min(
new BigNumber(routeInput).multipliedBy(scale).integerValue(BigNumber.ROUND_CEIL),
input,
);
const current = routeSamplesAndNativeOrders[routeSamplesAndNativeOrders.length - 1];
if (!isDexSample(current)) {
const nativeFill = nativeOrdersToFills(
side,
[current],
rustInputAdjusted,
opts.outputAmountPerEth,
opts.inputAmountPerEth,
fees,
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`
if (nativeFill) {
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
adjustedFills.push({ ...nativeFill, sourcePathId: sourcePathId ?? hexUtils.random() });
}
continue;
}
// NOTE: For DexSamples only
let fill = createFill(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
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]) ?? fill;
}
if (rustInputAdjusted.isGreaterThan(routeSamples[k].input)) {
const left = routeSamples[k];
const right = routeSamples[k + 1];
if (left && right) {
fill =
createFill({
...right, // default to the greater (for gas used)
input: rustInputAdjusted,
output: new BigNumber(outputAmount),
}) ?? fill;
} else {
assert.assert(Boolean(left || right), 'No valid sample to use');
fill = createFill(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));
// Scale output by scale factor but never go above the largest sample (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);
return BigNumber.max(BigNumber.min(scaled, maxSampledOutput), 1);
};
adjustedFills.push({
...fill,
input: rustInputAdjusted,
output: scaleOutput(fill.output),
adjustedOutput: scaleOutput(fill.adjustedOutput),
index: 0,
parent: undefined,
sourcePathId: sourcePathId ?? hexUtils.random(),
});
}
if (adjustedFills.length === 0) {
return undefined;
}
const pathFromRustInputs = Path.create(side, adjustedFills, input, opts);
return pathFromRustInputs;
} }
export function findOptimalRustPathFromSamples( export function findOptimalRustPathFromSamples(
@@ -348,9 +385,18 @@ export function findOptimalRustPathFromSamples(
neonRouterNumSamples: number, neonRouterNumSamples: number,
samplerMetrics?: SamplerMetrics, samplerMetrics?: SamplerMetrics,
): Path | undefined { ): Path | undefined {
const beforeAllTimeMs = performance.now(); const beforeTimeMs = performance.now();
let beforeTimeMs = performance.now(); const sendMetrics = () => {
const allSourcesPath = findRoutesAndCreateOptimalPath( // tslint:disable-next-line: no-unused-expression
samplerMetrics &&
samplerMetrics.logRouterDetails({
router: 'neon-router',
type: 'total',
timingMs: performance.now() - beforeTimeMs,
});
};
const vipSourcesSet = new Set(VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID[chainId]);
const paths = findRoutesAndCreateOptimalPath(
side, side,
samples, samples,
nativeOrders, nativeOrders,
@@ -358,58 +404,22 @@ export function findOptimalRustPathFromSamples(
opts, opts,
fees, fees,
neonRouterNumSamples, neonRouterNumSamples,
vipSourcesSet,
); );
// tslint:disable-next-line: no-unused-expression
samplerMetrics && if (!paths) {
samplerMetrics.logRouterDetails({ sendMetrics();
router: 'neon-router',
type: 'all',
timingMs: performance.now() - beforeTimeMs,
});
if (!allSourcesPath) {
return undefined; return undefined;
} }
const vipSources = VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID[chainId]; const { allSourcesPath, vipSourcesPath } = paths;
// HACK(kimpers): The Rust router currently doesn't account for VIP sources correctly if (!allSourcesPath || vipSourcesPath?.isBetterThan(allSourcesPath)) {
// we need to try to route them in isolation and compare with the results all sources sendMetrics();
if (vipSources.length > 0) { return vipSourcesPath;
beforeTimeMs = performance.now();
const vipSourcesSet = new Set(vipSources);
const vipSourcesSamples = samples.filter(s => s[0] && vipSourcesSet.has(s[0].source));
if (vipSourcesSamples.length > 0) {
const vipSourcesPath = findRoutesAndCreateOptimalPath(
side,
vipSourcesSamples,
[],
input,
opts,
fees,
neonRouterNumSamples,
);
// tslint:disable-next-line: no-unused-expression
samplerMetrics &&
samplerMetrics.logRouterDetails({
router: 'neon-router',
type: 'vip',
timingMs: performance.now() - beforeTimeMs,
});
if (vipSourcesPath?.isBetterThan(allSourcesPath)) {
return vipSourcesPath;
}
}
} }
// tslint:disable-next-line: no-unused-expression
samplerMetrics &&
samplerMetrics.logRouterDetails({
router: 'neon-router',
type: 'total',
timingMs: performance.now() - beforeAllTimeMs,
});
sendMetrics();
return allSourcesPath; return allSourcesPath;
} }

View File

@@ -10,7 +10,6 @@ import { NativeOrderWithFillableAmounts, RfqFirmQuoteValidator, RfqRequestOpts }
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from '../../utils/quote_requestor'; import { QuoteRequestor, V4RFQIndicativeQuoteMM } from '../../utils/quote_requestor';
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from '../quote_report_generator'; import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
import { CollapsedPath } from './path';
import { SourceFilters } from './source_filters'; import { SourceFilters } from './source_filters';
/** /**
@@ -579,7 +578,6 @@ export interface OptimizerResult {
liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>; liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
marketSideLiquidity: MarketSideLiquidity; marketSideLiquidity: MarketSideLiquidity;
adjustedRate: BigNumber; adjustedRate: BigNumber;
unoptimizedPath?: CollapsedPath;
takerAmountPerEth: BigNumber; takerAmountPerEth: BigNumber;
makerAmountPerEth: BigNumber; makerAmountPerEth: BigNumber;
} }

View File

@@ -952,9 +952,10 @@
typedoc "~0.16.11" typedoc "~0.16.11"
yargs "^10.0.3" yargs "^10.0.3"
"@0x/neon-router@^0.3.3": "@0x/neon-router@^0.3.5":
version "0.3.3" version "0.3.5"
resolved "https://registry.yarnpkg.com/@0x/neon-router/-/neon-router-0.3.3.tgz#dab540f4cd2aea6441ba29cbc35c28ca3f7a2b4f" resolved "https://registry.yarnpkg.com/@0x/neon-router/-/neon-router-0.3.5.tgz#895e7a2dc65d492a413daaea283cbc0ca6df83fa"
integrity sha512-8wizP3smc5o4jVg1smZzCCFo4ohOrgDhO4JFjF+/oNHbFImlGHOvmH9HQ2FJXAXiLEOTxrbp3T5XxP5GNATq3w==
dependencies: dependencies:
"@mapbox/node-pre-gyp" "^1.0.5" "@mapbox/node-pre-gyp" "^1.0.5"