From 4817d44befa686edd20691c6002487762d8f185d Mon Sep 17 00:00:00 2001 From: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Date: Tue, 7 May 2024 10:11:49 +0530 Subject: [PATCH 1/2] integrated-cowswap --- data/small_example.json | 2 +- src/_server.py | 40 ++++++++++++++++++++++++++++++++++--- src/models/batch_auction.py | 26 ++++++++++++++++++++++++ src/models/solver_args.py | 2 ++ 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/data/small_example.json b/data/small_example.json index d41ace5..5e0cc29 100644 --- a/data/small_example.json +++ b/data/small_example.json @@ -53,7 +53,7 @@ "sell_token": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", "buy_token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1", "sell_amount": "100000000000000000000", - "buy_amount": "10000000000000000000", + "buy_amount": "12000000000000000000", "allow_partial_fill": false, "is_sell_order": true, "fee": { diff --git a/src/_server.py b/src/_server.py index 61d9c4b..34dbdab 100644 --- a/src/_server.py +++ b/src/_server.py @@ -9,6 +9,7 @@ import uvicorn from dotenv import load_dotenv +from src.util.numbers import decimal_to_str from fastapi import FastAPI, Request from fastapi.middleware.gzip import GZipMiddleware from pydantic import BaseSettings @@ -54,6 +55,31 @@ def health() -> bool: """Convenience endpoint to check if server is alive.""" return True +async def fetch_best_rates(batch: BatchAuction): + url = "http://central-server-url/bestRates" + payload = { + "sellTokenAddress": [order.sell_token for order in batch.orders], + "buyTokenAddress": [order.buy_token for order in batch.orders], + "sellTokenAmount": [order.sell_amount for order in batch.orders], + "user": "optional_user_address" # if needed + } + response = requests.post(url, json=payload) + if response.status_code == 200: + return response.json() + else: + raise Exception("Failed to fetch best rates") + +def generate_solution(batch: BatchAuction): + return { + "ref_token": batch.ref_token.value, + "orders": {order.order_id: order.as_dict() for order in batch.orders if order.is_executed()}, + "prices": {str(key): decimal_to_str(value) for key, value in batch.prices.items()}, + "amms": {}, + "prices": {}, + "approvals": [], + "interaction_data": [], + "score": "0", + } @app.post("/notify", response_model=bool) async def notify(request: Request) -> bool: @@ -70,15 +96,23 @@ async def solve(problem: BatchAuctionModel, request: Request): # type: ignore batch = BatchAuction.from_dict(problem.dict(), solver_args.instance_name) + # Fetch best rates for each token pair involved in the auction + best_rates = await fetch_best_rates(batch) + + # Update batch auction with the fetched rates + update_batch_with_best_rates(batch, best_rates) + print("Received Batch Auction", batch.name) print("Parameters Supplied", solver_args) # 1. Solve BatchAuction: update batch_auction with - # batch.solve() + batch.solve() + print("in solve",99) trivial_solution = { - "orders": {}, - "foreign_liquidity_orders": [], + "ref_token": batch.ref_token.value, + "orders": {order.order_id: order.as_dict() for order in batch.orders if order.is_executed() }, + "prices": {str(key): decimal_to_str(value) for key, value in batch.prices.items()}, "amms": {}, "prices": {}, "approvals": [], diff --git a/src/models/batch_auction.py b/src/models/batch_auction.py index 0956e0f..3a2936b 100644 --- a/src/models/batch_auction.py +++ b/src/models/batch_auction.py @@ -9,6 +9,7 @@ from typing import Any, Optional from src.models.order import Order, OrdersSerializedType +from src.models.order import Order, OrderMatchType from src.models.token import ( Token, TokenInfo, @@ -154,6 +155,27 @@ def default_ref_token_price(self) -> Decimal: def solve(self) -> None: """Solve Batch""" + orders = self.orders + print("in solve",len(orders)) + for i in range(len(orders)-1): + print("in solve",i) + for j in range(i+1,len(orders)): + print("in solve",len(orders)) + order_i, order_j = orders[i], orders[j] + if order_i.match_type(order_j) == OrderMatchType.BOTH_FILLED: + order_i.execute( + buy_amount_value=order_j.sell_amount, + sell_amount_value=order_j.buy_amount + ) + order_j.execute( + buy_amount_value=order_i.sell_amount, + sell_amount_value=order_i.buy_amount + ) + token_a = self.token_info(order_i.sell_token) + token_b = self.token_info(order_i.buy_token) + self.prices[token_a.token] = order_j.sell_amount + self.prices[token_b.token] = order_i.sell_amount + return ################################# # SOLUTION PROCESSING METHODS # @@ -186,6 +208,10 @@ def __repr__(self) -> str: """Print batch auction data.""" return self.name +def update_batch_with_best_rates(batch: BatchAuction, best_rates): + for rate in best_rates: + order = batch.orders[rate['order_id']] + order.update_rate(rate['new_sell_amount'], rate['new_buy_amount']) def load_metadata(metadata: dict[str, Any]) -> dict[str, Any]: """Store some basic metadata information.""" diff --git a/src/models/solver_args.py b/src/models/solver_args.py index f33ea9c..6e05359 100644 --- a/src/models/solver_args.py +++ b/src/models/solver_args.py @@ -41,3 +41,5 @@ def from_request(cls, request: Request, meta: MetadataModel) -> SolverArgs: # Both: Prioritize query params over metadata. auction_id=param_dict.get("auction_id", meta.auction_id), ) + + From de810c0527a69102aad25aadeb531ae81f50ccdd Mon Sep 17 00:00:00 2001 From: Mayank Sharma Date: Wed, 8 May 2024 10:21:51 +0530 Subject: [PATCH 2/2] add-central-server-integration --- .dockerignore | 20 +- .env.sample | 16 +- .github/workflows/cla.yaml | 46 +- .github/workflows/deploy.yaml | 72 +- .github/workflows/pull-request.yaml | 56 +- .gitignore | 16 +- .pylintrc | 8 +- Dockerfile | 30 +- LICENSE | 42 +- README.md | 220 +-- data/large_example.json | 2646 +++++++++++++-------------- data/small_example.json | 628 +++---- mypy.ini | 24 +- requirements.txt | 18 +- src/_server.py | 388 ++-- src/models/batch_auction.py | 630 +++---- src/models/exchange_rate.py | 246 +-- src/models/order.py | 738 ++++---- src/models/solver_args.py | 90 +- src/models/token.py | 808 ++++---- src/models/types.py | 10 +- src/models/uniswap.py | 644 +++---- src/util/constants.py | 36 +- src/util/enums.py | 74 +- src/util/exec_plan_coords.py | 42 +- src/util/numbers.py | 20 +- src/util/schema.py | 798 ++++---- tests/unit/test_order.py | 196 +- 28 files changed, 4317 insertions(+), 4245 deletions(-) diff --git a/.dockerignore b/.dockerignore index 3ac4ab0..132c13c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,10 +1,10 @@ -.github -tests -data -.env.sample -.gitignore -.pylintrc -Dockerfile -LICENSE -mypy.ini -README.md +.github +tests +data +.env.sample +.gitignore +.pylintrc +Dockerfile +LICENSE +mypy.ini +README.md diff --git a/.env.sample b/.env.sample index 21b4184..2a3b668 100644 --- a/.env.sample +++ b/.env.sample @@ -1,8 +1,8 @@ -COW_DEX_AG_SOLVER_URL=http://host.docker.internal:8000 -ORDERBOOK_URL=https://barn.api.cow.fi/xdai -BASE_TOKENS=0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83 -NODE_URL=https://rpc.gnosis.gateway.fm -SOLVER_ACCOUNT=0x7942a2b3540d1ec40b2740896f87aecb2a588731 -SOLVERS=CowDexAg -TRANSACTION_STRATEGY=DryRun -LOG_FILTER=info +COW_DEX_AG_SOLVER_URL=http://host.docker.internal:8000 +ORDERBOOK_URL=https://barn.api.cow.fi/xdai +BASE_TOKENS=0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83 +NODE_URL=https://rpc.gnosis.gateway.fm +SOLVER_ACCOUNT=0x7942a2b3540d1ec40b2740896f87aecb2a588731 +SOLVERS=CowDexAg +TRANSACTION_STRATEGY=DryRun +LOG_FILTER=info diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index fcae10d..76ff335 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -1,23 +1,23 @@ -name: "cla" - -on: - issue_comment: - types: [created] - pull_request_target: - types: [opened, closed, synchronize] - -jobs: - cla: - runs-on: ubuntu-latest - steps: - - name: "CLA Assistant" - if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' - uses: contributor-assistant/github-action@v2.2.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PERSONAL_ACCESS_TOKEN : ${{ secrets.ORG_TOKEN }} - with: - branch: 'cla-signatures' - path-to-signatures: 'signatures/version1/cla.json' - path-to-document: 'https://github.com/cowprotocol/cla/blob/main/CLA.md' - allowlist: '*[bot]' +name: "cla" + +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] + +jobs: + cla: + runs-on: ubuntu-latest + steps: + - name: "CLA Assistant" + if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' + uses: contributor-assistant/github-action@v2.2.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PERSONAL_ACCESS_TOKEN : ${{ secrets.ORG_TOKEN }} + with: + branch: 'cla-signatures' + path-to-signatures: 'signatures/version1/cla.json' + path-to-document: 'https://github.com/cowprotocol/cla/blob/main/CLA.md' + allowlist: '*[bot]' diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 97ecb86..a01b081 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,36 +1,36 @@ -name: deploy - -on: - push: - branches: [main] - tags: [v*] - -jobs: - deploy: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - uses: actions/checkout@v2 - - - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - id: meta - uses: docker/metadata-action@v3 - with: - images: ghcr.io/${{ github.repository }} - labels: | - org.opencontainers.image.licenses=MIT OR Apache-2.0 - - uses: docker/build-push-action@v2 - with: - context: . - file: Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} +name: deploy + +on: + push: + branches: [main] + tags: [v*] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v2 + + - uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - id: meta + uses: docker/metadata-action@v3 + with: + images: ghcr.io/${{ github.repository }} + labels: | + org.opencontainers.image.licenses=MIT OR Apache-2.0 + - uses: docker/build-push-action@v2 + with: + context: . + file: Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 159d880..f05dc1b 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -1,29 +1,29 @@ -name: pull request -on: - pull_request: - push: - branches: [ main ] -jobs: - python: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Setup Python 3.10 - uses: actions/setup-python@v2 - with: - python-version: '3.10' - - name: Install Requirements - run: - pip install -r requirements.txt - - name: Lint - run: - pylint src/ - - name: Format - run: - black --check ./ - - name: Type Check - run: - mypy src --strict - - name: Unit Tests - run: +name: pull request +on: + pull_request: + push: + branches: [ main ] +jobs: + python: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: '3.10' + - name: Install Requirements + run: + pip install -r requirements.txt + - name: Lint + run: + pylint src/ + - name: Format + run: + black --check ./ + - name: Type Check + run: + mypy src --strict + - name: Unit Tests + run: python -m pytest tests/unit \ No newline at end of file diff --git a/.gitignore b/.gitignore index 21ba80e..ecb3891 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ -*.pyc -*/__pycache__ -venv/ -.env - -.idea/ -.vscode/ -.DS_Store +*.pyc +*/__pycache__ +venv/ +.env + +.idea/ +.vscode/ +.DS_Store diff --git a/.pylintrc b/.pylintrc index 70a75fc..e5958fa 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,4 +1,4 @@ -[MASTER] -disable=fixme,too-few-public-methods,too-many-instance-attributes,too-many-arguments,logging-fstring-interpolation,too-many-locals,duplicate-code, def buy_amount(self) -> Decimal: - -extension-pkg-allow-list=pydantic +[MASTER] +disable=fixme,too-few-public-methods,too-many-instance-attributes,too-many-arguments,logging-fstring-interpolation,too-many-locals,duplicate-code, def buy_amount(self) -> Decimal: + +extension-pkg-allow-list=pydantic diff --git a/Dockerfile b/Dockerfile index 8c1ab2d..f20e617 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,15 @@ -FROM python:3.10-alpine - -RUN apk add --update gcc libc-dev linux-headers - -WORKDIR /app - -# First copy over the requirements.txt and install dependencies, this makes -# building subsequent images easier. -COPY requirements.txt . -RUN pip install -r requirements.txt - -# Copy full source (see .dockerignore) -COPY . . - -CMD [ "python3", "-m" , "src._server"] +FROM python:3.10-alpine + +RUN apk add --update gcc libc-dev linux-headers + +WORKDIR /app + +# First copy over the requirements.txt and install dependencies, this makes +# building subsequent images easier. +COPY requirements.txt . +RUN pip install -r requirements.txt + +# Copy full source (see .dockerignore) +COPY . . + +CMD [ "python3", "-m" , "src._server"] diff --git a/LICENSE b/LICENSE index 2bbbd2a..b448efd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2022 CoW Protocol - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2022 CoW Protocol + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index b6ed5d0..4e0af60 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,110 @@ -# Setup Project - -Clone this repository - -```sh -git clone git@github.com:cowprotocol/solver-template-py.git -``` - -## Install Requirements - -1. Python 3.10 (or probably also 3.9) -2. Rust v1.60.0 or Docker - -```sh -python3.10 -m venv venv -source ./venv/bin/activate -pip install -r requirements.txt -``` - -# Run Solver Server - -```shell -python -m src._server -``` - -This can also be run via docker with - -```sh -docker run -p 8000:8000 gchr.io/cowprotocol/solver-template-py -``` - -or build your own docker image with - -```sh -docker build -t test-solver-image . -``` - -# Feed an Auction Instance to the Solver - -```shell -curl -X POST "http://127.0.0.1:8000/solve" \ - -H "accept: application/json" \ - -H "Content-Type: application/json" \ - --data "@data/small_example.json" -``` - -# Connect to the orderbook: - -Run the driver (auction dispatcher in DryRun mode). Configured to read the orderbook -from our staging environment on Gnosis Chain. These parameters can be altered -in [.env](.env) - -## With Docker - -If you have docker installed then you can run this. - -```shell -docker run -it --rm --env-file .env --add-host host.docker.internal:host-gateway ghcr.io/cowprotocol/services solver -``` - -or without an env file (as described in -the [How to Write a Solver Tutorial](https://docs.cow.fi/tutorials/how-to-write-a-solver)) - -```shell -docker run -it --rm --add-host host.docker.internal:host-gateway ghcr.io/cowprotocol/services solver \ ---orderbook-url https://barn.api.cow.fi/xdai/api \ ---base-tokens 0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83 \ ---node-url "https://rpc.gnosischain.com" \ ---cow-dex-ag-solver-url "http://127.0.0.1:8000" \ ---solver-account 0x7942a2b3540d1ec40b2740896f87aecb2a588731 \ ---solvers CowDexAg \ ---transaction-strategy DryRun -``` - -Here we have used the orderbook-url for our staging environment on Gnosis Chain (very low traffic) so you can work with your own orders. A complete list of orderbook URLs can be found in a table at the bottom of the services repo [README](https://github.com/cowprotocol/services#solvers) - -## Without Docker - -Clone the services project with - -```shell -git clone https://github.com/cowprotocol/services.git -``` - -```shell -cargo run -p solver -- \ - --orderbook-url https://barn.api.cow.fi/xdai/api \ - --base-tokens 0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83 \ - --node-url "https://rpc.gnosischain.com" \ - --cow-dex-ag-solver-url "http://127.0.0.1:8000" \ - --solver-account 0x7942a2b3540d1ec40b2740896f87aecb2a588731 \ - --solvers CowDexAg \ - --transaction-strategy DryRun \ - --log-filter=info,solver=debug -``` - -# Place an order - -Navigate to [barn.cowswap.exchange/](https://barn.cowswap.exchange/#/swap) and place a -tiny (real) order. See your driver pick it up and include it in the next auction being -sent to your solver - -# References - -- How to Build a Solver: https://docs.cow.fi/tutorials/how-to-write-a-solver -- In Depth Solver - Specification: https://docs.cow.fi/off-chain-services/in-depth-solver-specification -- Settlement Contract (namely the settle - method): https://github.com/cowprotocol/contracts/blob/ff6fb7cad7787b8d43a6468809cacb799601a10e/src/contracts/GPv2Settlement.sol#L121-L143 -- Interaction Model (Currently missing from this framework): https://github.com/cowprotocol/services/blob/cda5e36db34c55e7bf9eb4ea8b6e36ecb046f2b2/crates/shared/src/http_solver/model.rs#L125-L130 +# Setup Project + +Clone this repository + +```sh +git clone git@github.com:cowprotocol/solver-template-py.git +``` + +## Install Requirements + +1. Python 3.10 (or probably also 3.9) +2. Rust v1.60.0 or Docker + +```sh +python3.10 -m venv venv +source ./venv/bin/activate +pip install -r requirements.txt +``` + +# Run Solver Server + +```shell +python -m src._server +``` + +This can also be run via docker with + +```sh +docker run -p 8000:8000 gchr.io/cowprotocol/solver-template-py +``` + +or build your own docker image with + +```sh +docker build -t test-solver-image . +``` + +# Feed an Auction Instance to the Solver + +```shell +curl -X POST "http://127.0.0.1:8000/solve" \ + -H "accept: application/json" \ + -H "Content-Type: application/json" \ + --data "@data/small_example.json" +``` + +# Connect to the orderbook: + +Run the driver (auction dispatcher in DryRun mode). Configured to read the orderbook +from our staging environment on Gnosis Chain. These parameters can be altered +in [.env](.env) + +## With Docker + +If you have docker installed then you can run this. + +```shell +docker run -it --rm --env-file .env --add-host host.docker.internal:host-gateway ghcr.io/cowprotocol/services solver +``` + +or without an env file (as described in +the [How to Write a Solver Tutorial](https://docs.cow.fi/tutorials/how-to-write-a-solver)) + +```shell +docker run -it --rm --add-host host.docker.internal:host-gateway ghcr.io/cowprotocol/services solver \ +--orderbook-url https://barn.api.cow.fi/xdai/api \ +--base-tokens 0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83 \ +--node-url "https://rpc.gnosischain.com" \ +--cow-dex-ag-solver-url "http://127.0.0.1:8000" \ +--solver-account 0x7942a2b3540d1ec40b2740896f87aecb2a588731 \ +--solvers CowDexAg \ +--transaction-strategy DryRun +``` + +Here we have used the orderbook-url for our staging environment on Gnosis Chain (very low traffic) so you can work with your own orders. A complete list of orderbook URLs can be found in a table at the bottom of the services repo [README](https://github.com/cowprotocol/services#solvers) + +## Without Docker + +Clone the services project with + +```shell +git clone https://github.com/cowprotocol/services.git +``` + +```shell +cargo run -p solver -- \ + --orderbook-url https://barn.api.cow.fi/xdai/api \ + --base-tokens 0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83 \ + --node-url "https://rpc.gnosischain.com" \ + --cow-dex-ag-solver-url "http://127.0.0.1:8000" \ + --solver-account 0x7942a2b3540d1ec40b2740896f87aecb2a588731 \ + --solvers CowDexAg \ + --transaction-strategy DryRun \ + --log-filter=info,solver=debug +``` + +# Place an order + +Navigate to [barn.cowswap.exchange/](https://barn.cowswap.exchange/#/swap) and place a +tiny (real) order. See your driver pick it up and include it in the next auction being +sent to your solver + +# References + +- How to Build a Solver: https://docs.cow.fi/tutorials/how-to-write-a-solver +- In Depth Solver + Specification: https://docs.cow.fi/off-chain-services/in-depth-solver-specification +- Settlement Contract (namely the settle + method): https://github.com/cowprotocol/contracts/blob/ff6fb7cad7787b8d43a6468809cacb799601a10e/src/contracts/GPv2Settlement.sol#L121-L143 +- Interaction Model (Currently missing from this framework): https://github.com/cowprotocol/services/blob/cda5e36db34c55e7bf9eb4ea8b6e36ecb046f2b2/crates/shared/src/http_solver/model.rs#L125-L130 diff --git a/data/large_example.json b/data/large_example.json index 2fae682..6d89c03 100644 --- a/data/large_example.json +++ b/data/large_example.json @@ -1,1324 +1,1324 @@ -{ - "tokens": { - "0x03ab458634910aad20ef5f1c8ee96f1d6ac54919": { - "decimals": 18, - "alias": "RAI", - "external_price": 0.0006506460070578742, - "normalize_priority": 0, - "internal_buffer": "4106217917128523846795" - }, - "0x04fa0d235c4abf4bcf4787af4cf447de572ef828": { - "decimals": 18, - "alias": "UMA", - "external_price": 0.003605745825303479, - "normalize_priority": 0, - "internal_buffer": "948797221197316718535" - }, - "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { - "decimals": 18, - "alias": "YFI", - "external_price": 6.940226176727003, - "normalize_priority": 0, - "internal_buffer": "172166759331260832" - }, - "0x111111111117dc0aa78b770fa6a738034120c302": { - "decimals": 18, - "alias": "1INCH", - "external_price": 0.00090369635890507, - "normalize_priority": 0, - "internal_buffer": "250836440525249484992" - }, - "0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b": { - "decimals": 18, - "alias": "DPI", - "external_price": 0.08358714849705147, - "normalize_priority": 0, - "internal_buffer": "1049182574757442518" - }, - "0x18aaa7115705e8be94bffebde57af9bfc265b998": { - "decimals": 18, - "alias": "AUDIO", - "external_price": 0.0004929218495799725, - "normalize_priority": 0, - "internal_buffer": "303568400054356092900" - }, - "0x1b40183efb4dd766f11bda7a7c3ad8982e998421": { - "decimals": 18, - "alias": "VSP", - "external_price": 0.0015095155963260323, - "normalize_priority": 0, - "internal_buffer": "437186532097648092450" - }, - "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { - "decimals": 18, - "alias": "UNI", - "external_price": 0.005263815871994719, - "normalize_priority": 0, - "internal_buffer": "121228420874467284726" - }, - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { - "decimals": 8, - "alias": "WBTC", - "external_price": 138003957677.49826, - "normalize_priority": 0, - "internal_buffer": "10575896" - }, - "0x35a18000230da775cac24873d00ff85bccded550": { - "decimals": 8, - "alias": "cUNI", - "external_price": 1813940.152319275, - "normalize_priority": 0, - "internal_buffer": "0" - }, - "0x3845badade8e6dff049820680d1f14bd3903a5d0": { - "decimals": 18, - "alias": "SAND", - "external_price": 0.000564758060148669, - "normalize_priority": 0, - "internal_buffer": "1314916061357216981027" - }, - "0x514910771af9ca656af840dff83e8264ecf986ca": { - "decimals": 18, - "alias": "LINK", - "external_price": 0.007391778205841738, - "normalize_priority": 0, - "internal_buffer": "33991421692402196354" - }, - "0x61d5dc44849c9c87b0856a2a311536205c96c7fd": { - "decimals": 18, - "alias": "BED", - "external_price": null, - "normalize_priority": 0, - "internal_buffer": "0" - }, - "0x677ddbd918637e5f2c79e164d402454de7da8619": { - "decimals": 18, - "alias": "VUSD", - "external_price": 0.00023286555879694992, - "normalize_priority": 0, - "internal_buffer": "0" - }, - "0x6810e776880c02933d47db1b9fc05908e5386b96": { - "decimals": 18, - "alias": "GNO", - "external_price": 0.1051159378992619, - "normalize_priority": 0, - "internal_buffer": "22283326083484976689" - }, - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "decimals": 18, - "alias": "DAI", - "external_price": 0.00021508661247926934, - "normalize_priority": 0, - "internal_buffer": "8213967696976545926330" - }, - "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2": { - "decimals": 18, - "alias": "SUSHI", - "external_price": 0.0023565017250752507, - "normalize_priority": 0, - "internal_buffer": "532389814464422511800" - }, - "0x72e364f2abdc788b7e918bc238b21f109cd634d7": { - "decimals": 18, - "alias": "MVI", - "external_price": 0.052543323961485415, - "normalize_priority": 0, - "internal_buffer": "3433421993728834413" - }, - "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0": { - "decimals": 18, - "alias": "MATIC", - "external_price": 0.0003711115249774994, - "normalize_priority": 0, - "internal_buffer": "3441170860637545940171" - }, - "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { - "decimals": 18, - "alias": "AAVE", - "external_price": 0.06612446960232675, - "normalize_priority": 0, - "internal_buffer": "27722641716976223893" - }, - "0x86ed939b500e121c0c5f493f399084db596dad20": { - "decimals": 18, - "alias": "SPC", - "external_price": 4.1261511215831115e-06, - "normalize_priority": 0, - "internal_buffer": "32914340028683757797937" - }, - "0x8798249c2e607446efb7ad49ec89dd1865ff4272": { - "decimals": 18, - "alias": "xSUSHI", - "external_price": 0.0028045617645566356, - "normalize_priority": 0, - "internal_buffer": "10636021010421009340" - }, - "0x87d73e916d7057945c9bcd8cdd94e42a6f47f776": { - "decimals": 18, - "alias": "NFTX", - "external_price": 0.026216549031217097, - "normalize_priority": 0, - "internal_buffer": "47438294969856214110" - }, - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { - "decimals": 18, - "alias": null, - "external_price": 0.6197375729429615, - "normalize_priority": 0, - "internal_buffer": "440168254765910888" - }, - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "decimals": 6, - "alias": "USDC", - "external_price": 214890212.34875953, - "normalize_priority": 0, - "internal_buffer": "2217249148" - }, - "0xa3d58c4e56fedcae3a7c43a725aee9a71f0ece4e": { - "decimals": 18, - "alias": "MET", - "external_price": 0.001383182798662126, - "normalize_priority": 0, - "internal_buffer": "0" - }, - "0xb6ca7399b4f9ca56fc27cbff44f4d2e4eef1fc81": { - "decimals": 18, - "alias": "MUSE", - "external_price": 0.005638573096791319, - "normalize_priority": 0, - "internal_buffer": "7201165091578979003" - }, - "0xba100000625a3754423978a60c9317c58a424e3d": { - "decimals": 18, - "alias": "BAL", - "external_price": 0.005273380157361497, - "normalize_priority": 0, - "internal_buffer": "195404670885132586236" - }, - "0xbb0e17ef65f82ab018d8edd776e8dd940327b28b": { - "decimals": 18, - "alias": "AXS", - "external_price": 0.031233281341518034, - "normalize_priority": 0, - "internal_buffer": "8542298531938629643" - }, - "0xc00e94cb662c3520282e6f5717214004a7f26888": { - "decimals": 18, - "alias": "COMP", - "external_price": 0.07012814003979417, - "normalize_priority": 0, - "internal_buffer": "502061252574151003" - }, - "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": { - "decimals": 18, - "alias": "SNX", - "external_price": 0.002065836748078299, - "normalize_priority": 0, - "internal_buffer": "48158892818549095716" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "decimals": 18, - "alias": "WETH", - "external_price": 1.0, - "normalize_priority": 1, - "internal_buffer": "895880027660372311" - }, - "0xdac17f958d2ee523a2206206994597c13d831ec7": { - "decimals": 6, - "alias": "USDT", - "external_price": 214523029.31427807, - "normalize_priority": 0, - "internal_buffer": "4227015605" - }, - "0xf5d669627376ebd411e34b98f19c868c8aba5ada": { - "decimals": 18, - "alias": "AXS", - "external_price": 0.0316315369551985, - "normalize_priority": 0, - "internal_buffer": "3240347795546996681" - } - }, - "orders": { - "0": { - "sell_token": "0x6b175474e89094c44da98b954eedeac495271d0f", - "buy_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "sell_amount": "4693994755140375611596", - "buy_amount": "1000000000000000000", - "allow_partial_fill": false, - "is_sell_order": false, - "fee": { - "amount": "103079335446226157568", - "token": "0x6b175474e89094c44da98b954eedeac495271d0f" - }, - "cost": { - "amount": "6657722265694875", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "is_liquidity_order": false - }, - "1": { - "sell_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "buy_token": "0x6b175474e89094c44da98b954eedeac495271d0f", - "sell_amount": "1000000000000000000", - "buy_amount": "4692581049969374626065", - "allow_partial_fill": false, - "is_sell_order": true, - "fee": { - "amount": "23212472598551576", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "cost": { - "amount": "6657722265694875", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "is_liquidity_order": true - } - }, - "amms": { - "0": { - "kind": "ConstantProduct", - "reserves": { - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "145569872903238", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "31203154300783173264676" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "1": { - "kind": "ConstantProduct", - "reserves": { - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "32094048746274", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "32246000595783" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "2": { - "kind": "ConstantProduct", - "reserves": { - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "27888890246655", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "73592" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "3": { - "kind": "ConstantProduct", - "reserves": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "22863225777437632054921", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "106902377410356" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "4": { - "kind": "ConstantProduct", - "reserves": { - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "169787956009912606", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "537928202" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "5": { - "kind": "ConstantProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "134160721047695286341", - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "42881473583722456" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "6": { - "kind": "ConstantProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "15888560", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "10186789120" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "7": { - "kind": "ConstantProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "41233803245681607785277120", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "41231078854410" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "8": { - "kind": "ConstantProduct", - "reserves": { - "0xc00e94cb662c3520282e6f5717214004a7f26888": "9926566439412894848842", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "698770885779646213074" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "9": { - "kind": "ConstantProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "3512718386149447605851734", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "3509628668637" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "10": { - "kind": "ConstantProduct", - "reserves": { - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "4998709999204848554923", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "3087597737544666832671" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "11": { - "kind": "ConstantProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "153432658158", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "21112618231915508496108" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "12": { - "kind": "ConstantProduct", - "reserves": { - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "31680019939293183", - "0xc00e94cb662c3520282e6f5717214004a7f26888": "260190569505819239" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "13": { - "kind": "ConstantProduct", - "reserves": { - "0xc00e94cb662c3520282e6f5717214004a7f26888": "8063046894828888", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "2996831" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "14": { - "kind": "ConstantProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "463400567316558373359", - "0xc00e94cb662c3520282e6f5717214004a7f26888": "1369320914588629197" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "15": { - "kind": "ConstantProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "1729370314", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "1113867683775" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "16": { - "kind": "ConstantProduct", - "reserves": { - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "28499713", - "0xc00e94cb662c3520282e6f5717214004a7f26888": "92167578509045372" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "17": { - "kind": "ConstantProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "1035148074", - "0x6b175474e89094c44da98b954eedeac495271d0f": "662274161237620105343822" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "18": { - "kind": "ConstantProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "44897630044876228891318837", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "9626911517235794223708" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "19": { - "kind": "ConstantProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "390690734546", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "53754113629378649935266" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "20": { - "kind": "ConstantProduct", - "reserves": { - "0xc00e94cb662c3520282e6f5717214004a7f26888": "50264319597272926997920", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "3513371413991359073584" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "21": { - "kind": "ConstantProduct", - "reserves": { - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "184849299664303", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "39602141334816390292350" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "22": { - "kind": "ConstantProduct", - "reserves": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "21039325251239368172058", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "98245688621558" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "23": { - "kind": "ConstantProduct", - "reserves": { - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "2377024028", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "2369021572" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "24": { - "kind": "ConstantProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "31420234752449186215469", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "31584154870" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "25": { - "kind": "ConstantProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "16770", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "11001944" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "26": { - "kind": "ConstantProduct", - "reserves": { - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "6745276099673343931735", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "4173255630839005558502" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "27": { - "kind": "ConstantProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "84903768350604287941150958", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "18233677073990818080605" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "28": { - "kind": "ConstantProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "166890692159778890143", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "167471125" - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "29": { - "kind": "WeightedProduct", - "reserves": { - "0xc00e94cb662c3520282e6f5717214004a7f26888": { - "balance": "7023489501333501541452", - "weight": "0.5" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "487792715768814302631", - "weight": "0.5" - } - }, - "fee": "0.0025", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "30": { - "kind": "WeightedProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "balance": "1191959749018354276837", - "weight": "0.4" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "392171457910841840", - "weight": "0.6" - } - }, - "fee": "0.0025", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "32": { - "kind": "WeightedProduct", - "reserves": { - "0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b": { - "balance": "3183163540675204103301", - "weight": "0.333333333333333333" - }, - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { - "balance": "1946028292", - "weight": "0.333333333333333333" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "268258123288872175175", - "weight": "0.333333333333333334" - } - }, - "fee": "0.005", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "33": { - "kind": "WeightedProduct", - "reserves": { - "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { - "balance": "1150", - "weight": "0.125" - }, - "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { - "balance": "1937069", - "weight": "0.125" - }, - "0x514910771af9ca656af840dff83e8264ecf986ca": { - "balance": "1712376", - "weight": "0.125" - }, - "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { - "balance": "139846", - "weight": "0.125" - }, - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { - "balance": "13299", - "weight": "0.125" - }, - "0xc00e94cb662c3520282e6f5717214004a7f26888": { - "balance": "121022", - "weight": "0.125" - }, - "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": { - "balance": "4009385", - "weight": "0.125" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "17365", - "weight": "0.125" - } - }, - "fee": "0.0015", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "34": { - "kind": "WeightedProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { - "balance": "7886892", - "weight": "0.5" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "1126985534364820699", - "weight": "0.5" - } - }, - "fee": "0.0004", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "37": { - "kind": "WeightedProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { - "balance": "244170655453", - "weight": "0.5" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "33602873247647934281998", - "weight": "0.5" - } - }, - "fee": "0.0016", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "38": { - "kind": "WeightedProduct", - "reserves": { - "0x6810e776880c02933d47db1b9fc05908e5386b96": { - "balance": "21330539255670269346", - "weight": "0.25" - }, - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "balance": "10928595376682871418747", - "weight": "0.25" - }, - "0xba100000625a3754423978a60c9317c58a424e3d": { - "balance": "444658133648670940819", - "weight": "0.25" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "2237408990689298635", - "weight": "0.25" - } - }, - "fee": "0.01", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "40": { - "kind": "WeightedProduct", - "reserves": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "6158022349654322", - "weight": "0.5" - }, - "0xdac17f958d2ee523a2206206994597c13d831ec7": { - "balance": "23372424", - "weight": "0.5" - } - }, - "fee": "0.001", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "41": { - "kind": "WeightedProduct", - "reserves": { - "0x03ab458634910aad20ef5f1c8ee96f1d6ac54919": { - "balance": "1428563", - "weight": "0.1" - }, - "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { - "balance": "84", - "weight": "0.07" - }, - "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { - "balance": "153848", - "weight": "0.1" - }, - "0x61d5dc44849c9c87b0856a2a311536205c96c7fd": { - "balance": "620", - "weight": "0.1" - }, - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "balance": "11451302", - "weight": "0.15" - }, - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "balance": "1", - "weight": "0.13" - }, - "0xba100000625a3754423978a60c9317c58a424e3d": { - "balance": "205095", - "weight": "0.2" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "3853", - "weight": "0.15" - } - }, - "fee": "0.005", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "42": { - "kind": "WeightedProduct", - "reserves": { - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "balance": "17463225691118", - "weight": "0.5" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "3757802158500106767356", - "weight": "0.5" - } - }, - "fee": "0.0022", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "43": { - "kind": "WeightedProduct", - "reserves": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "78016391530210266667", - "weight": "0.5" - }, - "0xdac17f958d2ee523a2206206994597c13d831ec7": { - "balance": "362998299778", - "weight": "0.5" - } - }, - "fee": "0.0022", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "44": { - "kind": "WeightedProduct", - "reserves": { - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { - "balance": "7831731473578894597621", - "weight": "0.6" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "3247980137175769019160", - "weight": "0.4" - } - }, - "fee": "0.0027", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "46": { - "kind": "WeightedProduct", - "reserves": { - "0x18aaa7115705e8be94bffebde57af9bfc265b998": { - "balance": "60387271452376666026", - "weight": "0.125" - }, - "0x3845badade8e6dff049820680d1f14bd3903a5d0": { - "balance": "126370092106264106317", - "weight": "0.125" - }, - "0x72e364f2abdc788b7e918bc238b21f109cd634d7": { - "balance": "973866726433230259", - "weight": "0.125" - }, - "0x87d73e916d7057945c9bcd8cdd94e42a6f47f776": { - "balance": "568421440530172583", - "weight": "0.125" - }, - "0xb6ca7399b4f9ca56fc27cbff44f4d2e4eef1fc81": { - "balance": "2997023749294327812", - "weight": "0.125" - }, - "0xbb0e17ef65f82ab018d8edd776e8dd940327b28b": { - "balance": "1295943489744034642", - "weight": "0.125" - }, - "0xc00e94cb662c3520282e6f5717214004a7f26888": { - "balance": "297423910895795177", - "weight": "0.125" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "42300385807555997", - "weight": "0.125" - } - }, - "fee": "0.01", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "47": { - "kind": "WeightedProduct", - "reserves": { - "0x1b40183efb4dd766f11bda7a7c3ad8982e998421": { - "balance": "5861", - "weight": "0.1" - }, - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "balance": "50373", - "weight": "0.1" - }, - "0x86ed939b500e121c0c5f493f399084db596dad20": { - "balance": "9352828", - "weight": "0.6" - }, - "0xa3d58c4e56fedcae3a7c43a725aee9a71f0ece4e": { - "balance": "11459", - "weight": "0.1" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "16", - "weight": "0.1" - } - }, - "fee": "0.005", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "48": { - "kind": "WeightedProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { - "balance": "1", - "weight": "0.33" - }, - "0x677ddbd918637e5f2c79e164d402454de7da8619": { - "balance": "185143579", - "weight": "0.34" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "51158", - "weight": "0.33" - } - }, - "fee": "0.005", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "50": { - "kind": "WeightedProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "balance": "44761228399171133055581730", - "weight": "0.4" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "14407839606689791576732", - "weight": "0.6" - } - }, - "fee": "0.0021", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "51": { - "kind": "WeightedProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { - "balance": "1", - "weight": "0.5" - }, - "0x35a18000230da775cac24873d00ff85bccded550": { - "balance": "1", - "weight": "0.01" - }, - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "balance": "1", - "weight": "0.49" - } - }, - "fee": "0.0015", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "52": { - "kind": "WeightedProduct", - "reserves": { - "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { - "balance": "6472412946763199832", - "weight": "0.125" - }, - "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { - "balance": "8600510230735290526694", - "weight": "0.125" - }, - "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2": { - "balance": "19557650598562732453526", - "weight": "0.125" - }, - "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { - "balance": "689193422058489015058", - "weight": "0.125" - }, - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { - "balance": "73222575681890606005", - "weight": "0.125" - }, - "0xba100000625a3754423978a60c9317c58a424e3d": { - "balance": "8848294720356512217529", - "weight": "0.125" - }, - "0xc00e94cb662c3520282e6f5717214004a7f26888": { - "balance": "653880188325182856471", - "weight": "0.125" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "45015549375580771613", - "weight": "0.125" - } - }, - "fee": "0.0015", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "53": { - "kind": "WeightedProduct", - "reserves": { - "0x04fa0d235c4abf4bcf4787af4cf447de572ef828": { - "balance": "4562300116085354789", - "weight": "0.125" - }, - "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { - "balance": "1256657629424214", - "weight": "0.125" - }, - "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { - "balance": "2107829214730689206", - "weight": "0.125" - }, - "0x514910771af9ca656af840dff83e8264ecf986ca": { - "balance": "1949409240792221736", - "weight": "0.125" - }, - "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { - "balance": "154339756888995831", - "weight": "0.125" - }, - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { - "balance": "17270757086778536", - "weight": "0.125" - }, - "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": { - "balance": "5313162734654351870", - "weight": "0.125" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "18972472132080956", - "weight": "0.125" - } - }, - "fee": "0.0015", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "54": { - "kind": "WeightedProduct", - "reserves": { - "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { - "balance": "6927054066178298", - "weight": "0.125" - }, - "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0": { - "balance": "155376214405830998645", - "weight": "0.125" - }, - "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { - "balance": "652171084905308585", - "weight": "0.125" - }, - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { - "balance": "88653946919383609", - "weight": "0.125" - }, - "0xba100000625a3754423978a60c9317c58a424e3d": { - "balance": "11479463481449338709", - "weight": "0.125" - }, - "0xc00e94cb662c3520282e6f5717214004a7f26888": { - "balance": "642744083856292483", - "weight": "0.125" - }, - "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": { - "balance": "22056893760322547128", - "weight": "0.125" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "65185802710414326", - "weight": "0.125" - } - }, - "fee": "0.01", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "55": { - "kind": "WeightedProduct", - "reserves": { - "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { - "balance": "7106964898819589313", - "weight": "0.125" - }, - "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2": { - "balance": "12856221384869336968", - "weight": "0.125" - }, - "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { - "balance": "458179995950018385", - "weight": "0.125" - }, - "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { - "balance": "50480619372569735", - "weight": "0.125" - }, - "0xba100000625a3754423978a60c9317c58a424e3d": { - "balance": "7252864916011326167", - "weight": "0.125" - }, - "0xc00e94cb662c3520282e6f5717214004a7f26888": { - "balance": "475828038408280564", - "weight": "0.125" - }, - "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": { - "balance": "14837495085729431304", - "weight": "0.125" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "46988251681603941", - "weight": "0.125" - } - }, - "fee": "0.005", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "56": { - "kind": "WeightedProduct", - "reserves": { - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { - "balance": "100000", - "weight": "0.4" - }, - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "balance": "31791255", - "weight": "0.2" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "17015250000000000", - "weight": "0.4" - } - }, - "fee": "0.0055", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "57": { - "kind": "WeightedProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "balance": "28595840", - "weight": "0.5" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "8809", - "weight": "0.5" - } - }, - "fee": "0.005", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - }, - "58": { - "kind": "Stable", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "57452498438246071785170074", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "53340234019884", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "46859260361900" - }, - "scaling_rates": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "1000000000000000000", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "1000000", - "0xdac17f958d2ee523a2206206994597c13d831ec7": "1000000" - }, - "amplification_parameter": "620", - "fee": "0.0001", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "mandatory": false - } - }, - "metadata": { - "environment": "xDAI", - "auction_id": 20, - "gas_price": 4850000000.0, - "native_token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - } +{ + "tokens": { + "0x03ab458634910aad20ef5f1c8ee96f1d6ac54919": { + "decimals": 18, + "alias": "RAI", + "external_price": 0.0006506460070578742, + "normalize_priority": 0, + "internal_buffer": "4106217917128523846795" + }, + "0x04fa0d235c4abf4bcf4787af4cf447de572ef828": { + "decimals": 18, + "alias": "UMA", + "external_price": 0.003605745825303479, + "normalize_priority": 0, + "internal_buffer": "948797221197316718535" + }, + "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { + "decimals": 18, + "alias": "YFI", + "external_price": 6.940226176727003, + "normalize_priority": 0, + "internal_buffer": "172166759331260832" + }, + "0x111111111117dc0aa78b770fa6a738034120c302": { + "decimals": 18, + "alias": "1INCH", + "external_price": 0.00090369635890507, + "normalize_priority": 0, + "internal_buffer": "250836440525249484992" + }, + "0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b": { + "decimals": 18, + "alias": "DPI", + "external_price": 0.08358714849705147, + "normalize_priority": 0, + "internal_buffer": "1049182574757442518" + }, + "0x18aaa7115705e8be94bffebde57af9bfc265b998": { + "decimals": 18, + "alias": "AUDIO", + "external_price": 0.0004929218495799725, + "normalize_priority": 0, + "internal_buffer": "303568400054356092900" + }, + "0x1b40183efb4dd766f11bda7a7c3ad8982e998421": { + "decimals": 18, + "alias": "VSP", + "external_price": 0.0015095155963260323, + "normalize_priority": 0, + "internal_buffer": "437186532097648092450" + }, + "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { + "decimals": 18, + "alias": "UNI", + "external_price": 0.005263815871994719, + "normalize_priority": 0, + "internal_buffer": "121228420874467284726" + }, + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { + "decimals": 8, + "alias": "WBTC", + "external_price": 138003957677.49826, + "normalize_priority": 0, + "internal_buffer": "10575896" + }, + "0x35a18000230da775cac24873d00ff85bccded550": { + "decimals": 8, + "alias": "cUNI", + "external_price": 1813940.152319275, + "normalize_priority": 0, + "internal_buffer": "0" + }, + "0x3845badade8e6dff049820680d1f14bd3903a5d0": { + "decimals": 18, + "alias": "SAND", + "external_price": 0.000564758060148669, + "normalize_priority": 0, + "internal_buffer": "1314916061357216981027" + }, + "0x514910771af9ca656af840dff83e8264ecf986ca": { + "decimals": 18, + "alias": "LINK", + "external_price": 0.007391778205841738, + "normalize_priority": 0, + "internal_buffer": "33991421692402196354" + }, + "0x61d5dc44849c9c87b0856a2a311536205c96c7fd": { + "decimals": 18, + "alias": "BED", + "external_price": null, + "normalize_priority": 0, + "internal_buffer": "0" + }, + "0x677ddbd918637e5f2c79e164d402454de7da8619": { + "decimals": 18, + "alias": "VUSD", + "external_price": 0.00023286555879694992, + "normalize_priority": 0, + "internal_buffer": "0" + }, + "0x6810e776880c02933d47db1b9fc05908e5386b96": { + "decimals": 18, + "alias": "GNO", + "external_price": 0.1051159378992619, + "normalize_priority": 0, + "internal_buffer": "22283326083484976689" + }, + "0x6b175474e89094c44da98b954eedeac495271d0f": { + "decimals": 18, + "alias": "DAI", + "external_price": 0.00021508661247926934, + "normalize_priority": 0, + "internal_buffer": "8213967696976545926330" + }, + "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2": { + "decimals": 18, + "alias": "SUSHI", + "external_price": 0.0023565017250752507, + "normalize_priority": 0, + "internal_buffer": "532389814464422511800" + }, + "0x72e364f2abdc788b7e918bc238b21f109cd634d7": { + "decimals": 18, + "alias": "MVI", + "external_price": 0.052543323961485415, + "normalize_priority": 0, + "internal_buffer": "3433421993728834413" + }, + "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0": { + "decimals": 18, + "alias": "MATIC", + "external_price": 0.0003711115249774994, + "normalize_priority": 0, + "internal_buffer": "3441170860637545940171" + }, + "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { + "decimals": 18, + "alias": "AAVE", + "external_price": 0.06612446960232675, + "normalize_priority": 0, + "internal_buffer": "27722641716976223893" + }, + "0x86ed939b500e121c0c5f493f399084db596dad20": { + "decimals": 18, + "alias": "SPC", + "external_price": 4.1261511215831115e-06, + "normalize_priority": 0, + "internal_buffer": "32914340028683757797937" + }, + "0x8798249c2e607446efb7ad49ec89dd1865ff4272": { + "decimals": 18, + "alias": "xSUSHI", + "external_price": 0.0028045617645566356, + "normalize_priority": 0, + "internal_buffer": "10636021010421009340" + }, + "0x87d73e916d7057945c9bcd8cdd94e42a6f47f776": { + "decimals": 18, + "alias": "NFTX", + "external_price": 0.026216549031217097, + "normalize_priority": 0, + "internal_buffer": "47438294969856214110" + }, + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { + "decimals": 18, + "alias": null, + "external_price": 0.6197375729429615, + "normalize_priority": 0, + "internal_buffer": "440168254765910888" + }, + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { + "decimals": 6, + "alias": "USDC", + "external_price": 214890212.34875953, + "normalize_priority": 0, + "internal_buffer": "2217249148" + }, + "0xa3d58c4e56fedcae3a7c43a725aee9a71f0ece4e": { + "decimals": 18, + "alias": "MET", + "external_price": 0.001383182798662126, + "normalize_priority": 0, + "internal_buffer": "0" + }, + "0xb6ca7399b4f9ca56fc27cbff44f4d2e4eef1fc81": { + "decimals": 18, + "alias": "MUSE", + "external_price": 0.005638573096791319, + "normalize_priority": 0, + "internal_buffer": "7201165091578979003" + }, + "0xba100000625a3754423978a60c9317c58a424e3d": { + "decimals": 18, + "alias": "BAL", + "external_price": 0.005273380157361497, + "normalize_priority": 0, + "internal_buffer": "195404670885132586236" + }, + "0xbb0e17ef65f82ab018d8edd776e8dd940327b28b": { + "decimals": 18, + "alias": "AXS", + "external_price": 0.031233281341518034, + "normalize_priority": 0, + "internal_buffer": "8542298531938629643" + }, + "0xc00e94cb662c3520282e6f5717214004a7f26888": { + "decimals": 18, + "alias": "COMP", + "external_price": 0.07012814003979417, + "normalize_priority": 0, + "internal_buffer": "502061252574151003" + }, + "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": { + "decimals": 18, + "alias": "SNX", + "external_price": 0.002065836748078299, + "normalize_priority": 0, + "internal_buffer": "48158892818549095716" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "decimals": 18, + "alias": "WETH", + "external_price": 1.0, + "normalize_priority": 1, + "internal_buffer": "895880027660372311" + }, + "0xdac17f958d2ee523a2206206994597c13d831ec7": { + "decimals": 6, + "alias": "USDT", + "external_price": 214523029.31427807, + "normalize_priority": 0, + "internal_buffer": "4227015605" + }, + "0xf5d669627376ebd411e34b98f19c868c8aba5ada": { + "decimals": 18, + "alias": "AXS", + "external_price": 0.0316315369551985, + "normalize_priority": 0, + "internal_buffer": "3240347795546996681" + } + }, + "orders": { + "0": { + "sell_token": "0x6b175474e89094c44da98b954eedeac495271d0f", + "buy_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "sell_amount": "4693994755140375611596", + "buy_amount": "1000000000000000000", + "allow_partial_fill": false, + "is_sell_order": false, + "fee": { + "amount": "103079335446226157568", + "token": "0x6b175474e89094c44da98b954eedeac495271d0f" + }, + "cost": { + "amount": "6657722265694875", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "is_liquidity_order": false + }, + "1": { + "sell_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "buy_token": "0x6b175474e89094c44da98b954eedeac495271d0f", + "sell_amount": "1000000000000000000", + "buy_amount": "4692581049969374626065", + "allow_partial_fill": false, + "is_sell_order": true, + "fee": { + "amount": "23212472598551576", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "cost": { + "amount": "6657722265694875", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "is_liquidity_order": true + } + }, + "amms": { + "0": { + "kind": "ConstantProduct", + "reserves": { + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "145569872903238", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "31203154300783173264676" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "1": { + "kind": "ConstantProduct", + "reserves": { + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "32094048746274", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "32246000595783" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "2": { + "kind": "ConstantProduct", + "reserves": { + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "27888890246655", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "73592" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "3": { + "kind": "ConstantProduct", + "reserves": { + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "22863225777437632054921", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "106902377410356" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "4": { + "kind": "ConstantProduct", + "reserves": { + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "169787956009912606", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "537928202" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "5": { + "kind": "ConstantProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "134160721047695286341", + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "42881473583722456" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "6": { + "kind": "ConstantProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "15888560", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "10186789120" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "7": { + "kind": "ConstantProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "41233803245681607785277120", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "41231078854410" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "8": { + "kind": "ConstantProduct", + "reserves": { + "0xc00e94cb662c3520282e6f5717214004a7f26888": "9926566439412894848842", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "698770885779646213074" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "9": { + "kind": "ConstantProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "3512718386149447605851734", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "3509628668637" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "10": { + "kind": "ConstantProduct", + "reserves": { + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "4998709999204848554923", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "3087597737544666832671" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "11": { + "kind": "ConstantProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "153432658158", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "21112618231915508496108" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "12": { + "kind": "ConstantProduct", + "reserves": { + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "31680019939293183", + "0xc00e94cb662c3520282e6f5717214004a7f26888": "260190569505819239" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "13": { + "kind": "ConstantProduct", + "reserves": { + "0xc00e94cb662c3520282e6f5717214004a7f26888": "8063046894828888", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "2996831" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "14": { + "kind": "ConstantProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "463400567316558373359", + "0xc00e94cb662c3520282e6f5717214004a7f26888": "1369320914588629197" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "15": { + "kind": "ConstantProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "1729370314", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "1113867683775" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "16": { + "kind": "ConstantProduct", + "reserves": { + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "28499713", + "0xc00e94cb662c3520282e6f5717214004a7f26888": "92167578509045372" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "17": { + "kind": "ConstantProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "1035148074", + "0x6b175474e89094c44da98b954eedeac495271d0f": "662274161237620105343822" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "18": { + "kind": "ConstantProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "44897630044876228891318837", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "9626911517235794223708" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "19": { + "kind": "ConstantProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "390690734546", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "53754113629378649935266" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "20": { + "kind": "ConstantProduct", + "reserves": { + "0xc00e94cb662c3520282e6f5717214004a7f26888": "50264319597272926997920", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "3513371413991359073584" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "21": { + "kind": "ConstantProduct", + "reserves": { + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "184849299664303", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "39602141334816390292350" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "22": { + "kind": "ConstantProduct", + "reserves": { + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "21039325251239368172058", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "98245688621558" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "23": { + "kind": "ConstantProduct", + "reserves": { + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "2377024028", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "2369021572" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "24": { + "kind": "ConstantProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "31420234752449186215469", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "31584154870" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "25": { + "kind": "ConstantProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "16770", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "11001944" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "26": { + "kind": "ConstantProduct", + "reserves": { + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": "6745276099673343931735", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "4173255630839005558502" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "27": { + "kind": "ConstantProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "84903768350604287941150958", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "18233677073990818080605" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "28": { + "kind": "ConstantProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "166890692159778890143", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "167471125" + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "29": { + "kind": "WeightedProduct", + "reserves": { + "0xc00e94cb662c3520282e6f5717214004a7f26888": { + "balance": "7023489501333501541452", + "weight": "0.5" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "487792715768814302631", + "weight": "0.5" + } + }, + "fee": "0.0025", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "30": { + "kind": "WeightedProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": { + "balance": "1191959749018354276837", + "weight": "0.4" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "392171457910841840", + "weight": "0.6" + } + }, + "fee": "0.0025", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "32": { + "kind": "WeightedProduct", + "reserves": { + "0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b": { + "balance": "3183163540675204103301", + "weight": "0.333333333333333333" + }, + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { + "balance": "1946028292", + "weight": "0.333333333333333333" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "268258123288872175175", + "weight": "0.333333333333333334" + } + }, + "fee": "0.005", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "33": { + "kind": "WeightedProduct", + "reserves": { + "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { + "balance": "1150", + "weight": "0.125" + }, + "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { + "balance": "1937069", + "weight": "0.125" + }, + "0x514910771af9ca656af840dff83e8264ecf986ca": { + "balance": "1712376", + "weight": "0.125" + }, + "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { + "balance": "139846", + "weight": "0.125" + }, + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { + "balance": "13299", + "weight": "0.125" + }, + "0xc00e94cb662c3520282e6f5717214004a7f26888": { + "balance": "121022", + "weight": "0.125" + }, + "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": { + "balance": "4009385", + "weight": "0.125" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "17365", + "weight": "0.125" + } + }, + "fee": "0.0015", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "34": { + "kind": "WeightedProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { + "balance": "7886892", + "weight": "0.5" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "1126985534364820699", + "weight": "0.5" + } + }, + "fee": "0.0004", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "37": { + "kind": "WeightedProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { + "balance": "244170655453", + "weight": "0.5" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "33602873247647934281998", + "weight": "0.5" + } + }, + "fee": "0.0016", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "38": { + "kind": "WeightedProduct", + "reserves": { + "0x6810e776880c02933d47db1b9fc05908e5386b96": { + "balance": "21330539255670269346", + "weight": "0.25" + }, + "0x6b175474e89094c44da98b954eedeac495271d0f": { + "balance": "10928595376682871418747", + "weight": "0.25" + }, + "0xba100000625a3754423978a60c9317c58a424e3d": { + "balance": "444658133648670940819", + "weight": "0.25" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "2237408990689298635", + "weight": "0.25" + } + }, + "fee": "0.01", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "40": { + "kind": "WeightedProduct", + "reserves": { + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "6158022349654322", + "weight": "0.5" + }, + "0xdac17f958d2ee523a2206206994597c13d831ec7": { + "balance": "23372424", + "weight": "0.5" + } + }, + "fee": "0.001", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "41": { + "kind": "WeightedProduct", + "reserves": { + "0x03ab458634910aad20ef5f1c8ee96f1d6ac54919": { + "balance": "1428563", + "weight": "0.1" + }, + "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { + "balance": "84", + "weight": "0.07" + }, + "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { + "balance": "153848", + "weight": "0.1" + }, + "0x61d5dc44849c9c87b0856a2a311536205c96c7fd": { + "balance": "620", + "weight": "0.1" + }, + "0x6b175474e89094c44da98b954eedeac495271d0f": { + "balance": "11451302", + "weight": "0.15" + }, + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { + "balance": "1", + "weight": "0.13" + }, + "0xba100000625a3754423978a60c9317c58a424e3d": { + "balance": "205095", + "weight": "0.2" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "3853", + "weight": "0.15" + } + }, + "fee": "0.005", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "42": { + "kind": "WeightedProduct", + "reserves": { + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { + "balance": "17463225691118", + "weight": "0.5" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "3757802158500106767356", + "weight": "0.5" + } + }, + "fee": "0.0022", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "43": { + "kind": "WeightedProduct", + "reserves": { + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "78016391530210266667", + "weight": "0.5" + }, + "0xdac17f958d2ee523a2206206994597c13d831ec7": { + "balance": "362998299778", + "weight": "0.5" + } + }, + "fee": "0.0022", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "44": { + "kind": "WeightedProduct", + "reserves": { + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { + "balance": "7831731473578894597621", + "weight": "0.6" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "3247980137175769019160", + "weight": "0.4" + } + }, + "fee": "0.0027", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "46": { + "kind": "WeightedProduct", + "reserves": { + "0x18aaa7115705e8be94bffebde57af9bfc265b998": { + "balance": "60387271452376666026", + "weight": "0.125" + }, + "0x3845badade8e6dff049820680d1f14bd3903a5d0": { + "balance": "126370092106264106317", + "weight": "0.125" + }, + "0x72e364f2abdc788b7e918bc238b21f109cd634d7": { + "balance": "973866726433230259", + "weight": "0.125" + }, + "0x87d73e916d7057945c9bcd8cdd94e42a6f47f776": { + "balance": "568421440530172583", + "weight": "0.125" + }, + "0xb6ca7399b4f9ca56fc27cbff44f4d2e4eef1fc81": { + "balance": "2997023749294327812", + "weight": "0.125" + }, + "0xbb0e17ef65f82ab018d8edd776e8dd940327b28b": { + "balance": "1295943489744034642", + "weight": "0.125" + }, + "0xc00e94cb662c3520282e6f5717214004a7f26888": { + "balance": "297423910895795177", + "weight": "0.125" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "42300385807555997", + "weight": "0.125" + } + }, + "fee": "0.01", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "47": { + "kind": "WeightedProduct", + "reserves": { + "0x1b40183efb4dd766f11bda7a7c3ad8982e998421": { + "balance": "5861", + "weight": "0.1" + }, + "0x6b175474e89094c44da98b954eedeac495271d0f": { + "balance": "50373", + "weight": "0.1" + }, + "0x86ed939b500e121c0c5f493f399084db596dad20": { + "balance": "9352828", + "weight": "0.6" + }, + "0xa3d58c4e56fedcae3a7c43a725aee9a71f0ece4e": { + "balance": "11459", + "weight": "0.1" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "16", + "weight": "0.1" + } + }, + "fee": "0.005", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "48": { + "kind": "WeightedProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { + "balance": "1", + "weight": "0.33" + }, + "0x677ddbd918637e5f2c79e164d402454de7da8619": { + "balance": "185143579", + "weight": "0.34" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "51158", + "weight": "0.33" + } + }, + "fee": "0.005", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "50": { + "kind": "WeightedProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": { + "balance": "44761228399171133055581730", + "weight": "0.4" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "14407839606689791576732", + "weight": "0.6" + } + }, + "fee": "0.0021", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "51": { + "kind": "WeightedProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { + "balance": "1", + "weight": "0.5" + }, + "0x35a18000230da775cac24873d00ff85bccded550": { + "balance": "1", + "weight": "0.01" + }, + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { + "balance": "1", + "weight": "0.49" + } + }, + "fee": "0.0015", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "52": { + "kind": "WeightedProduct", + "reserves": { + "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { + "balance": "6472412946763199832", + "weight": "0.125" + }, + "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { + "balance": "8600510230735290526694", + "weight": "0.125" + }, + "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2": { + "balance": "19557650598562732453526", + "weight": "0.125" + }, + "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { + "balance": "689193422058489015058", + "weight": "0.125" + }, + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { + "balance": "73222575681890606005", + "weight": "0.125" + }, + "0xba100000625a3754423978a60c9317c58a424e3d": { + "balance": "8848294720356512217529", + "weight": "0.125" + }, + "0xc00e94cb662c3520282e6f5717214004a7f26888": { + "balance": "653880188325182856471", + "weight": "0.125" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "45015549375580771613", + "weight": "0.125" + } + }, + "fee": "0.0015", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "53": { + "kind": "WeightedProduct", + "reserves": { + "0x04fa0d235c4abf4bcf4787af4cf447de572ef828": { + "balance": "4562300116085354789", + "weight": "0.125" + }, + "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { + "balance": "1256657629424214", + "weight": "0.125" + }, + "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { + "balance": "2107829214730689206", + "weight": "0.125" + }, + "0x514910771af9ca656af840dff83e8264ecf986ca": { + "balance": "1949409240792221736", + "weight": "0.125" + }, + "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { + "balance": "154339756888995831", + "weight": "0.125" + }, + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { + "balance": "17270757086778536", + "weight": "0.125" + }, + "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": { + "balance": "5313162734654351870", + "weight": "0.125" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "18972472132080956", + "weight": "0.125" + } + }, + "fee": "0.0015", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "54": { + "kind": "WeightedProduct", + "reserves": { + "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": { + "balance": "6927054066178298", + "weight": "0.125" + }, + "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0": { + "balance": "155376214405830998645", + "weight": "0.125" + }, + "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { + "balance": "652171084905308585", + "weight": "0.125" + }, + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { + "balance": "88653946919383609", + "weight": "0.125" + }, + "0xba100000625a3754423978a60c9317c58a424e3d": { + "balance": "11479463481449338709", + "weight": "0.125" + }, + "0xc00e94cb662c3520282e6f5717214004a7f26888": { + "balance": "642744083856292483", + "weight": "0.125" + }, + "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": { + "balance": "22056893760322547128", + "weight": "0.125" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "65185802710414326", + "weight": "0.125" + } + }, + "fee": "0.01", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "55": { + "kind": "WeightedProduct", + "reserves": { + "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": { + "balance": "7106964898819589313", + "weight": "0.125" + }, + "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2": { + "balance": "12856221384869336968", + "weight": "0.125" + }, + "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": { + "balance": "458179995950018385", + "weight": "0.125" + }, + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": { + "balance": "50480619372569735", + "weight": "0.125" + }, + "0xba100000625a3754423978a60c9317c58a424e3d": { + "balance": "7252864916011326167", + "weight": "0.125" + }, + "0xc00e94cb662c3520282e6f5717214004a7f26888": { + "balance": "475828038408280564", + "weight": "0.125" + }, + "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": { + "balance": "14837495085729431304", + "weight": "0.125" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "46988251681603941", + "weight": "0.125" + } + }, + "fee": "0.005", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "56": { + "kind": "WeightedProduct", + "reserves": { + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { + "balance": "100000", + "weight": "0.4" + }, + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { + "balance": "31791255", + "weight": "0.2" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "17015250000000000", + "weight": "0.4" + } + }, + "fee": "0.0055", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "57": { + "kind": "WeightedProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": { + "balance": "28595840", + "weight": "0.5" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "8809", + "weight": "0.5" + } + }, + "fee": "0.005", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + }, + "58": { + "kind": "Stable", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "57452498438246071785170074", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "53340234019884", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "46859260361900" + }, + "scaling_rates": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "1000000000000000000", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "1000000", + "0xdac17f958d2ee523a2206206994597c13d831ec7": "1000000" + }, + "amplification_parameter": "620", + "fee": "0.0001", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "mandatory": false + } + }, + "metadata": { + "environment": "xDAI", + "auction_id": 20, + "gas_price": 4850000000.0, + "native_token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + } } \ No newline at end of file diff --git a/data/small_example.json b/data/small_example.json index 5e0cc29..fd1fae8 100644 --- a/data/small_example.json +++ b/data/small_example.json @@ -1,314 +1,314 @@ -{ - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "decimals": 18, - "alias": "COW", - "external_price": 0.45017049310571666, - "normalize_priority": 0, - "internal_buffer": "7015800954345235918043" - }, - "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": { - "decimals": 18, - "alias": "WETH", - "external_price": 3000.891285415493, - "normalize_priority": 0, - "internal_buffer": "573049294721507372" - }, - "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": { - "decimals": 6, - "alias": "USDC", - "external_price": null, - "normalize_priority": 0, - "internal_buffer": "623285390" - }, - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": { - "decimals": 18, - "alias": "WXDAI", - "external_price": 1.0, - "normalize_priority": 1, - "internal_buffer": "1462392042995826309875" - } - }, - "orders": { - "0": { - "sell_token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1", - "buy_token": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "sell_amount": "12000000000000000000", - "buy_amount": "100000000000000000000", - "allow_partial_fill": false, - "is_sell_order": true, - "fee": { - "amount": "115995469750", - "token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1" - }, - "cost": { - "amount": "321627750000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "is_liquidity_order": false, - "mandatory": false, - "has_atomic_execution": false - }, - "1": { - "sell_token": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "buy_token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1", - "sell_amount": "100000000000000000000", - "buy_amount": "12000000000000000000", - "allow_partial_fill": false, - "is_sell_order": true, - "fee": { - "amount": "115995469750", - "token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1" - }, - "cost": { - "amount": "321627750000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "is_liquidity_order": false, - "mandatory": false, - "has_atomic_execution": false - } - }, - "amms": { - "0": { - "kind": "ConstantProduct", - "reserves": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "2287989128381591343", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "1007038230838126034" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "1": { - "kind": "ConstantProduct", - "reserves": { - "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "2180532944296", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "2174634715149124817637856" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "2": { - "kind": "ConstantProduct", - "reserves": { - "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "170156453502161835", - "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "511510625" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "3": { - "kind": "ConstantProduct", - "reserves": { - "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "14078483442516189840", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "42448974815042756432777" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "4": { - "kind": "ConstantProduct", - "reserves": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "57085873974360172", - "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "9811310893524" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "5": { - "kind": "ConstantProduct", - "reserves": { - "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "203595149362", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "203435681684302344278234" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "6": { - "kind": "ConstantProduct", - "reserves": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "68751669412615145943", - "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "30941870" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "7": { - "kind": "ConstantProduct", - "reserves": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "1351303562283649601", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "594705071040441228" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "8": { - "kind": "ConstantProduct", - "reserves": { - "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "8924746518352797499", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "26876201972560924233860" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "9": { - "kind": "ConstantProduct", - "reserves": { - "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "1116060088457271", - "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "3382413" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "10": { - "kind": "ConstantProduct", - "reserves": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "431012081242526", - "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "637" - }, - "fee": "0.0025", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "11": { - "kind": "ConstantProduct", - "reserves": { - "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "693310602896589672028", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "2097830773557624263396314" - }, - "fee": "0.0025", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "12": { - "kind": "ConstantProduct", - "reserves": { - "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "232855103028", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "232878267041239073118599" - }, - "fee": "0.0025", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "13": { - "kind": "ConstantProduct", - "reserves": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "155455141264616753008", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "70000266796002910413" - }, - "fee": "0.0025", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "14": { - "kind": "ConstantProduct", - "reserves": { - "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "1146448176615081", - "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "3458045" - }, - "fee": "0.0025", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "15": { - "kind": "ConstantProduct", - "reserves": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "1368237000249912531662317", - "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "205800069191081940356" - }, - "fee": "0.0025", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "16": { - "kind": "ConstantProduct", - "reserves": { - "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "2121757817602532415", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "6388542347053342159973" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - }, - "17": { - "kind": "ConstantProduct", - "reserves": { - "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "4080009153", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "4070477982741939388872" - }, - "fee": "0.003", - "cost": { - "amount": "459275600000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - }, - "mandatory": false - } - }, - "metadata": { - "environment": "xDAI", - "auction_id": 20, - "gas_price": 4850000000.0, - "native_token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" - } -} +{ + "tokens": { + "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { + "decimals": 18, + "alias": "COW", + "external_price": 0.45017049310571666, + "normalize_priority": 0, + "internal_buffer": "7015800954345235918043" + }, + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": { + "decimals": 18, + "alias": "WETH", + "external_price": 3000.891285415493, + "normalize_priority": 0, + "internal_buffer": "573049294721507372" + }, + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": { + "decimals": 6, + "alias": "USDC", + "external_price": null, + "normalize_priority": 0, + "internal_buffer": "623285390" + }, + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": { + "decimals": 18, + "alias": "WXDAI", + "external_price": 1.0, + "normalize_priority": 1, + "internal_buffer": "1462392042995826309875" + } + }, + "orders": { + "0": { + "sell_token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1", + "buy_token": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", + "sell_amount": "12000000000000000000", + "buy_amount": "100000000000000000000", + "allow_partial_fill": false, + "is_sell_order": true, + "fee": { + "amount": "115995469750", + "token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1" + }, + "cost": { + "amount": "321627750000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "is_liquidity_order": false, + "mandatory": false, + "has_atomic_execution": false + }, + "1": { + "sell_token": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", + "buy_token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1", + "sell_amount": "100000000000000000000", + "buy_amount": "12000000000000000000", + "allow_partial_fill": false, + "is_sell_order": true, + "fee": { + "amount": "115995469750", + "token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1" + }, + "cost": { + "amount": "321627750000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "is_liquidity_order": false, + "mandatory": false, + "has_atomic_execution": false + } + }, + "amms": { + "0": { + "kind": "ConstantProduct", + "reserves": { + "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "2287989128381591343", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "1007038230838126034" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "1": { + "kind": "ConstantProduct", + "reserves": { + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "2180532944296", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "2174634715149124817637856" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "2": { + "kind": "ConstantProduct", + "reserves": { + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "170156453502161835", + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "511510625" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "3": { + "kind": "ConstantProduct", + "reserves": { + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "14078483442516189840", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "42448974815042756432777" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "4": { + "kind": "ConstantProduct", + "reserves": { + "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "57085873974360172", + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "9811310893524" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "5": { + "kind": "ConstantProduct", + "reserves": { + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "203595149362", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "203435681684302344278234" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "6": { + "kind": "ConstantProduct", + "reserves": { + "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "68751669412615145943", + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "30941870" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "7": { + "kind": "ConstantProduct", + "reserves": { + "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "1351303562283649601", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "594705071040441228" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "8": { + "kind": "ConstantProduct", + "reserves": { + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "8924746518352797499", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "26876201972560924233860" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "9": { + "kind": "ConstantProduct", + "reserves": { + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "1116060088457271", + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "3382413" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "10": { + "kind": "ConstantProduct", + "reserves": { + "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "431012081242526", + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "637" + }, + "fee": "0.0025", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "11": { + "kind": "ConstantProduct", + "reserves": { + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "693310602896589672028", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "2097830773557624263396314" + }, + "fee": "0.0025", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "12": { + "kind": "ConstantProduct", + "reserves": { + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "232855103028", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "232878267041239073118599" + }, + "fee": "0.0025", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "13": { + "kind": "ConstantProduct", + "reserves": { + "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "155455141264616753008", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "70000266796002910413" + }, + "fee": "0.0025", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "14": { + "kind": "ConstantProduct", + "reserves": { + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "1146448176615081", + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "3458045" + }, + "fee": "0.0025", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "15": { + "kind": "ConstantProduct", + "reserves": { + "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "1368237000249912531662317", + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "205800069191081940356" + }, + "fee": "0.0025", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "16": { + "kind": "ConstantProduct", + "reserves": { + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1": "2121757817602532415", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "6388542347053342159973" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + }, + "17": { + "kind": "ConstantProduct", + "reserves": { + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83": "4080009153", + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "4070477982741939388872" + }, + "fee": "0.003", + "cost": { + "amount": "459275600000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + }, + "mandatory": false + } + }, + "metadata": { + "environment": "xDAI", + "auction_id": 20, + "gas_price": 4850000000.0, + "native_token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + } +} diff --git a/mypy.ini b/mypy.ini index eefb839..942f811 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,13 +1,13 @@ -[mypy] -python_version = 3.10 - - -[mypy-src.*] -allow_untyped_calls = True -allow_any_generics = True - -[mypy-uvicorn.*] -ignore_missing_imports = True - -[mypy-fastapi] +[mypy] +python_version = 3.10 + + +[mypy-src.*] +allow_untyped_calls = True +allow_any_generics = True + +[mypy-uvicorn.*] +ignore_missing_imports = True + +[mypy-fastapi] implicit_reexport = True \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c439d6e..df24a12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ -black==22.3.0 -fastapi==0.65.2 -uvicorn==0.17.6 -pylint==2.13.5 -python-dotenv==0.20.0 -pydantic==1.9.0 -pytest==7.1.1 -makefun==1.13.1 -mypy==0.942 +black==22.3.0 +fastapi==0.65.2 +uvicorn==0.17.6 +pylint==2.13.5 +python-dotenv==0.20.0 +pydantic==1.9.0 +pytest==7.1.1 +makefun==1.13.1 +mypy==0.942 diff --git a/src/_server.py b/src/_server.py index 34dbdab..512f5ba 100644 --- a/src/_server.py +++ b/src/_server.py @@ -1,158 +1,230 @@ -""" -This is the project's Entry point. -""" -from __future__ import annotations - -import argparse -import decimal -import logging - -import uvicorn -from dotenv import load_dotenv -from src.util.numbers import decimal_to_str -from fastapi import FastAPI, Request -from fastapi.middleware.gzip import GZipMiddleware -from pydantic import BaseSettings - -from src.models.batch_auction import BatchAuction -from src.models.solver_args import SolverArgs -from src.util.schema import ( - BatchAuctionModel, - SettledBatchAuctionModel, -) - -# Set decimal precision. -decimal.getcontext().prec = 100 - -# Holds parameters passed on the command line when invoking the server. -# These will be merged with request solver parameters -SERVER_ARGS = None - - -# ++++ Interface definition ++++ - - -# Server settings: Can be overridden by passing them as env vars or in a .env file. -# Example: PORT=8001 python -m src._server -class ServerSettings(BaseSettings): - """Basic Server Settings""" - - host: str = "0.0.0.0" - port: int = 8000 - - -server_settings = ServerSettings() - -# ++++ Endpoints: ++++ - - -app = FastAPI(title="Batch auction solver") -app.add_middleware(GZipMiddleware) - - -@app.get("/health", status_code=200) -def health() -> bool: - """Convenience endpoint to check if server is alive.""" - return True - -async def fetch_best_rates(batch: BatchAuction): - url = "http://central-server-url/bestRates" - payload = { - "sellTokenAddress": [order.sell_token for order in batch.orders], - "buyTokenAddress": [order.buy_token for order in batch.orders], - "sellTokenAmount": [order.sell_amount for order in batch.orders], - "user": "optional_user_address" # if needed - } - response = requests.post(url, json=payload) - if response.status_code == 200: - return response.json() - else: - raise Exception("Failed to fetch best rates") - -def generate_solution(batch: BatchAuction): - return { - "ref_token": batch.ref_token.value, - "orders": {order.order_id: order.as_dict() for order in batch.orders if order.is_executed()}, - "prices": {str(key): decimal_to_str(value) for key, value in batch.prices.items()}, - "amms": {}, - "prices": {}, - "approvals": [], - "interaction_data": [], - "score": "0", - } - -@app.post("/notify", response_model=bool) -async def notify(request: Request) -> bool: - """Print response from notify endpoint.""" - print(f"Notify request {await request.json()}") - return True - - -@app.post("/solve", response_model=SettledBatchAuctionModel) -async def solve(problem: BatchAuctionModel, request: Request): # type: ignore - """API POST solve endpoint handler""" - logging.debug(f"Received solve request {await request.json()}") - solver_args = SolverArgs.from_request(request=request, meta=problem.metadata) - - batch = BatchAuction.from_dict(problem.dict(), solver_args.instance_name) - - # Fetch best rates for each token pair involved in the auction - best_rates = await fetch_best_rates(batch) - - # Update batch auction with the fetched rates - update_batch_with_best_rates(batch, best_rates) - - print("Received Batch Auction", batch.name) - print("Parameters Supplied", solver_args) - - # 1. Solve BatchAuction: update batch_auction with - batch.solve() - print("in solve",99) - - trivial_solution = { - "ref_token": batch.ref_token.value, - "orders": {order.order_id: order.as_dict() for order in batch.orders if order.is_executed() }, - "prices": {str(key): decimal_to_str(value) for key, value in batch.prices.items()}, - "amms": {}, - "prices": {}, - "approvals": [], - "interaction_data": [], - "score": "0", - } - - print("\n\n*************\n\nReturning solution: " + str(trivial_solution)) - return trivial_solution - - -# ++++ Server setup: ++++ - - -if __name__ == "__main__": - load_dotenv() - - parser = argparse.ArgumentParser( - fromfile_prefix_chars="@", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - # TODO - enable flag to write files to persistent storage - # parser.add_argument( - # "--write_auxiliary_files", - # type=bool, - # default=False, - # help="Write auxiliary instance and optimization files, or not.", - # ) - - parser.add_argument( - "--log-level", - type=str, - default="info", - help="Log level", - ) - - SERVER_ARGS = parser.parse_args() - uvicorn.run( - "__main__:app", - host=server_settings.host, - port=server_settings.port, - log_level=SERVER_ARGS.log_level, - ) +""" +This is the project's Entry point. +""" +from __future__ import annotations + +import argparse +import decimal +import logging +import requests +import asyncio # Ensure requests is imported +import httpx + + +import uvicorn +from dotenv import load_dotenv +from src.util.numbers import decimal_to_str +from fastapi import FastAPI, Request , HTTPException +from fastapi.middleware.gzip import GZipMiddleware +from pydantic import BaseSettings + +from src.models.batch_auction import BatchAuction +from src.models.solver_args import SolverArgs +from src.util.schema import ( + BatchAuctionModel, + SettledBatchAuctionModel, +) + +# Set decimal precision. +decimal.getcontext().prec = 100 + +# Holds parameters passed on the command line when invoking the server. +# These will be merged with request solver parameters +SERVER_ARGS = None + + +# ++++ Interface definition ++++ + + +# Server settings: Can be overridden by passing them as env vars or in a .env file. +# Example: PORT=8001 python -m src._server +class ServerSettings(BaseSettings): + """Basic Server Settings""" + host: str = "0.0.0.0" + port: int = 8000 + +server_settings = ServerSettings() + +# Example Token and Order class definitions +class Token: + def __init__(self, address): + self.address = address + +class Order: + def __init__(self, sell_token, buy_token, sell_amount, buy_amount): + self.sell_token = sell_token + self.buy_token = buy_token + self.sell_amount = sell_amount + self.buy_amount = buy_amount + +class BatchAuction: + def __init__(self, tokens, orders): + self.tokens = tokens + self.orders = orders + +# Token instances +token_weth = Token("0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1") +token_cow = Token("0x177127622c4a00f3d409b75571e12cb3c8973d3c") + +# Order instances using the above tokens +order1 = Order( + sell_token=token_weth, + buy_token=token_cow, + sell_amount="12000000000000000000", + buy_amount="100000000000000000000" +) +order2 = Order( + sell_token=token_cow, + buy_token=token_weth, + sell_amount="100000000000000000000", + buy_amount="12000000000000000000" +) + +# List of tokens and orders +tokens = [token_weth, token_cow] +orders = [order1, order2] + +batch_auction = BatchAuction(tokens, orders) + +def display_auction_details(batch): + for order in batch.orders: + print(f"Sell Token: {order.sell_token.address}, Buy Token: {order.buy_token.address}, Sell Amount: {order.sell_amount}, Buy Amount: {order.buy_amount}") + +# Display details of the batch auction +display_auction_details(batch_auction) +# ++++ Endpoints: ++++ + + + + + +app = FastAPI(title="Batch auction solver") +app.add_middleware(GZipMiddleware) + + +class ServerSettings(BaseSettings): + """Basic Server Settings""" + host: str = "0.0.0.0" + port: int = 8000 + central_server_url: str = "http://localhost:3000" + +server_settings = ServerSettings() + +@app.on_event("startup") +async def startup_event(): + await main() + # """Event that runs at the startup of the FastAPI application.""" + # batch = create_batch_auction() + # best_rates = await fetch_best_rates_from_central_server(batch) + # if best_rates: + # update_batch_with_best_rates(batch, best_rates) + # print("Startup Event Completed: Best rates fetched and batch updated.") + +@app.get("/health", status_code=200) +def health() -> bool: + """Convenience endpoint to check if server is alive.""" + return True + +@app.post("/notify", response_model=bool) +async def notify(request: Request) -> bool: + """Print response from notify endpoint.""" + print(f"Notify request {await request.json()}") + return True + +# Fetch best rates function +# async def fetch_best_rates_from_central_server(sell_tokens, buy_tokens, sell_amounts): +async def fetch_best_rates_from_central_server(sell_tokens, buy_tokens, sell_amounts, user_address): + url = "http://localhost:3000/bestRates" + payload = { + "sellTokenAddress": [token.address for token in sell_tokens], + "buyTokenAddress": [token.address for token in buy_tokens], + "sellTokenAmount": sell_amounts, + "user": user_address + } + async with httpx.AsyncClient() as client: + response = await client.post(url, json=payload) + if response.status_code == 200: + return response.json() + else: + raise HTTPException(status_code=response.status_code, detail="Failed to fetch best rates from central server") + +# # Function to update batch auction with fetched rates +def update_batch_with_best_rates(batch: BatchAuction, best_rates): + # Example logic to update orders based on fetched rates + for rate in best_rates: + # Assuming each rate item contains 'order_id' and new 'sell_amount' and 'buy_amount' + order = next((order for order in batch.orders if order.order_id == rate['order_id']), None) + if order: + order.update_rate(rate['new_sell_amount'], rate['new_buy_amount']) + +# # Function to generate the solution +def generate_solution(batch: BatchAuction): + return { + "ref_token": batch.ref_token.value, + "orders": {order.order_id: order.as_dict() for order in batch.orders if order.is_executed()}, + "prices": {str(key): decimal_to_str(value) for key, value in batch.prices.items()}, + "amms": {}, + "prices": {}, + "approvals": [], + "interaction_data": [], + "score": "0", + } + +@app.post("/solve", response_model=SettledBatchAuctionModel) +async def solve(problem: BatchAuctionModel, request: Request): + logging.debug(f"Received solve request {await request.json()}") + solver_args = SolverArgs.from_request(request=request, meta=problem.metadata) + batch = BatchAuction.from_dict(problem.dict(), solver_args.instance_name) + + # Fetch and apply best rates + best_rates = await fetch_best_rates_from_central_server( + [order.sell_token for order in batch.orders], + [order.buy_token for order in batch.orders], + [order.sell_amount for order in batch.orders] + ) + if best_rates: + update_batch_with_best_rates(batch, best_rates) + + batch.solve() + trivial_solution = generate_solution(batch) + print("\n\n*************\n\nReturning solution: " + str(trivial_solution)) + return trivial_solution + +def create_batch_auction() -> BatchAuction: + """Create a BatchAuction object with predefined tokens and orders.""" + token_weth = Token("0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1") + token_cow = Token("0x177127622c4a00f3d409b75571e12cb3c8973d3c") + order1 = Order(token_weth, token_cow, "12000000000000000000", "100000000000000000000") + order2 = Order(token_cow, token_weth, "100000000000000000000", "12000000000000000000") + return BatchAuction([token_weth, token_cow], [order1, order2]) + + +async def main(): + # Initialize tokens and orders + token_weth = Token("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c") + token_cow = Token("0xCC42724C6683B7E57334c4E856f4c9965ED682bD") + order1 = Order(token_weth, token_cow, "1000000000000", "5000000000000") + + # Create a batch auction with these tokens and orders + batch = BatchAuction([token_weth, token_cow], [order1]) + + # User address, can also be configured via environment variables + user_address = "0xE7C7f2E2D17FF5B1c0e291d5b3e37Fe56b858227" + + # Fetch rates + best_rates = await fetch_best_rates_from_central_server( + [order.sell_token for order in batch.orders], + [order.buy_token for order in batch.orders], + [order.sell_amount for order in batch.orders], + user_address + ) + print("Best Rates:", best_rates) + +if __name__ == "__main__": + load_dotenv() + parser = argparse.ArgumentParser() + parser.add_argument("--log-level", type=str, default="info", help="Log level") + SERVER_ARGS = parser.parse_args() + uvicorn.run("__main__:app", host=server_settings.host, port=server_settings.port, log_level=SERVER_ARGS.log_level) + # asyncio.run(main()) + diff --git a/src/models/batch_auction.py b/src/models/batch_auction.py index 3a2936b..ca603ad 100644 --- a/src/models/batch_auction.py +++ b/src/models/batch_auction.py @@ -1,315 +1,315 @@ -""" -Model containing BatchAuction which is what solvers operate on. -""" - -from __future__ import annotations -import decimal -import logging -from decimal import Decimal -from typing import Any, Optional - -from src.models.order import Order, OrdersSerializedType -from src.models.order import Order, OrderMatchType -from src.models.token import ( - Token, - TokenInfo, - select_token_with_highest_normalize_priority, - TokenDict, - TokenSerializedType, -) -from src.models.types import NumericType -from src.models.uniswap import Uniswap, UniswapsSerializedType -from src.util.enums import Chain - - -class BatchAuction: - """Class to represent a batch auction.""" - - def __init__( - self, - tokens: dict[Token, TokenInfo], - orders: dict[str, Order], - uniswaps: dict[str, Uniswap], - ref_token: Token, - prices: Optional[dict] = None, - name: str = "batch_auction", - metadata: Optional[dict] = None, - ): - """Initialize. - Args: - tokens: dict of tokens participating. - orders: dict of Order objects. - uniswaps: dict of Uniswap objects. - ref_token: Reference Token object. - prices: A dict of {token -> price}. - name: Name of the batch auction instance. - metadata: Some instance metadata. - """ - self.name = name - self.metadata = metadata if metadata else {} - - self._tokens = tokens - self._orders = orders - self._uniswaps = uniswaps - - # Store reference token and (previous) prices. - self.ref_token = ref_token - self.prices = ( - prices if prices else {ref_token: self._tokens[ref_token].external_price} - ) - - @classmethod - def from_dict(cls, data: dict, name: str) -> BatchAuction: - """Read a batch auction instance from a python dictionary. - - Args: - data: Python dict to be read. - name: Instance name. - - Returns: - The instance. - - """ - for key in ["tokens", "orders"]: - if key not in data: - raise ValueError(f"Mandatory field '{key}' missing in instance data!") - - tokens = load_tokens(data["tokens"]) - orders = load_orders(data["orders"]) - - uniswaps = load_amms(data.get("amms", {})) - metadata = load_metadata(data.get("metadata", {})) - prices = load_prices(data.get("prices", {})) - - ref_token = select_token_with_highest_normalize_priority(tokens) - - return cls( - tokens, - orders, - uniswaps, - ref_token, - prices=prices, - metadata=metadata, - name=name, - ) - - #################### - # ACCESS METHODS # - #################### - - @property - def tokens(self) -> list[Token]: - """Access to (sorted) token list.""" - return sorted(self._tokens.keys()) - - @property - def orders(self) -> list[Order]: - """Access to order list.""" - return list(self._orders.values()) - - @property - def uniswaps(self) -> list[Uniswap]: - """Access to uniswap list.""" - return list(self._uniswaps.values()) - - def token_info(self, token: Token) -> TokenInfo: - """Get the token info for a specific token.""" - assert isinstance(token, Token) - - if token not in self.tokens: - raise ValueError(f"Token <{token}> not in batch auction!") - - return self._tokens[token] - - @property - def chain(self) -> Chain: - """Return the blockchain on which the BatchAuction happens.""" - if self.ref_token is None: - return Chain.UNKNOWN - ref = self.ref_token.value.lower() - if ref == "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": - return Chain.MAINNET - if ref == "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": - return Chain.XDAI - return Chain.UNKNOWN - - @property - def default_ref_token_price(self) -> Decimal: - """Price of reference token if not given explicitly. - - This price is chosen so that the price of one unit of - a token with d_t=18 decimals that costs one unit of the - reference token is p_t=10^18: - - a_t * p_t * 10^d_t = a_r * p_r * 10^d_r - - with: - a_t/a_r = 1 - d_t = 18 - p_t = 10^18 - - p_r = a_t/a_r * 10^18 * 10^18 / 10^d_r - <-> p_r = 10^(2 * 18 - d_r) - """ - return Decimal(10) ** (2 * 18 - self.token_info(self.ref_token).decimals) - - def solve(self) -> None: - """Solve Batch""" - orders = self.orders - print("in solve",len(orders)) - for i in range(len(orders)-1): - print("in solve",i) - for j in range(i+1,len(orders)): - print("in solve",len(orders)) - order_i, order_j = orders[i], orders[j] - if order_i.match_type(order_j) == OrderMatchType.BOTH_FILLED: - order_i.execute( - buy_amount_value=order_j.sell_amount, - sell_amount_value=order_j.buy_amount - ) - order_j.execute( - buy_amount_value=order_i.sell_amount, - sell_amount_value=order_i.buy_amount - ) - token_a = self.token_info(order_i.sell_token) - token_b = self.token_info(order_i.buy_token) - self.prices[token_a.token] = order_j.sell_amount - self.prices[token_b.token] = order_i.sell_amount - return - - ################################# - # SOLUTION PROCESSING METHODS # - ################################# - - def __str__(self) -> str: - """Print batch auction data. - - Returns: - The string representation. - - """ - output_str = "BATCH AUCTION:" - - output_str += f"\n=== TOKENS ({len(self.tokens)}) ===" - for token in self.tokens: - output_str += f"\n-- {token}" - - output_str += f"\n=== ORDERS ({len(self.orders)}) ===" - for order in self.orders: - output_str += f"\n{order}" - - output_str += f"\n=== UNISWAPS ({len(self.uniswaps)}) ===" - for uni in self.uniswaps: - output_str += f"\n{uni}" - - return output_str - - def __repr__(self) -> str: - """Print batch auction data.""" - return self.name - -def update_batch_with_best_rates(batch: BatchAuction, best_rates): - for rate in best_rates: - order = batch.orders[rate['order_id']] - order.update_rate(rate['new_sell_amount'], rate['new_buy_amount']) - -def load_metadata(metadata: dict[str, Any]) -> dict[str, Any]: - """Store some basic metadata information.""" - metadata["scaling_factors"] = { - Token(t): Decimal(f) for t, f in metadata.get("scaling_factors", {}).items() - } - - return metadata - - -def load_prices( - prices_serialized: dict[TokenSerializedType, NumericType] -) -> dict[Token, Decimal]: - """Load token price information as dict of Token -> Decimal.""" - if not isinstance(prices_serialized, dict): - raise ValueError( - f"Prices must be given as dict, not {type(prices_serialized)}!" - ) - return {Token(t): Decimal(p) for t, p in prices_serialized.items()} - - -def load_orders(orders_serialized: OrdersSerializedType) -> dict[str, Order]: - """Load dict of orders as order pool_id -> order data. - - Args: - orders_serialized: dict of order_id -> order data dict. - - Returns: - A list of Order objects. - """ - order_list = [ - Order.from_dict(order_id, data) for order_id, data in orders_serialized.items() - ] - result: dict[str, Order] = {} - for order in order_list: - if order.order_id in result: - raise ValueError(f"Order pool_id <{order.order_id}> already exists!") - result[order.order_id] = order - return result - - -def load_amms(amms_serialized: UniswapsSerializedType) -> dict[str, Uniswap]: - """Load list of AMMs. - - NOTE: Currently, the code only supports Uniswap-style AMMs, i.e., - constant-product pools with two tokens and equal weights. - - Args: - amms_serialized: dict of pool_id -> AMM. - - Returns: - A list of Uniswap objects. - - """ - amm_list = [] - for amm_id, amm_data in amms_serialized.items(): - amm = Uniswap.from_dict(amm_id, amm_data) - if amm is not None: - amm_list.append(amm) - - results: dict[str, Uniswap] = {} - for uni in amm_list: - if uni.pool_id in results: - raise ValueError(f"Uniswap pool_id <{uni.pool_id}> already exists!") - results[uni.pool_id] = uni - - return results - - -def load_tokens(tokens_serialized: dict) -> TokenDict: - """Store tokens as sorted dictionary from Token -> token info. - - Args: - tokens_serialized: list or dict of tokens. - - Returns: - A dict of Token -> token info. - - """ - tokens_dict = {} - for token_str, token_info in sorted(tokens_serialized.items()): - token = Token(token_str) - if token_info is None: - token_info = {} - else: - for k, val in token_info.items(): - if val is None: - continue - try: - # Convert to str first to avoid float-precision artifacts: - # Decimal(0.1) -> Decimal('0.10000000000000000555...') - # Decimal('0.1') -> Decimal(0.1) - val = Decimal(str(val)) - except decimal.InvalidOperation: - pass - token_info[k] = val - if token in tokens_dict: - logging.warning(f"Token <{token}> already exists!") - tokens_dict[token] = TokenInfo(token, **token_info) - - return tokens_dict +""" +Model containing BatchAuction which is what solvers operate on. +""" + +from __future__ import annotations +import decimal +import logging +from decimal import Decimal +from typing import Any, Optional + +from src.models.order import Order, OrdersSerializedType +from src.models.order import Order, OrderMatchType +from src.models.token import ( + Token, + TokenInfo, + select_token_with_highest_normalize_priority, + TokenDict, + TokenSerializedType, +) +from src.models.types import NumericType +from src.models.uniswap import Uniswap, UniswapsSerializedType +from src.util.enums import Chain + + +class BatchAuction: + """Class to represent a batch auction.""" + + def __init__( + self, + tokens: dict[Token, TokenInfo], + orders: dict[str, Order], + uniswaps: dict[str, Uniswap], + ref_token: Token, + prices: Optional[dict] = None, + name: str = "batch_auction", + metadata: Optional[dict] = None, + ): + """Initialize. + Args: + tokens: dict of tokens participating. + orders: dict of Order objects. + uniswaps: dict of Uniswap objects. + ref_token: Reference Token object. + prices: A dict of {token -> price}. + name: Name of the batch auction instance. + metadata: Some instance metadata. + """ + self.name = name + self.metadata = metadata if metadata else {} + + self._tokens = tokens + self._orders = orders + self._uniswaps = uniswaps + + # Store reference token and (previous) prices. + self.ref_token = ref_token + self.prices = ( + prices if prices else {ref_token: self._tokens[ref_token].external_price} + ) + + @classmethod + def from_dict(cls, data: dict, name: str) -> BatchAuction: + """Read a batch auction instance from a python dictionary. + + Args: + data: Python dict to be read. + name: Instance name. + + Returns: + The instance. + + """ + for key in ["tokens", "orders"]: + if key not in data: + raise ValueError(f"Mandatory field '{key}' missing in instance data!") + + tokens = load_tokens(data["tokens"]) + orders = load_orders(data["orders"]) + + uniswaps = load_amms(data.get("amms", {})) + metadata = load_metadata(data.get("metadata", {})) + prices = load_prices(data.get("prices", {})) + + ref_token = select_token_with_highest_normalize_priority(tokens) + + return cls( + tokens, + orders, + uniswaps, + ref_token, + prices=prices, + metadata=metadata, + name=name, + ) + + #################### + # ACCESS METHODS # + #################### + + @property + def tokens(self) -> list[Token]: + """Access to (sorted) token list.""" + return sorted(self._tokens.keys()) + + @property + def orders(self) -> list[Order]: + """Access to order list.""" + return list(self._orders.values()) + + @property + def uniswaps(self) -> list[Uniswap]: + """Access to uniswap list.""" + return list(self._uniswaps.values()) + + def token_info(self, token: Token) -> TokenInfo: + """Get the token info for a specific token.""" + assert isinstance(token, Token) + + if token not in self.tokens: + raise ValueError(f"Token <{token}> not in batch auction!") + + return self._tokens[token] + + @property + def chain(self) -> Chain: + """Return the blockchain on which the BatchAuction happens.""" + if self.ref_token is None: + return Chain.UNKNOWN + ref = self.ref_token.value.lower() + if ref == "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": + return Chain.MAINNET + if ref == "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": + return Chain.XDAI + return Chain.UNKNOWN + + @property + def default_ref_token_price(self) -> Decimal: + """Price of reference token if not given explicitly. + + This price is chosen so that the price of one unit of + a token with d_t=18 decimals that costs one unit of the + reference token is p_t=10^18: + + a_t * p_t * 10^d_t = a_r * p_r * 10^d_r + + with: + a_t/a_r = 1 + d_t = 18 + p_t = 10^18 + + p_r = a_t/a_r * 10^18 * 10^18 / 10^d_r + <-> p_r = 10^(2 * 18 - d_r) + """ + return Decimal(10) ** (2 * 18 - self.token_info(self.ref_token).decimals) + + def solve(self) -> None: + """Solve Batch""" + orders = self.orders + print("in solve",len(orders)) + for i in range(len(orders)-1): + print("in solve",i) + for j in range(i+1,len(orders)): + print("in solve",len(orders)) + order_i, order_j = orders[i], orders[j] + if order_i.match_type(order_j) == OrderMatchType.BOTH_FILLED: + order_i.execute( + buy_amount_value=order_j.sell_amount, + sell_amount_value=order_j.buy_amount + ) + order_j.execute( + buy_amount_value=order_i.sell_amount, + sell_amount_value=order_i.buy_amount + ) + token_a = self.token_info(order_i.sell_token) + token_b = self.token_info(order_i.buy_token) + self.prices[token_a.token] = order_j.sell_amount + self.prices[token_b.token] = order_i.sell_amount + return + + ################################# + # SOLUTION PROCESSING METHODS # + ################################# + + def __str__(self) -> str: + """Print batch auction data. + + Returns: + The string representation. + + """ + output_str = "BATCH AUCTION:" + + output_str += f"\n=== TOKENS ({len(self.tokens)}) ===" + for token in self.tokens: + output_str += f"\n-- {token}" + + output_str += f"\n=== ORDERS ({len(self.orders)}) ===" + for order in self.orders: + output_str += f"\n{order}" + + output_str += f"\n=== UNISWAPS ({len(self.uniswaps)}) ===" + for uni in self.uniswaps: + output_str += f"\n{uni}" + + return output_str + + def __repr__(self) -> str: + """Print batch auction data.""" + return self.name + +def update_batch_with_best_rates(batch: BatchAuction, best_rates): + for rate in best_rates: + order = batch.orders[rate['order_id']] + order.update_rate(rate['new_sell_amount'], rate['new_buy_amount']) + +def load_metadata(metadata: dict[str, Any]) -> dict[str, Any]: + """Store some basic metadata information.""" + metadata["scaling_factors"] = { + Token(t): Decimal(f) for t, f in metadata.get("scaling_factors", {}).items() + } + + return metadata + + +def load_prices( + prices_serialized: dict[TokenSerializedType, NumericType] +) -> dict[Token, Decimal]: + """Load token price information as dict of Token -> Decimal.""" + if not isinstance(prices_serialized, dict): + raise ValueError( + f"Prices must be given as dict, not {type(prices_serialized)}!" + ) + return {Token(t): Decimal(p) for t, p in prices_serialized.items()} + + +def load_orders(orders_serialized: OrdersSerializedType) -> dict[str, Order]: + """Load dict of orders as order pool_id -> order data. + + Args: + orders_serialized: dict of order_id -> order data dict. + + Returns: + A list of Order objects. + """ + order_list = [ + Order.from_dict(order_id, data) for order_id, data in orders_serialized.items() + ] + result: dict[str, Order] = {} + for order in order_list: + if order.order_id in result: + raise ValueError(f"Order pool_id <{order.order_id}> already exists!") + result[order.order_id] = order + return result + + +def load_amms(amms_serialized: UniswapsSerializedType) -> dict[str, Uniswap]: + """Load list of AMMs. + + NOTE: Currently, the code only supports Uniswap-style AMMs, i.e., + constant-product pools with two tokens and equal weights. + + Args: + amms_serialized: dict of pool_id -> AMM. + + Returns: + A list of Uniswap objects. + + """ + amm_list = [] + for amm_id, amm_data in amms_serialized.items(): + amm = Uniswap.from_dict(amm_id, amm_data) + if amm is not None: + amm_list.append(amm) + + results: dict[str, Uniswap] = {} + for uni in amm_list: + if uni.pool_id in results: + raise ValueError(f"Uniswap pool_id <{uni.pool_id}> already exists!") + results[uni.pool_id] = uni + + return results + + +def load_tokens(tokens_serialized: dict) -> TokenDict: + """Store tokens as sorted dictionary from Token -> token info. + + Args: + tokens_serialized: list or dict of tokens. + + Returns: + A dict of Token -> token info. + + """ + tokens_dict = {} + for token_str, token_info in sorted(tokens_serialized.items()): + token = Token(token_str) + if token_info is None: + token_info = {} + else: + for k, val in token_info.items(): + if val is None: + continue + try: + # Convert to str first to avoid float-precision artifacts: + # Decimal(0.1) -> Decimal('0.10000000000000000555...') + # Decimal('0.1') -> Decimal(0.1) + val = Decimal(str(val)) + except decimal.InvalidOperation: + pass + token_info[k] = val + if token in tokens_dict: + logging.warning(f"Token <{token}> already exists!") + tokens_dict[token] = TokenInfo(token, **token_info) + + return tokens_dict diff --git a/src/models/exchange_rate.py b/src/models/exchange_rate.py index 3694d4a..99269c3 100644 --- a/src/models/exchange_rate.py +++ b/src/models/exchange_rate.py @@ -1,123 +1,123 @@ -"""Representation of an exchange rate between two tokens.""" -from __future__ import annotations - -from src.models.token import Token, TokenBalance -from src.models.types import NumericType - - -class ExchangeRate: - """Class representing the exchange rate between two tokens.""" - - def __init__(self, tb1: TokenBalance, tb2: TokenBalance): - """ - An ExchangeRate is represented as equivalence of two TokenBalances, - e.g., 2 [ETH] == 800 [EUR] --> 400 [EUR]/[ETH] or 0.0025 [ETH]/[EUR]. - Args: - tb1: First TokenBalance. - tb2: Second TokenBalance. - """ - assert isinstance(tb1, TokenBalance) - assert isinstance(tb2, TokenBalance) - - if tb1.token == tb2.token: - raise ValueError("Both given tokens are identical!") - - if not (tb1.is_positive() and tb2.is_positive()): - raise ValueError(f"Both token balances must be positive! {tb1} & {tb2}") - - # Store attributes. - self.tb1 = tb1 - self.tb2 = tb2 - - @classmethod - def from_prices( - cls, - token1_price: tuple[Token, NumericType], - token2_price: tuple[Token, NumericType], - ) -> ExchangeRate: - """Alternative constructor: Build ExchangeRate from (absolute) token prices. - - Args: - token1_price: Tuple of (Token, price). - token2_price: Tuple of (Token, price). - Returns: - The ExchangeRate between the input tokens at the given prices. - """ - # Validate Input - balances = [] - for token_price in [token1_price, token2_price]: - assert isinstance(token_price, tuple) and len(token_price) == 2 - token, price = token_price - assert isinstance(token, Token) - assert price > 0 - - balances.append(TokenBalance(price, token)) - - return cls(balances[0], balances[1]) - - def token_balance(self, token: Token) -> TokenBalance: - """Get token balance for given token.""" - if token == self.tb1.token: - return self.tb1 - if token == self.tb2.token: - return self.tb2 - - raise ValueError(f"Exchange rate does not involve {token}") - - @property - def tokens(self) -> set[Token]: - """Returns a set containing the two tokens.""" - return {self.tb1.token, self.tb2.token} - - def convert(self, token_balance: TokenBalance) -> TokenBalance: - """Convert a TokenBalance of one token into a TokenBalance of the other. - Args: - token_balance: TokenBalance to be converted. - Returns: Converted TokenBalance. - """ - assert isinstance(token_balance, TokenBalance) - - if token_balance.token == self.tb1.token: - # Division of two token balances with same token yields scalar - # which can then be multiplied with another token balance. - return (token_balance / self.tb1).as_decimal() * self.tb2 - - if token_balance.token == self.tb2.token: - return (token_balance / self.tb2).as_decimal() * self.tb1 - - raise ValueError( - f"Token balance <{token_balance}> can not be " - f"converted using ExchangeRate <{self.tb1.token}/{self.tb2.token}>!" - ) - - def convert_unit(self, token: Token) -> TokenBalance: - """Convert one unit of one token into a TokenBalance of the other. - Args: - token: Token to be converted. - Returns: - Converted TokenBalance. - """ - assert isinstance(token, Token) - return self.convert(TokenBalance(1, token)) - - def __eq__(self, other: object) -> bool: - """Equality operator""" - if not isinstance(other, ExchangeRate): - raise ValueError(f"Cannot compare ExchangeRate and type <{type(other)}>!") - - # The ratio of the TokenBalances must be equal. - return self.convert(other.tb1) == other.tb2 - - def __ne__(self, other: object) -> bool: - """Non-equality operator""" - return not self == other - - def __str__(self) -> str: - """Represent as string.""" - tb1 = self.convert(TokenBalance(1, self.tb1.token)) - tb2 = self.convert(TokenBalance(1, self.tb2.token)) - return f"{tb1}/[{self.tb1.token}] <=> {tb2}/[{self.tb2.token}]" - - def __repr__(self) -> str: - """Represent as string.""" - return str(self) +"""Representation of an exchange rate between two tokens.""" +from __future__ import annotations + +from src.models.token import Token, TokenBalance +from src.models.types import NumericType + + +class ExchangeRate: + """Class representing the exchange rate between two tokens.""" + + def __init__(self, tb1: TokenBalance, tb2: TokenBalance): + """ + An ExchangeRate is represented as equivalence of two TokenBalances, + e.g., 2 [ETH] == 800 [EUR] --> 400 [EUR]/[ETH] or 0.0025 [ETH]/[EUR]. + Args: + tb1: First TokenBalance. + tb2: Second TokenBalance. + """ + assert isinstance(tb1, TokenBalance) + assert isinstance(tb2, TokenBalance) + + if tb1.token == tb2.token: + raise ValueError("Both given tokens are identical!") + + if not (tb1.is_positive() and tb2.is_positive()): + raise ValueError(f"Both token balances must be positive! {tb1} & {tb2}") + + # Store attributes. + self.tb1 = tb1 + self.tb2 = tb2 + + @classmethod + def from_prices( + cls, + token1_price: tuple[Token, NumericType], + token2_price: tuple[Token, NumericType], + ) -> ExchangeRate: + """Alternative constructor: Build ExchangeRate from (absolute) token prices. + + Args: + token1_price: Tuple of (Token, price). + token2_price: Tuple of (Token, price). + Returns: + The ExchangeRate between the input tokens at the given prices. + """ + # Validate Input + balances = [] + for token_price in [token1_price, token2_price]: + assert isinstance(token_price, tuple) and len(token_price) == 2 + token, price = token_price + assert isinstance(token, Token) + assert price > 0 + + balances.append(TokenBalance(price, token)) + + return cls(balances[0], balances[1]) + + def token_balance(self, token: Token) -> TokenBalance: + """Get token balance for given token.""" + if token == self.tb1.token: + return self.tb1 + if token == self.tb2.token: + return self.tb2 + + raise ValueError(f"Exchange rate does not involve {token}") + + @property + def tokens(self) -> set[Token]: + """Returns a set containing the two tokens.""" + return {self.tb1.token, self.tb2.token} + + def convert(self, token_balance: TokenBalance) -> TokenBalance: + """Convert a TokenBalance of one token into a TokenBalance of the other. + Args: + token_balance: TokenBalance to be converted. + Returns: Converted TokenBalance. + """ + assert isinstance(token_balance, TokenBalance) + + if token_balance.token == self.tb1.token: + # Division of two token balances with same token yields scalar + # which can then be multiplied with another token balance. + return (token_balance / self.tb1).as_decimal() * self.tb2 + + if token_balance.token == self.tb2.token: + return (token_balance / self.tb2).as_decimal() * self.tb1 + + raise ValueError( + f"Token balance <{token_balance}> can not be " + f"converted using ExchangeRate <{self.tb1.token}/{self.tb2.token}>!" + ) + + def convert_unit(self, token: Token) -> TokenBalance: + """Convert one unit of one token into a TokenBalance of the other. + Args: + token: Token to be converted. + Returns: + Converted TokenBalance. + """ + assert isinstance(token, Token) + return self.convert(TokenBalance(1, token)) + + def __eq__(self, other: object) -> bool: + """Equality operator""" + if not isinstance(other, ExchangeRate): + raise ValueError(f"Cannot compare ExchangeRate and type <{type(other)}>!") + + # The ratio of the TokenBalances must be equal. + return self.convert(other.tb1) == other.tb2 + + def __ne__(self, other: object) -> bool: + """Non-equality operator""" + return not self == other + + def __str__(self) -> str: + """Represent as string.""" + tb1 = self.convert(TokenBalance(1, self.tb1.token)) + tb2 = self.convert(TokenBalance(1, self.tb2.token)) + return f"{tb1}/[{self.tb1.token}] <=> {tb2}/[{self.tb2.token}]" + + def __repr__(self) -> str: + """Represent as string.""" + return str(self) diff --git a/src/models/order.py b/src/models/order.py index fc0b607..0eaca77 100644 --- a/src/models/order.py +++ b/src/models/order.py @@ -1,369 +1,369 @@ -"""Representation of a limit order.""" -from __future__ import annotations - -import json -import logging -from decimal import Decimal -from enum import Enum -from typing import Optional, Any, Union - - -from src.models.exchange_rate import ExchangeRate as XRate -from src.models.token import Token, TokenBalance -from src.models.types import NumericType -from src.util.constants import Constants -from src.util.numbers import decimal_to_str - -OrderSerializedType = dict[str, Any] -OrdersSerializedType = dict[str, OrderSerializedType] - - -class OrderMatchType(Enum): - """Enum for different Order Matching""" - - LHS_FILLED = "LhsFilled" - RHS_FILLED = "RhsFilled" - BOTH_FILLED = "BothFilled" - - -# TODO - use dataclass for this. -class Order: - """Representation of a limit order. - An order is specified with 3 bounds: - * maximum amount of buy-token to be bought - * maximum amount of sell-token to be sold - * limit exchange rate of buy- vs. sell-token. - - Depending on which of the bounds are set, - the order represents a classical limit {buy|sell} order, - a cost-bounded {buy|sell} order or a {buy|sell} market order. - """ - - def __init__( - self, - order_id: str, - buy_token: Token, - sell_token: Token, - buy_amount: Decimal, - sell_amount: Decimal, - is_sell_order: bool, - allow_partial_fill: bool = False, - is_liquidity_order: bool = False, - has_atomic_execution: bool = False, - fee: Optional[TokenBalance] = None, - cost: Optional[TokenBalance] = None, - ) -> None: - """Initialize. - - Args: - order_id: Order pool_id. - buy_token: Token to be bought. - sell_token: Token to be sold. - - Kwargs: - max_buy_amount: Maximum amount of buy-token to be bought, or None. - max_sell_amount: Maximum amount of sell-token to be sold, or None. - max_limit: Limit exchange rate for order. - allow_partial_fill: Can order be partially matched, or not. - is_liquidity_order: Is the order from a market maker, or not. - has_atomic_execution: Needs to executed atomically, or not. - fee: Fee contribution of the order to the objective. - cost: Cost of including the order in the solution. - exec_buy_amount: Matched amount of buy-token in solution. - exec_sell_amount: Matched amount of sell-token in solution. - """ - if buy_token == sell_token: - raise ValueError("sell- and buy-token cannot be equal!") - - if not (buy_amount > 0 and sell_amount > 0): - raise ValueError( - f"buy {buy_amount} and sell {sell_amount} amounts must be positive!" - ) - - self.order_id = order_id - self.buy_token = buy_token - self.sell_token = sell_token - self.buy_amount = buy_amount - self.sell_amount = sell_amount - self.is_sell_order = is_sell_order - self.allow_partial_fill = allow_partial_fill - self.is_liquidity_order = is_liquidity_order - self.has_atomic_execution = has_atomic_execution - self.fee: Optional[TokenBalance] = fee - self.cost: Optional[TokenBalance] = cost - - # Stuff that isn't part of the constructor parameters. - self.exec_buy_amount: Optional[TokenBalance] = None - self.exec_sell_amount: Optional[TokenBalance] = None - - @classmethod - def from_dict(cls, order_id: str, data: OrderSerializedType) -> Order: - """ - Read Order object from order data dict. - Args: - order_id: ID of order - data: Dict of order data. - """ - - required_attributes = [ - "sell_token", - "buy_token", - "sell_amount", - "buy_amount", - "is_sell_order", - "allow_partial_fill", - "is_liquidity_order", - ] - - for attr in required_attributes: - if attr not in data: - raise ValueError(f"Missing field '{attr}' in order <{order_id}>!") - - return Order( - order_id=order_id, - buy_token=Token(data["buy_token"]), - sell_token=Token(data["sell_token"]), - buy_amount=Decimal(data["buy_amount"]), - sell_amount=Decimal(data["sell_amount"]), - is_sell_order=bool(data["is_sell_order"]), - allow_partial_fill=bool(data["allow_partial_fill"]), - is_liquidity_order=bool(data["is_liquidity_order"]), - fee=TokenBalance.parse(data.get("fee"), allow_none=True), - cost=TokenBalance.parse(data.get("cost"), allow_none=True), - ) - - def as_dict(self) -> OrderSerializedType: - """Return Order object as dictionary.""" - # Currently, only limit buy or sell orders be handled. - order_dict = { - "sell_token": str(self.sell_token), - "buy_token": str(self.buy_token), - "sell_amount": decimal_to_str(self.sell_amount), - "buy_amount": decimal_to_str(self.buy_amount), - "allow_partial_fill": self.allow_partial_fill, - "is_sell_order": self.is_sell_order, - "exec_sell_amount": decimal_to_str(self.exec_sell_amount.as_decimal()) - if self.exec_sell_amount is not None - else "0", - "exec_buy_amount": decimal_to_str(self.exec_buy_amount.as_decimal()) - if self.exec_buy_amount is not None - else "0", - } - - if self.fee is not None: - order_dict["fee"] = { - "token": str(self.fee.token), - "amount": decimal_to_str(self.fee.as_decimal()), - } - - if self.cost is not None: - order_dict["cost"] = { - "token": str(self.cost.token), - "amount": decimal_to_str(self.cost.as_decimal()), - } - - return order_dict - - @property - def max_limit(self) -> XRate: - """Max limit of the order as an exchange rate""" - return XRate( - tb1=TokenBalance(self.sell_amount, self.sell_token), - tb2=TokenBalance(self.buy_amount, self.buy_token), - ) - - @property - def max_buy_amount(self) -> Optional[TokenBalance]: - """None for sell-orders""" - if not self.is_sell_order: - return TokenBalance.parse_amount(self.buy_amount, self.buy_token) - return None - - @property - def max_sell_amount(self) -> Optional[TokenBalance]: - """None for buy-orders""" - if self.is_sell_order: - return TokenBalance.parse_amount(self.sell_amount, self.sell_token) - return None - - @property - def tokens(self) -> set[Token]: - """Return the buy and sell tokens.""" - return {self.buy_token, self.sell_token} - - ##################### - # UTILITY METHODS #` - ##################### - - def overlaps(self, other: Order) -> bool: - """ - Determine if one order can be matched with another. - opposite {buy|sell} tokens and matching prices - """ - token_conditions = [ - self.buy_token == other.sell_token, - self.sell_token == other.buy_token, - ] - if not all(token_conditions): - return False - - return ( - self.buy_amount * other.buy_amount <= other.sell_amount * self.sell_amount - ) - - def match_type(self, other: Order) -> Optional[OrderMatchType]: - """Determine to what extent two orders match""" - if not self.overlaps(other): - return None - - if self.buy_amount < other.sell_amount and self.sell_amount < other.buy_amount: - return OrderMatchType.LHS_FILLED - - if self.buy_amount > other.sell_amount and self.sell_amount > other.buy_amount: - return OrderMatchType.RHS_FILLED - - return OrderMatchType.BOTH_FILLED - - def is_executable(self, xrate: XRate, xrate_tol: Decimal = Decimal("1e-6")) -> bool: - """Determine if the order limit price satisfies a given market rate. - - Args: - xrate: Exchange rate. - xrate_tol: Accepted violation of the limit exchange rate constraint - per unit of buy token (default: 1e-6). - Returns: - True, if order can be executed; False otherwise. - """ - buy_token, sell_token = self.buy_token, self.sell_token - if xrate.tokens != {buy_token, sell_token}: - raise ValueError( - f"Exchange rate and order tokens do not " - f"match: {xrate} vs. <{buy_token}> | <{sell_token}>!" - ) - - assert xrate_tol >= 0 - converted_buy = xrate.convert_unit(buy_token) - converted_sell = self.max_limit.convert_unit(buy_token) - return bool(converted_buy <= (converted_sell * (1 + xrate_tol))) - - def execute( - self, - buy_amount_value: NumericType, - sell_amount_value: NumericType, - buy_token_price: Union[float, Decimal] = 0, - sell_token_price: Union[float, Decimal] = 0, - amount_tol: Decimal = Decimal("1e-8"), - xrate_tol: Decimal = Decimal("1e-6"), - ) -> None: - """Execute the order at given amounts. - - Args: - buy_amount_value: Buy amount. - sell_amount_value: Sell amount. - buy_token_price: Buy-token price. - sell_token_price: Sell-token price. - amount_tol: Accepted violation of the limit buy/sell amount constraints. - xrate_tol: Accepted violation of the limit exchange rate constraint - per unit of buy token (default: 1e-6). - """ - assert buy_amount_value >= -amount_tol - assert sell_amount_value >= -amount_tol - assert buy_token_price >= 0 - assert sell_token_price >= 0 - - buy_token, sell_token = self.buy_token, self.sell_token - - buy_amount = TokenBalance(buy_amount_value, buy_token) - sell_amount = TokenBalance(sell_amount_value, sell_token) - - xmax = self.max_buy_amount - ymax = self.max_sell_amount - - # (a) Check buyAmount: if too much above maxBuyAmount --> error! - if xmax is not None: - if buy_amount > xmax * ( - 1 + amount_tol - ) and buy_amount > xmax + TokenBalance(amount_tol, buy_token): - raise ValueError( - f"Invalid execution request for " - f"order <{self.order_id}>: " - f"buy amount (exec) : {buy_amount.balance} " - f"buy amount (max) : {xmax.balance}" - ) - - buy_amount = min(buy_amount, xmax) - - # (b) Check sellAmount: if too much above maxSellAmount --> error! - if ymax is not None: - if sell_amount > ymax * ( - 1 + amount_tol - ) and sell_amount > ymax + TokenBalance(amount_tol, sell_token): - message = ( - f"Invalid execution request for " - f"order <{self.order_id}>: " - f"sell (exec) : {sell_amount.balance} " - f"sell (max) : {ymax.balance}" - ) - logging.error(message) - if Constants.RAISE_ON_MAX_SELL_AMOUNT_VIOLATION: - raise ValueError(message) - sell_amount = min(sell_amount, ymax) - - # (c) if any amount is very small, set to zero. - if any( - [ - buy_amount <= TokenBalance(amount_tol, buy_token), - sell_amount <= TokenBalance(amount_tol, sell_token), - ] - ): - buy_amount = TokenBalance(0.0, buy_token) - sell_amount = TokenBalance(0.0, sell_token) - - # Check limit price. - if buy_amount > 0: - assert sell_amount > 0 - xrate = XRate(buy_amount, sell_amount) - if not self.is_executable(xrate, xrate_tol=xrate_tol): - message = ( - f"Invalid execution request for order <{self.order_id}>: " - f"buy amount (exec): {buy_amount.balance} " - f"sell amount (exec): {sell_amount.balance} " - f"xrate (exec): {xrate} " - f"limit (max): {self.max_limit}" - ) - logging.error(message) - if Constants.RAISE_ON_LIMIT_XRATE_VIOLATION: - raise ValueError(message) - - # Store execution information. - self.exec_buy_amount = buy_amount - self.exec_sell_amount = sell_amount - - def is_executed(self) -> bool: - """Check if order has already been executed.""" - return self.exec_buy_amount is not None and self.exec_sell_amount is not None - - def __str__(self) -> str: - """Represent as string.""" - return json.dumps(self.as_dict(), indent=2) - - def __repr__(self) -> str: - """Represent as short string.""" - return f"Order: {self.order_id}" - - def __hash__(self) -> int: - return hash(self.order_id) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Order): - return NotImplemented - if self.order_id != other.order_id: - return False - assert vars(self) == vars(other) - return True - - def __lt__(self, other: object) -> bool: - if not isinstance(other, Order): - return NotImplemented - - return self.order_id < other.order_id +"""Representation of a limit order.""" +from __future__ import annotations + +import json +import logging +from decimal import Decimal +from enum import Enum +from typing import Optional, Any, Union + + +from src.models.exchange_rate import ExchangeRate as XRate +from src.models.token import Token, TokenBalance +from src.models.types import NumericType +from src.util.constants import Constants +from src.util.numbers import decimal_to_str + +OrderSerializedType = dict[str, Any] +OrdersSerializedType = dict[str, OrderSerializedType] + + +class OrderMatchType(Enum): + """Enum for different Order Matching""" + + LHS_FILLED = "LhsFilled" + RHS_FILLED = "RhsFilled" + BOTH_FILLED = "BothFilled" + + +# TODO - use dataclass for this. +class Order: + """Representation of a limit order. + An order is specified with 3 bounds: + * maximum amount of buy-token to be bought + * maximum amount of sell-token to be sold + * limit exchange rate of buy- vs. sell-token. + + Depending on which of the bounds are set, + the order represents a classical limit {buy|sell} order, + a cost-bounded {buy|sell} order or a {buy|sell} market order. + """ + + def __init__( + self, + order_id: str, + buy_token: Token, + sell_token: Token, + buy_amount: Decimal, + sell_amount: Decimal, + is_sell_order: bool, + allow_partial_fill: bool = False, + is_liquidity_order: bool = False, + has_atomic_execution: bool = False, + fee: Optional[TokenBalance] = None, + cost: Optional[TokenBalance] = None, + ) -> None: + """Initialize. + + Args: + order_id: Order pool_id. + buy_token: Token to be bought. + sell_token: Token to be sold. + + Kwargs: + max_buy_amount: Maximum amount of buy-token to be bought, or None. + max_sell_amount: Maximum amount of sell-token to be sold, or None. + max_limit: Limit exchange rate for order. + allow_partial_fill: Can order be partially matched, or not. + is_liquidity_order: Is the order from a market maker, or not. + has_atomic_execution: Needs to executed atomically, or not. + fee: Fee contribution of the order to the objective. + cost: Cost of including the order in the solution. + exec_buy_amount: Matched amount of buy-token in solution. + exec_sell_amount: Matched amount of sell-token in solution. + """ + if buy_token == sell_token: + raise ValueError("sell- and buy-token cannot be equal!") + + if not (buy_amount > 0 and sell_amount > 0): + raise ValueError( + f"buy {buy_amount} and sell {sell_amount} amounts must be positive!" + ) + + self.order_id = order_id + self.buy_token = buy_token + self.sell_token = sell_token + self.buy_amount = buy_amount + self.sell_amount = sell_amount + self.is_sell_order = is_sell_order + self.allow_partial_fill = allow_partial_fill + self.is_liquidity_order = is_liquidity_order + self.has_atomic_execution = has_atomic_execution + self.fee: Optional[TokenBalance] = fee + self.cost: Optional[TokenBalance] = cost + + # Stuff that isn't part of the constructor parameters. + self.exec_buy_amount: Optional[TokenBalance] = None + self.exec_sell_amount: Optional[TokenBalance] = None + + @classmethod + def from_dict(cls, order_id: str, data: OrderSerializedType) -> Order: + """ + Read Order object from order data dict. + Args: + order_id: ID of order + data: Dict of order data. + """ + + required_attributes = [ + "sell_token", + "buy_token", + "sell_amount", + "buy_amount", + "is_sell_order", + "allow_partial_fill", + "is_liquidity_order", + ] + + for attr in required_attributes: + if attr not in data: + raise ValueError(f"Missing field '{attr}' in order <{order_id}>!") + + return Order( + order_id=order_id, + buy_token=Token(data["buy_token"]), + sell_token=Token(data["sell_token"]), + buy_amount=Decimal(data["buy_amount"]), + sell_amount=Decimal(data["sell_amount"]), + is_sell_order=bool(data["is_sell_order"]), + allow_partial_fill=bool(data["allow_partial_fill"]), + is_liquidity_order=bool(data["is_liquidity_order"]), + fee=TokenBalance.parse(data.get("fee"), allow_none=True), + cost=TokenBalance.parse(data.get("cost"), allow_none=True), + ) + + def as_dict(self) -> OrderSerializedType: + """Return Order object as dictionary.""" + # Currently, only limit buy or sell orders be handled. + order_dict = { + "sell_token": str(self.sell_token), + "buy_token": str(self.buy_token), + "sell_amount": decimal_to_str(self.sell_amount), + "buy_amount": decimal_to_str(self.buy_amount), + "allow_partial_fill": self.allow_partial_fill, + "is_sell_order": self.is_sell_order, + "exec_sell_amount": decimal_to_str(self.exec_sell_amount.as_decimal()) + if self.exec_sell_amount is not None + else "0", + "exec_buy_amount": decimal_to_str(self.exec_buy_amount.as_decimal()) + if self.exec_buy_amount is not None + else "0", + } + + if self.fee is not None: + order_dict["fee"] = { + "token": str(self.fee.token), + "amount": decimal_to_str(self.fee.as_decimal()), + } + + if self.cost is not None: + order_dict["cost"] = { + "token": str(self.cost.token), + "amount": decimal_to_str(self.cost.as_decimal()), + } + + return order_dict + + @property + def max_limit(self) -> XRate: + """Max limit of the order as an exchange rate""" + return XRate( + tb1=TokenBalance(self.sell_amount, self.sell_token), + tb2=TokenBalance(self.buy_amount, self.buy_token), + ) + + @property + def max_buy_amount(self) -> Optional[TokenBalance]: + """None for sell-orders""" + if not self.is_sell_order: + return TokenBalance.parse_amount(self.buy_amount, self.buy_token) + return None + + @property + def max_sell_amount(self) -> Optional[TokenBalance]: + """None for buy-orders""" + if self.is_sell_order: + return TokenBalance.parse_amount(self.sell_amount, self.sell_token) + return None + + @property + def tokens(self) -> set[Token]: + """Return the buy and sell tokens.""" + return {self.buy_token, self.sell_token} + + ##################### + # UTILITY METHODS #` + ##################### + + def overlaps(self, other: Order) -> bool: + """ + Determine if one order can be matched with another. + opposite {buy|sell} tokens and matching prices + """ + token_conditions = [ + self.buy_token == other.sell_token, + self.sell_token == other.buy_token, + ] + if not all(token_conditions): + return False + + return ( + self.buy_amount * other.buy_amount <= other.sell_amount * self.sell_amount + ) + + def match_type(self, other: Order) -> Optional[OrderMatchType]: + """Determine to what extent two orders match""" + if not self.overlaps(other): + return None + + if self.buy_amount < other.sell_amount and self.sell_amount < other.buy_amount: + return OrderMatchType.LHS_FILLED + + if self.buy_amount > other.sell_amount and self.sell_amount > other.buy_amount: + return OrderMatchType.RHS_FILLED + + return OrderMatchType.BOTH_FILLED + + def is_executable(self, xrate: XRate, xrate_tol: Decimal = Decimal("1e-6")) -> bool: + """Determine if the order limit price satisfies a given market rate. + + Args: + xrate: Exchange rate. + xrate_tol: Accepted violation of the limit exchange rate constraint + per unit of buy token (default: 1e-6). + Returns: + True, if order can be executed; False otherwise. + """ + buy_token, sell_token = self.buy_token, self.sell_token + if xrate.tokens != {buy_token, sell_token}: + raise ValueError( + f"Exchange rate and order tokens do not " + f"match: {xrate} vs. <{buy_token}> | <{sell_token}>!" + ) + + assert xrate_tol >= 0 + converted_buy = xrate.convert_unit(buy_token) + converted_sell = self.max_limit.convert_unit(buy_token) + return bool(converted_buy <= (converted_sell * (1 + xrate_tol))) + + def execute( + self, + buy_amount_value: NumericType, + sell_amount_value: NumericType, + buy_token_price: Union[float, Decimal] = 0, + sell_token_price: Union[float, Decimal] = 0, + amount_tol: Decimal = Decimal("1e-8"), + xrate_tol: Decimal = Decimal("1e-6"), + ) -> None: + """Execute the order at given amounts. + + Args: + buy_amount_value: Buy amount. + sell_amount_value: Sell amount. + buy_token_price: Buy-token price. + sell_token_price: Sell-token price. + amount_tol: Accepted violation of the limit buy/sell amount constraints. + xrate_tol: Accepted violation of the limit exchange rate constraint + per unit of buy token (default: 1e-6). + """ + assert buy_amount_value >= -amount_tol + assert sell_amount_value >= -amount_tol + assert buy_token_price >= 0 + assert sell_token_price >= 0 + + buy_token, sell_token = self.buy_token, self.sell_token + + buy_amount = TokenBalance(buy_amount_value, buy_token) + sell_amount = TokenBalance(sell_amount_value, sell_token) + + xmax = self.max_buy_amount + ymax = self.max_sell_amount + + # (a) Check buyAmount: if too much above maxBuyAmount --> error! + if xmax is not None: + if buy_amount > xmax * ( + 1 + amount_tol + ) and buy_amount > xmax + TokenBalance(amount_tol, buy_token): + raise ValueError( + f"Invalid execution request for " + f"order <{self.order_id}>: " + f"buy amount (exec) : {buy_amount.balance} " + f"buy amount (max) : {xmax.balance}" + ) + + buy_amount = min(buy_amount, xmax) + + # (b) Check sellAmount: if too much above maxSellAmount --> error! + if ymax is not None: + if sell_amount > ymax * ( + 1 + amount_tol + ) and sell_amount > ymax + TokenBalance(amount_tol, sell_token): + message = ( + f"Invalid execution request for " + f"order <{self.order_id}>: " + f"sell (exec) : {sell_amount.balance} " + f"sell (max) : {ymax.balance}" + ) + logging.error(message) + if Constants.RAISE_ON_MAX_SELL_AMOUNT_VIOLATION: + raise ValueError(message) + sell_amount = min(sell_amount, ymax) + + # (c) if any amount is very small, set to zero. + if any( + [ + buy_amount <= TokenBalance(amount_tol, buy_token), + sell_amount <= TokenBalance(amount_tol, sell_token), + ] + ): + buy_amount = TokenBalance(0.0, buy_token) + sell_amount = TokenBalance(0.0, sell_token) + + # Check limit price. + if buy_amount > 0: + assert sell_amount > 0 + xrate = XRate(buy_amount, sell_amount) + if not self.is_executable(xrate, xrate_tol=xrate_tol): + message = ( + f"Invalid execution request for order <{self.order_id}>: " + f"buy amount (exec): {buy_amount.balance} " + f"sell amount (exec): {sell_amount.balance} " + f"xrate (exec): {xrate} " + f"limit (max): {self.max_limit}" + ) + logging.error(message) + if Constants.RAISE_ON_LIMIT_XRATE_VIOLATION: + raise ValueError(message) + + # Store execution information. + self.exec_buy_amount = buy_amount + self.exec_sell_amount = sell_amount + + def is_executed(self) -> bool: + """Check if order has already been executed.""" + return self.exec_buy_amount is not None and self.exec_sell_amount is not None + + def __str__(self) -> str: + """Represent as string.""" + return json.dumps(self.as_dict(), indent=2) + + def __repr__(self) -> str: + """Represent as short string.""" + return f"Order: {self.order_id}" + + def __hash__(self) -> int: + return hash(self.order_id) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Order): + return NotImplemented + if self.order_id != other.order_id: + return False + assert vars(self) == vars(other) + return True + + def __lt__(self, other: object) -> bool: + if not isinstance(other, Order): + return NotImplemented + + return self.order_id < other.order_id diff --git a/src/models/solver_args.py b/src/models/solver_args.py index 6e05359..7a3159a 100644 --- a/src/models/solver_args.py +++ b/src/models/solver_args.py @@ -1,45 +1,45 @@ -"""Argument parser for solve Request that combines query parameters with metadata""" -from __future__ import annotations - -from dataclasses import dataclass -from typing import Optional - -from fastapi import Request - -from src.util.schema import MetadataModel - - -@dataclass -class SolverArgs: - """Parameters passed in POST URL""" - - auction_id: Optional[str] - instance_name: str - time_limit: int - max_nr_exec_orders: int - use_internal_buffers: bool - use_external_prices: bool - environment: Optional[str] - gas_price: Optional[float] - native_token: Optional[str] - - @classmethod - def from_request(cls, request: Request, meta: MetadataModel) -> SolverArgs: - """Parses Request query params dict as struct""" - param_dict = request.query_params - return cls( - # Query Parameter Arguments - instance_name=param_dict.get("instance_name", "Not Provided"), - time_limit=int(param_dict.get("time_limit", 30)), - max_nr_exec_orders=int(param_dict.get("max_nr_exec_orders", 100)), - use_internal_buffers=bool(param_dict.get("use_internal_buffers", False)), - use_external_prices=bool(param_dict.get("use_external_prices", False)), - # Meta Data Arguments - environment=meta.environment, - gas_price=meta.gas_price, - native_token=meta.native_token, - # Both: Prioritize query params over metadata. - auction_id=param_dict.get("auction_id", meta.auction_id), - ) - - +"""Argument parser for solve Request that combines query parameters with metadata""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + +from fastapi import Request + +from src.util.schema import MetadataModel + + +@dataclass +class SolverArgs: + """Parameters passed in POST URL""" + + auction_id: Optional[str] + instance_name: str + time_limit: int + max_nr_exec_orders: int + use_internal_buffers: bool + use_external_prices: bool + environment: Optional[str] + gas_price: Optional[float] + native_token: Optional[str] + + @classmethod + def from_request(cls, request: Request, meta: MetadataModel) -> SolverArgs: + """Parses Request query params dict as struct""" + param_dict = request.query_params + return cls( + # Query Parameter Arguments + instance_name=param_dict.get("instance_name", "Not Provided"), + time_limit=int(param_dict.get("time_limit", 30)), + max_nr_exec_orders=int(param_dict.get("max_nr_exec_orders", 100)), + use_internal_buffers=bool(param_dict.get("use_internal_buffers", False)), + use_external_prices=bool(param_dict.get("use_external_prices", False)), + # Meta Data Arguments + environment=meta.environment, + gas_price=meta.gas_price, + native_token=meta.native_token, + # Both: Prioritize query params over metadata. + auction_id=param_dict.get("auction_id", meta.auction_id), + ) + + diff --git a/src/models/token.py b/src/models/token.py index 1922c71..5baab74 100644 --- a/src/models/token.py +++ b/src/models/token.py @@ -1,404 +1,404 @@ -"""A class for extendable token enum's.""" -from __future__ import annotations - -import re -from decimal import Decimal, getcontext -from typing import Optional, Union - -from src.models.types import NumericType -from src.util.constants import Constants - - -class Token: - """Enumeration over available tokens.""" - - def __init__(self, value: str): - if Token._is_valid(value): - self.value = value - else: - raise ValueError(f"Invalid Ethereum Address {value}") - - @staticmethod - def _is_valid(address: str) -> bool: - match_result = re.match( - pattern=r"^(0x)?[0-9a-f]{40}$", string=address, flags=re.IGNORECASE - ) - return match_result is not None - - def __str__(self) -> str: - """Convert to string.""" - return self.value - - def __repr__(self) -> str: - """Convert to string.""" - return self.__str__() - - def __hash__(self) -> int: - """Hash of token.""" - return hash(self.value) - - def __eq__(self, other: object) -> bool: - """Equality operator.""" - if isinstance(other, Token): - return self.value == other.value - return False - - def __lt__(self, other: object) -> bool: - """Less-than operator.""" - if isinstance(other, Token): - return self.value < other.value - return NotImplemented - - -class TokenInfo: - """Class for storing token information.""" - - def __init__( - self, - token: Token, - decimals: int, - alias: Optional[str] = None, - external_price: Optional[Decimal] = None, - estimated_price: Optional[Decimal] = None, - internal_buffer: Optional[Decimal] = None, - normalize_priority: Optional[int] = 0, - ): - """Constructor.""" - self.token = token - self.alias = alias - self.decimals = decimals - self.external_price = external_price - self.estimated_price = estimated_price - self.internal_buffer = internal_buffer - self._normalize_priority = normalize_priority or 0 - - @property - def normalize_priority(self) -> int: - """ - Return the token priority for normalization purposes. - - Higher value means higher priority. - """ - return self._normalize_priority - - def as_dict(self) -> dict: - """Convert to dict.""" - attr = [ - a - for a in dir(self) - if not callable(getattr(self, a)) and not a.startswith("_") and a != "token" - ] - - return {a: getattr(self, a) for a in sorted(attr)} - - def __str__(self) -> str: - """Convert to string.""" - token_info_dict = self.as_dict() - - _str = f"Token [{self.token}]:" - for attr, value in token_info_dict.items(): - if isinstance(value, Decimal) and attr not in [ - "external_price", - "internal_buffer", - ]: - value = value.quantize(Constants.DECIMAL_STR_PREC) - _str += f"\n-- {attr} : {value}" - - return _str - - -def select_token_with_highest_normalize_priority( - tokens: dict[Token, TokenInfo] -) -> Token: - """ - Select token with highest normalize priority from the list of tokens. - - If the highest normalize_priority is shared by multiple tokens, the - ref_token is the first lexicographically. - """ - max_priority = max(t.normalize_priority for t in tokens.values()) - highest_priority_tokens = [ - t for t, info in tokens.items() if info.normalize_priority == max_priority - ] - return highest_priority_tokens[0] - - -TokenDict = dict[Token, TokenInfo] -TokenSerializedType = str -TokenAmountSerializedType = tuple[Union[str, NumericType], TokenSerializedType] - - -class TokenBalance: - """Class to represent an amount of some token.""" - - def __init__(self, balance: NumericType, token: Token): - """Initialize. - - Args: - balance: Amount of tokens. - token: Token. - """ - - self._balance = Decimal(balance) - self.balance = balance - self.token = token - - if not self._balance.is_finite(): - raise ValueError(f"Token balance must be finite, not {self._balance}!") - - @classmethod - def parse( - cls, - token_amount_serialized: Optional[TokenAmountSerializedType], - allow_negative: bool = False, - allow_none: bool = False, - ) -> Optional[TokenBalance]: - """ - Method to parse a token amount given as (amount, token) into a TokenBalance. - """ - if token_amount_serialized is None: - if not allow_none: - raise ValueError("Token amount must not be None!") - token_amount = None - else: - if not isinstance(token_amount_serialized, dict) or set( - token_amount_serialized.keys() - ) != {"amount", "token"}: - raise ValueError( - "token amount must be given as dict of {'amount': .., 'token': ..}," - f" not <{token_amount_serialized}>!" - ) - token_amount = cls( - Decimal(token_amount_serialized["amount"]), - Token(token_amount_serialized["token"]), - ) - if not allow_negative and token_amount.is_negative(): - raise ValueError(f"Token amount must be non-negative ({token_amount})!") - return token_amount - - @classmethod - def parse_amount( - cls, amt_type: Optional[Amount], token: Token - ) -> Optional[TokenBalance]: - """Auxiliary method to parse a numerical value into a TokenBalance. - - Args: - amt_type: Amount to be set, or None. - token: Token belonging to amount. - - Returns: - A TokenBalance, or None. - - """ - - if isinstance(amt_type, (int, float, Decimal)): - return cls(amt_type, token) - - if isinstance(amt_type, TokenBalance): - if amt_type.token != token: - raise ValueError( - f"Tokens do not match: <{amt_type.token}> vs. <{token}>!" - ) - return amt_type - - return None - - def as_decimal(self) -> Decimal: - """Returns balance attribute as Decimal type""" - return self._balance - - @staticmethod - def precision() -> int: - """Return precision currently associated with TokenBalance.""" - return getcontext().prec - - def is_positive(self) -> bool: - """Determine if a TokenBalance is positive.""" - return self._balance > 0 - - def is_negative(self) -> bool: - """Determine if a TokenBalance is negative.""" - return self._balance < 0 - - def is_zero(self) -> bool: - """Determine if a TokenBalance is zero.""" - return self._balance == 0 - - def __eq__(self, other: object) -> bool: - """Equality operator. - - Args: - other: Another TokenBalance, or zero. - """ - if other == 0: - return self.is_zero() - - if isinstance(other, TokenBalance): - - if self.token != other.token: - raise ValueError( - f"Cannot compare different tokens <{self.token}> / <{other.token}>!" - ) - return self._balance == other._balance - - raise ValueError(f"Cannot compare TokenBalance and type <{type(other)}>!") - - def __ne__(self, other: object) -> bool: - """Non-equality operator""" - return not self == other - - def __lt__(self, other: Union[TokenBalance, NumericType]) -> bool: - """Less-than operator. - - Args: - other: Another TokenBalance, or zero. - """ - if isinstance(other, TokenBalance): - - if self.token != other.token: - raise ValueError( - f"Cannot compare different tokens <{self.token}> / <{other.token}>!" - ) - return self._balance < other._balance - - if other == 0: - return self._balance < 0 - - raise ValueError(f"Cannot compare TokenBalance and type <{type(other)}>") - - def __le__(self, other: Union[TokenBalance, NumericType]) -> bool: - """Less-than-or-equal operator. - - Args: - other: Another TokenBalance, or zero. - """ - return self < other or self == other - - def __gt__(self, other: Union[TokenBalance, NumericType]) -> bool: - """Greater-than operator. - - Args: - other: Another TokenBalance, or zero. - """ - return not self <= other - - def __ge__(self, other: Union[TokenBalance, NumericType]) -> bool: - """Greater-than-or-equal operator. - - Args: - other: Another TokenBalance, or zero. - """ - return self > other or self == other - - def __neg__(self) -> TokenBalance: - """Negation operator.""" - return TokenBalance(-self._balance, self.token) - - def __abs__(self) -> TokenBalance: - """Absolute value operator.""" - return TokenBalance(abs(self._balance), self.token) - - def __add__(self, other: Union[TokenBalance, NumericType]) -> TokenBalance: - """Addition operator. - - Args: - other: Another TokenBalance, or zero. - """ - if isinstance(other, TokenBalance): - - if self.token == other.token: - return TokenBalance(self._balance + other._balance, self.token) - - raise ValueError(f"Cannot add <{other.token}> and <{self.token}>!") - - if other == 0: - # This is required to enable the use of the sum() function: - # sum() by design starts with a value of '0' and then iteratively - # adds the items in the list that is passed as argument. See: - # https://stackoverflow.com/questions/1218710/pythons-sum-and-non-integer-values - return self - - raise ValueError(f"Cannot add <{type(other)}> and TokenBalance!") - - def __radd__(self, other: Union[TokenBalance, NumericType]) -> TokenBalance: - """Addition-from-right operator. - - Args: - other: Another TokenBalance, or zero. - """ - return self + other - - def __sub__(self, other: Union[TokenBalance, NumericType]) -> TokenBalance: - """Subtraction operator. - - Args: - other: Another TokenBalance, or zero. - """ - return self + (-other) - - def __rsub__(self, other: Union[TokenBalance, NumericType]) -> TokenBalance: - """Subtraction operator. - - Args: - other: Another TokenBalance, or zero. - """ - return other + (-self) - - def __mul__(self, other: NumericType) -> TokenBalance: - """Multiplication operator. - - Args: - other: A {float|int|Decimal}. - """ - if isinstance(other, (int, float, Decimal)): - return TokenBalance(Decimal(other) * self._balance, self.token) - - raise ValueError(f"Cannot multiply TokenBalance by <{type(other)}>!") - - def __rmul__(self, other: NumericType) -> TokenBalance: - """Multiplication-from-right operator. - - Args: - other: A {float|int|Decimal}. - """ - return self * other - - def __truediv__(self, other: Union[TokenBalance, NumericType]) -> TokenBalance: - """Division operator. - - Args: - other: A {TokenBalance|float|int|Decimal}. - """ - if isinstance(other, (int, float, Decimal)): - if other == 0: - raise ZeroDivisionError - return TokenBalance(self._balance / Decimal(other), self.token) - - if isinstance(other, TokenBalance): - if self.token == other.token: - return TokenBalance(self._balance / other._balance, self.token) - raise ValueError( - f"Can't divide TokenBalances with different " - f"tokens <{self.token}> and <{other.token}>!" - ) - - raise ValueError(f"Cannot divide TokenBalance by <{type(other)}>!") - - def __rtruediv__(self, other: object) -> None: - """Division-from-right operator. - - Args: - other: Something. - """ - raise ValueError(f"<{type(other)}> cannot be divided by TokenBalance!") - - def __str__(self) -> str: - """Represent as string (rounded to 5 decimals).""" - return f"{self.token}: {self.balance}" - - def __repr__(self) -> str: - """Represent as string.""" - return str(self) - - -Amount = Union[TokenBalance, NumericType] +"""A class for extendable token enum's.""" +from __future__ import annotations + +import re +from decimal import Decimal, getcontext +from typing import Optional, Union + +from src.models.types import NumericType +from src.util.constants import Constants + + +class Token: + """Enumeration over available tokens.""" + + def __init__(self, value: str): + if Token._is_valid(value): + self.value = value + else: + raise ValueError(f"Invalid Ethereum Address {value}") + + @staticmethod + def _is_valid(address: str) -> bool: + match_result = re.match( + pattern=r"^(0x)?[0-9a-f]{40}$", string=address, flags=re.IGNORECASE + ) + return match_result is not None + + def __str__(self) -> str: + """Convert to string.""" + return self.value + + def __repr__(self) -> str: + """Convert to string.""" + return self.__str__() + + def __hash__(self) -> int: + """Hash of token.""" + return hash(self.value) + + def __eq__(self, other: object) -> bool: + """Equality operator.""" + if isinstance(other, Token): + return self.value == other.value + return False + + def __lt__(self, other: object) -> bool: + """Less-than operator.""" + if isinstance(other, Token): + return self.value < other.value + return NotImplemented + + +class TokenInfo: + """Class for storing token information.""" + + def __init__( + self, + token: Token, + decimals: int, + alias: Optional[str] = None, + external_price: Optional[Decimal] = None, + estimated_price: Optional[Decimal] = None, + internal_buffer: Optional[Decimal] = None, + normalize_priority: Optional[int] = 0, + ): + """Constructor.""" + self.token = token + self.alias = alias + self.decimals = decimals + self.external_price = external_price + self.estimated_price = estimated_price + self.internal_buffer = internal_buffer + self._normalize_priority = normalize_priority or 0 + + @property + def normalize_priority(self) -> int: + """ + Return the token priority for normalization purposes. + + Higher value means higher priority. + """ + return self._normalize_priority + + def as_dict(self) -> dict: + """Convert to dict.""" + attr = [ + a + for a in dir(self) + if not callable(getattr(self, a)) and not a.startswith("_") and a != "token" + ] + + return {a: getattr(self, a) for a in sorted(attr)} + + def __str__(self) -> str: + """Convert to string.""" + token_info_dict = self.as_dict() + + _str = f"Token [{self.token}]:" + for attr, value in token_info_dict.items(): + if isinstance(value, Decimal) and attr not in [ + "external_price", + "internal_buffer", + ]: + value = value.quantize(Constants.DECIMAL_STR_PREC) + _str += f"\n-- {attr} : {value}" + + return _str + + +def select_token_with_highest_normalize_priority( + tokens: dict[Token, TokenInfo] +) -> Token: + """ + Select token with highest normalize priority from the list of tokens. + + If the highest normalize_priority is shared by multiple tokens, the + ref_token is the first lexicographically. + """ + max_priority = max(t.normalize_priority for t in tokens.values()) + highest_priority_tokens = [ + t for t, info in tokens.items() if info.normalize_priority == max_priority + ] + return highest_priority_tokens[0] + + +TokenDict = dict[Token, TokenInfo] +TokenSerializedType = str +TokenAmountSerializedType = tuple[Union[str, NumericType], TokenSerializedType] + + +class TokenBalance: + """Class to represent an amount of some token.""" + + def __init__(self, balance: NumericType, token: Token): + """Initialize. + + Args: + balance: Amount of tokens. + token: Token. + """ + + self._balance = Decimal(balance) + self.balance = balance + self.token = token + + if not self._balance.is_finite(): + raise ValueError(f"Token balance must be finite, not {self._balance}!") + + @classmethod + def parse( + cls, + token_amount_serialized: Optional[TokenAmountSerializedType], + allow_negative: bool = False, + allow_none: bool = False, + ) -> Optional[TokenBalance]: + """ + Method to parse a token amount given as (amount, token) into a TokenBalance. + """ + if token_amount_serialized is None: + if not allow_none: + raise ValueError("Token amount must not be None!") + token_amount = None + else: + if not isinstance(token_amount_serialized, dict) or set( + token_amount_serialized.keys() + ) != {"amount", "token"}: + raise ValueError( + "token amount must be given as dict of {'amount': .., 'token': ..}," + f" not <{token_amount_serialized}>!" + ) + token_amount = cls( + Decimal(token_amount_serialized["amount"]), + Token(token_amount_serialized["token"]), + ) + if not allow_negative and token_amount.is_negative(): + raise ValueError(f"Token amount must be non-negative ({token_amount})!") + return token_amount + + @classmethod + def parse_amount( + cls, amt_type: Optional[Amount], token: Token + ) -> Optional[TokenBalance]: + """Auxiliary method to parse a numerical value into a TokenBalance. + + Args: + amt_type: Amount to be set, or None. + token: Token belonging to amount. + + Returns: + A TokenBalance, or None. + + """ + + if isinstance(amt_type, (int, float, Decimal)): + return cls(amt_type, token) + + if isinstance(amt_type, TokenBalance): + if amt_type.token != token: + raise ValueError( + f"Tokens do not match: <{amt_type.token}> vs. <{token}>!" + ) + return amt_type + + return None + + def as_decimal(self) -> Decimal: + """Returns balance attribute as Decimal type""" + return self._balance + + @staticmethod + def precision() -> int: + """Return precision currently associated with TokenBalance.""" + return getcontext().prec + + def is_positive(self) -> bool: + """Determine if a TokenBalance is positive.""" + return self._balance > 0 + + def is_negative(self) -> bool: + """Determine if a TokenBalance is negative.""" + return self._balance < 0 + + def is_zero(self) -> bool: + """Determine if a TokenBalance is zero.""" + return self._balance == 0 + + def __eq__(self, other: object) -> bool: + """Equality operator. + + Args: + other: Another TokenBalance, or zero. + """ + if other == 0: + return self.is_zero() + + if isinstance(other, TokenBalance): + + if self.token != other.token: + raise ValueError( + f"Cannot compare different tokens <{self.token}> / <{other.token}>!" + ) + return self._balance == other._balance + + raise ValueError(f"Cannot compare TokenBalance and type <{type(other)}>!") + + def __ne__(self, other: object) -> bool: + """Non-equality operator""" + return not self == other + + def __lt__(self, other: Union[TokenBalance, NumericType]) -> bool: + """Less-than operator. + + Args: + other: Another TokenBalance, or zero. + """ + if isinstance(other, TokenBalance): + + if self.token != other.token: + raise ValueError( + f"Cannot compare different tokens <{self.token}> / <{other.token}>!" + ) + return self._balance < other._balance + + if other == 0: + return self._balance < 0 + + raise ValueError(f"Cannot compare TokenBalance and type <{type(other)}>") + + def __le__(self, other: Union[TokenBalance, NumericType]) -> bool: + """Less-than-or-equal operator. + + Args: + other: Another TokenBalance, or zero. + """ + return self < other or self == other + + def __gt__(self, other: Union[TokenBalance, NumericType]) -> bool: + """Greater-than operator. + + Args: + other: Another TokenBalance, or zero. + """ + return not self <= other + + def __ge__(self, other: Union[TokenBalance, NumericType]) -> bool: + """Greater-than-or-equal operator. + + Args: + other: Another TokenBalance, or zero. + """ + return self > other or self == other + + def __neg__(self) -> TokenBalance: + """Negation operator.""" + return TokenBalance(-self._balance, self.token) + + def __abs__(self) -> TokenBalance: + """Absolute value operator.""" + return TokenBalance(abs(self._balance), self.token) + + def __add__(self, other: Union[TokenBalance, NumericType]) -> TokenBalance: + """Addition operator. + + Args: + other: Another TokenBalance, or zero. + """ + if isinstance(other, TokenBalance): + + if self.token == other.token: + return TokenBalance(self._balance + other._balance, self.token) + + raise ValueError(f"Cannot add <{other.token}> and <{self.token}>!") + + if other == 0: + # This is required to enable the use of the sum() function: + # sum() by design starts with a value of '0' and then iteratively + # adds the items in the list that is passed as argument. See: + # https://stackoverflow.com/questions/1218710/pythons-sum-and-non-integer-values + return self + + raise ValueError(f"Cannot add <{type(other)}> and TokenBalance!") + + def __radd__(self, other: Union[TokenBalance, NumericType]) -> TokenBalance: + """Addition-from-right operator. + + Args: + other: Another TokenBalance, or zero. + """ + return self + other + + def __sub__(self, other: Union[TokenBalance, NumericType]) -> TokenBalance: + """Subtraction operator. + + Args: + other: Another TokenBalance, or zero. + """ + return self + (-other) + + def __rsub__(self, other: Union[TokenBalance, NumericType]) -> TokenBalance: + """Subtraction operator. + + Args: + other: Another TokenBalance, or zero. + """ + return other + (-self) + + def __mul__(self, other: NumericType) -> TokenBalance: + """Multiplication operator. + + Args: + other: A {float|int|Decimal}. + """ + if isinstance(other, (int, float, Decimal)): + return TokenBalance(Decimal(other) * self._balance, self.token) + + raise ValueError(f"Cannot multiply TokenBalance by <{type(other)}>!") + + def __rmul__(self, other: NumericType) -> TokenBalance: + """Multiplication-from-right operator. + + Args: + other: A {float|int|Decimal}. + """ + return self * other + + def __truediv__(self, other: Union[TokenBalance, NumericType]) -> TokenBalance: + """Division operator. + + Args: + other: A {TokenBalance|float|int|Decimal}. + """ + if isinstance(other, (int, float, Decimal)): + if other == 0: + raise ZeroDivisionError + return TokenBalance(self._balance / Decimal(other), self.token) + + if isinstance(other, TokenBalance): + if self.token == other.token: + return TokenBalance(self._balance / other._balance, self.token) + raise ValueError( + f"Can't divide TokenBalances with different " + f"tokens <{self.token}> and <{other.token}>!" + ) + + raise ValueError(f"Cannot divide TokenBalance by <{type(other)}>!") + + def __rtruediv__(self, other: object) -> None: + """Division-from-right operator. + + Args: + other: Something. + """ + raise ValueError(f"<{type(other)}> cannot be divided by TokenBalance!") + + def __str__(self) -> str: + """Represent as string (rounded to 5 decimals).""" + return f"{self.token}: {self.balance}" + + def __repr__(self) -> str: + """Represent as string.""" + return str(self) + + +Amount = Union[TokenBalance, NumericType] diff --git a/src/models/types.py b/src/models/types.py index 06e458e..140dcbc 100644 --- a/src/models/types.py +++ b/src/models/types.py @@ -1,5 +1,5 @@ -"""Generic Type derived from primitives and imports""" -from decimal import Decimal -from typing import Union - -NumericType = Union[int, float, Decimal] +"""Generic Type derived from primitives and imports""" +from decimal import Decimal +from typing import Union + +NumericType = Union[int, float, Decimal] diff --git a/src/models/uniswap.py b/src/models/uniswap.py index 386f82b..c2ce1e8 100644 --- a/src/models/uniswap.py +++ b/src/models/uniswap.py @@ -1,322 +1,322 @@ -"""Representation of Uniswap pool.""" -from __future__ import annotations - -import json -import logging -from decimal import Decimal -from typing import Optional, Any, Union - -from src.models.exchange_rate import ExchangeRate as XRate -from src.models.token import Token, TokenBalance -from src.models.types import NumericType -from src.util.enums import AMMKind -from src.util.exec_plan_coords import ExecPlanCoords -from src.util.numbers import decimal_to_str - -FeeType = Union[float, Decimal] -UniswapSerializedType = dict[str, Any] -UniswapsSerializedType = dict[str, UniswapSerializedType] - - -class Uniswap: - """Representation of an Automated Market Maker. - - An Uniswap pool is represented by two token balances. - """ - - def __init__( - self, - pool_id: str, - balance1: TokenBalance, - balance2: TokenBalance, - fee: FeeType, - cost: Optional[TokenBalance] = None, - mandatory: bool = False, - kind: AMMKind = AMMKind.UNISWAP, - weight: float = 1, - ): - """Initialize. - - Args: - pool_id: Uniswap pool pool_id. - balance1: TokenBalance of first token. - balance2: TokenBalance of second token. - fee: Uniswap fee percentage. - - Kwargs: - cost: Cost of using the Uniswap pool. - mandatory: Is pool usage mandatory when price moves, or not. - """ - # Consistency checks. - if balance1.token == balance2.token: - logging.error("Pool tokens cannot be equal!") - raise ValueError - - if not all(tb.is_positive() for tb in [balance1, balance2]): - message = f"Uniswap <{pool_id}>: balance1={balance1} balance2={balance2}" - logging.error(message) - raise ValueError("Both token balances must be positive!") - - # Store given pool pool_id. - self.pool_id = pool_id - self.balance1 = balance1 - self.balance2 = balance2 - self.fee = fee if isinstance(fee, Decimal) else Decimal(fee) - self.cost = cost - self.mandatory = mandatory - self.kind = kind - self.weight = weight - - self._balance_update1: Optional[TokenBalance] = None - self._balance_update2: Optional[TokenBalance] = None - self.exec_plan_coords: Optional[ExecPlanCoords] = None - - @classmethod - def from_dict( - cls, amm_id: str, amm_data: UniswapSerializedType - ) -> Optional[Uniswap]: - """Construct AMM object from data dict. - NOTE: Currently, the code only supports Uniswap-style AMMs, i.e., - constant-product pools with two tokens and equal weights. - Args: - amm_id: AMM pool_id. - amm_data: Dict of uniswap data. - Returns: - A Uniswap object. - """ - for attr in ["kind", "reserves", "fee"]: - if attr not in amm_data: - raise ValueError(f"Missing field '{attr}' in amm <{amm_id}>!") - - kind = AMMKind(amm_data["kind"]) - reserves = amm_data.get("reserves") - weight = 0.5 - - if kind == AMMKind.CONSTANT_PRODUCT: - # Parse UniswapV2/Sushiswap pools. - if not isinstance(reserves, dict): - raise ValueError( - f"AMM <{amm_id}>: 'reserves' must be a dict of Token -> amount!" - ) - if len(reserves) != 2: - message = ( - f"AMM <{amm_id}>: " - f"ConstantProduct AMMs are only supported with 2 tokens!" - ) - logging.warning(message) - return None - balance1, balance2 = [ - TokenBalance(Decimal(b), Token(t)) for t, b in reserves.items() - ] - - elif kind == AMMKind.WEIGHTED_PRODUCT: - # Parse Balancer weighted constant-product pools. - if not ( - isinstance(reserves, dict) - and all( - isinstance(reserve_info, dict) and key in reserve_info - for reserve_info in reserves.values() - for key in ["balance", "weight"] - ) - ): - raise ValueError( - f"AMM <{amm_id}>: 'reserves' must be a dict " - f"of Token -> {'balance': .., 'weight': ..}" - ) - if ( - len(reserves) != 2 - or len(set(b["weight"] for b in reserves.values())) > 1 - ): - logging.warning( - f"AMM <{amm_id}>: WeightedProduct AMMs are only supported " - "with 2 tokens and equal weights!" - ) - return None - - weight = list(reserves.values())[0]["weight"] - balance1, balance2 = [ - TokenBalance(Decimal(b["balance"]), Token(t)) - for t, b in reserves.items() - ] - - else: - logging.warning( - f"AMM <{amm_id}>: type <{kind}> is currently not supported!" - ) - return None - - if balance1 == 0 or balance2 == 0: - return None - - return Uniswap( - pool_id=amm_id, - balance1=balance1, - balance2=balance2, - fee=Decimal(amm_data["fee"]), - cost=TokenBalance.parse(amm_data.get("cost"), allow_none=True), - kind=kind, - weight=weight, - ) - - def as_dict(self) -> dict: - """Return AMM object as dictionary. - - NOTE: Currently, the code only supports Uniswap-style AMMs, i.e., - constant-product pools with two tokens and equal weights. - - """ - token1 = str(self.token1) - token2 = str(self.token2) - balance1 = decimal_to_str(self.balance1.as_decimal()) - balance2 = decimal_to_str(self.balance2.as_decimal()) - - reserves: Union[str, dict] - if self.kind == AMMKind.WEIGHTED_PRODUCT: - reserves = { - token1: {"balance": balance1, "weight": self.weight}, - token2: {"balance": balance2, "weight": self.weight}, - } - else: - reserves = {token1: balance1, token2: balance2} - - cost = None - if self.cost is not None: - cost = { - "token": str(self.cost.token), - "amount": decimal_to_str(self.cost.as_decimal()), - } - - execution = {} - if self.is_executed(): - assert self.balance_update1 is not None and self.balance_update2 is not None - b1_update = self.balance_update1.as_decimal() - b2_update = self.balance_update2.as_decimal() - # One update has to be positive and the other negative. - assert ( - b1_update * b2_update < 0 - ), f"Failed assertion, {b1_update} * {b2_update} < 0" - - # Determine buy- and sell-tokens and -amounts. - if b1_update > 0: - buy_token = self.token1 - sell_token = self.token2 - exec_buy_amount = b1_update - exec_sell_amount = -b2_update - - else: - buy_token = self.token2 - sell_token = self.token1 - exec_buy_amount = b2_update - exec_sell_amount = -b1_update - - if self.exec_plan_coords is None: - logging.warning( - f"AMM <{self.pool_id}>: " - f"has balance updates with invalid execution plan" - ) - exec_plan = None - else: - exec_plan = self.exec_plan_coords.as_dict() - - execution = { - "sell_token": str(sell_token), - "buy_token": str(buy_token), - "exec_sell_amount": decimal_to_str(exec_sell_amount), - "exec_buy_amount": decimal_to_str(exec_buy_amount), - "exec_plan": exec_plan, - } - - return { - "kind": str(self.kind), - "reserves": reserves, - "cost": cost, - "fee": decimal_to_str(self.fee), - "execution": execution, - } - - #################### - # ACCESS METHODS # - #################### - - @property - def token1(self) -> Token: - """Returns token1""" - return self.balance1.token - - @property - def token2(self) -> Token: - """Returns token2""" - return self.balance2.token - - @property - def tokens(self) -> set[Token]: - """Return the pool tokens.""" - return {self.balance1.token, self.balance2.token} - - @property - def balance_update1(self) -> Optional[TokenBalance]: - """Return the traded amount of the first token.""" - return self._balance_update1 - - @property - def balance_update2(self) -> Optional[TokenBalance]: - """Return the traded amount of the second token.""" - return self._balance_update2 - - def other_token(self, token: Token) -> Token: - """Returns the "other" token that is not token.""" - assert token in self.tokens - return (self.tokens - {token}).pop() - - ##################### - # UTILITY METHODS # - ##################### - - def execute( - self, - b1_update: NumericType, - b2_update: NumericType, - ) -> None: - """Execute the uniswap at given amounts. - - Args: - b1_update: Traded amount of token1. - b2_update: Traded amount of token2. - """ - assert isinstance(b1_update, (int, float, Decimal)) - assert isinstance(b2_update, (int, float, Decimal)) - - # Store execution information. - self._balance_update1 = TokenBalance(b1_update, self.token1) - self._balance_update2 = TokenBalance(b2_update, self.token2) - - def is_executed(self) -> bool: - """True if amm is executed otherwise false""" - return self.balance_update1 is not None and self.balance_update2 is not None - - def get_marginal_xrate(self) -> XRate: - """Derive the marginal exchange rate from the pool balances.""" - return XRate(self.balance1, self.balance2) - - def __str__(self) -> str: - """Represent as string.""" - return json.dumps(self.as_dict(), indent=2) - - def __repr__(self) -> str: - """Represent as short string.""" - return f"u{self.pool_id}" - - def __hash__(self) -> int: - return hash(self.pool_id) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Uniswap): - return NotImplemented - if self.pool_id != other.pool_id: - return False - return True - - def __lt__(self, other: object) -> bool: - if not isinstance(other, Uniswap): - return NotImplemented - return self.pool_id < other.pool_id +"""Representation of Uniswap pool.""" +from __future__ import annotations + +import json +import logging +from decimal import Decimal +from typing import Optional, Any, Union + +from src.models.exchange_rate import ExchangeRate as XRate +from src.models.token import Token, TokenBalance +from src.models.types import NumericType +from src.util.enums import AMMKind +from src.util.exec_plan_coords import ExecPlanCoords +from src.util.numbers import decimal_to_str + +FeeType = Union[float, Decimal] +UniswapSerializedType = dict[str, Any] +UniswapsSerializedType = dict[str, UniswapSerializedType] + + +class Uniswap: + """Representation of an Automated Market Maker. + + An Uniswap pool is represented by two token balances. + """ + + def __init__( + self, + pool_id: str, + balance1: TokenBalance, + balance2: TokenBalance, + fee: FeeType, + cost: Optional[TokenBalance] = None, + mandatory: bool = False, + kind: AMMKind = AMMKind.UNISWAP, + weight: float = 1, + ): + """Initialize. + + Args: + pool_id: Uniswap pool pool_id. + balance1: TokenBalance of first token. + balance2: TokenBalance of second token. + fee: Uniswap fee percentage. + + Kwargs: + cost: Cost of using the Uniswap pool. + mandatory: Is pool usage mandatory when price moves, or not. + """ + # Consistency checks. + if balance1.token == balance2.token: + logging.error("Pool tokens cannot be equal!") + raise ValueError + + if not all(tb.is_positive() for tb in [balance1, balance2]): + message = f"Uniswap <{pool_id}>: balance1={balance1} balance2={balance2}" + logging.error(message) + raise ValueError("Both token balances must be positive!") + + # Store given pool pool_id. + self.pool_id = pool_id + self.balance1 = balance1 + self.balance2 = balance2 + self.fee = fee if isinstance(fee, Decimal) else Decimal(fee) + self.cost = cost + self.mandatory = mandatory + self.kind = kind + self.weight = weight + + self._balance_update1: Optional[TokenBalance] = None + self._balance_update2: Optional[TokenBalance] = None + self.exec_plan_coords: Optional[ExecPlanCoords] = None + + @classmethod + def from_dict( + cls, amm_id: str, amm_data: UniswapSerializedType + ) -> Optional[Uniswap]: + """Construct AMM object from data dict. + NOTE: Currently, the code only supports Uniswap-style AMMs, i.e., + constant-product pools with two tokens and equal weights. + Args: + amm_id: AMM pool_id. + amm_data: Dict of uniswap data. + Returns: + A Uniswap object. + """ + for attr in ["kind", "reserves", "fee"]: + if attr not in amm_data: + raise ValueError(f"Missing field '{attr}' in amm <{amm_id}>!") + + kind = AMMKind(amm_data["kind"]) + reserves = amm_data.get("reserves") + weight = 0.5 + + if kind == AMMKind.CONSTANT_PRODUCT: + # Parse UniswapV2/Sushiswap pools. + if not isinstance(reserves, dict): + raise ValueError( + f"AMM <{amm_id}>: 'reserves' must be a dict of Token -> amount!" + ) + if len(reserves) != 2: + message = ( + f"AMM <{amm_id}>: " + f"ConstantProduct AMMs are only supported with 2 tokens!" + ) + logging.warning(message) + return None + balance1, balance2 = [ + TokenBalance(Decimal(b), Token(t)) for t, b in reserves.items() + ] + + elif kind == AMMKind.WEIGHTED_PRODUCT: + # Parse Balancer weighted constant-product pools. + if not ( + isinstance(reserves, dict) + and all( + isinstance(reserve_info, dict) and key in reserve_info + for reserve_info in reserves.values() + for key in ["balance", "weight"] + ) + ): + raise ValueError( + f"AMM <{amm_id}>: 'reserves' must be a dict " + f"of Token -> {'balance': .., 'weight': ..}" + ) + if ( + len(reserves) != 2 + or len(set(b["weight"] for b in reserves.values())) > 1 + ): + logging.warning( + f"AMM <{amm_id}>: WeightedProduct AMMs are only supported " + "with 2 tokens and equal weights!" + ) + return None + + weight = list(reserves.values())[0]["weight"] + balance1, balance2 = [ + TokenBalance(Decimal(b["balance"]), Token(t)) + for t, b in reserves.items() + ] + + else: + logging.warning( + f"AMM <{amm_id}>: type <{kind}> is currently not supported!" + ) + return None + + if balance1 == 0 or balance2 == 0: + return None + + return Uniswap( + pool_id=amm_id, + balance1=balance1, + balance2=balance2, + fee=Decimal(amm_data["fee"]), + cost=TokenBalance.parse(amm_data.get("cost"), allow_none=True), + kind=kind, + weight=weight, + ) + + def as_dict(self) -> dict: + """Return AMM object as dictionary. + + NOTE: Currently, the code only supports Uniswap-style AMMs, i.e., + constant-product pools with two tokens and equal weights. + + """ + token1 = str(self.token1) + token2 = str(self.token2) + balance1 = decimal_to_str(self.balance1.as_decimal()) + balance2 = decimal_to_str(self.balance2.as_decimal()) + + reserves: Union[str, dict] + if self.kind == AMMKind.WEIGHTED_PRODUCT: + reserves = { + token1: {"balance": balance1, "weight": self.weight}, + token2: {"balance": balance2, "weight": self.weight}, + } + else: + reserves = {token1: balance1, token2: balance2} + + cost = None + if self.cost is not None: + cost = { + "token": str(self.cost.token), + "amount": decimal_to_str(self.cost.as_decimal()), + } + + execution = {} + if self.is_executed(): + assert self.balance_update1 is not None and self.balance_update2 is not None + b1_update = self.balance_update1.as_decimal() + b2_update = self.balance_update2.as_decimal() + # One update has to be positive and the other negative. + assert ( + b1_update * b2_update < 0 + ), f"Failed assertion, {b1_update} * {b2_update} < 0" + + # Determine buy- and sell-tokens and -amounts. + if b1_update > 0: + buy_token = self.token1 + sell_token = self.token2 + exec_buy_amount = b1_update + exec_sell_amount = -b2_update + + else: + buy_token = self.token2 + sell_token = self.token1 + exec_buy_amount = b2_update + exec_sell_amount = -b1_update + + if self.exec_plan_coords is None: + logging.warning( + f"AMM <{self.pool_id}>: " + f"has balance updates with invalid execution plan" + ) + exec_plan = None + else: + exec_plan = self.exec_plan_coords.as_dict() + + execution = { + "sell_token": str(sell_token), + "buy_token": str(buy_token), + "exec_sell_amount": decimal_to_str(exec_sell_amount), + "exec_buy_amount": decimal_to_str(exec_buy_amount), + "exec_plan": exec_plan, + } + + return { + "kind": str(self.kind), + "reserves": reserves, + "cost": cost, + "fee": decimal_to_str(self.fee), + "execution": execution, + } + + #################### + # ACCESS METHODS # + #################### + + @property + def token1(self) -> Token: + """Returns token1""" + return self.balance1.token + + @property + def token2(self) -> Token: + """Returns token2""" + return self.balance2.token + + @property + def tokens(self) -> set[Token]: + """Return the pool tokens.""" + return {self.balance1.token, self.balance2.token} + + @property + def balance_update1(self) -> Optional[TokenBalance]: + """Return the traded amount of the first token.""" + return self._balance_update1 + + @property + def balance_update2(self) -> Optional[TokenBalance]: + """Return the traded amount of the second token.""" + return self._balance_update2 + + def other_token(self, token: Token) -> Token: + """Returns the "other" token that is not token.""" + assert token in self.tokens + return (self.tokens - {token}).pop() + + ##################### + # UTILITY METHODS # + ##################### + + def execute( + self, + b1_update: NumericType, + b2_update: NumericType, + ) -> None: + """Execute the uniswap at given amounts. + + Args: + b1_update: Traded amount of token1. + b2_update: Traded amount of token2. + """ + assert isinstance(b1_update, (int, float, Decimal)) + assert isinstance(b2_update, (int, float, Decimal)) + + # Store execution information. + self._balance_update1 = TokenBalance(b1_update, self.token1) + self._balance_update2 = TokenBalance(b2_update, self.token2) + + def is_executed(self) -> bool: + """True if amm is executed otherwise false""" + return self.balance_update1 is not None and self.balance_update2 is not None + + def get_marginal_xrate(self) -> XRate: + """Derive the marginal exchange rate from the pool balances.""" + return XRate(self.balance1, self.balance2) + + def __str__(self) -> str: + """Represent as string.""" + return json.dumps(self.as_dict(), indent=2) + + def __repr__(self) -> str: + """Represent as short string.""" + return f"u{self.pool_id}" + + def __hash__(self) -> int: + return hash(self.pool_id) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Uniswap): + return NotImplemented + if self.pool_id != other.pool_id: + return False + return True + + def __lt__(self, other: object) -> bool: + if not isinstance(other, Uniswap): + return NotImplemented + return self.pool_id < other.pool_id diff --git a/src/util/constants.py b/src/util/constants.py index a2c27d7..1704225 100644 --- a/src/util/constants.py +++ b/src/util/constants.py @@ -1,18 +1,18 @@ -"""Global constants.""" - -from decimal import Decimal - - -class Constants: - """Configuration parameters for the solver.""" - - # Precision of Decimal in strings (to be used as x.quantize(DECIMAL_STR_PREC)). - DECIMAL_STR_PREC = Decimal("1e-10") - - # Should an exception be raised when the solution violates the - # max sell amount constraint. - RAISE_ON_MAX_SELL_AMOUNT_VIOLATION = False - - # Should an exception be raised when the solution violates the - # limit exchange rate constraint. - RAISE_ON_LIMIT_XRATE_VIOLATION = False +"""Global constants.""" + +from decimal import Decimal + + +class Constants: + """Configuration parameters for the solver.""" + + # Precision of Decimal in strings (to be used as x.quantize(DECIMAL_STR_PREC)). + DECIMAL_STR_PREC = Decimal("1e-10") + + # Should an exception be raised when the solution violates the + # max sell amount constraint. + RAISE_ON_MAX_SELL_AMOUNT_VIOLATION = False + + # Should an exception be raised when the solution violates the + # limit exchange rate constraint. + RAISE_ON_LIMIT_XRATE_VIOLATION = False diff --git a/src/util/enums.py b/src/util/enums.py index 6bb6f8a..02c37bc 100644 --- a/src/util/enums.py +++ b/src/util/enums.py @@ -1,37 +1,37 @@ -"""Location of all Enum types""" - -from enum import Enum - - -class AMMKind(Enum): - """Enum for different AMM kinds.""" - - UNISWAP = "Uniswap" - CONSTANT_PRODUCT = "ConstantProduct" - WEIGHTED_PRODUCT = "WeightedProduct" - STABLE = "Stable" - CONCENTRATED = "Concentrated" - - def __str__(self) -> str: - """Represent as string.""" - return self.value - - def __repr__(self) -> str: - """Represent as string.""" - return str(self) - - -class Chain(Enum): - """Enum for the blockchain of the batch auction.""" - - MAINNET = "MAINNET" - XDAI = "XDAI" - UNKNOWN = "UNKNOWN" - - def __str__(self) -> str: - """Represent as string.""" - return self.name - - def __repr__(self) -> str: - """Represent as string.""" - return str(self) +"""Location of all Enum types""" + +from enum import Enum + + +class AMMKind(Enum): + """Enum for different AMM kinds.""" + + UNISWAP = "Uniswap" + CONSTANT_PRODUCT = "ConstantProduct" + WEIGHTED_PRODUCT = "WeightedProduct" + STABLE = "Stable" + CONCENTRATED = "Concentrated" + + def __str__(self) -> str: + """Represent as string.""" + return self.value + + def __repr__(self) -> str: + """Represent as string.""" + return str(self) + + +class Chain(Enum): + """Enum for the blockchain of the batch auction.""" + + MAINNET = "MAINNET" + XDAI = "XDAI" + UNKNOWN = "UNKNOWN" + + def __str__(self) -> str: + """Represent as string.""" + return self.name + + def __repr__(self) -> str: + """Represent as string.""" + return str(self) diff --git a/src/util/exec_plan_coords.py b/src/util/exec_plan_coords.py index befc26b..75dc171 100644 --- a/src/util/exec_plan_coords.py +++ b/src/util/exec_plan_coords.py @@ -1,21 +1,21 @@ -"""Execution Plan Coordinates""" - - -class ExecPlanCoords: - """The position coordinates of the uniswap in the execution plan. - - The position is defined by a pair of integers: - * Id of the sequence. - * Position within that sequence. - """ - - def __init__(self, sequence: int, position: int): - self.sequence = sequence - self.position = position - - def as_dict(self) -> dict[str, str]: - """returns string dict of class""" - return { - "sequence": str(self.sequence), - "position": str(self.position), - } +"""Execution Plan Coordinates""" + + +class ExecPlanCoords: + """The position coordinates of the uniswap in the execution plan. + + The position is defined by a pair of integers: + * Id of the sequence. + * Position within that sequence. + """ + + def __init__(self, sequence: int, position: int): + self.sequence = sequence + self.position = position + + def as_dict(self) -> dict[str, str]: + """returns string dict of class""" + return { + "sequence": str(self.sequence), + "position": str(self.position), + } diff --git a/src/util/numbers.py b/src/util/numbers.py index 7967359..660dfdd 100644 --- a/src/util/numbers.py +++ b/src/util/numbers.py @@ -1,10 +1,10 @@ -"""Utility methods for number handling""" -from decimal import Decimal - - -def decimal_to_str(number: Decimal) -> str: - """Converts Decimal to string""" - try: - return f"{round(float(number), 12):.12f}".rstrip("0").rstrip(".") - except ValueError as err: - raise ValueError(f"Could not convert <{number}> into a string!") from err +"""Utility methods for number handling""" +from decimal import Decimal + + +def decimal_to_str(number: Decimal) -> str: + """Converts Decimal to string""" + try: + return f"{round(float(number), 12):.12f}".rstrip("0").rstrip(".") + except ValueError as err: + raise ValueError(f"Could not convert <{number}> into a string!") from err diff --git a/src/util/schema.py b/src/util/schema.py index f9f3917..ef2754a 100644 --- a/src/util/schema.py +++ b/src/util/schema.py @@ -1,399 +1,399 @@ -""" -This file defines problem, solution, and solver parameters schemas, -using pydantic that is then used to validate IO and autogenerate -documentation. -""" - -from typing import Dict, List, Optional, Union - -from enum import Enum -from pydantic import BaseModel, Field - -# Example instance to use in the autogenerated API documentation. - -example_instance = { - "metadata": { - "environment": "xDAI", - "auction_id": 1, - "gas_price": 4850000000.0, - "native_token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", - }, - "tokens": { - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "decimals": 18, - "alias": "DAI", - "external_price": 0.00021508661247926934, - "normalize_priority": 0, - "internal_buffer": "8213967696976545926330", - }, - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "decimals": 6, - "alias": "USDC", - "external_price": 214890212.34875953, - "normalize_priority": 0, - "internal_buffer": "2217249148", - }, - "0xdac17f958d2ee523a2206206994597c13d831ec7": { - "decimals": 6, - "alias": "USDT", - "external_price": 214523029.31427807, - "normalize_priority": 0, - "internal_buffer": "4227015605", - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "decimals": 18, - "alias": "WETH", - "external_price": 1.0, - "normalize_priority": 1, - "internal_buffer": "895880027660372311", - }, - }, - "orders": { - "0": { - "sell_token": "0x6b175474e89094c44da98b954eedeac495271d0f", - "buy_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "sell_amount": "4693994755140375611596", - "buy_amount": "1000000000000000000", - "allow_partial_fill": False, - "is_sell_order": False, - "fee": { - "amount": "103079335446226157568", - "token": "0x6b175474e89094c44da98b954eedeac495271d0f", - }, - "cost": { - "amount": "6657722265694875", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "is_liquidity_order": False, - }, - "1": { - "sell_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "buy_token": "0x6b175474e89094c44da98b954eedeac495271d0f", - "sell_amount": "1000000000000000000", - "buy_amount": "4692581049969374626065", - "allow_partial_fill": False, - "is_sell_order": True, - "fee": { - "amount": "23212472598551576", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "cost": { - "amount": "6657722265694875", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "is_liquidity_order": True, - }, - }, - "amms": { - "01": { - "kind": "ConstantProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "44897630044876228891318837", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "9626911517235794223708", - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "mandatory": False, - }, - "02": { - "kind": "ConstantProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "84903768350604287941150958", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "18233677073990818080605", - }, - "fee": "0.003", - "cost": { - "amount": "9507044675748200", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "mandatory": False, - }, - "03": { - "kind": "WeightedProduct", - "reserves": { - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "balance": "1191959749018354276837", - "weight": "0.4", - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "392171457910841840", - "weight": "0.6", - }, - }, - "fee": "0.0025", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "mandatory": False, - }, - "04": { - "kind": "WeightedProduct", - "reserves": { - "0x6810e776880c02933d47db1b9fc05908e5386b96": { - "balance": "21330539255670269346", - "weight": "0.25", - }, - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "balance": "10928595376682871418747", - "weight": "0.25", - }, - "0xba100000625a3754423978a60c9317c58a424e3d": { - "balance": "444658133648670940819", - "weight": "0.25", - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "2237408990689298635", - "weight": "0.25", - }, - }, - "fee": "0.01", - "cost": { - "amount": "12047450379000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "mandatory": False, - }, - }, -} - - -# The following classes model the contents of a PROBLEM instance. -# They are used for input validation and documentation. - - -class TokenId(str): - """Token unique identifier.""" - - -class OrderId(str): - """Order unique identifier.""" - - -class AmmId(str): - """AMM unique identifier.""" - - -class BigInt(str): - """Big integer (as a string).""" - - -class Decimal(str): - """Decimal number (as a string).""" - - -class TokenInfoModel(BaseModel): - """Token-specific data.""" - - decimals: Optional[int] = Field(None, description="Number of decimals.") - alias: Optional[str] = Field(None, description="Human-readable name (e.g. DAI).") - normalize_priority: Optional[int] = Field( - 0, - description="Priority for solution price vector normalization purposes " - "(larger=higher preference).", - ) - external_price: Optional[Decimal] = Field(None, description="External token price.") - internal_buffer: Optional[BigInt] = Field( - None, description="Internal token buffer." - ) - - -class TokenAmountModel(BaseModel): - """Order/AMM cost and order fee.""" - - amount: BigInt = Field(..., description="Amount.") - token: TokenId = Field(..., description="Token.") - - -class OrderModel(BaseModel): - """Order data.""" - - sell_token: TokenId = Field(..., description="Token to be sold.") - buy_token: TokenId = Field(..., description="Token to be bought.") - sell_amount: BigInt = Field( - ..., - description="If is_sell_order=true indicates the maximum amount to sell, " - "otherwise the maximum amount to sell in order to buy buy_amount.", - ) - buy_amount: BigInt = Field( - ..., - description="If is_sell_order=false indicates the maximum amount to buy, " - "otherwise the minimum amount to buy in order to sell sell_amount.", - ) - allow_partial_fill: bool = Field( - ..., - description="If the order can sell/buy less than its maximum sell/buy amount.", - ) - is_sell_order: bool = Field( - ..., - description="If it is a sell or buy order, changing the semantics of " - "sell_amount/buy_amount accordingly.", - ) - is_liquidity_order: Optional[bool] = Field( - False, - description="Liquidity orders (from market makers) can not receive surplus.", - ) - has_atomic_execution: Optional[bool] = Field( - False, description="Indicates, if the order needs to be executed atomically." - ) - fee: Optional[TokenAmountModel] = Field( - None, - description="Fee contribution when order is matched " - "(pro-rata for partial matching).", - ) - cost: Optional[TokenAmountModel] = Field( - None, description="Cost of matching the order." - ) - - class Config: - """Includes example in generated openapi file""" - - schema_extra = { - "example": { - "sell_token": "0x6b175474e89094c44da98b954eedeac495271d0f", - "buy_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "sell_amount": "4693994755140375611596", - "buy_amount": "1000000000000000000", - "allow_partial_fill": False, - "is_sell_order": False, - "fee": { - "amount": "103079335446226157568", - "token": "0x6b175474e89094c44da98b954eedeac495271d0f", - }, - "cost": { - "amount": "6657722265694875", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "is_liquidity_order": False, - } - } - - -class AmmKindEnum(str, Enum): - """AMM kind.""" - - CONSTANT_PRODUCT = "ConstantProduct" - WEIGHTED_PRODUCT = "WeightedProduct" - STABLE = "Stable" - CONCENTRATED = "Concentrated" - - -class ConstantProductReservesModel(BigInt): - """Tokens and balances of constant-product AMMs.""" - - -class WeightedProductReservesModel(BaseModel): - """Tokens and balances+weights of weighted-product AMMs.""" - - balance: BigInt = Field(..., description="Token balance in AMM.") - weight: BigInt = Field(..., description="Weight of the token.") - - -class AmmModel(BaseModel): - """AMM data.""" - - kind: AmmKindEnum = Field(..., description="AMM type.") - reserves: Optional[ - Dict[TokenId, Union[ConstantProductReservesModel, WeightedProductReservesModel]] - ] = Field(None, description="AMM tokens and balances.") - fee: Optional[Decimal] = Field( - None, description="AMM trading fee (e.g. 0.003 for 0.3% fee)." - ) - cost: Optional[TokenAmountModel] = Field( - None, description="Cost of using the pool." - ) - - -class MetadataModel(BaseModel): - """Batch auction metadata.""" - - environment: Optional[str] = Field( - None, description="Runtime/blockchain environment." - ) - auction_id: Optional[str] = Field(..., description="Max Number of executed orders") - gas_price: Optional[float] = Field(..., description="Current Gas price") - native_token: Optional[TokenId] = Field(..., description="Wrapped Native Token") - - -class BatchAuctionModel(BaseModel): - """Batch auction instance data.""" - - tokens: Dict[TokenId, TokenInfoModel] = Field(..., description="Tokens.") - orders: Dict[OrderId, OrderModel] = Field(..., description="Orders.") - metadata: MetadataModel = Field({}, description="Metadata.") - amms: Optional[Dict[AmmId, AmmModel]] = Field({}, description="AMMs") - - class Config: - """Includes example in generated openapi file""" - - schema_extra = {"example": example_instance} - - -# The following classes model the contents of a SOLUTION instance. -# They are used for input validation and documentation. - - -class ExecutedOrderModel(OrderModel): - """Executed order data (solution).""" - - exec_buy_amount: BigInt = Field(..., description="Executed buy amount.") - exec_sell_amount: BigInt = Field(..., description="Executed sell amount.") - - -class ExecPlanCoordsModel(BaseModel): - """Execution plan coordinates.""" - - sequence: int = Field(..., description="Sequence index.") - position: int = Field(..., description="Position within the sequence.") - internal: Optional[bool] = Field(False, description="Using internal liquidity") - - -class AmmExecutionModel(BaseModel): - """AMM settlement information.""" - - sell_token: TokenId = Field(..., description="Token sold by the AMM.") - buy_token: TokenId = Field(..., description="Token bought by the AMM.") - exec_sell_amount: BigInt = Field(..., description="Executed sell amount.") - exec_buy_amount: BigInt = Field(..., description="Executed buy amount.") - exec_plan: Optional[ExecPlanCoordsModel] = Field( - None, description="Execution plan coordinates." - ) - - -class ExecutedAmmModel(AmmModel): - """List of AMM executions.""" - - execution: Optional[List[AmmExecutionModel]] = Field( - None, description="AMM settlement data." - ) - - -class InteractionData(BaseModel): - """Interaction data.""" - - target: TokenId = Field( - ..., description="Target contract address to interact with." - ) - value: BigInt = Field( - ..., description="Value of native token, e.g. amount eth in eth transfer" - ) - call_data: bytes = Field(..., description="Interaction encoding.") - - -class SettledBatchAuctionModel(BaseModel): - """Settled batch auction data (solution).""" - - orders: Dict[OrderId, ExecutedOrderModel] = Field( - ..., description="Executed orders." - ) - prices: Dict[TokenId, BigInt] = Field( - ..., description="Settled price for each token." - ) - amms: Dict[AmmId, ExecutedAmmModel] = Field(..., description="Executed AMMs.") - - interaction_data: List[InteractionData] = Field( - description="List of interaction data.", default=[] - ) +""" +This file defines problem, solution, and solver parameters schemas, +using pydantic that is then used to validate IO and autogenerate +documentation. +""" + +from typing import Dict, List, Optional, Union + +from enum import Enum +from pydantic import BaseModel, Field + +# Example instance to use in the autogenerated API documentation. + +example_instance = { + "metadata": { + "environment": "xDAI", + "auction_id": 1, + "gas_price": 4850000000.0, + "native_token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", + }, + "tokens": { + "0x6b175474e89094c44da98b954eedeac495271d0f": { + "decimals": 18, + "alias": "DAI", + "external_price": 0.00021508661247926934, + "normalize_priority": 0, + "internal_buffer": "8213967696976545926330", + }, + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { + "decimals": 6, + "alias": "USDC", + "external_price": 214890212.34875953, + "normalize_priority": 0, + "internal_buffer": "2217249148", + }, + "0xdac17f958d2ee523a2206206994597c13d831ec7": { + "decimals": 6, + "alias": "USDT", + "external_price": 214523029.31427807, + "normalize_priority": 0, + "internal_buffer": "4227015605", + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "decimals": 18, + "alias": "WETH", + "external_price": 1.0, + "normalize_priority": 1, + "internal_buffer": "895880027660372311", + }, + }, + "orders": { + "0": { + "sell_token": "0x6b175474e89094c44da98b954eedeac495271d0f", + "buy_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "sell_amount": "4693994755140375611596", + "buy_amount": "1000000000000000000", + "allow_partial_fill": False, + "is_sell_order": False, + "fee": { + "amount": "103079335446226157568", + "token": "0x6b175474e89094c44da98b954eedeac495271d0f", + }, + "cost": { + "amount": "6657722265694875", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + }, + "is_liquidity_order": False, + }, + "1": { + "sell_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "buy_token": "0x6b175474e89094c44da98b954eedeac495271d0f", + "sell_amount": "1000000000000000000", + "buy_amount": "4692581049969374626065", + "allow_partial_fill": False, + "is_sell_order": True, + "fee": { + "amount": "23212472598551576", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + }, + "cost": { + "amount": "6657722265694875", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + }, + "is_liquidity_order": True, + }, + }, + "amms": { + "01": { + "kind": "ConstantProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "44897630044876228891318837", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "9626911517235794223708", + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + }, + "mandatory": False, + }, + "02": { + "kind": "ConstantProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "84903768350604287941150958", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "18233677073990818080605", + }, + "fee": "0.003", + "cost": { + "amount": "9507044675748200", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + }, + "mandatory": False, + }, + "03": { + "kind": "WeightedProduct", + "reserves": { + "0x6b175474e89094c44da98b954eedeac495271d0f": { + "balance": "1191959749018354276837", + "weight": "0.4", + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "392171457910841840", + "weight": "0.6", + }, + }, + "fee": "0.0025", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + }, + "mandatory": False, + }, + "04": { + "kind": "WeightedProduct", + "reserves": { + "0x6810e776880c02933d47db1b9fc05908e5386b96": { + "balance": "21330539255670269346", + "weight": "0.25", + }, + "0x6b175474e89094c44da98b954eedeac495271d0f": { + "balance": "10928595376682871418747", + "weight": "0.25", + }, + "0xba100000625a3754423978a60c9317c58a424e3d": { + "balance": "444658133648670940819", + "weight": "0.25", + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "2237408990689298635", + "weight": "0.25", + }, + }, + "fee": "0.01", + "cost": { + "amount": "12047450379000000", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + }, + "mandatory": False, + }, + }, +} + + +# The following classes model the contents of a PROBLEM instance. +# They are used for input validation and documentation. + + +class TokenId(str): + """Token unique identifier.""" + + +class OrderId(str): + """Order unique identifier.""" + + +class AmmId(str): + """AMM unique identifier.""" + + +class BigInt(str): + """Big integer (as a string).""" + + +class Decimal(str): + """Decimal number (as a string).""" + + +class TokenInfoModel(BaseModel): + """Token-specific data.""" + + decimals: Optional[int] = Field(None, description="Number of decimals.") + alias: Optional[str] = Field(None, description="Human-readable name (e.g. DAI).") + normalize_priority: Optional[int] = Field( + 0, + description="Priority for solution price vector normalization purposes " + "(larger=higher preference).", + ) + external_price: Optional[Decimal] = Field(None, description="External token price.") + internal_buffer: Optional[BigInt] = Field( + None, description="Internal token buffer." + ) + + +class TokenAmountModel(BaseModel): + """Order/AMM cost and order fee.""" + + amount: BigInt = Field(..., description="Amount.") + token: TokenId = Field(..., description="Token.") + + +class OrderModel(BaseModel): + """Order data.""" + + sell_token: TokenId = Field(..., description="Token to be sold.") + buy_token: TokenId = Field(..., description="Token to be bought.") + sell_amount: BigInt = Field( + ..., + description="If is_sell_order=true indicates the maximum amount to sell, " + "otherwise the maximum amount to sell in order to buy buy_amount.", + ) + buy_amount: BigInt = Field( + ..., + description="If is_sell_order=false indicates the maximum amount to buy, " + "otherwise the minimum amount to buy in order to sell sell_amount.", + ) + allow_partial_fill: bool = Field( + ..., + description="If the order can sell/buy less than its maximum sell/buy amount.", + ) + is_sell_order: bool = Field( + ..., + description="If it is a sell or buy order, changing the semantics of " + "sell_amount/buy_amount accordingly.", + ) + is_liquidity_order: Optional[bool] = Field( + False, + description="Liquidity orders (from market makers) can not receive surplus.", + ) + has_atomic_execution: Optional[bool] = Field( + False, description="Indicates, if the order needs to be executed atomically." + ) + fee: Optional[TokenAmountModel] = Field( + None, + description="Fee contribution when order is matched " + "(pro-rata for partial matching).", + ) + cost: Optional[TokenAmountModel] = Field( + None, description="Cost of matching the order." + ) + + class Config: + """Includes example in generated openapi file""" + + schema_extra = { + "example": { + "sell_token": "0x6b175474e89094c44da98b954eedeac495271d0f", + "buy_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "sell_amount": "4693994755140375611596", + "buy_amount": "1000000000000000000", + "allow_partial_fill": False, + "is_sell_order": False, + "fee": { + "amount": "103079335446226157568", + "token": "0x6b175474e89094c44da98b954eedeac495271d0f", + }, + "cost": { + "amount": "6657722265694875", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + }, + "is_liquidity_order": False, + } + } + + +class AmmKindEnum(str, Enum): + """AMM kind.""" + + CONSTANT_PRODUCT = "ConstantProduct" + WEIGHTED_PRODUCT = "WeightedProduct" + STABLE = "Stable" + CONCENTRATED = "Concentrated" + + +class ConstantProductReservesModel(BigInt): + """Tokens and balances of constant-product AMMs.""" + + +class WeightedProductReservesModel(BaseModel): + """Tokens and balances+weights of weighted-product AMMs.""" + + balance: BigInt = Field(..., description="Token balance in AMM.") + weight: BigInt = Field(..., description="Weight of the token.") + + +class AmmModel(BaseModel): + """AMM data.""" + + kind: AmmKindEnum = Field(..., description="AMM type.") + reserves: Optional[ + Dict[TokenId, Union[ConstantProductReservesModel, WeightedProductReservesModel]] + ] = Field(None, description="AMM tokens and balances.") + fee: Optional[Decimal] = Field( + None, description="AMM trading fee (e.g. 0.003 for 0.3% fee)." + ) + cost: Optional[TokenAmountModel] = Field( + None, description="Cost of using the pool." + ) + + +class MetadataModel(BaseModel): + """Batch auction metadata.""" + + environment: Optional[str] = Field( + None, description="Runtime/blockchain environment." + ) + auction_id: Optional[str] = Field(..., description="Max Number of executed orders") + gas_price: Optional[float] = Field(..., description="Current Gas price") + native_token: Optional[TokenId] = Field(..., description="Wrapped Native Token") + + +class BatchAuctionModel(BaseModel): + """Batch auction instance data.""" + + tokens: Dict[TokenId, TokenInfoModel] = Field(..., description="Tokens.") + orders: Dict[OrderId, OrderModel] = Field(..., description="Orders.") + metadata: MetadataModel = Field({}, description="Metadata.") + amms: Optional[Dict[AmmId, AmmModel]] = Field({}, description="AMMs") + + class Config: + """Includes example in generated openapi file""" + + schema_extra = {"example": example_instance} + + +# The following classes model the contents of a SOLUTION instance. +# They are used for input validation and documentation. + + +class ExecutedOrderModel(OrderModel): + """Executed order data (solution).""" + + exec_buy_amount: BigInt = Field(..., description="Executed buy amount.") + exec_sell_amount: BigInt = Field(..., description="Executed sell amount.") + + +class ExecPlanCoordsModel(BaseModel): + """Execution plan coordinates.""" + + sequence: int = Field(..., description="Sequence index.") + position: int = Field(..., description="Position within the sequence.") + internal: Optional[bool] = Field(False, description="Using internal liquidity") + + +class AmmExecutionModel(BaseModel): + """AMM settlement information.""" + + sell_token: TokenId = Field(..., description="Token sold by the AMM.") + buy_token: TokenId = Field(..., description="Token bought by the AMM.") + exec_sell_amount: BigInt = Field(..., description="Executed sell amount.") + exec_buy_amount: BigInt = Field(..., description="Executed buy amount.") + exec_plan: Optional[ExecPlanCoordsModel] = Field( + None, description="Execution plan coordinates." + ) + + +class ExecutedAmmModel(AmmModel): + """List of AMM executions.""" + + execution: Optional[List[AmmExecutionModel]] = Field( + None, description="AMM settlement data." + ) + + +class InteractionData(BaseModel): + """Interaction data.""" + + target: TokenId = Field( + ..., description="Target contract address to interact with." + ) + value: BigInt = Field( + ..., description="Value of native token, e.g. amount eth in eth transfer" + ) + call_data: bytes = Field(..., description="Interaction encoding.") + + +class SettledBatchAuctionModel(BaseModel): + """Settled batch auction data (solution).""" + + orders: Dict[OrderId, ExecutedOrderModel] = Field( + ..., description="Executed orders." + ) + prices: Dict[TokenId, BigInt] = Field( + ..., description="Settled price for each token." + ) + amms: Dict[AmmId, ExecutedAmmModel] = Field(..., description="Executed AMMs.") + + interaction_data: List[InteractionData] = Field( + description="List of interaction data.", default=[] + ) diff --git a/tests/unit/test_order.py b/tests/unit/test_order.py index 70b379c..8e4b08f 100644 --- a/tests/unit/test_order.py +++ b/tests/unit/test_order.py @@ -1,98 +1,98 @@ -import unittest - -from src.models.order import Order, OrderMatchType -from src.models.token import Token - - -class MyTestCase(unittest.TestCase): - def setUp(self) -> None: - self.token_a = "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1" - self.token_b = "0x177127622c4a00f3d409b75571e12cb3c8973d3c" - self.token_c = "0x1111111111111111111111111111111111111111" - - def overlapping_orders(self): - order1 = Order.from_dict( - "1", - { - "sell_token": self.token_a, - "buy_token": self.token_b, - "sell_amount": "12", - "buy_amount": "100", - "allow_partial_fill": False, - "is_sell_order": True, - "fee": { - "amount": "115995469750", - "token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1", - }, - "cost": { - "amount": "321627750000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", - }, - "is_liquidity_order": False, - "mandatory": False, - "has_atomic_execution": False, - }, - ) - order2 = Order.from_dict( - "2", - { - "sell_token": self.token_b, - "buy_token": self.token_a, - "sell_amount": "100", - "buy_amount": "10", - "allow_partial_fill": False, - "is_sell_order": True, - "fee": { - "amount": "115995469750", - "token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1", - }, - "cost": { - "amount": "321627750000000", - "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", - }, - "is_liquidity_order": False, - "mandatory": False, - "has_atomic_execution": False, - }, - ) - - return order1, order2 - - def test_overlaps(self): - - order1, order2 = self.overlapping_orders() - self.assertTrue(order1.overlaps(order2)) - - # Change the buy amount to make it so the orders don't overlap. - old_buy_amount = order1.buy_amount - - order1.buy_amount *= 10 - self.assertFalse(order1.overlaps(order2)) - - # Set the buy amount back and change the token. - order1.buy_amount = old_buy_amount - self.assertTrue(order1.overlaps(order2)) - token_c = "0x1111111111111111111111111111111111111111" - order1.buy_token = Token(token_c) - self.assertFalse(order1.overlaps(order2)) - - def test_match_type(self): - order1, order2 = self.overlapping_orders() - - self.assertEqual(order1.match_type(order2), OrderMatchType.BOTH_FILLED) - - # Make order1 half the size - order1.buy_amount /= 2 - order1.sell_amount /= 2 - - self.assertEqual(order1.match_type(order2), OrderMatchType.LHS_FILLED) - # Reverse the orders to get RHS Filled - self.assertEqual(order2.match_type(order1), OrderMatchType.RHS_FILLED) - - order1.buy_token = Token(self.token_c) - - self.assertIsNone(order1.match_type(order2)) - - -if __name__ == "__main__": - unittest.main() +import unittest + +from src.models.order import Order, OrderMatchType +from src.models.token import Token + + +class MyTestCase(unittest.TestCase): + def setUp(self) -> None: + self.token_a = "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1" + self.token_b = "0x177127622c4a00f3d409b75571e12cb3c8973d3c" + self.token_c = "0x1111111111111111111111111111111111111111" + + def overlapping_orders(self): + order1 = Order.from_dict( + "1", + { + "sell_token": self.token_a, + "buy_token": self.token_b, + "sell_amount": "12", + "buy_amount": "100", + "allow_partial_fill": False, + "is_sell_order": True, + "fee": { + "amount": "115995469750", + "token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1", + }, + "cost": { + "amount": "321627750000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", + }, + "is_liquidity_order": False, + "mandatory": False, + "has_atomic_execution": False, + }, + ) + order2 = Order.from_dict( + "2", + { + "sell_token": self.token_b, + "buy_token": self.token_a, + "sell_amount": "100", + "buy_amount": "10", + "allow_partial_fill": False, + "is_sell_order": True, + "fee": { + "amount": "115995469750", + "token": "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1", + }, + "cost": { + "amount": "321627750000000", + "token": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", + }, + "is_liquidity_order": False, + "mandatory": False, + "has_atomic_execution": False, + }, + ) + + return order1, order2 + + def test_overlaps(self): + + order1, order2 = self.overlapping_orders() + self.assertTrue(order1.overlaps(order2)) + + # Change the buy amount to make it so the orders don't overlap. + old_buy_amount = order1.buy_amount + + order1.buy_amount *= 10 + self.assertFalse(order1.overlaps(order2)) + + # Set the buy amount back and change the token. + order1.buy_amount = old_buy_amount + self.assertTrue(order1.overlaps(order2)) + token_c = "0x1111111111111111111111111111111111111111" + order1.buy_token = Token(token_c) + self.assertFalse(order1.overlaps(order2)) + + def test_match_type(self): + order1, order2 = self.overlapping_orders() + + self.assertEqual(order1.match_type(order2), OrderMatchType.BOTH_FILLED) + + # Make order1 half the size + order1.buy_amount /= 2 + order1.sell_amount /= 2 + + self.assertEqual(order1.match_type(order2), OrderMatchType.LHS_FILLED) + # Reverse the orders to get RHS Filled + self.assertEqual(order2.match_type(order1), OrderMatchType.RHS_FILLED) + + order1.buy_token = Token(self.token_c) + + self.assertIsNone(order1.match_type(order2)) + + +if __name__ == "__main__": + unittest.main()