Compare commits

..

19 Commits

Author SHA1 Message Date
Github Actions
0063e8178f Publish
- @0x/asset-swapper@16.47.0
2022-01-25 18:51:07 +00:00
Github Actions
ec6e5dd517 Updated CHANGELOGS & MD docs 2022-01-25 18:51:04 +00:00
Noah Khamliche
9d08fefa1c Feat/synapse (#400)
* added synapse support
2022-01-25 13:21:07 -05:00
Github Actions
2fdca24d4e Publish
- @0x/asset-swapper@16.46.0
2022-01-11 01:10:02 +00:00
Github Actions
42ec0b144e Updated CHANGELOGS & MD docs 2022-01-11 01:09:58 +00:00
Jacob Evans
3f6ce78b46 chore: Enable Curve ETH/CVX (#394)
* chore: Enable Curve ETH/CVX

* pr number
2022-01-11 09:32:07 +10:00
Github Actions
c1300c1068 Publish
- @0x/asset-swapper@16.45.2
2022-01-10 15:09:26 +00:00
Github Actions
9a641cfab6 Updated CHANGELOGS & MD docs 2022-01-10 15:09:23 +00:00
Kim Persson
60345d4465 fix: don't create fills for 0 output samples and negative adjusted rate orders (#387)
* fix: don't try to create fills for 0 output samples

* fix: negative adjusted output native orders causing undefined fills

* fix: make sure to use the same sourcePathId for fills from same source

* fix: should be same sourcePathId within the same DexSample[]

* fix: split native orders into 13 samples to align with interpolation

* chore: add changelog entry for asset-swapper
2022-01-10 14:55:03 +01:00
Github Actions
11dfea47a6 Publish
- @0x/asset-swapper@16.45.1
2022-01-05 05:08:43 +00:00
Github Actions
55e9dd39a2 Updated CHANGELOGS & MD docs 2022-01-05 05:08:41 +00:00
Jacob Evans
1993929bed chore: Celo Update certain tokens since Optics v2 (#390)
* chore: Celo Update certain tokens since Optics v2

* Changelog
2022-01-05 14:44:29 +10:00
Oskar Paolini
e1d81de517 fixes axios object dumping in logs (#345) 2022-01-05 09:46:01 +10:00
Github Actions
a6b3a21635 Publish
- @0x/asset-swapper@16.45.0
2022-01-04 15:00:16 +00:00
Github Actions
fd59cdc2db Updated CHANGELOGS & MD docs 2022-01-04 15:00:13 +00:00
Jacob Evans
98e11b5189 feat: Capture Routing timing metrics (#388) 2022-01-04 15:42:14 +01:00
Github Actions
3bebc7cd62 Publish
- @0x/asset-swapper@16.44.0
2021-12-29 11:45:33 +00:00
Github Actions
56dab6ae8c Updated CHANGELOGS & MD docs 2021-12-29 11:45:30 +00:00
Kim Persson
285f98e9e9 feat: Udate neon-router and use router estimated output amount (#354)
* feat: use Rust router estimated output amount when possible

* fix: use strings for sample ids, and increase samples in the rust router

* fix: remove unnecessary interpolation of out of range values

* fix: don't recalculate sampled dist sum in a loop

* fix: use 14 samples for rust router to work around interpolation issues

* fix: unintentional logic change

* fix: remove local dev plotting param from route fn call

* feat: make neon-router number of samples configurable

* chore: bump to newly published neon-router version

* fix: handle insufficient liquidity at all requested sources

* chore: update asset-swapper changelog
2021-12-29 12:08:24 +01:00
13 changed files with 545 additions and 119 deletions

View File

@@ -1,4 +1,64 @@
[
{
"version": "16.47.0",
"changes": [
{
"note": "Adding support for Synapse on all networks",
"pr": 400
}
],
"timestamp": 1643136662
},
{
"version": "16.46.0",
"changes": [
{
"note": "Enable `Curve` ETH/CVX pool",
"pr": 394
}
],
"timestamp": 1641863395
},
{
"version": "16.45.2",
"changes": [
{
"note": "Handle 0 output samples and negative adjusted rate native orders in routing",
"pr": 387
}
],
"timestamp": 1641827361
},
{
"version": "16.45.1",
"changes": [
{
"note": "Update `Celo` intermediate tokens",
"pr": 390
}
],
"timestamp": 1641359319
},
{
"version": "16.45.0",
"changes": [
{
"note": "Capture router timings",
"pr": 388
}
],
"timestamp": 1641308410
},
{
"version": "16.44.0",
"changes": [
{
"note": "Update neon-router and use router estimated output amount",
"pr": 354
}
],
"timestamp": 1640778328
},
{
"version": "16.43.0",
"changes": [

View File

@@ -5,6 +5,30 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v16.47.0 - _January 25, 2022_
* Adding support for Synapse on all networks (#400)
## v16.46.0 - _January 11, 2022_
* Enable `Curve` ETH/CVX pool (#394)
## v16.45.2 - _January 10, 2022_
* Handle 0 output samples and negative adjusted rate native orders in routing (#387)
## v16.45.1 - _January 5, 2022_
* Update `Celo` intermediate tokens (#390)
## v16.45.0 - _January 4, 2022_
* Capture router timings (#388)
## v16.44.0 - _December 29, 2021_
* Update neon-router and use router estimated output amount (#354)
## v16.43.0 - _December 24, 2021_
* `UniswapV3` support for `Optimism` (#385)

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/asset-swapper",
"version": "16.43.0",
"version": "16.47.0",
"engines": {
"node": ">=6.12"
},
@@ -66,7 +66,7 @@
"@0x/contracts-zero-ex": "^0.30.1",
"@0x/dev-utils": "^4.2.9",
"@0x/json-schemas": "^6.3.0",
"@0x/neon-router": "^0.2.1",
"@0x/neon-router": "^0.3.1",
"@0x/protocol-utils": "^1.10.1",
"@0x/quote-server": "^6.0.6",
"@0x/types": "^3.3.4",

View File

@@ -223,7 +223,17 @@ export async function returnQuoteFromAltMMAsync<ResponseT>(
cancelToken,
})
.catch(err => {
warningLogger(err, `Alt RFQ MM request failed`);
if (err.response) {
// request was made and market maker responded
warningLogger(
{ data: err.response.data, status: err.response.status, headers: err.response.headers },
`Alt RFQ MM request failed`,
);
} else if (err.request) {
warningLogger({}, 'Alt RFQ MM no response received');
} else {
warningLogger({ err: err.message }, 'Failed to construct Alt RFQ MM request');
}
throw new Error(`Alt RFQ MM request failed`);
});

View File

@@ -49,6 +49,12 @@ import {
SPOOKYSWAP_ROUTER_BY_CHAIN_ID,
SUSHISWAP_ROUTER_BY_CHAIN_ID,
SWERVE_MAINNET_INFOS,
SYNAPSE_AVALANCHE_INFOS,
SYNAPSE_BSC_INFOS,
SYNAPSE_FANTOM_INFOS,
SYNAPSE_MAINNET_INFOS,
SYNAPSE_OPTIMISM_INFOS,
SYNAPSE_POLYGON_INFOS,
TRADER_JOE_ROUTER_BY_CHAIN_ID,
UBESWAP_ROUTER_BY_CHAIN_ID,
UNISWAPV2_ROUTER_BY_CHAIN_ID,
@@ -247,6 +253,67 @@ export function getNerveInfosForPair(chainId: ChainId, takerToken: string, maker
);
}
export function getSynapseInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
switch (chainId) {
case ChainId.Mainnet:
return Object.values(SYNAPSE_MAINNET_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
case ChainId.Optimism:
return Object.values(SYNAPSE_OPTIMISM_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
case ChainId.BSC:
return Object.values(SYNAPSE_BSC_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
case ChainId.Polygon:
return Object.values(SYNAPSE_POLYGON_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
case ChainId.Fantom:
return Object.values(SYNAPSE_FANTOM_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
case ChainId.Avalanche:
return Object.values(SYNAPSE_AVALANCHE_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
default:
return [];
}
}
export function getFirebirdOneSwapInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId === ChainId.BSC) {
return Object.values(FIREBIRDONESWAP_BSC_INFOS).filter(c =>
@@ -406,6 +473,7 @@ export function getCurveLikeInfosForPair(
| ERC20BridgeSource.Swerve
| ERC20BridgeSource.SnowSwap
| ERC20BridgeSource.Nerve
| ERC20BridgeSource.Synapse
| ERC20BridgeSource.Belt
| ERC20BridgeSource.Ellipsis
| ERC20BridgeSource.Smoothy
@@ -432,6 +500,9 @@ export function getCurveLikeInfosForPair(
case ERC20BridgeSource.Nerve:
pools = getNerveInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.Synapse:
pools = getSynapseInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.Belt:
pools = getBeltInfosForPair(chainId, takerToken, makerToken);
break;

View File

@@ -104,6 +104,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.UniswapV3,
ERC20BridgeSource.CurveV2,
ERC20BridgeSource.ShibaSwap,
ERC20BridgeSource.Synapse,
// TODO: enable after FQT has been redeployed on Ethereum mainnet
// ERC20BridgeSource.AaveV2,
// ERC20BridgeSource.Compound,
@@ -130,6 +131,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.Mooniswap,
ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Nerve,
ERC20BridgeSource.Synapse,
ERC20BridgeSource.PancakeSwap,
ERC20BridgeSource.PancakeSwapV2,
ERC20BridgeSource.SushiSwap,
@@ -167,6 +169,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.IronSwap,
ERC20BridgeSource.AaveV2,
ERC20BridgeSource.UniswapV3,
ERC20BridgeSource.Synapse,
]),
[ChainId.Avalanche]: new SourceFilters([
ERC20BridgeSource.MultiHop,
@@ -177,6 +180,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.CurveV2,
ERC20BridgeSource.KyberDmm,
ERC20BridgeSource.AaveV2,
ERC20BridgeSource.Synapse,
]),
[ChainId.Fantom]: new SourceFilters([
ERC20BridgeSource.MultiHop,
@@ -188,13 +192,18 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.SpiritSwap,
ERC20BridgeSource.SpookySwap,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.Synapse,
]),
[ChainId.Celo]: new SourceFilters([
ERC20BridgeSource.UbeSwap,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.MultiHop,
]),
[ChainId.Optimism]: new SourceFilters([ERC20BridgeSource.UniswapV3]),
[ChainId.Optimism]: new SourceFilters([
ERC20BridgeSource.UniswapV3,
ERC20BridgeSource.Synapse,
ERC20BridgeSource.MultiHop,
]),
},
new SourceFilters([]),
);
@@ -237,6 +246,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.UniswapV3,
ERC20BridgeSource.CurveV2,
ERC20BridgeSource.ShibaSwap,
ERC20BridgeSource.Synapse,
// TODO: enable after FQT has been redeployed on Ethereum mainnet
// ERC20BridgeSource.AaveV2,
// ERC20BridgeSource.Compound,
@@ -277,6 +287,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.JetSwap,
ERC20BridgeSource.ACryptos,
ERC20BridgeSource.KyberDmm,
ERC20BridgeSource.Synapse,
]),
[ChainId.Polygon]: new SourceFilters([
ERC20BridgeSource.SushiSwap,
@@ -300,6 +311,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.IronSwap,
ERC20BridgeSource.AaveV2,
ERC20BridgeSource.UniswapV3,
ERC20BridgeSource.Synapse,
]),
[ChainId.Avalanche]: new SourceFilters([
ERC20BridgeSource.MultiHop,
@@ -310,6 +322,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.CurveV2,
ERC20BridgeSource.KyberDmm,
ERC20BridgeSource.AaveV2,
ERC20BridgeSource.Synapse,
]),
[ChainId.Fantom]: new SourceFilters([
ERC20BridgeSource.MultiHop,
@@ -321,13 +334,18 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.SpiritSwap,
ERC20BridgeSource.SpookySwap,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.Synapse,
]),
[ChainId.Celo]: new SourceFilters([
ERC20BridgeSource.UbeSwap,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.MultiHop,
]),
[ChainId.Optimism]: new SourceFilters([ERC20BridgeSource.UniswapV3]),
[ChainId.Optimism]: new SourceFilters([
ERC20BridgeSource.UniswapV3,
ERC20BridgeSource.Synapse,
ERC20BridgeSource.MultiHop,
]),
},
new SourceFilters([]),
);
@@ -462,6 +480,9 @@ export const MAINNET_TOKENS = {
CRV: '0xd533a949740bb3306d119cc777fa900ba034cd52',
MIM: '0x99d8a9c45b2eca8864373a26d1459e3dff1e17f3',
EURT: '0xc581b735a1688071a1746c968e0798d642ede491',
// Synapse ecosystem
nUSD: '0x1b84765de8b7566e4ceaf4d0fd3c5af52d3dde4f',
CVX: '0x4e3fbd56cd56c3e72c1403e103b45db9da5b9d2b',
};
export const BSC_TOKENS = {
@@ -478,6 +499,7 @@ export const BSC_TOKENS = {
BTCB: '0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c',
renBTC: '0xfce146bf3146100cfe5db4129cf6c82b0ef4ad8c',
pBTC: '0xed28a457a5a76596ac48d87c0f577020f6ea1c4c',
nUSD: '0x23b891e5c62e0955ae2bd185990103928ab817b3',
};
export const POLYGON_TOKENS = {
@@ -495,6 +517,7 @@ export const POLYGON_TOKENS = {
DFYN: '0xc168e40227e4ebd8c1cae80f7a55a4f0e6d66c97',
BANANA: '0x5d47baba0d66083c52009271faf3f50dcc01023c',
WEXPOLY: '0x4c4bf319237d98a30a929a96112effa8da3510eb',
nUSD: '0xb6c473756050de474286bed418b77aeac39b02af',
};
export const AVALANCHE_TOKENS = {
@@ -507,12 +530,29 @@ export const AVALANCHE_TOKENS = {
aDAI: '0x47afa96cdc9fab46904a55a6ad4bf6660b53c38a',
aUSDC: '0x46a51127c3ce23fb7ab1de06226147f446e4a857',
aUSDT: '0x532e6537fea298397212f09a61e03311686f548e',
nETH: '0x19e1ae0ee35c0404f835521146206595d37981ae',
nUSD: '0xcfc37a6ab183dd4aed08c204d1c2773c0b1bdf46',
aWETH: '0x53f7c5869a859f0aec3d334ee8b4cf01e3492f21',
};
export const CELO_TOKENS = {
WETH: '0xe919f65739c26a42616b7b8eedc6b5524d1e3ac4',
WCELO: '0x471ece3750da237f93b8e339c536989b8978a438',
// Some of these tokens are Optics bridge? tokens which
// had an issue and migrated from v1 to v2
WETHv1: '0xe919f65739c26a42616b7b8eedc6b5524d1e3ac4',
WETH: '0x122013fd7df1c6f636a5bb8f03108e876548b455',
WBTC: '0xbaab46e28388d2779e6e31fd00cf0e5ad95e327b',
cUSD: '0x765de816845861e75a25fca122bb6898b8b1282a',
// ??
WBTCv1: '0xd629eb00deced2a080b7ec630ef6ac117e614f1b',
cETH: '0x2def4285787d58a2f811af24755a8150622f4361',
UBE: '0x00be915b9dcf56a3cbe739d9b9c202ca692409ec',
// Moolah
mCELO: '0x7d00cd74ff385c955ea3d79e47bf06bd7386387d',
mCUSD: '0x918146359264c492bd6934071c6bd31c854edbc3',
mCEUR: '0xe273ad7ee11dcfaa87383ad5977ee1504ac07568',
amCUSD: '0x64defa3544c695db8c535d289d843a189aa26b98',
MOO: '0x17700282592d6917f6a73d0bf8accf4d578c131e',
};
export const FANTOM_TOKENS = {
@@ -523,6 +563,9 @@ export const FANTOM_TOKENS = {
fUSDT: '0x049d68029688eabf473097a2fc38ef61633a3c7a',
WBTC: '0x321162cd933e2be498cd2267a90534a804051b11',
renBTC: '0xdbf31df14b66535af65aac99c32e9ea844e14501',
MIM: '0x82f0b8b456c1a451378467398982d4834b6829c1',
nUSD: '0xed2a7edd7413021d440b09d654f3b87712abab66',
nETH: '0x67c10c397dd0ba417329543c1a40eb48aaa7cd00',
};
export const OPTIMISM_TOKENS = {
@@ -531,6 +574,8 @@ export const OPTIMISM_TOKENS = {
USDT: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58',
DAI: '0xda10009cbd5d07dd0cecc66161fc93d7c9000da1',
WBTC: '0x68f180fcce6836688e9084f035309e29bf0a2095',
nETH: '0x809dc529f07651bd43a172e8db6f4a7a0d771036',
sWETH: '0x121ab82b49b2bc4c7901ca46b8277962b4350204',
};
export const CURVE_POOLS = {
@@ -578,6 +623,7 @@ export const CURVE_POOLS = {
mim: '0x5a6a4d54456819380173272a5e8e9b9904bdf41b',
eurt: '0xfd5db7463a3ab53fd211b4af195c5bccc1a03890',
ethcrv: '0x8301ae4fc9c624d1d396cbdaa1ed877821d7c511',
ethcvx: '0xb576491f1e6e5e62f1d8f26062ee822b40b0e0d4',
};
export const CURVE_V2_POOLS = {
@@ -647,6 +693,37 @@ export const NERVE_POOLS = {
threePool: '0x1b3771a66ee31180906972580ade9b81afc5fcdc',
};
export const SYNAPSE_MAINNET_POOLS = {
nUSDLP: '0x1116898dda4015ed8ddefb84b6e8bc24528af2d8',
};
export const SYNAPSE_OPTIMISM_POOLS = {
nETHLP: '0xe27bff97ce92c3e1ff7aa9f86781fdd6d48f5ee9',
};
export const SYNAPSE_BSC_POOLS = {
nUSDLP: '0x28ec0b36f0819ecb5005cab836f4ed5a2eca4d13',
};
export const SYNAPSE_POLYGON_POOLS = {
nUSDLP: '0x85fcd7dd0a1e1a9fcd5fd886ed522de8221c3ee5',
};
export const SYNAPSE_FANTOM_POOLS = {
nUSDLP: '0x2913e812cf0dcca30fb28e6cac3d2dcff4497688',
nETHLP: '0x8d9ba570d6cb60c7e3e0f31343efe75ab8e65fb1',
};
export const SYNAPSE_AVALANCHE_POOLS = {
nUSDLP: '0xed2a7edd7413021d440b09d654f3b87712abab66',
nETHLP: '0x77a7e60555bc18b4be44c181b2575eee46212d44',
};
export const SYNAPSE_ARBITRUM_POOLS = {
nUSDLP: '0x0db3fe3b770c95a0b99d1ed6f2627933466c0dd8',
nETHLP: '0xd70a52248e546a3b260849386410c7170c7bd1e9',
};
export const BELT_POOLS = {
vPool: '0xf16d312d119c13dd27fd0dc814b0bcdcaaa62dfd',
};
@@ -704,6 +781,7 @@ export const DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID = valueByChainId<string[]>(
POLYGON_TOKENS.DAI,
POLYGON_TOKENS.USDT,
POLYGON_TOKENS.WBTC,
POLYGON_TOKENS.nUSD,
],
[ChainId.Avalanche]: [
AVALANCHE_TOKENS.WAVAX,
@@ -711,10 +789,26 @@ export const DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID = valueByChainId<string[]>(
AVALANCHE_TOKENS.DAI,
AVALANCHE_TOKENS.USDT,
AVALANCHE_TOKENS.USDC,
AVALANCHE_TOKENS.nUSD,
AVALANCHE_TOKENS.nETH,
AVALANCHE_TOKENS.aWETH,
],
[ChainId.Fantom]: [
FANTOM_TOKENS.WFTM,
FANTOM_TOKENS.WETH,
FANTOM_TOKENS.DAI,
FANTOM_TOKENS.USDC,
FANTOM_TOKENS.nUSD,
FANTOM_TOKENS.nETH,
],
[ChainId.Celo]: [CELO_TOKENS.WCELO, CELO_TOKENS.mCUSD, CELO_TOKENS.WETH, CELO_TOKENS.amCUSD, CELO_TOKENS.WBTC],
[ChainId.Optimism]: [
OPTIMISM_TOKENS.WETH,
OPTIMISM_TOKENS.DAI,
OPTIMISM_TOKENS.USDC,
OPTIMISM_TOKENS.nETH,
OPTIMISM_TOKENS.sWETH,
],
[ChainId.Fantom]: [FANTOM_TOKENS.WFTM, FANTOM_TOKENS.WETH, FANTOM_TOKENS.DAI, FANTOM_TOKENS.USDC],
[ChainId.Celo]: [CELO_TOKENS.mCUSD, CELO_TOKENS.WETH, CELO_TOKENS.WCELO],
[ChainId.Optimism]: [OPTIMISM_TOKENS.WETH, OPTIMISM_TOKENS.DAI, OPTIMISM_TOKENS.USDC],
},
[],
);
@@ -747,7 +841,14 @@ export const DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID = valueByChainId<TokenAdj
}).build(),
[ChainId.Avalanche]: new TokenAdjacencyGraphBuilder({
default: DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID[ChainId.Avalanche],
}).build(),
})
.tap(builder => {
// Synape nETH/aWETH pool
builder
.add(AVALANCHE_TOKENS.aWETH, AVALANCHE_TOKENS.nETH)
.add(AVALANCHE_TOKENS.nETH, AVALANCHE_TOKENS.aWETH);
})
.build(),
[ChainId.Fantom]: new TokenAdjacencyGraphBuilder({
default: DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID[ChainId.Fantom],
}).build(),
@@ -1059,6 +1160,17 @@ export const CURVE_MAINNET_INFOS: { [name: string]: CurveInfo } = {
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_uint256,
exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying_uint256,
},
[CURVE_POOLS.ethcvx]: {
...createCurveExchangePool({
// This pool uses ETH
tokens: [MAINNET_TOKENS.WETH, MAINNET_TOKENS.CVX],
pool: CURVE_POOLS.ethcvx,
gasSchedule: 350e3,
}),
// This pool has a custom get_dy and exchange selector with uint256
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_uint256,
exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying_uint256,
},
};
export const CURVE_V2_MAINNET_INFOS: { [name: string]: CurveInfo } = {
@@ -1316,6 +1428,87 @@ export const NERVE_BSC_INFOS: { [name: string]: CurveInfo } = {
},
};
export const SYNAPSE_BSC_INFOS: { [name: string]: CurveInfo } = {
[SYNAPSE_BSC_POOLS.nUSDLP]: {
exchangeFunctionSelector: CurveFunctionSelectors.swap,
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
poolAddress: SYNAPSE_BSC_POOLS.nUSDLP,
tokens: [BSC_TOKENS.nUSD, BSC_TOKENS.BUSD, BSC_TOKENS.USDC, BSC_TOKENS.USDT],
metaTokens: undefined,
gasSchedule: 140e3,
},
};
export const SYNAPSE_FANTOM_INFOS: { [name: string]: CurveInfo } = {
[SYNAPSE_FANTOM_POOLS.nUSDLP]: {
exchangeFunctionSelector: CurveFunctionSelectors.swap,
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
poolAddress: SYNAPSE_FANTOM_POOLS.nUSDLP,
tokens: [FANTOM_TOKENS.nUSD, FANTOM_TOKENS.MIM, FANTOM_TOKENS.USDC, FANTOM_TOKENS.fUSDT],
metaTokens: undefined,
gasSchedule: 140e3,
},
};
export const SYNAPSE_MAINNET_INFOS: { [name: string]: CurveInfo } = {
[SYNAPSE_MAINNET_POOLS.nUSDLP]: {
exchangeFunctionSelector: CurveFunctionSelectors.swap,
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
poolAddress: SYNAPSE_MAINNET_POOLS.nUSDLP,
tokens: [MAINNET_TOKENS.DAI, MAINNET_TOKENS.USDC, MAINNET_TOKENS.USDT],
metaTokens: undefined,
gasSchedule: 140e3,
},
};
export const SYNAPSE_OPTIMISM_INFOS: { [name: string]: CurveInfo } = {
[SYNAPSE_OPTIMISM_POOLS.nETHLP]: {
exchangeFunctionSelector: CurveFunctionSelectors.swap,
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
poolAddress: SYNAPSE_OPTIMISM_POOLS.nETHLP,
tokens: [OPTIMISM_TOKENS.nETH, OPTIMISM_TOKENS.sWETH],
metaTokens: undefined,
gasSchedule: 140e3,
},
};
export const SYNAPSE_POLYGON_INFOS: { [name: string]: CurveInfo } = {
[SYNAPSE_POLYGON_POOLS.nUSDLP]: {
exchangeFunctionSelector: CurveFunctionSelectors.swap,
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
poolAddress: SYNAPSE_POLYGON_POOLS.nUSDLP,
tokens: [POLYGON_TOKENS.nUSD, POLYGON_TOKENS.DAI, POLYGON_TOKENS.USDC, POLYGON_TOKENS.USDT],
metaTokens: undefined,
gasSchedule: 140e3,
},
};
export const SYNAPSE_AVALANCHE_INFOS: { [name: string]: CurveInfo } = {
[SYNAPSE_AVALANCHE_POOLS.nUSDLP]: {
exchangeFunctionSelector: CurveFunctionSelectors.swap,
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
poolAddress: SYNAPSE_AVALANCHE_POOLS.nUSDLP,
tokens: [AVALANCHE_TOKENS.nUSD, AVALANCHE_TOKENS.DAI, AVALANCHE_TOKENS.USDC, AVALANCHE_TOKENS.USDT],
metaTokens: undefined,
gasSchedule: 140e3,
},
[SYNAPSE_AVALANCHE_POOLS.nETHLP]: {
exchangeFunctionSelector: CurveFunctionSelectors.swap,
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
poolAddress: SYNAPSE_AVALANCHE_POOLS.nETHLP,
tokens: [AVALANCHE_TOKENS.nETH, AVALANCHE_TOKENS.aWETH],
metaTokens: undefined,
gasSchedule: 140e3,
},
};
export const FIREBIRDONESWAP_BSC_INFOS: { [name: string]: CurveInfo } = {
[FIREBIRDONESWAP_BSC_POOLS.oneswap]: {
exchangeFunctionSelector: CurveFunctionSelectors.swap,
@@ -1955,6 +2148,7 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
[ERC20BridgeSource.Swerve]: fillData => (fillData as CurveFillData).pool.gasSchedule,
[ERC20BridgeSource.SnowSwap]: fillData => (fillData as CurveFillData).pool.gasSchedule,
[ERC20BridgeSource.Nerve]: fillData => (fillData as CurveFillData).pool.gasSchedule,
[ERC20BridgeSource.Synapse]: fillData => (fillData as CurveFillData).pool.gasSchedule,
[ERC20BridgeSource.Belt]: fillData => (fillData as CurveFillData).pool.gasSchedule,
[ERC20BridgeSource.Ellipsis]: fillData => (fillData as CurveFillData).pool.gasSchedule,
[ERC20BridgeSource.Smoothy]: fillData => (fillData as CurveFillData).pool.gasSchedule,
@@ -2103,4 +2297,5 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: Omit<GetMarketOrdersOpts, 'gasPrice
shouldGenerateQuoteReport: true,
shouldIncludePriceComparisonsReport: false,
tokenAdjacencyGraph: { default: [] },
neonRouterNumSamples: 14,
};

View File

@@ -443,6 +443,7 @@ export class MarketOperationUtils {
feeSchedule: _opts.feeSchedule,
allowFallback: _opts.allowFallback,
gasPrice: _opts.gasPrice,
neonRouterNumSamples: _opts.neonRouterNumSamples,
},
);
return optimizerResult;
@@ -531,9 +532,18 @@ export class MarketOperationUtils {
penaltyOpts,
opts.feeSchedule,
this._sampler.chainId,
opts.neonRouterNumSamples,
opts.samplerMetrics,
);
} else {
optimalPath = await findOptimalPathJSAsync(side, fills, inputAmount, opts.runLimit, penaltyOpts);
optimalPath = await findOptimalPathJSAsync(
side,
fills,
inputAmount,
opts.runLimit,
opts.samplerMetrics,
penaltyOpts,
);
}
const optimalPathRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
@@ -596,6 +606,8 @@ export class MarketOperationUtils {
allowFallback: _opts.allowFallback,
exchangeProxyOverhead: _opts.exchangeProxyOverhead,
gasPrice: _opts.gasPrice,
neonRouterNumSamples: _opts.neonRouterNumSamples,
samplerMetrics: _opts.samplerMetrics,
};
if (nativeOrders.length === 0) {
@@ -806,6 +818,8 @@ export class MarketOperationUtils {
sturdyPenaltyOpts,
opts.feeSchedule,
this._sampler.chainId,
opts.neonRouterNumSamples,
undefined, // hack: set sampler metrics to undefined to avoid fallback timings
);
} else {
const sturdyFills = fills.filter(p => p.length > 0 && !fragileSources.includes(p[0].source));
@@ -814,6 +828,7 @@ export class MarketOperationUtils {
sturdyFills,
inputAmount,
opts.runLimit,
undefined, // hack: set sampler metrics to undefined to avoid fallback timings
sturdyPenaltyOpts,
);
}

View File

@@ -134,6 +134,8 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'BakerySwap');
case ERC20BridgeSource.Nerve:
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'Nerve');
case ERC20BridgeSource.Synapse:
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'Synapse');
case ERC20BridgeSource.Belt:
return encodeBridgeSourceId(BridgeProtocol.Curve, 'Belt');
case ERC20BridgeSource.Ellipsis:
@@ -226,6 +228,7 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
case ERC20BridgeSource.Swerve:
case ERC20BridgeSource.SnowSwap:
case ERC20BridgeSource.Nerve:
case ERC20BridgeSource.Synapse:
case ERC20BridgeSource.Belt:
case ERC20BridgeSource.Ellipsis:
case ERC20BridgeSource.Smoothy:
@@ -463,6 +466,7 @@ export const BRIDGE_ENCODERS: {
[ERC20BridgeSource.Swerve]: curveEncoder,
[ERC20BridgeSource.SnowSwap]: curveEncoder,
[ERC20BridgeSource.Nerve]: curveEncoder,
[ERC20BridgeSource.Synapse]: curveEncoder,
[ERC20BridgeSource.Belt]: curveEncoder,
[ERC20BridgeSource.Ellipsis]: curveEncoder,
[ERC20BridgeSource.Smoothy]: curveEncoder,

View File

@@ -1,23 +1,21 @@
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 { BigNumber, hexUtils } from '@0x/utils';
import * as _ from 'lodash';
import { performance } from 'perf_hooks';
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 { getRate } from './rate_utils';
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillData } from './types';
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillData, SamplerMetrics } from './types';
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
const RUN_LIMIT_DECAY_FACTOR = 0.5;
const 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;
@@ -69,21 +67,6 @@ function calculateOuputFee(
}
}
// 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[][],
@@ -91,29 +74,27 @@ function findRoutesAndCreateOptimalPath(
input: BigNumber,
opts: PathPenaltyOpts,
fees: FeeSchedule,
neonRouterNumSamples: number,
): 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 createFill = (sample: DexSample): Fill | undefined => {
const fills = dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, fees);
// NOTE: If the sample has 0 output dexSamplesToFills will return [] because no fill can be created
if (fills.length === 0) {
return undefined;
}
return fills[0];
};
const samplesAndNativeOrdersWithResults: Array<DexSample[] | NativeOrderWithFillableAmounts[]> = [];
const serializedPaths: SerializedPath[] = [];
const sampleSourcePathIds: string[] = [];
for (const singleSourceSamples of samples) {
if (singleSourceSamples.length === 0) {
continue;
}
const sourcePathId = hexUtils.random();
const singleSourceSamplesWithOutput = [...singleSourceSamples];
for (let i = singleSourceSamples.length - 1; i >= 0; i--) {
if (singleSourceSamples[i].output.isZero()) {
@@ -131,7 +112,7 @@ function findRoutesAndCreateOptimalPath(
// 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.ids.push(`${sample.source}-${serializedPaths.length}-${sampleIdx}`);
memo.inputs.push(sample.input.integerValue().toNumber());
memo.outputs.push(sample.output.integerValue().toNumber());
memo.outputFees.push(
@@ -152,8 +133,10 @@ function findRoutesAndCreateOptimalPath(
samplesAndNativeOrdersWithResults.push(singleSourceSamplesWithOutput);
serializedPaths.push(serializedPath);
sampleSourcePathIds.push(sourcePathId);
}
const nativeOrdersourcePathId = hexUtils.random();
for (const [idx, nativeOrder] of nativeOrders.entries()) {
const { input: normalizedOrderInput, output: normalizedOrderOutput } = nativeOrderToNormalizedAmounts(
side,
@@ -164,32 +147,25 @@ function findRoutesAndCreateOptimalPath(
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];
// HACK: due to an issue with the Rust router interpolation we need to create exactly 13 samples from the native order
const ids = [];
const inputs = [];
const outputs = [];
const outputFees = [];
for (let i = 1; i <= 13; i++) {
const fraction = i / 13;
const currentInput = BigNumber.min(normalizedOrderInput.times(fraction), normalizedOrderInput);
const currentOutput = BigNumber.min(normalizedOrderOutput.times(fraction), normalizedOrderOutput);
const id = `${ERC20BridgeSource.Native}-${serializedPaths.length}-${idx}-${i}`;
inputs.push(currentInput.integerValue().toNumber());
outputs.push(currentOutput.integerValue().toNumber());
outputFees.push(fee);
ids.push(id);
}
const serializedPath: SerializedPath = {
ids,
@@ -200,6 +176,7 @@ function findRoutesAndCreateOptimalPath(
samplesAndNativeOrdersWithResults.push([nativeOrder]);
serializedPaths.push(serializedPath);
sampleSourcePathIds.push(nativeOrdersourcePathId);
}
if (serializedPaths.length === 0) {
@@ -212,30 +189,33 @@ function findRoutesAndCreateOptimalPath(
pathsIn: serializedPaths,
};
const before = performance.now();
const allSourcesRustRoute = new Float64Array(rustArgs.pathsIn.length);
route(rustArgs, allSourcesRustRoute, RUST_ROUTER_NUM_SAMPLES);
DEFAULT_INFO_LOGGER(
{ router: 'neon-router', performanceMs: performance.now() - before, type: 'real' },
'Rust router real routing performance',
);
const strategySourcesOutputAmounts = new Float64Array(rustArgs.pathsIn.length);
route(rustArgs, allSourcesRustRoute, strategySourcesOutputAmounts, neonRouterNumSamples);
assert.assert(
rustArgs.pathsIn.length === allSourcesRustRoute.length,
'different number of sources in the Router output than the input',
);
assert.assert(
rustArgs.pathsIn.length === strategySourcesOutputAmounts.length,
'different number of sources in the Router output amounts results than the input',
);
const routesAndSamples = _.zip(allSourcesRustRoute, samplesAndNativeOrdersWithResults);
const routesAndSamplesAndOutputs = _.zip(
allSourcesRustRoute,
samplesAndNativeOrdersWithResults,
strategySourcesOutputAmounts,
sampleSourcePathIds,
);
const adjustedFills: Fill[] = [];
const totalRoutedAmount = BigNumber.sum(...allSourcesRustRoute);
const scale = input.dividedBy(totalRoutedAmount);
for (const [routeInput, routeSamplesAndNativeOrders] of routesAndSamples) {
if (!routeInput || !routeSamplesAndNativeOrders) {
for (const [routeInput, routeSamplesAndNativeOrders, outputAmount, sourcePathId] of routesAndSamplesAndOutputs) {
if (!routeInput || !routeSamplesAndNativeOrders || !outputAmount || !Number.isFinite(outputAmount)) {
continue;
}
// TODO(kimpers): [TKR-241] amounts are sometimes clipped in the router due to precisions loss for number/f64
// 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),
@@ -251,14 +231,21 @@ function findRoutesAndCreateOptimalPath(
opts.outputAmountPerEth,
opts.inputAmountPerEth,
fees,
)[0];
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
adjustedFills.push(nativeFill);
)[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
@@ -267,49 +254,47 @@ function findRoutesAndCreateOptimalPath(
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 = createFill(routeSamples[0]) ?? fill;
}
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,
});
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 = createFill(left || right) ?? fill;
}
break;
}
}
const scaleOutput = (output: BigNumber) =>
// TODO(kimpers): remove once we have solved the rounding/precision loss issues in the Rust router
const scaleOutput = (fillInput: BigNumber, output: BigNumber) =>
output
.dividedBy(fill.input)
.dividedBy(fillInput)
.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),
output: scaleOutput(fill.input, fill.output),
adjustedOutput: scaleOutput(fill.input, fill.adjustedOutput),
index: 0,
parent: undefined,
sourcePathId: sourcePathId ?? hexUtils.random(),
});
}
if (adjustedFills.length === 0) {
return undefined;
}
const pathFromRustInputs = Path.create(side, adjustedFills, input);
return pathFromRustInputs;
@@ -323,15 +308,27 @@ export function findOptimalRustPathFromSamples(
opts: PathPenaltyOpts,
fees: FeeSchedule,
chainId: ChainId,
neonRouterNumSamples: number,
samplerMetrics?: SamplerMetrics,
): 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);
const beforeAllTimeMs = performance.now();
let beforeTimeMs = performance.now();
const allSourcesPath = findRoutesAndCreateOptimalPath(
side,
samples,
nativeOrders,
input,
opts,
fees,
neonRouterNumSamples,
);
// tslint:disable-next-line: no-unused-expression
samplerMetrics &&
samplerMetrics.logRouterDetails({
router: 'neon-router',
type: 'all',
timingMs: performance.now() - beforeTimeMs,
});
if (!allSourcesPath) {
return undefined;
}
@@ -341,11 +338,27 @@ export function findOptimalRustPathFromSamples(
// HACK(kimpers): The Rust router currently doesn't account for VIP sources correctly
// we need to try to route them in isolation and compare with the results all sources
if (vipSources.length > 0) {
beforeTimeMs = performance.now();
const vipSourcesSet = new Set(vipSources);
const vipSourcesSamples = samples.filter(s => s[0] && vipSourcesSet.has(s[0].source));
if (vipSourcesSamples.length > 0) {
const vipSourcesPath = findRoutesAndCreateOptimalPath(side, vipSourcesSamples, [], input, opts, fees);
const vipSourcesPath = findRoutesAndCreateOptimalPath(
side,
vipSourcesSamples,
[],
input,
opts,
fees,
neonRouterNumSamples,
);
// tslint:disable-next-line: no-unused-expression
samplerMetrics &&
samplerMetrics.logRouterDetails({
router: 'neon-router',
type: 'vip',
timingMs: performance.now() - beforeTimeMs,
});
const { input: allSourcesInput, output: allSourcesOutput } = allSourcesPath.adjustedSize();
// NOTE: For sell quotes input is the taker asset and for buy quotes input is the maker asset
@@ -358,13 +371,18 @@ export function findOptimalRustPathFromSamples(
const allSourcesAdjustedRateWithFqtOverhead = getRate(side, allSourcesInput, outputWithFqtOverhead);
if (vipSourcesPath?.adjustedRate().isGreaterThan(allSourcesAdjustedRateWithFqtOverhead)) {
logPerformance();
return vipSourcesPath;
}
}
}
// tslint:disable-next-line: no-unused-expression
samplerMetrics &&
samplerMetrics.logRouterDetails({
router: 'neon-router',
type: 'total',
timingMs: performance.now() - beforeAllTimeMs,
});
logPerformance();
return allSourcesPath;
}
@@ -377,8 +395,10 @@ export async function findOptimalPathJSAsync(
fills: Fill[][],
targetInput: BigNumber,
runLimit: number = 2 ** 8,
samplerMetrics?: SamplerMetrics,
opts: PathPenaltyOpts = DEFAULT_PATH_PENALTY_OPTS,
): Promise<Path | undefined> {
const beforeTimeMs = performance.now();
// Sort fill arrays by descending adjusted completed rate.
// Remove any paths which cannot impact the optimal path
const sortedPaths = reducePaths(fillsToSortedPaths(fills, side, targetInput, opts), side);
@@ -392,7 +412,15 @@ export async function findOptimalPathJSAsync(
// Yield to event loop.
await Promise.resolve();
}
return optimalPath.isComplete() ? optimalPath : undefined;
const finalPath = optimalPath.isComplete() ? optimalPath : undefined;
// tslint:disable-next-line: no-unused-expression
samplerMetrics &&
samplerMetrics.logRouterDetails({
router: 'js',
type: 'total',
timingMs: performance.now() - beforeTimeMs,
});
return finalPath;
}
// Sort fill arrays by descending adjusted completed rate.

View File

@@ -14,7 +14,8 @@ import { BatchedOperation, ERC20BridgeSource, LiquidityProviderRegistry, TokenAd
*/
export function getSampleAmounts(maxFillAmount: BigNumber, numSamples: number, expBase: number = 1): BigNumber[] {
const distribution = [...Array<BigNumber>(numSamples)].map((_v, i) => new BigNumber(expBase).pow(i));
const stepSizes = distribution.map(d => d.div(BigNumber.sum(...distribution)));
const distributionSum = BigNumber.sum(...distribution);
const stepSizes = distribution.map(d => d.div(distributionSum));
const amounts = stepSizes.map((_s, i) => {
if (i === numSamples - 1) {
return maxFillAmount;

View File

@@ -1342,6 +1342,7 @@ export class SamplerOperations {
case ERC20BridgeSource.Swerve:
case ERC20BridgeSource.SnowSwap:
case ERC20BridgeSource.Nerve:
case ERC20BridgeSource.Synapse:
case ERC20BridgeSource.Belt:
case ERC20BridgeSource.Ellipsis:
case ERC20BridgeSource.Saddle:
@@ -1647,6 +1648,7 @@ export class SamplerOperations {
case ERC20BridgeSource.Swerve:
case ERC20BridgeSource.SnowSwap:
case ERC20BridgeSource.Nerve:
case ERC20BridgeSource.Synapse:
case ERC20BridgeSource.Belt:
case ERC20BridgeSource.Ellipsis:
case ERC20BridgeSource.Saddle:

View File

@@ -70,6 +70,7 @@ export enum ERC20BridgeSource {
ShibaSwap = 'ShibaSwap',
AaveV2 = 'Aave_V2',
Compound = 'Compound',
Synapse = 'Synapse',
// BSC only
PancakeSwap = 'PancakeSwap',
PancakeSwapV2 = 'PancakeSwap_V2',
@@ -130,7 +131,7 @@ export enum CurveFunctionSelectors {
// Smoothy
swap_uint256 = '0x5673b02d', // swap(uint256,uint256,uint256,uint256)
get_swap_amount = '0x45cf2ef6', // getSwapAmount(uint256,uint256,uint256)
// Nerve BSC, Saddle Mainnet
// Nerve BSC, Saddle Mainnet, Synapse
swap = '0x91695586', // swap(uint8,uint8,uint256,uint256,uint256)
calculateSwap = '0xa95b089f', // calculateSwap(uint8,uint8,uint256)
}
@@ -455,6 +456,10 @@ export interface GetMarketOrdersOpts {
* Default: 1.25.
*/
sampleDistributionBase: number;
/**
* Number of samples to use when creating fill curves with neon-router
*/
neonRouterNumSamples: number;
/**
* Fees for each liquidity source, expressed in gas.
*/
@@ -514,6 +519,15 @@ export interface SamplerMetrics {
* @param blockNumber block number of the sampler call
*/
logBlockNumber(blockNumber: BigNumber): void;
/**
* Logs the routing timings
*
* @param data.router The router type (neon-router or js)
* @param data.type The type of timing being recorded (e.g total timing, all sources timing or vip timing)
* @param data.timingMs The timing in milliseconds
*/
logRouterDetails(data: { router: 'neon-router' | 'js'; type: 'all' | 'vip' | 'total'; timingMs: number }): void;
}
/**
@@ -599,6 +613,8 @@ export interface GenerateOptimizedOrdersOpts {
allowFallback?: boolean;
shouldBatchBridgeOrders?: boolean;
gasPrice: BigNumber;
neonRouterNumSamples: number;
samplerMetrics?: SamplerMetrics;
}
export interface ComparisonPrice {

View File

@@ -959,10 +959,10 @@
typedoc "~0.16.11"
yargs "^10.0.3"
"@0x/neon-router@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@0x/neon-router/-/neon-router-0.2.1.tgz#23bb3cedc0eafd55a8ba6b6ea8a59ee4c538064b"
integrity sha512-feCCKuox4staZl8lxLY4nf5U256NcDHrgvSFra5cU/TUhoblLHb8F7eWAC9ygpukZUCVFLy13mExkFQHXlEOYw==
"@0x/neon-router@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@0x/neon-router/-/neon-router-0.3.1.tgz#4ec13e750d1435357c4928d7f2521a2b4376f27e"
integrity sha512-M4ypTov9KyxsGJpYwobrld3Y2JOlR7U0XjR6BEQE2gQ1k3nie/1wNEI2J4ZjKw++RLDxdv/RCqhgA5VnINzjxA==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.5"