chore: Delete asset-swapper [GOV-6] (#617)
* Delete `asset-swapper` * Remove `package/asset-swapper` in CODEOWNERS
This commit is contained in:
@@ -6,8 +6,6 @@
|
||||
|
||||
# https://git-scm.com/docs/gitignore#_pattern_format
|
||||
|
||||
packages/asset-swapper/ @dekz @dextracker @kyu-c
|
||||
|
||||
# Dev tools & setup
|
||||
|
||||
.circleci/ @dekz
|
||||
|
||||
2
packages/asset-swapper/.gitignore
vendored
2
packages/asset-swapper/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
**/generated-artifacts
|
||||
**/generated-wrappers
|
||||
@@ -1,11 +0,0 @@
|
||||
# Blacklist all files
|
||||
.*
|
||||
*
|
||||
# Whitelist lib
|
||||
!lib/**/*
|
||||
# Whitelist Solidity contracts
|
||||
!contracts/src/**/*
|
||||
# Blacklist tests and publish scripts
|
||||
/lib/test/*
|
||||
/lib/monorepo_scripts/
|
||||
# Package specific ignore
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"extends": "default",
|
||||
"rules": {
|
||||
"avoid-low-level-calls": false,
|
||||
"avoid-tx-origin": "warn",
|
||||
"bracket-align": false,
|
||||
"code-complexity": false,
|
||||
"compiler-fixed": false,
|
||||
"const-name-snakecase": "error",
|
||||
"expression-indent": "error",
|
||||
"function-max-lines": false,
|
||||
"func-order": "error",
|
||||
"indent": ["error", 4],
|
||||
"max-line-length": ["warn", 160],
|
||||
"no-inline-assembly": false,
|
||||
"quotes": ["error", "double"],
|
||||
"separate-by-one-line-in-contract": "error",
|
||||
"space-after-comma": "error",
|
||||
"statement-indent": "error"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,893 +0,0 @@
|
||||
<!--
|
||||
changelogUtils.file is auto-generated using the monorepo-scripts package. Don't edit directly.
|
||||
Edit the package's CHANGELOG.json file only.
|
||||
-->
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v16.66.4 - _August 22, 2022_
|
||||
|
||||
* Offboard Cream (#546)
|
||||
* Change WooFi gas estimates (#551)
|
||||
|
||||
## v16.66.3 - _August 10, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.66.2 - _August 9, 2022_
|
||||
|
||||
* Upgrade dependency (#543)
|
||||
|
||||
## v16.66.1 - _August 8, 2022_
|
||||
|
||||
* Upgrade dependency (#538)
|
||||
|
||||
## v16.66.0 - _August 6, 2022_
|
||||
|
||||
* Add WOOFi support (#513)
|
||||
|
||||
## v16.65.0 - _August 1, 2022_
|
||||
|
||||
* Use 0x gas api instead of eth gas station api (#532)
|
||||
|
||||
## v16.64.0 - _July 27, 2022_
|
||||
|
||||
* Refactor `TokenAdjacency` and `TokenAdjacencyBuilder` (#517)
|
||||
* Add Synthetix support` (#518)
|
||||
* Replace Beethoven X subgraph URL (#519)
|
||||
* Remove Mooniswap on Ethereum mainnet (#529)
|
||||
|
||||
## v16.63.1 - _July 12, 2022_
|
||||
|
||||
* Better error handling for balancer cache (#515)
|
||||
|
||||
## v16.63.0 - _June 29, 2022_
|
||||
|
||||
* Remove JS router (#480)
|
||||
* Removed Median price in favour of best gas adjusted price (#480)
|
||||
|
||||
## v16.62.2 - _Invalid date_
|
||||
|
||||
* Offboard Smoothy and ComethSwap (#509)
|
||||
|
||||
## v16.62.1 - _June 15, 2022_
|
||||
|
||||
* Remove nUSD from intermediate liquidity to save on sampler gas (#505)
|
||||
|
||||
## v16.62.0 - _June 14, 2022_
|
||||
|
||||
* Add MDEX on BSC (#496)
|
||||
* Add KnightSwap on BSC (#498)
|
||||
* Add Velodrome support on Optimism (#494)
|
||||
* Do not send empty entries on Quote Report (#501)
|
||||
* KnightSwap/Mdex cosmetic change (#502)
|
||||
* Offboard JetSwap, CafeSwap, JulSwap, and PolyDex (#503)
|
||||
|
||||
## v16.61.0 - _June 3, 2022_
|
||||
|
||||
* Add stETH wrap/unwrap support (#476)
|
||||
* Offboard/clean up Oasis, CoFix, and legacy Kyber (#482)
|
||||
* Add MeshSwap on Polygon (#491)
|
||||
|
||||
## v16.60.1 - _May 19, 2022_
|
||||
|
||||
* Alias Balancer sor to the old version (#481)
|
||||
|
||||
## v16.60.0 - _May 19, 2022_
|
||||
|
||||
* Add BiSwap on BSC (#467)
|
||||
* Add GMX and Platypus on Avalanche and Enable KyberDMM on bsc (#478)
|
||||
* Add Yoshi Exchange support in Fantom (#473)
|
||||
* Fix KyberDMM gas underestimation (#479)
|
||||
|
||||
## v16.59.0 - _May 13, 2022_
|
||||
|
||||
* Remove SnowSwap on mainnet (#468)
|
||||
* Offboard Swerve Finance and LinkSwap (#469)
|
||||
* Offboard Eth2Dai (#470)
|
||||
* Add an optional IRfqClient for SwapQuoter#getSwapQuoteAsync (#467)
|
||||
|
||||
## v16.58.0 - _Invalid date_
|
||||
|
||||
* Update Saddle pools on Mainnet (#450)
|
||||
|
||||
## v16.57.3 - _May 10, 2022_
|
||||
|
||||
* Fix a runtime error related to BalancerV2SwapInfoCache (#472)
|
||||
|
||||
## v16.57.2 - _May 2, 2022_
|
||||
|
||||
* Fix missing AMM quotes on indicative Quote Reports (#466)
|
||||
|
||||
## v16.57.1 - _Invalid date_
|
||||
|
||||
* Added QUICK/ANY pair on Polygon (#464)
|
||||
* Added cvxFXS/FXS curve pool on mainnet (#465)
|
||||
|
||||
## v16.57.0 - _April 22, 2022_
|
||||
|
||||
* Add BalancerV2 batch swap support (#462)
|
||||
|
||||
## v16.56.0 - _April 21, 2022_
|
||||
|
||||
* Add estimatedGas to ExtendedQuoteReport (#463)
|
||||
|
||||
## v16.55.0 - _April 7, 2022_
|
||||
|
||||
* Fix fillRfqOrder VIP being used for swaps that need transformERC20 (#461)
|
||||
|
||||
## v16.54.0 - _April 6, 2022_
|
||||
|
||||
* Add true VIP support for eligible RFQt swaps (#458)
|
||||
|
||||
## v16.53.0 - _March 31, 2022_
|
||||
|
||||
* Adds support for STG/USDC pool on Curve Mainnet (#451)
|
||||
* Use neon-router in asset-swapper tests (#453)
|
||||
* Add sampler blocknumber to quote report data (#448)
|
||||
|
||||
## v16.52.0 - _Invalid date_
|
||||
|
||||
* Adds support for mobius money on celo (#423)
|
||||
|
||||
## 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_
|
||||
|
||||
* Adding support for Geist on `Fantom` (#398)
|
||||
* Improve Uniswap V3 gas schedule (#424)
|
||||
|
||||
## v16.49.9 - _February 24, 2022_
|
||||
|
||||
* Fix native order scaling & filter out 1 wei quotes (#430)
|
||||
|
||||
## v16.49.8 - _February 22, 2022_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.49.7 - _February 22, 2022_
|
||||
|
||||
* Fix native order handling for very small quotes and bump `neon-router` dependency (#425)
|
||||
|
||||
## v16.49.6 - _February 17, 2022_
|
||||
|
||||
* Fixed btrfly routing to include the ohmV2/dai, ohmV2/btfly, and ohmV2/weth pools (#427)
|
||||
|
||||
## v16.49.5 - _February 14, 2022_
|
||||
|
||||
* Fix scaling 1 base unit to 0, round output to base units (#422)
|
||||
|
||||
## v16.49.4 - _February 10, 2022_
|
||||
|
||||
* Reverts 'Improve Uniswap V3 gas schedule' due to issue with buys (#419)
|
||||
|
||||
## v16.49.3 - _February 10, 2022_
|
||||
|
||||
* Fix `slippage` inconsistency when recalculated in exchange proxy quote consumer (#412)
|
||||
* Fix incorrect output scaling when input is less than desired amount, update fast-abi (#401)
|
||||
* Improve Uniswap V3 gas schedule (#397)
|
||||
* Fix add Native as VIP and use Path to compare all sources vs vip only (#413)
|
||||
|
||||
## v16.49.2 - _January 31, 2022_
|
||||
|
||||
* Fix ABI encoding error with two hop buys due to applying slippage to uint(-1) values (#410)
|
||||
|
||||
## v16.49.1 - _January 31, 2022_
|
||||
|
||||
* Fix WorstCaseQuoteInfo encoding bug (#402)
|
||||
|
||||
## v16.49.0 - _January 28, 2022_
|
||||
|
||||
* Add more curve pools (#409)
|
||||
|
||||
## v16.48.0 - _January 25, 2022_
|
||||
|
||||
* Use `MIM` as an intermediate asset on `Fantom` (#405)
|
||||
|
||||
## 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)
|
||||
|
||||
## v16.42.0 - _December 21, 2021_
|
||||
|
||||
* `UniswapV3` support for `Polygon` (#382)
|
||||
* Update `Beethoven` Graphql url (#383)
|
||||
|
||||
## v16.41.0 - _December 6, 2021_
|
||||
|
||||
* Update mcusd contract address, and made celo native asset (#376)
|
||||
|
||||
## v16.40.0 - _December 1, 2021_
|
||||
|
||||
* Add `AaveV2` and `Compound` deposit/withdrawal liquidity source (#321)
|
||||
|
||||
## v16.39.0 - _Invalid date_
|
||||
|
||||
* Curve ETH/CRV pool (#378)
|
||||
|
||||
## v16.38.0 - _November 29, 2021_
|
||||
|
||||
* Capture sampler metrics (#374)
|
||||
|
||||
## v16.37.0 - _November 19, 2021_
|
||||
|
||||
* Changed Sushiswap router address (#373)
|
||||
|
||||
## v16.36.0 - _November 19, 2021_
|
||||
|
||||
* Specify liquid routes for FEI/TRIBE FXS/FRAX and OHM/FRAX (#371)
|
||||
|
||||
## v16.35.0 - _November 18, 2021_
|
||||
|
||||
* Add Beethoven X, MorpheusSwap and JetSwap to Fantom (#370)
|
||||
|
||||
## v16.34.0 - _November 16, 2021_
|
||||
|
||||
* Add support Celo (#367)
|
||||
|
||||
## v16.33.0 - _November 16, 2021_
|
||||
|
||||
* Add support for Uniswap V3 1 bps pools (#366)
|
||||
|
||||
## v16.32.0 - _November 9, 2021_
|
||||
|
||||
* Extended Quote Report (#361)
|
||||
|
||||
## v16.31.0 - _November 3, 2021_
|
||||
|
||||
* Added `Curve`, `Curve_V2` and `KyberDmm` to Avalanche (#363)
|
||||
|
||||
## v16.30.1 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.30.0 - _October 19, 2021_
|
||||
|
||||
* Fantom deployment (#347)
|
||||
|
||||
## v16.29.3 - _October 18, 2021_
|
||||
|
||||
* Update neon-router version and address breaking changes (#344)
|
||||
|
||||
## v16.29.2 - _October 13, 2021_
|
||||
|
||||
* Check MAX_IN_RATIO in sampleBuysFromBalancer (#338)
|
||||
* Go back to using transformERC20 (instead of transformERC20Staging) (#343)
|
||||
|
||||
## v16.29.1 - _October 4, 2021_
|
||||
|
||||
* Remove `Clipper` as a custom liquidity source (#335)
|
||||
|
||||
## v16.29.0 - _October 4, 2021_
|
||||
|
||||
* Initial integration of neon-router (behind feature flag) (#295)
|
||||
|
||||
## v16.28.0 - _September 29, 2021_
|
||||
|
||||
* Update ExchangeProxySwapQuoteConsumer for Multiplex V2 and friends (#282)
|
||||
|
||||
## v16.27.5 - _Invalid date_
|
||||
|
||||
* Remove protocol fees by setting `PROTOCOL_FEE_MULTIPLIER` to 0 (#333)
|
||||
|
||||
## v16.27.4 - _September 15, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.27.3 - _September 14, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.27.2 - _September 14, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.27.1 - _September 8, 2021_
|
||||
|
||||
* Fix ApproximateBuys sampler to terminate if the buy amount is not met (#319)
|
||||
|
||||
## v16.27.0 - _September 1, 2021_
|
||||
|
||||
* Avalanche deployment (#312)
|
||||
|
||||
## v16.26.2 - _August 31, 2021_
|
||||
|
||||
* chore: Curve new pools (CVX-CRX, MIM, atricrypto3)
|
||||
|
||||
## v16.26.1 - _August 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.26.0 - _August 19, 2021_
|
||||
|
||||
* feat: Enable partial Native fills to be consumed, previously for v3 they were dropped (#309)
|
||||
* feat: Modify Intermediate tokens to be a union (#309)
|
||||
* feat: Retire Eth2Dai/Oasis (#309)
|
||||
|
||||
## v16.25.0 - _August 16, 2021_
|
||||
|
||||
* Fix: fallback fills which have not been used, unique id by source-index
|
||||
|
||||
## v16.24.1 - _August 11, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.24.0 - _August 6, 2021_
|
||||
|
||||
* Add `Clipper` as a custom liquidity source (#299)
|
||||
* Added `Curve` `Tricrypto2` and `ESD` v2 (#302)
|
||||
|
||||
## v16.23.1 - _July 29, 2021_
|
||||
|
||||
* Fix fill amount rounding error when covnerting fills to orders. (#296)
|
||||
|
||||
## v16.23.0 - _July 16, 2021_
|
||||
|
||||
* ACryptoS (#284)
|
||||
|
||||
## v16.22.0 - _July 13, 2021_
|
||||
|
||||
* IronSwap (#281)
|
||||
|
||||
## v16.21.0 - _July 10, 2021_
|
||||
|
||||
* JetSwap (#280)
|
||||
|
||||
## v16.20.0 - _July 6, 2021_
|
||||
|
||||
* ShibaSwap (#276)
|
||||
|
||||
## v16.19.1 - _July 6, 2021_
|
||||
|
||||
* Fix LiquidityProvider fallback (#272)
|
||||
|
||||
## v16.19.0 - _July 2, 2021_
|
||||
|
||||
* Add LiquidityProvider to Polygon sources (#270)
|
||||
|
||||
## v6.18.3 - _June 29, 2021_
|
||||
|
||||
* Polygon Balance V2
|
||||
|
||||
## v6.18.2 - _June 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v6.18.1 - _June 22, 2021_
|
||||
|
||||
* FirebirdOneSwap, ApeSwap. New hop tokens: DFYN, BANANA, WEXPOLY (#265)
|
||||
|
||||
## v6.18.0 - _June 22, 2021_
|
||||
|
||||
* Add Lido stETH deposit integration (#260)
|
||||
|
||||
## v6.17.3 - _June 16, 2021_
|
||||
|
||||
* QUICK, TITAN, IRON as intermediate tokens, integrating WaultSwap and Polydex for Polygon, Curve renBTC pool
|
||||
|
||||
## v6.17.2 - _June 11, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v6.17.1 - _June 2, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v6.17.0 - _May 27, 2021_
|
||||
|
||||
* Re-enable liquidity provider and update KNC address (#253)
|
||||
|
||||
## v6.16.0 - _May 25, 2021_
|
||||
|
||||
* Add support for the Polygon chain (#240)
|
||||
|
||||
## v6.15.0 - _May 21, 2021_
|
||||
|
||||
* Fix KyberDmm (#236)
|
||||
* Re-enable KyberDmm (#247)
|
||||
* Add Huobi Token to liquidity provider tokens (#246)
|
||||
* Temporarily disable specific LiquidityProvider
|
||||
|
||||
## v6.14.0 - _May 12, 2021_
|
||||
|
||||
* Add support for additional sources and intermediate tokens on Ropsten (#231)
|
||||
* Add UniswapV3 VIP support (#237)
|
||||
|
||||
## v6.13.0 - _May 11, 2021_
|
||||
|
||||
* Add LiquidityProvider to BSC sources (#234)
|
||||
|
||||
## v6.12.0 - _May 10, 2021_
|
||||
|
||||
* `TwoHopSampler` to use `call` over `staticcall` in order to support sources like `Uniswap_V3` and `Balancer_V2` (#233)
|
||||
|
||||
## v6.11.0 - _May 7, 2021_
|
||||
|
||||
* Add price comparisons data separate from the quote report (#219)
|
||||
* Add caching for top Balancer V2 pools on startup and during regular intervals (#228)
|
||||
* Tweak compiler settings for smaller sampler bytecode (#229)
|
||||
* Fix Multiplex multihop encoding for ETH buys/sells (#230)
|
||||
* Fix Sampler address override for Ganache (#232)
|
||||
|
||||
## v6.10.0 - _May 5, 2021_
|
||||
|
||||
* Reactivate PancakeSwapV2 and BakerySwap VIP on BSC (#222)
|
||||
* Add LUSD Curve pool (#218)
|
||||
* Fix exchangeProxyGasOverhead for fallback path (#215)
|
||||
* Enable ETH based Curve pools (#220)
|
||||
* Reactivate PancakeSwapV2 and BakerySwap VIP on BSC (#222)
|
||||
* Disable WETH based SnowSwap pools (#220)
|
||||
* PLP now includes a fallback due to observed collisions (#223)
|
||||
* Add Balancer V2 integration (#206)
|
||||
* Re-work the PoolCache for Balancer et al (#226)
|
||||
|
||||
## v6.9.1 - _May 1, 2021_
|
||||
|
||||
* Temporarily remove PancakeV2 and BakerySwap from VIP
|
||||
|
||||
## v6.9.0 - _April 30, 2021_
|
||||
|
||||
* Remove conflicting Kyber reserve (#216)
|
||||
|
||||
## v6.8.0 - _April 28, 2021_
|
||||
|
||||
* Prune paths which cannot improve the best path (#183)
|
||||
* Use FastABI for Sampler ABI encoding and decoding (#183)
|
||||
|
||||
## v6.7.0 - _April 26, 2021_
|
||||
|
||||
* Support PancakeSwap V2 (#211)
|
||||
|
||||
## v6.6.1 - _Invalid date_
|
||||
|
||||
* Fixing Positive Slippage logic to not force the EP route (#209)
|
||||
|
||||
## v6.6.0 - _April 16, 2021_
|
||||
|
||||
* Support `Ropsten` network (#203)
|
||||
* BSC Uniswap clones (ApeSwap, CafeSwap, CheeseSwap, JulSwap), Saddle BTC pool, Curve gas schedule (#208)
|
||||
|
||||
## v6.5.3 - _April 14, 2021_
|
||||
|
||||
* Apply slippage to bridge orders in consumer (#198)
|
||||
|
||||
## v6.5.2 - _April 13, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v6.5.1 - _April 12, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v6.5.0 - _April 8, 2021_
|
||||
|
||||
* Add Kyber DMM to Ethereum mainnet (#194)
|
||||
* Add default LiquidityProvider registry and allow LiquidityProvider gasCost to be a function of tokens (#196)
|
||||
|
||||
## v6.4.0 - _April 1, 2021_
|
||||
|
||||
* Added Component, Smoothy, Saddle, Curve open pools, tweeks gas schedule, adding SushiSwap as a fee quote source (#182)
|
||||
* Use SOURCE_FLAGS.rfqOrder in comparisonPrice (#177)
|
||||
* Add a cancel token to ensure timeouts are respected (#176)
|
||||
* Rename {Rfqt=>Rfq} for many types in Asset Swapper (#179)
|
||||
* improve logging for alt RFQ requests (#158)
|
||||
* Use new bridge source ID encoding. (#162)
|
||||
* Refactor to provide chain id specific addresses (#163)
|
||||
* Added PancakeSwap and BakerySwap on Chain 56 (#163)
|
||||
* Added Nerve and Dodo (v1) to BSC (#181)
|
||||
|
||||
## v6.3.0 - _March 17, 2021_
|
||||
|
||||
* Add MooniswapLiquidityProvider "direct" route to EP consumer. (#143)
|
||||
* Enable the ability to send RFQT requests thru a proxy (#159)
|
||||
* Add support for MultiplexFeature (#168)
|
||||
|
||||
## v6.2.0 - _March 2, 2021_
|
||||
|
||||
* drop curve Y and BUSD pools (#161)
|
||||
|
||||
## v6.1.0 - _February 24, 2021_
|
||||
|
||||
* Filter MultiHop where second source is not present (#138)
|
||||
* Add CurveLiquidityProvider "direct" route to EP consumer. (#127)
|
||||
* Fix compiler error on `ILiquidityProvider` call (#127)
|
||||
* Add deployed `CurveLiquidityProvider` addresses (#144)
|
||||
* Support `Mirror Protocol` with hops to `UST` (#142)
|
||||
* Fix protocol fee in fee schedule for `RfqOrder` (#146)
|
||||
* Special case BNB in uni v1 sampler (#147)
|
||||
* Create `FakeTaker` contract to get result data and gas used (#151)
|
||||
* Added support for `Dodo` v2 (#152)
|
||||
* Added support for `Linkswap` (#153)
|
||||
* Re-add WBTC in default intermediate hops (#154)
|
||||
* Add an alternative RFQ market making implementation (#139)
|
||||
* Added an opt-in `PositiveSlippageAffiliateFee` (#101)
|
||||
|
||||
## v6.0.0 - _February 10, 2021_
|
||||
|
||||
* Pull top 250 Balancer pairs on initialization (#113)
|
||||
* Support v4 `RFQ` and `Limit` orders (#113)
|
||||
* Refactor to consume latest `FillQuoteTransformer` (#113)
|
||||
* Enable `fillData` for all sources, no longer optional (#113)
|
||||
* Support `tx.origin` in RFQT quote requestor (#113)
|
||||
|
||||
## v5.8.2 - _January 28, 2021_
|
||||
|
||||
* Fix error when Multihop data is not present (#80)
|
||||
|
||||
## v5.8.1 - _January 26, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.8.0 - _January 13, 2021_
|
||||
|
||||
* Automatically Discover Kyber reserves for tokens using `getTradingReserves` (#111)
|
||||
* Return `CallResults` from the Sampler (#111)
|
||||
|
||||
## v5.7.0 - _Invalid date_
|
||||
|
||||
* Add SPDX license identifiers to solidity files (#105)
|
||||
|
||||
## v5.6.2 - _January 4, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.6.1 - _December 31, 2020_
|
||||
|
||||
* Fix fillAmount `ExchangeProxySwapQuoteConsumer` encoding when quote is a BuyQuote
|
||||
|
||||
## v5.6.0 - _December 27, 2020_
|
||||
|
||||
* Added Mooniswap V2 factory address (#100)
|
||||
|
||||
## v5.5.3 - _December 23, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.5.2 - _December 17, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.5.1 - _December 16, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.5.0 - _December 16, 2020_
|
||||
|
||||
* Bancor now supported in all pairs (#88)
|
||||
|
||||
## v5.4.2 - _December 9, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.1 - _December 7, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.0 - _December 7, 2020_
|
||||
|
||||
* Add `takerAssetToEthRate` and `makerAssetToEthRate` to swap quote response (#49)
|
||||
|
||||
## v5.3.1 - _December 3, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.3.0 - _December 3, 2020_
|
||||
|
||||
* Added Crypto.com (#43)
|
||||
* Add `getQuoteInfoMinBuyAmount` to quote consumer utils (#62)
|
||||
* Add `unoptimizedQuoteInfo` and `unoptimizedOrders` to SwapQuoteBase (#62)
|
||||
* Add `unoptimizedPath` to OptimizerResult (#62)
|
||||
* Enable PLP VIP feature and add gasCost field to LiquidityProviderRegistry (#65)
|
||||
|
||||
## v5.2.0 - _November 19, 2020_
|
||||
|
||||
* Update Gas schedules (#34)
|
||||
* Return the maker/taker token decimals from the sampler as part of the `SwapQuote` (#34)
|
||||
* Disable off-chain sampling for Balancer and CREAM (#41)
|
||||
|
||||
## v5.1.1 - _November 14, 2020_
|
||||
|
||||
* Disable PLP VIP feature in EP swap quote consumer (#36)
|
||||
|
||||
## v5.1.0 - _November 13, 2020_
|
||||
|
||||
* Add support for LiquidityProvider feature in the swap quote consumer (#16)
|
||||
* Remove support for MultiBridge 😞 (#16)
|
||||
|
||||
## v5.0.3 - _November 5, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.0.2 - _November 3, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
* adding Curve pools: PAX, hBTC, metapools: gUSD, hUSD, USDn, mUSD, tBTC (#26)
|
||||
|
||||
## v5.0.1 - _November 3, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.0.0 - _November 2, 2020_
|
||||
|
||||
* Support multiple `Shells` by supplying the `pool` address (#17)
|
||||
* Make use of Token Adjacency in more places. Moved as a parameter for the quote (#24)
|
||||
|
||||
## v4.8.1 - _October 28, 2020_
|
||||
|
||||
* Fix Gas schedule with `SnowSwap` and `Bancor` (#15)
|
||||
|
||||
## v4.8.0 - _October 27, 2020_
|
||||
|
||||
* Moved Bridge addresses into Asset-swapper (#4)
|
||||
* Updated Sampler to Solidity 0.6 (#4)
|
||||
|
||||
## v4.7.1 - _October 23, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.7.0 - _October 21, 2020_
|
||||
|
||||
* Return quoteReport from SwapQuoter functions (#2627)
|
||||
* Allow an empty override for sampler overrides (#2637)
|
||||
* Potentially heavy CPU functions inside the optimizer now yield to the event loop. As such they are now async. (#2637)
|
||||
* Support more varied curves (#2633)
|
||||
* Make path optimization go faster (#2640)
|
||||
* Adds `getBidAskLiquidityForMakerTakerAssetPairAsync` to return more detailed sample information (#2641)
|
||||
* Fix regression where a split on the same source was collapsed into a single fill (#2654)
|
||||
* Add support for buy token affiliate fees (#2658)
|
||||
* Fix optimization of buy paths (#2655)
|
||||
* Fix depth buy scale (#2659)
|
||||
* Adjust fill by ethToInputRate when ethToOutputRate is 0 (#2660)
|
||||
* Add Bancor as liquidity source (#2650)
|
||||
* Added `mStable` (#2662)
|
||||
* Merge `erc20-bridge-sampler` into this package (#2664)
|
||||
* Added `Mooniswap` (#2675)
|
||||
* Stop requiring takerAddress for RFQ-T indicative quotes (#2684)
|
||||
* Added two-hop support (#2647)
|
||||
* Move ERC20BridgeSampler interfaces into `interfaces` directory (#2647)
|
||||
* Use on-chain sampling (sometimes) for Balancer (#2647)
|
||||
* Re-worked `Kyber` quotes supporting multiple reserves (#2683)
|
||||
* Enable Quote Report to be generated with an option `shouldGenerateQuoteReport`. Default is `false` (#2687)
|
||||
* Add `refundReceiver` to `ExchangeProxySwapQuoteConsumer` options. (#2657)
|
||||
* Use `IZeroExContract` in EP swap quote consumer. (#2657)
|
||||
* Set `rfqtTakerAddress` to null in EP consumer (#2692)
|
||||
* Return Mooniswap pool in sampler and encode it in bridge data (#2692)
|
||||
* Added `Swerve` (#2698)
|
||||
* Added `SushiSwap` (#2698)
|
||||
* Add uniswap VIP support (#2703)
|
||||
* Add `includedSources` support (#2703)
|
||||
* Added `Curve` Tripool (#2708)
|
||||
* Pass back fillData from quote reporter (#2702)
|
||||
* Fix Balancer sampling (#2711)
|
||||
* Respect max slippage in EP consumer (#2712)
|
||||
* Introduced Path class, exchangeProxyOverhead parameter (#2691)
|
||||
* Added `Shell` (#2722)
|
||||
* Fix exchange proxy overhead gas being scaled by gas price (#2723)
|
||||
* Remove 0x-API swap/v0-specifc code from asset-swapper (#2725)
|
||||
* Added `DODO` (#2701)
|
||||
* Fix for some edge cases with `includedSources` and `MultiHop` (#2730)
|
||||
* Introduced `excludedFeeSources` to disable sources when determining the price of an asset in ETH (#2731)
|
||||
* Support `DODO` Trade Allowed parameter to automatically disable the pool (#2732)
|
||||
* Added `SwerveBridge` and `SnowSwapBridge` deployed addresses (#7)
|
||||
|
||||
## v4.6.0 - _July 15, 2020_
|
||||
|
||||
* Use internal Eth Gas Station proxy (#2614)
|
||||
* Renamed RFQT request parameters (#2582)
|
||||
* Fix worst case asset amount calculations. (#2615)
|
||||
* Specify EthGasStation url as an optional parameter (#2617)
|
||||
* Singleton Gas Price Oracle (#2619)
|
||||
* "Fix" forwarder buys of low decimal tokens. (#2618)
|
||||
* Add Balancer support (#2613)
|
||||
* Consolidate UniswapV2 sources, Curve sources in `ERC20BridgeSource` enum (#2613)
|
||||
* Change gas/fee schedule values from constants to functions returning numbers (#2613)
|
||||
* Specify overrides to the ERC20Sampler contract, by default the latest bytecode is the override (#2629)
|
||||
|
||||
## v4.5.0 - _June 24, 2020_
|
||||
|
||||
* Add support for private liquidity providers (#2505)
|
||||
* Big refactor of market operation utils (#2513)
|
||||
* Remove `dustFractionThreshold`, `noConflicts` options. (#2513)
|
||||
* Revamp fill optimization algorithm (#2513)
|
||||
* Add fallback orders to quotes via `allowFallback` option. (#2513)
|
||||
* Add `maxFallbackSlippage` option. (#2513)
|
||||
* Fix fee schedule not being scaled by gas price. (#2522)
|
||||
* Fix quote optimizer bug not properly accounting for fees. (#2526)
|
||||
* Fix `getBatchMarketBuyOrdersAsync` throwing NO_OPTIMAL_PATH (#2533)
|
||||
* Add DFB support + refactor swap quote calculator utils (#2536)
|
||||
* Add support for RFQ-T, querying maker-hosted endpoints for quotes to be submitted by the taker (#2541)
|
||||
* Add support for indicative (non-committal) quotes via RFQ-T (#2555)
|
||||
* Collapse `LiquidityProvider` into `DexForwarderBridge` (#2560)
|
||||
* Added Curve `sUSD` (#2563)
|
||||
* Fix sporadically failing quote simulation tests (#2564)
|
||||
* Apply Native order penalty inline with the target amount (#2565)
|
||||
* Remove Kyber exclusion when Uniswap/Eth2Dai is present (#2575)
|
||||
* Expose fills object in asset-swapper quote orders (#2583)
|
||||
* Increase timeout for tests (#2587)
|
||||
* Add support for Uniswap V2 (#2599)
|
||||
* Add support for MultiBridge (#2593)
|
||||
* Fix Uniswap V2 path ordering (#2601)
|
||||
* Add exchange proxy support (#2591)
|
||||
|
||||
## v4.4.0 - _March 3, 2020_
|
||||
|
||||
* Add support for ERC721 assets (#2491)
|
||||
* Add destroy for gas heartbeat (#2492)
|
||||
* Added `BUSD` Curve (#2506)
|
||||
* Updated `Compound` Curve address (#2506)
|
||||
|
||||
## v4.3.2 - _February 27, 2020_
|
||||
|
||||
* Fix order native pruning by fill amount (#2500)
|
||||
|
||||
## v4.3.1 - _February 26, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.3.0 - _February 25, 2020_
|
||||
|
||||
* Add `fees` to `GetMarketOrdersOpts` (#2481)
|
||||
* Incorporate fees into fill optimization (#2481)
|
||||
|
||||
## v4.2.0 - _February 15, 2020_
|
||||
|
||||
* Use `batchCall()` version of the `ERC20BridgeSampler` contract (#2477)
|
||||
* Support for sampling Curve contracts (#2483)
|
||||
|
||||
## v4.1.2 - _February 8, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.1.1 - _February 6, 2020_
|
||||
|
||||
* Fix bug with liquidity source breakdown (#2472)
|
||||
* Prune orders before creating a dummy order for the Sampler (#2470)
|
||||
* Bump sampler gas limit to 60e6 (#2471)
|
||||
|
||||
## v4.1.0 - _February 4, 2020_
|
||||
|
||||
* Allow contract addresses to be passed as optional constructor ags instead of hardcoding (#2461)
|
||||
* Add swap quote liquidity source breakdown (#2465)
|
||||
|
||||
## v4.0.1 - _January 23, 2020_
|
||||
|
||||
* Fix underestimated protocol fee in worst case quote. (#2452)
|
||||
|
||||
## v4.0.0 - _January 22, 2020_
|
||||
|
||||
* Upgrade to new `Forwarder` contract with flat affiliate fees. (#2432)
|
||||
* Remove `getSmartContractParamsOrThrow()` from `SwapQuoteConsumer`s. (#2432)
|
||||
* Added `getBatchMarketBuySwapQuoteForAssetDataAsync` on `SwapQuoter` (#2427)
|
||||
* Add exponential sampling distribution and `sampleDistributionBase` option to `SwapQuoter` (#2427)
|
||||
* Compute more accurate best quote price (#2427)
|
||||
* Change Exchange sell function from `marketSellOrdersNoThrow` to `marketSellOrdersFillOrKill` (#2450)
|
||||
|
||||
## v3.0.3 - _January 6, 2020_
|
||||
|
||||
* Ignore zero sample results from the sampler contract. (#2406)
|
||||
* Increase default `runLimit` from `1024` to `4096`. (#2406)
|
||||
* Increase default `numSamples` from `8` to `10` (#2406)
|
||||
* Fix ordering of optimized orders. (#2406)
|
||||
* Fix best and worst quotes being reversed sometimes. (#2406)
|
||||
* Fix rounding of quoted asset amounts. (#2406)
|
||||
* Undo bridge slippage in best case quote calculation. (#2406)
|
||||
* Compare equivalent asset data when validating quotes and checking fee asset data. (#2421)
|
||||
|
||||
## v3.0.2 - _December 17, 2019_
|
||||
|
||||
* Fix gasPrice from `ethgasstation` to be in WEI instead of GWEI (#2393)
|
||||
* Add aggregator utils (#2353)
|
||||
|
||||
## v3.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.0 - _December 2, 2019_
|
||||
|
||||
* Refactor of logic for marketBuy/marketSell order pruning and selecting, introduced protocol fees, and refactored types used by the package (#2272)
|
||||
* Incorporate paying protocol fees. (#2350)
|
||||
* Update BigNumber version to ~9.0.0 (#2342)
|
||||
* All references to network ID have been removed, and references to chain ID have been introduced instead (#2313)
|
||||
|
||||
## v2.1.0-beta.4 - _December 2, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.1.0-beta.3 - _November 20, 2019_
|
||||
|
||||
* Refactor of logic for marketBuy/marketSell order pruning and selecting, introduced protocol fees, and refactored types used by the package (#2272)
|
||||
* Incorporate paying protocol fees. (#2350)
|
||||
|
||||
## v2.1.0-beta.2 - _November 7, 2019_
|
||||
|
||||
* Update BigNumber version to ~9.0.0 (#2342)
|
||||
|
||||
## v2.1.0-beta.1 - _November 7, 2019_
|
||||
|
||||
* All references to network ID have been removed, and references to chain ID have been introduced instead (#2313)
|
||||
|
||||
## v2.1.0-beta.0 - _October 3, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.0 - _September 17, 2019_
|
||||
|
||||
* AssetSwapper to use `@0x/orderbook` to fetch and subscribe to order updates (#2056)
|
||||
|
||||
## v1.0.3 - _September 3, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.2 - _August 22, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.1 - _August 8, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.0 - _July 31, 2019_
|
||||
|
||||
* Added optimization utils to consumer output (#1988)
|
||||
* Expanded test coverage (#1980)
|
||||
|
||||
## v0.0.5 - _July 24, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.0.4 - _July 15, 2019_
|
||||
|
||||
* Switched MarketOperation type to enum and expanded default constants configuration (#1959)
|
||||
* Added additional options to control asset-swapper behavior and optimized consumer output (#1966)
|
||||
|
||||
## v0.0.3 - _July 13, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.0.2 - _July 13, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.0.1 - _Invalid date_
|
||||
|
||||
* Refactored asset-buyer into asset-swapper to support ERC<>ERC marketSell and marketBuy operations (#1845)
|
||||
@@ -1,86 +0,0 @@
|
||||
> :warning: **@0x/asset-swapper has been deprecated!** The `asset-swapper` code has been moved to [0x-api](https://github.com/0xProject/0x-api). Please do not open a PR with `asset-swapper` changes.
|
||||
|
||||
## @0x/asset-swapper
|
||||
|
||||
Convenience package for swapping assets represented on the Ethereum blockchain using 0x. The package helps to perform all the off-chain computations to execute a marketBuy or marketSell function execution with 0x exchange contracts, or 0x extension contracts. Given some liquidity (0x signed orders), it helps estimate the cost of buying or selling a certain asset (giving a range) and then provide varying consumable outputs to execute the buy or sell.
|
||||
|
||||
Asset-swapper integrates with the [Standard Relayer API](https://github.com/0xProject/standard-relayer-api)(in the future Mesh as well) and takes care of sourcing liquidity, order-pruning, and order-validation. The final result is a library that tells you what assets are available, provides a quote based on specified assets, and provide varying consumable metadata that can be used both on-chain in smart contracts or off-chain through web3 to swap a desired amount of ERC20 for another ERC20 asset.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
yarn add @0x/asset-swapper
|
||||
```
|
||||
|
||||
**Import**
|
||||
|
||||
```typescript
|
||||
import { SwapQuoter } from '@0x/asset-swapper';
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```javascript
|
||||
var SwapQuoter = require('@0x/asset-swapper').SwapQuoter;
|
||||
var SwapQuoteConsumer = require('@0x/asset-swapper').SwapQuoteConsumer;
|
||||
```
|
||||
|
||||
If your project is in [TypeScript](https://www.typescriptlang.org/), add the following to your `tsconfig.json`:
|
||||
|
||||
```json
|
||||
"compilerOptions": {
|
||||
"typeRoots": ["node_modules/@0x/typescript-typings/types", "node_modules/@types"],
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository.
|
||||
|
||||
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
|
||||
|
||||
### Install dependencies
|
||||
|
||||
If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
|
||||
|
||||
```bash
|
||||
yarn config set workspaces-experimental true
|
||||
```
|
||||
|
||||
Then install dependencies
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
|
||||
|
||||
```bash
|
||||
PKG=@0x/asset-swapper yarn build
|
||||
```
|
||||
|
||||
Or continuously rebuild on change:
|
||||
|
||||
```bash
|
||||
PKG=@0x/asset-swapper yarn watch
|
||||
```
|
||||
|
||||
### Clean
|
||||
|
||||
```bash
|
||||
yarn clean
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"artifactsDir": "./test/generated-artifacts",
|
||||
"contractsDir": "./contracts",
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"shouldSaveStandardInput": true,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": { "enabled": true, "runs": 200, "details": { "yul": false, "deduplicate": true } },
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"devdoc",
|
||||
"evm.bytecode.object",
|
||||
"evm.bytecode.sourceMap",
|
||||
"evm.deployedBytecode.object",
|
||||
"evm.deployedBytecode.sourceMap"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||
|
||||
|
||||
contract ApproximateBuys {
|
||||
|
||||
/// @dev Information computing buy quotes for sources that do not have native
|
||||
/// buy quote support.
|
||||
struct ApproximateBuyQuoteOpts {
|
||||
// Arbitrary maker token data to pass to `getSellQuoteCallback`.
|
||||
bytes makerTokenData;
|
||||
// Arbitrary taker token data to pass to `getSellQuoteCallback`.
|
||||
bytes takerTokenData;
|
||||
// Callback to retrieve a sell quote.
|
||||
function (bytes memory, bytes memory, uint256)
|
||||
internal
|
||||
view
|
||||
returns (uint256) getSellQuoteCallback;
|
||||
}
|
||||
|
||||
uint256 private constant ONE_HUNDED_PERCENT_BPS = 1e4;
|
||||
/// @dev Maximum approximate (positive) error rate when approximating a buy quote.
|
||||
uint256 private constant APPROXIMATE_BUY_TARGET_EPSILON_BPS = 0.0005e4;
|
||||
/// @dev Maximum iterations to perform when approximating a buy quote.
|
||||
uint256 private constant APPROXIMATE_BUY_MAX_ITERATIONS = 5;
|
||||
|
||||
function _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts memory opts,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
|
||||
if (makerTokenAmounts.length == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
uint256 sellAmount = opts.getSellQuoteCallback(
|
||||
opts.makerTokenData,
|
||||
opts.takerTokenData,
|
||||
makerTokenAmounts[0]
|
||||
);
|
||||
if (sellAmount == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
uint256 buyAmount = opts.getSellQuoteCallback(
|
||||
opts.takerTokenData,
|
||||
opts.makerTokenData,
|
||||
sellAmount
|
||||
);
|
||||
if (buyAmount == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
|
||||
uint256 eps = 0;
|
||||
for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) {
|
||||
// adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER
|
||||
sellAmount = _safeGetPartialAmountCeil(
|
||||
makerTokenAmounts[i],
|
||||
buyAmount,
|
||||
sellAmount
|
||||
);
|
||||
if (sellAmount == 0) {
|
||||
break;
|
||||
}
|
||||
sellAmount = _safeGetPartialAmountCeil(
|
||||
(ONE_HUNDED_PERCENT_BPS + APPROXIMATE_BUY_TARGET_EPSILON_BPS),
|
||||
ONE_HUNDED_PERCENT_BPS,
|
||||
sellAmount
|
||||
);
|
||||
if (sellAmount == 0) {
|
||||
break;
|
||||
}
|
||||
uint256 _buyAmount = opts.getSellQuoteCallback(
|
||||
opts.takerTokenData,
|
||||
opts.makerTokenData,
|
||||
sellAmount
|
||||
);
|
||||
if (_buyAmount == 0) {
|
||||
break;
|
||||
}
|
||||
// We re-use buyAmount next iteration, only assign if it is
|
||||
// non zero
|
||||
buyAmount = _buyAmount;
|
||||
// If we've reached our goal, exit early
|
||||
if (buyAmount >= makerTokenAmounts[i]) {
|
||||
eps =
|
||||
(buyAmount - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS /
|
||||
makerTokenAmounts[i];
|
||||
if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (eps == 0 || eps > APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
|
||||
break;
|
||||
}
|
||||
// We do our best to close in on the requested amount, but we can either over buy or under buy and exit
|
||||
// if we hit a max iteration limit
|
||||
// We scale the sell amount to get the approximate target
|
||||
takerTokenAmounts[i] = _safeGetPartialAmountCeil(
|
||||
makerTokenAmounts[i],
|
||||
buyAmount,
|
||||
sellAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function _safeGetPartialAmountCeil(
|
||||
uint256 numerator,
|
||||
uint256 denominator,
|
||||
uint256 target
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256 partialAmount)
|
||||
{
|
||||
if (numerator == 0 || target == 0 || denominator == 0) return 0;
|
||||
uint256 c = numerator * target;
|
||||
if (c / numerator != target) return 0;
|
||||
return (c + (denominator - 1)) / denominator;
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
// ERC20 contract interface
|
||||
abstract contract IToken {
|
||||
/// @dev Query the balance of owner
|
||||
/// @param _owner The address from which the balance will be retrieved
|
||||
/// @return Balance of owner
|
||||
function balanceOf(address _owner) public virtual view returns (uint256);
|
||||
|
||||
/// @param _owner The address of the account owning tokens
|
||||
/// @param _spender The address of the account able to transfer the tokens
|
||||
/// @return Amount of remaining tokens allowed to spent
|
||||
function allowance(address _owner, address _spender) public virtual view returns (uint256);
|
||||
}
|
||||
|
||||
contract BalanceChecker {
|
||||
/*
|
||||
Check the token balances of wallet-token pairs.
|
||||
Pass 0xeee... as a "token" address to get ETH balance.
|
||||
Possible error throws:
|
||||
- extremely large arrays for user and or tokens (gas cost too high)
|
||||
|
||||
Returns a one-dimensional that's user.length long.
|
||||
*/
|
||||
function balances(address[] calldata users, address[] calldata tokens) external view returns (uint256[] memory) {
|
||||
// make sure the users array and tokens array are of equal length
|
||||
require(users.length == tokens.length, "users array is a different length than the tokens array");
|
||||
|
||||
uint256[] memory addrBalances = new uint256[](users.length);
|
||||
|
||||
for(uint i = 0; i < users.length; i++) {
|
||||
if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) {
|
||||
addrBalances[i] = IToken(tokens[i]).balanceOf(users[i]);
|
||||
} else {
|
||||
addrBalances[i] = users[i].balance; // ETH balance
|
||||
}
|
||||
}
|
||||
|
||||
return addrBalances;
|
||||
}
|
||||
|
||||
/*
|
||||
Check the token balances of wallet-token pairs with a spender contract for an allowance check.
|
||||
Pass 0xeee... as a "token" address to get ETH balance.
|
||||
Possible error throws:
|
||||
- extremely large arrays for user and or tokens (gas cost too high)
|
||||
|
||||
Returns a one-dimensional that's user.length long. It is the lesser of balance and allowance
|
||||
*/
|
||||
function getMinOfBalancesOrAllowances(address[] calldata users, address[] calldata tokens, address spender) external view returns (uint256[] memory) {
|
||||
// make sure the users array and tokens array are of equal length
|
||||
require(users.length == tokens.length, "users array is a different length than the tokens array");
|
||||
|
||||
uint256[] memory addrBalances = new uint256[](users.length);
|
||||
|
||||
for(uint i = 0; i < users.length; i++) {
|
||||
if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) {
|
||||
uint256 balance;
|
||||
uint256 allowance;
|
||||
balance = IToken(tokens[i]).balanceOf(users[i]);
|
||||
allowance = IToken(tokens[i]).allowance(users[i], spender);
|
||||
if (allowance < balance) {
|
||||
addrBalances[i] = allowance;
|
||||
} else {
|
||||
addrBalances[i] = balance;
|
||||
}
|
||||
} else {
|
||||
addrBalances[i] = users[i].balance; // ETH balance
|
||||
}
|
||||
}
|
||||
|
||||
return addrBalances;
|
||||
}
|
||||
|
||||
/*
|
||||
Check the allowances of an array of owner-spender-tokens
|
||||
|
||||
Returns 0 for 0xeee... (ETH)
|
||||
Possible error throws:
|
||||
- extremely large arrays for user and or tokens (gas cost too high)
|
||||
|
||||
Returns a one-dimensional array that's owners.length long.
|
||||
*/
|
||||
function allowances(address[] calldata owners, address[] calldata spenders, address[] calldata tokens) external view returns (uint256[] memory) {
|
||||
// make sure the arrays are all of equal length
|
||||
require(owners.length == spenders.length, "all arrays must be of equal length");
|
||||
require(owners.length == tokens.length, "all arrays must be of equal length");
|
||||
|
||||
uint256[] memory addrAllowances = new uint256[](owners.length);
|
||||
|
||||
for(uint i = 0; i < owners.length; i++) {
|
||||
if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) {
|
||||
addrAllowances[i] = IToken(tokens[i]).allowance(owners[i], spenders[i]);
|
||||
} else {
|
||||
// ETH
|
||||
addrAllowances[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return addrAllowances;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IBalancer.sol";
|
||||
|
||||
|
||||
contract BalancerSampler {
|
||||
|
||||
/// @dev Base gas limit for Balancer calls.
|
||||
uint256 constant private BALANCER_CALL_GAS = 300e3; // 300k
|
||||
|
||||
// Balancer math constants
|
||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol
|
||||
uint256 constant private BONE = 10 ** 18;
|
||||
uint256 constant private MAX_IN_RATIO = BONE / 2;
|
||||
uint256 constant private MAX_OUT_RATIO = (BONE / 3) + 1 wei;
|
||||
|
||||
struct BalancerState {
|
||||
uint256 takerTokenBalance;
|
||||
uint256 makerTokenBalance;
|
||||
uint256 takerTokenWeight;
|
||||
uint256 makerTokenWeight;
|
||||
uint256 swapFee;
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Balancer.
|
||||
/// @param poolAddress Address of the Balancer pool to query.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromBalancer(
|
||||
address poolAddress,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
IBalancer pool = IBalancer(poolAddress);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
BalancerState memory poolState;
|
||||
poolState.takerTokenBalance = pool.getBalance(takerToken);
|
||||
poolState.makerTokenBalance = pool.getBalance(makerToken);
|
||||
poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken);
|
||||
poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken);
|
||||
poolState.swapFee = pool.getSwapFee();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
// Handles this revert scenario:
|
||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443
|
||||
if (takerTokenAmounts[i] > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) {
|
||||
break;
|
||||
}
|
||||
try
|
||||
pool.calcOutGivenIn
|
||||
{gas: BALANCER_CALL_GAS}
|
||||
(
|
||||
poolState.takerTokenBalance,
|
||||
poolState.takerTokenWeight,
|
||||
poolState.makerTokenBalance,
|
||||
poolState.makerTokenWeight,
|
||||
takerTokenAmounts[i],
|
||||
poolState.swapFee
|
||||
)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Balancer.
|
||||
/// @param poolAddress Address of the Balancer pool to query.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromBalancer(
|
||||
address poolAddress,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
IBalancer pool = IBalancer(poolAddress);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
BalancerState memory poolState;
|
||||
poolState.takerTokenBalance = pool.getBalance(takerToken);
|
||||
poolState.makerTokenBalance = pool.getBalance(makerToken);
|
||||
poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken);
|
||||
poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken);
|
||||
poolState.swapFee = pool.getSwapFee();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
// Handles this revert scenario:
|
||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L505
|
||||
if (makerTokenAmounts[i] > _bmul(poolState.makerTokenBalance, MAX_OUT_RATIO)) {
|
||||
break;
|
||||
}
|
||||
try
|
||||
pool.calcInGivenOut
|
||||
{gas: BALANCER_CALL_GAS}
|
||||
(
|
||||
poolState.takerTokenBalance,
|
||||
poolState.takerTokenWeight,
|
||||
poolState.makerTokenBalance,
|
||||
poolState.makerTokenWeight,
|
||||
makerTokenAmounts[i],
|
||||
poolState.swapFee
|
||||
)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
// Handles this revert scenario:
|
||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443
|
||||
if (amount > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) {
|
||||
break;
|
||||
}
|
||||
|
||||
takerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Hacked version of Balancer's `bmul` function, returning 0 instead
|
||||
/// of reverting.
|
||||
/// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol#L63-L73
|
||||
/// @param a The first operand.
|
||||
/// @param b The second operand.
|
||||
/// @param c The result of the multiplication, or 0 if `bmul` would've reverted.
|
||||
function _bmul(uint256 a, uint256 b)
|
||||
private
|
||||
pure
|
||||
returns (uint256 c)
|
||||
{
|
||||
uint c0 = a * b;
|
||||
if (a != 0 && c0 / a != b) {
|
||||
return 0;
|
||||
}
|
||||
uint c1 = c0 + (BONE / 2);
|
||||
if (c1 < c0) {
|
||||
return 0;
|
||||
}
|
||||
uint c2 = c1 / BONE;
|
||||
return c2;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IBalancerV2Vault.sol";
|
||||
import "./BalancerV2Common.sol";
|
||||
|
||||
contract BalancerV2BatchSampler is BalancerV2Common {
|
||||
|
||||
// Replaces amount for first step with each takerTokenAmount and calls queryBatchSwap using supplied steps
|
||||
/// @dev Sample sell quotes from Balancer V2 supporting multihops.
|
||||
/// @param swapSteps Array of swap steps (can be >= 1).
|
||||
/// @param swapAssets Array of token address for swaps.
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
function sampleMultihopSellsFromBalancerV2(
|
||||
IBalancerV2Vault vault,
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps,
|
||||
address[] memory swapAssets,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
IBalancerV2Vault.FundManagement memory swapFunds =
|
||||
_createSwapFunds();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
swapSteps[0].amount = takerTokenAmounts[i];
|
||||
try
|
||||
// For sells we specify the takerToken which is what the vault will receive from the trade
|
||||
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_IN, swapSteps, swapAssets, swapFunds)
|
||||
// amounts represent pool balance deltas from the swap (incoming balance, outgoing balance)
|
||||
returns (int256[] memory amounts) {
|
||||
// Outgoing balance is negative so we need to flip the sign
|
||||
// Note - queryBatchSwap will return a delta for each token in the assets array and last asset should be tokenOut
|
||||
int256 amountOutFromPool = amounts[amounts.length - 1] * -1;
|
||||
if (amountOutFromPool <= 0) {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = uint256(amountOutFromPool);
|
||||
} catch {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces amount for first step with each makerTokenAmount and calls queryBatchSwap using supplied steps
|
||||
/// @dev Sample buy quotes from Balancer V2 supporting multihops.
|
||||
/// @param swapSteps Array of swap steps (can be >= 1).
|
||||
/// @param swapAssets Array of token address for swaps.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
function sampleMultihopBuysFromBalancerV2(
|
||||
IBalancerV2Vault vault,
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps,
|
||||
address[] memory swapAssets,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
IBalancerV2Vault.FundManagement memory swapFunds =
|
||||
_createSwapFunds();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
swapSteps[0].amount = makerTokenAmounts[i];
|
||||
try
|
||||
// Uses GIVEN_OUT type for Buy
|
||||
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_OUT, swapSteps, swapAssets, swapFunds)
|
||||
// amounts represent pool balance deltas from the swap (incoming balance, outgoing balance)
|
||||
returns (int256[] memory amounts) {
|
||||
int256 amountIntoPool = amounts[0];
|
||||
if (amountIntoPool <= 0) {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = uint256(amountIntoPool);
|
||||
} catch {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IBalancerV2Vault.sol";
|
||||
|
||||
|
||||
contract BalancerV2Common {
|
||||
|
||||
function _createSwapFunds()
|
||||
internal
|
||||
view
|
||||
returns (IBalancerV2Vault.FundManagement memory)
|
||||
{
|
||||
return
|
||||
IBalancerV2Vault.FundManagement({
|
||||
sender: address(this),
|
||||
fromInternalBalance: false,
|
||||
recipient: payable(address(this)),
|
||||
toInternalBalance: false
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,142 +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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./SamplerUtils.sol";
|
||||
import "./interfaces/IBalancerV2Vault.sol";
|
||||
import "./BalancerV2Common.sol";
|
||||
|
||||
|
||||
contract BalancerV2Sampler is SamplerUtils, BalancerV2Common {
|
||||
|
||||
/// @dev Sample sell quotes from Balancer V2.
|
||||
/// @param poolInfo Struct with pool related data
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromBalancerV2(
|
||||
IBalancerV2Vault.BalancerV2PoolInfo memory poolInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault);
|
||||
address[] memory swapAssets = new address[](2);
|
||||
swapAssets[0] = takerToken;
|
||||
swapAssets[1] = makerToken;
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
IBalancerV2Vault.FundManagement memory swapFunds =
|
||||
_createSwapFunds();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
||||
_createSwapSteps(poolInfo, takerTokenAmounts[i]);
|
||||
|
||||
try
|
||||
// For sells we specify the takerToken which is what the vault will receive from the trade
|
||||
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_IN, swapSteps, swapAssets, swapFunds)
|
||||
// amounts represent pool balance deltas from the swap (incoming balance, outgoing balance)
|
||||
returns (int256[] memory amounts) {
|
||||
// Outgoing balance is negative so we need to flip the sign
|
||||
int256 amountOutFromPool = amounts[amounts.length - 1] * -1;
|
||||
if (amountOutFromPool <= 0) {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = uint256(amountOutFromPool);
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Balancer V2.
|
||||
/// @param poolInfo Struct with pool related data
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromBalancerV2(
|
||||
IBalancerV2Vault.BalancerV2PoolInfo memory poolInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault);
|
||||
address[] memory swapAssets = new address[](2);
|
||||
swapAssets[0] = takerToken;
|
||||
swapAssets[1] = makerToken;
|
||||
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
IBalancerV2Vault.FundManagement memory swapFunds =
|
||||
_createSwapFunds();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
||||
_createSwapSteps(poolInfo, makerTokenAmounts[i]);
|
||||
|
||||
try
|
||||
// For buys we specify the makerToken which is what taker will receive from the trade
|
||||
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_OUT, swapSteps, swapAssets, swapFunds)
|
||||
returns (int256[] memory amounts) {
|
||||
int256 amountIntoPool = amounts[0];
|
||||
if (amountIntoPool <= 0) {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = uint256(amountIntoPool);
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _createSwapSteps(
|
||||
IBalancerV2Vault.BalancerV2PoolInfo memory poolInfo,
|
||||
uint256 amount
|
||||
) private pure returns (IBalancerV2Vault.BatchSwapStep[] memory) {
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
||||
new IBalancerV2Vault.BatchSwapStep[](1);
|
||||
swapSteps[0] = IBalancerV2Vault.BatchSwapStep({
|
||||
poolId: poolInfo.poolId,
|
||||
assetInIndex: 0,
|
||||
assetOutIndex: 1,
|
||||
amount: amount,
|
||||
userData: ""
|
||||
});
|
||||
|
||||
return swapSteps;
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IBancor.sol";
|
||||
|
||||
|
||||
contract BancorSampler {
|
||||
|
||||
/// @dev Base gas limit for Bancor calls.
|
||||
uint256 constant private BANCOR_CALL_GAS = 300e3; // 300k
|
||||
|
||||
struct BancorSamplerOpts {
|
||||
IBancorRegistry registry;
|
||||
address[][] paths;
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Bancor.
|
||||
/// @param opts BancorSamplerOpts The Bancor registry contract address and paths
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return bancorNetwork the Bancor Network address
|
||||
/// @return path the selected conversion path from bancor
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromBancor(
|
||||
BancorSamplerOpts memory opts,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (address bancorNetwork, address[] memory path, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
if (opts.paths.length == 0) {
|
||||
return (bancorNetwork, path, makerTokenAmounts);
|
||||
}
|
||||
(bancorNetwork, path) = _findBestPath(opts, takerToken, makerToken, takerTokenAmounts);
|
||||
makerTokenAmounts = new uint256[](takerTokenAmounts.length);
|
||||
|
||||
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
|
||||
try
|
||||
IBancorNetwork(bancorNetwork)
|
||||
.rateByPath
|
||||
{gas: BANCOR_CALL_GAS}
|
||||
(path, takerTokenAmounts[i])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (bancorNetwork, path, makerTokenAmounts);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Bancor. Unimplemented
|
||||
/// @param opts BancorSamplerOpts The Bancor registry contract address and paths
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return bancorNetwork the Bancor Network address
|
||||
/// @return path the selected conversion path from bancor
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromBancor(
|
||||
BancorSamplerOpts memory opts,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (address bancorNetwork, address[] memory path, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
}
|
||||
|
||||
function _findBestPath(
|
||||
BancorSamplerOpts memory opts,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (address bancorNetwork, address[] memory path)
|
||||
{
|
||||
bancorNetwork = opts.registry.getAddress(opts.registry.BANCOR_NETWORK());
|
||||
if (opts.paths.length == 0) {
|
||||
return (bancorNetwork, path);
|
||||
}
|
||||
uint256 maxBoughtAmount = 0;
|
||||
// Find the best path by selling the largest taker amount
|
||||
for (uint256 i = 0; i < opts.paths.length; i++) {
|
||||
if (opts.paths[i].length < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
IBancorNetwork(bancorNetwork)
|
||||
.rateByPath
|
||||
{gas: BANCOR_CALL_GAS}
|
||||
(opts.paths[i], takerTokenAmounts[takerTokenAmounts.length-1])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
if (amount > maxBoughtAmount) {
|
||||
maxBoughtAmount = amount;
|
||||
path = opts.paths[i];
|
||||
}
|
||||
} catch {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2022 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IBancorV3.sol";
|
||||
|
||||
|
||||
contract BancorV3Sampler
|
||||
{
|
||||
/// @dev Gas limit for BancorV3 calls.
|
||||
uint256 constant private BancorV3_CALL_GAS = 150e3; // 150k
|
||||
|
||||
address constant public ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||
|
||||
/// @dev Sample sell quotes from BancorV3.
|
||||
/// @param weth The WETH contract address
|
||||
/// @param router Router to look up tokens and amounts
|
||||
/// @param path Token route. Should be takerToken -> makerToken
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromBancorV3(
|
||||
address weth,
|
||||
address router,
|
||||
address[] memory path,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
if(path[0] == weth){
|
||||
path[0] = ETH;
|
||||
}
|
||||
if(path[1] == weth){
|
||||
path[1] = ETH;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IBancorV3(router).tradeOutputBySourceAmount(path[0], path[1], takerTokenAmounts[i])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from BancorV3.
|
||||
/// @param weth The WETH contract address
|
||||
/// @param router Router to look up tokens and amounts
|
||||
/// @param path Token route. Should be takerToken -> makerToken.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromBancorV3(
|
||||
address weth,
|
||||
address router,
|
||||
address[] memory path,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
if(path[0] == weth){
|
||||
path[0] = ETH;
|
||||
}
|
||||
if(path[1] == weth){
|
||||
path[1] = ETH;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IBancorV3(router).tradeInputByTargetAmount(path[0], path[1], makerTokenAmounts[i])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
takerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
|
||||
// Minimal CToken interface
|
||||
interface ICToken {
|
||||
function mint(uint mintAmount) external returns (uint);
|
||||
function redeem(uint redeemTokens) external returns (uint);
|
||||
function redeemUnderlying(uint redeemAmount) external returns (uint);
|
||||
function exchangeRateStored() external view returns (uint);
|
||||
function decimals() external view returns (uint8);
|
||||
}
|
||||
|
||||
contract CompoundSampler is SamplerUtils {
|
||||
uint256 constant private EXCHANGE_RATE_SCALE = 1e10;
|
||||
|
||||
function sampleSellsFromCompound(
|
||||
ICToken cToken,
|
||||
IERC20TokenV06 takerToken,
|
||||
IERC20TokenV06 makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
// Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals
|
||||
uint256 exchangeRate = cToken.exchangeRateStored();
|
||||
uint256 cTokenDecimals = uint256(cToken.decimals());
|
||||
|
||||
if (address(makerToken) == address(cToken)) {
|
||||
// mint
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] = (takerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals) / exchangeRate;
|
||||
}
|
||||
|
||||
} else if (address(takerToken) == address(cToken)) {
|
||||
// redeem
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] = (takerTokenAmounts[i] * exchangeRate) / (EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sampleBuysFromCompound(
|
||||
ICToken cToken,
|
||||
IERC20TokenV06 takerToken,
|
||||
IERC20TokenV06 makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
// Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals
|
||||
uint256 exchangeRate = cToken.exchangeRateStored();
|
||||
uint256 cTokenDecimals = uint256(cToken.decimals());
|
||||
|
||||
if (address(makerToken) == address(cToken)) {
|
||||
// mint
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
takerTokenAmounts[i] = makerTokenAmounts[i] * exchangeRate / (EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals);
|
||||
}
|
||||
} else if (address(takerToken) == address(cToken)) {
|
||||
// redeem
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
takerTokenAmounts[i] = (makerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals)/exchangeRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/ICurve.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract CurveSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
/// @dev Information for sampling from curve sources.
|
||||
struct CurveInfo {
|
||||
address poolAddress;
|
||||
bytes4 sellQuoteFunctionSelector;
|
||||
bytes4 buyQuoteFunctionSelector;
|
||||
}
|
||||
|
||||
/// @dev Base gas limit for Curve calls. Some Curves have multiple tokens
|
||||
/// So a reasonable ceil is 150k per token. Biggest Curve has 4 tokens.
|
||||
uint256 constant private CURVE_CALL_GAS = 2000e3; // Was 600k for Curve but SnowSwap is using 1500k+
|
||||
|
||||
/// @dev Sample sell quotes from Curve.
|
||||
/// @param curveInfo Curve information specific to this token pair.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromCurve(
|
||||
CurveInfo memory curveInfo,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
curveInfo.sellQuoteFunctionSelector,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Curve.
|
||||
/// @param curveInfo Curve information specific to this token pair.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromCurve(
|
||||
CurveInfo memory curveInfo,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
if (curveInfo.buyQuoteFunctionSelector == bytes4(0)) {
|
||||
// Buys not supported on this curve, so approximate it.
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(toTokenIdx, curveInfo),
|
||||
takerTokenData: abi.encode(fromTokenIdx, curveInfo),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromCurve
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
curveInfo.buyQuoteFunctionSelector,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
makerTokenAmounts[i]
|
||||
));
|
||||
uint256 sellAmount = 0;
|
||||
if (didSucceed) {
|
||||
sellAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromCurve(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(int128 takerTokenIdx, CurveInfo memory curveInfo) =
|
||||
abi.decode(takerTokenData, (int128, CurveInfo));
|
||||
(int128 makerTokenIdx) =
|
||||
abi.decode(makerTokenData, (int128));
|
||||
(bool success, bytes memory resultData) =
|
||||
address(this).staticcall(abi.encodeWithSelector(
|
||||
this.sampleSellsFromCurve.selector,
|
||||
curveInfo,
|
||||
takerTokenIdx,
|
||||
makerTokenIdx,
|
||||
_toSingleValueArray(sellAmount)
|
||||
));
|
||||
if (!success) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
interface IDODOZoo {
|
||||
function getDODO(address baseToken, address quoteToken) external view returns (address);
|
||||
}
|
||||
|
||||
interface IDODOHelper {
|
||||
function querySellQuoteToken(address dodo, uint256 amount) external view returns (uint256);
|
||||
}
|
||||
|
||||
interface IDODO {
|
||||
function querySellBaseToken(uint256 amount) external view returns (uint256);
|
||||
function _TRADE_ALLOWED_() external view returns (bool);
|
||||
}
|
||||
|
||||
contract DODOSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
|
||||
/// @dev Gas limit for DODO calls.
|
||||
uint256 constant private DODO_CALL_GAS = 300e3; // 300k
|
||||
struct DODOSamplerOpts {
|
||||
address registry;
|
||||
address helper;
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from DODO.
|
||||
/// @param opts DODOSamplerOpts DODO Registry and helper addresses
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return sellBase whether the bridge needs to sell the base token
|
||||
/// @return pool the DODO pool address
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromDODO(
|
||||
DODOSamplerOpts memory opts,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
pool = IDODOZoo(opts.registry).getDODO(takerToken, makerToken);
|
||||
address baseToken;
|
||||
// If pool exists we have the correct order of Base/Quote
|
||||
if (pool != address(0)) {
|
||||
baseToken = takerToken;
|
||||
sellBase = true;
|
||||
} else {
|
||||
pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken);
|
||||
// No pool either direction
|
||||
if (address(pool) == address(0)) {
|
||||
return (sellBase, pool, makerTokenAmounts);
|
||||
}
|
||||
baseToken = makerToken;
|
||||
sellBase = false;
|
||||
}
|
||||
|
||||
// DODO Pool has been disabled
|
||||
if (!IDODO(pool)._TRADE_ALLOWED_()) {
|
||||
return (sellBase, pool, makerTokenAmounts);
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 buyAmount = _sampleSellForApproximateBuyFromDODO(
|
||||
abi.encode(takerToken, pool, baseToken, opts.helper), // taker token data
|
||||
abi.encode(makerToken, pool, baseToken, opts.helper), // maker token data
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from DODO.
|
||||
/// @param opts DODOSamplerOpts DODO Registry and helper addresses
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return sellBase whether the bridge needs to sell the base token
|
||||
/// @return pool the DODO pool address
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromDODO(
|
||||
DODOSamplerOpts memory opts,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
// Pool is BASE/QUOTE
|
||||
// Look up the pool from the taker/maker combination
|
||||
pool = IDODOZoo(opts.registry).getDODO(takerToken, makerToken);
|
||||
address baseToken;
|
||||
// If pool exists we have the correct order of Base/Quote
|
||||
if (pool != address(0)) {
|
||||
baseToken = takerToken;
|
||||
sellBase = true;
|
||||
} else {
|
||||
// Look up the pool from the maker/taker combination
|
||||
pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken);
|
||||
// No pool either direction
|
||||
if (address(pool) == address(0)) {
|
||||
return (sellBase, pool, takerTokenAmounts);
|
||||
}
|
||||
baseToken = makerToken;
|
||||
sellBase = false;
|
||||
}
|
||||
|
||||
// DODO Pool has been disabled
|
||||
if (!IDODO(pool)._TRADE_ALLOWED_()) {
|
||||
return (sellBase, pool, takerTokenAmounts);
|
||||
}
|
||||
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, pool, baseToken, opts.helper),
|
||||
takerTokenData: abi.encode(takerToken, pool, baseToken, opts.helper),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromDODO
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromDODO(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory /* makerTokenData */,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
(address takerToken, address pool, address baseToken, address helper) = abi.decode(
|
||||
takerTokenData,
|
||||
(address, address, address, address)
|
||||
);
|
||||
|
||||
// We will get called to sell both the taker token and also to sell the maker token
|
||||
if (takerToken == baseToken) {
|
||||
// If base token then use the original query on the pool
|
||||
try
|
||||
IDODO(pool).querySellBaseToken
|
||||
{gas: DODO_CALL_GAS}
|
||||
(sellAmount)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
return amount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// If quote token then use helper, this is less accurate
|
||||
try
|
||||
IDODOHelper(helper).querySellQuoteToken
|
||||
{gas: DODO_CALL_GAS}
|
||||
(pool, sellAmount)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
return amount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,204 +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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
interface IDODOV2Registry {
|
||||
function getDODOPool(address baseToken, address quoteToken)
|
||||
external
|
||||
view
|
||||
returns (address[] memory machines);
|
||||
}
|
||||
|
||||
interface IDODOV2Pool {
|
||||
function querySellBase(address trader, uint256 payBaseAmount)
|
||||
external
|
||||
view
|
||||
returns (uint256 receiveQuoteAmount, uint256 mtFee);
|
||||
|
||||
function querySellQuote(address trader, uint256 payQuoteAmount)
|
||||
external
|
||||
view
|
||||
returns (uint256 receiveBaseAmount, uint256 mtFee);
|
||||
}
|
||||
|
||||
contract DODOV2Sampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
|
||||
/// @dev Gas limit for DODO V2 calls.
|
||||
uint256 constant private DODO_V2_CALL_GAS = 300e3; // 300k
|
||||
|
||||
/// @dev Sample sell quotes from DODO V2.
|
||||
/// @param registry Address of the registry to look up.
|
||||
/// @param offset offset index for the pool in the registry.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return sellBase whether the bridge needs to sell the base token
|
||||
/// @return pool the DODO pool address
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromDODOV2(
|
||||
address registry,
|
||||
uint256 offset,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
|
||||
if (pool == address(0)) {
|
||||
return (sellBase, pool, makerTokenAmounts);
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 buyAmount = _sampleSellForApproximateBuyFromDODOV2(
|
||||
abi.encode(takerToken, pool, sellBase), // taker token data
|
||||
abi.encode(makerToken, pool, sellBase), // maker token data
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from DODO.
|
||||
/// @param registry Address of the registry to look up.
|
||||
/// @param offset offset index for the pool in the registry.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return sellBase whether the bridge needs to sell the base token
|
||||
/// @return pool the DODO pool address
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromDODOV2(
|
||||
address registry,
|
||||
uint256 offset,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
|
||||
if (pool == address(0)) {
|
||||
return (sellBase, pool, takerTokenAmounts);
|
||||
}
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, pool, !sellBase),
|
||||
takerTokenData: abi.encode(takerToken, pool, sellBase),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromDODOV2
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromDODOV2(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory /* makerTokenData */,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
(address takerToken, address pool, bool sellBase) = abi.decode(
|
||||
takerTokenData,
|
||||
(address, address, bool)
|
||||
);
|
||||
|
||||
// We will get called to sell both the taker token and also to sell the maker token
|
||||
// since we use approximate buy for sell and buy functions
|
||||
if (sellBase) {
|
||||
try
|
||||
IDODOV2Pool(pool).querySellBase
|
||||
{ gas: DODO_V2_CALL_GAS }
|
||||
(address(0), sellAmount)
|
||||
returns (uint256 amount, uint256)
|
||||
{
|
||||
return amount;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
try
|
||||
IDODOV2Pool(pool).querySellQuote
|
||||
{ gas: DODO_V2_CALL_GAS }
|
||||
(address(0), sellAmount)
|
||||
returns (uint256 amount, uint256)
|
||||
{
|
||||
return amount;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _getNextDODOV2Pool(
|
||||
address registry,
|
||||
uint256 offset,
|
||||
address takerToken,
|
||||
address makerToken
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (address machine, bool sellBase)
|
||||
{
|
||||
// Query in base -> quote direction, if a pool is found then we are selling the base
|
||||
address[] memory machines = IDODOV2Registry(registry).getDODOPool(takerToken, makerToken);
|
||||
sellBase = true;
|
||||
if (machines.length == 0) {
|
||||
// Query in quote -> base direction, if a pool is found then we are selling the quote
|
||||
machines = IDODOV2Registry(registry).getDODOPool(makerToken, takerToken);
|
||||
sellBase = false;
|
||||
}
|
||||
|
||||
if (offset >= machines.length) {
|
||||
return (address(0), false);
|
||||
}
|
||||
|
||||
machine = machines[offset];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./BalancerSampler.sol";
|
||||
import "./BalancerV2Sampler.sol";
|
||||
import "./BalancerV2BatchSampler.sol";
|
||||
import "./BancorSampler.sol";
|
||||
import "./BancorV3Sampler.sol";
|
||||
import "./CompoundSampler.sol";
|
||||
import "./CurveSampler.sol";
|
||||
import "./DODOSampler.sol";
|
||||
import "./DODOV2Sampler.sol";
|
||||
import "./GMXSampler.sol";
|
||||
import "./KyberDmmSampler.sol";
|
||||
import "./LidoSampler.sol";
|
||||
import "./LiquidityProviderSampler.sol";
|
||||
import "./MakerPSMSampler.sol";
|
||||
import "./MStableSampler.sol";
|
||||
import "./MooniswapSampler.sol";
|
||||
import "./NativeOrderSampler.sol";
|
||||
import "./PlatypusSampler.sol";
|
||||
import "./ShellSampler.sol";
|
||||
import "./SynthetixSampler.sol";
|
||||
import "./TwoHopSampler.sol";
|
||||
import "./UniswapSampler.sol";
|
||||
import "./UniswapV2Sampler.sol";
|
||||
import "./UniswapV3Sampler.sol";
|
||||
import "./VelodromeSampler.sol";
|
||||
import "./WooPPSampler.sol";
|
||||
import "./UtilitySampler.sol";
|
||||
|
||||
|
||||
contract ERC20BridgeSampler is
|
||||
BalancerSampler,
|
||||
BalancerV2Sampler,
|
||||
BalancerV2BatchSampler,
|
||||
BancorSampler,
|
||||
BancorV3Sampler,
|
||||
CompoundSampler,
|
||||
CurveSampler,
|
||||
DODOSampler,
|
||||
DODOV2Sampler,
|
||||
GMXSampler,
|
||||
KyberDmmSampler,
|
||||
LidoSampler,
|
||||
LiquidityProviderSampler,
|
||||
MakerPSMSampler,
|
||||
MStableSampler,
|
||||
MooniswapSampler,
|
||||
NativeOrderSampler,
|
||||
PlatypusSampler,
|
||||
ShellSampler,
|
||||
SynthetixSampler,
|
||||
TwoHopSampler,
|
||||
UniswapSampler,
|
||||
UniswapV2Sampler,
|
||||
UniswapV3Sampler,
|
||||
VelodromeSampler,
|
||||
WooPPSampler,
|
||||
UtilitySampler
|
||||
{
|
||||
|
||||
struct CallResults {
|
||||
bytes data;
|
||||
bool success;
|
||||
}
|
||||
|
||||
/// @dev Call multiple public functions on this contract in a single transaction.
|
||||
/// @param callDatas ABI-encoded call data for each function call.
|
||||
/// @return callResults ABI-encoded results data for each call.
|
||||
function batchCall(bytes[] calldata callDatas)
|
||||
external
|
||||
returns (CallResults[] memory callResults)
|
||||
{
|
||||
callResults = new CallResults[](callDatas.length);
|
||||
for (uint256 i = 0; i != callDatas.length; ++i) {
|
||||
callResults[i].success = true;
|
||||
if (callDatas[i].length == 0) {
|
||||
continue;
|
||||
}
|
||||
(callResults[i].success, callResults[i].data) = address(this).call(callDatas[i]);
|
||||
}
|
||||
}
|
||||
|
||||
receive() external payable {}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
contract FakeTaker {
|
||||
|
||||
struct Result {
|
||||
bool success;
|
||||
bytes resultData;
|
||||
uint256 gasUsed;
|
||||
}
|
||||
|
||||
receive() payable external {}
|
||||
|
||||
function execute(address payable to, bytes calldata data)
|
||||
public
|
||||
payable
|
||||
returns (Result memory result)
|
||||
{
|
||||
uint256 gasBefore = gasleft();
|
||||
(
|
||||
result.success,
|
||||
result.resultData
|
||||
) = to.call{ value: msg.value }(data);
|
||||
result.gasUsed = gasBefore - gasleft();
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IGMX.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
contract GMXSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
struct GMXInfo {
|
||||
address reader;
|
||||
address vault;
|
||||
address[] path;
|
||||
}
|
||||
|
||||
function sampleSellsFromGMX(
|
||||
address reader,
|
||||
address vault,
|
||||
address[] memory path,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IGMX(reader).getAmountOut(IVault(vault), path[0], path[1], takerTokenAmounts[i])
|
||||
returns (uint256 amountAfterFees, uint256 feeAmount)
|
||||
{
|
||||
makerTokenAmounts[i] = amountAfterFees;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sampleBuysFromGMX(
|
||||
address reader,
|
||||
address vault,
|
||||
address[] memory path,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
address[] memory invertBuyPath = new address[](2);
|
||||
invertBuyPath[0] = path[1];
|
||||
invertBuyPath[1] = path[0];
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(reader, vault, invertBuyPath),
|
||||
takerTokenData: abi.encode(reader, vault, path),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromGMX
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function _sampleSellForApproximateBuyFromGMX(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(address _reader, address _vault, address[] memory _path ) = abi.decode(takerTokenData, (address, address, address[]));
|
||||
|
||||
(bool success, bytes memory resultData) = address(this).staticcall(abi.encodeWithSelector(
|
||||
this.sampleSellsFromGMX.selector,
|
||||
_reader,
|
||||
_vault,
|
||||
_path,
|
||||
_toSingleValueArray(sellAmount)
|
||||
));
|
||||
if(!success) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
interface IKyberDmmPool {
|
||||
|
||||
function totalSupply()
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
}
|
||||
|
||||
interface IKyberDmmFactory {
|
||||
|
||||
function getPools(address token0, address token1)
|
||||
external
|
||||
view
|
||||
returns (address[] memory _tokenPools);
|
||||
}
|
||||
|
||||
interface IKyberDmmRouter {
|
||||
|
||||
function factory() external view returns (address);
|
||||
|
||||
function getAmountsOut(uint256 amountIn, address[] calldata pools, address[] calldata path)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory amounts);
|
||||
|
||||
function getAmountsIn(uint256 amountOut, address[] calldata pools, address[] calldata path)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory amounts);
|
||||
}
|
||||
|
||||
|
||||
|
||||
contract KyberDmmSampler
|
||||
{
|
||||
/// @dev Gas limit for KyberDmm calls.
|
||||
uint256 constant private KYBER_DMM_CALL_GAS = 150e3; // 150k
|
||||
|
||||
/// @dev Sample sell quotes from KyberDmm.
|
||||
/// @param router Router to look up tokens and amounts
|
||||
/// @param path Token route. Should be takerToken -> makerToken
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return pools The pool addresses involved in the multi path trade
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromKyberDmm(
|
||||
address router,
|
||||
address[] memory path,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (address[] memory pools, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
pools = _getKyberDmmPools(router, path);
|
||||
if (pools.length == 0) {
|
||||
return (pools, makerTokenAmounts);
|
||||
}
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IKyberDmmRouter(router).getAmountsOut
|
||||
{gas: KYBER_DMM_CALL_GAS}
|
||||
(takerTokenAmounts[i], pools, path)
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
makerTokenAmounts[i] = amounts[path.length - 1];
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from KyberDmm.
|
||||
/// @param router Router to look up tokens and amounts
|
||||
/// @param path Token route. Should be takerToken -> makerToken.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return pools The pool addresses involved in the multi path trade
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromKyberDmm(
|
||||
address router,
|
||||
address[] memory path,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (address[] memory pools, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
pools = _getKyberDmmPools(router, path);
|
||||
if (pools.length == 0) {
|
||||
return (pools, takerTokenAmounts);
|
||||
}
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IKyberDmmRouter(router).getAmountsIn
|
||||
{gas: KYBER_DMM_CALL_GAS}
|
||||
(makerTokenAmounts[i], pools, path)
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
takerTokenAmounts[i] = amounts[0];
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _getKyberDmmPools(
|
||||
address router,
|
||||
address[] memory path
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (address[] memory pools)
|
||||
{
|
||||
IKyberDmmFactory factory = IKyberDmmFactory(IKyberDmmRouter(router).factory());
|
||||
pools = new address[](path.length - 1);
|
||||
for (uint256 i = 0; i < pools.length; i++) {
|
||||
// find the best pool
|
||||
address[] memory allPools;
|
||||
try
|
||||
factory.getPools
|
||||
{gas: KYBER_DMM_CALL_GAS}
|
||||
(path[i], path[i + 1])
|
||||
returns (address[] memory allPools)
|
||||
{
|
||||
if (allPools.length == 0) {
|
||||
return new address[](0);
|
||||
}
|
||||
|
||||
uint256 maxSupply = 0;
|
||||
for (uint256 j = 0; j < allPools.length; j++) {
|
||||
uint256 totalSupply = IKyberDmmPool(allPools[j]).totalSupply();
|
||||
if (totalSupply > maxSupply) {
|
||||
maxSupply = totalSupply;
|
||||
pools[i] = allPools[j];
|
||||
}
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
return new address[](0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
interface IWstETH {
|
||||
function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256);
|
||||
function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256);
|
||||
}
|
||||
|
||||
|
||||
contract LidoSampler is SamplerUtils {
|
||||
struct LidoInfo {
|
||||
address stEthToken;
|
||||
address wethToken;
|
||||
address wstEthToken;
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Lido
|
||||
/// @param lidoInfo Info regarding a specific Lido deployment
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromLido(
|
||||
LidoInfo memory lidoInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
|
||||
if (takerToken == lidoInfo.wethToken && makerToken == address(lidoInfo.stEthToken)) {
|
||||
// Minting stETH is always 1:1 therefore we can just return the same amounts back.
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
return _sampleSellsForWrapped(lidoInfo, takerToken, makerToken, takerTokenAmounts);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Lido.
|
||||
/// @param lidoInfo Info regarding a specific Lido deployment
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromLido(
|
||||
LidoInfo memory lidoInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
if (takerToken == lidoInfo.wethToken && makerToken == address(lidoInfo.stEthToken)) {
|
||||
// Minting stETH is always 1:1 therefore we can just return the same amounts back.
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
// Swap out `makerToken` and `takerToken` and re-use `_sampleSellsForWrapped`.
|
||||
return _sampleSellsForWrapped(lidoInfo, makerToken, takerToken, makerTokenAmounts);
|
||||
}
|
||||
|
||||
function _sampleSellsForWrapped(
|
||||
LidoInfo memory lidoInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
) private view returns (uint256[] memory) {
|
||||
IWstETH wstETH = IWstETH(lidoInfo.wstEthToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
uint256[] memory makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
if (takerToken == lidoInfo.stEthToken && makerToken == lidoInfo.wstEthToken) {
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] = wstETH.getWstETHByStETH(takerTokenAmounts[i]);
|
||||
}
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
if (takerToken == lidoInfo.wstEthToken && makerToken == lidoInfo.stEthToken) {
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] = wstETH.getStETHByWstETH(takerTokenAmounts[i]);
|
||||
}
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
// Returns 0 values.
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/vendor/ILiquidityProvider.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract LiquidityProviderSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
/// @dev Default gas limit for liquidity provider calls.
|
||||
uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k
|
||||
|
||||
/// @dev Sample sell quotes from an arbitrary on-chain liquidity provider.
|
||||
/// @param providerAddress Address of the liquidity provider.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromLiquidityProvider(
|
||||
address providerAddress,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
ILiquidityProvider(providerAddress).getSellQuote
|
||||
{gas: DEFAULT_CALL_GAS}
|
||||
(
|
||||
IERC20TokenV06(takerToken),
|
||||
IERC20TokenV06(makerToken),
|
||||
takerTokenAmounts[i]
|
||||
)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from an arbitrary on-chain liquidity provider.
|
||||
/// @param providerAddress Address of the liquidity provider.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromLiquidityProvider(
|
||||
address providerAddress,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, providerAddress),
|
||||
takerTokenData: abi.encode(takerToken, providerAddress),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromLiquidityProvider
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromLiquidityProvider(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(address takerToken, address providerAddress) =
|
||||
abi.decode(takerTokenData, (address, address));
|
||||
(address makerToken) =
|
||||
abi.decode(makerTokenData, (address));
|
||||
try
|
||||
this.sampleSellsFromLiquidityProvider
|
||||
{gas: DEFAULT_CALL_GAS}
|
||||
(providerAddress, takerToken, makerToken, _toSingleValueArray(sellAmount))
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
return amounts[0];
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IMStable.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract MStableSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
/// @dev Default gas limit for mStable calls.
|
||||
uint256 constant private DEFAULT_CALL_GAS = 800e3; // 800k
|
||||
|
||||
/// @dev Sample sell quotes from the mStable contract
|
||||
/// @param router Address of the mStable contract
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromMStable(
|
||||
address router,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IMStable(router).getSwapOutput
|
||||
{gas: DEFAULT_CALL_GAS}
|
||||
(takerToken, makerToken, takerTokenAmounts[i])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from MStable contract
|
||||
/// @param router Address of the mStable contract
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromMStable(
|
||||
address router,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, router),
|
||||
takerTokenData: abi.encode(takerToken, router),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromMStable
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromMStable(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(address takerToken, address router) =
|
||||
abi.decode(takerTokenData, (address, address));
|
||||
(address makerToken) =
|
||||
abi.decode(makerTokenData, (address));
|
||||
try
|
||||
this.sampleSellsFromMStable
|
||||
(router, takerToken, makerToken, _toSingleValueArray(sellAmount))
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
return amounts[0];
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,267 +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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||
|
||||
interface IPSM {
|
||||
// @dev Get the fee for selling USDC to DAI in PSM
|
||||
// @return tin toll in [wad]
|
||||
function tin() external view returns (uint256);
|
||||
// @dev Get the fee for selling DAI to USDC in PSM
|
||||
// @return tout toll out [wad]
|
||||
function tout() external view returns (uint256);
|
||||
|
||||
// @dev Get the address of the PSM state Vat
|
||||
// @return address of the Vat
|
||||
function vat() external view returns (address);
|
||||
|
||||
// @dev Get the address of the underlying vault powering PSM
|
||||
// @return address of gemJoin contract
|
||||
function gemJoin() external view returns (address);
|
||||
|
||||
// @dev Get the address of DAI
|
||||
// @return address of DAI contract
|
||||
function dai() external view returns (address);
|
||||
|
||||
// @dev Sell USDC for DAI
|
||||
// @param usr The address of the account trading USDC for DAI.
|
||||
// @param gemAmt The amount of USDC to sell in USDC base units
|
||||
function sellGem(
|
||||
address usr,
|
||||
uint256 gemAmt
|
||||
) external;
|
||||
// @dev Buy USDC for DAI
|
||||
// @param usr The address of the account trading DAI for USDC
|
||||
// @param gemAmt The amount of USDC to buy in USDC base units
|
||||
function buyGem(
|
||||
address usr,
|
||||
uint256 gemAmt
|
||||
) external;
|
||||
}
|
||||
|
||||
interface IVAT {
|
||||
// @dev Get a collateral type by identifier
|
||||
// @param ilkIdentifier bytes32 identifier. Example: ethers.utils.formatBytes32String("PSM-USDC-A")
|
||||
// @return ilk
|
||||
// @return ilk.Art Total Normalised Debt in wad
|
||||
// @return ilk.rate Accumulated Rates in ray
|
||||
// @return ilk.spot Price with Safety Margin in ray
|
||||
// @return ilk.line Debt Ceiling in rad
|
||||
// @return ilk.dust Urn Debt Floor in rad
|
||||
function ilks(
|
||||
bytes32 ilkIdentifier
|
||||
) external view returns (
|
||||
uint256 Art,
|
||||
uint256 rate,
|
||||
uint256 spot,
|
||||
uint256 line,
|
||||
uint256 dust
|
||||
);
|
||||
}
|
||||
|
||||
contract MakerPSMSampler is
|
||||
SamplerUtils
|
||||
{
|
||||
using LibSafeMathV06 for uint256;
|
||||
|
||||
/// @dev Information about which PSM module to use
|
||||
struct MakerPsmInfo {
|
||||
address psmAddress;
|
||||
bytes32 ilkIdentifier;
|
||||
address gemTokenAddress;
|
||||
}
|
||||
|
||||
/// @dev Gas limit for MakerPsm calls.
|
||||
uint256 constant private MAKER_PSM_CALL_GAS = 300e3; // 300k
|
||||
|
||||
|
||||
// Maker units
|
||||
// wad: fixed point decimal with 18 decimals (for basic quantities, e.g. balances)
|
||||
uint256 constant private WAD = 10 ** 18;
|
||||
// ray: fixed point decimal with 27 decimals (for precise quantites, e.g. ratios)
|
||||
uint256 constant private RAY = 10 ** 27;
|
||||
// rad: fixed point decimal with 45 decimals (result of integer multiplication with a wad and a ray)
|
||||
uint256 constant private RAD = 10 ** 45;
|
||||
// See https://github.com/makerdao/dss/blob/master/DEVELOPING.m
|
||||
|
||||
/// @dev Sample sell quotes from Maker PSM
|
||||
function sampleSellsFromMakerPsm(
|
||||
MakerPsmInfo memory psmInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
IPSM psm = IPSM(psmInfo.psmAddress);
|
||||
IVAT vat = IVAT(psm.vat());
|
||||
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
if (makerToken != psm.dai() && takerToken != psm.dai()) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 buyAmount = _samplePSMSell(psmInfo, makerToken, takerToken, takerTokenAmounts[i], psm, vat);
|
||||
|
||||
if (buyAmount == 0) {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
}
|
||||
|
||||
function sampleBuysFromMakerPsm(
|
||||
MakerPsmInfo memory psmInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
IPSM psm = IPSM(psmInfo.psmAddress);
|
||||
IVAT vat = IVAT(psm.vat());
|
||||
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
if (makerToken != psm.dai() && takerToken != psm.dai()) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 sellAmount = _samplePSMBuy(psmInfo, makerToken, takerToken, makerTokenAmounts[i], psm, vat);
|
||||
|
||||
if (sellAmount == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function _samplePSMSell(MakerPsmInfo memory psmInfo, address makerToken, address takerToken, uint256 takerTokenAmount, IPSM psm, IVAT vat)
|
||||
private
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
(uint256 totalDebtInWad,,, uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(psmInfo.ilkIdentifier);
|
||||
uint256 gemTokenBaseUnit = uint256(1e6);
|
||||
|
||||
if (takerToken == psmInfo.gemTokenAddress) {
|
||||
// Simulate sellGem
|
||||
// Selling USDC to the PSM, increasing the total debt
|
||||
// Convert USDC 6 decimals to 18 decimals [wad]
|
||||
uint256 takerTokenAmountInWad = takerTokenAmount.safeMul(1e12);
|
||||
|
||||
uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY);
|
||||
|
||||
// PSM is too full to fit
|
||||
if (newTotalDebtInRad >= debtCeilingInRad) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint256 feeInWad = takerTokenAmountInWad.safeMul(psm.tin()).safeDiv(WAD);
|
||||
uint256 makerTokenAmountInWad = takerTokenAmountInWad.safeSub(feeInWad);
|
||||
|
||||
return makerTokenAmountInWad;
|
||||
} else if (makerToken == psmInfo.gemTokenAddress) {
|
||||
// Simulate buyGem
|
||||
// Buying USDC from the PSM, decreasing the total debt
|
||||
// Selling DAI for USDC, already in 18 decimals [wad]
|
||||
uint256 takerTokenAmountInWad = takerTokenAmount;
|
||||
if (takerTokenAmountInWad > totalDebtInWad) {
|
||||
return 0;
|
||||
}
|
||||
uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY);
|
||||
|
||||
// PSM is empty, not enough USDC to buy from it
|
||||
if (newTotalDebtInRad <= debtFloorInRad) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint256 feeDivisorInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout;
|
||||
uint256 makerTokenAmountInGemTokenBaseUnits = takerTokenAmountInWad.safeMul(gemTokenBaseUnit).safeDiv(feeDivisorInWad);
|
||||
|
||||
return makerTokenAmountInGemTokenBaseUnits;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function _samplePSMBuy(MakerPsmInfo memory psmInfo, address makerToken, address takerToken, uint256 makerTokenAmount, IPSM psm, IVAT vat)
|
||||
private
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
(uint256 totalDebtInWad,,, uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(psmInfo.ilkIdentifier);
|
||||
|
||||
if (takerToken == psmInfo.gemTokenAddress) {
|
||||
// Simulate sellGem
|
||||
// Selling USDC to the PSM, increasing the total debt
|
||||
uint256 makerTokenAmountInWad = makerTokenAmount;
|
||||
uint256 feeDivisorInWad = WAD.safeSub(psm.tin()); // eg. 0.999 * 10 ** 18 with 0.1% tin;
|
||||
uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(WAD).safeDiv(feeDivisorInWad);
|
||||
uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY);
|
||||
|
||||
// PSM is too full to fit
|
||||
if (newTotalDebtInRad >= debtCeilingInRad) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint256 takerTokenAmountInGemInGemBaseUnits = (takerTokenAmountInWad.safeDiv(1e12)).safeAdd(1); // Add 1 to deal with cut off decimals converting to lower decimals
|
||||
|
||||
return takerTokenAmountInGemInGemBaseUnits;
|
||||
} else if (makerToken == psmInfo.gemTokenAddress) {
|
||||
// Simulate buyGem
|
||||
// Buying USDC from the PSM, decreasing the total debt
|
||||
uint256 makerTokenAmountInWad = makerTokenAmount.safeMul(1e12);
|
||||
uint256 feeMultiplierInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout;
|
||||
uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(feeMultiplierInWad).safeDiv(WAD);
|
||||
if (takerTokenAmountInWad > totalDebtInWad) {
|
||||
return 0;
|
||||
}
|
||||
uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY);
|
||||
|
||||
// PSM is empty, not enough USDC to buy
|
||||
if (newTotalDebtInRad <= debtFloorInRad) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
return takerTokenAmountInWad;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IMooniswap.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract MooniswapSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
/// @dev Gas limit for Mooniswap calls.
|
||||
uint256 constant private MOONISWAP_CALL_GAS = 150e3; // 150k
|
||||
|
||||
/// @dev Sample sell quotes from Mooniswap.
|
||||
/// @param registry Address of the Mooniswap Registry.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return pool The contract address for the pool
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromMooniswap(
|
||||
address registry,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (IMooniswap pool, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 buyAmount = sampleSingleSellFromMooniswapPool(
|
||||
registry,
|
||||
takerToken,
|
||||
makerToken,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pool = IMooniswap(
|
||||
IMooniswapRegistry(registry).pools(takerToken, makerToken)
|
||||
);
|
||||
}
|
||||
|
||||
function sampleSingleSellFromMooniswapPool(
|
||||
address registry,
|
||||
address mooniswapTakerToken,
|
||||
address mooniswapMakerToken,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
// Find the pool for the pair.
|
||||
IMooniswap pool = IMooniswap(
|
||||
IMooniswapRegistry(registry).pools(mooniswapTakerToken, mooniswapMakerToken)
|
||||
);
|
||||
// If there is no pool then return early
|
||||
if (address(pool) == address(0)) {
|
||||
return 0;
|
||||
}
|
||||
uint256 poolBalance = mooniswapTakerToken == address(0)
|
||||
? address(pool).balance
|
||||
: IERC20TokenV06(mooniswapTakerToken).balanceOf(address(pool));
|
||||
// If the pool balance is smaller than the sell amount
|
||||
// don't sample to avoid multiplication overflow in buys
|
||||
if (poolBalance < takerTokenAmount) {
|
||||
return 0;
|
||||
}
|
||||
try
|
||||
pool.getReturn
|
||||
{gas: MOONISWAP_CALL_GAS}
|
||||
(mooniswapTakerToken, mooniswapMakerToken, takerTokenAmount)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
return amount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Mooniswap.
|
||||
/// @param registry Address of the Mooniswap Registry.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return pool The contract address for the pool
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromMooniswap(
|
||||
address registry,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (IMooniswap pool, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(registry, makerToken),
|
||||
takerTokenData: abi.encode(registry, takerToken),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
|
||||
pool = IMooniswap(
|
||||
IMooniswapRegistry(registry).pools(takerToken, makerToken)
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromMooniswap(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(address registry, address mooniswapTakerToken) = abi.decode(takerTokenData, (address, address));
|
||||
(address _registry, address mooniswapMakerToken) = abi.decode(makerTokenData, (address, address));
|
||||
return sampleSingleSellFromMooniswapPool(
|
||||
registry,
|
||||
mooniswapTakerToken,
|
||||
mooniswapMakerToken,
|
||||
sellAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,239 +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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
|
||||
|
||||
interface IExchange {
|
||||
|
||||
enum OrderStatus {
|
||||
INVALID,
|
||||
FILLABLE,
|
||||
FILLED,
|
||||
CANCELLED,
|
||||
EXPIRED
|
||||
}
|
||||
|
||||
/// @dev A standard OTC or OO limit order.
|
||||
struct LimitOrder {
|
||||
IERC20TokenV06 makerToken;
|
||||
IERC20TokenV06 takerToken;
|
||||
uint128 makerAmount;
|
||||
uint128 takerAmount;
|
||||
uint128 takerTokenFeeAmount;
|
||||
address maker;
|
||||
address taker;
|
||||
address sender;
|
||||
address feeRecipient;
|
||||
bytes32 pool;
|
||||
uint64 expiry;
|
||||
uint256 salt;
|
||||
}
|
||||
|
||||
/// @dev An RFQ limit order.
|
||||
struct RfqOrder {
|
||||
IERC20TokenV06 makerToken;
|
||||
IERC20TokenV06 takerToken;
|
||||
uint128 makerAmount;
|
||||
uint128 takerAmount;
|
||||
address maker;
|
||||
address taker;
|
||||
address txOrigin;
|
||||
bytes32 pool;
|
||||
uint64 expiry;
|
||||
uint256 salt;
|
||||
}
|
||||
|
||||
/// @dev Info on a limit or RFQ order.
|
||||
struct OrderInfo {
|
||||
bytes32 orderHash;
|
||||
OrderStatus status;
|
||||
uint128 takerTokenFilledAmount;
|
||||
}
|
||||
|
||||
/// @dev Allowed signature types.
|
||||
enum SignatureType {
|
||||
ILLEGAL,
|
||||
INVALID,
|
||||
EIP712,
|
||||
ETHSIGN
|
||||
}
|
||||
|
||||
/// @dev Encoded EC signature.
|
||||
struct Signature {
|
||||
// How to validate the signature.
|
||||
SignatureType signatureType;
|
||||
// EC Signature data.
|
||||
uint8 v;
|
||||
// EC Signature data.
|
||||
bytes32 r;
|
||||
// EC Signature data.
|
||||
bytes32 s;
|
||||
}
|
||||
|
||||
/// @dev Get the order info for a limit order.
|
||||
/// @param order The limit order.
|
||||
/// @return orderInfo Info about the order.
|
||||
function getLimitOrderInfo(LimitOrder memory order)
|
||||
external
|
||||
view
|
||||
returns (OrderInfo memory orderInfo);
|
||||
|
||||
/// @dev Get order info, fillable amount, and signature validity for a limit order.
|
||||
/// Fillable amount is determined using balances and allowances of the maker.
|
||||
/// @param order The limit order.
|
||||
/// @param signature The order signature.
|
||||
/// @return orderInfo Info about the order.
|
||||
/// @return actualFillableTakerTokenAmount How much of the order is fillable
|
||||
/// based on maker funds, in taker tokens.
|
||||
/// @return isSignatureValid Whether the signature is valid.
|
||||
function getLimitOrderRelevantState(
|
||||
LimitOrder memory order,
|
||||
Signature calldata signature
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
OrderInfo memory orderInfo,
|
||||
uint128 actualFillableTakerTokenAmount,
|
||||
bool isSignatureValid
|
||||
);
|
||||
}
|
||||
|
||||
contract NativeOrderSampler {
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibBytesV06 for bytes;
|
||||
|
||||
/// @dev Gas limit for calls to `getOrderFillableTakerAmount()`.
|
||||
uint256 constant internal DEFAULT_CALL_GAS = 200e3; // 200k
|
||||
|
||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
||||
/// Effectively ignores orders that have empty signatures or
|
||||
/// maker/taker asset amounts (returning 0).
|
||||
/// @param orders Native limit orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param exchange The V4 exchange.
|
||||
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getLimitOrderFillableTakerAssetAmounts(
|
||||
IExchange.LimitOrder[] memory orders,
|
||||
IExchange.Signature[] memory orderSignatures,
|
||||
IExchange exchange
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory orderFillableTakerAssetAmounts)
|
||||
{
|
||||
orderFillableTakerAssetAmounts = new uint256[](orders.length);
|
||||
for (uint256 i = 0; i != orders.length; i++) {
|
||||
try
|
||||
this.getLimitOrderFillableTakerAmount
|
||||
{gas: DEFAULT_CALL_GAS}
|
||||
(
|
||||
orders[i],
|
||||
orderSignatures[i],
|
||||
exchange
|
||||
)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
orderFillableTakerAssetAmounts[i] = amount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
orderFillableTakerAssetAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
||||
/// Effectively ignores orders that have empty signatures or
|
||||
/// @param orders Native orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param exchange The V4 exchange.
|
||||
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getLimitOrderFillableMakerAssetAmounts(
|
||||
IExchange.LimitOrder[] memory orders,
|
||||
IExchange.Signature[] memory orderSignatures,
|
||||
IExchange exchange
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory orderFillableMakerAssetAmounts)
|
||||
{
|
||||
orderFillableMakerAssetAmounts = getLimitOrderFillableTakerAssetAmounts(
|
||||
orders,
|
||||
orderSignatures,
|
||||
exchange
|
||||
);
|
||||
// `orderFillableMakerAssetAmounts` now holds taker asset amounts, so
|
||||
// convert them to maker asset amounts.
|
||||
for (uint256 i = 0; i < orders.length; ++i) {
|
||||
if (orderFillableMakerAssetAmounts[i] != 0) {
|
||||
orderFillableMakerAssetAmounts[i] = LibMathV06.getPartialAmountCeil(
|
||||
orderFillableMakerAssetAmounts[i],
|
||||
orders[i].takerAmount,
|
||||
orders[i].makerAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Get the fillable taker amount of an order, taking into account
|
||||
/// order state, maker fees, and maker balances.
|
||||
function getLimitOrderFillableTakerAmount(
|
||||
IExchange.LimitOrder memory order,
|
||||
IExchange.Signature memory signature,
|
||||
IExchange exchange
|
||||
)
|
||||
virtual
|
||||
public
|
||||
view
|
||||
returns (uint256 fillableTakerAmount)
|
||||
{
|
||||
if (signature.signatureType == IExchange.SignatureType.ILLEGAL ||
|
||||
signature.signatureType == IExchange.SignatureType.INVALID ||
|
||||
order.makerAmount == 0 ||
|
||||
order.takerAmount == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
(
|
||||
IExchange.OrderInfo memory orderInfo,
|
||||
uint128 remainingFillableTakerAmount,
|
||||
bool isSignatureValid
|
||||
) = exchange.getLimitOrderRelevantState(order, signature);
|
||||
|
||||
if (
|
||||
orderInfo.status != IExchange.OrderStatus.FILLABLE ||
|
||||
!isSignatureValid ||
|
||||
order.makerToken == IERC20TokenV06(0)
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fillableTakerAmount = uint256(remainingFillableTakerAmount);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IPlatypus.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract PlatypusSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
|
||||
function sampleSellsFromPlatypus(
|
||||
address pool,
|
||||
address[] memory path,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IPlatypus(pool).quotePotentialSwap(path[0], path[1], takerTokenAmounts[i])
|
||||
returns (uint256 amountAfterFees, uint256 feeAmount)
|
||||
{
|
||||
makerTokenAmounts[i] = amountAfterFees;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory result) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sampleBuysFromPlatypus(
|
||||
address pool,
|
||||
address[] memory path,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
address[] memory invertBuyPath = new address[](2);
|
||||
invertBuyPath[0] = path[1];
|
||||
invertBuyPath[1] = path[0];
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(pool, invertBuyPath),
|
||||
takerTokenData: abi.encode(pool, path),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromPlatypus
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function _sampleSellForApproximateBuyFromPlatypus(
|
||||
bytes memory makerTokenData,
|
||||
bytes memory takerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(address _pool, address[] memory _path ) = abi.decode(makerTokenData, (address, address[]));
|
||||
|
||||
(bool success, bytes memory resultData) = address(this).staticcall(abi.encodeWithSelector(
|
||||
this.sampleSellsFromPlatypus.selector,
|
||||
_pool,
|
||||
_path,
|
||||
_toSingleValueArray(sellAmount)
|
||||
));
|
||||
if(!success) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
|
||||
|
||||
contract SamplerUtils {
|
||||
|
||||
/// @dev Overridable way to get token decimals.
|
||||
/// @param tokenAddress Address of the token.
|
||||
/// @return decimals The decimal places for the token.
|
||||
function _getTokenDecimals(address tokenAddress)
|
||||
virtual
|
||||
internal
|
||||
view
|
||||
returns (uint8 decimals)
|
||||
{
|
||||
return LibERC20TokenV06.compatDecimals(IERC20TokenV06(tokenAddress));
|
||||
}
|
||||
|
||||
function _toSingleValueArray(uint256 v)
|
||||
internal
|
||||
pure
|
||||
returns (uint256[] memory arr)
|
||||
{
|
||||
arr = new uint256[](1);
|
||||
arr[0] = v;
|
||||
}
|
||||
|
||||
/// @dev Assert that the tokens in a trade pair are valid.
|
||||
/// @param makerToken Address of the maker token.
|
||||
/// @param takerToken Address of the taker token.
|
||||
function _assertValidPair(address makerToken, address takerToken)
|
||||
internal
|
||||
pure
|
||||
{
|
||||
require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR");
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./interfaces/IShell.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract ShellSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
|
||||
struct ShellInfo {
|
||||
address poolAddress;
|
||||
}
|
||||
|
||||
/// @dev Default gas limit for Shell calls.
|
||||
uint256 constant private DEFAULT_CALL_GAS = 300e3; // 300k
|
||||
|
||||
/// @dev Sample sell quotes from the Shell pool contract
|
||||
/// @param pool Address of the Shell pool contract
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromShell(
|
||||
address pool,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IShell(pool).viewOriginSwap
|
||||
{gas: DEFAULT_CALL_GAS}
|
||||
(takerToken, makerToken, takerTokenAmounts[i])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Shell pool contract
|
||||
/// @param pool Address of the Shell pool contract
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromShell(
|
||||
address pool,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, pool),
|
||||
takerTokenData: abi.encode(takerToken, pool),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromShell
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromShell(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(address takerToken, address pool) = abi.decode(takerTokenData, (address, address));
|
||||
(address makerToken) = abi.decode(makerTokenData, (address));
|
||||
|
||||
try
|
||||
this.sampleSellsFromShell
|
||||
(pool, takerToken, makerToken, _toSingleValueArray(sellAmount))
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
return amounts[0];
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2022 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
interface IReadProxyAddressResolver {
|
||||
function target() external view returns (address);
|
||||
}
|
||||
|
||||
interface IAddressResolver {
|
||||
function getAddress(bytes32 name) external view returns (address);
|
||||
}
|
||||
|
||||
interface IExchanger {
|
||||
// Ethereum Mainnet
|
||||
function getAmountsForAtomicExchange(
|
||||
uint256 sourceAmount,
|
||||
bytes32 sourceCurrencyKey,
|
||||
bytes32 destinationCurrencyKey
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
uint256 amountReceived,
|
||||
uint256 fee,
|
||||
uint256 exchangeFeeRate
|
||||
);
|
||||
|
||||
// Optimism
|
||||
function getAmountsForExchange(
|
||||
uint256 sourceAmount,
|
||||
bytes32 sourceCurrencyKey,
|
||||
bytes32 destinationCurrencyKey
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
uint256 amountReceived,
|
||||
uint256 fee,
|
||||
uint256 exchangeFeeRate
|
||||
);
|
||||
}
|
||||
|
||||
contract SynthetixSampler {
|
||||
|
||||
/// @dev Sample sell quotes from Synthetix Atomic Swap.
|
||||
/// @param takerTokenSymbol Symbol (currency key) of the taker token (what to sell).
|
||||
/// @param makerTokenSymbol Symbol (currency key) of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample (sorted in ascending order).
|
||||
/// @return synthetix Synthetix address.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
|
||||
function sampleSellsFromSynthetix(
|
||||
IReadProxyAddressResolver readProxy,
|
||||
bytes32 takerTokenSymbol,
|
||||
bytes32 makerTokenSymbol,
|
||||
uint256[] memory takerTokenAmounts
|
||||
) public view returns (address synthetix, uint256[] memory makerTokenAmounts) {
|
||||
synthetix = getSynthetixAddress(readProxy);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
if (numSamples == 0) {
|
||||
return (synthetix, makerTokenAmounts);
|
||||
}
|
||||
|
||||
makerTokenAmounts[0] = exchange(
|
||||
readProxy,
|
||||
takerTokenAmounts[0],
|
||||
takerTokenSymbol,
|
||||
makerTokenSymbol
|
||||
);
|
||||
|
||||
// Synthetix atomic swap has a fixed rate. Calculate the rest based on the first value (and save gas).
|
||||
for (uint256 i = 1; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] =
|
||||
(makerTokenAmounts[0] * takerTokenAmounts[i]) /
|
||||
takerTokenAmounts[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Synthetix Atomic Swap.
|
||||
/// @param takerTokenSymbol Symbol (currency key) of the taker token (what to sell).
|
||||
/// @param makerTokenSymbol Symbol (currency key) of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample (sorted in ascending order).
|
||||
/// @return synthetix Synthetix address.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
|
||||
function sampleBuysFromSynthetix(
|
||||
IReadProxyAddressResolver readProxy,
|
||||
bytes32 takerTokenSymbol,
|
||||
bytes32 makerTokenSymbol,
|
||||
uint256[] memory makerTokenAmounts
|
||||
) public view returns (address synthetix, uint256[] memory takerTokenAmounts) {
|
||||
synthetix = getSynthetixAddress(readProxy);
|
||||
// Since Synthetix atomic have a fixed rate, we can pick any reasonablely size takerTokenAmount (fixed to 1 ether here) and calculate the rest.
|
||||
uint256 amountReceivedForEther = exchange(
|
||||
readProxy,
|
||||
1 ether,
|
||||
takerTokenSymbol,
|
||||
makerTokenSymbol
|
||||
);
|
||||
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
takerTokenAmounts[i] =
|
||||
(1 ether * makerTokenAmounts[i]) /
|
||||
amountReceivedForEther;
|
||||
}
|
||||
}
|
||||
|
||||
function exchange(
|
||||
IReadProxyAddressResolver readProxy,
|
||||
uint256 sourceAmount,
|
||||
bytes32 sourceCurrencyKey,
|
||||
bytes32 destinationCurrencyKey
|
||||
) private view returns (uint256 amountReceived) {
|
||||
IExchanger exchanger = getExchanger(readProxy);
|
||||
uint256 chainId;
|
||||
assembly {
|
||||
chainId := chainid()
|
||||
}
|
||||
|
||||
if (chainId == 1) {
|
||||
(amountReceived, , ) = exchanger.getAmountsForAtomicExchange(
|
||||
sourceAmount,
|
||||
sourceCurrencyKey,
|
||||
destinationCurrencyKey
|
||||
);
|
||||
} else {
|
||||
(amountReceived, , ) = exchanger.getAmountsForExchange(
|
||||
sourceAmount,
|
||||
sourceCurrencyKey,
|
||||
destinationCurrencyKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getSynthetixAddress(IReadProxyAddressResolver readProxy)
|
||||
private
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
return IAddressResolver(readProxy.target()).getAddress("Synthetix");
|
||||
}
|
||||
|
||||
function getExchanger(IReadProxyAddressResolver readProxy)
|
||||
private
|
||||
view
|
||||
returns (IExchanger)
|
||||
{
|
||||
return
|
||||
IExchanger(
|
||||
IAddressResolver(readProxy.target()).getAddress("Exchanger")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||
|
||||
|
||||
contract TwoHopSampler {
|
||||
using LibBytesV06 for bytes;
|
||||
|
||||
struct HopInfo {
|
||||
uint256 sourceIndex;
|
||||
bytes returnData;
|
||||
}
|
||||
|
||||
function sampleTwoHopSell(
|
||||
bytes[] memory firstHopCalls,
|
||||
bytes[] memory secondHopCalls,
|
||||
uint256 sellAmount
|
||||
)
|
||||
public
|
||||
returns (
|
||||
HopInfo memory firstHop,
|
||||
HopInfo memory secondHop,
|
||||
uint256 buyAmount
|
||||
)
|
||||
{
|
||||
uint256 intermediateAssetAmount = 0;
|
||||
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
|
||||
firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, sellAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (amount > intermediateAssetAmount) {
|
||||
intermediateAssetAmount = amount;
|
||||
firstHop.sourceIndex = i;
|
||||
firstHop.returnData = returnData;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intermediateAssetAmount == 0) {
|
||||
return (firstHop, secondHop, buyAmount);
|
||||
}
|
||||
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
|
||||
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, intermediateAssetAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[j]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (amount > buyAmount) {
|
||||
buyAmount = amount;
|
||||
secondHop.sourceIndex = j;
|
||||
secondHop.returnData = returnData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sampleTwoHopBuy(
|
||||
bytes[] memory firstHopCalls,
|
||||
bytes[] memory secondHopCalls,
|
||||
uint256 buyAmount
|
||||
)
|
||||
public
|
||||
returns (
|
||||
HopInfo memory firstHop,
|
||||
HopInfo memory secondHop,
|
||||
uint256 sellAmount
|
||||
)
|
||||
{
|
||||
sellAmount = uint256(-1);
|
||||
uint256 intermediateAssetAmount = uint256(-1);
|
||||
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
|
||||
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, buyAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[j]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (
|
||||
amount > 0 &&
|
||||
amount < intermediateAssetAmount
|
||||
) {
|
||||
intermediateAssetAmount = amount;
|
||||
secondHop.sourceIndex = j;
|
||||
secondHop.returnData = returnData;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intermediateAssetAmount == uint256(-1)) {
|
||||
return (firstHop, secondHop, sellAmount);
|
||||
}
|
||||
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
|
||||
firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, intermediateAssetAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (
|
||||
amount > 0 &&
|
||||
amount < sellAmount
|
||||
) {
|
||||
sellAmount = amount;
|
||||
firstHop.sourceIndex = i;
|
||||
firstHop.returnData = returnData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IUniswapExchangeQuotes.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
interface IUniswapExchangeFactory {
|
||||
|
||||
/// @dev Get the exchange for a token.
|
||||
/// @param tokenAddress The address of the token contract.
|
||||
function getExchange(address tokenAddress)
|
||||
external
|
||||
view
|
||||
returns (address);
|
||||
}
|
||||
|
||||
|
||||
contract UniswapSampler is
|
||||
SamplerUtils
|
||||
{
|
||||
/// @dev Gas limit for Uniswap calls.
|
||||
uint256 constant private UNISWAP_CALL_GAS = 150e3; // 150k
|
||||
|
||||
/// @dev Sample sell quotes from Uniswap.
|
||||
/// @param router Address of the Uniswap Router
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromUniswap(
|
||||
address router,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == address(0) ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, takerToken);
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == address(0) ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, makerToken);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
bool didSucceed = true;
|
||||
if (makerToken == address(0)) {
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
} else if (takerToken == address(0)) {
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
} else {
|
||||
uint256 ethBought;
|
||||
(ethBought, didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
if (ethBought != 0) {
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||
ethBought
|
||||
);
|
||||
} else {
|
||||
makerTokenAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
// Break early if amounts are 0
|
||||
if (!didSucceed || makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Uniswap.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromUniswap(
|
||||
address router,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == address(0) ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, takerToken);
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == address(0) ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, makerToken);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
bool didSucceed = true;
|
||||
if (makerToken == address(0)) {
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
} else if (takerToken == address(0)) {
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
} else {
|
||||
uint256 ethSold;
|
||||
(ethSold, didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
if (ethSold != 0) {
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||
ethSold
|
||||
);
|
||||
} else {
|
||||
takerTokenAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
// Break early if amounts are 0
|
||||
if (!didSucceed || takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Gracefully calls a Uniswap pricing function.
|
||||
/// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange.
|
||||
/// @param functionSelector Selector of the target function.
|
||||
/// @param inputAmount Quantity parameter particular to the pricing function.
|
||||
/// @return outputAmount The returned amount from the function call. Will be
|
||||
/// zero if the call fails or if `uniswapExchangeAddress` is zero.
|
||||
function _callUniswapExchangePriceFunction(
|
||||
address uniswapExchangeAddress,
|
||||
bytes4 functionSelector,
|
||||
uint256 inputAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 outputAmount, bool didSucceed)
|
||||
{
|
||||
if (uniswapExchangeAddress == address(0)) {
|
||||
return (outputAmount, didSucceed);
|
||||
}
|
||||
bytes memory resultData;
|
||||
(didSucceed, resultData) =
|
||||
uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
functionSelector,
|
||||
inputAmount
|
||||
));
|
||||
if (didSucceed) {
|
||||
outputAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Retrive an existing Uniswap exchange contract.
|
||||
/// Throws if the exchange does not exist.
|
||||
/// @param router Address of the Uniswap router.
|
||||
/// @param tokenAddress Address of the token contract.
|
||||
/// @return exchange `IUniswapExchangeQuotes` for the token.
|
||||
function _getUniswapExchange(address router, address tokenAddress)
|
||||
private
|
||||
view
|
||||
returns (IUniswapExchangeQuotes exchange)
|
||||
{
|
||||
exchange = IUniswapExchangeQuotes(
|
||||
address(IUniswapExchangeFactory(router)
|
||||
.getExchange(tokenAddress))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IUniswapV2Router01.sol";
|
||||
|
||||
|
||||
contract UniswapV2Sampler
|
||||
{
|
||||
/// @dev Gas limit for UniswapV2 calls.
|
||||
uint256 constant private UNISWAPV2_CALL_GAS = 150e3; // 150k
|
||||
|
||||
/// @dev Sample sell quotes from UniswapV2.
|
||||
/// @param router Router to look up tokens and amounts
|
||||
/// @param path Token route. Should be takerToken -> makerToken
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromUniswapV2(
|
||||
address router,
|
||||
address[] memory path,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IUniswapV2Router01(router).getAmountsOut
|
||||
{gas: UNISWAPV2_CALL_GAS}
|
||||
(takerTokenAmounts[i], path)
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
makerTokenAmounts[i] = amounts[path.length - 1];
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from UniswapV2.
|
||||
/// @param router Router to look up tokens and amounts
|
||||
/// @param path Token route. Should be takerToken -> makerToken.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromUniswapV2(
|
||||
address router,
|
||||
address[] memory path,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IUniswapV2Router01(router).getAmountsIn
|
||||
{gas: UNISWAPV2_CALL_GAS}
|
||||
(makerTokenAmounts[i], path)
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
takerTokenAmounts[i] = amounts[0];
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,363 +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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
|
||||
interface IUniswapV3QuoterV2 {
|
||||
function factory()
|
||||
external
|
||||
view
|
||||
returns (IUniswapV3Factory factory);
|
||||
|
||||
// @notice Returns the amount out received for a given exact input swap without executing the swap
|
||||
// @param path The path of the swap, i.e. each token pair and the pool fee
|
||||
// @param amountIn The amount of the first token to swap
|
||||
// @return amountOut The amount of the last token that would be received
|
||||
// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
|
||||
// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
|
||||
// @return gasEstimate The estimate of the gas that the swap consumes
|
||||
function quoteExactInput(bytes memory path, uint256 amountIn)
|
||||
external
|
||||
returns (
|
||||
uint256 amountOut,
|
||||
uint160[] memory sqrtPriceX96AfterList,
|
||||
uint32[] memory initializedTicksCrossedList,
|
||||
uint256 gasEstimate
|
||||
);
|
||||
|
||||
// @notice Returns the amount in required for a given exact output swap without executing the swap
|
||||
// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
|
||||
// @param amountOut The amount of the last token to receive
|
||||
// @return amountIn The amount of first token required to be paid
|
||||
// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
|
||||
// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
|
||||
// @return gasEstimate The estimate of the gas that the swap consumes
|
||||
function quoteExactOutput(bytes memory path, uint256 amountOut)
|
||||
external
|
||||
returns (
|
||||
uint256 amountIn,
|
||||
uint160[] memory sqrtPriceX96AfterList,
|
||||
uint32[] memory initializedTicksCrossedList,
|
||||
uint256 gasEstimate
|
||||
);
|
||||
}
|
||||
|
||||
interface IUniswapV3Factory {
|
||||
function getPool(IERC20TokenV06 a, IERC20TokenV06 b, uint24 fee)
|
||||
external
|
||||
view
|
||||
returns (IUniswapV3Pool pool);
|
||||
}
|
||||
|
||||
interface IUniswapV3Pool {
|
||||
function token0() external view returns (IERC20TokenV06);
|
||||
function token1() external view returns (IERC20TokenV06);
|
||||
function fee() external view returns (uint24);
|
||||
}
|
||||
|
||||
contract UniswapV3Sampler
|
||||
{
|
||||
/// @dev Gas limit for UniswapV3 calls. This is 100% a guess.
|
||||
uint256 constant private QUOTE_GAS = 700e3;
|
||||
|
||||
/// @dev Sample sell quotes from UniswapV3.
|
||||
/// @param quoter UniswapV3 Quoter contract.
|
||||
/// @param path Token route. Should be takerToken -> makerToken
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return uniswapPaths The encoded uniswap path for each sample.
|
||||
/// @return uniswapGasUsed Estimated amount of gas used
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromUniswapV3(
|
||||
IUniswapV3QuoterV2 quoter,
|
||||
IERC20TokenV06[] memory path,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (
|
||||
bytes[] memory uniswapPaths,
|
||||
uint256[] memory uniswapGasUsed,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
{
|
||||
IUniswapV3Pool[][] memory poolPaths =
|
||||
_getValidPoolPaths(quoter.factory(), path, 0);
|
||||
|
||||
makerTokenAmounts = new uint256[](takerTokenAmounts.length);
|
||||
uniswapPaths = new bytes[](takerTokenAmounts.length);
|
||||
uniswapGasUsed = new uint256[](takerTokenAmounts.length);
|
||||
|
||||
for (uint256 i = 0; i < takerTokenAmounts.length; ++i) {
|
||||
// Pick the best result from all the paths.
|
||||
uint256 topBuyAmount = 0;
|
||||
for (uint256 j = 0; j < poolPaths.length; ++j) {
|
||||
bytes memory uniswapPath = _toUniswapPath(path, poolPaths[j]);
|
||||
try quoter.quoteExactInput
|
||||
{ gas: QUOTE_GAS }
|
||||
(uniswapPath, takerTokenAmounts[i])
|
||||
returns (
|
||||
uint256 buyAmount,
|
||||
uint160[] memory, /* sqrtPriceX96AfterList */
|
||||
uint32[] memory, /* initializedTicksCrossedList */
|
||||
uint256 gasUsed
|
||||
)
|
||||
{
|
||||
if (topBuyAmount <= buyAmount) {
|
||||
topBuyAmount = buyAmount;
|
||||
uniswapPaths[i] = uniswapPath;
|
||||
uniswapGasUsed[i] = gasUsed;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
// Break early if we can't complete the sells.
|
||||
if (topBuyAmount == 0) {
|
||||
// HACK(kimpers): To avoid too many local variables, paths and gas used is set directly in the loop
|
||||
// then reset if no valid valid quote was found
|
||||
uniswapPaths[i] = "";
|
||||
uniswapGasUsed[i] = 0;
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = topBuyAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from UniswapV3.
|
||||
/// @param quoter UniswapV3 Quoter contract.
|
||||
/// @param path Token route. Should be takerToken -> makerToken.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return uniswapPaths The encoded uniswap path for each sample.
|
||||
/// @return uniswapGasUsed Estimated amount of gas used
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromUniswapV3(
|
||||
IUniswapV3QuoterV2 quoter,
|
||||
IERC20TokenV06[] memory path,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (
|
||||
bytes[] memory uniswapPaths,
|
||||
uint256[] memory uniswapGasUsed,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
{
|
||||
IUniswapV3Pool[][] memory poolPaths =
|
||||
_getValidPoolPaths(quoter.factory(), path, 0);
|
||||
IERC20TokenV06[] memory reversedPath = _reverseTokenPath(path);
|
||||
|
||||
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
|
||||
uniswapPaths = new bytes[](makerTokenAmounts.length);
|
||||
uniswapGasUsed = new uint256[](makerTokenAmounts.length);
|
||||
|
||||
for (uint256 i = 0; i < makerTokenAmounts.length; ++i) {
|
||||
// Pick the best result from all the paths.
|
||||
uint256 topSellAmount = 0;
|
||||
for (uint256 j = 0; j < poolPaths.length; ++j) {
|
||||
// quoter requires path to be reversed for buys.
|
||||
bytes memory uniswapPath = _toUniswapPath(
|
||||
reversedPath,
|
||||
_reversePoolPath(poolPaths[j])
|
||||
);
|
||||
try
|
||||
quoter.quoteExactOutput
|
||||
{ gas: QUOTE_GAS }
|
||||
(uniswapPath, makerTokenAmounts[i])
|
||||
returns (
|
||||
uint256 sellAmount,
|
||||
uint160[] memory, /* sqrtPriceX96AfterList */
|
||||
uint32[] memory, /* initializedTicksCrossedList */
|
||||
uint256 gasUsed
|
||||
)
|
||||
{
|
||||
if (topSellAmount == 0 || topSellAmount >= sellAmount) {
|
||||
topSellAmount = sellAmount;
|
||||
// But the output path should still be encoded for sells.
|
||||
uniswapPaths[i] = _toUniswapPath(path, poolPaths[j]);
|
||||
uniswapGasUsed[i] = gasUsed;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
// Break early if we can't complete the buys.
|
||||
if (topSellAmount == 0) {
|
||||
// HACK(kimpers): To avoid too many local variables, paths and gas used is set directly in the loop
|
||||
// then reset if no valid valid quote was found
|
||||
uniswapPaths[i] = "";
|
||||
uniswapGasUsed[i] = 0;
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = topSellAmount;
|
||||
}
|
||||
}
|
||||
|
||||
function _getValidPoolPaths(
|
||||
IUniswapV3Factory factory,
|
||||
IERC20TokenV06[] memory tokenPath,
|
||||
uint256 startIndex
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (IUniswapV3Pool[][] memory poolPaths)
|
||||
{
|
||||
require(
|
||||
tokenPath.length - startIndex >= 2,
|
||||
"UniswapV3Sampler/tokenPath too short"
|
||||
);
|
||||
uint24[4] memory validPoolFees = [
|
||||
// The launch pool fees. Could get hairier if they add more.
|
||||
uint24(0.0001e6),
|
||||
uint24(0.0005e6),
|
||||
uint24(0.003e6),
|
||||
uint24(0.01e6)
|
||||
];
|
||||
IUniswapV3Pool[] memory validPools =
|
||||
new IUniswapV3Pool[](validPoolFees.length);
|
||||
uint256 numValidPools = 0;
|
||||
{
|
||||
IERC20TokenV06 inputToken = tokenPath[startIndex];
|
||||
IERC20TokenV06 outputToken = tokenPath[startIndex + 1];
|
||||
for (uint256 i = 0; i < validPoolFees.length; ++i) {
|
||||
IUniswapV3Pool pool =
|
||||
factory.getPool(inputToken, outputToken, validPoolFees[i]);
|
||||
if (_isValidPool(pool)) {
|
||||
validPools[numValidPools++] = pool;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (numValidPools == 0) {
|
||||
// No valid pools for this hop.
|
||||
return poolPaths;
|
||||
}
|
||||
if (startIndex + 2 == tokenPath.length) {
|
||||
// End of path.
|
||||
poolPaths = new IUniswapV3Pool[][](numValidPools);
|
||||
for (uint256 i = 0; i < numValidPools; ++i) {
|
||||
poolPaths[i] = new IUniswapV3Pool[](1);
|
||||
poolPaths[i][0] = validPools[i];
|
||||
}
|
||||
return poolPaths;
|
||||
}
|
||||
// Get paths for subsequent hops.
|
||||
IUniswapV3Pool[][] memory subsequentPoolPaths =
|
||||
_getValidPoolPaths(factory, tokenPath, startIndex + 1);
|
||||
if (subsequentPoolPaths.length == 0) {
|
||||
// Could not complete the path.
|
||||
return poolPaths;
|
||||
}
|
||||
// Combine our pools with the next hop paths.
|
||||
poolPaths = new IUniswapV3Pool[][](
|
||||
numValidPools * subsequentPoolPaths.length
|
||||
);
|
||||
for (uint256 i = 0; i < numValidPools; ++i) {
|
||||
for (uint256 j = 0; j < subsequentPoolPaths.length; ++j) {
|
||||
uint256 o = i * subsequentPoolPaths.length + j;
|
||||
// Prepend pool to the subsequent path.
|
||||
poolPaths[o] =
|
||||
new IUniswapV3Pool[](1 + subsequentPoolPaths[j].length);
|
||||
poolPaths[o][0] = validPools[i];
|
||||
for (uint256 k = 0; k < subsequentPoolPaths[j].length; ++k) {
|
||||
poolPaths[o][1 + k] = subsequentPoolPaths[j][k];
|
||||
}
|
||||
}
|
||||
}
|
||||
return poolPaths;
|
||||
}
|
||||
|
||||
function _reverseTokenPath(IERC20TokenV06[] memory tokenPath)
|
||||
private
|
||||
pure
|
||||
returns (IERC20TokenV06[] memory reversed)
|
||||
{
|
||||
reversed = new IERC20TokenV06[](tokenPath.length);
|
||||
for (uint256 i = 0; i < tokenPath.length; ++i) {
|
||||
reversed[i] = tokenPath[tokenPath.length - i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
function _reversePoolPath(IUniswapV3Pool[] memory poolPath)
|
||||
private
|
||||
pure
|
||||
returns (IUniswapV3Pool[] memory reversed)
|
||||
{
|
||||
reversed = new IUniswapV3Pool[](poolPath.length);
|
||||
for (uint256 i = 0; i < poolPath.length; ++i) {
|
||||
reversed[i] = poolPath[poolPath.length - i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
function _isValidPool(IUniswapV3Pool pool)
|
||||
private
|
||||
view
|
||||
returns (bool isValid)
|
||||
{
|
||||
// Check if it has been deployed.
|
||||
{
|
||||
uint256 codeSize;
|
||||
assembly {
|
||||
codeSize := extcodesize(pool)
|
||||
}
|
||||
if (codeSize == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Must have a balance of both tokens.
|
||||
if (pool.token0().balanceOf(address(pool)) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (pool.token1().balanceOf(address(pool)) == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function _toUniswapPath(
|
||||
IERC20TokenV06[] memory tokenPath,
|
||||
IUniswapV3Pool[] memory poolPath
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (bytes memory uniswapPath)
|
||||
{
|
||||
require(
|
||||
tokenPath.length >= 2 && tokenPath.length == poolPath.length + 1,
|
||||
"UniswapV3Sampler/invalid path lengths"
|
||||
);
|
||||
// Uniswap paths are tightly packed as:
|
||||
// [token0, token0token1PairFee, token1, token1Token2PairFee, token2, ...]
|
||||
uniswapPath = new bytes(tokenPath.length * 20 + poolPath.length * 3);
|
||||
uint256 o;
|
||||
assembly { o := add(uniswapPath, 32) }
|
||||
for (uint256 i = 0; i < tokenPath.length; ++i) {
|
||||
if (i > 0) {
|
||||
uint24 poolFee = poolPath[i - 1].fee();
|
||||
assembly {
|
||||
mstore(o, shl(232, poolFee))
|
||||
o := add(o, 3)
|
||||
}
|
||||
}
|
||||
IERC20TokenV06 token = tokenPath[i];
|
||||
assembly {
|
||||
mstore(o, shl(96, token))
|
||||
o := add(o, 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
|
||||
contract UtilitySampler {
|
||||
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
|
||||
IERC20TokenV06 private immutable UTILITY_ETH_ADDRESS = IERC20TokenV06(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
|
||||
|
||||
function getTokenDecimals(IERC20TokenV06[] memory tokens)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory decimals)
|
||||
{
|
||||
decimals = new uint256[](tokens.length);
|
||||
for (uint256 i = 0; i != tokens.length; i++) {
|
||||
decimals[i] = tokens[i] == UTILITY_ETH_ADDRESS
|
||||
? 18
|
||||
: tokens[i].compatDecimals();
|
||||
}
|
||||
}
|
||||
|
||||
function getBalanceOf(IERC20TokenV06[] memory tokens, address account)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory balances)
|
||||
{
|
||||
balances = new uint256[](tokens.length);
|
||||
for (uint256 i = 0; i != tokens.length; i++) {
|
||||
balances[i] = tokens[i] == UTILITY_ETH_ADDRESS
|
||||
? account.balance
|
||||
: tokens[i].compatBalanceOf(account);
|
||||
}
|
||||
}
|
||||
|
||||
function getAllowanceOf(IERC20TokenV06[] memory tokens, address account, address spender)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory allowances)
|
||||
{
|
||||
allowances = new uint256[](tokens.length);
|
||||
for (uint256 i = 0; i != tokens.length; i++) {
|
||||
allowances[i] = tokens[i] == UTILITY_ETH_ADDRESS
|
||||
? 0
|
||||
: tokens[i].compatAllowance(account, spender);
|
||||
}
|
||||
}
|
||||
|
||||
function isContract(address account)
|
||||
public
|
||||
view
|
||||
returns (bool)
|
||||
{
|
||||
uint256 size;
|
||||
assembly { size := extcodesize(account) }
|
||||
return size > 0;
|
||||
}
|
||||
|
||||
function getGasLeft()
|
||||
public
|
||||
returns (uint256)
|
||||
{
|
||||
return gasleft();
|
||||
}
|
||||
|
||||
function getBlockNumber()
|
||||
public
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return block.number;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2022 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import './ApproximateBuys.sol';
|
||||
import './SamplerUtils.sol';
|
||||
|
||||
struct VeloRoute {
|
||||
address from;
|
||||
address to;
|
||||
bool stable;
|
||||
}
|
||||
|
||||
interface IVelodromeRouter {
|
||||
function getAmountOut(
|
||||
uint256 amountIn,
|
||||
address tokenIn,
|
||||
address tokenOut
|
||||
) external view returns (uint256 amount, bool stable);
|
||||
|
||||
function getAmountsOut(uint256 amountIn, VeloRoute[] calldata routes)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory amounts);
|
||||
}
|
||||
|
||||
contract VelodromeSampler is SamplerUtils, ApproximateBuys {
|
||||
/// @dev Sample sell quotes from Velodrome
|
||||
/// @param router Address of Velodrome router.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample (sorted in ascending order).
|
||||
/// @return stable Whether the pool is a stable pool (vs volatile).
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
|
||||
function sampleSellsFromVelodrome(
|
||||
IVelodromeRouter router,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
) public view returns (bool stable, uint256[] memory makerTokenAmounts) {
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
// Sampling should not mix stable and volatile pools.
|
||||
// Find the most liquid pool based on max(takerTokenAmounts) and stick with it.
|
||||
stable = _isMostLiquidPoolStablePool(router, takerToken, makerToken, takerTokenAmounts);
|
||||
VeloRoute[] memory routes = new VeloRoute[](1);
|
||||
routes[0] = VeloRoute({ from: takerToken, to: makerToken, stable: stable });
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] = router.getAmountsOut(takerTokenAmounts[i], routes)[1];
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Velodrome.
|
||||
/// @param router Address of Velodrome router.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return stable Whether the pool is a stable pool (vs volatile).
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
|
||||
function sampleBuysFromVelodrome(
|
||||
IVelodromeRouter router,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
) public view returns (bool stable, uint256[] memory takerTokenAmounts) {
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
|
||||
// Sampling should not mix stable and volatile pools.
|
||||
// Find the most liquid pool based on the reverse swap (maker -> taker) and stick with it.
|
||||
stable = _isMostLiquidPoolStablePool(router, makerToken, takerToken, makerTokenAmounts);
|
||||
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
takerTokenData: abi.encode(router, VeloRoute({ from: takerToken, to: makerToken, stable: stable })),
|
||||
makerTokenData: abi.encode(router, VeloRoute({ from: makerToken, to: takerToken, stable: stable })),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromVelodrome
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromVelodrome(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory, /* makerTokenData */
|
||||
uint256 sellAmount
|
||||
) internal view returns (uint256) {
|
||||
(IVelodromeRouter router, VeloRoute memory route) = abi.decode(takerTokenData, (IVelodromeRouter, VeloRoute));
|
||||
|
||||
VeloRoute[] memory routes = new VeloRoute[](1);
|
||||
routes[0] = route;
|
||||
return router.getAmountsOut(sellAmount, routes)[1];
|
||||
}
|
||||
|
||||
/// @dev Returns whether the most liquid pool is a stable pool.
|
||||
/// @param router Address of Velodrome router.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token buy amount for each sample (sorted in ascending order)
|
||||
/// @return stable Whether the pool is a stable pool (vs volatile).
|
||||
function _isMostLiquidPoolStablePool(
|
||||
IVelodromeRouter router,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
) internal view returns (bool stable) {
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
(, stable) = router.getAmountOut(takerTokenAmounts[numSamples - 1], takerToken, makerToken);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
import "./SamplerUtils.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
|
||||
interface IWooPP {
|
||||
/// @dev get the quote token address (immutable)
|
||||
/// @return address of quote token
|
||||
function quoteToken() external view returns (address);
|
||||
|
||||
/// @dev Query the amount for selling the base token amount.
|
||||
/// @param baseToken the base token to sell
|
||||
/// @param baseAmount the amount to sell
|
||||
/// @return quoteAmount the swapped quote amount
|
||||
function querySellBase(address baseToken, uint256 baseAmount) external view returns (uint256 quoteAmount);
|
||||
|
||||
/// @dev Query the amount for selling the quote token.
|
||||
/// @param baseToken the base token to receive (buy)
|
||||
/// @param quoteAmount the amount to sell
|
||||
/// @return baseAmount the swapped base token amount
|
||||
function querySellQuote(address baseToken, uint256 quoteAmount) external view returns (uint256 baseAmount);
|
||||
}
|
||||
|
||||
contract WooPPSampler is SamplerUtils, ApproximateBuys{
|
||||
|
||||
function query(
|
||||
uint amountIn,
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
address pool
|
||||
) internal view returns (uint256 amountOut) {
|
||||
if (amountIn == 0) {
|
||||
return 0;
|
||||
}
|
||||
address quoteToken = IWooPP(pool).quoteToken();
|
||||
if (tokenIn == quoteToken) {
|
||||
amountOut = IWooPP(pool).querySellQuote(tokenOut, amountIn);
|
||||
} else if (tokenOut == quoteToken) {
|
||||
amountOut = IWooPP(pool).querySellBase(tokenIn, amountIn);
|
||||
} else {
|
||||
uint quoteAmount = IWooPP(pool).querySellBase(tokenIn, amountIn);
|
||||
amountOut = IWooPP(pool).querySellQuote(tokenOut, quoteAmount);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from WooFI.
|
||||
/// @param pool Address of the pool we are sampling from
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample (sorted in ascending order).
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromWooPP(
|
||||
address pool,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] = query(takerTokenAmounts[i], takerToken, makerToken, pool);
|
||||
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from WooFI.
|
||||
/// @param pool Address of the pool we are sampling from
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample (sorted in ascending order).
|
||||
/// @return takerTokenAmounts Taker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleBuysFromWooPP(
|
||||
address pool,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
takerTokenData: abi.encode(pool,takerToken, makerToken),
|
||||
makerTokenData: abi.encode(pool, makerToken, takerToken),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromWoofi
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromWoofi(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
) internal view returns (uint256) {
|
||||
(address _pool, address _takerToken, address _makerToken) = abi.decode(takerTokenData, (address, address, address));
|
||||
(bool success, bytes memory resultData) = address(this).staticcall(abi.encodeWithSelector(
|
||||
this.sampleSellsFromWooPP.selector,
|
||||
_pool,
|
||||
_takerToken,
|
||||
_makerToken,
|
||||
_toSingleValueArray(sellAmount)
|
||||
));
|
||||
if(!success) {
|
||||
return 0;
|
||||
}
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IBalancer {
|
||||
function isBound(address t) external view returns (bool);
|
||||
function getDenormalizedWeight(address token) external view returns (uint256);
|
||||
function getBalance(address token) external view returns (uint256);
|
||||
function getSwapFee() external view returns (uint256);
|
||||
function calcOutGivenIn(
|
||||
uint256 tokenBalanceIn,
|
||||
uint256 tokenWeightIn,
|
||||
uint256 tokenBalanceOut,
|
||||
uint256 tokenWeightOut,
|
||||
uint256 tokenAmountIn,
|
||||
uint256 swapFee
|
||||
) external pure returns (uint256 tokenAmountOut);
|
||||
function calcInGivenOut(
|
||||
uint256 tokenBalanceIn,
|
||||
uint256 tokenWeightIn,
|
||||
uint256 tokenBalanceOut,
|
||||
uint256 tokenWeightOut,
|
||||
uint256 tokenAmountOut,
|
||||
uint256 swapFee
|
||||
) external pure returns (uint256 tokenAmountIn);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
/// @dev Minimal Balancer V2 Vault interface
|
||||
/// for documentation refer to https://github.com/balancer-labs/balancer-core-v2/blob/master/contracts/vault/interfaces/IVault.sol
|
||||
interface IBalancerV2Vault {
|
||||
enum SwapKind { GIVEN_IN, GIVEN_OUT }
|
||||
|
||||
struct BatchSwapStep {
|
||||
bytes32 poolId;
|
||||
uint256 assetInIndex;
|
||||
uint256 assetOutIndex;
|
||||
uint256 amount;
|
||||
bytes userData;
|
||||
}
|
||||
|
||||
struct FundManagement {
|
||||
address sender;
|
||||
bool fromInternalBalance;
|
||||
address payable recipient;
|
||||
bool toInternalBalance;
|
||||
}
|
||||
|
||||
struct BalancerV2PoolInfo {
|
||||
bytes32 poolId;
|
||||
address vault;
|
||||
}
|
||||
|
||||
function queryBatchSwap(
|
||||
SwapKind kind,
|
||||
BatchSwapStep[] calldata swaps,
|
||||
address[] calldata assets,
|
||||
FundManagement calldata funds
|
||||
) external returns (int256[] memory assetDeltas);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IBancor {}
|
||||
|
||||
interface IBancorNetwork {
|
||||
function conversionPath(address _sourceToken, address _targetToken) external view returns (address[] memory);
|
||||
function rateByPath(address[] memory _path, uint256 _amount) external view returns (uint256);
|
||||
}
|
||||
|
||||
interface IBancorRegistry {
|
||||
function getAddress(bytes32 _contractName) external view returns (address);
|
||||
function BANCOR_NETWORK() external view returns (bytes32);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2022 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
interface IBancorV3 {
|
||||
|
||||
/**
|
||||
* @dev returns the output amount when trading by providing the source amount
|
||||
*/
|
||||
function tradeOutputBySourceAmount(
|
||||
address sourceToken,
|
||||
address targetToken,
|
||||
uint256 sourceAmount
|
||||
) external view returns (uint256);
|
||||
|
||||
/**
|
||||
* @dev returns the input amount when trading by providing the target amount
|
||||
*/
|
||||
function tradeInputByTargetAmount(
|
||||
address sourceToken,
|
||||
address targetToken,
|
||||
uint256 targetAmount
|
||||
) external view returns (uint256);
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
interface ICurve {
|
||||
|
||||
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
|
||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param sellAmount The amount of token being bought.
|
||||
/// @param minBuyAmount The minimum buy amount of the token being bought.
|
||||
function exchange_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount
|
||||
)
|
||||
external;
|
||||
|
||||
/// @dev Get the amount of `toToken` by selling `sellAmount` of `fromToken`
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param sellAmount The amount of token being bought.
|
||||
function get_dy_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 sellAmount
|
||||
)
|
||||
external
|
||||
returns (uint256 dy);
|
||||
|
||||
/// @dev Get the amount of `fromToken` by buying `buyAmount` of `toToken`
|
||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param buyAmount The amount of token being bought.
|
||||
function get_dx_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 buyAmount
|
||||
)
|
||||
external
|
||||
returns (uint256 dx);
|
||||
|
||||
/// @dev Get the underlying token address from the token index
|
||||
/// @param i The token index.
|
||||
function underlying_coins(
|
||||
int128 i
|
||||
)
|
||||
external
|
||||
returns (address tokenAddress);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
interface IGMX {
|
||||
function getMaxAmountIn(IVault _vault, address _tokenIn, address _tokenOut)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
function getAmountOut(IVault _vault, address _tokenIn, address _tokenOut, uint256 _amountIn)
|
||||
external
|
||||
view
|
||||
returns (uint256, uint256);
|
||||
}
|
||||
|
||||
interface IVault {
|
||||
function getFeeBasisPoints(address _token, uint256 _usdgDelta, uint256 _feeBasisPoints, uint256 _taxBasisPoints, bool _increment) external view returns (uint256);
|
||||
function stableSwapFeeBasisPoints() external view returns (uint256);
|
||||
function stableTokens(address _token) external view returns (bool);
|
||||
function tokenDecimals(address _token) external view returns (uint256);
|
||||
function getMaxPrice(address _token) external view returns (uint256);
|
||||
function getMinPrice(address _token) external view returns (uint256);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IMStable {
|
||||
|
||||
function getSwapOutput(
|
||||
address _input,
|
||||
address _output,
|
||||
uint256 _quantity
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 swapOutput);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IMooniswapRegistry {
|
||||
|
||||
function pools(address token1, address token2) external view returns(address);
|
||||
}
|
||||
|
||||
interface IMooniswap {
|
||||
|
||||
function getReturn(
|
||||
address fromToken,
|
||||
address destToken,
|
||||
uint256 amount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns(uint256 returnAmount);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IMultiBridge {
|
||||
|
||||
/// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
|
||||
/// @param tokenAddress The address of the ERC20 token to transfer.
|
||||
/// @param from Address to transfer asset from.
|
||||
/// @param to Address to transfer asset to.
|
||||
/// @param amount Amount of asset to transfer.
|
||||
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
|
||||
/// @return success The magic bytes `0xdc1600f3` if successful.
|
||||
function bridgeTransferFrom(
|
||||
address tokenAddress,
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata bridgeData
|
||||
)
|
||||
external
|
||||
returns (bytes4 success);
|
||||
|
||||
/// @dev Quotes the amount of `makerToken` that would be obtained by
|
||||
/// selling `sellAmount` of `takerToken`.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param intermediateToken The address of the intermediate token to
|
||||
/// use in an indirect route.
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param sellAmount Amount of `takerToken` to sell.
|
||||
/// @return makerTokenAmount Amount of `makerToken` that would be obtained.
|
||||
function getSellQuote(
|
||||
address takerToken,
|
||||
address intermediateToken,
|
||||
address makerToken,
|
||||
uint256 sellAmount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 makerTokenAmount);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
pragma solidity ^0.6;
|
||||
|
||||
interface IPlatypus {
|
||||
function quotePotentialSwap(
|
||||
address fromToken,
|
||||
address toToken,
|
||||
uint256 fromAmount
|
||||
) external view returns (uint256 potentialOutcome, uint256 haircut);
|
||||
|
||||
function assetOf(address token) external view returns (address);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IShell {
|
||||
|
||||
function viewOriginSwap (
|
||||
address from,
|
||||
address to,
|
||||
uint256 fromAmount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 toAmount);
|
||||
|
||||
function viewTargetSwap (
|
||||
address from,
|
||||
address to,
|
||||
uint256 toAmount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 fromAmount);
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IUniswapExchangeQuotes {
|
||||
|
||||
function getEthToTokenInputPrice(
|
||||
uint256 ethSold
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 tokensBought);
|
||||
|
||||
function getEthToTokenOutputPrice(
|
||||
uint256 tokensBought
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 ethSold);
|
||||
|
||||
function getTokenToEthInputPrice(
|
||||
uint256 tokensSold
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 ethBought);
|
||||
|
||||
function getTokenToEthOutputPrice(
|
||||
uint256 ethBought
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 tokensSold);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IUniswapV2Router01 {
|
||||
|
||||
function getAmountsOut(uint256 amountIn, address[] calldata path)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory amounts);
|
||||
|
||||
function getAmountsIn(uint256 amountOut, address[] calldata path)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory amounts);
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../src/NativeOrderSampler.sol";
|
||||
import "../src/UtilitySampler.sol";
|
||||
|
||||
|
||||
contract TestNativeOrderSamplerToken {
|
||||
mapping (address => uint256) public balanceOf;
|
||||
mapping (address => mapping(address => uint256)) public allowance;
|
||||
|
||||
function setBalanceAndAllowance(
|
||||
address owner,
|
||||
address spender,
|
||||
uint256 balance,
|
||||
uint256 allowance_
|
||||
)
|
||||
external
|
||||
{
|
||||
balanceOf[owner] = balance;
|
||||
allowance[owner][spender] = allowance_;
|
||||
}
|
||||
}
|
||||
|
||||
contract TestNativeOrderSampler is
|
||||
NativeOrderSampler,
|
||||
UtilitySampler
|
||||
{
|
||||
uint8 private constant MAX_ORDER_STATUS = uint8(IExchange.OrderStatus.CANCELLED) + 1;
|
||||
bytes32 private constant VALID_SIGNATURE_HASH = bytes32(hex"01");
|
||||
|
||||
function createTokens(uint256 count)
|
||||
external
|
||||
returns (TestNativeOrderSamplerToken[] memory tokens)
|
||||
{
|
||||
tokens = new TestNativeOrderSamplerToken[](count);
|
||||
for (uint256 i = 0; i < count; ++i) {
|
||||
tokens[i] = new TestNativeOrderSamplerToken();
|
||||
}
|
||||
}
|
||||
|
||||
function setTokenBalanceAndAllowance(
|
||||
TestNativeOrderSamplerToken token,
|
||||
address owner,
|
||||
address spender,
|
||||
uint256 balance,
|
||||
uint256 allowance
|
||||
)
|
||||
external
|
||||
{
|
||||
token.setBalanceAndAllowance(owner, spender, balance, allowance);
|
||||
}
|
||||
|
||||
// IExchange.getLimitOrderRelevantState()
|
||||
function getLimitOrderRelevantState(
|
||||
IExchange.LimitOrder memory order,
|
||||
IExchange.Signature calldata signature
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
IExchange.OrderInfo memory orderInfo,
|
||||
uint128 actualFillableTakerTokenAmount,
|
||||
bool isSignatureValid
|
||||
)
|
||||
{
|
||||
// The order salt determines everything.
|
||||
orderInfo.orderHash = keccak256(abi.encode(order.salt));
|
||||
if (uint8(order.salt) == 0xFF) {
|
||||
orderInfo.status = IExchange.OrderStatus.FILLED;
|
||||
} else {
|
||||
orderInfo.status = IExchange.OrderStatus.FILLABLE;
|
||||
}
|
||||
|
||||
isSignatureValid = signature.r == VALID_SIGNATURE_HASH;
|
||||
|
||||
// The expiration time is the filled taker asset amount.
|
||||
orderInfo.takerTokenFilledAmount = uint128(order.expiry);
|
||||
|
||||
// Calculate how much is fillable in maker terms given the filled taker amount
|
||||
uint256 fillableMakerTokenAmount = LibMathV06.getPartialAmountFloor(
|
||||
uint256(
|
||||
order.takerAmount
|
||||
- orderInfo.takerTokenFilledAmount
|
||||
),
|
||||
uint256(order.takerAmount),
|
||||
uint256(order.makerAmount)
|
||||
);
|
||||
|
||||
// Take the min of the balance/allowance and the fillable maker amount
|
||||
fillableMakerTokenAmount = LibSafeMathV06.min256(
|
||||
fillableMakerTokenAmount,
|
||||
_getSpendableERC20BalanceOf(order.makerToken, order.maker)
|
||||
);
|
||||
|
||||
// Convert to taker terms
|
||||
actualFillableTakerTokenAmount = LibMathV06.getPartialAmountCeil(
|
||||
fillableMakerTokenAmount,
|
||||
uint256(order.makerAmount),
|
||||
uint256(order.takerAmount)
|
||||
).safeDowncastToUint128();
|
||||
}
|
||||
|
||||
function _getSpendableERC20BalanceOf(
|
||||
IERC20TokenV06 token,
|
||||
address owner
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return LibSafeMathV06.min256(
|
||||
token.allowance(owner, address(this)),
|
||||
token.balanceOf(owner)
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
1839
packages/asset-swapper/package-lock.json
generated
1839
packages/asset-swapper/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,95 +0,0 @@
|
||||
{
|
||||
"name": "@0x/asset-swapper",
|
||||
"version": "16.66.19",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
"description": "Convenience package for discovering and buying assets on-chain and off-chain.",
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|SynthetixSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler|VelodromeSampler|WooPPSampler).json",
|
||||
"postpublish": {
|
||||
"assets": []
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/protocol.git"
|
||||
},
|
||||
"author": "David Sun",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/0xProject/protocol/issues"
|
||||
},
|
||||
"homepage": "https://0x.org/asset-swapper",
|
||||
"gitpkg": {
|
||||
"registry": "git@github.com:0xProject/gitpkg-registry.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.35",
|
||||
"@0x/base-contract": "^7.0.0",
|
||||
"@0x/contract-addresses": "^7.4.0",
|
||||
"@0x/contract-wrappers": "^13.22.11",
|
||||
"@0x/contracts-erc20": "^3.3.52",
|
||||
"@0x/contracts-zero-ex": "^0.38.1",
|
||||
"@0x/dev-utils": "^5.0.0",
|
||||
"@0x/fast-abi": "^0.0.5",
|
||||
"@0x/json-schemas": "^6.4.4",
|
||||
"@0x/neon-router": "^0.3.5",
|
||||
"@0x/protocol-utils": "^11.17.1",
|
||||
"@0x/quote-server": "^8.0.0",
|
||||
"@0x/types": "^3.3.6",
|
||||
"@0x/utils": "^7.0.0",
|
||||
"@0x/web3-wrapper": "^8.0.0",
|
||||
"@balancer-labs/sdk": "0.1.6",
|
||||
"@bancor/sdk": "0.2.9",
|
||||
"@ethersproject/abi": "^5.0.1",
|
||||
"@ethersproject/address": "^5.0.1",
|
||||
"@ethersproject/contracts": "^5.0.1",
|
||||
"@ethersproject/providers": "^5.0.4",
|
||||
"@ethersproject/strings": "^5.0.10",
|
||||
"axios": "^0.21.1",
|
||||
"axios-mock-adapter": "^1.19.0",
|
||||
"balancer-labs-sor-v1": "npm:@balancer-labs/sor@0.3.2",
|
||||
"ethereum-types": "^3.7.1",
|
||||
"graphql": "^15.4.0",
|
||||
"graphql-request": "^3.4.0",
|
||||
"heartbeats": "^5.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"msw": "^0.44.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.8.1",
|
||||
"@0x/contracts-gen": "^2.0.47",
|
||||
"@0x/contracts-test-utils": "^5.4.42",
|
||||
"@0x/sol-compiler": "^4.8.2",
|
||||
"@0x/subproviders": "^7.0.0",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.4",
|
||||
"@types/lodash": "4.14.137",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "12.12.54",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-bignumber": "^3.0.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"gitpkg": "https://github.com/0xProject/gitpkg.git",
|
||||
"mocha": "^6.2.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"nyc": "^11.0.1",
|
||||
"shx": "^0.2.2",
|
||||
"tslint": "^6.1.3",
|
||||
"typedoc": "~0.16.11",
|
||||
"typemoq": "^2.1.0",
|
||||
"typescript": "4.6.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "4f91bfd907996b2f4dd383778b50c479c2602b56"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as BalanceChecker from '../generated-artifacts/BalanceChecker.json';
|
||||
import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.json';
|
||||
import * as FakeTaker from '../generated-artifacts/FakeTaker.json';
|
||||
export const artifacts = {
|
||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
||||
BalanceChecker: BalanceChecker as ContractArtifact,
|
||||
FakeTaker: FakeTaker as ContractArtifact,
|
||||
};
|
||||
@@ -1,128 +0,0 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { SignatureType } from '@0x/protocol-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
|
||||
import {
|
||||
AffiliateFeeType,
|
||||
ExchangeProxyContractOpts,
|
||||
LogFunction,
|
||||
OrderPrunerOpts,
|
||||
OrderPrunerPermittedFeeTypes,
|
||||
RfqRequestOpts,
|
||||
SwapQuoteGetOutputOpts,
|
||||
SwapQuoteRequestOpts,
|
||||
SwapQuoterOpts,
|
||||
} from './types';
|
||||
import {
|
||||
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID,
|
||||
DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID,
|
||||
} from './utils/market_operation_utils/constants';
|
||||
|
||||
const ZERO_EX_GAS_API_URL = 'https://gas.api.0x.org/source/median';
|
||||
const NULL_BYTES = '0x';
|
||||
const NULL_ERC20_ASSET_DATA = '0xf47261b00000000000000000000000000000000000000000000000000000000000000000';
|
||||
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||
const MAINNET_CHAIN_ID = 1;
|
||||
const ONE_SECOND_MS = 1000;
|
||||
const ONE_MINUTE_SECS = 60;
|
||||
const ONE_MINUTE_MS = ONE_SECOND_MS * ONE_MINUTE_SECS;
|
||||
const DEFAULT_PER_PAGE = 1000;
|
||||
const ALT_MM_IMPUTED_INDICATIVE_EXPIRY_SECONDS = 180;
|
||||
|
||||
const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = {
|
||||
expiryBufferMs: 120000, // 2 minutes
|
||||
permittedOrderFeeTypes: new Set<OrderPrunerPermittedFeeTypes>([OrderPrunerPermittedFeeTypes.NoFees]), // Default asset-swapper for CFL oriented fee types
|
||||
};
|
||||
|
||||
// 6 seconds polling interval
|
||||
const PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS = 6000;
|
||||
const PROTOCOL_FEE_MULTIPLIER = new BigNumber(0);
|
||||
|
||||
// default 50% buffer for selecting native orders to be aggregated with other sources
|
||||
const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5;
|
||||
|
||||
export const ZERO_AMOUNT = new BigNumber(0);
|
||||
const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||
chainId: ChainId.Mainnet,
|
||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
||||
...DEFAULT_ORDER_PRUNER_OPTS,
|
||||
samplerGasLimit: 500e6,
|
||||
zeroExGasApiUrl: ZERO_EX_GAS_API_URL,
|
||||
rfqt: {
|
||||
integratorsWhitelist: [],
|
||||
makerAssetOfferings: {},
|
||||
txOriginBlacklist: new Set(),
|
||||
},
|
||||
tokenAdjacencyGraph: DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID[ChainId.Mainnet],
|
||||
};
|
||||
|
||||
const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts = {
|
||||
isFromETH: false,
|
||||
isToETH: false,
|
||||
affiliateFee: {
|
||||
feeType: AffiliateFeeType.None,
|
||||
recipient: NULL_ADDRESS,
|
||||
buyTokenFeeAmount: ZERO_AMOUNT,
|
||||
sellTokenFeeAmount: ZERO_AMOUNT,
|
||||
},
|
||||
refundReceiver: NULL_ADDRESS,
|
||||
isMetaTransaction: false,
|
||||
shouldSellEntireBalance: false,
|
||||
};
|
||||
|
||||
const DEFAULT_EXCHANGE_PROXY_SWAP_QUOTE_GET_OPTS: SwapQuoteGetOutputOpts = {
|
||||
extensionContractOpts: DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,
|
||||
};
|
||||
|
||||
const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = {
|
||||
...DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
};
|
||||
|
||||
const DEFAULT_RFQT_REQUEST_OPTS: Partial<RfqRequestOpts> = {
|
||||
makerEndpointMaxResponseTimeMs: 1000,
|
||||
};
|
||||
|
||||
export const DEFAULT_INFO_LOGGER: LogFunction = (obj, msg) =>
|
||||
logUtils.log(`${msg ? `${msg}: ` : ''}${JSON.stringify(obj)}`);
|
||||
export const DEFAULT_WARNING_LOGGER: LogFunction = (obj, msg) =>
|
||||
logUtils.warn(`${msg ? `${msg}: ` : ''}${JSON.stringify(obj)}`);
|
||||
|
||||
const EMPTY_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||
export const INVALID_SIGNATURE = { signatureType: SignatureType.Invalid, v: 1, r: EMPTY_BYTES32, s: EMPTY_BYTES32 };
|
||||
|
||||
export { DEFAULT_FEE_SCHEDULE, DEFAULT_GAS_SCHEDULE } from './utils/market_operation_utils/constants';
|
||||
|
||||
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(30000);
|
||||
|
||||
export const KEEP_ALIVE_TTL = 5 * 60 * ONE_SECOND_MS;
|
||||
|
||||
export const constants = {
|
||||
ZERO_EX_GAS_API_URL,
|
||||
PROTOCOL_FEE_MULTIPLIER,
|
||||
POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS,
|
||||
NULL_BYTES,
|
||||
ZERO_AMOUNT,
|
||||
NULL_ADDRESS,
|
||||
MAINNET_CHAIN_ID,
|
||||
DEFAULT_ORDER_PRUNER_OPTS,
|
||||
ETHER_TOKEN_DECIMALS: 18,
|
||||
ONE_AMOUNT: new BigNumber(1),
|
||||
ONE_SECOND_MS,
|
||||
ONE_MINUTE_MS,
|
||||
DEFAULT_SWAP_QUOTER_OPTS,
|
||||
DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID,
|
||||
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
|
||||
DEFAULT_EXCHANGE_PROXY_SWAP_QUOTE_GET_OPTS,
|
||||
DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,
|
||||
DEFAULT_PER_PAGE,
|
||||
DEFAULT_RFQT_REQUEST_OPTS,
|
||||
NULL_ERC20_ASSET_DATA,
|
||||
PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
|
||||
MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE,
|
||||
BRIDGE_ASSET_DATA_PREFIX: '0xdc1600f3',
|
||||
DEFAULT_INFO_LOGGER,
|
||||
DEFAULT_WARNING_LOGGER,
|
||||
EMPTY_BYTES32,
|
||||
ALT_MM_IMPUTED_INDICATIVE_EXPIRY_SECONDS,
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { SwapQuoterError } from './types';
|
||||
|
||||
/**
|
||||
* Error class representing insufficient asset liquidity
|
||||
*/
|
||||
export class InsufficientAssetLiquidityError extends Error {
|
||||
/**
|
||||
* The amount availabe to fill (in base units) factoring in slippage.
|
||||
*/
|
||||
public amountAvailableToFill: BigNumber;
|
||||
/**
|
||||
* @param amountAvailableToFill The amount availabe to fill (in base units) factoring in slippage
|
||||
*/
|
||||
constructor(amountAvailableToFill: BigNumber) {
|
||||
super(SwapQuoterError.InsufficientAssetLiquidity);
|
||||
this.amountAvailableToFill = amountAvailableToFill;
|
||||
// Setting prototype so instanceof works. See https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
||||
Object.setPrototypeOf(this, InsufficientAssetLiquidityError.prototype);
|
||||
}
|
||||
}
|
||||
8
packages/asset-swapper/src/globals.d.ts
vendored
8
packages/asset-swapper/src/globals.d.ts
vendored
@@ -1,8 +0,0 @@
|
||||
declare module '*.json' {
|
||||
const json: any;
|
||||
/* tslint:disable */
|
||||
export default json;
|
||||
/* tslint:enable */
|
||||
}
|
||||
|
||||
declare module 'heartbeats';
|
||||
@@ -1,196 +0,0 @@
|
||||
export {
|
||||
AwaitTransactionSuccessOpts,
|
||||
ContractFunctionObj,
|
||||
ContractTxFunctionObj,
|
||||
SendTransactionOpts,
|
||||
} from '@0x/base-contract';
|
||||
export { ContractAddresses, ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
export {
|
||||
V4RFQFirmQuote,
|
||||
V4RFQIndicativeQuote,
|
||||
V4SignedRfqOrder,
|
||||
TakerRequestQueryParamsUnnested as TakerRequestQueryParams,
|
||||
} from '@0x/quote-server';
|
||||
export { Asset, AssetPairsItem, DecodedLogEvent, EventCallback, IndexedFilterValues } from '@0x/types';
|
||||
export { BigNumber } from '@0x/utils';
|
||||
export {
|
||||
RfqOrderFields,
|
||||
LimitOrderFields,
|
||||
FillQuoteTransformerOrderType,
|
||||
RfqOrder,
|
||||
LimitOrder,
|
||||
Signature,
|
||||
SignatureType,
|
||||
} from '@0x/protocol-utils';
|
||||
export { AxiosInstance } from 'axios';
|
||||
export {
|
||||
AbiDefinition,
|
||||
BlockParam,
|
||||
BlockParamLiteral,
|
||||
CallData,
|
||||
CompilerOpts,
|
||||
CompilerSettings,
|
||||
CompilerSettingsMetadata,
|
||||
ConstructorAbi,
|
||||
ConstructorStateMutability,
|
||||
ContractAbi,
|
||||
ContractArtifact,
|
||||
ContractChainData,
|
||||
ContractChains,
|
||||
ContractEventArg,
|
||||
DataItem,
|
||||
DecodedLogArgs,
|
||||
DevdocOutput,
|
||||
EIP1193Event,
|
||||
EIP1193Provider,
|
||||
EventAbi,
|
||||
EventParameter,
|
||||
EvmBytecodeOutput,
|
||||
EvmBytecodeOutputLinkReferences,
|
||||
EvmOutput,
|
||||
FallbackAbi,
|
||||
FunctionAbi,
|
||||
GanacheProvider,
|
||||
GethCallOverrides,
|
||||
JSONRPCErrorCallback,
|
||||
JSONRPCRequestPayload,
|
||||
JSONRPCResponseError,
|
||||
JSONRPCResponsePayload,
|
||||
LogWithDecodedArgs,
|
||||
MethodAbi,
|
||||
OptimizerSettings,
|
||||
OutputField,
|
||||
ParamDescription,
|
||||
RevertErrorAbi,
|
||||
StandardContractOutput,
|
||||
StateMutability,
|
||||
SupportedProvider,
|
||||
TupleDataItem,
|
||||
TxData,
|
||||
TxDataPayable,
|
||||
Web3JsProvider,
|
||||
Web3JsV1Provider,
|
||||
Web3JsV2Provider,
|
||||
Web3JsV3Provider,
|
||||
ZeroExProvider,
|
||||
} from 'ethereum-types';
|
||||
export { artifacts } from './artifacts';
|
||||
export { InsufficientAssetLiquidityError } from './errors';
|
||||
export { SwapQuoteConsumer } from './quote_consumers/swap_quote_consumer';
|
||||
export { SwapQuoter, Orderbook } from './swap_quoter';
|
||||
export {
|
||||
AltOffering,
|
||||
AltRfqMakerAssetOfferings,
|
||||
AffiliateFeeType,
|
||||
AffiliateFeeAmount,
|
||||
AssetSwapperContractAddresses,
|
||||
CalldataInfo,
|
||||
ExchangeProxyContractOpts,
|
||||
ExchangeProxyRefundReceiver,
|
||||
GetExtensionContractTypeOpts,
|
||||
Integrator,
|
||||
LogFunction,
|
||||
MarketBuySwapQuote,
|
||||
MarketOperation,
|
||||
MarketSellSwapQuote,
|
||||
MockedRfqQuoteResponse,
|
||||
OrderPrunerPermittedFeeTypes,
|
||||
RfqMakerAssetOfferings,
|
||||
RfqFirmQuoteValidator,
|
||||
RfqRequestOpts,
|
||||
SamplerOverrides,
|
||||
SignedNativeOrder,
|
||||
SignedOrder,
|
||||
SwapQuote,
|
||||
SwapQuoteConsumerBase,
|
||||
SwapQuoteConsumerError,
|
||||
SwapQuoteConsumerOpts,
|
||||
SwapQuoteExecutionOpts,
|
||||
SwapQuoteGetOutputOpts,
|
||||
SwapQuoteInfo,
|
||||
SwapQuoteOrdersBreakdown,
|
||||
SwapQuoteRequestOpts,
|
||||
SwapQuoterError,
|
||||
SwapQuoterOpts,
|
||||
SwapQuoterRfqOpts,
|
||||
SamplerMetrics,
|
||||
} from './types';
|
||||
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
||||
export {
|
||||
IRfqClient,
|
||||
RfqClientV1Price,
|
||||
RfqClientV1PriceRequest,
|
||||
RfqClientV1PriceResponse,
|
||||
RfqClientV1Quote,
|
||||
RfqClientV1QuoteRequest,
|
||||
RfqClientV1QuoteResponse,
|
||||
} from './utils/irfq_client';
|
||||
export {
|
||||
DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID,
|
||||
DEFAULT_GAS_SCHEDULE,
|
||||
SOURCE_FLAGS,
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
||||
ZERO_AMOUNT,
|
||||
} from './utils/market_operation_utils/constants';
|
||||
export {
|
||||
Parameters,
|
||||
SamplerContractCall,
|
||||
SamplerContractOperation,
|
||||
} from './utils/market_operation_utils/sampler_contract_operation';
|
||||
export {
|
||||
BalancerFillData,
|
||||
BancorFillData,
|
||||
CurveFillData,
|
||||
CurveFunctionSelectors,
|
||||
CurveInfo,
|
||||
DexSample,
|
||||
DODOFillData,
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
FeeSchedule,
|
||||
GasSchedule,
|
||||
Fill,
|
||||
FillAdjustor,
|
||||
FillData,
|
||||
GetMarketOrdersRfqOpts,
|
||||
LiquidityProviderFillData,
|
||||
LiquidityProviderRegistry,
|
||||
MooniswapFillData,
|
||||
MultiHopFillData,
|
||||
NativeRfqOrderFillData,
|
||||
NativeLimitOrderFillData,
|
||||
NativeFillData,
|
||||
OptimizedMarketOrder,
|
||||
SourceQuoteOperation,
|
||||
UniswapV2FillData,
|
||||
} from './utils/market_operation_utils/types';
|
||||
|
||||
export { TokenAdjacencyGraph, TokenAdjacencyGraphBuilder } from './utils/token_adjacency_graph';
|
||||
export { IdentityFillAdjustor } from './utils/market_operation_utils/identity_fill_adjustor';
|
||||
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||
export {
|
||||
BridgeQuoteReportEntry,
|
||||
jsonifyFillData,
|
||||
MultiHopQuoteReportEntry,
|
||||
NativeLimitOrderQuoteReportEntry,
|
||||
NativeRfqOrderQuoteReportEntry,
|
||||
QuoteReport,
|
||||
QuoteReportEntry,
|
||||
ExtendedQuoteReport,
|
||||
ExtendedQuoteReportSources,
|
||||
ExtendedQuoteReportEntry,
|
||||
ExtendedQuoteReportIndexedEntry,
|
||||
ExtendedQuoteReportIndexedEntryOutbound,
|
||||
PriceComparisonsReport,
|
||||
} from './utils/quote_report_generator';
|
||||
export { QuoteRequestor, V4RFQIndicativeQuoteMM } from './utils/quote_requestor';
|
||||
export { ERC20BridgeSamplerContract, BalanceCheckerContract, FakeTakerContract } from './wrappers';
|
||||
import { ERC20BridgeSource } from './utils/market_operation_utils/types';
|
||||
export type Native = ERC20BridgeSource.Native;
|
||||
export type MultiHop = ERC20BridgeSource.MultiHop;
|
||||
|
||||
export { rfqtMocker, RfqtQuoteEndpoint } from './utils/rfqt_mocker';
|
||||
|
||||
export { adjustOutput } from './utils/market_operation_utils/fills';
|
||||
@@ -1,57 +0,0 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { ZERO_AMOUNT } from '../constants';
|
||||
export interface AaveInfo {
|
||||
lendingPool: string;
|
||||
aToken: string;
|
||||
underlyingToken: string;
|
||||
}
|
||||
// tslint:disable-next-line:no-unnecessary-class
|
||||
export class AaveV2Sampler {
|
||||
public static sampleSellsFromAaveV2(
|
||||
aaveInfo: AaveInfo,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerTokenAmounts: BigNumber[],
|
||||
): BigNumber[] {
|
||||
// Deposit/Withdrawal underlying <-> aToken is always 1:1
|
||||
if (
|
||||
(takerToken.toLowerCase() === aaveInfo.aToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase()) ||
|
||||
(takerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === aaveInfo.aToken.toLowerCase())
|
||||
) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
// Not matching the reserve return 0 results
|
||||
const numSamples = takerTokenAmounts.length;
|
||||
|
||||
const makerTokenAmounts = new Array(numSamples);
|
||||
makerTokenAmounts.fill(ZERO_AMOUNT);
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
public static sampleBuysFromAaveV2(
|
||||
aaveInfo: AaveInfo,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerTokenAmounts: BigNumber[],
|
||||
): BigNumber[] {
|
||||
// Deposit/Withdrawal underlying <-> aToken is always 1:1
|
||||
if (
|
||||
(takerToken.toLowerCase() === aaveInfo.aToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase()) ||
|
||||
(takerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === aaveInfo.aToken.toLowerCase())
|
||||
) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
// Not matching the reserve return 0 results
|
||||
const numSamples = makerTokenAmounts.length;
|
||||
const takerTokenAmounts = new Array(numSamples);
|
||||
takerTokenAmounts.fill(ZERO_AMOUNT);
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { ZERO_AMOUNT } from '../constants';
|
||||
export interface GeistInfo {
|
||||
lendingPool: string;
|
||||
gToken: string;
|
||||
underlyingToken: string;
|
||||
}
|
||||
// tslint:disable-next-line:no-unnecessary-class
|
||||
export class GeistSampler {
|
||||
public static sampleSellsFromGeist(
|
||||
geistInfo: GeistInfo,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerTokenAmounts: BigNumber[],
|
||||
): BigNumber[] {
|
||||
// Deposit/Withdrawal underlying <-> gToken is always 1:1
|
||||
if (
|
||||
(takerToken.toLowerCase() === geistInfo.gToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === geistInfo.underlyingToken.toLowerCase()) ||
|
||||
(takerToken.toLowerCase() === geistInfo.underlyingToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === geistInfo.gToken.toLowerCase())
|
||||
) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
// Not matching the reserve return 0 results
|
||||
const numSamples = takerTokenAmounts.length;
|
||||
|
||||
const makerTokenAmounts = new Array(numSamples);
|
||||
makerTokenAmounts.fill(ZERO_AMOUNT);
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
public static sampleBuysFromGeist(
|
||||
geistInfo: GeistInfo,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerTokenAmounts: BigNumber[],
|
||||
): BigNumber[] {
|
||||
// Deposit/Withdrawal underlying <-> gToken is always 1:1
|
||||
if (
|
||||
(takerToken.toLowerCase() === geistInfo.gToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === geistInfo.underlyingToken.toLowerCase()) ||
|
||||
(takerToken.toLowerCase() === geistInfo.underlyingToken.toLowerCase() &&
|
||||
makerToken.toLowerCase() === geistInfo.gToken.toLowerCase())
|
||||
) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
// Not matching the reserve return 0 results
|
||||
const numSamples = makerTokenAmounts.length;
|
||||
const takerTokenAmounts = new Array(numSamples);
|
||||
takerTokenAmounts.fill(ZERO_AMOUNT);
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
}
|
||||
@@ -1,733 +0,0 @@
|
||||
import { ChainId, ContractAddresses } from '@0x/contract-addresses';
|
||||
import { IZeroExContract } from '@0x/contract-wrappers';
|
||||
import {
|
||||
encodeAffiliateFeeTransformerData,
|
||||
encodeCurveLiquidityProviderData,
|
||||
encodeFillQuoteTransformerData,
|
||||
encodePayTakerTransformerData,
|
||||
encodePositiveSlippageFeeTransformerData,
|
||||
encodeWethTransformerData,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
FillQuoteTransformerOrderType,
|
||||
FillQuoteTransformerSide,
|
||||
findTransformerNonce,
|
||||
} from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants, POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS } from '../constants';
|
||||
import {
|
||||
AffiliateFeeType,
|
||||
CalldataInfo,
|
||||
ExchangeProxyContractOpts,
|
||||
MarketBuySwapQuote,
|
||||
MarketOperation,
|
||||
MarketSellSwapQuote,
|
||||
SwapQuote,
|
||||
SwapQuoteConsumerBase,
|
||||
SwapQuoteConsumerOpts,
|
||||
SwapQuoteExecutionOpts,
|
||||
SwapQuoteGetOutputOpts,
|
||||
} from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
import {
|
||||
CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID,
|
||||
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
||||
} from '../utils/market_operation_utils/constants';
|
||||
import {
|
||||
CurveFillData,
|
||||
ERC20BridgeSource,
|
||||
FinalUniswapV3FillData,
|
||||
LiquidityProviderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
OptimizedMarketBridgeOrder,
|
||||
OptimizedMarketOrder,
|
||||
UniswapV2FillData,
|
||||
} from '../utils/market_operation_utils/types';
|
||||
|
||||
import {
|
||||
multiplexPlpEncoder,
|
||||
multiplexRfqEncoder,
|
||||
MultiplexSubcall,
|
||||
multiplexTransformERC20Encoder,
|
||||
multiplexUniswapEncoder,
|
||||
} from './multiplex_encoders';
|
||||
import {
|
||||
getFQTTransformerDataFromOptimizedOrders,
|
||||
isBuyQuote,
|
||||
isDirectSwapCompatible,
|
||||
isMultiplexBatchFillCompatible,
|
||||
isMultiplexMultiHopFillCompatible,
|
||||
requiresTransformERC20,
|
||||
} from './quote_consumer_utils';
|
||||
|
||||
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
|
||||
const { NULL_ADDRESS, NULL_BYTES, ZERO_AMOUNT } = constants;
|
||||
|
||||
// use the same order in IPancakeSwapFeature.sol
|
||||
const PANCAKE_SWAP_FORKS = [
|
||||
ERC20BridgeSource.PancakeSwap,
|
||||
ERC20BridgeSource.PancakeSwapV2,
|
||||
ERC20BridgeSource.BakerySwap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
];
|
||||
const FAKE_PROVIDER: any = {
|
||||
sendAsync(): void {
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
public readonly chainId: ChainId;
|
||||
public readonly transformerNonces: {
|
||||
wethTransformer: number;
|
||||
payTakerTransformer: number;
|
||||
fillQuoteTransformer: number;
|
||||
affiliateFeeTransformer: number;
|
||||
positiveSlippageFeeTransformer: number;
|
||||
};
|
||||
|
||||
private readonly _exchangeProxy: IZeroExContract;
|
||||
|
||||
constructor(public readonly contractAddresses: ContractAddresses, options: Partial<SwapQuoteConsumerOpts> = {}) {
|
||||
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
||||
assert.isNumber('chainId', chainId);
|
||||
this.chainId = chainId;
|
||||
this.contractAddresses = contractAddresses;
|
||||
this._exchangeProxy = new IZeroExContract(contractAddresses.exchangeProxy, FAKE_PROVIDER);
|
||||
this.transformerNonces = {
|
||||
wethTransformer: findTransformerNonce(
|
||||
contractAddresses.transformers.wethTransformer,
|
||||
contractAddresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
payTakerTransformer: findTransformerNonce(
|
||||
contractAddresses.transformers.payTakerTransformer,
|
||||
contractAddresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
fillQuoteTransformer: findTransformerNonce(
|
||||
contractAddresses.transformers.fillQuoteTransformer,
|
||||
contractAddresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
affiliateFeeTransformer: findTransformerNonce(
|
||||
contractAddresses.transformers.affiliateFeeTransformer,
|
||||
contractAddresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
positiveSlippageFeeTransformer: findTransformerNonce(
|
||||
contractAddresses.transformers.positiveSlippageFeeTransformer,
|
||||
contractAddresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
public async getCalldataOrThrowAsync(
|
||||
quote: MarketBuySwapQuote | MarketSellSwapQuote,
|
||||
opts: Partial<SwapQuoteGetOutputOpts> = {},
|
||||
): Promise<CalldataInfo> {
|
||||
const optsWithDefaults: ExchangeProxyContractOpts = {
|
||||
...constants.DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,
|
||||
...opts.extensionContractOpts,
|
||||
};
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
const { refundReceiver, affiliateFee, isFromETH, isToETH, shouldSellEntireBalance } = optsWithDefaults;
|
||||
|
||||
const sellToken = quote.takerToken;
|
||||
const buyToken = quote.makerToken;
|
||||
|
||||
// Take the bounds from the worst case
|
||||
const sellAmount = BigNumber.max(
|
||||
quote.bestCaseQuoteInfo.totalTakerAmount,
|
||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
);
|
||||
let minBuyAmount = quote.worstCaseQuoteInfo.makerAmount;
|
||||
let ethAmount = quote.worstCaseQuoteInfo.protocolFeeInWeiAmount;
|
||||
|
||||
if (isFromETH) {
|
||||
ethAmount = ethAmount.plus(sellAmount);
|
||||
}
|
||||
|
||||
const slippedOrders = slipNonNativeOrders(quote);
|
||||
|
||||
// VIP routes.
|
||||
if (
|
||||
this.chainId === ChainId.Mainnet &&
|
||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap])
|
||||
) {
|
||||
const source = slippedOrders[0].source;
|
||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToUniswap(
|
||||
fillData.tokenAddressPath.map((a, i) => {
|
||||
if (i === 0 && isFromETH) {
|
||||
return ETH_TOKEN_ADDRESS;
|
||||
}
|
||||
if (i === fillData.tokenAddressPath.length - 1 && isToETH) {
|
||||
return ETH_TOKEN_ADDRESS;
|
||||
}
|
||||
return a;
|
||||
}),
|
||||
sellAmount,
|
||||
minBuyAmount,
|
||||
source === ERC20BridgeSource.SushiSwap,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
this.chainId === ChainId.Mainnet &&
|
||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV3])
|
||||
) {
|
||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
|
||||
let _calldataHexString;
|
||||
if (isFromETH) {
|
||||
_calldataHexString = this._exchangeProxy
|
||||
.sellEthForTokenToUniswapV3(fillData.uniswapPath, minBuyAmount, NULL_ADDRESS)
|
||||
.getABIEncodedTransactionData();
|
||||
} else if (isToETH) {
|
||||
_calldataHexString = this._exchangeProxy
|
||||
.sellTokenForEthToUniswapV3(fillData.uniswapPath, sellAmount, minBuyAmount, NULL_ADDRESS)
|
||||
.getABIEncodedTransactionData();
|
||||
} else {
|
||||
_calldataHexString = this._exchangeProxy
|
||||
.sellTokenForTokenToUniswapV3(fillData.uniswapPath, sellAmount, minBuyAmount, NULL_ADDRESS)
|
||||
.getABIEncodedTransactionData();
|
||||
}
|
||||
return {
|
||||
calldataHexString: _calldataHexString,
|
||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
this.chainId === ChainId.BSC &&
|
||||
isDirectSwapCompatible(quote, optsWithDefaults, [
|
||||
ERC20BridgeSource.PancakeSwap,
|
||||
ERC20BridgeSource.PancakeSwapV2,
|
||||
ERC20BridgeSource.BakerySwap,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
])
|
||||
) {
|
||||
const source = slippedOrders[0].source;
|
||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToPancakeSwap(
|
||||
fillData.tokenAddressPath.map((a, i) => {
|
||||
if (i === 0 && isFromETH) {
|
||||
return ETH_TOKEN_ADDRESS;
|
||||
}
|
||||
if (i === fillData.tokenAddressPath.length - 1 && isToETH) {
|
||||
return ETH_TOKEN_ADDRESS;
|
||||
}
|
||||
return a;
|
||||
}),
|
||||
sellAmount,
|
||||
minBuyAmount,
|
||||
PANCAKE_SWAP_FORKS.indexOf(source),
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
[ChainId.Mainnet, ChainId.BSC].includes(this.chainId) &&
|
||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.LiquidityProvider])
|
||||
) {
|
||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
|
||||
const target = fillData.poolAddress;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToLiquidityProvider(
|
||||
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
|
||||
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
target,
|
||||
NULL_ADDRESS,
|
||||
sellAmount,
|
||||
minBuyAmount,
|
||||
NULL_BYTES,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
this.chainId === ChainId.Mainnet &&
|
||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Curve]) &&
|
||||
// Curve VIP cannot currently support WETH buy/sell as the functionality needs to WITHDRAW or DEPOSIT
|
||||
// into WETH prior/post the trade.
|
||||
// ETH buy/sell is supported
|
||||
![sellToken, buyToken].includes(NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet])
|
||||
) {
|
||||
const fillData = slippedOrders[0].fillData as CurveFillData;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToLiquidityProvider(
|
||||
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
|
||||
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID[this.chainId],
|
||||
NULL_ADDRESS,
|
||||
sellAmount,
|
||||
minBuyAmount,
|
||||
encodeCurveLiquidityProviderData({
|
||||
curveAddress: fillData.pool.poolAddress,
|
||||
exchangeFunctionSelector: fillData.pool.exchangeFunctionSelector,
|
||||
fromCoinIdx: new BigNumber(fillData.fromTokenIdx),
|
||||
toCoinIdx: new BigNumber(fillData.toTokenIdx),
|
||||
}),
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
// RFQT VIP
|
||||
if (
|
||||
[ChainId.Mainnet, ChainId.Polygon].includes(this.chainId) &&
|
||||
!isToETH &&
|
||||
!isFromETH &&
|
||||
quote.orders.every(o => o.type === FillQuoteTransformerOrderType.Rfq) &&
|
||||
!requiresTransformERC20(optsWithDefaults)
|
||||
) {
|
||||
const rfqOrdersData = quote.orders.map(o => o.fillData as NativeRfqOrderFillData);
|
||||
const fillAmountPerOrder = (() => {
|
||||
// Don't think order taker amounts are clipped to actual sell amount
|
||||
// (the last one might be too large) so figure them out manually.
|
||||
let remaining = sellAmount;
|
||||
const fillAmounts = [];
|
||||
for (const o of quote.orders) {
|
||||
const fillAmount = BigNumber.min(o.takerAmount, remaining);
|
||||
fillAmounts.push(fillAmount);
|
||||
remaining = remaining.minus(fillAmount);
|
||||
}
|
||||
return fillAmounts;
|
||||
})();
|
||||
const callData =
|
||||
quote.orders.length === 1
|
||||
? this._exchangeProxy
|
||||
.fillRfqOrder(rfqOrdersData[0].order, rfqOrdersData[0].signature, fillAmountPerOrder[0])
|
||||
.getABIEncodedTransactionData()
|
||||
: this._exchangeProxy
|
||||
.batchFillRfqOrders(
|
||||
rfqOrdersData.map(d => d.order),
|
||||
rfqOrdersData.map(d => d.signature),
|
||||
fillAmountPerOrder,
|
||||
true,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
return {
|
||||
calldataHexString: callData,
|
||||
ethAmount: ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) {
|
||||
return {
|
||||
calldataHexString: this._encodeMultiplexBatchFillCalldata(
|
||||
{ ...quote, orders: slippedOrders },
|
||||
optsWithDefaults,
|
||||
),
|
||||
ethAmount,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
if (this.chainId === ChainId.Mainnet && isMultiplexMultiHopFillCompatible(quote, optsWithDefaults)) {
|
||||
return {
|
||||
calldataHexString: this._encodeMultiplexMultiHopFillCalldata(
|
||||
{ ...quote, orders: slippedOrders },
|
||||
optsWithDefaults,
|
||||
),
|
||||
ethAmount,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
// Build up the transforms.
|
||||
const transforms = [];
|
||||
// Create a WETH wrapper if coming from ETH.
|
||||
// Dont add the wethTransformer to CELO. There is no wrap/unwrap logic for CELO.
|
||||
if (isFromETH && this.chainId !== ChainId.Celo) {
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.wethTransformer,
|
||||
data: encodeWethTransformerData({
|
||||
token: ETH_TOKEN_ADDRESS,
|
||||
amount: shouldSellEntireBalance ? MAX_UINT256 : sellAmount,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// If it's two hop we have an intermediate token this is needed to encode the individual FQT
|
||||
// and we also want to ensure no dust amount is left in the flash wallet
|
||||
const intermediateToken = quote.isTwoHop ? slippedOrders[0].makerToken : NULL_ADDRESS;
|
||||
// This transformer will fill the quote.
|
||||
if (quote.isTwoHop) {
|
||||
const [firstHopOrder, secondHopOrder] = slippedOrders;
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
||||
data: encodeFillQuoteTransformerData({
|
||||
side: FillQuoteTransformerSide.Sell,
|
||||
sellToken,
|
||||
buyToken: intermediateToken,
|
||||
...getFQTTransformerDataFromOptimizedOrders([firstHopOrder]),
|
||||
refundReceiver: refundReceiver || NULL_ADDRESS,
|
||||
fillAmount: shouldSellEntireBalance ? MAX_UINT256 : firstHopOrder.takerAmount,
|
||||
}),
|
||||
});
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
||||
data: encodeFillQuoteTransformerData({
|
||||
side: FillQuoteTransformerSide.Sell,
|
||||
buyToken,
|
||||
sellToken: intermediateToken,
|
||||
...getFQTTransformerDataFromOptimizedOrders([secondHopOrder]),
|
||||
refundReceiver: refundReceiver || NULL_ADDRESS,
|
||||
fillAmount: MAX_UINT256,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
const fillAmount = isBuyQuote(quote) ? quote.makerTokenFillAmount : quote.takerTokenFillAmount;
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
||||
data: encodeFillQuoteTransformerData({
|
||||
side: isBuyQuote(quote) ? FillQuoteTransformerSide.Buy : FillQuoteTransformerSide.Sell,
|
||||
sellToken,
|
||||
buyToken,
|
||||
...getFQTTransformerDataFromOptimizedOrders(slippedOrders),
|
||||
refundReceiver: refundReceiver || NULL_ADDRESS,
|
||||
fillAmount: !isBuyQuote(quote) && shouldSellEntireBalance ? MAX_UINT256 : fillAmount,
|
||||
}),
|
||||
});
|
||||
}
|
||||
// Create a WETH unwrapper if going to ETH.
|
||||
// Dont add the wethTransformer on CELO. There is no wrap/unwrap logic for CELO.
|
||||
if (isToETH && this.chainId !== ChainId.Celo) {
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.wethTransformer,
|
||||
data: encodeWethTransformerData({
|
||||
token: NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId],
|
||||
amount: MAX_UINT256,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
const { feeType, buyTokenFeeAmount, sellTokenFeeAmount, recipient: feeRecipient } = affiliateFee;
|
||||
let gasOverhead = ZERO_AMOUNT;
|
||||
if (feeType === AffiliateFeeType.PositiveSlippageFee && feeRecipient !== NULL_ADDRESS) {
|
||||
// bestCaseAmountWithSurplus is used to cover gas cost of sending positive slipapge fee to fee recipient
|
||||
// this helps avoid sending dust amounts which are not worth the gas cost to transfer
|
||||
let bestCaseAmountWithSurplus = quote.bestCaseQuoteInfo.makerAmount
|
||||
.plus(
|
||||
POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS.multipliedBy(quote.gasPrice).multipliedBy(
|
||||
quote.makerAmountPerEth,
|
||||
),
|
||||
)
|
||||
.integerValue();
|
||||
// In the event makerAmountPerEth is unknown, we only allow for positive slippage which is greater than
|
||||
// the best case amount
|
||||
bestCaseAmountWithSurplus = BigNumber.max(bestCaseAmountWithSurplus, quote.bestCaseQuoteInfo.makerAmount);
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.positiveSlippageFeeTransformer,
|
||||
data: encodePositiveSlippageFeeTransformerData({
|
||||
token: isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
bestCaseAmount: BigNumber.max(bestCaseAmountWithSurplus, quote.bestCaseQuoteInfo.makerAmount),
|
||||
recipient: feeRecipient,
|
||||
}),
|
||||
});
|
||||
// This may not be visible at eth_estimateGas time, so we explicitly add overhead
|
||||
gasOverhead = POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS;
|
||||
} else if (feeType === AffiliateFeeType.PercentageFee && feeRecipient !== NULL_ADDRESS) {
|
||||
// This transformer pays affiliate fees.
|
||||
if (buyTokenFeeAmount.isGreaterThan(0)) {
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.affiliateFeeTransformer,
|
||||
data: encodeAffiliateFeeTransformerData({
|
||||
fees: [
|
||||
{
|
||||
token: isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
amount: buyTokenFeeAmount,
|
||||
recipient: feeRecipient,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
// Adjust the minimum buy amount by the fee.
|
||||
minBuyAmount = BigNumber.max(0, minBuyAmount.minus(buyTokenFeeAmount));
|
||||
}
|
||||
if (sellTokenFeeAmount.isGreaterThan(0)) {
|
||||
throw new Error('Affiliate fees denominated in sell token are not yet supported');
|
||||
}
|
||||
}
|
||||
|
||||
// Return any unspent sell tokens.
|
||||
const payTakerTokens = [sellToken];
|
||||
// Return any unspent intermediate tokens for two-hop swaps.
|
||||
if (quote.isTwoHop) {
|
||||
payTakerTokens.push(intermediateToken);
|
||||
}
|
||||
// Return any unspent ETH. If ETH is the buy token, it will
|
||||
// be returned in TransformERC20Feature rather than PayTakerTransformer.
|
||||
if (!isToETH) {
|
||||
payTakerTokens.push(ETH_TOKEN_ADDRESS);
|
||||
}
|
||||
// The final transformer will send all funds to the taker.
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.payTakerTransformer,
|
||||
data: encodePayTakerTransformerData({
|
||||
tokens: payTakerTokens,
|
||||
amounts: [],
|
||||
}),
|
||||
});
|
||||
const TO_ETH_ADDRESS = this.chainId === ChainId.Celo ? this.contractAddresses.etherToken : ETH_TOKEN_ADDRESS;
|
||||
const calldataHexString = this._exchangeProxy
|
||||
.transformERC20(
|
||||
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
|
||||
isToETH ? TO_ETH_ADDRESS : buyToken,
|
||||
shouldSellEntireBalance ? MAX_UINT256 : sellAmount,
|
||||
minBuyAmount,
|
||||
transforms,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
|
||||
return {
|
||||
calldataHexString,
|
||||
ethAmount,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead,
|
||||
};
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
public async executeSwapQuoteOrThrowAsync(
|
||||
_quote: SwapQuote,
|
||||
_opts: Partial<SwapQuoteExecutionOpts>,
|
||||
): Promise<string> {
|
||||
throw new Error('Execution not supported for Exchange Proxy quotes');
|
||||
}
|
||||
|
||||
private _encodeMultiplexBatchFillCalldata(quote: SwapQuote, opts: ExchangeProxyContractOpts): string {
|
||||
const subcalls = [];
|
||||
for_loop: for (const [i, order] of quote.orders.entries()) {
|
||||
switch_statement: switch (order.source) {
|
||||
case ERC20BridgeSource.Native:
|
||||
if (order.type !== FillQuoteTransformerOrderType.Rfq) {
|
||||
// Should never happen because we check `isMultiplexBatchFillCompatible`
|
||||
// before calling this function.
|
||||
throw new Error('Multiplex batch fill only supported for RFQ native orders');
|
||||
}
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.Rfq,
|
||||
sellAmount: order.takerAmount,
|
||||
data: multiplexRfqEncoder.encode({
|
||||
order: order.fillData.order,
|
||||
signature: order.fillData.signature,
|
||||
}),
|
||||
});
|
||||
break switch_statement;
|
||||
case ERC20BridgeSource.UniswapV2:
|
||||
case ERC20BridgeSource.SushiSwap:
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.UniswapV2,
|
||||
sellAmount: order.takerAmount,
|
||||
data: multiplexUniswapEncoder.encode({
|
||||
tokens: (order.fillData as UniswapV2FillData).tokenAddressPath,
|
||||
isSushi: order.source === ERC20BridgeSource.SushiSwap,
|
||||
}),
|
||||
});
|
||||
break switch_statement;
|
||||
case ERC20BridgeSource.LiquidityProvider:
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.LiquidityProvider,
|
||||
sellAmount: order.takerAmount,
|
||||
data: multiplexPlpEncoder.encode({
|
||||
provider: (order.fillData as LiquidityProviderFillData).poolAddress,
|
||||
auxiliaryData: NULL_BYTES,
|
||||
}),
|
||||
});
|
||||
break switch_statement;
|
||||
case ERC20BridgeSource.UniswapV3:
|
||||
const fillData = (order as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.UniswapV3,
|
||||
sellAmount: order.takerAmount,
|
||||
data: fillData.uniswapPath,
|
||||
});
|
||||
break switch_statement;
|
||||
default:
|
||||
const fqtData = encodeFillQuoteTransformerData({
|
||||
side: FillQuoteTransformerSide.Sell,
|
||||
sellToken: quote.takerToken,
|
||||
buyToken: quote.makerToken,
|
||||
...getFQTTransformerDataFromOptimizedOrders(quote.orders.slice(i)),
|
||||
refundReceiver: NULL_ADDRESS,
|
||||
fillAmount: MAX_UINT256,
|
||||
});
|
||||
const transformations = [
|
||||
{ deploymentNonce: this.transformerNonces.fillQuoteTransformer, data: fqtData },
|
||||
{
|
||||
deploymentNonce: this.transformerNonces.payTakerTransformer,
|
||||
data: encodePayTakerTransformerData({
|
||||
tokens: [quote.takerToken],
|
||||
amounts: [],
|
||||
}),
|
||||
},
|
||||
];
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.TransformERC20,
|
||||
sellAmount: BigNumber.sum(...quote.orders.slice(i).map(o => o.takerAmount)),
|
||||
data: multiplexTransformERC20Encoder.encode({
|
||||
transformations,
|
||||
}),
|
||||
});
|
||||
break for_loop;
|
||||
}
|
||||
}
|
||||
if (opts.isFromETH) {
|
||||
return this._exchangeProxy
|
||||
.multiplexBatchSellEthForToken(quote.makerToken, subcalls, quote.worstCaseQuoteInfo.makerAmount)
|
||||
.getABIEncodedTransactionData();
|
||||
} else if (opts.isToETH) {
|
||||
return this._exchangeProxy
|
||||
.multiplexBatchSellTokenForEth(
|
||||
quote.takerToken,
|
||||
subcalls,
|
||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
quote.worstCaseQuoteInfo.makerAmount,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
} else {
|
||||
return this._exchangeProxy
|
||||
.multiplexBatchSellTokenForToken(
|
||||
quote.takerToken,
|
||||
quote.makerToken,
|
||||
subcalls,
|
||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
quote.worstCaseQuoteInfo.makerAmount,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
}
|
||||
}
|
||||
|
||||
private _encodeMultiplexMultiHopFillCalldata(quote: SwapQuote, opts: ExchangeProxyContractOpts): string {
|
||||
const subcalls = [];
|
||||
const [firstHopOrder, secondHopOrder] = quote.orders;
|
||||
const intermediateToken = firstHopOrder.makerToken;
|
||||
const tokens = [quote.takerToken, intermediateToken, quote.makerToken];
|
||||
|
||||
for (const order of [firstHopOrder, secondHopOrder]) {
|
||||
switch (order.source) {
|
||||
case ERC20BridgeSource.UniswapV2:
|
||||
case ERC20BridgeSource.SushiSwap:
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.UniswapV2,
|
||||
data: multiplexUniswapEncoder.encode({
|
||||
tokens: (order.fillData as UniswapV2FillData).tokenAddressPath,
|
||||
isSushi: order.source === ERC20BridgeSource.SushiSwap,
|
||||
}),
|
||||
});
|
||||
break;
|
||||
case ERC20BridgeSource.LiquidityProvider:
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.LiquidityProvider,
|
||||
data: multiplexPlpEncoder.encode({
|
||||
provider: (order.fillData as LiquidityProviderFillData).poolAddress,
|
||||
auxiliaryData: NULL_BYTES,
|
||||
}),
|
||||
});
|
||||
break;
|
||||
case ERC20BridgeSource.UniswapV3:
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.UniswapV3,
|
||||
data: (order.fillData as FinalUniswapV3FillData).uniswapPath,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// Should never happen because we check `isMultiplexMultiHopFillCompatible`
|
||||
// before calling this function.
|
||||
throw new Error(`Multiplex multi-hop unsupported source: ${order.source}`);
|
||||
}
|
||||
}
|
||||
if (opts.isFromETH) {
|
||||
return this._exchangeProxy
|
||||
.multiplexMultiHopSellEthForToken(tokens, subcalls, quote.worstCaseQuoteInfo.makerAmount)
|
||||
.getABIEncodedTransactionData();
|
||||
} else if (opts.isToETH) {
|
||||
return this._exchangeProxy
|
||||
.multiplexMultiHopSellTokenForEth(
|
||||
tokens,
|
||||
subcalls,
|
||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
quote.worstCaseQuoteInfo.makerAmount,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
} else {
|
||||
return this._exchangeProxy
|
||||
.multiplexMultiHopSellTokenForToken(
|
||||
tokens,
|
||||
subcalls,
|
||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
quote.worstCaseQuoteInfo.makerAmount,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function slipNonNativeOrders(quote: MarketSellSwapQuote | MarketBuySwapQuote): OptimizedMarketOrder[] {
|
||||
const slippage = getMaxQuoteSlippageRate(quote);
|
||||
if (slippage === 0) {
|
||||
return quote.orders;
|
||||
}
|
||||
return quote.orders.map(o => {
|
||||
if (o.source === ERC20BridgeSource.Native) {
|
||||
return o;
|
||||
}
|
||||
return {
|
||||
...o,
|
||||
...(quote.type === MarketOperation.Sell
|
||||
? {
|
||||
makerAmount: o.makerAmount.eq(MAX_UINT256)
|
||||
? MAX_UINT256
|
||||
: o.makerAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN),
|
||||
}
|
||||
: {
|
||||
takerAmount: o.takerAmount.eq(MAX_UINT256)
|
||||
? MAX_UINT256
|
||||
: o.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP),
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getMaxQuoteSlippageRate(quote: MarketBuySwapQuote | MarketSellSwapQuote): number {
|
||||
return quote.worstCaseQuoteInfo.slippage;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { RfqOrder, SIGNATURE_ABI } from '@0x/protocol-utils';
|
||||
import { AbiEncoder } from '@0x/utils';
|
||||
|
||||
export enum MultiplexSubcall {
|
||||
Invalid,
|
||||
Rfq,
|
||||
Otc,
|
||||
UniswapV2,
|
||||
UniswapV3,
|
||||
LiquidityProvider,
|
||||
TransformERC20,
|
||||
BatchSell,
|
||||
MultiHopSell,
|
||||
}
|
||||
export const multiplexTransformERC20Encoder = AbiEncoder.create([
|
||||
{
|
||||
name: 'transformations',
|
||||
type: 'tuple[]',
|
||||
components: [
|
||||
{ name: 'deploymentNonce', type: 'uint32' },
|
||||
{ name: 'data', type: 'bytes' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
export const multiplexRfqEncoder = AbiEncoder.create([
|
||||
{ name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI },
|
||||
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
|
||||
]);
|
||||
export const multiplexUniswapEncoder = AbiEncoder.create([
|
||||
{ name: 'tokens', type: 'address[]' },
|
||||
{ name: 'isSushi', type: 'bool' },
|
||||
]);
|
||||
export const multiplexPlpEncoder = AbiEncoder.create([
|
||||
{ name: 'provider', type: 'address' },
|
||||
{ name: 'auxiliaryData', type: 'bytes' },
|
||||
]);
|
||||
@@ -1,170 +0,0 @@
|
||||
import { FillQuoteTransformerData, FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
|
||||
import { ExchangeProxyContractOpts, MarketBuySwapQuote, MarketOperation, SwapQuote } from '../types';
|
||||
import {
|
||||
createBridgeDataForBridgeOrder,
|
||||
getErc20BridgeSourceToBridgeSource,
|
||||
} from '../utils/market_operation_utils/orders';
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
NativeLimitOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
OptimizedMarketBridgeOrder,
|
||||
OptimizedMarketOrder,
|
||||
OptimizedMarketOrderBase,
|
||||
} from '../utils/market_operation_utils/types';
|
||||
|
||||
const MULTIPLEX_BATCH_FILL_SOURCES = [
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns true iff a quote can be filled via `MultiplexFeature.batchFill`.
|
||||
*/
|
||||
export function isMultiplexBatchFillCompatible(quote: SwapQuote, opts: ExchangeProxyContractOpts): boolean {
|
||||
if (requiresTransformERC20(opts)) {
|
||||
return false;
|
||||
}
|
||||
if (quote.isTwoHop) {
|
||||
return false;
|
||||
}
|
||||
if (quote.orders.map(o => o.type).includes(FillQuoteTransformerOrderType.Limit)) {
|
||||
return false;
|
||||
}
|
||||
// Use Multiplex if the non-fallback sources are a subset of
|
||||
// {UniswapV2, Sushiswap, RFQ, PLP, UniswapV3}
|
||||
const nonFallbackSources = Object.keys(quote.sourceBreakdown);
|
||||
return nonFallbackSources.every(source => MULTIPLEX_BATCH_FILL_SOURCES.includes(source as ERC20BridgeSource));
|
||||
}
|
||||
|
||||
const MULTIPLEX_MULTIHOP_FILL_SOURCES = [
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
ERC20BridgeSource.SushiSwap,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns true iff a quote can be filled via `MultiplexFeature.multiHopFill`.
|
||||
*/
|
||||
export function isMultiplexMultiHopFillCompatible(quote: SwapQuote, opts: ExchangeProxyContractOpts): boolean {
|
||||
if (requiresTransformERC20(opts)) {
|
||||
return false;
|
||||
}
|
||||
if (!quote.isTwoHop) {
|
||||
return false;
|
||||
}
|
||||
const [firstHopOrder, secondHopOrder] = quote.orders;
|
||||
return (
|
||||
MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(firstHopOrder.source) &&
|
||||
MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(secondHopOrder.source)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true iff a quote can be filled via a VIP feature.
|
||||
*/
|
||||
|
||||
export function isDirectSwapCompatible(
|
||||
quote: SwapQuote,
|
||||
opts: ExchangeProxyContractOpts,
|
||||
directSources: ERC20BridgeSource[],
|
||||
): boolean {
|
||||
if (requiresTransformERC20(opts)) {
|
||||
return false;
|
||||
}
|
||||
// Must be a single order.
|
||||
if (quote.orders.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const order = quote.orders[0];
|
||||
if (!directSources.includes(order.source)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a quote is a market buy or not.
|
||||
*/
|
||||
export function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
|
||||
return quote.type === MarketOperation.Buy;
|
||||
}
|
||||
|
||||
function isOptimizedBridgeOrder(x: OptimizedMarketOrder): x is OptimizedMarketBridgeOrder {
|
||||
return x.type === FillQuoteTransformerOrderType.Bridge;
|
||||
}
|
||||
|
||||
function isOptimizedLimitOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
||||
return x.type === FillQuoteTransformerOrderType.Limit;
|
||||
}
|
||||
|
||||
function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
||||
return x.type === FillQuoteTransformerOrderType.Rfq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given `OptimizedMarketOrder`s into bridge, limit, and RFQ orders for
|
||||
* FillQuoteTransformer.
|
||||
*/
|
||||
export function getFQTTransformerDataFromOptimizedOrders(
|
||||
orders: OptimizedMarketOrder[],
|
||||
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> {
|
||||
const fqtData: Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> = {
|
||||
bridgeOrders: [],
|
||||
limitOrders: [],
|
||||
rfqOrders: [],
|
||||
fillSequence: [],
|
||||
};
|
||||
|
||||
for (const order of orders) {
|
||||
if (isOptimizedBridgeOrder(order)) {
|
||||
fqtData.bridgeOrders.push({
|
||||
bridgeData: createBridgeDataForBridgeOrder(order),
|
||||
makerTokenAmount: order.makerAmount,
|
||||
takerTokenAmount: order.takerAmount,
|
||||
source: getErc20BridgeSourceToBridgeSource(order.source),
|
||||
});
|
||||
} else if (isOptimizedLimitOrder(order)) {
|
||||
fqtData.limitOrders.push({
|
||||
order: order.fillData.order,
|
||||
signature: order.fillData.signature,
|
||||
maxTakerTokenFillAmount: order.takerAmount,
|
||||
});
|
||||
} else if (isOptimizedRfqOrder(order)) {
|
||||
fqtData.rfqOrders.push({
|
||||
order: order.fillData.order,
|
||||
signature: order.fillData.signature,
|
||||
maxTakerTokenFillAmount: order.takerAmount,
|
||||
});
|
||||
} else {
|
||||
// Should never happen
|
||||
throw new Error('Unknown Order type');
|
||||
}
|
||||
fqtData.fillSequence.push(order.type);
|
||||
}
|
||||
return fqtData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if swap quote must go through `tranformERC20`.
|
||||
*/
|
||||
export function requiresTransformERC20(opts: ExchangeProxyContractOpts): boolean {
|
||||
// Is a mtx.
|
||||
if (opts.isMetaTransaction) {
|
||||
return true;
|
||||
}
|
||||
// Has an affiliate fee.
|
||||
if (!opts.affiliateFee.buyTokenFeeAmount.eq(0) || !opts.affiliateFee.sellTokenFeeAmount.eq(0)) {
|
||||
return true;
|
||||
}
|
||||
// VIP does not support selling the entire balance
|
||||
if (opts.shouldSellEntireBalance) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { ContractAddresses, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import {
|
||||
CalldataInfo,
|
||||
SwapQuote,
|
||||
SwapQuoteConsumerBase,
|
||||
SwapQuoteConsumerOpts,
|
||||
SwapQuoteExecutionOpts,
|
||||
SwapQuoteGetOutputOpts,
|
||||
} from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
|
||||
import { ExchangeProxySwapQuoteConsumer } from './exchange_proxy_swap_quote_consumer';
|
||||
|
||||
export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
public readonly chainId: number;
|
||||
|
||||
private readonly _contractAddresses: ContractAddresses;
|
||||
private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer;
|
||||
|
||||
public static getSwapQuoteConsumer(options: Partial<SwapQuoteConsumerOpts> = {}): SwapQuoteConsumer {
|
||||
return new SwapQuoteConsumer(options);
|
||||
}
|
||||
|
||||
constructor(options: Partial<SwapQuoteConsumerOpts> = {}) {
|
||||
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
||||
assert.isNumber('chainId', chainId);
|
||||
|
||||
this.chainId = chainId;
|
||||
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
|
||||
this._exchangeProxyConsumer = new ExchangeProxySwapQuoteConsumer(this._contractAddresses, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a SwapQuote, returns 'CalldataInfo' for a 0x extesion or exchange call. See type definition of CalldataInfo for more information.
|
||||
* @param quote An object that conforms to SwapQuote. See type definition for more information.
|
||||
* @param opts Options for getting SmartContractParams. See type definition for more information.
|
||||
*/
|
||||
public async getCalldataOrThrowAsync(
|
||||
quote: SwapQuote,
|
||||
opts: Partial<SwapQuoteGetOutputOpts> = {},
|
||||
): Promise<CalldataInfo> {
|
||||
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
|
||||
return consumer.getCalldataOrThrowAsync(quote, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a SwapQuote and desired rate (in takerAsset), attempt to execute the swap with 0x extension or exchange contract.
|
||||
* @param quote An object that conforms to SwapQuote. See type definition for more information.
|
||||
* @param opts Options for getting CalldataInfo. See type definition for more information.
|
||||
*/
|
||||
public async executeSwapQuoteOrThrowAsync(
|
||||
quote: SwapQuote,
|
||||
opts: Partial<SwapQuoteExecutionOpts> = {},
|
||||
): Promise<string> {
|
||||
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
|
||||
return consumer.executeSwapQuoteOrThrowAsync(quote, opts);
|
||||
}
|
||||
|
||||
private async _getConsumerForSwapQuoteAsync(opts: Partial<SwapQuoteGetOutputOpts>): Promise<SwapQuoteConsumerBase> {
|
||||
return this._exchangeProxyConsumer;
|
||||
}
|
||||
}
|
||||
@@ -1,620 +0,0 @@
|
||||
import { ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import { FastABI } from '@0x/fast-abi';
|
||||
import { FillQuoteTransformerOrderType, LimitOrder } from '@0x/protocol-utils';
|
||||
import { BigNumber, providerUtils } from '@0x/utils';
|
||||
import Axios, { AxiosInstance } from 'axios';
|
||||
import { BlockParamLiteral, MethodAbi, SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
||||
import { Agent as HttpAgent } from 'http';
|
||||
import { Agent as HttpsAgent } from 'https';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts } from './artifacts';
|
||||
import { constants, INVALID_SIGNATURE, KEEP_ALIVE_TTL } from './constants';
|
||||
import {
|
||||
AssetSwapperContractAddresses,
|
||||
MarketBuySwapQuote,
|
||||
MarketOperation,
|
||||
OrderPrunerPermittedFeeTypes,
|
||||
RfqRequestOpts,
|
||||
SignedNativeOrder,
|
||||
SwapQuote,
|
||||
SwapQuoteInfo,
|
||||
SwapQuoteOrdersBreakdown,
|
||||
SwapQuoteRequestOpts,
|
||||
SwapQuoterOpts,
|
||||
SwapQuoterRfqOpts,
|
||||
} from './types';
|
||||
import { assert } from './utils/assert';
|
||||
import { IRfqClient } from './utils/irfq_client';
|
||||
import { MarketOperationUtils } from './utils/market_operation_utils';
|
||||
import { BancorService } from './utils/market_operation_utils/bancor_service';
|
||||
import { SAMPLER_ADDRESS, SOURCE_FLAGS, ZERO_AMOUNT } from './utils/market_operation_utils/constants';
|
||||
import { DexOrderSampler } from './utils/market_operation_utils/sampler';
|
||||
import { SourceFilters } from './utils/market_operation_utils/source_filters';
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
FillData,
|
||||
GasSchedule,
|
||||
GetMarketOrdersOpts,
|
||||
OptimizedMarketOrder,
|
||||
OptimizerResultWithReport,
|
||||
} from './utils/market_operation_utils/types';
|
||||
import { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||
import { QuoteRequestor } from './utils/quote_requestor';
|
||||
import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './utils/quote_simulation';
|
||||
import { ERC20BridgeSamplerContract } from './wrappers';
|
||||
|
||||
export abstract class Orderbook {
|
||||
public abstract getOrdersAsync(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
pruneFn?: (o: SignedNativeOrder) => boolean,
|
||||
): Promise<SignedNativeOrder[]>;
|
||||
public abstract getBatchOrdersAsync(
|
||||
makerTokens: string[],
|
||||
takerToken: string,
|
||||
pruneFn?: (o: SignedNativeOrder) => boolean,
|
||||
): Promise<SignedNativeOrder[][]>;
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
public async destroyAsync(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable:max-classes-per-file
|
||||
export class SwapQuoter {
|
||||
public readonly provider: ZeroExProvider;
|
||||
public readonly orderbook: Orderbook;
|
||||
public readonly expiryBufferMs: number;
|
||||
public readonly chainId: number;
|
||||
public readonly permittedOrderFeeTypes: Set<OrderPrunerPermittedFeeTypes>;
|
||||
private readonly _contractAddresses: AssetSwapperContractAddresses;
|
||||
private readonly _protocolFeeUtils: ProtocolFeeUtils;
|
||||
private readonly _marketOperationUtils: MarketOperationUtils;
|
||||
private readonly _rfqtOptions?: SwapQuoterRfqOpts;
|
||||
private readonly _quoteRequestorHttpClient: AxiosInstance;
|
||||
private readonly _integratorIdsSet: Set<string>;
|
||||
|
||||
/**
|
||||
* Instantiates a new SwapQuoter instance
|
||||
* @param supportedProvider The Provider instance you would like to use for interacting with the Ethereum network.
|
||||
* @param orderbook An object that conforms to Orderbook, see type for definition.
|
||||
* @param options Initialization options for the SwapQuoter. See type definition for details.
|
||||
*
|
||||
* @return An instance of SwapQuoter
|
||||
*/
|
||||
constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: Partial<SwapQuoterOpts> = {}) {
|
||||
const {
|
||||
chainId,
|
||||
expiryBufferMs,
|
||||
permittedOrderFeeTypes,
|
||||
samplerGasLimit,
|
||||
rfqt,
|
||||
tokenAdjacencyGraph,
|
||||
liquidityProviderRegistry,
|
||||
} = { ...constants.DEFAULT_SWAP_QUOTER_OPTS, ...options };
|
||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||
assert.isValidOrderbook('orderbook', orderbook);
|
||||
assert.isNumber('chainId', chainId);
|
||||
assert.isNumber('expiryBufferMs', expiryBufferMs);
|
||||
this.chainId = chainId;
|
||||
this.provider = provider;
|
||||
this.orderbook = orderbook;
|
||||
this.expiryBufferMs = expiryBufferMs;
|
||||
this.permittedOrderFeeTypes = permittedOrderFeeTypes;
|
||||
|
||||
this._rfqtOptions = rfqt;
|
||||
this._contractAddresses = options.contractAddresses || {
|
||||
...getContractAddressesForChainOrThrow(chainId),
|
||||
};
|
||||
this._protocolFeeUtils = ProtocolFeeUtils.getInstance(
|
||||
constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
|
||||
options.zeroExGasApiUrl,
|
||||
);
|
||||
// Allow the sampler bytecode to be overwritten using geths override functionality
|
||||
const samplerBytecode = _.get(artifacts.ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object');
|
||||
// Allow address of the Sampler to be overridden, i.e in Ganache where overrides do not work
|
||||
const samplerAddress = (options.samplerOverrides && options.samplerOverrides.to) || SAMPLER_ADDRESS;
|
||||
const defaultCodeOverrides = samplerBytecode
|
||||
? {
|
||||
[samplerAddress]: { code: samplerBytecode },
|
||||
}
|
||||
: {};
|
||||
const samplerOverrides = _.assign(
|
||||
{ block: BlockParamLiteral.Latest, overrides: defaultCodeOverrides },
|
||||
options.samplerOverrides,
|
||||
);
|
||||
const fastAbi = new FastABI(ERC20BridgeSamplerContract.ABI() as MethodAbi[], { BigNumber });
|
||||
const samplerContract = new ERC20BridgeSamplerContract(
|
||||
samplerAddress,
|
||||
this.provider,
|
||||
{
|
||||
gas: samplerGasLimit,
|
||||
},
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
encodeInput: (fnName: string, values: any) => fastAbi.encodeInput(fnName, values),
|
||||
decodeOutput: (fnName: string, data: string) => fastAbi.decodeOutput(fnName, data),
|
||||
},
|
||||
);
|
||||
|
||||
this._marketOperationUtils = new MarketOperationUtils(
|
||||
new DexOrderSampler(
|
||||
this.chainId,
|
||||
samplerContract,
|
||||
samplerOverrides,
|
||||
undefined, // pools caches for balancer
|
||||
tokenAdjacencyGraph,
|
||||
liquidityProviderRegistry,
|
||||
this.chainId === ChainId.Mainnet // Enable Bancor only on Mainnet
|
||||
? async () => BancorService.createAsync(provider)
|
||||
: async () => undefined,
|
||||
),
|
||||
this._contractAddresses,
|
||||
{
|
||||
chainId,
|
||||
exchangeAddress: this._contractAddresses.exchange,
|
||||
},
|
||||
);
|
||||
|
||||
this._quoteRequestorHttpClient = Axios.create({
|
||||
httpAgent: new HttpAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }),
|
||||
httpsAgent: new HttpsAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }),
|
||||
...(rfqt ? rfqt.axiosInstanceOpts : {}),
|
||||
});
|
||||
|
||||
const integratorIds = this._rfqtOptions?.integratorsWhitelist.map(integrator => integrator.integratorId) || [];
|
||||
this._integratorIdsSet = new Set(integratorIds);
|
||||
}
|
||||
|
||||
public async getBatchMarketBuySwapQuoteAsync(
|
||||
makerTokens: string[],
|
||||
targetTakerToken: string,
|
||||
makerTokenBuyAmounts: BigNumber[],
|
||||
options: Partial<SwapQuoteRequestOpts> = {},
|
||||
): Promise<MarketBuySwapQuote[]> {
|
||||
makerTokenBuyAmounts.map((a, i) => assert.isBigNumber(`makerAssetBuyAmounts[${i}]`, a));
|
||||
let gasPrice: BigNumber;
|
||||
if (!!options.gasPrice) {
|
||||
gasPrice = options.gasPrice;
|
||||
assert.isBigNumber('gasPrice', gasPrice);
|
||||
} else {
|
||||
gasPrice = await this.getGasPriceEstimationOrThrowAsync();
|
||||
}
|
||||
|
||||
const allOrders = await this.orderbook.getBatchOrdersAsync(
|
||||
makerTokens,
|
||||
targetTakerToken,
|
||||
this._limitOrderPruningFn,
|
||||
);
|
||||
|
||||
// Orders could be missing from the orderbook, so we create a dummy one as a placeholder
|
||||
allOrders.forEach((orders: SignedNativeOrder[], i: number) => {
|
||||
if (!orders || orders.length === 0) {
|
||||
allOrders[i] = [createDummyOrder(makerTokens[i], targetTakerToken)];
|
||||
}
|
||||
});
|
||||
|
||||
const opts = { ...constants.DEFAULT_SWAP_QUOTE_REQUEST_OPTS, ...options };
|
||||
const optimizerResults = await this._marketOperationUtils.getBatchMarketBuyOrdersAsync(
|
||||
allOrders,
|
||||
makerTokenBuyAmounts,
|
||||
opts as GetMarketOrdersOpts,
|
||||
);
|
||||
|
||||
const batchSwapQuotes = await Promise.all(
|
||||
optimizerResults.map(async (result, i) => {
|
||||
if (result) {
|
||||
const { makerToken, takerToken } = allOrders[i][0].order;
|
||||
return createSwapQuote(
|
||||
result,
|
||||
makerToken,
|
||||
takerToken,
|
||||
MarketOperation.Buy,
|
||||
makerTokenBuyAmounts[i],
|
||||
gasPrice,
|
||||
opts.gasSchedule,
|
||||
opts.bridgeSlippage,
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}),
|
||||
);
|
||||
return batchSwapQuotes.filter(x => x !== undefined) as MarketBuySwapQuote[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recommended gas price for a fast transaction
|
||||
*/
|
||||
public async getGasPriceEstimationOrThrowAsync(): Promise<BigNumber> {
|
||||
return this._protocolFeeUtils.getGasPriceEstimationOrThrowAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys any subscriptions or connections.
|
||||
*/
|
||||
public async destroyAsync(): Promise<void> {
|
||||
await this._protocolFeeUtils.destroyAsync();
|
||||
await this.orderbook.destroyAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to get Ether token address
|
||||
*/
|
||||
public getEtherToken(): string {
|
||||
return this._contractAddresses.etherToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a `SwapQuote` containing all information relevant to fulfilling a swap between a desired ERC20 token address and ERC20 owned by a provided address.
|
||||
* You can then pass the `SwapQuote` to a `SwapQuoteConsumer` to execute a buy, or process SwapQuote for on-chain consumption.
|
||||
* @param makerToken The address of the maker asset
|
||||
* @param takerToken The address of the taker asset
|
||||
* @param assetFillAmount If a buy, the amount of maker asset to buy. If a sell, the amount of taker asset to sell.
|
||||
* @param marketOperation Either a Buy or a Sell quote
|
||||
* @param options Options for the request. See type definition for more information.
|
||||
*
|
||||
* @return An object that conforms to SwapQuote that satisfies the request. See type definition for more information.
|
||||
*/
|
||||
public async getSwapQuoteAsync(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
assetFillAmount: BigNumber,
|
||||
marketOperation: MarketOperation,
|
||||
options: Partial<SwapQuoteRequestOpts>,
|
||||
rfqClient?: IRfqClient | undefined,
|
||||
): Promise<SwapQuote> {
|
||||
assert.isETHAddressHex('makerToken', makerToken);
|
||||
assert.isETHAddressHex('takerToken', takerToken);
|
||||
assert.isBigNumber('assetFillAmount', assetFillAmount);
|
||||
const opts = _.merge({}, constants.DEFAULT_SWAP_QUOTE_REQUEST_OPTS, options);
|
||||
let gasPrice: BigNumber;
|
||||
if (!!opts.gasPrice) {
|
||||
gasPrice = opts.gasPrice;
|
||||
assert.isBigNumber('gasPrice', gasPrice);
|
||||
} else {
|
||||
gasPrice = await this.getGasPriceEstimationOrThrowAsync();
|
||||
}
|
||||
|
||||
const sourceFilters = new SourceFilters([], opts.excludedSources, opts.includedSources);
|
||||
|
||||
opts.rfqt = this._validateRfqtOpts(sourceFilters, opts.rfqt);
|
||||
const rfqtOptions = this._rfqtOptions;
|
||||
|
||||
// Get SRA orders (limit orders)
|
||||
const shouldSkipOpenOrderbook =
|
||||
!sourceFilters.isAllowed(ERC20BridgeSource.Native) ||
|
||||
(opts.rfqt && opts.rfqt.nativeExclusivelyRFQ === true);
|
||||
const nativeOrders = shouldSkipOpenOrderbook
|
||||
? await Promise.resolve([])
|
||||
: await this.orderbook.getOrdersAsync(makerToken, takerToken, this._limitOrderPruningFn);
|
||||
|
||||
// if no native orders, pass in a dummy order for the sampler to have required metadata for sampling
|
||||
if (nativeOrders.length === 0) {
|
||||
nativeOrders.push(createDummyOrder(makerToken, takerToken));
|
||||
}
|
||||
|
||||
// ** Prepare options for fetching market side liquidity **
|
||||
// Scale fees by gas price.
|
||||
const cloneOpts = _.omit(opts, 'gasPrice') as GetMarketOrdersOpts;
|
||||
const calcOpts: GetMarketOrdersOpts = {
|
||||
...cloneOpts,
|
||||
gasPrice,
|
||||
feeSchedule: _.mapValues(opts.gasSchedule, gasCost => (fillData: FillData) => {
|
||||
const gas = gasCost ? gasCost(fillData) : 0;
|
||||
const fee = gasPrice.times(gas);
|
||||
return { gas, fee };
|
||||
}),
|
||||
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
|
||||
};
|
||||
// pass the QuoteRequestor on if rfqt enabled
|
||||
if (calcOpts.rfqt !== undefined) {
|
||||
calcOpts.rfqt.quoteRequestor = new QuoteRequestor(
|
||||
rfqtOptions?.makerAssetOfferings || {},
|
||||
{},
|
||||
this._quoteRequestorHttpClient,
|
||||
rfqtOptions?.altRfqCreds,
|
||||
rfqtOptions?.warningLogger,
|
||||
rfqtOptions?.infoLogger,
|
||||
this.expiryBufferMs,
|
||||
rfqtOptions?.metricsProxy,
|
||||
);
|
||||
calcOpts.rfqt.rfqClient = rfqClient;
|
||||
}
|
||||
|
||||
const result: OptimizerResultWithReport = await this._marketOperationUtils.getOptimizerResultAsync(
|
||||
nativeOrders,
|
||||
assetFillAmount,
|
||||
marketOperation,
|
||||
calcOpts,
|
||||
);
|
||||
|
||||
const swapQuote = createSwapQuote(
|
||||
result,
|
||||
makerToken,
|
||||
takerToken,
|
||||
marketOperation,
|
||||
assetFillAmount,
|
||||
gasPrice,
|
||||
opts.gasSchedule,
|
||||
opts.bridgeSlippage,
|
||||
);
|
||||
|
||||
// Use the raw gas, not scaled by gas price
|
||||
const exchangeProxyOverhead = opts.exchangeProxyOverhead(result.sourceFlags).toNumber();
|
||||
swapQuote.bestCaseQuoteInfo.gas += exchangeProxyOverhead;
|
||||
swapQuote.worstCaseQuoteInfo.gas += exchangeProxyOverhead;
|
||||
|
||||
return swapQuote;
|
||||
}
|
||||
|
||||
private readonly _limitOrderPruningFn = (limitOrder: SignedNativeOrder) => {
|
||||
const order = new LimitOrder(limitOrder.order);
|
||||
const isOpenOrder = order.taker === constants.NULL_ADDRESS;
|
||||
const willOrderExpire = order.willExpire(this.expiryBufferMs / constants.ONE_SECOND_MS); // tslint:disable-line:boolean-naming
|
||||
const isFeeTypeAllowed =
|
||||
this.permittedOrderFeeTypes.has(OrderPrunerPermittedFeeTypes.NoFees) &&
|
||||
order.takerTokenFeeAmount.eq(constants.ZERO_AMOUNT);
|
||||
return isOpenOrder && !willOrderExpire && isFeeTypeAllowed;
|
||||
}; // tslint:disable-line:semicolon
|
||||
|
||||
private _isIntegratorIdWhitelisted(integratorId: string | undefined): boolean {
|
||||
if (!integratorId) {
|
||||
return false;
|
||||
}
|
||||
return this._integratorIdsSet.has(integratorId);
|
||||
}
|
||||
|
||||
private _isTxOriginBlacklisted(txOrigin: string | undefined): boolean {
|
||||
if (!txOrigin) {
|
||||
return false;
|
||||
}
|
||||
const blacklistedTxOrigins = this._rfqtOptions ? this._rfqtOptions.txOriginBlacklist : new Set();
|
||||
return blacklistedTxOrigins.has(txOrigin.toLowerCase());
|
||||
}
|
||||
|
||||
private _validateRfqtOpts(
|
||||
sourceFilters: SourceFilters,
|
||||
rfqt: RfqRequestOpts | undefined,
|
||||
): RfqRequestOpts | undefined {
|
||||
if (!rfqt) {
|
||||
return rfqt;
|
||||
}
|
||||
// tslint:disable-next-line: boolean-naming
|
||||
const { integrator, nativeExclusivelyRFQ, intentOnFilling, txOrigin } = rfqt;
|
||||
// If RFQ-T is enabled and `nativeExclusivelyRFQ` is set, then `ERC20BridgeSource.Native` should
|
||||
// never be excluded.
|
||||
if (nativeExclusivelyRFQ === true && !sourceFilters.isAllowed(ERC20BridgeSource.Native)) {
|
||||
throw new Error('Native liquidity cannot be excluded if "rfqt.nativeExclusivelyRFQ" is set');
|
||||
}
|
||||
|
||||
// If an integrator ID was provided, but the ID is not whitelisted, raise a warning and disable RFQ
|
||||
if (!this._isIntegratorIdWhitelisted(integrator.integratorId)) {
|
||||
if (this._rfqtOptions && this._rfqtOptions.warningLogger) {
|
||||
this._rfqtOptions.warningLogger(
|
||||
{
|
||||
...integrator,
|
||||
},
|
||||
'Attempt at using an RFQ API key that is not whitelisted. Disabling RFQ for the request lifetime.',
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If the requested tx origin is blacklisted, raise a warning and disable RFQ
|
||||
if (this._isTxOriginBlacklisted(txOrigin)) {
|
||||
if (this._rfqtOptions && this._rfqtOptions.warningLogger) {
|
||||
this._rfqtOptions.warningLogger(
|
||||
{
|
||||
txOrigin,
|
||||
},
|
||||
'Attempt at using a tx Origin that is blacklisted. Disabling RFQ for the request lifetime.',
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Otherwise check other RFQ options
|
||||
if (
|
||||
intentOnFilling && // The requestor is asking for a firm quote
|
||||
this._isIntegratorIdWhitelisted(integrator.integratorId) && // A valid API key was provided
|
||||
sourceFilters.isAllowed(ERC20BridgeSource.Native) // Native liquidity is not excluded
|
||||
) {
|
||||
if (!txOrigin || txOrigin === constants.NULL_ADDRESS) {
|
||||
throw new Error('RFQ-T firm quote requests must specify a tx origin');
|
||||
}
|
||||
}
|
||||
|
||||
return rfqt;
|
||||
}
|
||||
}
|
||||
// tslint:disable-next-line: max-file-line-count
|
||||
|
||||
// begin formatting and report generation functions
|
||||
function createSwapQuote(
|
||||
optimizerResult: OptimizerResultWithReport,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
operation: MarketOperation,
|
||||
assetFillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
gasSchedule: GasSchedule,
|
||||
slippage: number,
|
||||
): SwapQuote {
|
||||
const {
|
||||
optimizedOrders,
|
||||
quoteReport,
|
||||
extendedQuoteReportSources,
|
||||
sourceFlags,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
priceComparisonsReport,
|
||||
} = optimizerResult;
|
||||
const isTwoHop = sourceFlags === SOURCE_FLAGS[ERC20BridgeSource.MultiHop];
|
||||
|
||||
// Calculate quote info
|
||||
const { bestCaseQuoteInfo, worstCaseQuoteInfo, sourceBreakdown } = isTwoHop
|
||||
? calculateTwoHopQuoteInfo(optimizedOrders, operation, gasSchedule, slippage)
|
||||
: calculateQuoteInfo(optimizedOrders, operation, assetFillAmount, gasPrice, gasSchedule, slippage);
|
||||
|
||||
// Put together the swap quote
|
||||
const { makerTokenDecimals, takerTokenDecimals, blockNumber } = optimizerResult.marketSideLiquidity;
|
||||
const swapQuote = {
|
||||
makerToken,
|
||||
takerToken,
|
||||
gasPrice,
|
||||
orders: optimizedOrders,
|
||||
bestCaseQuoteInfo,
|
||||
worstCaseQuoteInfo,
|
||||
sourceBreakdown,
|
||||
makerTokenDecimals,
|
||||
takerTokenDecimals,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
quoteReport,
|
||||
extendedQuoteReportSources,
|
||||
isTwoHop,
|
||||
priceComparisonsReport,
|
||||
blockNumber,
|
||||
};
|
||||
|
||||
if (operation === MarketOperation.Buy) {
|
||||
return {
|
||||
...swapQuote,
|
||||
type: MarketOperation.Buy,
|
||||
makerTokenFillAmount: assetFillAmount,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...swapQuote,
|
||||
type: MarketOperation.Sell,
|
||||
takerTokenFillAmount: assetFillAmount,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function calculateQuoteInfo(
|
||||
optimizedOrders: OptimizedMarketOrder[],
|
||||
operation: MarketOperation,
|
||||
assetFillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
gasSchedule: GasSchedule,
|
||||
slippage: number,
|
||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
||||
const bestCaseFillResult = simulateBestCaseFill({
|
||||
gasPrice,
|
||||
orders: optimizedOrders,
|
||||
side: operation,
|
||||
fillAmount: assetFillAmount,
|
||||
opts: { gasSchedule },
|
||||
});
|
||||
|
||||
const worstCaseFillResult = simulateWorstCaseFill({
|
||||
gasPrice,
|
||||
orders: optimizedOrders,
|
||||
side: operation,
|
||||
fillAmount: assetFillAmount,
|
||||
opts: { gasSchedule, slippage },
|
||||
});
|
||||
|
||||
return {
|
||||
bestCaseQuoteInfo: fillResultsToQuoteInfo(bestCaseFillResult, 0),
|
||||
worstCaseQuoteInfo: fillResultsToQuoteInfo(worstCaseFillResult, slippage),
|
||||
sourceBreakdown: getSwapQuoteOrdersBreakdown(bestCaseFillResult.fillAmountBySource),
|
||||
};
|
||||
}
|
||||
|
||||
function calculateTwoHopQuoteInfo(
|
||||
optimizedOrders: OptimizedMarketOrder[],
|
||||
operation: MarketOperation,
|
||||
gasSchedule: GasSchedule,
|
||||
slippage: number,
|
||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
||||
const [firstHopOrder, secondHopOrder] = optimizedOrders;
|
||||
const gas = new BigNumber(
|
||||
gasSchedule[ERC20BridgeSource.MultiHop]!({
|
||||
firstHopSource: _.pick(firstHopOrder, 'source', 'fillData'),
|
||||
secondHopSource: _.pick(secondHopOrder, 'source', 'fillData'),
|
||||
}),
|
||||
).toNumber();
|
||||
const isSell = operation === MarketOperation.Sell;
|
||||
|
||||
return {
|
||||
bestCaseQuoteInfo: {
|
||||
makerAmount: isSell ? secondHopOrder.fill.output : secondHopOrder.fill.input,
|
||||
takerAmount: isSell ? firstHopOrder.fill.input : firstHopOrder.fill.output,
|
||||
totalTakerAmount: isSell ? firstHopOrder.fill.input : firstHopOrder.fill.output,
|
||||
feeTakerTokenAmount: constants.ZERO_AMOUNT,
|
||||
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
||||
gas,
|
||||
slippage: 0,
|
||||
},
|
||||
// TODO jacob consolidate this with quote simulation worstCase
|
||||
worstCaseQuoteInfo: {
|
||||
makerAmount: isSell
|
||||
? secondHopOrder.makerAmount.times(1 - slippage).integerValue()
|
||||
: secondHopOrder.makerAmount,
|
||||
takerAmount: isSell
|
||||
? firstHopOrder.takerAmount
|
||||
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP),
|
||||
totalTakerAmount: isSell
|
||||
? firstHopOrder.takerAmount
|
||||
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP),
|
||||
feeTakerTokenAmount: constants.ZERO_AMOUNT,
|
||||
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
||||
gas,
|
||||
slippage,
|
||||
},
|
||||
sourceBreakdown: {
|
||||
[ERC20BridgeSource.MultiHop]: {
|
||||
proportion: new BigNumber(1),
|
||||
intermediateToken: secondHopOrder.takerToken,
|
||||
hops: [firstHopOrder.source, secondHopOrder.source],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getSwapQuoteOrdersBreakdown(fillAmountBySource: { [source: string]: BigNumber }): SwapQuoteOrdersBreakdown {
|
||||
const totalFillAmount = BigNumber.sum(...Object.values(fillAmountBySource));
|
||||
const breakdown: SwapQuoteOrdersBreakdown = {};
|
||||
Object.entries(fillAmountBySource).forEach(([s, fillAmount]) => {
|
||||
const source = s as keyof SwapQuoteOrdersBreakdown;
|
||||
if (source === ERC20BridgeSource.MultiHop) {
|
||||
// TODO jacob has a different breakdown
|
||||
} else {
|
||||
breakdown[source] = fillAmount.div(totalFillAmount);
|
||||
}
|
||||
});
|
||||
return breakdown;
|
||||
}
|
||||
|
||||
function fillResultsToQuoteInfo(fr: QuoteFillResult, slippage: number): SwapQuoteInfo {
|
||||
return {
|
||||
makerAmount: fr.totalMakerAssetAmount,
|
||||
takerAmount: fr.takerAssetAmount,
|
||||
totalTakerAmount: fr.totalTakerAssetAmount,
|
||||
feeTakerTokenAmount: fr.takerFeeTakerAssetAmount,
|
||||
protocolFeeInWeiAmount: fr.protocolFeeAmount,
|
||||
gas: fr.gas,
|
||||
slippage,
|
||||
};
|
||||
}
|
||||
|
||||
function createDummyOrder(makerToken: string, takerToken: string): SignedNativeOrder {
|
||||
return {
|
||||
type: FillQuoteTransformerOrderType.Limit,
|
||||
order: {
|
||||
...new LimitOrder({
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerAmount: ZERO_AMOUNT,
|
||||
takerAmount: ZERO_AMOUNT,
|
||||
takerTokenFeeAmount: ZERO_AMOUNT,
|
||||
}),
|
||||
},
|
||||
signature: INVALID_SIGNATURE,
|
||||
};
|
||||
}
|
||||
@@ -1,467 +0,0 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { BlockParam, ContractAddresses, GethCallOverrides } from '@0x/contract-wrappers';
|
||||
import {
|
||||
FillQuoteTransformerOrderType,
|
||||
LimitOrderFields,
|
||||
RfqOrder,
|
||||
RfqOrderFields,
|
||||
Signature,
|
||||
} from '@0x/protocol-utils';
|
||||
import { TakerRequestQueryParamsUnnested, V4SignedRfqOrder } from '@0x/quote-server';
|
||||
import { Fee } from '@0x/quote-server/lib/src/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
GetMarketOrdersOpts,
|
||||
LiquidityProviderRegistry,
|
||||
OptimizedMarketOrder,
|
||||
} from './utils/market_operation_utils/types';
|
||||
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
|
||||
import { MetricsProxy } from './utils/quote_requestor';
|
||||
import { TokenAdjacencyGraph } from './utils/token_adjacency_graph';
|
||||
export { SamplerMetrics } from './utils/market_operation_utils/types';
|
||||
|
||||
export type Address = string;
|
||||
|
||||
/**
|
||||
* expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
|
||||
* permittedOrderFeeTypes: A set of all the takerFee types that OrderPruner will filter for
|
||||
*/
|
||||
export interface OrderPrunerOpts {
|
||||
expiryBufferMs: number;
|
||||
permittedOrderFeeTypes: Set<OrderPrunerPermittedFeeTypes>;
|
||||
}
|
||||
|
||||
export interface SignedOrder<T> {
|
||||
order: T;
|
||||
type: FillQuoteTransformerOrderType.Limit | FillQuoteTransformerOrderType.Rfq;
|
||||
signature: Signature;
|
||||
}
|
||||
|
||||
export type SignedNativeOrder = SignedOrder<LimitOrderFields> | SignedOrder<RfqOrderFields>;
|
||||
export type NativeOrderWithFillableAmounts = SignedNativeOrder & NativeOrderFillableAmountFields;
|
||||
|
||||
/**
|
||||
* fillableMakerAmount: Amount of makerAsset that is fillable
|
||||
* fillableTakerAmount: Amount of takerAsset that is fillable
|
||||
* fillableTakerFeeAmount: Amount of takerFee paid to fill fillableTakerAmount
|
||||
*/
|
||||
export interface NativeOrderFillableAmountFields {
|
||||
fillableMakerAmount: BigNumber;
|
||||
fillableTakerAmount: BigNumber;
|
||||
fillableTakerFeeAmount: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the metadata to call a smart contract with calldata.
|
||||
* calldataHexString: The hexstring of the calldata.
|
||||
* toAddress: The contract address to call.
|
||||
* ethAmount: The eth amount in wei to send with the smart contract call.
|
||||
* allowanceTarget: The address the taker should grant an allowance to.
|
||||
* gasOverhead: The gas overhead needed to be added to the gas limit to allow for optional
|
||||
* operations which may not visible at eth_estimateGas time
|
||||
*/
|
||||
export interface CalldataInfo {
|
||||
calldataHexString: string;
|
||||
toAddress: string;
|
||||
ethAmount: BigNumber;
|
||||
allowanceTarget: string;
|
||||
gasOverhead: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface that varying SwapQuoteConsumers adhere to (exchange consumer, router consumer, forwarder consumer, coordinator consumer)
|
||||
* getCalldataOrThrow: Get CalldataInfo to swap for tokens with provided SwapQuote. Throws if invalid SwapQuote is provided.
|
||||
* executeSwapQuoteOrThrowAsync: Executes a web3 transaction to swap for tokens with provided SwapQuote. Throws if invalid SwapQuote is provided.
|
||||
*/
|
||||
export interface SwapQuoteConsumerBase {
|
||||
getCalldataOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteGetOutputOpts>): Promise<CalldataInfo>;
|
||||
executeSwapQuoteOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteExecutionOpts>): Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* chainId: The chainId that the desired orders should be for.
|
||||
*/
|
||||
export interface SwapQuoteConsumerOpts {
|
||||
chainId: number;
|
||||
contractAddresses?: ContractAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the options provided to a generic SwapQuoteConsumer
|
||||
*/
|
||||
export interface SwapQuoteGetOutputOpts {
|
||||
extensionContractOpts?: ExchangeProxyContractOpts | any;
|
||||
}
|
||||
|
||||
/**
|
||||
* ethAmount: The amount of eth sent with the execution of a swap.
|
||||
* takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
|
||||
* gasLimit: The amount of gas to send with a transaction (in Gwei). Defaults to an eth_estimateGas rpc call.
|
||||
*/
|
||||
export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts {
|
||||
ethAmount?: BigNumber;
|
||||
takerAddress?: string;
|
||||
gasLimit?: number;
|
||||
}
|
||||
|
||||
export enum AffiliateFeeType {
|
||||
None,
|
||||
PercentageFee,
|
||||
PositiveSlippageFee,
|
||||
}
|
||||
|
||||
export interface AffiliateFeeAmount {
|
||||
feeType: AffiliateFeeType;
|
||||
recipient: string;
|
||||
buyTokenFeeAmount: BigNumber;
|
||||
sellTokenFeeAmount: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically resolved protocol fee refund receiver addresses.
|
||||
*/
|
||||
export enum ExchangeProxyRefundReceiver {
|
||||
// Refund to the taker address.
|
||||
Taker = '0x0000000000000000000000000000000000000001',
|
||||
// Refund to the sender address.
|
||||
Sender = '0x0000000000000000000000000000000000000002',
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isFromETH Whether the input token is ETH.
|
||||
* @param isToETH Whether the output token is ETH.
|
||||
* @param affiliateFee Fee denominated in taker or maker asset to send to specified recipient.
|
||||
* @param refundReceiver The receiver of unspent protocol fees.
|
||||
* May be a valid address or one of:
|
||||
* `address(0)`: Stay in flash wallet.
|
||||
* `address(1)`: Send to the taker.
|
||||
* `address(2)`: Send to the sender (caller of `transformERC20()`).
|
||||
* @param shouldSellEntireBalance Whether the entire balance of the caller should be sold. Used
|
||||
* for contracts where the balance at transaction time is different to the quote amount.
|
||||
* This foregos certain VIP routes which do not support this feature.
|
||||
*/
|
||||
export interface ExchangeProxyContractOpts {
|
||||
isFromETH: boolean;
|
||||
isToETH: boolean;
|
||||
affiliateFee: AffiliateFeeAmount;
|
||||
refundReceiver: string | ExchangeProxyRefundReceiver;
|
||||
isMetaTransaction: boolean;
|
||||
shouldSellEntireBalance: boolean;
|
||||
}
|
||||
|
||||
export interface GetExtensionContractTypeOpts {
|
||||
takerAddress?: string;
|
||||
ethAmount?: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* takerToken: Address of the taker asset.
|
||||
* makerToken: Address of the maker asset.
|
||||
* gasPrice: gas price used to determine protocolFee amount, default to ethGasStation fast amount.
|
||||
* orders: An array of objects conforming to OptimizedMarketOrder. These orders can be used to cover the requested assetBuyAmount plus slippage.
|
||||
* bestCaseQuoteInfo: Info about the best case price for the asset.
|
||||
* worstCaseQuoteInfo: Info about the worst case price for the asset.
|
||||
*/
|
||||
export interface SwapQuoteBase {
|
||||
takerToken: string;
|
||||
makerToken: string;
|
||||
gasPrice: BigNumber;
|
||||
orders: OptimizedMarketOrder[];
|
||||
bestCaseQuoteInfo: SwapQuoteInfo;
|
||||
worstCaseQuoteInfo: SwapQuoteInfo;
|
||||
sourceBreakdown: SwapQuoteOrdersBreakdown;
|
||||
quoteReport?: QuoteReport;
|
||||
extendedQuoteReportSources?: ExtendedQuoteReportSources;
|
||||
priceComparisonsReport?: PriceComparisonsReport;
|
||||
isTwoHop: boolean;
|
||||
makerTokenDecimals: number;
|
||||
takerTokenDecimals: number;
|
||||
takerAmountPerEth: BigNumber;
|
||||
makerAmountPerEth: BigNumber;
|
||||
blockNumber: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* takerAssetFillAmount: The amount of takerAsset sold for makerAsset.
|
||||
* type: Specified MarketOperation the SwapQuote is provided for
|
||||
*/
|
||||
export interface MarketSellSwapQuote extends SwapQuoteBase {
|
||||
takerTokenFillAmount: BigNumber;
|
||||
type: MarketOperation.Sell;
|
||||
}
|
||||
|
||||
/**
|
||||
* makerAssetFillAmount: The amount of makerAsset bought with takerAsset.
|
||||
* type: Specified MarketOperation the SwapQuote is provided for
|
||||
*/
|
||||
export interface MarketBuySwapQuote extends SwapQuoteBase {
|
||||
makerTokenFillAmount: BigNumber;
|
||||
type: MarketOperation.Buy;
|
||||
}
|
||||
|
||||
export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
|
||||
|
||||
/**
|
||||
* feeTakerTokenAmount: The amount of takerAsset reserved for paying takerFees when swapping for desired assets.
|
||||
* takerTokenAmount: The amount of takerAsset swapped for desired makerAsset.
|
||||
* totalTakerTokenAmount: The total amount of takerAsset required to complete the swap (filling orders, and paying takerFees).
|
||||
* makerTokenAmount: The amount of makerAsset that will be acquired through the swap.
|
||||
* protocolFeeInWeiAmount: The amount of ETH to pay (in WEI) as protocol fee to perform the swap for desired asset.
|
||||
* gas: Amount of estimated gas needed to fill the quote.
|
||||
* slippage: Amount of slippage to allow for.
|
||||
*/
|
||||
export interface SwapQuoteInfo {
|
||||
feeTakerTokenAmount: BigNumber;
|
||||
takerAmount: BigNumber;
|
||||
totalTakerAmount: BigNumber;
|
||||
makerAmount: BigNumber;
|
||||
protocolFeeInWeiAmount: BigNumber;
|
||||
gas: number;
|
||||
slippage: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* percentage breakdown of each liquidity source used in quote
|
||||
*/
|
||||
export type SwapQuoteOrdersBreakdown = Partial<
|
||||
{ [key in Exclude<ERC20BridgeSource, typeof ERC20BridgeSource.MultiHop>]: BigNumber } & {
|
||||
[ERC20BridgeSource.MultiHop]: {
|
||||
proportion: BigNumber;
|
||||
intermediateToken: string;
|
||||
hops: ERC20BridgeSource[];
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* nativeExclusivelyRFQ: if set to `true`, Swap quote will exclude Open Orderbook liquidity.
|
||||
* If set to `true` and `ERC20BridgeSource.Native` is part of the `excludedSources`
|
||||
* array in `SwapQuoteRequestOpts`, an Error will be raised.
|
||||
*/
|
||||
|
||||
export interface RfqmRequestOptions extends RfqRequestOpts {
|
||||
isLastLook: true;
|
||||
fee: Fee;
|
||||
}
|
||||
|
||||
export interface RfqRequestOpts {
|
||||
takerAddress: string;
|
||||
txOrigin: string;
|
||||
integrator: Integrator;
|
||||
intentOnFilling: boolean;
|
||||
isIndicative?: boolean;
|
||||
makerEndpointMaxResponseTimeMs?: number;
|
||||
nativeExclusivelyRFQ?: boolean;
|
||||
altRfqAssetOfferings?: AltRfqMakerAssetOfferings;
|
||||
isLastLook?: boolean;
|
||||
fee?: Fee;
|
||||
}
|
||||
|
||||
/**
|
||||
* gasPrice: gas price to determine protocolFee amount, default to ethGasStation fast amount
|
||||
*/
|
||||
export interface SwapQuoteRequestOpts extends Omit<GetMarketOrdersOpts, 'gasPrice'> {
|
||||
gasPrice?: BigNumber;
|
||||
rfqt?: RfqRequestOpts;
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping from RFQ-T/M quote provider URLs to the trading pairs they support.
|
||||
* The value type represents an array of supported asset pairs, with each array element encoded as a 2-element array of token addresses.
|
||||
*/
|
||||
export interface RfqMakerAssetOfferings {
|
||||
[endpoint: string]: Array<[string, string]>;
|
||||
}
|
||||
export interface AltOffering {
|
||||
id: string;
|
||||
baseAsset: string;
|
||||
quoteAsset: string;
|
||||
baseAssetDecimals: number;
|
||||
quoteAssetDecimals: number;
|
||||
}
|
||||
export interface AltRfqMakerAssetOfferings {
|
||||
[endpoint: string]: AltOffering[];
|
||||
}
|
||||
export enum RfqPairType {
|
||||
Standard = 'standard',
|
||||
Alt = 'alt',
|
||||
}
|
||||
export interface TypedMakerUrl {
|
||||
url: string;
|
||||
pairType: RfqPairType;
|
||||
}
|
||||
|
||||
export type LogFunction = (obj: object, msg?: string, ...args: any[]) => void;
|
||||
|
||||
export interface RfqFirmQuoteValidator {
|
||||
getRfqtTakerFillableAmountsAsync(quotes: RfqOrder[]): Promise<BigNumber[]>;
|
||||
}
|
||||
|
||||
export interface Integrator {
|
||||
integratorId: string;
|
||||
label: string;
|
||||
whitelistIntegratorUrls?: string[];
|
||||
}
|
||||
|
||||
export interface SwapQuoterRfqOpts {
|
||||
integratorsWhitelist: Integrator[];
|
||||
makerAssetOfferings: RfqMakerAssetOfferings;
|
||||
txOriginBlacklist: Set<string>;
|
||||
altRfqCreds?: {
|
||||
altRfqApiKey: string;
|
||||
altRfqProfile: string;
|
||||
};
|
||||
warningLogger?: LogFunction;
|
||||
infoLogger?: LogFunction;
|
||||
metricsProxy?: MetricsProxy;
|
||||
axiosInstanceOpts?: AxiosRequestConfig;
|
||||
}
|
||||
|
||||
export type AssetSwapperContractAddresses = ContractAddresses;
|
||||
|
||||
/**
|
||||
* chainId: The ethereum chain id. Defaults to 1 (mainnet).
|
||||
* orderRefreshIntervalMs: The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
|
||||
* expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
|
||||
* contractAddresses: Optionally override the contract addresses used for the chain
|
||||
* samplerGasLimit: The gas limit used when querying the sampler contract. Defaults to 36e6
|
||||
*/
|
||||
export interface SwapQuoterOpts extends OrderPrunerOpts {
|
||||
chainId: ChainId;
|
||||
orderRefreshIntervalMs: number;
|
||||
expiryBufferMs: number;
|
||||
ethereumRpcUrl?: string;
|
||||
contractAddresses?: AssetSwapperContractAddresses;
|
||||
samplerGasLimit?: number;
|
||||
multiBridgeAddress?: string;
|
||||
zeroExGasApiUrl?: string;
|
||||
rfqt?: SwapQuoterRfqOpts;
|
||||
samplerOverrides?: SamplerOverrides;
|
||||
tokenAdjacencyGraph?: TokenAdjacencyGraph;
|
||||
liquidityProviderRegistry?: LiquidityProviderRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible error messages thrown by an SwapQuoterConsumer instance or associated static methods.
|
||||
*/
|
||||
export enum SwapQuoteConsumerError {
|
||||
InvalidMarketSellOrMarketBuySwapQuote = 'INVALID_MARKET_BUY_SELL_SWAP_QUOTE',
|
||||
InvalidForwarderSwapQuote = 'INVALID_FORWARDER_SWAP_QUOTE_PROVIDED',
|
||||
NoAddressAvailable = 'NO_ADDRESS_AVAILABLE',
|
||||
SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED',
|
||||
TransactionValueTooLow = 'TRANSACTION_VALUE_TOO_LOW',
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible error messages thrown by an SwapQuoter instance or associated static methods.
|
||||
*/
|
||||
export enum SwapQuoterError {
|
||||
NoEtherTokenContractFound = 'NO_ETHER_TOKEN_CONTRACT_FOUND',
|
||||
StandardRelayerApiError = 'STANDARD_RELAYER_API_ERROR',
|
||||
InsufficientAssetLiquidity = 'INSUFFICIENT_ASSET_LIQUIDITY',
|
||||
AssetUnavailable = 'ASSET_UNAVAILABLE',
|
||||
NoGasPriceProvidedOrEstimated = 'NO_GAS_PRICE_PROVIDED_OR_ESTIMATED',
|
||||
AssetDataUnsupported = 'ASSET_DATA_UNSUPPORTED',
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents two main market operations supported by asset-swapper.
|
||||
*/
|
||||
export enum MarketOperation {
|
||||
Sell = 'Sell',
|
||||
Buy = 'Buy',
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents varying order takerFee types that can be pruned for by OrderPruner.
|
||||
*/
|
||||
export enum OrderPrunerPermittedFeeTypes {
|
||||
NoFees = 'NO_FEES',
|
||||
TakerDenominatedTakerFee = 'TAKER_DENOMINATED_TAKER_FEE',
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a mocked RFQ-T/M maker responses.
|
||||
*/
|
||||
export interface MockedRfqQuoteResponse {
|
||||
endpoint: string;
|
||||
requestApiKey: string;
|
||||
requestParams: TakerRequestQueryParamsUnnested;
|
||||
responseData: any;
|
||||
responseCode: number;
|
||||
callback?: (config: any) => Promise<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a mocked RFQ-T/M alternative maker responses.
|
||||
*/
|
||||
export interface AltMockedRfqQuoteResponse {
|
||||
endpoint: string;
|
||||
mmApiKey: string;
|
||||
requestData: AltQuoteRequestData;
|
||||
responseData: any;
|
||||
responseCode: number;
|
||||
}
|
||||
|
||||
export interface SamplerOverrides {
|
||||
overrides: GethCallOverrides;
|
||||
block: BlockParam;
|
||||
to?: string;
|
||||
}
|
||||
|
||||
export interface SamplerCallResult {
|
||||
success: boolean;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
export enum AltQuoteModel {
|
||||
Firm = 'firm',
|
||||
Indicative = 'indicative',
|
||||
}
|
||||
|
||||
export enum AltQuoteSide {
|
||||
Buy = 'buy',
|
||||
Sell = 'sell',
|
||||
}
|
||||
|
||||
export interface AltQuoteRequestData {
|
||||
market: string;
|
||||
model: AltQuoteModel;
|
||||
profile: string;
|
||||
side: AltQuoteSide;
|
||||
value?: string;
|
||||
amount?: string;
|
||||
meta: {
|
||||
txOrigin: string;
|
||||
taker: string;
|
||||
client: string;
|
||||
existingOrder?: {
|
||||
price: string;
|
||||
value?: string;
|
||||
amount?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface AltBaseRfqResponse extends AltQuoteRequestData {
|
||||
id: string;
|
||||
price?: string;
|
||||
}
|
||||
|
||||
export interface AltIndicativeQuoteResponse extends AltBaseRfqResponse {
|
||||
model: AltQuoteModel.Indicative;
|
||||
status: 'live' | 'rejected';
|
||||
}
|
||||
|
||||
export interface AltFirmQuoteResponse extends AltBaseRfqResponse {
|
||||
model: AltQuoteModel.Firm;
|
||||
data: {
|
||||
'0xv4order': V4SignedRfqOrder;
|
||||
};
|
||||
status: 'active' | 'rejected';
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { SwapQuoteInfo } from '../types';
|
||||
|
||||
import { assert } from './assert';
|
||||
|
||||
export const affiliateFeeUtils = {
|
||||
/**
|
||||
* Get the amount of eth to send for a forwarder contract call (includes takerAssetAmount, protocol fees, and specified affiliate fee amount)
|
||||
* @param swapQuoteInfo SwapQuoteInfo to generate total eth amount from
|
||||
* @param feePercentage Percentage of additive fees to apply to totalTakerAssetAmount + protocol fee.
|
||||
*/
|
||||
getTotalEthAmountWithAffiliateFee(swapQuoteInfo: SwapQuoteInfo, feePercentage: number): BigNumber {
|
||||
const ethAmount = swapQuoteInfo.protocolFeeInWeiAmount.plus(swapQuoteInfo.totalTakerAmount);
|
||||
const ethAmountWithFees = ethAmount.plus(affiliateFeeUtils.getFeeAmount(swapQuoteInfo, feePercentage));
|
||||
return ethAmountWithFees;
|
||||
},
|
||||
/**
|
||||
* Get the affiliate fee owed to the forwarder fee recipient.
|
||||
* @param swapQuoteInfo SwapQuoteInfo to generate total eth amount from
|
||||
* @param feePercentage Percentage of additive fees to apply to totalTakerAssetAmount + protocol fee.
|
||||
*/
|
||||
getFeeAmount(swapQuoteInfo: SwapQuoteInfo, feePercentage: number): BigNumber {
|
||||
assert.assert(feePercentage >= 0, 'feePercentage must be >= 0');
|
||||
const ethAmount = swapQuoteInfo.protocolFeeInWeiAmount.plus(swapQuoteInfo.totalTakerAmount);
|
||||
// HACK(dekz): This is actually in WEI amount not ETH
|
||||
return ethAmount.times(feePercentage).integerValue(BigNumber.ROUND_UP);
|
||||
},
|
||||
};
|
||||
@@ -1,279 +0,0 @@
|
||||
import { Web3Wrapper } from '@0x/dev-utils';
|
||||
import { TakerRequestQueryParamsUnnested, V4RFQFirmQuote, V4RFQIndicativeQuote } from '@0x/quote-server';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { AxiosInstance, CancelToken } from 'axios';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import {
|
||||
AltFirmQuoteResponse,
|
||||
AltIndicativeQuoteResponse,
|
||||
AltOffering,
|
||||
AltQuoteModel,
|
||||
AltQuoteRequestData,
|
||||
AltQuoteSide,
|
||||
AltRfqMakerAssetOfferings,
|
||||
LogFunction,
|
||||
} from '../types';
|
||||
|
||||
const SUCCESS_CODE = 201;
|
||||
|
||||
/**
|
||||
* Returns the AltOffering if it exists for a given pair
|
||||
*/
|
||||
export function getAltMarketInfo(
|
||||
offerings: AltOffering[],
|
||||
buyTokenAddress: string,
|
||||
sellTokenAddress: string,
|
||||
): AltOffering | undefined {
|
||||
for (const offering of offerings) {
|
||||
if (
|
||||
(buyTokenAddress.toLowerCase() === offering.baseAsset.toLowerCase() &&
|
||||
sellTokenAddress.toLowerCase() === offering.quoteAsset.toLowerCase()) ||
|
||||
(sellTokenAddress.toLowerCase() === offering.baseAsset.toLowerCase() &&
|
||||
buyTokenAddress.toLowerCase() === offering.quoteAsset.toLowerCase())
|
||||
) {
|
||||
return offering;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseFirmQuoteResponseFromAltMM(altFirmQuoteReponse: AltFirmQuoteResponse): V4RFQFirmQuote {
|
||||
return {
|
||||
signedOrder: altFirmQuoteReponse.data['0xv4order'],
|
||||
};
|
||||
}
|
||||
|
||||
function parseIndicativeQuoteResponseFromAltMM(
|
||||
altIndicativeQuoteResponse: AltIndicativeQuoteResponse,
|
||||
altPair: AltOffering,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
): V4RFQIndicativeQuote {
|
||||
let makerAmount: BigNumber;
|
||||
let takerAmount: BigNumber;
|
||||
let quoteAmount: BigNumber;
|
||||
let baseAmount: BigNumber;
|
||||
|
||||
if (!altIndicativeQuoteResponse.price) {
|
||||
throw new Error('Price not returned by alt MM');
|
||||
}
|
||||
if (altIndicativeQuoteResponse.amount) {
|
||||
// if amount is specified, amount is the base token amount
|
||||
baseAmount = Web3Wrapper.toBaseUnitAmount(
|
||||
new BigNumber(altIndicativeQuoteResponse.amount),
|
||||
altPair.baseAssetDecimals,
|
||||
);
|
||||
// if amount is specified, use the price (quote/base) to get the quote amount
|
||||
quoteAmount = Web3Wrapper.toBaseUnitAmount(
|
||||
new BigNumber(altIndicativeQuoteResponse.amount)
|
||||
.times(new BigNumber(altIndicativeQuoteResponse.price))
|
||||
.decimalPlaces(altPair.quoteAssetDecimals, BigNumber.ROUND_DOWN),
|
||||
altPair.quoteAssetDecimals,
|
||||
);
|
||||
} else if (altIndicativeQuoteResponse.value) {
|
||||
// if value is specified, value is the quote token amount
|
||||
quoteAmount = Web3Wrapper.toBaseUnitAmount(
|
||||
new BigNumber(altIndicativeQuoteResponse.value),
|
||||
altPair.quoteAssetDecimals,
|
||||
);
|
||||
// if value is specified, use the price (quote/base) to get the base amount
|
||||
baseAmount = Web3Wrapper.toBaseUnitAmount(
|
||||
new BigNumber(altIndicativeQuoteResponse.value)
|
||||
.dividedBy(new BigNumber(altIndicativeQuoteResponse.price))
|
||||
.decimalPlaces(altPair.baseAssetDecimals, BigNumber.ROUND_DOWN),
|
||||
altPair.baseAssetDecimals,
|
||||
);
|
||||
} else {
|
||||
throw new Error('neither amount or value were specified');
|
||||
}
|
||||
if (makerToken.toLowerCase() === altPair.baseAsset.toLowerCase()) {
|
||||
makerAmount = baseAmount;
|
||||
takerAmount = quoteAmount;
|
||||
} else if (makerToken.toLowerCase() === altPair.quoteAsset.toLowerCase()) {
|
||||
makerAmount = quoteAmount;
|
||||
takerAmount = baseAmount;
|
||||
} else {
|
||||
throw new Error(`Base, quote tokens don't align with maker, taker tokens`);
|
||||
}
|
||||
|
||||
return {
|
||||
makerToken,
|
||||
makerAmount,
|
||||
takerToken,
|
||||
takerAmount,
|
||||
// HACK: alt implementation does not return an expiration with indicative quotes
|
||||
// return now + { IMPUTED EXPIRY SECONDS } to have it included after order checks
|
||||
expiry: new BigNumber(Date.now() / 1000)
|
||||
.integerValue(BigNumber.ROUND_DOWN)
|
||||
.plus(constants.ALT_MM_IMPUTED_INDICATIVE_EXPIRY_SECONDS),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a standard quote request into an alt quote request
|
||||
* and return the appropriate standard quote response
|
||||
*/
|
||||
export async function returnQuoteFromAltMMAsync<ResponseT>(
|
||||
url: string,
|
||||
apiKey: string,
|
||||
profile: string,
|
||||
integratorKey: string,
|
||||
quoteModel: AltQuoteModel,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
maxResponseTimeMs: number,
|
||||
altRfqAssetOfferings: AltRfqMakerAssetOfferings,
|
||||
takerRequestQueryParams: TakerRequestQueryParamsUnnested,
|
||||
axiosInstance: AxiosInstance,
|
||||
warningLogger: LogFunction,
|
||||
cancelToken: CancelToken,
|
||||
): Promise<{ data: ResponseT; status: number }> {
|
||||
const altPair = getAltMarketInfo(
|
||||
altRfqAssetOfferings[url],
|
||||
takerRequestQueryParams.buyTokenAddress,
|
||||
takerRequestQueryParams.sellTokenAddress,
|
||||
);
|
||||
|
||||
if (!altPair) {
|
||||
throw new Error(`Alt pair not found`);
|
||||
}
|
||||
const side = altPair.baseAsset === takerRequestQueryParams.buyTokenAddress ? AltQuoteSide.Sell : AltQuoteSide.Buy;
|
||||
|
||||
// comparison price needs to be quote/base
|
||||
// in the standard implementation, it's maker/taker
|
||||
let altComparisonPrice: string | undefined;
|
||||
if (altPair.quoteAsset === makerToken) {
|
||||
altComparisonPrice = takerRequestQueryParams.comparisonPrice
|
||||
? takerRequestQueryParams.comparisonPrice
|
||||
: undefined;
|
||||
} else {
|
||||
altComparisonPrice = takerRequestQueryParams.comparisonPrice
|
||||
? new BigNumber(takerRequestQueryParams.comparisonPrice).pow(-1).toString()
|
||||
: undefined;
|
||||
}
|
||||
|
||||
let data: AltQuoteRequestData;
|
||||
data = {
|
||||
market: `${altPair.id}`,
|
||||
model: quoteModel,
|
||||
profile,
|
||||
side,
|
||||
meta: {
|
||||
txOrigin: takerRequestQueryParams.txOrigin!,
|
||||
taker: takerRequestQueryParams.takerAddress,
|
||||
client: integratorKey,
|
||||
},
|
||||
};
|
||||
|
||||
// specify a comparison price if it exists
|
||||
if (altComparisonPrice) {
|
||||
data.meta.existingOrder = {
|
||||
price: altComparisonPrice,
|
||||
};
|
||||
}
|
||||
|
||||
// need to specify amount or value
|
||||
// amount is units of the base asset
|
||||
// value is units of the quote asset
|
||||
let requestSize: string;
|
||||
if (takerRequestQueryParams.buyAmountBaseUnits) {
|
||||
requestSize = Web3Wrapper.toUnitAmount(
|
||||
new BigNumber(takerRequestQueryParams.buyAmountBaseUnits),
|
||||
takerRequestQueryParams.buyTokenAddress === altPair.baseAsset
|
||||
? altPair.baseAssetDecimals
|
||||
: altPair.quoteAssetDecimals,
|
||||
).toString();
|
||||
if (takerRequestQueryParams.buyTokenAddress === altPair.baseAsset) {
|
||||
data.amount = requestSize;
|
||||
// add to 'existing order' if there is a comparison price
|
||||
if (data.meta.existingOrder) {
|
||||
data.meta.existingOrder.amount = requestSize;
|
||||
}
|
||||
} else {
|
||||
data.value = requestSize;
|
||||
// add to 'existing order' if there is a comparison price
|
||||
if (data.meta.existingOrder) {
|
||||
data.meta.existingOrder.value = requestSize;
|
||||
}
|
||||
}
|
||||
} else if (takerRequestQueryParams.sellAmountBaseUnits) {
|
||||
requestSize = Web3Wrapper.toUnitAmount(
|
||||
new BigNumber(takerRequestQueryParams.sellAmountBaseUnits),
|
||||
takerRequestQueryParams.sellTokenAddress === altPair.baseAsset
|
||||
? altPair.baseAssetDecimals
|
||||
: altPair.quoteAssetDecimals,
|
||||
).toString();
|
||||
if (takerRequestQueryParams.sellTokenAddress === altPair.baseAsset) {
|
||||
data.amount = requestSize;
|
||||
if (data.meta.existingOrder) {
|
||||
data.meta.existingOrder.amount = requestSize;
|
||||
}
|
||||
} else {
|
||||
data.value = requestSize;
|
||||
if (data.meta.existingOrder) {
|
||||
data.meta.existingOrder.value = requestSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const response = await axiosInstance
|
||||
.post(`${url}/quotes`, data, {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
timeout: maxResponseTimeMs,
|
||||
cancelToken,
|
||||
})
|
||||
.catch(err => {
|
||||
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`);
|
||||
});
|
||||
|
||||
// empty response will get filtered out in validation
|
||||
const emptyResponse = {};
|
||||
|
||||
if (response.status !== SUCCESS_CODE) {
|
||||
const rejectedRequestInfo = {
|
||||
status: response.status,
|
||||
message: response.data,
|
||||
};
|
||||
warningLogger(rejectedRequestInfo, `Alt RFQ MM did not return a status of ${SUCCESS_CODE}`);
|
||||
return {
|
||||
data: (emptyResponse as unknown) as ResponseT,
|
||||
status: response.status,
|
||||
};
|
||||
}
|
||||
// successful handling but no quote is indicated by status = 'rejected'
|
||||
if (response.data.status === 'rejected') {
|
||||
warningLogger(
|
||||
response.data.id,
|
||||
`Alt RFQ MM handled the request successfully but did not return a quote (status = 'rejected')`,
|
||||
);
|
||||
return {
|
||||
data: (emptyResponse as unknown) as ResponseT,
|
||||
// hack: set the http status to 204 no content so we can more
|
||||
// easily track when no quote is returned
|
||||
status: 204,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedResponse =
|
||||
quoteModel === 'firm'
|
||||
? parseFirmQuoteResponseFromAltMM(response.data)
|
||||
: parseIndicativeQuoteResponseFromAltMM(response.data, altPair, makerToken, takerToken);
|
||||
|
||||
return {
|
||||
// hack to appease type checking
|
||||
data: (parsedResponse as unknown) as ResponseT,
|
||||
status: response.status,
|
||||
};
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { assert as sharedAssert } from '@0x/assert';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { Orderbook } from '../swap_quoter';
|
||||
|
||||
export const assert = {
|
||||
...sharedAssert,
|
||||
isValidOrderbook(variableName: string, orderFetcher: Orderbook): void {
|
||||
sharedAssert.isFunction(`${variableName}.getOrdersAsync`, orderFetcher.getOrdersAsync.bind(orderFetcher));
|
||||
sharedAssert.isFunction(
|
||||
`${variableName}.getBatchOrdersAsync`,
|
||||
orderFetcher.getBatchOrdersAsync.bind(orderFetcher),
|
||||
);
|
||||
},
|
||||
isValidPercentage(variableName: string, percentage: number): void {
|
||||
assert.isNumber(variableName, percentage);
|
||||
assert.assert(
|
||||
percentage >= 0 && percentage <= 1,
|
||||
`Expected ${variableName} to be between 0 and 1, but is ${percentage}`,
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
import { RfqOrder, Signature } from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { AltRfqMakerAssetOfferings } from '../types';
|
||||
|
||||
export interface RfqClientV1PriceRequest {
|
||||
altRfqAssetOfferings: AltRfqMakerAssetOfferings | undefined;
|
||||
assetFillAmount: BigNumber;
|
||||
chainId: number;
|
||||
comparisonPrice: BigNumber | undefined;
|
||||
integratorId: string;
|
||||
intentOnFilling: boolean;
|
||||
makerToken: string;
|
||||
marketOperation: 'Sell' | 'Buy';
|
||||
takerAddress: string;
|
||||
takerToken: string;
|
||||
txOrigin: string;
|
||||
}
|
||||
|
||||
export interface RfqClientV1QuoteRequest extends RfqClientV1PriceRequest {}
|
||||
|
||||
export interface RfqClientV1Price {
|
||||
expiry: BigNumber;
|
||||
kind: 'rfq' | 'otc';
|
||||
makerAmount: BigNumber;
|
||||
makerToken: string;
|
||||
makerUri: string;
|
||||
takerAmount: BigNumber;
|
||||
takerToken: string;
|
||||
}
|
||||
|
||||
export interface RfqClientV1PriceResponse {
|
||||
prices: RfqClientV1Price[];
|
||||
}
|
||||
|
||||
export interface RfqClientV1Quote {
|
||||
makerUri: string;
|
||||
order: RfqOrder;
|
||||
signature: Signature;
|
||||
}
|
||||
|
||||
export interface RfqClientV1QuoteResponse {
|
||||
quotes: RfqClientV1Quote[];
|
||||
}
|
||||
|
||||
/**
|
||||
* IRfqClient is an interface that defines how to connect with an Rfq system.
|
||||
*/
|
||||
export interface IRfqClient {
|
||||
/**
|
||||
* Fetches a list of "indicative quotes" or prices from a remote Rfq server
|
||||
*/
|
||||
getV1PricesAsync(request: RfqClientV1PriceRequest): Promise<RfqClientV1PriceResponse>;
|
||||
|
||||
/**
|
||||
* Fetches a list of "firm quotes" or signed quotes from a remote Rfq server.
|
||||
*/
|
||||
getV1QuotesAsync(request: RfqClientV1QuoteRequest): Promise<RfqClientV1QuoteResponse>;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import { logUtils } from '@0x/utils';
|
||||
import { gql, request } from 'graphql-request';
|
||||
|
||||
import { constants } from '../../constants';
|
||||
|
||||
const RESERVES_GQL_QUERY = gql`
|
||||
{
|
||||
reserves(
|
||||
first: 300
|
||||
where: { isActive: true, isFrozen: false }
|
||||
orderBy: totalLiquidity
|
||||
orderDirection: desc
|
||||
) {
|
||||
id
|
||||
underlyingAsset
|
||||
aToken {
|
||||
id
|
||||
}
|
||||
pool {
|
||||
id
|
||||
lendingPool
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface AaveReserve {
|
||||
id: string;
|
||||
underlyingAsset: string;
|
||||
aToken: {
|
||||
id: string;
|
||||
};
|
||||
pool: {
|
||||
id: string;
|
||||
lendingPool: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Cache {
|
||||
[key: string]: AaveReserve[];
|
||||
}
|
||||
|
||||
const RESERVES_REFRESH_INTERVAL_MS = 30 * constants.ONE_MINUTE_MS;
|
||||
|
||||
/**
|
||||
* Fetches Aave V2 reserve information from the official subgraph(s).
|
||||
* The reserve information is updated every 30 minutes and cached
|
||||
* so that it can be accessed with the underlying token's address
|
||||
*/
|
||||
export class AaveV2ReservesCache {
|
||||
private _cache: Cache = {};
|
||||
constructor(private readonly _subgraphUrl: string) {
|
||||
const resfreshReserves = async () => this.fetchAndUpdateReservesAsync();
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
resfreshReserves();
|
||||
setInterval(resfreshReserves, RESERVES_REFRESH_INTERVAL_MS);
|
||||
}
|
||||
/**
|
||||
* Fetches Aave V2 reserves from the subgraph and updates the cache
|
||||
*/
|
||||
public async fetchAndUpdateReservesAsync(): Promise<void> {
|
||||
try {
|
||||
const { reserves } = await request<{ reserves: AaveReserve[] }>(this._subgraphUrl, RESERVES_GQL_QUERY);
|
||||
const newCache = reserves.reduce<Cache>((memo, reserve) => {
|
||||
const underlyingAsset = reserve.underlyingAsset.toLowerCase();
|
||||
if (!memo[underlyingAsset]) {
|
||||
memo[underlyingAsset] = [];
|
||||
}
|
||||
|
||||
memo[underlyingAsset].push(reserve);
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
this._cache = newCache;
|
||||
} catch (err) {
|
||||
logUtils.warn(`Failed to update Aave V2 reserves cache: ${err.message}`);
|
||||
// Empty cache just to be safe
|
||||
this._cache = {};
|
||||
}
|
||||
}
|
||||
public get(takerToken: string, makerToken: string): AaveReserve | undefined {
|
||||
// Deposit takerToken into reserve
|
||||
if (this._cache[takerToken.toLowerCase()]) {
|
||||
const matchingReserve = this._cache[takerToken.toLowerCase()].find(
|
||||
r => r.aToken.id === makerToken.toLowerCase(),
|
||||
);
|
||||
if (matchingReserve) {
|
||||
return matchingReserve;
|
||||
}
|
||||
}
|
||||
|
||||
// Withdraw makerToken from reserve
|
||||
if (this._cache[makerToken.toLowerCase()]) {
|
||||
const matchingReserve = this._cache[makerToken.toLowerCase()].find(
|
||||
r => r.aToken.id === takerToken.toLowerCase(),
|
||||
);
|
||||
if (matchingReserve) {
|
||||
return matchingReserve;
|
||||
}
|
||||
}
|
||||
|
||||
// No match
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { SupportedProvider } from '@0x/dev-utils';
|
||||
import { SDK } from '@bancor/sdk';
|
||||
import { Ethereum } from '@bancor/sdk/dist/blockchains/ethereum';
|
||||
import { BlockchainType } from '@bancor/sdk/dist/types';
|
||||
|
||||
import { MAINNET_TOKENS } from './constants';
|
||||
|
||||
const findToken = (tokenAddress: string, graph: object): string =>
|
||||
// If we're looking for WETH it is stored by Bancor as the 0xeee address
|
||||
tokenAddress.toLowerCase() === MAINNET_TOKENS.WETH.toLowerCase()
|
||||
? '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
|
||||
: Object.keys(graph).filter(k => k.toLowerCase() === tokenAddress.toLowerCase())[0];
|
||||
|
||||
export class BancorService {
|
||||
public static async createAsync(provider: SupportedProvider): Promise<BancorService> {
|
||||
const sdk = await SDK.create({ ethereumNodeEndpoint: provider });
|
||||
const service = new BancorService(sdk);
|
||||
return service;
|
||||
}
|
||||
|
||||
constructor(public sdk: SDK) {}
|
||||
public getPaths(_fromToken: string, _toToken: string): string[][] {
|
||||
// HACK: We reach into the blockchain object and pull in it's cache of tokens
|
||||
// and we use it's internal non-async getPathsFunc
|
||||
try {
|
||||
const blockchain = this.sdk._core.blockchains[BlockchainType.Ethereum] as Ethereum;
|
||||
const fromToken = findToken(_fromToken, blockchain.graph);
|
||||
const toToken = findToken(_toToken, blockchain.graph);
|
||||
return blockchain.getPathsFunc.bind(blockchain)(fromToken, toToken);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,574 +0,0 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import {
|
||||
ACRYPTOS_BSC_INFOS,
|
||||
APESWAP_ROUTER_BY_CHAIN_ID,
|
||||
BAKERYSWAP_ROUTER_BY_CHAIN_ID,
|
||||
BELT_BSC_INFOS,
|
||||
BISWAP_ROUTER_BY_CHAIN_ID,
|
||||
CHEESESWAP_ROUTER_BY_CHAIN_ID,
|
||||
COMPONENT_POOLS_BY_CHAIN_ID,
|
||||
CRYPTO_COM_ROUTER_BY_CHAIN_ID,
|
||||
CURVE_AVALANCHE_INFOS,
|
||||
CURVE_FANTOM_INFOS,
|
||||
CURVE_MAINNET_INFOS,
|
||||
CURVE_OPTIMISM_INFOS,
|
||||
CURVE_POLYGON_INFOS,
|
||||
CURVE_V2_AVALANCHE_INFOS,
|
||||
CURVE_V2_FANTOM_INFOS,
|
||||
CURVE_V2_MAINNET_INFOS,
|
||||
CURVE_V2_POLYGON_INFOS,
|
||||
DFYN_ROUTER_BY_CHAIN_ID,
|
||||
ELLIPSIS_BSC_INFOS,
|
||||
FIREBIRDONESWAP_BSC_INFOS,
|
||||
FIREBIRDONESWAP_POLYGON_INFOS,
|
||||
IRONSWAP_POLYGON_INFOS,
|
||||
KNIGHTSWAP_ROUTER_BY_CHAIN_ID,
|
||||
MAX_DODOV2_POOLS_QUERIED,
|
||||
MDEX_ROUTER_BY_CHAIN_ID,
|
||||
MESHSWAP_ROUTER_BY_CHAIN_ID,
|
||||
MOBIUSMONEY_CELO_INFOS,
|
||||
MORPHEUSSWAP_ROUTER_BY_CHAIN_ID,
|
||||
MSTABLE_POOLS_BY_CHAIN_ID,
|
||||
NERVE_BSC_INFOS,
|
||||
NULL_ADDRESS,
|
||||
PANCAKESWAPV2_ROUTER_BY_CHAIN_ID,
|
||||
PANCAKESWAP_ROUTER_BY_CHAIN_ID,
|
||||
PANGOLIN_ROUTER_BY_CHAIN_ID,
|
||||
PLATYPUS_AVALANCHE_INFOS,
|
||||
QUICKSWAP_ROUTER_BY_CHAIN_ID,
|
||||
SADDLE_MAINNET_INFOS,
|
||||
SHELL_POOLS_BY_CHAIN_ID,
|
||||
SHIBASWAP_ROUTER_BY_CHAIN_ID,
|
||||
SPIRITSWAP_ROUTER_BY_CHAIN_ID,
|
||||
SPOOKYSWAP_ROUTER_BY_CHAIN_ID,
|
||||
SUSHISWAP_ROUTER_BY_CHAIN_ID,
|
||||
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,
|
||||
WAULTSWAP_ROUTER_BY_CHAIN_ID,
|
||||
XSIGMA_MAINNET_INFOS,
|
||||
YOSHI_ROUTER_BY_CHAIN_ID,
|
||||
} from './constants';
|
||||
import { CurveInfo, ERC20BridgeSource, PlatypusInfo } from './types';
|
||||
|
||||
// tslint:disable-next-line: completed-docs ban-types
|
||||
export function isValidAddress(address: string | String): address is string {
|
||||
return (typeof address === 'string' || address instanceof String) && address.toString() !== NULL_ADDRESS;
|
||||
}
|
||||
|
||||
// tslint:disable completed-docs
|
||||
export function getDodoV2Offsets(): BigNumber[] {
|
||||
return Array(MAX_DODOV2_POOLS_QUERIED)
|
||||
.fill(0)
|
||||
.map((_v, i) => new BigNumber(i));
|
||||
}
|
||||
|
||||
// tslint:disable completed-docs
|
||||
export function getShellsForPair(chainId: ChainId, takerToken: string, makerToken: string): string[] {
|
||||
if (chainId !== ChainId.Mainnet) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(SHELL_POOLS_BY_CHAIN_ID[chainId])
|
||||
.filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)))
|
||||
.map(i => i.poolAddress);
|
||||
}
|
||||
|
||||
// tslint:disable completed-docs
|
||||
export function getComponentForPair(chainId: ChainId, takerToken: string, makerToken: string): string[] {
|
||||
if (chainId !== ChainId.Mainnet) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(COMPONENT_POOLS_BY_CHAIN_ID[chainId])
|
||||
.filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)))
|
||||
.map(i => i.poolAddress);
|
||||
}
|
||||
|
||||
// tslint:disable completed-docs
|
||||
export function getMStableForPair(chainId: ChainId, takerToken: string, makerToken: string): string[] {
|
||||
if (chainId !== ChainId.Mainnet && chainId !== ChainId.Polygon) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(MSTABLE_POOLS_BY_CHAIN_ID[chainId])
|
||||
.filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)))
|
||||
.map(i => i.poolAddress);
|
||||
}
|
||||
|
||||
// tslint:disable completed-docs
|
||||
export function getCurveInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
switch (chainId) {
|
||||
case ChainId.Mainnet:
|
||||
return Object.values(CURVE_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.Polygon:
|
||||
return Object.values(CURVE_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(CURVE_FANTOM_INFOS).filter(c =>
|
||||
[makerToken, takerToken].every(
|
||||
t =>
|
||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
||||
(c.tokens.includes(t) &&
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
case ChainId.Avalanche:
|
||||
return Object.values(CURVE_AVALANCHE_INFOS).filter(c =>
|
||||
[makerToken, takerToken].every(
|
||||
t =>
|
||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
||||
(c.tokens.includes(t) &&
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
case ChainId.Optimism:
|
||||
return Object.values(CURVE_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),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable completed-docs
|
||||
export function getCurveV2InfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
switch (chainId) {
|
||||
case ChainId.Mainnet:
|
||||
return Object.values(CURVE_V2_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.Polygon:
|
||||
return Object.values(CURVE_V2_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(CURVE_V2_FANTOM_INFOS).filter(c =>
|
||||
[makerToken, takerToken].every(
|
||||
t =>
|
||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
||||
(c.tokens.includes(t) &&
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
case ChainId.Avalanche:
|
||||
return Object.values(CURVE_V2_AVALANCHE_INFOS).filter(c =>
|
||||
[makerToken, takerToken].every(
|
||||
t =>
|
||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
||||
(c.tokens.includes(t) &&
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function getNerveInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
if (chainId !== ChainId.BSC) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(NERVE_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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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 =>
|
||||
[makerToken, takerToken].every(
|
||||
t =>
|
||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
||||
(c.tokens.includes(t) &&
|
||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
||||
),
|
||||
);
|
||||
} else if (chainId === ChainId.Polygon) {
|
||||
return Object.values(FIREBIRDONESWAP_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),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function getBeltInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
if (chainId !== ChainId.BSC) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(BELT_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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getEllipsisInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
if (chainId !== ChainId.BSC) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(ELLIPSIS_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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getSaddleInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
if (chainId !== ChainId.Mainnet) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(SADDLE_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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getIronSwapInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
if (chainId !== ChainId.Polygon) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(IRONSWAP_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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getXSigmaInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
if (chainId !== ChainId.Mainnet) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(XSIGMA_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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getAcryptosInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
if (chainId !== ChainId.BSC) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(ACRYPTOS_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),
|
||||
),
|
||||
);
|
||||
}
|
||||
export function getMobiusMoneyInfoForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||
if (chainId !== ChainId.Celo) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(MOBIUSMONEY_CELO_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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getPlatypusInfoForPair(chainId: ChainId, takerToken: string, makerToken: string): PlatypusInfo[] {
|
||||
if (chainId !== ChainId.Avalanche) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(PLATYPUS_AVALANCHE_INFOS).filter(c =>
|
||||
[makerToken, takerToken].every(t => c.tokens.includes(t)),
|
||||
);
|
||||
}
|
||||
|
||||
export function getShellLikeInfosForPair(
|
||||
chainId: ChainId,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
source: ERC20BridgeSource.Shell | ERC20BridgeSource.Component | ERC20BridgeSource.MStable,
|
||||
): string[] {
|
||||
switch (source) {
|
||||
case ERC20BridgeSource.Shell:
|
||||
return getShellsForPair(chainId, takerToken, makerToken);
|
||||
case ERC20BridgeSource.Component:
|
||||
return getComponentForPair(chainId, takerToken, makerToken);
|
||||
case ERC20BridgeSource.MStable:
|
||||
return getMStableForPair(chainId, takerToken, makerToken);
|
||||
default:
|
||||
throw new Error(`Unknown Shell like source ${source}`);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CurveDetailedInfo extends CurveInfo {
|
||||
makerTokenIdx: number;
|
||||
takerTokenIdx: number;
|
||||
}
|
||||
|
||||
export function getCurveLikeInfosForPair(
|
||||
chainId: ChainId,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
source:
|
||||
| ERC20BridgeSource.Curve
|
||||
| ERC20BridgeSource.CurveV2
|
||||
| ERC20BridgeSource.Nerve
|
||||
| ERC20BridgeSource.Synapse
|
||||
| ERC20BridgeSource.Belt
|
||||
| ERC20BridgeSource.Ellipsis
|
||||
| ERC20BridgeSource.Saddle
|
||||
| ERC20BridgeSource.IronSwap
|
||||
| ERC20BridgeSource.XSigma
|
||||
| ERC20BridgeSource.FirebirdOneSwap
|
||||
| ERC20BridgeSource.ACryptos
|
||||
| ERC20BridgeSource.MobiusMoney,
|
||||
): CurveDetailedInfo[] {
|
||||
let pools: CurveInfo[] = [];
|
||||
switch (source) {
|
||||
case ERC20BridgeSource.Curve:
|
||||
pools = getCurveInfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
case ERC20BridgeSource.CurveV2:
|
||||
pools = getCurveV2InfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
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;
|
||||
case ERC20BridgeSource.Ellipsis:
|
||||
pools = getEllipsisInfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
case ERC20BridgeSource.Saddle:
|
||||
pools = getSaddleInfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
case ERC20BridgeSource.XSigma:
|
||||
pools = getXSigmaInfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
case ERC20BridgeSource.FirebirdOneSwap:
|
||||
pools = getFirebirdOneSwapInfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
case ERC20BridgeSource.IronSwap:
|
||||
pools = getIronSwapInfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
case ERC20BridgeSource.ACryptos:
|
||||
pools = getAcryptosInfosForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
case ERC20BridgeSource.MobiusMoney:
|
||||
pools = getMobiusMoneyInfoForPair(chainId, takerToken, makerToken);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown Curve like source ${source}`);
|
||||
}
|
||||
return pools.map(pool => ({
|
||||
...pool,
|
||||
makerTokenIdx: pool.tokens.indexOf(makerToken),
|
||||
takerTokenIdx: pool.tokens.indexOf(takerToken),
|
||||
}));
|
||||
}
|
||||
|
||||
export function uniswapV2LikeRouterAddress(
|
||||
chainId: ChainId,
|
||||
source:
|
||||
| ERC20BridgeSource.UniswapV2
|
||||
| ERC20BridgeSource.SushiSwap
|
||||
| ERC20BridgeSource.CryptoCom
|
||||
| ERC20BridgeSource.PancakeSwap
|
||||
| ERC20BridgeSource.PancakeSwapV2
|
||||
| ERC20BridgeSource.BakerySwap
|
||||
| ERC20BridgeSource.ApeSwap
|
||||
| ERC20BridgeSource.CheeseSwap
|
||||
| ERC20BridgeSource.QuickSwap
|
||||
| ERC20BridgeSource.Dfyn
|
||||
| ERC20BridgeSource.WaultSwap
|
||||
| ERC20BridgeSource.ShibaSwap
|
||||
| ERC20BridgeSource.TraderJoe
|
||||
| ERC20BridgeSource.Pangolin
|
||||
| ERC20BridgeSource.UbeSwap
|
||||
| ERC20BridgeSource.MorpheusSwap
|
||||
| ERC20BridgeSource.SpookySwap
|
||||
| ERC20BridgeSource.SpiritSwap
|
||||
| ERC20BridgeSource.BiSwap
|
||||
| ERC20BridgeSource.Yoshi
|
||||
| ERC20BridgeSource.MDex
|
||||
| ERC20BridgeSource.KnightSwap
|
||||
| ERC20BridgeSource.MeshSwap,
|
||||
): string {
|
||||
switch (source) {
|
||||
case ERC20BridgeSource.UniswapV2:
|
||||
return UNISWAPV2_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.SushiSwap:
|
||||
return SUSHISWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.CryptoCom:
|
||||
return CRYPTO_COM_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.PancakeSwap:
|
||||
return PANCAKESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.PancakeSwapV2:
|
||||
return PANCAKESWAPV2_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.BakerySwap:
|
||||
return BAKERYSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.ApeSwap:
|
||||
return APESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.CheeseSwap:
|
||||
return CHEESESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.QuickSwap:
|
||||
return QUICKSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.Dfyn:
|
||||
return DFYN_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.WaultSwap:
|
||||
return WAULTSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
return SHIBASWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
return PANGOLIN_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
return TRADER_JOE_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
return UBESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
return MORPHEUSSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.SpookySwap:
|
||||
return SPOOKYSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.SpiritSwap:
|
||||
return SPIRITSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.BiSwap:
|
||||
return BISWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.Yoshi:
|
||||
return YOSHI_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.MeshSwap:
|
||||
return MESHSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.MDex:
|
||||
return MDEX_ROUTER_BY_CHAIN_ID[chainId];
|
||||
case ERC20BridgeSource.KnightSwap:
|
||||
return KNIGHTSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||
default:
|
||||
throw new Error(`Unknown UniswapV2 like source ${source}`);
|
||||
}
|
||||
}
|
||||
|
||||
const BAD_TOKENS_BY_SOURCE: Partial<{ [key in ERC20BridgeSource]: string[] }> = {
|
||||
[ERC20BridgeSource.Uniswap]: [
|
||||
'0xb8c77482e45f1f44de1745f52c74426c631bdd52', // BNB
|
||||
],
|
||||
};
|
||||
|
||||
export function isBadTokenForSource(token: string, source: ERC20BridgeSource): boolean {
|
||||
return (BAD_TOKENS_BY_SOURCE[source] || []).includes(token.toLowerCase());
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { Web3Wrapper } from '@0x/dev-utils';
|
||||
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { MarketOperation } from '../../types';
|
||||
|
||||
import { COMPARISON_PRICE_DECIMALS, SOURCE_FLAGS } from './constants';
|
||||
import {
|
||||
ComparisonPrice,
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
FeeEstimate,
|
||||
FeeSchedule,
|
||||
MarketSideLiquidity,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Takes in an optimizer response and returns a price for RFQT MMs to beat
|
||||
* returns the price of the taker asset in terms of the maker asset
|
||||
* So the RFQT MM should aim for a higher price
|
||||
* @param adjustedRate the adjusted rate (accounting for fees) from the optimizer, maker/taker
|
||||
* @param amount the amount specified by the client
|
||||
* @param marketSideLiquidity the results from querying liquidity sources
|
||||
* @param feeSchedule the fee schedule passed to the Optimizer
|
||||
* @return ComparisonPrice object with the prices for RFQ MMs to beat
|
||||
*/
|
||||
export function getComparisonPrices(
|
||||
adjustedRate: BigNumber,
|
||||
amount: BigNumber,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
feeSchedule: FeeSchedule,
|
||||
exchangeProxyOverhead: ExchangeProxyOverhead,
|
||||
): ComparisonPrice {
|
||||
let wholeOrder: BigNumber | undefined;
|
||||
let feeInEth: BigNumber | number;
|
||||
|
||||
// HACK: get the fee penalty of a single 0x native order
|
||||
// The FeeSchedule function takes in a `FillData` object and returns a fee estimate in ETH
|
||||
// We don't have fill data here, we just want the cost of a single native order, so we pass in undefined
|
||||
// This works because the feeSchedule returns a constant for Native orders, this will need
|
||||
// to be tweaked if the feeSchedule for native orders uses the fillData passed in
|
||||
// 2 potential issues: there is no native fee schedule or the fee schedule depends on fill data
|
||||
if (feeSchedule[ERC20BridgeSource.Native] === undefined) {
|
||||
logUtils.warn('ComparisonPrice function did not find native order fee schedule');
|
||||
|
||||
return { wholeOrder };
|
||||
} else {
|
||||
try {
|
||||
const fillFeeInEth = new BigNumber(
|
||||
(feeSchedule[ERC20BridgeSource.Native] as FeeEstimate)({ type: FillQuoteTransformerOrderType.Rfq }).fee,
|
||||
);
|
||||
const exchangeProxyOverheadInEth = new BigNumber(exchangeProxyOverhead(SOURCE_FLAGS.RfqOrder));
|
||||
feeInEth = fillFeeInEth.plus(exchangeProxyOverheadInEth);
|
||||
} catch {
|
||||
logUtils.warn('Native order fee schedule requires fill data');
|
||||
|
||||
return { wholeOrder };
|
||||
}
|
||||
}
|
||||
|
||||
// Calc native order fee penalty in output unit (maker units for sells, taker unit for buys)
|
||||
const feePenalty = !marketSideLiquidity.outputAmountPerEth.isZero()
|
||||
? marketSideLiquidity.outputAmountPerEth.times(feeInEth)
|
||||
: // if it's a sell, the input token is the taker token
|
||||
marketSideLiquidity.inputAmountPerEth
|
||||
.times(feeInEth)
|
||||
.times(marketSideLiquidity.side === MarketOperation.Sell ? adjustedRate : adjustedRate.pow(-1));
|
||||
|
||||
// the adjusted rate is defined as maker/taker
|
||||
// input is the taker token for sells, input is the maker token for buys
|
||||
const orderMakerAmount =
|
||||
marketSideLiquidity.side === MarketOperation.Sell ? adjustedRate.times(amount).plus(feePenalty) : amount;
|
||||
const orderTakerAmount =
|
||||
marketSideLiquidity.side === MarketOperation.Sell ? amount : amount.dividedBy(adjustedRate).minus(feePenalty);
|
||||
|
||||
if (orderTakerAmount.gt(0) && orderMakerAmount.gt(0)) {
|
||||
const optimalMakerUnitAmount = Web3Wrapper.toUnitAmount(
|
||||
// round up maker amount -- err to giving more competitive price
|
||||
orderMakerAmount.integerValue(BigNumber.ROUND_UP),
|
||||
marketSideLiquidity.makerTokenDecimals,
|
||||
);
|
||||
const optimalTakerUnitAmount = Web3Wrapper.toUnitAmount(
|
||||
// round down taker amount -- err to giving more competitive price
|
||||
orderTakerAmount.integerValue(BigNumber.ROUND_DOWN),
|
||||
marketSideLiquidity.takerTokenDecimals,
|
||||
);
|
||||
wholeOrder = optimalMakerUnitAmount.div(optimalTakerUnitAmount).decimalPlaces(COMPARISON_PRICE_DECIMALS);
|
||||
}
|
||||
|
||||
return { wholeOrder };
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { logUtils } from '@0x/utils';
|
||||
import axios from 'axios';
|
||||
|
||||
import { constants } from '../../constants';
|
||||
|
||||
export interface CToken {
|
||||
tokenAddress: string;
|
||||
underlyingAddress: string;
|
||||
}
|
||||
|
||||
interface CTokenApiResponse {
|
||||
cToken: Array<{
|
||||
token_address: string;
|
||||
underlying_address: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface Cache {
|
||||
[key: string]: CToken;
|
||||
}
|
||||
|
||||
const CTOKEN_REFRESH_INTERVAL_MS = 30 * constants.ONE_MINUTE_MS;
|
||||
|
||||
/**
|
||||
* Fetches a list of CTokens from Compound's official API.
|
||||
* The token information is updated every 30 minutes and cached
|
||||
* so that it can be accessed with the underlying token's address.
|
||||
*/
|
||||
export class CompoundCTokenCache {
|
||||
private _cache: Cache = {};
|
||||
constructor(private readonly _apiUrl: string, private readonly _wethAddress: string) {
|
||||
const refreshCTokenCache = async () => this.fetchAndUpdateCTokensAsync();
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
refreshCTokenCache();
|
||||
setInterval(refreshCTokenCache, CTOKEN_REFRESH_INTERVAL_MS);
|
||||
}
|
||||
|
||||
public async fetchAndUpdateCTokensAsync(): Promise<void> {
|
||||
try {
|
||||
const { data } = await axios.get<CTokenApiResponse>(`${this._apiUrl}/ctoken`);
|
||||
const newCache = data?.cToken.reduce<Cache>((memo, cToken) => {
|
||||
// NOTE: Re-map cETH with null underlying token address to WETH address (we only handle WETH internally)
|
||||
const underlyingAddressClean = cToken.underlying_address
|
||||
? cToken.underlying_address.toLowerCase()
|
||||
: this._wethAddress;
|
||||
|
||||
const tokenData: CToken = {
|
||||
tokenAddress: cToken.token_address.toLowerCase(),
|
||||
underlyingAddress: underlyingAddressClean,
|
||||
};
|
||||
memo[underlyingAddressClean] = tokenData;
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
this._cache = newCache;
|
||||
} catch (err) {
|
||||
logUtils.warn(`Failed to update Compound cToken cache: ${err.message}`);
|
||||
// NOTE: Safe to keep already cached data as tokens should only be added to the list
|
||||
}
|
||||
}
|
||||
public get(takerToken: string, makerToken: string): CToken | undefined {
|
||||
// mint cToken
|
||||
let cToken = this._cache[takerToken.toLowerCase()];
|
||||
if (cToken && makerToken.toLowerCase() === cToken.tokenAddress.toLowerCase()) {
|
||||
return cToken;
|
||||
}
|
||||
|
||||
// redeem cToken
|
||||
cToken = this._cache[makerToken.toLowerCase()];
|
||||
if (cToken && takerToken.toLowerCase() === cToken.tokenAddress.toLowerCase()) {
|
||||
return cToken;
|
||||
}
|
||||
|
||||
// No match
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,133 +0,0 @@
|
||||
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||
|
||||
import { DEFAULT_FEE_ESTIMATE, POSITIVE_INF, SOURCE_FLAGS } from './constants';
|
||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||
|
||||
/**
|
||||
* Converts the ETH value to an amount in output tokens.
|
||||
*
|
||||
* By default this prefers the outputAmountPerEth, but if this value
|
||||
* is zero it will utilize the inputAmountPerEth and input.
|
||||
*/
|
||||
export function ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
ethAmount,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
}: {
|
||||
input: BigNumber;
|
||||
output: BigNumber;
|
||||
inputAmountPerEth: BigNumber;
|
||||
outputAmountPerEth: BigNumber;
|
||||
ethAmount: BigNumber | number;
|
||||
}): BigNumber {
|
||||
return !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(ethAmount).integerValue()
|
||||
: inputAmountPerEth.times(ethAmount).times(output.dividedToIntegerBy(input));
|
||||
}
|
||||
|
||||
export function nativeOrderToFill(
|
||||
side: MarketOperation,
|
||||
order: NativeOrderWithFillableAmounts,
|
||||
targetInput: BigNumber = POSITIVE_INF,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
filterNegativeAdjustedRateOrders: boolean = true,
|
||||
): Fill | undefined {
|
||||
const sourcePathId = hexUtils.random();
|
||||
// Create a single path from all orders.
|
||||
const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = order;
|
||||
const makerAmount = fillableMakerAmount;
|
||||
const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
|
||||
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
||||
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
||||
const { fee, gas } =
|
||||
fees[ERC20BridgeSource.Native] === undefined ? DEFAULT_FEE_ESTIMATE : fees[ERC20BridgeSource.Native]!(order);
|
||||
const outputPenalty = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
// targetInput can be less than the order size
|
||||
// whilst the penalty is constant, it affects the adjusted output
|
||||
// only up until the target has been exhausted.
|
||||
// A large order and an order at the exact target should be penalized
|
||||
// the same.
|
||||
const clippedInput = BigNumber.min(targetInput, input);
|
||||
// scale the clipped output inline with the input
|
||||
const clippedOutput = clippedInput.dividedBy(input).times(output);
|
||||
const adjustedOutput =
|
||||
side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty);
|
||||
const adjustedRate =
|
||||
side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput);
|
||||
// Optionally skip orders with rates that are <= 0.
|
||||
if (filterNegativeAdjustedRateOrders && adjustedRate.lte(0)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
sourcePathId,
|
||||
adjustedOutput,
|
||||
input: clippedInput,
|
||||
output: clippedOutput,
|
||||
flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
||||
source: ERC20BridgeSource.Native,
|
||||
type,
|
||||
fillData: { ...order },
|
||||
gas,
|
||||
};
|
||||
}
|
||||
|
||||
export function dexSampleToFill(
|
||||
side: MarketOperation,
|
||||
sample: DexSample,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
): Fill {
|
||||
const sourcePathId = hexUtils.random();
|
||||
const { source, fillData } = sample;
|
||||
const input = sample.input;
|
||||
const output = sample.output;
|
||||
const { fee, gas } =
|
||||
fees[source] === undefined ? DEFAULT_FEE_ESTIMATE : fees[source]!(sample.fillData) || DEFAULT_FEE_ESTIMATE;
|
||||
|
||||
const penalty = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
|
||||
return {
|
||||
sourcePathId,
|
||||
input,
|
||||
output,
|
||||
adjustedOutput: adjustOutput(side, output, penalty),
|
||||
source,
|
||||
fillData,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
flags: SOURCE_FLAGS[source],
|
||||
gas,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the output depending on whether this is a buy or a sell.
|
||||
*
|
||||
* If it is a sell, than output is lowered by the adjustment.
|
||||
* If it is a buy, than output is increased by adjustment.
|
||||
*/
|
||||
export function adjustOutput(side: MarketOperation, output: BigNumber, penalty: BigNumber): BigNumber {
|
||||
return side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { FANTOM_TOKENS, GEIST_FANTOM_POOLS } from './constants';
|
||||
import { GeistInfo } from './types';
|
||||
|
||||
const gTokenToUnderlyingToken = new Map<string, string>([
|
||||
[FANTOM_TOKENS.gFTM, FANTOM_TOKENS.WFTM],
|
||||
[FANTOM_TOKENS.gfUSDT, FANTOM_TOKENS.fUSDT],
|
||||
[FANTOM_TOKENS.gDAI, FANTOM_TOKENS.DAI],
|
||||
[FANTOM_TOKENS.gUSDC, FANTOM_TOKENS.USDC],
|
||||
[FANTOM_TOKENS.gETH, FANTOM_TOKENS.WETH],
|
||||
[FANTOM_TOKENS.gWBTC, FANTOM_TOKENS.WBTC],
|
||||
[FANTOM_TOKENS.gCRV, FANTOM_TOKENS.WCRV],
|
||||
[FANTOM_TOKENS.gMIM, FANTOM_TOKENS.MIM],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Returns GeistInfo for a certain pair if that pair exists on Geist
|
||||
*/
|
||||
export function getGeistInfoForPair(takerToken: string, makerToken: string): GeistInfo | undefined {
|
||||
let gToken;
|
||||
let underlyingToken;
|
||||
if (gTokenToUnderlyingToken.get(takerToken) === makerToken) {
|
||||
gToken = takerToken;
|
||||
underlyingToken = makerToken;
|
||||
} else if (gTokenToUnderlyingToken.get(makerToken) === takerToken) {
|
||||
gToken = makerToken;
|
||||
underlyingToken = takerToken;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
lendingPool: GEIST_FANTOM_POOLS.lendingPool,
|
||||
gToken,
|
||||
underlyingToken,
|
||||
};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { MarketOperation } from '../../types';
|
||||
|
||||
import { Fill, FillAdjustor } from './types';
|
||||
|
||||
// tslint:disable:prefer-function-over-method
|
||||
|
||||
export class IdentityFillAdjustor implements FillAdjustor {
|
||||
public adjustFills(side: MarketOperation, fills: Fill[], amount: BigNumber): Fill[] {
|
||||
return fills;
|
||||
}
|
||||
}
|
||||
@@ -1,870 +0,0 @@
|
||||
import { FillQuoteTransformerOrderType, RfqOrder } from '@0x/protocol-utils';
|
||||
import { BigNumber, NULL_ADDRESS } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DEFAULT_INFO_LOGGER, INVALID_SIGNATURE } from '../../constants';
|
||||
import {
|
||||
AltRfqMakerAssetOfferings,
|
||||
AssetSwapperContractAddresses,
|
||||
MarketOperation,
|
||||
NativeOrderWithFillableAmounts,
|
||||
SignedNativeOrder,
|
||||
} from '../../types';
|
||||
import { getAltMarketInfo } from '../alt_mm_implementation_utils';
|
||||
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from '../quote_requestor';
|
||||
import { toSignedNativeOrder } from '../rfq_client_mappers';
|
||||
import {
|
||||
getNativeAdjustedFillableAmountsFromMakerAmount,
|
||||
getNativeAdjustedFillableAmountsFromTakerAmount,
|
||||
getNativeAdjustedMakerFillAmount,
|
||||
} from '../utils';
|
||||
|
||||
import {
|
||||
dexSampleToReportSource,
|
||||
ExtendedQuoteReportSources,
|
||||
generateExtendedQuoteReportSources,
|
||||
generateQuoteReport,
|
||||
multiHopSampleToReportSource,
|
||||
nativeOrderToReportEntry,
|
||||
PriceComparisonsReport,
|
||||
QuoteReport,
|
||||
} from './../quote_report_generator';
|
||||
import { getComparisonPrices } from './comparison_price';
|
||||
import {
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
FEE_QUOTE_SOURCES_BY_CHAIN_ID,
|
||||
NATIVE_FEE_TOKEN_AMOUNT_BY_CHAIN_ID,
|
||||
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
SOURCE_FLAGS,
|
||||
ZERO_AMOUNT,
|
||||
} from './constants';
|
||||
import { IdentityFillAdjustor } from './identity_fill_adjustor';
|
||||
import { getBestTwoHopQuote } from './multihop_utils';
|
||||
import { createOrdersFromTwoHopSample } from './orders';
|
||||
import { Path, PathPenaltyOpts } from './path';
|
||||
import { findOptimalPathFromSamples } from './path_optimizer';
|
||||
import { DexOrderSampler, getSampleAmounts } from './sampler';
|
||||
import { SourceFilters } from './source_filters';
|
||||
import {
|
||||
AggregationError,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
GenerateOptimizedOrdersOpts,
|
||||
GetMarketOrdersOpts,
|
||||
MarketSideLiquidity,
|
||||
OptimizerResult,
|
||||
OptimizerResultWithReport,
|
||||
OrderDomain,
|
||||
} from './types';
|
||||
|
||||
// tslint:disable:boolean-naming
|
||||
|
||||
export class MarketOperationUtils {
|
||||
private readonly _sellSources: SourceFilters;
|
||||
private readonly _buySources: SourceFilters;
|
||||
private readonly _feeSources: SourceFilters;
|
||||
private readonly _nativeFeeToken: string;
|
||||
private readonly _nativeFeeTokenAmount: BigNumber;
|
||||
|
||||
private static _computeQuoteReport(
|
||||
quoteRequestor: QuoteRequestor | undefined,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
optimizerResult: OptimizerResult,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
): QuoteReport {
|
||||
const { side, quotes } = marketSideLiquidity;
|
||||
const { liquidityDelivered } = optimizerResult;
|
||||
return generateQuoteReport(side, quotes.nativeOrders, liquidityDelivered, comparisonPrice, quoteRequestor);
|
||||
}
|
||||
|
||||
private static _computeExtendedQuoteReportSources(
|
||||
quoteRequestor: QuoteRequestor | undefined,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
amount: BigNumber,
|
||||
optimizerResult: OptimizerResult,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
): ExtendedQuoteReportSources {
|
||||
const { side, quotes } = marketSideLiquidity;
|
||||
const { liquidityDelivered } = optimizerResult;
|
||||
return generateExtendedQuoteReportSources(
|
||||
side,
|
||||
quotes,
|
||||
liquidityDelivered,
|
||||
amount,
|
||||
comparisonPrice,
|
||||
quoteRequestor,
|
||||
);
|
||||
}
|
||||
|
||||
private static _computePriceComparisonsReport(
|
||||
quoteRequestor: QuoteRequestor | undefined,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
): PriceComparisonsReport {
|
||||
const { side, quotes } = marketSideLiquidity;
|
||||
const dexSources = _.flatten(quotes.dexQuotes).map(quote => dexSampleToReportSource(quote, side));
|
||||
const multiHopSources = quotes.twoHopQuotes.map(quote => multiHopSampleToReportSource(quote, side));
|
||||
const nativeSources = quotes.nativeOrders.map(order =>
|
||||
nativeOrderToReportEntry(
|
||||
order.type,
|
||||
order as any,
|
||||
order.fillableTakerAmount,
|
||||
comparisonPrice,
|
||||
quoteRequestor,
|
||||
),
|
||||
);
|
||||
|
||||
return { dexSources, multiHopSources, nativeSources };
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _sampler: DexOrderSampler,
|
||||
private readonly contractAddresses: AssetSwapperContractAddresses,
|
||||
private readonly _orderDomain: OrderDomain,
|
||||
) {
|
||||
this._buySources = BUY_SOURCE_FILTER_BY_CHAIN_ID[_sampler.chainId];
|
||||
this._sellSources = SELL_SOURCE_FILTER_BY_CHAIN_ID[_sampler.chainId];
|
||||
this._feeSources = new SourceFilters(FEE_QUOTE_SOURCES_BY_CHAIN_ID[_sampler.chainId]);
|
||||
this._nativeFeeToken = NATIVE_FEE_TOKEN_BY_CHAIN_ID[_sampler.chainId];
|
||||
this._nativeFeeTokenAmount = NATIVE_FEE_TOKEN_AMOUNT_BY_CHAIN_ID[_sampler.chainId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the liquidity available for a market sell operation
|
||||
* @param nativeOrders Native orders. Assumes LimitOrders not RfqOrders
|
||||
* @param takerAmount Amount of taker asset to sell.
|
||||
* @param opts Options object.
|
||||
* @return MarketSideLiquidity.
|
||||
*/
|
||||
public async getMarketSellLiquidityAsync(
|
||||
nativeOrders: SignedNativeOrder[],
|
||||
takerAmount: BigNumber,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
): Promise<MarketSideLiquidity> {
|
||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
const { makerToken, takerToken } = nativeOrders[0].order;
|
||||
const sampleAmounts = getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase);
|
||||
|
||||
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
|
||||
const quoteSourceFilters = this._sellSources.merge(requestFilters);
|
||||
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
|
||||
|
||||
// Used to determine whether the tx origin is an EOA or a contract
|
||||
const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
|
||||
|
||||
// Call the sampler contract.
|
||||
const samplerPromise = this._sampler.executeAsync(
|
||||
this._sampler.getBlockNumber(),
|
||||
this._sampler.getGasLeft(),
|
||||
this._sampler.getTokenDecimals([makerToken, takerToken]),
|
||||
// Get native order fillable amounts.
|
||||
this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||
// Get ETH -> maker token price.
|
||||
this._sampler.getBestNativeTokenSellRate(
|
||||
feeSourceFilters.sources,
|
||||
makerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
_opts.feeSchedule,
|
||||
),
|
||||
// Get ETH -> taker token price.
|
||||
this._sampler.getBestNativeTokenSellRate(
|
||||
feeSourceFilters.sources,
|
||||
takerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
_opts.feeSchedule,
|
||||
),
|
||||
// Get sell quotes for taker -> maker.
|
||||
this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||
this._sampler.getTwoHopSellQuotes(
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerAmount,
|
||||
),
|
||||
this._sampler.isAddressContract(txOrigin),
|
||||
this._sampler.getGasLeft(),
|
||||
);
|
||||
|
||||
// Refresh the cached pools asynchronously if required
|
||||
void this._refreshPoolCacheIfRequiredAsync(takerToken, makerToken);
|
||||
|
||||
const [
|
||||
[
|
||||
blockNumber,
|
||||
gasBefore,
|
||||
tokenDecimals,
|
||||
orderFillableTakerAmounts,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
dexQuotes,
|
||||
rawTwoHopQuotes,
|
||||
isTxOriginContract,
|
||||
gasAfter,
|
||||
],
|
||||
] = await Promise.all([samplerPromise]);
|
||||
|
||||
// Log the gas metrics
|
||||
_opts.samplerMetrics?.logGasDetails({ gasBefore, gasAfter });
|
||||
_opts.samplerMetrics?.logBlockNumber(blockNumber);
|
||||
|
||||
// Filter out any invalid two hop quotes where we couldn't find a route
|
||||
const twoHopQuotes = rawTwoHopQuotes.filter(
|
||||
q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource,
|
||||
);
|
||||
|
||||
const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
|
||||
|
||||
const isRfqSupported = !!(_opts.rfqt && !isTxOriginContract);
|
||||
const limitOrdersWithFillableAmounts = nativeOrders.map((order, i) => ({
|
||||
...order,
|
||||
...getNativeAdjustedFillableAmountsFromTakerAmount(order, orderFillableTakerAmounts[i]),
|
||||
}));
|
||||
|
||||
return {
|
||||
side: MarketOperation.Sell,
|
||||
inputAmount: takerAmount,
|
||||
inputToken: takerToken,
|
||||
outputToken: makerToken,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
quoteSourceFilters,
|
||||
makerTokenDecimals: makerTokenDecimals.toNumber(),
|
||||
takerTokenDecimals: takerTokenDecimals.toNumber(),
|
||||
quotes: {
|
||||
nativeOrders: limitOrdersWithFillableAmounts,
|
||||
rfqtIndicativeQuotes: [],
|
||||
twoHopQuotes,
|
||||
dexQuotes,
|
||||
},
|
||||
isRfqSupported,
|
||||
blockNumber: blockNumber.toNumber(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the liquidity available for a market buy operation
|
||||
* @param nativeOrders Native orders. Assumes LimitOrders not RfqOrders
|
||||
* @param makerAmount Amount of maker asset to buy.
|
||||
* @param opts Options object.
|
||||
* @return MarketSideLiquidity.
|
||||
*/
|
||||
public async getMarketBuyLiquidityAsync(
|
||||
nativeOrders: SignedNativeOrder[],
|
||||
makerAmount: BigNumber,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
): Promise<MarketSideLiquidity> {
|
||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
const { makerToken, takerToken } = nativeOrders[0].order;
|
||||
const sampleAmounts = getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase);
|
||||
|
||||
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
|
||||
const quoteSourceFilters = this._buySources.merge(requestFilters);
|
||||
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
|
||||
|
||||
// Used to determine whether the tx origin is an EOA or a contract
|
||||
const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
|
||||
|
||||
// Call the sampler contract.
|
||||
const samplerPromise = this._sampler.executeAsync(
|
||||
this._sampler.getBlockNumber(),
|
||||
this._sampler.getTokenDecimals([makerToken, takerToken]),
|
||||
// Get native order fillable amounts.
|
||||
this._sampler.getLimitOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||
// Get ETH -> makerToken token price.
|
||||
this._sampler.getBestNativeTokenSellRate(
|
||||
feeSourceFilters.sources,
|
||||
makerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
_opts.feeSchedule,
|
||||
),
|
||||
// Get ETH -> taker token price.
|
||||
this._sampler.getBestNativeTokenSellRate(
|
||||
feeSourceFilters.sources,
|
||||
takerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
_opts.feeSchedule,
|
||||
),
|
||||
// Get buy quotes for taker -> maker.
|
||||
this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||
this._sampler.getTwoHopBuyQuotes(
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerAmount,
|
||||
),
|
||||
this._sampler.isAddressContract(txOrigin),
|
||||
);
|
||||
|
||||
// Refresh the cached pools asynchronously if required
|
||||
void this._refreshPoolCacheIfRequiredAsync(takerToken, makerToken);
|
||||
|
||||
const [
|
||||
[
|
||||
blockNumber,
|
||||
tokenDecimals,
|
||||
orderFillableMakerAmounts,
|
||||
ethToMakerAssetRate,
|
||||
ethToTakerAssetRate,
|
||||
dexQuotes,
|
||||
rawTwoHopQuotes,
|
||||
isTxOriginContract,
|
||||
],
|
||||
] = await Promise.all([samplerPromise]);
|
||||
|
||||
// Filter out any invalid two hop quotes where we couldn't find a route
|
||||
const twoHopQuotes = rawTwoHopQuotes.filter(
|
||||
q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource,
|
||||
);
|
||||
|
||||
const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
|
||||
const isRfqSupported = !isTxOriginContract;
|
||||
|
||||
const limitOrdersWithFillableAmounts = nativeOrders.map((order, i) => ({
|
||||
...order,
|
||||
...getNativeAdjustedFillableAmountsFromMakerAmount(order, orderFillableMakerAmounts[i]),
|
||||
}));
|
||||
|
||||
return {
|
||||
side: MarketOperation.Buy,
|
||||
inputAmount: makerAmount,
|
||||
inputToken: makerToken,
|
||||
outputToken: takerToken,
|
||||
outputAmountPerEth: ethToTakerAssetRate,
|
||||
inputAmountPerEth: ethToMakerAssetRate,
|
||||
quoteSourceFilters,
|
||||
makerTokenDecimals: makerTokenDecimals.toNumber(),
|
||||
takerTokenDecimals: takerTokenDecimals.toNumber(),
|
||||
quotes: {
|
||||
nativeOrders: limitOrdersWithFillableAmounts,
|
||||
rfqtIndicativeQuotes: [],
|
||||
twoHopQuotes,
|
||||
dexQuotes,
|
||||
},
|
||||
isRfqSupported,
|
||||
blockNumber: blockNumber.toNumber(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the orders required for a batch of market buy operations by (potentially) merging native orders with
|
||||
* generated bridge orders.
|
||||
*
|
||||
* NOTE: Currently `getBatchMarketBuyOrdersAsync()` does not support external liquidity providers.
|
||||
*
|
||||
* @param batchNativeOrders Batch of Native orders. Assumes LimitOrders not RfqOrders
|
||||
* @param makerAmounts Array amount of maker asset to buy for each batch.
|
||||
* @param opts Options object.
|
||||
* @return orders.
|
||||
*/
|
||||
public async getBatchMarketBuyOrdersAsync(
|
||||
batchNativeOrders: SignedNativeOrder[][],
|
||||
makerAmounts: BigNumber[],
|
||||
opts: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber },
|
||||
): Promise<Array<OptimizerResult | undefined>> {
|
||||
if (batchNativeOrders.length === 0) {
|
||||
throw new Error(AggregationError.EmptyOrders);
|
||||
}
|
||||
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);
|
||||
|
||||
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
|
||||
|
||||
const ops = [
|
||||
this._sampler.getBlockNumber(),
|
||||
...batchNativeOrders.map(orders =>
|
||||
this._sampler.getLimitOrderFillableMakerAmounts(orders, this.contractAddresses.exchangeProxy),
|
||||
),
|
||||
...batchNativeOrders.map(orders =>
|
||||
this._sampler.getBestNativeTokenSellRate(
|
||||
feeSourceFilters.sources,
|
||||
orders[0].order.takerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
_opts.feeSchedule,
|
||||
),
|
||||
),
|
||||
...batchNativeOrders.map((orders, i) =>
|
||||
this._sampler.getBuyQuotes(
|
||||
quoteSourceFilters.sources,
|
||||
orders[0].order.makerToken,
|
||||
orders[0].order.takerToken,
|
||||
[makerAmounts[i]],
|
||||
),
|
||||
),
|
||||
...batchNativeOrders.map(orders =>
|
||||
this._sampler.getTokenDecimals([orders[0].order.makerToken, orders[0].order.takerToken]),
|
||||
),
|
||||
];
|
||||
|
||||
const [blockNumberRaw, ...executeResults] = await this._sampler.executeBatchAsync(ops);
|
||||
const batchOrderFillableMakerAmounts = executeResults.splice(0, batchNativeOrders.length) as BigNumber[][];
|
||||
const batchEthToTakerAssetRate = executeResults.splice(0, batchNativeOrders.length) as BigNumber[];
|
||||
const batchDexQuotes = executeResults.splice(0, batchNativeOrders.length) as DexSample[][][];
|
||||
const batchTokenDecimals = executeResults.splice(0, batchNativeOrders.length) as number[][];
|
||||
const inputAmountPerEth = ZERO_AMOUNT;
|
||||
|
||||
const blockNumber: number = (blockNumberRaw as BigNumber).toNumber();
|
||||
|
||||
return Promise.all(
|
||||
batchNativeOrders.map(async (nativeOrders, i) => {
|
||||
if (nativeOrders.length === 0) {
|
||||
throw new Error(AggregationError.EmptyOrders);
|
||||
}
|
||||
const { makerToken, takerToken } = nativeOrders[0].order;
|
||||
const orderFillableMakerAmounts = batchOrderFillableMakerAmounts[i];
|
||||
const outputAmountPerEth = batchEthToTakerAssetRate[i];
|
||||
const dexQuotes = batchDexQuotes[i];
|
||||
const makerAmount = makerAmounts[i];
|
||||
try {
|
||||
const optimizerResult = await this._generateOptimizedOrdersAsync(
|
||||
{
|
||||
side: MarketOperation.Buy,
|
||||
inputToken: makerToken,
|
||||
outputToken: takerToken,
|
||||
inputAmount: makerAmount,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
quoteSourceFilters,
|
||||
makerTokenDecimals: batchTokenDecimals[i][0],
|
||||
takerTokenDecimals: batchTokenDecimals[i][1],
|
||||
quotes: {
|
||||
nativeOrders: nativeOrders.map((o, k) => ({
|
||||
...o,
|
||||
...getNativeAdjustedFillableAmountsFromMakerAmount(o, orderFillableMakerAmounts[k]),
|
||||
})),
|
||||
dexQuotes,
|
||||
rfqtIndicativeQuotes: [],
|
||||
twoHopQuotes: [],
|
||||
},
|
||||
isRfqSupported: false,
|
||||
blockNumber,
|
||||
},
|
||||
{
|
||||
bridgeSlippage: _opts.bridgeSlippage,
|
||||
maxFallbackSlippage: _opts.maxFallbackSlippage,
|
||||
excludedSources: _opts.excludedSources,
|
||||
feeSchedule: _opts.feeSchedule,
|
||||
allowFallback: _opts.allowFallback,
|
||||
gasPrice: _opts.gasPrice,
|
||||
neonRouterNumSamples: _opts.neonRouterNumSamples,
|
||||
fillAdjustor: _opts.fillAdjustor,
|
||||
},
|
||||
);
|
||||
return optimizerResult;
|
||||
} catch (e) {
|
||||
// It's possible for one of the pairs to have no path
|
||||
// rather than throw NO_OPTIMAL_PATH we return undefined
|
||||
return undefined;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async _generateOptimizedOrdersAsync(
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
opts: GenerateOptimizedOrdersOpts,
|
||||
): Promise<OptimizerResult> {
|
||||
const {
|
||||
inputToken,
|
||||
outputToken,
|
||||
side,
|
||||
inputAmount,
|
||||
quotes,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
} = marketSideLiquidity;
|
||||
const { nativeOrders, rfqtIndicativeQuotes, dexQuotes } = quotes;
|
||||
|
||||
const orderOpts = {
|
||||
side,
|
||||
inputToken,
|
||||
outputToken,
|
||||
orderDomain: this._orderDomain,
|
||||
contractAddresses: this.contractAddresses,
|
||||
bridgeSlippage: opts.bridgeSlippage || 0,
|
||||
};
|
||||
|
||||
const augmentedRfqtIndicativeQuotes: NativeOrderWithFillableAmounts[] = rfqtIndicativeQuotes.map(
|
||||
q =>
|
||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
||||
({
|
||||
order: { ...new RfqOrder({ ...q }) },
|
||||
signature: INVALID_SIGNATURE,
|
||||
fillableMakerAmount: new BigNumber(q.makerAmount),
|
||||
fillableTakerAmount: new BigNumber(q.takerAmount),
|
||||
fillableTakerFeeAmount: ZERO_AMOUNT,
|
||||
type: FillQuoteTransformerOrderType.Rfq,
|
||||
} as NativeOrderWithFillableAmounts),
|
||||
);
|
||||
|
||||
// Find the optimal path.
|
||||
const penaltyOpts: PathPenaltyOpts = {
|
||||
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
|
||||
const takerAmountPerEth = side === MarketOperation.Sell ? inputAmountPerEth : outputAmountPerEth;
|
||||
const makerAmountPerEth = side === MarketOperation.Sell ? outputAmountPerEth : inputAmountPerEth;
|
||||
|
||||
// Find the optimal path using Rust router if enabled, otherwise fallback to JS Router
|
||||
let optimalPath: Path | undefined;
|
||||
optimalPath = findOptimalPathFromSamples(
|
||||
side,
|
||||
dexQuotes,
|
||||
[...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
||||
inputAmount,
|
||||
penaltyOpts,
|
||||
opts.feeSchedule,
|
||||
this._sampler.chainId,
|
||||
opts.neonRouterNumSamples,
|
||||
opts.fillAdjustor,
|
||||
opts.samplerMetrics,
|
||||
);
|
||||
|
||||
const optimalPathAdjustedRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
|
||||
|
||||
const { adjustedRate: bestTwoHopAdjustedRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
||||
marketSideLiquidity,
|
||||
opts.feeSchedule,
|
||||
opts.exchangeProxyOverhead,
|
||||
opts.fillAdjustor,
|
||||
);
|
||||
|
||||
if (bestTwoHopQuote && bestTwoHopAdjustedRate.isGreaterThan(optimalPathAdjustedRate)) {
|
||||
const twoHopOrders = createOrdersFromTwoHopSample(bestTwoHopQuote, orderOpts);
|
||||
return {
|
||||
optimizedOrders: twoHopOrders,
|
||||
liquidityDelivered: bestTwoHopQuote,
|
||||
sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop],
|
||||
marketSideLiquidity,
|
||||
adjustedRate: bestTwoHopAdjustedRate,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
};
|
||||
}
|
||||
|
||||
// If there is no optimal path AND we didn't return a MultiHop quote, then throw
|
||||
if (optimalPath === undefined) {
|
||||
throw new Error(AggregationError.NoOptimalPath);
|
||||
}
|
||||
|
||||
const finalizedPath = optimalPath.finalize(orderOpts);
|
||||
|
||||
return {
|
||||
optimizedOrders: finalizedPath.orders,
|
||||
liquidityDelivered: finalizedPath.fills,
|
||||
sourceFlags: finalizedPath.sourceFlags,
|
||||
marketSideLiquidity,
|
||||
adjustedRate: optimalPathAdjustedRate,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nativeOrders: Assumes LimitOrders not RfqOrders
|
||||
*/
|
||||
public async getOptimizerResultAsync(
|
||||
nativeOrders: SignedNativeOrder[],
|
||||
amount: BigNumber,
|
||||
side: MarketOperation,
|
||||
opts: Partial<GetMarketOrdersOpts> & { gasPrice: BigNumber },
|
||||
): Promise<OptimizerResultWithReport> {
|
||||
const _opts: GetMarketOrdersOpts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
const optimizerOpts: GenerateOptimizedOrdersOpts = {
|
||||
bridgeSlippage: _opts.bridgeSlippage,
|
||||
maxFallbackSlippage: _opts.maxFallbackSlippage,
|
||||
excludedSources: _opts.excludedSources,
|
||||
feeSchedule: _opts.feeSchedule,
|
||||
allowFallback: _opts.allowFallback,
|
||||
exchangeProxyOverhead: _opts.exchangeProxyOverhead,
|
||||
gasPrice: _opts.gasPrice,
|
||||
neonRouterNumSamples: _opts.neonRouterNumSamples,
|
||||
samplerMetrics: _opts.samplerMetrics,
|
||||
fillAdjustor: _opts.fillAdjustor,
|
||||
};
|
||||
|
||||
if (nativeOrders.length === 0) {
|
||||
throw new Error(AggregationError.EmptyOrders);
|
||||
}
|
||||
|
||||
// Compute an optimized path for on-chain DEX and open-orderbook. This should not include RFQ liquidity.
|
||||
const marketLiquidityFnAsync =
|
||||
side === MarketOperation.Sell
|
||||
? this.getMarketSellLiquidityAsync.bind(this)
|
||||
: this.getMarketBuyLiquidityAsync.bind(this);
|
||||
const marketSideLiquidity: MarketSideLiquidity = await marketLiquidityFnAsync(nativeOrders, amount, _opts);
|
||||
|
||||
// Phase 1 Routing
|
||||
// We find an optimized path for ALL the DEX and open-orderbook liquidity
|
||||
let optimizerResult: OptimizerResult | undefined;
|
||||
try {
|
||||
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, {
|
||||
...optimizerOpts,
|
||||
fillAdjustor: new IdentityFillAdjustor(),
|
||||
});
|
||||
} catch (e) {
|
||||
// If no on-chain or off-chain Open Orderbook orders are present, a `NoOptimalPath` will be thrown.
|
||||
// If this happens at this stage, there is still a chance that an RFQ order is fillable, therefore
|
||||
// we catch the error and continue.
|
||||
if (e.message !== AggregationError.NoOptimalPath) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate a suggested price. For now, this is simply the overall price of the aggregation.
|
||||
// We can use this as a comparison price for RFQ
|
||||
let wholeOrderPrice: BigNumber | undefined;
|
||||
if (optimizerResult) {
|
||||
wholeOrderPrice = getComparisonPrices(
|
||||
optimizerResult.adjustedRate,
|
||||
amount,
|
||||
marketSideLiquidity,
|
||||
_opts.feeSchedule,
|
||||
_opts.exchangeProxyOverhead,
|
||||
).wholeOrder;
|
||||
}
|
||||
|
||||
// If RFQ liquidity is enabled, make a request to check RFQ liquidity against the first optimizer result
|
||||
|
||||
// Phase 2 Routing
|
||||
// Mix in any off-chain RFQ quotes
|
||||
// Apply any fill adjustments i
|
||||
const phaseTwoOptimizerOpts = {
|
||||
...optimizerOpts,
|
||||
// Pass in the FillAdjustor for Phase 2 adjustment, in the future we may perform this adjustment
|
||||
// in Phase 1.
|
||||
fillAdjustor: _opts.fillAdjustor,
|
||||
};
|
||||
|
||||
const { rfqt } = _opts;
|
||||
if (
|
||||
marketSideLiquidity.isRfqSupported &&
|
||||
rfqt &&
|
||||
rfqt.quoteRequestor &&
|
||||
marketSideLiquidity.quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)
|
||||
) {
|
||||
// Timing of RFQT lifecycle
|
||||
const timeStart = new Date().getTime();
|
||||
const { makerToken, takerToken } = nativeOrders[0].order;
|
||||
|
||||
// Filter Alt Rfq Maker Asset Offerings to the current pair
|
||||
const filteredOfferings: AltRfqMakerAssetOfferings = {};
|
||||
if (rfqt.altRfqAssetOfferings) {
|
||||
const endpoints = Object.keys(rfqt.altRfqAssetOfferings);
|
||||
for (const endpoint of endpoints) {
|
||||
// Get the current pair if being offered
|
||||
const offering = getAltMarketInfo(rfqt.altRfqAssetOfferings[endpoint], makerToken, takerToken);
|
||||
if (offering) {
|
||||
filteredOfferings[endpoint] = [offering];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rfqt.isIndicative) {
|
||||
// An indicative quote is being requested, and indicative quotes price-aware enabled
|
||||
// Make the RFQT request and then re-run the sampler if new orders come back.
|
||||
|
||||
const indicativeQuotes =
|
||||
rfqt.rfqClient !== undefined
|
||||
? ((
|
||||
await rfqt.rfqClient.getV1PricesAsync({
|
||||
altRfqAssetOfferings: filteredOfferings,
|
||||
assetFillAmount: amount,
|
||||
chainId: this._sampler.chainId,
|
||||
comparisonPrice: wholeOrderPrice,
|
||||
integratorId: rfqt.integrator.integratorId,
|
||||
intentOnFilling: rfqt.intentOnFilling,
|
||||
makerToken,
|
||||
marketOperation: side,
|
||||
takerAddress: rfqt.takerAddress,
|
||||
takerToken,
|
||||
txOrigin: rfqt.txOrigin,
|
||||
})
|
||||
).prices as V4RFQIndicativeQuoteMM[])
|
||||
: await rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync(
|
||||
makerToken,
|
||||
takerToken,
|
||||
amount,
|
||||
side,
|
||||
wholeOrderPrice,
|
||||
rfqt,
|
||||
);
|
||||
const deltaTime = new Date().getTime() - timeStart;
|
||||
DEFAULT_INFO_LOGGER({
|
||||
rfqQuoteType: 'indicative',
|
||||
deltaTime,
|
||||
});
|
||||
// Re-run optimizer with the new indicative quote
|
||||
if (indicativeQuotes.length > 0) {
|
||||
// Attach the indicative quotes to the market side liquidity
|
||||
marketSideLiquidity.quotes.rfqtIndicativeQuotes = indicativeQuotes;
|
||||
|
||||
// Phase 2 Routing
|
||||
const phase1OptimalSources = optimizerResult
|
||||
? optimizerResult.optimizedOrders.map(o => o.source)
|
||||
: [];
|
||||
const phase2MarketSideLiquidity: MarketSideLiquidity = {
|
||||
...marketSideLiquidity,
|
||||
quotes: {
|
||||
...marketSideLiquidity.quotes,
|
||||
// Select only the quotes that were chosen in Phase 1
|
||||
dexQuotes: marketSideLiquidity.quotes.dexQuotes.filter(
|
||||
q => q.length > 0 && phase1OptimalSources.includes(q[0].source),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
optimizerResult = await this._generateOptimizedOrdersAsync(
|
||||
phase2MarketSideLiquidity,
|
||||
phaseTwoOptimizerOpts,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// A firm quote is being requested, and firm quotes price-aware enabled.
|
||||
// Ensure that `intentOnFilling` is enabled and make the request.
|
||||
const firmQuotes =
|
||||
rfqt.rfqClient !== undefined
|
||||
? (
|
||||
await rfqt.rfqClient.getV1QuotesAsync({
|
||||
altRfqAssetOfferings: filteredOfferings,
|
||||
assetFillAmount: amount,
|
||||
chainId: this._sampler.chainId,
|
||||
comparisonPrice: wholeOrderPrice,
|
||||
integratorId: rfqt.integrator.integratorId,
|
||||
intentOnFilling: rfqt.intentOnFilling,
|
||||
makerToken,
|
||||
marketOperation: side,
|
||||
takerAddress: rfqt.takerAddress,
|
||||
takerToken,
|
||||
txOrigin: rfqt.txOrigin,
|
||||
})
|
||||
).quotes.map(toSignedNativeOrder)
|
||||
: await rfqt.quoteRequestor.requestRfqtFirmQuotesAsync(
|
||||
makerToken,
|
||||
takerToken,
|
||||
amount,
|
||||
side,
|
||||
wholeOrderPrice,
|
||||
rfqt,
|
||||
);
|
||||
const deltaTime = new Date().getTime() - timeStart;
|
||||
DEFAULT_INFO_LOGGER({
|
||||
rfqQuoteType: 'firm',
|
||||
deltaTime,
|
||||
});
|
||||
if (firmQuotes.length > 0) {
|
||||
// Compute the RFQ order fillable amounts. This is done by performing a "soft" order
|
||||
// validation and by checking order balances that are monitored by our worker.
|
||||
// If a firm quote validator does not exist, then we assume that all orders are valid.
|
||||
const rfqTakerFillableAmounts =
|
||||
rfqt.firmQuoteValidator === undefined
|
||||
? firmQuotes.map(signedOrder => signedOrder.order.takerAmount)
|
||||
: await rfqt.firmQuoteValidator.getRfqtTakerFillableAmountsAsync(
|
||||
firmQuotes.map(q => new RfqOrder(q.order)),
|
||||
);
|
||||
|
||||
const quotesWithOrderFillableAmounts: NativeOrderWithFillableAmounts[] = firmQuotes.map(
|
||||
(order, i) => ({
|
||||
...order,
|
||||
fillableTakerAmount: rfqTakerFillableAmounts[i],
|
||||
// Adjust the maker amount by the available taker fill amount
|
||||
fillableMakerAmount: getNativeAdjustedMakerFillAmount(
|
||||
order.order,
|
||||
rfqTakerFillableAmounts[i],
|
||||
),
|
||||
fillableTakerFeeAmount: ZERO_AMOUNT,
|
||||
}),
|
||||
);
|
||||
|
||||
// Attach the firm RFQt quotes to the market side liquidity
|
||||
marketSideLiquidity.quotes.nativeOrders = [
|
||||
...quotesWithOrderFillableAmounts,
|
||||
...marketSideLiquidity.quotes.nativeOrders,
|
||||
];
|
||||
|
||||
// Re-run optimizer with the new firm quote. This is the second and last time
|
||||
// we run the optimized in a block of code. In this case, we don't catch a potential `NoOptimalPath` exception
|
||||
// and we let it bubble up if it happens.
|
||||
|
||||
// Phase 2 Routing
|
||||
// Optimization: Filter by what is already currently in the Phase1 output as it doesn't
|
||||
// seem possible that inclusion of RFQT could impact the sources chosen from Phase 1.
|
||||
const phase1OptimalSources = optimizerResult
|
||||
? optimizerResult.optimizedOrders.map(o => o.source)
|
||||
: [];
|
||||
const phase2MarketSideLiquidity: MarketSideLiquidity = {
|
||||
...marketSideLiquidity,
|
||||
quotes: {
|
||||
...marketSideLiquidity.quotes,
|
||||
// Select only the quotes that were chosen in Phase 1
|
||||
dexQuotes: marketSideLiquidity.quotes.dexQuotes.filter(
|
||||
q => q.length > 0 && phase1OptimalSources.includes(q[0].source),
|
||||
),
|
||||
},
|
||||
};
|
||||
optimizerResult = await this._generateOptimizedOrdersAsync(
|
||||
phase2MarketSideLiquidity,
|
||||
phaseTwoOptimizerOpts,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we should have at least one valid optimizer result, therefore we manually raise
|
||||
// `NoOptimalPath` if no optimizer result was ever set.
|
||||
if (optimizerResult === undefined) {
|
||||
throw new Error(AggregationError.NoOptimalPath);
|
||||
}
|
||||
|
||||
// Compute Quote Report and return the results.
|
||||
let quoteReport: QuoteReport | undefined;
|
||||
if (_opts.shouldGenerateQuoteReport) {
|
||||
quoteReport = MarketOperationUtils._computeQuoteReport(
|
||||
_opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
|
||||
marketSideLiquidity,
|
||||
optimizerResult,
|
||||
wholeOrderPrice,
|
||||
);
|
||||
}
|
||||
|
||||
// Always compute the Extended Quote Report
|
||||
let extendedQuoteReportSources: ExtendedQuoteReportSources | undefined;
|
||||
extendedQuoteReportSources = MarketOperationUtils._computeExtendedQuoteReportSources(
|
||||
_opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
|
||||
marketSideLiquidity,
|
||||
amount,
|
||||
optimizerResult,
|
||||
wholeOrderPrice,
|
||||
);
|
||||
|
||||
let priceComparisonsReport: PriceComparisonsReport | undefined;
|
||||
if (_opts.shouldIncludePriceComparisonsReport) {
|
||||
priceComparisonsReport = MarketOperationUtils._computePriceComparisonsReport(
|
||||
_opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
|
||||
marketSideLiquidity,
|
||||
wholeOrderPrice,
|
||||
);
|
||||
}
|
||||
return { ...optimizerResult, quoteReport, extendedQuoteReportSources, priceComparisonsReport };
|
||||
}
|
||||
|
||||
private async _refreshPoolCacheIfRequiredAsync(takerToken: string, makerToken: string): Promise<void> {
|
||||
_.values(this._sampler.poolsCaches)
|
||||
.filter(cache => cache !== undefined && !cache.isFresh(takerToken, makerToken))
|
||||
.forEach(cache => cache?.getFreshPoolsForPairAsync(takerToken, makerToken));
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable: max-file-line-count
|
||||
@@ -1,26 +0,0 @@
|
||||
import { LiquidityProviderRegistry } from './types';
|
||||
|
||||
// tslint:disable completed-docs
|
||||
export function getLiquidityProvidersForPair(
|
||||
registry: LiquidityProviderRegistry,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
): Array<{ providerAddress: string; gasCost: number }> {
|
||||
return Object.entries(registry)
|
||||
.filter(([, plp]) => [makerToken, takerToken].every(t => plp.tokens.includes(t)))
|
||||
.map(([providerAddress]) => {
|
||||
let gasCost: number;
|
||||
if (typeof registry[providerAddress].gasCost === 'number') {
|
||||
gasCost = registry[providerAddress].gasCost as number;
|
||||
} else {
|
||||
gasCost = (registry[providerAddress].gasCost as (takerToken: string, makerToken: string) => number)(
|
||||
takerToken,
|
||||
makerToken,
|
||||
);
|
||||
}
|
||||
return {
|
||||
providerAddress,
|
||||
gasCost,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { Omit } from '../../types';
|
||||
|
||||
import { ZERO_AMOUNT } from './constants';
|
||||
import { getTwoHopAdjustedRate } from './rate_utils';
|
||||
import {
|
||||
DexSample,
|
||||
ExchangeProxyOverhead,
|
||||
FeeSchedule,
|
||||
FillAdjustor,
|
||||
MarketSideLiquidity,
|
||||
MultiHopFillData,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Returns the best two-hop quote and the fee-adjusted rate of that quote.
|
||||
*/
|
||||
export function getBestTwoHopQuote(
|
||||
marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>,
|
||||
feeSchedule?: FeeSchedule,
|
||||
exchangeProxyOverhead?: ExchangeProxyOverhead,
|
||||
fillAdjustor?: FillAdjustor,
|
||||
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
|
||||
const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity;
|
||||
const { twoHopQuotes } = quotes;
|
||||
// Ensure the expected data we require exists. In the case where all hops reverted
|
||||
// or there were no sources included that allowed for multi hop,
|
||||
// we can end up with empty, but not undefined, fill data
|
||||
const filteredQuotes = twoHopQuotes.filter(
|
||||
quote =>
|
||||
quote &&
|
||||
quote.fillData &&
|
||||
quote.fillData.firstHopSource &&
|
||||
quote.fillData.secondHopSource &&
|
||||
quote.output.isGreaterThan(ZERO_AMOUNT),
|
||||
);
|
||||
if (filteredQuotes.length === 0) {
|
||||
return { quote: undefined, adjustedRate: ZERO_AMOUNT };
|
||||
}
|
||||
const best = filteredQuotes
|
||||
.map(quote =>
|
||||
getTwoHopAdjustedRate(
|
||||
side,
|
||||
quote,
|
||||
inputAmount,
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
exchangeProxyOverhead,
|
||||
fillAdjustor,
|
||||
),
|
||||
)
|
||||
.reduce(
|
||||
(prev, curr, i) =>
|
||||
curr.isGreaterThan(prev.adjustedRate) ? { adjustedRate: curr, quote: filteredQuotes[i] } : prev,
|
||||
{
|
||||
adjustedRate: getTwoHopAdjustedRate(
|
||||
side,
|
||||
filteredQuotes[0],
|
||||
inputAmount,
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
exchangeProxyOverhead,
|
||||
fillAdjustor,
|
||||
),
|
||||
quote: filteredQuotes[0],
|
||||
},
|
||||
);
|
||||
return best;
|
||||
}
|
||||
@@ -1,628 +0,0 @@
|
||||
import { BridgeProtocol, encodeBridgeSourceId, FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
import _ = require('lodash');
|
||||
|
||||
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
|
||||
|
||||
import { MAX_UINT256, ZERO_AMOUNT } from './constants';
|
||||
import {
|
||||
AaveV2FillData,
|
||||
AggregationError,
|
||||
BalancerV2BatchSwapFillData,
|
||||
BalancerV2FillData,
|
||||
BancorFillData,
|
||||
CompoundFillData,
|
||||
CurveFillData,
|
||||
DexSample,
|
||||
DODOFillData,
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
FillData,
|
||||
FinalUniswapV3FillData,
|
||||
GeistFillData,
|
||||
GenericRouterFillData,
|
||||
GMXFillData,
|
||||
KyberDmmFillData,
|
||||
LidoFillData,
|
||||
LiquidityProviderFillData,
|
||||
MakerPsmFillData,
|
||||
MooniswapFillData,
|
||||
MultiHopFillData,
|
||||
NativeFillData,
|
||||
NativeLimitOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
OptimizedMarketBridgeOrder,
|
||||
OptimizedMarketOrder,
|
||||
OptimizedMarketOrderBase,
|
||||
OrderDomain,
|
||||
PlatypusFillData,
|
||||
ShellFillData,
|
||||
SynthetixFillData,
|
||||
UniswapV2FillData,
|
||||
UniswapV3FillData,
|
||||
UniswapV3PathAmount,
|
||||
VelodromeFillData,
|
||||
WOOFiFillData,
|
||||
} from './types';
|
||||
|
||||
// tslint:disable completed-docs
|
||||
|
||||
export interface CreateOrderFromPathOpts {
|
||||
side: MarketOperation;
|
||||
inputToken: string;
|
||||
outputToken: string;
|
||||
orderDomain: OrderDomain;
|
||||
contractAddresses: AssetSwapperContractAddresses;
|
||||
bridgeSlippage: number;
|
||||
}
|
||||
|
||||
export function createOrdersFromTwoHopSample(
|
||||
sample: DexSample<MultiHopFillData>,
|
||||
opts: CreateOrderFromPathOpts,
|
||||
): OptimizedMarketOrder[] {
|
||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
||||
const { firstHopSource, secondHopSource, intermediateToken } = sample.fillData;
|
||||
const firstHopFill: Fill = {
|
||||
sourcePathId: '',
|
||||
source: firstHopSource.source,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
input: opts.side === MarketOperation.Sell ? sample.input : ZERO_AMOUNT,
|
||||
output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
|
||||
adjustedOutput: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
|
||||
fillData: firstHopSource.fillData,
|
||||
flags: BigInt(0),
|
||||
gas: 1,
|
||||
};
|
||||
const secondHopFill: Fill = {
|
||||
sourcePathId: '',
|
||||
source: secondHopSource.source,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
input: opts.side === MarketOperation.Sell ? MAX_UINT256 : sample.input,
|
||||
output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
|
||||
adjustedOutput: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
|
||||
fillData: secondHopSource.fillData,
|
||||
flags: BigInt(0),
|
||||
gas: 1,
|
||||
};
|
||||
return [
|
||||
createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts.side),
|
||||
createBridgeOrder(secondHopFill, makerToken, intermediateToken, opts.side),
|
||||
];
|
||||
}
|
||||
|
||||
export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): string {
|
||||
switch (source) {
|
||||
case ERC20BridgeSource.Balancer:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Balancer, 'Balancer');
|
||||
case ERC20BridgeSource.BalancerV2:
|
||||
return encodeBridgeSourceId(BridgeProtocol.BalancerV2Batch, 'BalancerV2');
|
||||
case ERC20BridgeSource.Bancor:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Bancor, 'Bancor');
|
||||
case ERC20BridgeSource.Curve:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'Curve');
|
||||
case ERC20BridgeSource.CryptoCom:
|
||||
return encodeBridgeSourceId(BridgeProtocol.CryptoCom, 'CryptoCom');
|
||||
case ERC20BridgeSource.Dodo:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Dodo, 'Dodo');
|
||||
case ERC20BridgeSource.LiquidityProvider:
|
||||
// "LiquidityProvider" is too long to encode (17 characters).
|
||||
return encodeBridgeSourceId(BridgeProtocol.Unknown, 'LP');
|
||||
case ERC20BridgeSource.MakerPsm:
|
||||
return encodeBridgeSourceId(BridgeProtocol.MakerPsm, 'MakerPsm');
|
||||
case ERC20BridgeSource.Mooniswap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Mooniswap, 'Mooniswap');
|
||||
case ERC20BridgeSource.MStable:
|
||||
return encodeBridgeSourceId(BridgeProtocol.MStable, 'MStable');
|
||||
case ERC20BridgeSource.Shell:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Shell, 'Shell');
|
||||
case ERC20BridgeSource.SushiSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'SushiSwap');
|
||||
case ERC20BridgeSource.Uniswap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Uniswap, 'Uniswap');
|
||||
case ERC20BridgeSource.UniswapV2:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'UniswapV2');
|
||||
case ERC20BridgeSource.DodoV2:
|
||||
return encodeBridgeSourceId(BridgeProtocol.DodoV2, 'DodoV2');
|
||||
case ERC20BridgeSource.PancakeSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'PancakeSwap');
|
||||
case ERC20BridgeSource.PancakeSwapV2:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'PancakeSwapV2');
|
||||
case ERC20BridgeSource.BakerySwap:
|
||||
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:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'Ellipsis');
|
||||
case ERC20BridgeSource.Component:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Shell, 'Component');
|
||||
case ERC20BridgeSource.Saddle:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'Saddle');
|
||||
case ERC20BridgeSource.XSigma:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'xSigma');
|
||||
case ERC20BridgeSource.ApeSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'ApeSwap');
|
||||
case ERC20BridgeSource.CheeseSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'CheeseSwap');
|
||||
case ERC20BridgeSource.UniswapV3:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV3, 'UniswapV3');
|
||||
case ERC20BridgeSource.KyberDmm:
|
||||
return encodeBridgeSourceId(BridgeProtocol.KyberDmm, 'KyberDmm');
|
||||
case ERC20BridgeSource.QuickSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'QuickSwap');
|
||||
case ERC20BridgeSource.Dfyn:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Dfyn');
|
||||
case ERC20BridgeSource.CurveV2:
|
||||
return encodeBridgeSourceId(BridgeProtocol.CurveV2, 'CurveV2');
|
||||
case ERC20BridgeSource.WaultSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'WaultSwap');
|
||||
case ERC20BridgeSource.FirebirdOneSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'FirebirdOneSwap');
|
||||
case ERC20BridgeSource.Lido:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Lido, 'Lido');
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'ShibaSwap');
|
||||
case ERC20BridgeSource.IronSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'IronSwap');
|
||||
case ERC20BridgeSource.ACryptos:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'ACryptoS');
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Pangolin');
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'TraderJoe');
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'UbeSwap');
|
||||
case ERC20BridgeSource.Beethovenx:
|
||||
return encodeBridgeSourceId(BridgeProtocol.BalancerV2, 'Beethovenx');
|
||||
case ERC20BridgeSource.SpiritSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'SpiritSwap');
|
||||
case ERC20BridgeSource.SpookySwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'SpookySwap');
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MorpheusSwap');
|
||||
case ERC20BridgeSource.Yoshi:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Yoshi');
|
||||
case ERC20BridgeSource.AaveV2:
|
||||
return encodeBridgeSourceId(BridgeProtocol.AaveV2, 'AaveV2');
|
||||
case ERC20BridgeSource.Compound:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Compound, 'Compound');
|
||||
case ERC20BridgeSource.Geist:
|
||||
return encodeBridgeSourceId(BridgeProtocol.AaveV2, 'Geist');
|
||||
case ERC20BridgeSource.MobiusMoney:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'MobiusMoney');
|
||||
case ERC20BridgeSource.BiSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'BiSwap');
|
||||
case ERC20BridgeSource.MDex:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MDex');
|
||||
case ERC20BridgeSource.KnightSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'KnightSwap');
|
||||
case ERC20BridgeSource.GMX:
|
||||
return encodeBridgeSourceId(BridgeProtocol.GMX, 'GMX');
|
||||
case ERC20BridgeSource.Platypus:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Platypus, 'Platypus');
|
||||
case ERC20BridgeSource.MeshSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MeshSwap');
|
||||
case ERC20BridgeSource.BancorV3:
|
||||
return encodeBridgeSourceId(BridgeProtocol.BancorV3, 'BancorV3');
|
||||
case ERC20BridgeSource.Velodrome:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Velodrome, 'Velodrome');
|
||||
case ERC20BridgeSource.Synthetix:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Synthetix, 'Synthetix');
|
||||
case ERC20BridgeSource.WOOFi:
|
||||
return encodeBridgeSourceId(BridgeProtocol.WOOFi, 'WOOFi');
|
||||
default:
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
}
|
||||
|
||||
export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder): string {
|
||||
let bridgeData: string;
|
||||
if (
|
||||
order.source === ERC20BridgeSource.MultiHop ||
|
||||
order.source === ERC20BridgeSource.MultiBridge ||
|
||||
order.source === ERC20BridgeSource.Native
|
||||
) {
|
||||
throw new Error('Invalid order to encode for Bridge Data');
|
||||
}
|
||||
const encoder = BRIDGE_ENCODERS[order.source];
|
||||
|
||||
if (!encoder) {
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
|
||||
switch (order.source) {
|
||||
case ERC20BridgeSource.Curve:
|
||||
case ERC20BridgeSource.CurveV2:
|
||||
case ERC20BridgeSource.Nerve:
|
||||
case ERC20BridgeSource.Synapse:
|
||||
case ERC20BridgeSource.Belt:
|
||||
case ERC20BridgeSource.Ellipsis:
|
||||
case ERC20BridgeSource.Saddle:
|
||||
case ERC20BridgeSource.XSigma:
|
||||
case ERC20BridgeSource.FirebirdOneSwap:
|
||||
case ERC20BridgeSource.IronSwap:
|
||||
case ERC20BridgeSource.ACryptos:
|
||||
case ERC20BridgeSource.MobiusMoney:
|
||||
const curveFillData = (order as OptimizedMarketBridgeOrder<CurveFillData>).fillData;
|
||||
bridgeData = encoder.encode([
|
||||
curveFillData.pool.poolAddress,
|
||||
curveFillData.pool.exchangeFunctionSelector,
|
||||
curveFillData.fromTokenIdx,
|
||||
curveFillData.toTokenIdx,
|
||||
]);
|
||||
break;
|
||||
case ERC20BridgeSource.Balancer:
|
||||
case ERC20BridgeSource.BalancerV2:
|
||||
{
|
||||
const balancerV2FillData = (order as OptimizedMarketBridgeOrder<BalancerV2BatchSwapFillData>).fillData;
|
||||
bridgeData = encoder.encode([
|
||||
balancerV2FillData.vault,
|
||||
balancerV2FillData.swapSteps,
|
||||
balancerV2FillData.assets,
|
||||
]);
|
||||
}
|
||||
break;
|
||||
case ERC20BridgeSource.Beethovenx:
|
||||
const beethovenFillData = (order as OptimizedMarketBridgeOrder<BalancerV2FillData>).fillData;
|
||||
const { vault, poolId } = beethovenFillData;
|
||||
bridgeData = encoder.encode([vault, poolId]);
|
||||
break;
|
||||
case ERC20BridgeSource.Bancor:
|
||||
const bancorFillData = (order as OptimizedMarketBridgeOrder<BancorFillData>).fillData;
|
||||
bridgeData = encoder.encode([bancorFillData.networkAddress, bancorFillData.path]);
|
||||
break;
|
||||
case ERC20BridgeSource.UniswapV2:
|
||||
case ERC20BridgeSource.SushiSwap:
|
||||
case ERC20BridgeSource.CryptoCom:
|
||||
case ERC20BridgeSource.PancakeSwap:
|
||||
case ERC20BridgeSource.PancakeSwapV2:
|
||||
case ERC20BridgeSource.BakerySwap:
|
||||
case ERC20BridgeSource.ApeSwap:
|
||||
case ERC20BridgeSource.CheeseSwap:
|
||||
case ERC20BridgeSource.QuickSwap:
|
||||
case ERC20BridgeSource.Dfyn:
|
||||
case ERC20BridgeSource.WaultSwap:
|
||||
case ERC20BridgeSource.ShibaSwap:
|
||||
case ERC20BridgeSource.Pangolin:
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
case ERC20BridgeSource.SpiritSwap:
|
||||
case ERC20BridgeSource.SpookySwap:
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
case ERC20BridgeSource.BiSwap:
|
||||
case ERC20BridgeSource.MDex:
|
||||
case ERC20BridgeSource.KnightSwap:
|
||||
case ERC20BridgeSource.Yoshi:
|
||||
case ERC20BridgeSource.MeshSwap:
|
||||
const uniswapV2FillData = (order as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
|
||||
bridgeData = encoder.encode([uniswapV2FillData.router, uniswapV2FillData.tokenAddressPath]);
|
||||
break;
|
||||
case ERC20BridgeSource.Mooniswap:
|
||||
const mooniswapFillData = (order as OptimizedMarketBridgeOrder<MooniswapFillData>).fillData;
|
||||
bridgeData = encoder.encode([mooniswapFillData.poolAddress]);
|
||||
break;
|
||||
case ERC20BridgeSource.Dodo:
|
||||
const dodoFillData = (order as OptimizedMarketBridgeOrder<DODOFillData>).fillData;
|
||||
bridgeData = encoder.encode([
|
||||
dodoFillData.helperAddress,
|
||||
dodoFillData.poolAddress,
|
||||
dodoFillData.isSellBase,
|
||||
]);
|
||||
break;
|
||||
case ERC20BridgeSource.DodoV2:
|
||||
const dodoV2FillData = (order as OptimizedMarketBridgeOrder<DODOFillData>).fillData;
|
||||
bridgeData = encoder.encode([dodoV2FillData.poolAddress, dodoV2FillData.isSellBase]);
|
||||
break;
|
||||
case ERC20BridgeSource.Shell:
|
||||
case ERC20BridgeSource.Component:
|
||||
const shellFillData = (order as OptimizedMarketBridgeOrder<ShellFillData>).fillData;
|
||||
bridgeData = encoder.encode([shellFillData.poolAddress]);
|
||||
break;
|
||||
case ERC20BridgeSource.LiquidityProvider:
|
||||
const lpFillData = (order as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
|
||||
bridgeData = encoder.encode([lpFillData.poolAddress, tokenAddressEncoder.encode([order.takerToken])]);
|
||||
break;
|
||||
case ERC20BridgeSource.Uniswap:
|
||||
const uniFillData = (order as OptimizedMarketBridgeOrder<GenericRouterFillData>).fillData;
|
||||
bridgeData = encoder.encode([uniFillData.router]);
|
||||
break;
|
||||
case ERC20BridgeSource.MStable:
|
||||
const mStableFillData = (order as OptimizedMarketBridgeOrder<GenericRouterFillData>).fillData;
|
||||
bridgeData = encoder.encode([mStableFillData.router]);
|
||||
break;
|
||||
case ERC20BridgeSource.MakerPsm:
|
||||
const psmFillData = (order as OptimizedMarketBridgeOrder<MakerPsmFillData>).fillData;
|
||||
bridgeData = encoder.encode([psmFillData.psmAddress, psmFillData.gemTokenAddress]);
|
||||
break;
|
||||
case ERC20BridgeSource.UniswapV3:
|
||||
const uniswapV3FillData = (order as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
|
||||
bridgeData = encoder.encode([uniswapV3FillData.router, uniswapV3FillData.uniswapPath]);
|
||||
break;
|
||||
case ERC20BridgeSource.KyberDmm:
|
||||
const kyberDmmFillData = (order as OptimizedMarketBridgeOrder<KyberDmmFillData>).fillData;
|
||||
bridgeData = encoder.encode([
|
||||
kyberDmmFillData.router,
|
||||
kyberDmmFillData.poolsPath,
|
||||
kyberDmmFillData.tokenAddressPath,
|
||||
]);
|
||||
break;
|
||||
case ERC20BridgeSource.Lido:
|
||||
const lidoFillData = (order as OptimizedMarketBridgeOrder<LidoFillData>).fillData;
|
||||
bridgeData = encoder.encode([lidoFillData.stEthTokenAddress, lidoFillData.wstEthTokenAddress]);
|
||||
break;
|
||||
case ERC20BridgeSource.AaveV2:
|
||||
const aaveFillData = (order as OptimizedMarketBridgeOrder<AaveV2FillData>).fillData;
|
||||
bridgeData = encoder.encode([aaveFillData.lendingPool, aaveFillData.aToken]);
|
||||
break;
|
||||
case ERC20BridgeSource.Compound:
|
||||
const compoundFillData = (order as OptimizedMarketBridgeOrder<CompoundFillData>).fillData;
|
||||
bridgeData = encoder.encode([compoundFillData.cToken]);
|
||||
break;
|
||||
case ERC20BridgeSource.Geist:
|
||||
const geistFillData = (order as OptimizedMarketBridgeOrder<GeistFillData>).fillData;
|
||||
bridgeData = encoder.encode([geistFillData.lendingPool, geistFillData.gToken]);
|
||||
break;
|
||||
case ERC20BridgeSource.GMX:
|
||||
const gmxFillData = (order as OptimizedMarketBridgeOrder<GMXFillData>).fillData;
|
||||
bridgeData = encoder.encode([
|
||||
gmxFillData.router,
|
||||
gmxFillData.reader,
|
||||
gmxFillData.vault,
|
||||
gmxFillData.tokenAddressPath,
|
||||
]);
|
||||
break;
|
||||
case ERC20BridgeSource.Platypus:
|
||||
const platypusFillData = (order as OptimizedMarketBridgeOrder<PlatypusFillData>).fillData;
|
||||
bridgeData = encoder.encode([
|
||||
platypusFillData.router,
|
||||
platypusFillData.pool,
|
||||
platypusFillData.tokenAddressPath,
|
||||
]);
|
||||
break;
|
||||
case ERC20BridgeSource.BancorV3:
|
||||
const bancorV3FillData = (order as OptimizedMarketBridgeOrder<BancorFillData>).fillData;
|
||||
bridgeData = encoder.encode([bancorV3FillData.networkAddress, bancorV3FillData.path]);
|
||||
break;
|
||||
case ERC20BridgeSource.Velodrome:
|
||||
const velodromeFillData = (order as OptimizedMarketBridgeOrder<VelodromeFillData>).fillData;
|
||||
bridgeData = encoder.encode([velodromeFillData.router, velodromeFillData.stable]);
|
||||
break;
|
||||
case ERC20BridgeSource.Synthetix:
|
||||
const fillData = (order as OptimizedMarketBridgeOrder<SynthetixFillData>).fillData;
|
||||
bridgeData = encoder.encode([
|
||||
fillData.synthetix,
|
||||
fillData.takerTokenSymbolBytes32,
|
||||
fillData.makerTokenSymbolBytes32,
|
||||
]);
|
||||
break;
|
||||
case ERC20BridgeSource.WOOFi:
|
||||
const woofiFillData = (order as OptimizedMarketBridgeOrder<WOOFiFillData>).fillData;
|
||||
bridgeData = encoder.encode([woofiFillData.poolAddress]);
|
||||
break;
|
||||
default:
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
return bridgeData;
|
||||
}
|
||||
|
||||
export const poolEncoder = AbiEncoder.create([{ name: 'poolAddress', type: 'address' }]);
|
||||
const curveEncoder = AbiEncoder.create([
|
||||
{ name: 'curveAddress', type: 'address' },
|
||||
{ name: 'exchangeFunctionSelector', type: 'bytes4' },
|
||||
{ name: 'fromTokenIdx', type: 'int128' },
|
||||
{ name: 'toTokenIdx', type: 'int128' },
|
||||
]);
|
||||
const makerPsmEncoder = AbiEncoder.create([
|
||||
{ name: 'psmAddress', type: 'address' },
|
||||
{ name: 'gemTokenAddress', type: 'address' },
|
||||
]);
|
||||
const balancerV2Encoder = AbiEncoder.create([
|
||||
{ name: 'vault', type: 'address' },
|
||||
{ name: 'poolId', type: 'bytes32' },
|
||||
]);
|
||||
const routerAddressPathEncoder = AbiEncoder.create('(address,address[])');
|
||||
const tokenAddressEncoder = AbiEncoder.create([{ name: 'tokenAddress', type: 'address' }]);
|
||||
const gmxAddressPathEncoder = AbiEncoder.create('(address,address,address,address[])');
|
||||
const platypusAddressPathEncoder = AbiEncoder.create('(address,address[],address[])');
|
||||
|
||||
export const BRIDGE_ENCODERS: {
|
||||
[key in Exclude<
|
||||
ERC20BridgeSource,
|
||||
ERC20BridgeSource.Native | ERC20BridgeSource.MultiHop | ERC20BridgeSource.MultiBridge
|
||||
>]: AbiEncoder.DataType;
|
||||
} = {
|
||||
[ERC20BridgeSource.LiquidityProvider]: AbiEncoder.create([
|
||||
{ name: 'provider', type: 'address' },
|
||||
{ name: 'data', type: 'bytes' },
|
||||
]),
|
||||
[ERC20BridgeSource.Dodo]: AbiEncoder.create([
|
||||
{ name: 'helper', type: 'address' },
|
||||
{ name: 'poolAddress', type: 'address' },
|
||||
{ name: 'isSellBase', type: 'bool' },
|
||||
]),
|
||||
[ERC20BridgeSource.DodoV2]: AbiEncoder.create([
|
||||
{ name: 'poolAddress', type: 'address' },
|
||||
{ name: 'isSellBase', type: 'bool' },
|
||||
]),
|
||||
// Curve like
|
||||
[ERC20BridgeSource.Curve]: curveEncoder,
|
||||
[ERC20BridgeSource.CurveV2]: curveEncoder,
|
||||
[ERC20BridgeSource.Nerve]: curveEncoder,
|
||||
[ERC20BridgeSource.Synapse]: curveEncoder,
|
||||
[ERC20BridgeSource.Belt]: curveEncoder,
|
||||
[ERC20BridgeSource.Ellipsis]: curveEncoder,
|
||||
[ERC20BridgeSource.Saddle]: curveEncoder,
|
||||
[ERC20BridgeSource.XSigma]: curveEncoder,
|
||||
[ERC20BridgeSource.FirebirdOneSwap]: curveEncoder,
|
||||
[ERC20BridgeSource.IronSwap]: curveEncoder,
|
||||
[ERC20BridgeSource.ACryptos]: curveEncoder,
|
||||
[ERC20BridgeSource.MobiusMoney]: curveEncoder,
|
||||
// UniswapV2 like, (router, address[])
|
||||
[ERC20BridgeSource.Bancor]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.BancorV3]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.UniswapV2]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.SushiSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.CryptoCom]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.ShibaSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.Pangolin]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.TraderJoe]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.SpiritSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.SpookySwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.MorpheusSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.BiSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.MDex]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.KnightSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.Yoshi]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.MeshSwap]: routerAddressPathEncoder,
|
||||
// Avalanche
|
||||
[ERC20BridgeSource.GMX]: gmxAddressPathEncoder,
|
||||
[ERC20BridgeSource.Platypus]: platypusAddressPathEncoder,
|
||||
// Celo
|
||||
[ERC20BridgeSource.UbeSwap]: routerAddressPathEncoder,
|
||||
// BSC
|
||||
[ERC20BridgeSource.PancakeSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.PancakeSwapV2]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.BakerySwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.ApeSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.CheeseSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.WaultSwap]: routerAddressPathEncoder,
|
||||
// Polygon
|
||||
[ERC20BridgeSource.QuickSwap]: routerAddressPathEncoder,
|
||||
[ERC20BridgeSource.Dfyn]: routerAddressPathEncoder,
|
||||
// Generic pools
|
||||
[ERC20BridgeSource.Shell]: poolEncoder,
|
||||
[ERC20BridgeSource.Component]: poolEncoder,
|
||||
[ERC20BridgeSource.Mooniswap]: poolEncoder,
|
||||
[ERC20BridgeSource.MStable]: poolEncoder,
|
||||
[ERC20BridgeSource.Balancer]: poolEncoder,
|
||||
[ERC20BridgeSource.Uniswap]: poolEncoder,
|
||||
// Custom integrations
|
||||
[ERC20BridgeSource.MakerPsm]: makerPsmEncoder,
|
||||
[ERC20BridgeSource.BalancerV2]: AbiEncoder.create([
|
||||
{ name: 'vault', type: 'address' },
|
||||
{
|
||||
name: 'swapSteps',
|
||||
type: 'tuple[]',
|
||||
components: [
|
||||
{ name: 'poolId', type: 'bytes32' },
|
||||
{ name: 'assetInIndex', type: 'uint256' },
|
||||
{ name: 'assetOutIndex', type: 'uint256' },
|
||||
{ name: 'amount', type: 'uint256' },
|
||||
{ name: 'userData', type: 'bytes' },
|
||||
],
|
||||
},
|
||||
{ name: 'assets', type: 'address[]' },
|
||||
]),
|
||||
[ERC20BridgeSource.Beethovenx]: balancerV2Encoder,
|
||||
[ERC20BridgeSource.UniswapV3]: AbiEncoder.create([
|
||||
{ name: 'router', type: 'address' },
|
||||
{ name: 'path', type: 'bytes' },
|
||||
]),
|
||||
[ERC20BridgeSource.KyberDmm]: AbiEncoder.create('(address,address[],address[])'),
|
||||
[ERC20BridgeSource.Lido]: AbiEncoder.create('(address,address)'),
|
||||
[ERC20BridgeSource.AaveV2]: AbiEncoder.create('(address,address)'),
|
||||
[ERC20BridgeSource.Compound]: AbiEncoder.create('(address)'),
|
||||
[ERC20BridgeSource.Geist]: AbiEncoder.create('(address,address)'),
|
||||
[ERC20BridgeSource.Velodrome]: AbiEncoder.create('(address,bool)'),
|
||||
[ERC20BridgeSource.Synthetix]: AbiEncoder.create('(address,bytes32,bytes32)'),
|
||||
[ERC20BridgeSource.WOOFi]: AbiEncoder.create('(address)'),
|
||||
};
|
||||
|
||||
function getFillTokenAmounts(fill: Fill, side: MarketOperation): [BigNumber, BigNumber] {
|
||||
return [
|
||||
// Maker asset amount.
|
||||
side === MarketOperation.Sell ? fill.output.integerValue(BigNumber.ROUND_DOWN) : fill.input,
|
||||
// Taker asset amount.
|
||||
side === MarketOperation.Sell ? fill.input : fill.output.integerValue(BigNumber.ROUND_UP),
|
||||
];
|
||||
}
|
||||
|
||||
export function createNativeOptimizedOrder(
|
||||
fill: Fill<NativeFillData>,
|
||||
side: MarketOperation,
|
||||
): OptimizedMarketOrderBase<NativeLimitOrderFillData> | OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
||||
const fillData = fill.fillData;
|
||||
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
||||
const base = {
|
||||
type: fill.type,
|
||||
source: ERC20BridgeSource.Native,
|
||||
makerToken: fillData.order.makerToken,
|
||||
takerToken: fillData.order.takerToken,
|
||||
makerAmount,
|
||||
takerAmount,
|
||||
fillData,
|
||||
fill: cleanFillForExport(fill),
|
||||
};
|
||||
return fill.type === FillQuoteTransformerOrderType.Rfq
|
||||
? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
|
||||
: { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
|
||||
}
|
||||
|
||||
export function createBridgeOrder(
|
||||
fill: Fill,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
side: MarketOperation,
|
||||
): OptimizedMarketBridgeOrder {
|
||||
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
||||
return {
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
source: fill.source,
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerAmount,
|
||||
takerAmount,
|
||||
fillData: createFinalBridgeOrderFillDataFromCollapsedFill(fill),
|
||||
fill: cleanFillForExport(fill),
|
||||
sourcePathId: fill.sourcePathId,
|
||||
};
|
||||
}
|
||||
|
||||
function cleanFillForExport(fill: Fill): Fill {
|
||||
return _.omit(fill, ['flags', 'fillData', 'sourcePathId', 'source', 'type']) as Fill;
|
||||
}
|
||||
|
||||
function createFinalBridgeOrderFillDataFromCollapsedFill(fill: Fill): FillData {
|
||||
switch (fill.source) {
|
||||
case ERC20BridgeSource.UniswapV3: {
|
||||
const fd = fill.fillData as UniswapV3FillData;
|
||||
const { uniswapPath, gasUsed } = getBestUniswapV3PathAmountForInputAmount(fd, fill.input);
|
||||
const finalFillData: FinalUniswapV3FillData = {
|
||||
router: fd.router,
|
||||
tokenAddressPath: fd.tokenAddressPath,
|
||||
uniswapPath,
|
||||
gasUsed,
|
||||
};
|
||||
return finalFillData;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return fill.fillData;
|
||||
}
|
||||
|
||||
function getBestUniswapV3PathAmountForInputAmount(
|
||||
fillData: UniswapV3FillData,
|
||||
inputAmount: BigNumber,
|
||||
): UniswapV3PathAmount {
|
||||
if (fillData.pathAmounts.length === 0) {
|
||||
throw new Error(`No Uniswap V3 paths`);
|
||||
}
|
||||
// Find the best path that can satisfy `inputAmount`.
|
||||
// Assumes `fillData.pathAmounts` is sorted ascending.
|
||||
for (const pathAmount of fillData.pathAmounts) {
|
||||
if (pathAmount.inputAmount.gte(inputAmount)) {
|
||||
return pathAmount;
|
||||
}
|
||||
}
|
||||
return fillData.pathAmounts[fillData.pathAmounts.length - 1];
|
||||
}
|
||||
|
||||
export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] {
|
||||
const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
|
||||
const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;
|
||||
return [makerToken, takerToken];
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import _ = require('lodash');
|
||||
|
||||
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 { ERC20BridgeSource, ExchangeProxyOverhead, Fill, NativeFillData, OptimizedMarketOrder } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||
|
||||
export interface PathSize {
|
||||
input: BigNumber;
|
||||
output: BigNumber;
|
||||
}
|
||||
|
||||
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 {
|
||||
public orders?: OptimizedMarketOrder[];
|
||||
public sourceFlags: bigint = BigInt(0);
|
||||
protected _size: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
||||
protected _adjustedSize: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
||||
|
||||
public static create(
|
||||
side: MarketOperation,
|
||||
fills: ReadonlyArray<Fill>,
|
||||
targetInput: BigNumber = POSITIVE_INF,
|
||||
pathPenaltyOpts: PathPenaltyOpts = DEFAULT_PATH_PENALTY_OPTS,
|
||||
): Path {
|
||||
const path = new Path(side, fills, targetInput, pathPenaltyOpts);
|
||||
fills.forEach(fill => {
|
||||
path.sourceFlags |= fill.flags;
|
||||
path._addFillSize(fill);
|
||||
});
|
||||
return path;
|
||||
}
|
||||
|
||||
protected constructor(
|
||||
protected readonly side: MarketOperation,
|
||||
public fills: ReadonlyArray<Fill>,
|
||||
protected readonly targetInput: BigNumber,
|
||||
public readonly pathPenaltyOpts: PathPenaltyOpts,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Finalizes this path, creating fillable orders with the information required
|
||||
* for settlement
|
||||
*/
|
||||
public finalize(opts: CreateOrderFromPathOpts): FinalizedPath {
|
||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
||||
this.orders = [];
|
||||
for (const fill of this.fills) {
|
||||
// internal BigInt flag field is not supported JSON and is tricky
|
||||
// to remove upstream. Since it's not needed in a FinalizedPath we just drop it.
|
||||
const normalizedFill = _.omit(fill, 'flags') as Fill;
|
||||
if (fill.source === ERC20BridgeSource.Native) {
|
||||
this.orders.push(createNativeOptimizedOrder(normalizedFill as Fill<NativeFillData>, opts.side));
|
||||
} else {
|
||||
this.orders.push(createBridgeOrder(normalizedFill, makerToken, takerToken, opts.side));
|
||||
}
|
||||
}
|
||||
return this as FinalizedPath;
|
||||
}
|
||||
|
||||
public adjustedSize(): PathSize {
|
||||
// Adjusted input/output has been adjusted by the cost of the DEX, but not by any
|
||||
// overhead added by the exchange proxy.
|
||||
const { input, output } = this._adjustedSize;
|
||||
const { exchangeProxyOverhead, outputAmountPerEth, inputAmountPerEth } = this.pathPenaltyOpts;
|
||||
// Calculate the additional penalty from the ways this path can be filled
|
||||
// by the exchange proxy, e.g VIPs (small) or FillQuoteTransformer (large)
|
||||
const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
|
||||
const pathPenalty = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: gasOverhead,
|
||||
});
|
||||
return {
|
||||
input,
|
||||
output: this.side === MarketOperation.Sell ? output.minus(pathPenalty) : output.plus(pathPenalty),
|
||||
};
|
||||
}
|
||||
|
||||
public adjustedCompleteRate(): BigNumber {
|
||||
const { input, output } = this.adjustedSize();
|
||||
return getCompleteRate(this.side, input, output, this.targetInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the rate of this path, where the output has been
|
||||
* adjusted for penalties (e.g cost)
|
||||
*/
|
||||
public adjustedRate(): BigNumber {
|
||||
const { input, output } = this.adjustedSize();
|
||||
return getRate(this.side, input, output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best possible rate this path can offer, given the fills.
|
||||
*/
|
||||
public bestRate(): BigNumber {
|
||||
const best = this.fills.reduce((prevRate, curr) => {
|
||||
const currRate = getRate(this.side, curr.input, curr.output);
|
||||
return prevRate.isLessThan(currRate) ? currRate : prevRate;
|
||||
}, new BigNumber(0));
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two paths returning if this adjusted path
|
||||
* is better than the other adjusted path
|
||||
*/
|
||||
public isAdjustedBetterThan(other: Path): boolean {
|
||||
if (!this.targetInput.isEqualTo(other.targetInput)) {
|
||||
throw new Error(`Target input mismatch: ${this.targetInput} !== ${other.targetInput}`);
|
||||
}
|
||||
const { targetInput } = this;
|
||||
const { input } = this._size;
|
||||
const { input: otherInput } = other._size;
|
||||
if (input.isLessThan(targetInput) || otherInput.isLessThan(targetInput)) {
|
||||
return input.isGreaterThan(otherInput);
|
||||
} else {
|
||||
return this.adjustedCompleteRate().isGreaterThan(other.adjustedCompleteRate());
|
||||
}
|
||||
}
|
||||
|
||||
private _addFillSize(fill: Fill): void {
|
||||
if (this._size.input.plus(fill.input).isGreaterThan(this.targetInput)) {
|
||||
const remainingInput = this.targetInput.minus(this._size.input);
|
||||
const scaledFillOutput = fill.output.times(remainingInput.div(fill.input));
|
||||
this._size.input = this.targetInput;
|
||||
this._size.output = this._size.output.plus(scaledFillOutput);
|
||||
// Penalty does not get interpolated.
|
||||
const penalty = fill.adjustedOutput.minus(fill.output);
|
||||
this._adjustedSize.input = this.targetInput;
|
||||
this._adjustedSize.output = this._adjustedSize.output.plus(scaledFillOutput).plus(penalty);
|
||||
} else {
|
||||
this._size.input = this._size.input.plus(fill.input);
|
||||
this._size.output = this._size.output.plus(fill.output);
|
||||
this._adjustedSize.input = this._adjustedSize.input.plus(fill.input);
|
||||
this._adjustedSize.output = this._adjustedSize.output.plus(fill.adjustedOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface FinalizedPath extends Path {
|
||||
readonly orders: OptimizedMarketOrder[];
|
||||
}
|
||||
@@ -1,444 +0,0 @@
|
||||
import { assert } from '@0x/assert';
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { OptimizerCapture, route, SerializedPath } from '@0x/neon-router';
|
||||
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
import { performance } from 'perf_hooks';
|
||||
|
||||
import { DEFAULT_WARNING_LOGGER } from '../../constants';
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||
|
||||
import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID, ZERO_AMOUNT } from './constants';
|
||||
import { dexSampleToFill, ethToOutputAmount, nativeOrderToFill } from './fills';
|
||||
import { Path, PathPenaltyOpts } from './path';
|
||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillAdjustor, FillData, SamplerMetrics } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of completed-docs no-bitwise
|
||||
|
||||
// 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;
|
||||
|
||||
const ONE_BASE_UNIT = new BigNumber(1);
|
||||
|
||||
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).fee || ZERO_AMOUNT;
|
||||
const outputFee = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
return outputFee;
|
||||
} else {
|
||||
const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
|
||||
const fee = fees[ERC20BridgeSource.Native]?.(sampleOrNativeOrder).fee || ZERO_AMOUNT;
|
||||
const outputFee = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
return outputFee;
|
||||
}
|
||||
}
|
||||
|
||||
function findRoutesAndCreateOptimalPath(
|
||||
side: MarketOperation,
|
||||
samples: DexSample[][],
|
||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||
input: BigNumber,
|
||||
opts: PathPenaltyOpts,
|
||||
fees: FeeSchedule,
|
||||
neonRouterNumSamples: number,
|
||||
vipSourcesSet: Set<ERC20BridgeSource>,
|
||||
fillAdjustor: FillAdjustor,
|
||||
): { allSourcesPath: Path | undefined; vipSourcesPath: Path | undefined } | undefined {
|
||||
// Currently the rust router is unable to handle 1 base unit sized quotes and will error out
|
||||
// To avoid flooding the logs with these errors we just return an insufficient liquidity error
|
||||
// which is how the JS router handles these quotes today
|
||||
if (input.isLessThanOrEqualTo(ONE_BASE_UNIT)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Create a `Fill` from a dex sample and adjust it with any passed in
|
||||
// adjustor
|
||||
const createFillFromDexSample = (sample: DexSample): Fill => {
|
||||
const fill = dexSampleToFill(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, fees);
|
||||
const adjustedFills = fillAdjustor.adjustFills(side, [fill], input);
|
||||
return adjustedFills[0];
|
||||
};
|
||||
|
||||
const createPathFromStrategy = (optimalRouteInputs: Float64Array, optimalRouteOutputs: Float64Array) => {
|
||||
/**
|
||||
* inputs are the amounts to fill at each source index
|
||||
* e.g fill 2076 at index 4
|
||||
* [ 0, 0, 0, 0, 2076, 464, 230,
|
||||
* 230, 0, 0, 0 ]
|
||||
* the sum represents the total input amount
|
||||
*
|
||||
* outputs are the amounts we expect out at each source index
|
||||
* [ 0, 0, 0, 0, 42216, 9359, 4677,
|
||||
* 4674, 0, 0, 0 ]
|
||||
* the sum represents the total expected output amount
|
||||
*/
|
||||
|
||||
const routesAndSamplesAndOutputs = _.zip(
|
||||
optimalRouteInputs,
|
||||
optimalRouteOutputs,
|
||||
samplesAndNativeOrdersWithResults,
|
||||
sampleSourcePathIds,
|
||||
);
|
||||
const adjustedFills: Fill[] = [];
|
||||
const totalRoutedAmount = BigNumber.sum(...optimalRouteInputs);
|
||||
|
||||
// Due to precision errors we can end up with a totalRoutedAmount that is not exactly equal to the input
|
||||
const precisionErrorScalar = input.dividedBy(totalRoutedAmount);
|
||||
|
||||
for (const [
|
||||
routeInput,
|
||||
outputAmount,
|
||||
routeSamplesAndNativeOrders,
|
||||
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: [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 routeInputCorrected = BigNumber.min(
|
||||
precisionErrorScalar.multipliedBy(routeInput).integerValue(BigNumber.ROUND_CEIL),
|
||||
input,
|
||||
);
|
||||
|
||||
const current = routeSamplesAndNativeOrders[routeSamplesAndNativeOrders.length - 1];
|
||||
// If it is a native single order we only have one Input/output
|
||||
// we want to convert this to an array of samples
|
||||
if (!isDexSample(current)) {
|
||||
const nativeFill = nativeOrderToFill(
|
||||
side,
|
||||
current,
|
||||
routeInputCorrected,
|
||||
opts.outputAmountPerEth,
|
||||
opts.inputAmountPerEth,
|
||||
fees,
|
||||
false,
|
||||
);
|
||||
// Note: If the order has an adjusted rate of less than or equal to 0 it will be undefined
|
||||
if (nativeFill) {
|
||||
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
|
||||
adjustedFills.push({ ...nativeFill, sourcePathId: sourcePathId ?? hexUtils.random() });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: For DexSamples only
|
||||
let fill = createFillFromDexSample(current);
|
||||
if (!fill) {
|
||||
continue;
|
||||
}
|
||||
const routeSamples = routeSamplesAndNativeOrders as Array<DexSample<FillData>>;
|
||||
|
||||
// From the output of the router, find the closest Sample in terms of input.
|
||||
// The Router may have chosen an amount to fill that we do not have a measured sample of
|
||||
// Choosing this accurately is required in some sources where the `FillData` may change depending
|
||||
// on the size of the trade. For example, UniswapV3 has variable gas cost
|
||||
// which increases with input.
|
||||
assert.assert(routeSamples.length >= 1, 'Found no sample to use for source');
|
||||
for (let k = routeSamples.length - 1; k >= 0; k--) {
|
||||
// If we're at the last remaining sample that's all we have left to use
|
||||
if (k === 0) {
|
||||
fill = createFillFromDexSample(routeSamples[0]) ?? fill;
|
||||
}
|
||||
if (routeInputCorrected.isGreaterThan(routeSamples[k].input)) {
|
||||
const left = routeSamples[k];
|
||||
const right = routeSamples[k + 1];
|
||||
if (left && right) {
|
||||
fill =
|
||||
createFillFromDexSample({
|
||||
...right, // default to the greater (for gas used)
|
||||
input: routeInputCorrected,
|
||||
output: new BigNumber(outputAmount).integerValue(),
|
||||
}) ?? fill;
|
||||
} else {
|
||||
assert.assert(Boolean(left || right), 'No valid sample to use');
|
||||
fill = createFillFromDexSample(left || right) ?? fill;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove once we have solved the rounding/precision loss issues in the Rust router
|
||||
const maxSampledOutput = BigNumber.max(...routeSamples.map(s => s.output)).integerValue();
|
||||
// Scale output by scale factor but never go above the largest sample in sell quotes (unknown liquidity) or below 1 base unit (unfillable)
|
||||
const scaleOutput = (output: BigNumber) => {
|
||||
const capped = BigNumber.min(output.integerValue(), maxSampledOutput);
|
||||
return BigNumber.max(capped, 1);
|
||||
};
|
||||
|
||||
adjustedFills.push({
|
||||
...fill,
|
||||
input: routeInputCorrected,
|
||||
output: scaleOutput(fill.output),
|
||||
adjustedOutput: scaleOutput(fill.adjustedOutput),
|
||||
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 serializedPaths: SerializedPath[] = [];
|
||||
const sampleSourcePathIds: string[] = [];
|
||||
for (const singleSourceSamples of samples) {
|
||||
if (singleSourceSamples.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const singleSourceSamplesWithOutput = [...singleSourceSamples];
|
||||
for (let i = singleSourceSamples.length - 1; i >= 0; i--) {
|
||||
const currentOutput = singleSourceSamples[i].output;
|
||||
if (currentOutput.isZero() || !currentOutput.isFinite()) {
|
||||
// Remove trailing 0/invalid output samples
|
||||
singleSourceSamplesWithOutput.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (singleSourceSamplesWithOutput.length < MIN_NUM_SAMPLE_INPUTS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Do we need to handle 0 entries, from eg Kyber?
|
||||
const serializedPath = singleSourceSamplesWithOutput.reduce<SerializedPath>(
|
||||
(memo, sample, sampleIdx) => {
|
||||
// Use the fill from createFillFromDexSample to apply
|
||||
// any user supplied adjustments
|
||||
const f = createFillFromDexSample(sample);
|
||||
memo.ids.push(`${f.source}-${serializedPaths.length}-${sampleIdx}`);
|
||||
memo.inputs.push(f.input.integerValue().toNumber());
|
||||
memo.outputs.push(f.output.integerValue().toNumber());
|
||||
// Calculate the penalty of this sample as the diff between the
|
||||
// output and the adjusted output
|
||||
const outputFee = f.output
|
||||
.minus(f.adjustedOutput)
|
||||
.absoluteValue()
|
||||
.integerValue()
|
||||
.toNumber();
|
||||
memo.outputFees.push(outputFee);
|
||||
|
||||
return memo;
|
||||
},
|
||||
{
|
||||
ids: [],
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
outputFees: [],
|
||||
isVip: vipSourcesSet.has(singleSourceSamplesWithOutput[0]?.source),
|
||||
},
|
||||
);
|
||||
|
||||
samplesAndNativeOrdersWithResults.push(singleSourceSamplesWithOutput);
|
||||
serializedPaths.push(serializedPath);
|
||||
|
||||
const sourcePathId = hexUtils.random();
|
||||
sampleSourcePathIds.push(sourcePathId);
|
||||
}
|
||||
|
||||
const nativeOrdersourcePathId = hexUtils.random();
|
||||
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;
|
||||
}
|
||||
const fee = calculateOuputFee(side, nativeOrder, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
|
||||
.integerValue()
|
||||
.toNumber();
|
||||
|
||||
// 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 = [];
|
||||
|
||||
// NOTE: Limit orders can be both larger or smaller than the input amount
|
||||
// If the order is larger than the input we can scale the order to the size of
|
||||
// the quote input (order pricing is constant) and then create 13 "samples" up to
|
||||
// and including the full quote input amount.
|
||||
// If the order is smaller we don't need to scale anything, we will just end up
|
||||
// with trailing duplicate samples for the order input as we cannot go higher
|
||||
const scaleToInput = BigNumber.min(input.dividedBy(normalizedOrderInput), 1);
|
||||
for (let i = 1; i <= 13; i++) {
|
||||
const fraction = i / 13;
|
||||
const currentInput = BigNumber.min(
|
||||
normalizedOrderInput.times(scaleToInput).times(fraction),
|
||||
normalizedOrderInput,
|
||||
);
|
||||
const currentOutput = BigNumber.min(
|
||||
normalizedOrderOutput.times(scaleToInput).times(fraction),
|
||||
normalizedOrderOutput,
|
||||
);
|
||||
const id = `${ERC20BridgeSource.Native}-${nativeOrder.type}-${serializedPaths.length}-${idx}-${i}`;
|
||||
inputs.push(currentInput.integerValue().toNumber());
|
||||
outputs.push(currentOutput.integerValue().toNumber());
|
||||
outputFees.push(fee);
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
// We have a VIP for the Rfq order type, Limit order currently goes through FQT
|
||||
const isVip = nativeOrder.type !== FillQuoteTransformerOrderType.Limit;
|
||||
|
||||
const serializedPath: SerializedPath = {
|
||||
ids,
|
||||
inputs,
|
||||
outputs,
|
||||
outputFees,
|
||||
isVip,
|
||||
};
|
||||
|
||||
samplesAndNativeOrdersWithResults.push([nativeOrder]);
|
||||
serializedPaths.push(serializedPath);
|
||||
sampleSourcePathIds.push(nativeOrdersourcePathId);
|
||||
}
|
||||
|
||||
if (serializedPaths.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const rustArgs: OptimizerCapture = {
|
||||
side,
|
||||
targetInput: input.toNumber(),
|
||||
pathsIn: serializedPaths,
|
||||
};
|
||||
|
||||
const allSourcesRustRoute = new Float64Array(rustArgs.pathsIn.length);
|
||||
const allSourcesOutputAmounts = new Float64Array(rustArgs.pathsIn.length);
|
||||
const vipSourcesRustRoute = new Float64Array(rustArgs.pathsIn.length);
|
||||
const vipSourcesOutputAmounts = new Float64Array(rustArgs.pathsIn.length);
|
||||
|
||||
route(
|
||||
rustArgs,
|
||||
allSourcesRustRoute,
|
||||
allSourcesOutputAmounts,
|
||||
vipSourcesRustRoute,
|
||||
vipSourcesOutputAmounts,
|
||||
neonRouterNumSamples,
|
||||
);
|
||||
assert.assert(
|
||||
rustArgs.pathsIn.length === allSourcesRustRoute.length,
|
||||
'different number of sources in the Router output than the input',
|
||||
);
|
||||
assert.assert(
|
||||
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',
|
||||
);
|
||||
|
||||
const allSourcesPath = createPathFromStrategy(allSourcesRustRoute, allSourcesOutputAmounts);
|
||||
const vipSourcesPath = createPathFromStrategy(vipSourcesRustRoute, vipSourcesOutputAmounts);
|
||||
|
||||
return {
|
||||
allSourcesPath,
|
||||
vipSourcesPath,
|
||||
};
|
||||
}
|
||||
|
||||
export function findOptimalPathFromSamples(
|
||||
side: MarketOperation,
|
||||
samples: DexSample[][],
|
||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||
input: BigNumber,
|
||||
opts: PathPenaltyOpts,
|
||||
fees: FeeSchedule,
|
||||
chainId: ChainId,
|
||||
neonRouterNumSamples: number,
|
||||
fillAdjustor: FillAdjustor,
|
||||
samplerMetrics?: SamplerMetrics,
|
||||
): Path | undefined {
|
||||
const beforeTimeMs = performance.now();
|
||||
const sendMetrics = () => {
|
||||
// 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,
|
||||
samples,
|
||||
nativeOrders,
|
||||
input,
|
||||
opts,
|
||||
fees,
|
||||
neonRouterNumSamples,
|
||||
vipSourcesSet,
|
||||
fillAdjustor,
|
||||
);
|
||||
|
||||
if (!paths) {
|
||||
sendMetrics();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { allSourcesPath, vipSourcesPath } = paths;
|
||||
|
||||
if (!allSourcesPath || vipSourcesPath?.isAdjustedBetterThan(allSourcesPath)) {
|
||||
sendMetrics();
|
||||
return vipSourcesPath;
|
||||
}
|
||||
|
||||
sendMetrics();
|
||||
return allSourcesPath;
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { getPoolsWithTokens, parsePoolData } from 'balancer-labs-sor-v1';
|
||||
import { Pool } from 'balancer-labs-sor-v1/dist/types';
|
||||
import { gql, request } from 'graphql-request';
|
||||
|
||||
import { DEFAULT_WARNING_LOGGER } from '../../../constants';
|
||||
import { LogFunction } from '../../../types';
|
||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_TOP_POOLS_FETCHED } from '../constants';
|
||||
|
||||
import { NoOpPoolsCache } from './no_op_pools_cache';
|
||||
import { AbstractPoolsCache, CacheValue, PoolsCache } from './pools_cache';
|
||||
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
// tslint:disable: member-ordering
|
||||
|
||||
const BALANCER_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer';
|
||||
|
||||
interface BalancerPoolResponse {
|
||||
id: string;
|
||||
swapFee: string;
|
||||
tokens: Array<{ address: string; decimals: number; balance: string }>;
|
||||
tokensList: string[];
|
||||
totalWeight: string;
|
||||
}
|
||||
|
||||
export class BalancerPoolsCache extends AbstractPoolsCache {
|
||||
public static create(chainId: ChainId): PoolsCache {
|
||||
if (chainId !== ChainId.Mainnet) {
|
||||
return new NoOpPoolsCache();
|
||||
}
|
||||
|
||||
return new BalancerPoolsCache();
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly _subgraphUrl: string = BALANCER_SUBGRAPH_URL,
|
||||
cache: Map<string, CacheValue> = new Map(),
|
||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
||||
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
||||
) {
|
||||
super(cache);
|
||||
void this._loadTopPoolsAsync();
|
||||
// Reload the top pools every 12 hours
|
||||
setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2);
|
||||
}
|
||||
|
||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
try {
|
||||
const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
|
||||
// Sort by maker token balance (descending)
|
||||
const pools = parsePoolData(poolData, takerToken, makerToken).sort((a, b) =>
|
||||
b.balanceOut.minus(a.balanceOut).toNumber(),
|
||||
);
|
||||
return pools.length > this.maxPoolsFetched ? pools.slice(0, this.maxPoolsFetched) : pools;
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
protected async _loadTopPoolsAsync(): Promise<void> {
|
||||
const fromToPools: {
|
||||
[from: string]: { [to: string]: Pool[] };
|
||||
} = {};
|
||||
|
||||
let pools: BalancerPoolResponse[];
|
||||
try {
|
||||
pools = await this._fetchTopPoolsAsync();
|
||||
} catch (err) {
|
||||
this._warningLogger(err, 'Failed to fetch top pools for Balancer V1');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const pool of pools) {
|
||||
const { tokensList } = pool;
|
||||
for (const from of tokensList) {
|
||||
for (const to of tokensList.filter(t => t.toLowerCase() !== from.toLowerCase())) {
|
||||
fromToPools[from] = fromToPools[from] || {};
|
||||
fromToPools[from][to] = fromToPools[from][to] || [];
|
||||
|
||||
try {
|
||||
// The list of pools must be relevant to `from` and `to` for `parsePoolData`
|
||||
const poolData = parsePoolData([pool], from, to);
|
||||
fromToPools[from][to].push(poolData[0]);
|
||||
// Cache this as we progress through
|
||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
||||
this._cachePoolsForPair(from, to, fromToPools[from][to], expiresAt);
|
||||
} catch {
|
||||
// soldier on
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async _fetchTopPoolsAsync(): Promise<BalancerPoolResponse[]> {
|
||||
const query = gql`
|
||||
query fetchTopPools($topPoolsFetched: Int!) {
|
||||
pools(
|
||||
first: $topPoolsFetched
|
||||
where: { publicSwap: true, liquidity_gt: 0 }
|
||||
orderBy: swapsCount
|
||||
orderDirection: desc
|
||||
) {
|
||||
id
|
||||
publicSwap
|
||||
swapFee
|
||||
totalWeight
|
||||
tokensList
|
||||
tokens {
|
||||
id
|
||||
address
|
||||
balance
|
||||
decimals
|
||||
symbol
|
||||
denormWeight
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
try {
|
||||
const { pools } = await request(this._subgraphUrl, query, { topPoolsFetched: this._topPoolsFetched });
|
||||
return pools;
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
/**
|
||||
* This has been copied from https://github.com/balancer-labs/balancer-sor/blob/john/rc2/src/helpers.ts.
|
||||
* Still awaiting V2 support for @balancer-labs/sor, once full V2 support is shipped we can upgrade sor and delete this file
|
||||
*/
|
||||
export const parsePoolData = (
|
||||
directPools: SubGraphPoolDictionary,
|
||||
tokenIn: string,
|
||||
tokenOut: string,
|
||||
mostLiquidPoolsFirstHop: SubGraphPool[] = [],
|
||||
mostLiquidPoolsSecondHop: SubGraphPool[] = [],
|
||||
hopTokens: string[] = [],
|
||||
): [SubGraphPoolDictionary, Path[]] => {
|
||||
const pathDataList: Path[] = [];
|
||||
const pools: SubGraphPoolDictionary = {};
|
||||
|
||||
// First add direct pair paths
|
||||
// tslint:disable-next-line:forin
|
||||
for (const idKey in directPools) {
|
||||
const p: SubGraphPool = directPools[idKey];
|
||||
// Add pool to the set with all pools (only adds if it's still not present in dict)
|
||||
pools[idKey] = p;
|
||||
|
||||
const swap: Swap = {
|
||||
pool: p.id,
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
tokenInDecimals: 18, // Placeholder for actual decimals
|
||||
tokenOutDecimals: 18,
|
||||
};
|
||||
|
||||
const path: Path = {
|
||||
id: p.id,
|
||||
swaps: [swap],
|
||||
};
|
||||
pathDataList.push(path);
|
||||
}
|
||||
|
||||
// Now add multi-hop paths.
|
||||
// mostLiquidPoolsFirstHop and mostLiquidPoolsSecondHop always has the same
|
||||
// lengh of hopTokens
|
||||
for (let i = 0; i < hopTokens.length; i++) {
|
||||
// Add pools to the set with all pools (only adds if it's still not present in dict)
|
||||
pools[mostLiquidPoolsFirstHop[i].id] = mostLiquidPoolsFirstHop[i];
|
||||
pools[mostLiquidPoolsSecondHop[i].id] = mostLiquidPoolsSecondHop[i];
|
||||
|
||||
const swap1: Swap = {
|
||||
pool: mostLiquidPoolsFirstHop[i].id,
|
||||
tokenIn,
|
||||
tokenOut: hopTokens[i],
|
||||
tokenInDecimals: 18, // Placeholder for actual decimals
|
||||
tokenOutDecimals: 18,
|
||||
};
|
||||
|
||||
const swap2: Swap = {
|
||||
pool: mostLiquidPoolsSecondHop[i].id,
|
||||
tokenIn: hopTokens[i],
|
||||
tokenOut,
|
||||
tokenInDecimals: 18, // Placeholder for actual decimals
|
||||
tokenOutDecimals: 18,
|
||||
};
|
||||
|
||||
const path: Path = {
|
||||
id: mostLiquidPoolsFirstHop[i].id + mostLiquidPoolsSecondHop[i].id, // Path id is the concatenation of the ids of poolFirstHop and poolSecondHop
|
||||
swaps: [swap1, swap2],
|
||||
};
|
||||
pathDataList.push(path);
|
||||
}
|
||||
return [pools, pathDataList];
|
||||
};
|
||||
|
||||
interface SubGraphPool {
|
||||
id: string;
|
||||
swapFee: string;
|
||||
totalWeight: string;
|
||||
totalShares: string;
|
||||
tokens: SubGraphToken[];
|
||||
tokensList: string[];
|
||||
poolType?: string;
|
||||
|
||||
// Only for stable pools
|
||||
amp: string;
|
||||
|
||||
// Only for element pools
|
||||
lpShares?: BigNumber;
|
||||
time?: BigNumber;
|
||||
principalToken?: string;
|
||||
baseToken?: string;
|
||||
}
|
||||
|
||||
interface SubGraphPoolDictionary {
|
||||
[poolId: string]: SubGraphPool;
|
||||
}
|
||||
|
||||
interface SubGraphToken {
|
||||
address: string;
|
||||
balance: string;
|
||||
decimals: string | number;
|
||||
// Stable & Element field
|
||||
weight?: string;
|
||||
}
|
||||
interface Path {
|
||||
id: string; // pool address if direct path, contactenation of pool addresses if multihop
|
||||
swaps: Swap[];
|
||||
poolPairData?: PoolPairData[];
|
||||
limitAmount?: BigNumber;
|
||||
filterEffectivePrice?: BigNumber; // TODO: This is just used for filtering, maybe there is a better way to filter?
|
||||
}
|
||||
|
||||
interface Swap {
|
||||
pool: string;
|
||||
tokenIn: string;
|
||||
tokenOut: string;
|
||||
swapAmount?: string;
|
||||
limitReturnAmount?: string;
|
||||
maxPrice?: string;
|
||||
tokenInDecimals: number;
|
||||
tokenOutDecimals: number;
|
||||
}
|
||||
|
||||
export interface PoolPairData {
|
||||
id: string;
|
||||
poolType?: string; // Todo: make this a mandatory field?
|
||||
pairType?: string; // Todo: make this a mandatory field?
|
||||
tokenIn: string;
|
||||
tokenOut: string;
|
||||
balanceIn?: BigNumber;
|
||||
balanceOut?: BigNumber;
|
||||
decimalsIn: number;
|
||||
decimalsOut: number;
|
||||
swapFee: BigNumber;
|
||||
|
||||
// For weighted & element pools
|
||||
weightIn?: BigNumber;
|
||||
weightOut?: BigNumber;
|
||||
|
||||
// Only for stable pools
|
||||
allBalances: BigNumber[];
|
||||
invariant?: BigNumber;
|
||||
amp?: BigNumber;
|
||||
tokenIndexIn?: number;
|
||||
tokenIndexOut?: number;
|
||||
|
||||
// Only for element pools
|
||||
lpShares?: BigNumber;
|
||||
time?: BigNumber;
|
||||
principalToken?: string;
|
||||
baseToken?: string;
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
// import { parsePoolData } from '@balancer-labs'; // TODO - upgrade to v2
|
||||
import { Pool } from 'balancer-labs-sor-v1/dist/types';
|
||||
import { gql, request } from 'graphql-request';
|
||||
|
||||
import { DEFAULT_WARNING_LOGGER } from '../../../constants';
|
||||
import { LogFunction } from '../../../types';
|
||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_TOP_POOLS_FETCHED } from '../constants';
|
||||
|
||||
import { parsePoolData } from './balancer_sor_v2';
|
||||
import { NoOpPoolsCache } from './no_op_pools_cache';
|
||||
import { AbstractPoolsCache, CacheValue, PoolsCache } from './pools_cache';
|
||||
|
||||
// tslint:disable: member-ordering
|
||||
|
||||
const BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN = new Map<ChainId, string>([
|
||||
[ChainId.Fantom, 'https://api.thegraph.com/subgraphs/name/beethovenxfi/beethovenx'],
|
||||
]);
|
||||
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
interface BalancerPoolResponse {
|
||||
id: string;
|
||||
swapFee: string;
|
||||
tokens: Array<{ address: string; decimals: number; balance: string; weight: string; symbol: string }>;
|
||||
tokensList: string[];
|
||||
totalWeight: string;
|
||||
totalShares: string;
|
||||
amp: string | null;
|
||||
}
|
||||
|
||||
export class BalancerV2PoolsCache extends AbstractPoolsCache {
|
||||
public static createBeethovenXPoolCache(chainId: ChainId): PoolsCache {
|
||||
const subgraphUrl = BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN.get(chainId);
|
||||
if (subgraphUrl === undefined) {
|
||||
return new NoOpPoolsCache();
|
||||
}
|
||||
|
||||
return new BalancerV2PoolsCache(subgraphUrl);
|
||||
}
|
||||
|
||||
private static _parseSubgraphPoolData(pool: any, takerToken: string, makerToken: string): Pool {
|
||||
const tToken = pool.tokens.find((t: any) => t.address === takerToken);
|
||||
const mToken = pool.tokens.find((t: any) => t.address === makerToken);
|
||||
const swap = pool.swaps && pool.swaps[0];
|
||||
const tokenAmountOut = swap ? swap.tokenAmountOut : undefined;
|
||||
const tokenAmountIn = swap ? swap.tokenAmountIn : undefined;
|
||||
const spotPrice =
|
||||
tokenAmountOut && tokenAmountIn ? new BigNumber(tokenAmountOut).div(tokenAmountIn) : undefined; // TODO: xianny check
|
||||
|
||||
return {
|
||||
id: pool.id,
|
||||
balanceIn: new BigNumber(tToken.balance),
|
||||
balanceOut: new BigNumber(mToken.balance),
|
||||
weightIn: new BigNumber(tToken.weight),
|
||||
weightOut: new BigNumber(mToken.weight),
|
||||
swapFee: new BigNumber(pool.swapFee),
|
||||
spotPrice,
|
||||
};
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly subgraphUrl: string,
|
||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
||||
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
||||
cache: Map<string, CacheValue> = new Map(),
|
||||
) {
|
||||
super(cache);
|
||||
void this._loadTopPoolsAsync();
|
||||
// Reload the top pools every 12 hours
|
||||
setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2);
|
||||
}
|
||||
|
||||
protected async _fetchTopPoolsAsync(): Promise<BalancerPoolResponse[]> {
|
||||
const query = gql`
|
||||
query fetchTopPools($topPoolsFetched: Int!) {
|
||||
pools(
|
||||
first: $topPoolsFetched
|
||||
where: { totalLiquidity_gt: 0 }
|
||||
orderBy: swapsCount
|
||||
orderDirection: desc
|
||||
) {
|
||||
id
|
||||
swapFee
|
||||
totalWeight
|
||||
tokensList
|
||||
amp
|
||||
totalShares
|
||||
tokens {
|
||||
id
|
||||
address
|
||||
balance
|
||||
decimals
|
||||
symbol
|
||||
weight
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const { pools } = await request<{ pools: BalancerPoolResponse[] }>(this.subgraphUrl, query, {
|
||||
topPoolsFetched: this._topPoolsFetched,
|
||||
});
|
||||
|
||||
return pools;
|
||||
}
|
||||
protected async _loadTopPoolsAsync(): Promise<void> {
|
||||
const fromToPools: {
|
||||
[from: string]: { [to: string]: Pool[] };
|
||||
} = {};
|
||||
|
||||
let pools: BalancerPoolResponse[];
|
||||
try {
|
||||
pools = await this._fetchTopPoolsAsync();
|
||||
} catch (err) {
|
||||
this._warningLogger(err, 'Failed to fetch top pools for Balancer V2');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const pool of pools) {
|
||||
const { tokensList } = pool;
|
||||
for (const from of tokensList) {
|
||||
for (const to of tokensList.filter(t => t.toLowerCase() !== from.toLowerCase())) {
|
||||
fromToPools[from] = fromToPools[from] || {};
|
||||
fromToPools[from][to] = fromToPools[from][to] || [];
|
||||
|
||||
try {
|
||||
// The list of pools must be relevant to `from` and `to` for `parsePoolData`
|
||||
const [poolData] = parsePoolData({ [pool.id]: pool as any }, from, to);
|
||||
fromToPools[from][to].push(
|
||||
BalancerV2PoolsCache._parseSubgraphPoolData(poolData[pool.id], from, to),
|
||||
);
|
||||
// Cache this as we progress through
|
||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
||||
this._cachePoolsForPair(from, to, fromToPools[from][to], expiresAt);
|
||||
} catch (err) {
|
||||
this._warningLogger(err, `Failed to load Balancer V2 top pools`);
|
||||
// soldier on
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
const query = gql`
|
||||
query getPools {
|
||||
pools(
|
||||
first: ${this.maxPoolsFetched},
|
||||
where: {
|
||||
tokensList_contains: ["${takerToken}", "${makerToken}"]
|
||||
}
|
||||
) {
|
||||
id
|
||||
tokens {
|
||||
address
|
||||
balance
|
||||
weight
|
||||
}
|
||||
swapFee
|
||||
swaps(
|
||||
orderBy: timestamp, orderDirection: desc, first: 1,
|
||||
where:{
|
||||
tokenIn: "${takerToken}",
|
||||
tokenOut: "${makerToken}"
|
||||
}
|
||||
) {
|
||||
tokenAmountIn
|
||||
tokenAmountOut
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
try {
|
||||
const { pools } = await request(this.subgraphUrl, query);
|
||||
return pools.map((pool: any) => BalancerV2PoolsCache._parseSubgraphPoolData(pool, takerToken, makerToken));
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import {
|
||||
BalancerSDK,
|
||||
BalancerSdkConfig,
|
||||
formatSequence,
|
||||
getTokenAddressesForSwap,
|
||||
NewPath,
|
||||
parseToPoolsDict,
|
||||
PoolDictionary,
|
||||
RouteProposer,
|
||||
SwapTypes,
|
||||
} from '@balancer-labs/sdk';
|
||||
|
||||
import { DEFAULT_WARNING_LOGGER } from '../../../constants';
|
||||
import { LogFunction } from '../../../types';
|
||||
import { BALANCER_V2_SUBGRAPH_URL_BY_CHAIN, ONE_SECOND_MS } from '../constants';
|
||||
import { BalancerSwapInfo, BalancerSwaps } from '../types';
|
||||
|
||||
import { CacheValue, EMPTY_BALANCER_SWAPS, SwapInfoCache } from './pair_swaps_cache';
|
||||
import { SubgraphPoolDataService } from './sgPoolDataService';
|
||||
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * ONE_SECOND_MS;
|
||||
|
||||
export interface BalancerPoolResponse {
|
||||
poolType: string;
|
||||
id: string;
|
||||
tokens: Array<{ address: string }>;
|
||||
tokensList: string[];
|
||||
}
|
||||
|
||||
export class BalancerV2SwapInfoCache extends SwapInfoCache {
|
||||
private static readonly _MAX_POOLS_PER_PATH = 4;
|
||||
private static readonly _MAX_CANDIDATE_PATHS_PER_PAIR = 2;
|
||||
private readonly _routeProposer: RouteProposer;
|
||||
private readonly _poolDataService: SubgraphPoolDataService;
|
||||
|
||||
constructor(
|
||||
chainId: ChainId,
|
||||
subgraphUrl: string | null = BALANCER_V2_SUBGRAPH_URL_BY_CHAIN[chainId],
|
||||
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
||||
cache: { [key: string]: CacheValue } = {},
|
||||
) {
|
||||
super(cache);
|
||||
const config: BalancerSdkConfig = {
|
||||
network: chainId as number, // wtf TS
|
||||
rpcUrl: '', // Not actually used by SDK for this.
|
||||
};
|
||||
const balancerSdk = new BalancerSDK(config);
|
||||
// The RouteProposer finds paths between a token pair using direct/multihop/linearPool routes
|
||||
this._routeProposer = balancerSdk.sor.routeProposer;
|
||||
// Uses Subgraph to retrieve up to date pool data required for routeProposer
|
||||
this._poolDataService = new SubgraphPoolDataService({
|
||||
chainId,
|
||||
subgraphUrl,
|
||||
});
|
||||
void this._loadTopPoolsAsync();
|
||||
// Reload the top pools every 12 hours
|
||||
setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2);
|
||||
}
|
||||
|
||||
protected async _loadTopPoolsAsync(): Promise<void> {
|
||||
const fromToSwapInfo: {
|
||||
[from: string]: { [to: string]: BalancerSwaps };
|
||||
} = {};
|
||||
|
||||
// Retrieve pool data from Subgraph
|
||||
const pools = await this._poolDataService.getPools();
|
||||
// timestamp is used for Element pools
|
||||
const timestamp = Math.floor(Date.now() / ONE_SECOND_MS);
|
||||
const poolsDict = parseToPoolsDict(pools, timestamp);
|
||||
|
||||
for (const pool of pools) {
|
||||
const { tokensList } = pool;
|
||||
// tslint:disable-next-line: await-promise
|
||||
await null; // This loop can be CPU heavy so yield to event loop.
|
||||
for (const from of tokensList) {
|
||||
for (const to of tokensList.filter(t => t.toLowerCase() !== from.toLowerCase())) {
|
||||
fromToSwapInfo[from] = fromToSwapInfo[from] || {};
|
||||
// If a record for pair already exists skip as all paths alreay found
|
||||
if (fromToSwapInfo[from][to]) {
|
||||
continue;
|
||||
} else {
|
||||
try {
|
||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
||||
// Retrieve swap steps and assets for a token pair
|
||||
// This only needs to be called once per pair as all paths will be created from single call
|
||||
const pairSwapInfo = this._getPoolPairSwapInfo(poolsDict, from, to);
|
||||
fromToSwapInfo[from][to] = pairSwapInfo;
|
||||
this._cacheSwapInfoForPair(from, to, fromToSwapInfo[from][to], expiresAt);
|
||||
} catch (err) {
|
||||
this._warningLogger(err, `Failed to load Balancer V2 top pools`);
|
||||
// soldier on
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will retrieve fresh pair and path data from Subgraph and return and array of swap info for pair..
|
||||
* @param takerToken Address of takerToken.
|
||||
* @param makerToken Address of makerToken.
|
||||
* @returns Swap data for pair consisting of assets and swap steps for ExactIn and ExactOut swap types.
|
||||
*/
|
||||
protected async _fetchSwapInfoForPairAsync(takerToken: string, makerToken: string): Promise<BalancerSwaps> {
|
||||
try {
|
||||
// retrieve up to date pools from SG
|
||||
const pools = await this._poolDataService.getPools();
|
||||
|
||||
// timestamp is used for Element pools
|
||||
const timestamp = Math.floor(Date.now() / ONE_SECOND_MS);
|
||||
const poolDictionary = parseToPoolsDict(pools, timestamp);
|
||||
return this._getPoolPairSwapInfo(poolDictionary, takerToken, makerToken);
|
||||
} catch (e) {
|
||||
return EMPTY_BALANCER_SWAPS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses pool data from provided dictionary to find top swap paths for token pair.
|
||||
* @param pools Dictionary of pool data.
|
||||
* @param takerToken Address of taker token.
|
||||
* @param makerToken Address of maker token.
|
||||
* @returns Swap data for pair consisting of assets and swap steps for ExactIn and ExactOut swap types.
|
||||
*/
|
||||
private _getPoolPairSwapInfo(pools: PoolDictionary, takerToken: string, makerToken: string): BalancerSwaps {
|
||||
/*
|
||||
Uses Balancer SDK to construct available paths for pair.
|
||||
Paths can be direct, i.e. both tokens are in same pool or multihop.
|
||||
Will also create paths for the new Balancer Linear pools.
|
||||
These are returned in order of available liquidity which is useful for filtering.
|
||||
*/
|
||||
const paths = this._routeProposer.getCandidatePathsFromDict(
|
||||
takerToken,
|
||||
makerToken,
|
||||
SwapTypes.SwapExactIn,
|
||||
pools,
|
||||
BalancerV2SwapInfoCache._MAX_POOLS_PER_PATH,
|
||||
);
|
||||
|
||||
if (paths.length === 0) {
|
||||
return EMPTY_BALANCER_SWAPS;
|
||||
}
|
||||
|
||||
// Convert paths data to swap information suitable for queryBatchSwap. Only use top 2 liquid paths
|
||||
return formatSwaps(paths.slice(0, BalancerV2SwapInfoCache._MAX_CANDIDATE_PATHS_PER_PAIR));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of Balancer paths, returns swap information that can be passed to queryBatchSwap.
|
||||
* @param paths Array of Balancer paths.
|
||||
* @returns Formatted swap data consisting of assets and swap steps for ExactIn and ExactOut swap types.
|
||||
*/
|
||||
function formatSwaps(paths: NewPath[]): BalancerSwaps {
|
||||
const formattedSwapsExactIn: BalancerSwapInfo[] = [];
|
||||
const formattedSwapsExactOut: BalancerSwapInfo[] = [];
|
||||
let assets: string[];
|
||||
paths.forEach(path => {
|
||||
// Add a swap amount for each swap so we can use formatSequence. (This will be overwritten with actual amount during query)
|
||||
path.swaps.forEach(s => (s.swapAmount = '0'));
|
||||
const tokenAddresses = getTokenAddressesForSwap(path.swaps);
|
||||
// Formats for both ExactIn and ExactOut swap types
|
||||
const swapsExactIn = formatSequence(SwapTypes.SwapExactIn, path.swaps, tokenAddresses);
|
||||
const swapsExactOut = formatSequence(SwapTypes.SwapExactOut, path.swaps, tokenAddresses);
|
||||
assets = tokenAddresses;
|
||||
formattedSwapsExactIn.push({
|
||||
assets,
|
||||
swapSteps: swapsExactIn.map(s => ({
|
||||
...s,
|
||||
amount: new BigNumber(s.amount),
|
||||
})),
|
||||
});
|
||||
formattedSwapsExactOut.push({
|
||||
assets,
|
||||
swapSteps: swapsExactOut.map(s => ({
|
||||
...s,
|
||||
amount: new BigNumber(s.amount),
|
||||
})),
|
||||
});
|
||||
});
|
||||
const formattedSwaps: BalancerSwaps = {
|
||||
swapInfoExactIn: formattedSwapsExactIn,
|
||||
swapInfoExactOut: formattedSwapsExactOut,
|
||||
};
|
||||
return formattedSwaps;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export { BalancerPoolsCache } from './balancer_pools_cache';
|
||||
export { BalancerV2PoolsCache } from './balancer_v2_pools_cache';
|
||||
export { AbstractPoolsCache, PoolsCache } from './pools_cache';
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Pool, PoolsCache } from './pools_cache';
|
||||
|
||||
// tslint:disable:prefer-function-over-method
|
||||
|
||||
export class NoOpPoolsCache implements PoolsCache {
|
||||
public async getFreshPoolsForPairAsync(
|
||||
_takerToken: string,
|
||||
_makerToken: string,
|
||||
_timeoutMs?: number | undefined,
|
||||
): Promise<Pool[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
public getPoolAddressesForPair(_takerToken: string, _makerToken: string): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public isFresh(_takerToken: string, _makerToken: string): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { ONE_HOUR_IN_SECONDS, ONE_SECOND_MS } from '../constants';
|
||||
import { BalancerSwaps } from '../types';
|
||||
|
||||
export interface CacheValue {
|
||||
expiresAt: number;
|
||||
balancerSwaps: BalancerSwaps;
|
||||
}
|
||||
|
||||
// Cache results for 30mins
|
||||
const DEFAULT_CACHE_TIME_MS = (ONE_HOUR_IN_SECONDS / 2) * ONE_SECOND_MS;
|
||||
const DEFAULT_TIMEOUT_MS = ONE_SECOND_MS;
|
||||
export const EMPTY_BALANCER_SWAPS = { swapInfoExactIn: [], swapInfoExactOut: [] };
|
||||
|
||||
/**
|
||||
* Caches SwapInfo for a pair of tokens.
|
||||
* SwapInfo includes swap steps and asset information for those swap steps.
|
||||
*/
|
||||
export abstract class SwapInfoCache {
|
||||
protected static _isExpired(value: CacheValue): boolean {
|
||||
return Date.now() >= value.expiresAt;
|
||||
}
|
||||
constructor(
|
||||
protected readonly _cache: { [key: string]: CacheValue },
|
||||
protected readonly _cacheTimeMs: number = DEFAULT_CACHE_TIME_MS,
|
||||
) {}
|
||||
|
||||
public async getFreshPoolsForPairAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
timeoutMs: number = DEFAULT_TIMEOUT_MS,
|
||||
): Promise<BalancerSwaps> {
|
||||
const timeout = new Promise<BalancerSwaps>(resolve => setTimeout(resolve, timeoutMs, []));
|
||||
return Promise.race([this._getAndSaveFreshSwapInfoForPairAsync(takerToken, makerToken), timeout]);
|
||||
}
|
||||
|
||||
public getCachedSwapInfoForPair(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
ignoreExpired: boolean = true,
|
||||
): BalancerSwaps | undefined {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
if (ignoreExpired) {
|
||||
return value === undefined ? EMPTY_BALANCER_SWAPS : value.balancerSwaps;
|
||||
}
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
if (SwapInfoCache._isExpired(value)) {
|
||||
return undefined;
|
||||
}
|
||||
return value.balancerSwaps;
|
||||
}
|
||||
|
||||
public isFresh(takerToken: string, makerToken: string): boolean {
|
||||
const cached = this.getCachedSwapInfoForPair(takerToken, makerToken, false);
|
||||
return cached !== undefined;
|
||||
}
|
||||
|
||||
protected async _getAndSaveFreshSwapInfoForPairAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
): Promise<BalancerSwaps> {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
if (value === undefined || value.expiresAt >= Date.now()) {
|
||||
const swapInfo = await this._fetchSwapInfoForPairAsync(takerToken, makerToken);
|
||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
||||
this._cacheSwapInfoForPair(takerToken, makerToken, swapInfo, expiresAt);
|
||||
}
|
||||
return this._cache[key].balancerSwaps;
|
||||
}
|
||||
|
||||
protected _cacheSwapInfoForPair(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
swapInfo: BalancerSwaps,
|
||||
expiresAt: number,
|
||||
): void {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
this._cache[key] = {
|
||||
expiresAt,
|
||||
balancerSwaps: swapInfo,
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract _fetchSwapInfoForPairAsync(takerToken: string, makerToken: string): Promise<BalancerSwaps>;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { Pool } from 'balancer-labs-sor-v1/dist/types';
|
||||
|
||||
import { ONE_HOUR_IN_SECONDS, ONE_SECOND_MS } from '../constants';
|
||||
export { Pool };
|
||||
export interface CacheValue {
|
||||
expiresAt: number;
|
||||
pools: Pool[];
|
||||
}
|
||||
|
||||
// Cache results for 30mins
|
||||
const DEFAULT_CACHE_TIME_MS = (ONE_HOUR_IN_SECONDS / 2) * ONE_SECOND_MS;
|
||||
const DEFAULT_TIMEOUT_MS = 3000;
|
||||
|
||||
export interface PoolsCache {
|
||||
getFreshPoolsForPairAsync(takerToken: string, makerToken: string, timeoutMs?: number): Promise<Pool[]>;
|
||||
getPoolAddressesForPair(takerToken: string, makerToken: string): string[];
|
||||
isFresh(takerToken: string, makerToken: string): boolean;
|
||||
}
|
||||
|
||||
export abstract class AbstractPoolsCache implements PoolsCache {
|
||||
protected static _getKey(takerToken: string, makerToken: string): string {
|
||||
return `${takerToken}-${makerToken}`;
|
||||
}
|
||||
|
||||
protected static _isExpired(value: CacheValue | undefined): boolean {
|
||||
if (value === undefined) {
|
||||
return true;
|
||||
}
|
||||
return Date.now() >= value.expiresAt;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected readonly _cache: Map<string, CacheValue>,
|
||||
protected readonly _cacheTimeMs: number = DEFAULT_CACHE_TIME_MS,
|
||||
) {}
|
||||
|
||||
public async getFreshPoolsForPairAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
timeoutMs: number = DEFAULT_TIMEOUT_MS,
|
||||
): Promise<Pool[]> {
|
||||
const timeout = new Promise<Pool[]>(resolve => setTimeout(resolve, timeoutMs, []));
|
||||
return Promise.race([this._getAndSaveFreshPoolsForPairAsync(takerToken, makerToken), timeout]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pool addresses (can be stale) for a pair.
|
||||
*
|
||||
* An empty array will be returned if cache does not exist.
|
||||
*/
|
||||
public getPoolAddressesForPair(takerToken: string, makerToken: string): string[] {
|
||||
const value = this._getValue(takerToken, makerToken);
|
||||
return value === undefined ? [] : value.pools.map(pool => pool.id);
|
||||
}
|
||||
|
||||
public isFresh(takerToken: string, makerToken: string): boolean {
|
||||
const value = this._getValue(takerToken, makerToken);
|
||||
return !AbstractPoolsCache._isExpired(value);
|
||||
}
|
||||
|
||||
protected _getValue(takerToken: string, makerToken: string): CacheValue | undefined {
|
||||
const key = AbstractPoolsCache._getKey(takerToken, makerToken);
|
||||
return this._cache.get(key);
|
||||
}
|
||||
|
||||
protected async _getAndSaveFreshPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
const key = AbstractPoolsCache._getKey(takerToken, makerToken);
|
||||
const value = this._cache.get(key);
|
||||
if (!AbstractPoolsCache._isExpired(value)) {
|
||||
return value!.pools;
|
||||
}
|
||||
|
||||
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
|
||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
||||
this._cachePoolsForPair(takerToken, makerToken, pools, expiresAt);
|
||||
return pools;
|
||||
}
|
||||
|
||||
protected _cachePoolsForPair(takerToken: string, makerToken: string, pools: Pool[], expiresAt: number): void {
|
||||
const key = AbstractPoolsCache._getKey(takerToken, makerToken);
|
||||
this._cache.set(key, { pools, expiresAt });
|
||||
}
|
||||
|
||||
protected abstract _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]>;
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { logUtils } from '@0x/utils';
|
||||
import { PoolDataService, SubgraphPoolBase } from '@balancer-labs/sdk';
|
||||
import { gql, request } from 'graphql-request';
|
||||
|
||||
const queryWithLinear = gql`
|
||||
query fetchTopPoolsWithLinear($maxPoolsFetched: Int!) {
|
||||
pools: pools(
|
||||
first: $maxPoolsFetched
|
||||
where: { swapEnabled: true }
|
||||
orderBy: totalLiquidity
|
||||
orderDirection: desc
|
||||
) {
|
||||
id
|
||||
address
|
||||
poolType
|
||||
swapFee
|
||||
totalShares
|
||||
tokens {
|
||||
address
|
||||
balance
|
||||
decimals
|
||||
weight
|
||||
priceRate
|
||||
}
|
||||
tokensList
|
||||
totalWeight
|
||||
amp
|
||||
expiryTime
|
||||
unitSeconds
|
||||
principalToken
|
||||
baseToken
|
||||
swapEnabled
|
||||
wrappedIndex
|
||||
mainIndex
|
||||
lowerTarget
|
||||
upperTarget
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const queryWithOutLinear = gql`
|
||||
query fetchTopPoolsWithoutLinear($maxPoolsFetched: Int!) {
|
||||
pools: pools(
|
||||
first: $maxPoolsFetched
|
||||
where: { swapEnabled: true }
|
||||
orderBy: totalLiquidity
|
||||
orderDirection: desc
|
||||
) {
|
||||
id
|
||||
address
|
||||
poolType
|
||||
swapFee
|
||||
totalShares
|
||||
tokens {
|
||||
address
|
||||
balance
|
||||
decimals
|
||||
weight
|
||||
priceRate
|
||||
}
|
||||
tokensList
|
||||
totalWeight
|
||||
amp
|
||||
expiryTime
|
||||
unitSeconds
|
||||
principalToken
|
||||
baseToken
|
||||
swapEnabled
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const QUERY_BY_CHAIN_ID: { [chainId: number]: string } = {
|
||||
[ChainId.Mainnet]: queryWithLinear,
|
||||
[ChainId.Polygon]: queryWithOutLinear,
|
||||
};
|
||||
|
||||
const DEFAULT_MAX_POOLS_FETCHED = 96;
|
||||
|
||||
/**
|
||||
* Simple service to query required info from Subgraph for Balancer Pools.
|
||||
* Because Balancer Subgraphs have slightly different schema depending on network the queries are adjusted as needed.
|
||||
*/
|
||||
export class SubgraphPoolDataService implements PoolDataService {
|
||||
private readonly _gqlQuery: string | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _config: {
|
||||
chainId: number;
|
||||
subgraphUrl: string | null;
|
||||
maxPoolsFetched?: number;
|
||||
},
|
||||
) {
|
||||
this._config.maxPoolsFetched = this._config.maxPoolsFetched || DEFAULT_MAX_POOLS_FETCHED;
|
||||
this._gqlQuery = QUERY_BY_CHAIN_ID[this._config.chainId];
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: async-suffix
|
||||
public async getPools(): Promise<SubgraphPoolBase[]> {
|
||||
if (!this._gqlQuery || !this._config.subgraphUrl) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const { pools } = await request<{ pools: SubgraphPoolBase[] }>(this._config.subgraphUrl, this._gqlQuery, {
|
||||
maxPoolsFetched: this._config.maxPoolsFetched,
|
||||
});
|
||||
return pools;
|
||||
} catch (err) {
|
||||
logUtils.warn(`Failed to fetch BalancerV2 subgraph pools: ${err.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user