-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 0da4f15
Showing
58 changed files
with
10,335 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Include any files or directories that you don't want to be copied to your | ||
# container here (e.g., local build artifacts, temporary files, etc.). | ||
# | ||
# For more help, visit the .dockerignore file reference guide at | ||
# https://docs.docker.com/go/build-context-dockerignore/ | ||
|
||
**/.DS_Store | ||
**/__pycache__ | ||
**/.venv | ||
**/.classpath | ||
**/.dockerignore | ||
**/.env | ||
**/.git | ||
**/.gitignore | ||
**/.project | ||
**/.settings | ||
**/.toolstarget | ||
**/.vs | ||
**/.vscode | ||
**/*.*proj.user | ||
**/*.dbmdl | ||
**/*.jfm | ||
**/bin | ||
**/charts | ||
**/docker-compose* | ||
**/compose.y*ml | ||
**/Dockerfile* | ||
**/node_modules | ||
**/npm-debug.log | ||
**/obj | ||
**/secrets.dev.yaml | ||
**/values.dev.yaml | ||
LICENSE | ||
README.md | ||
**/lib |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# EOA that holds the gas that should be used for liquidation | ||
LIQUIDATOR_EOA=0x0001 | ||
LIQUIDATOR_PRIVATE_KEY=0x0002 | ||
|
||
# RPC URL | ||
RPC_URL=https://example.rpc.url.com | ||
|
||
# Relevant API keys | ||
API_KEY_1INCH=api_key | ||
|
||
### OPTIONAL ### | ||
|
||
# Slack webhook URL for sending notifications | ||
SLACK_WEBHOOK_URL="https://hooks.slack.com/services/SLACK_KEY" | ||
|
||
# URL for the liquidation UI, if including in the slack notification | ||
RISK_DASHBOARD_URL="http://127.0.0.1:8080" | ||
|
||
# Accounts that are only used for running the test/LiquidationSetupWithVaultCreated.sol test file for setting up vaults | ||
DEPOSITOR_ADDRESS=0x0003 | ||
DEPOSITOR_PRIVATE_KEY=0x0004 | ||
BORROWER_ADDRESS=0x0005 | ||
BORROWER_PRIVATE_KEY=0x0006 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
name: test | ||
|
||
on: workflow_dispatch | ||
|
||
env: | ||
FOUNDRY_PROFILE: ci | ||
|
||
jobs: | ||
check: | ||
strategy: | ||
fail-fast: true | ||
|
||
name: Foundry project | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
submodules: recursive | ||
|
||
- name: Install Foundry | ||
uses: foundry-rs/foundry-toolchain@v1 | ||
with: | ||
version: nightly | ||
|
||
- name: Run Forge build | ||
run: | | ||
forge --version | ||
forge build --sizes | ||
id: build | ||
|
||
- name: Run Forge tests | ||
run: | | ||
forge test -vvv | ||
id: test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Compiler files | ||
cache/ | ||
out/ | ||
|
||
# Ignores development broadcast logs | ||
!/broadcast | ||
/broadcast/*/31337/ | ||
/broadcast/**/dry-run/ | ||
broadcast/ | ||
|
||
# Docs | ||
docs/ | ||
|
||
# Dotenv file | ||
.env | ||
.env_local | ||
.copy_env | ||
.vscode | ||
lcov.info | ||
|
||
# Virtual env | ||
python-venv/ | ||
venv/ | ||
|
||
# Logs and save state | ||
logs/ | ||
state/ | ||
|
||
# Python cache files | ||
__pycache__/ | ||
*.pyc | ||
*.pyo | ||
*.pyd | ||
|
||
/redstone_script/node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[submodule "lib/forge-std"] | ||
path = lib/forge-std | ||
url = https://github.com/foundry-rs/forge-std |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# syntax=docker/dockerfile:1 | ||
|
||
ARG PYTHON_VERSION=3.12.5 | ||
FROM 310118226683.dkr.ecr.eu-west-1.amazonaws.com/python:${PYTHON_VERSION} as base | ||
|
||
# Copy the project files | ||
COPY . . | ||
|
||
# Initialize git repository | ||
RUN git init && \ | ||
git add -A && \ | ||
git commit -m "Initial commit" | ||
|
||
# Manually clone submodules | ||
RUN mkdir -p lib/forge-std && \ | ||
git clone https://github.com/foundry-rs/forge-std.git lib/forge-std | ||
|
||
# Run Forge commands | ||
RUN forge install --no-commit | ||
RUN forge update | ||
RUN forge build | ||
|
||
# Create a non-privileged user | ||
ARG UID=10001 | ||
RUN adduser \ | ||
--disabled-password \ | ||
--gecos "" \ | ||
--home "/nonexistent" \ | ||
--shell "/sbin/nologin" \ | ||
--no-create-home \ | ||
--uid "${UID}" \ | ||
appuser | ||
|
||
RUN mkdir -p /app/logs /app/state | ||
|
||
# Install Python dependencies | ||
RUN --mount=type=cache,target=/root/.cache/pip \ | ||
--mount=type=bind,source=requirements.txt,target=requirements.txt \ | ||
python -m pip install -r requirements.txt | ||
|
||
# Install NPM dependencies | ||
WORKDIR /redstone_script | ||
COPY redstone_script/package.json redstone_script/package-lock.json* ./ | ||
RUN npm ci | ||
WORKDIR / | ||
|
||
# Set correct permissions | ||
RUN chown -R appuser:appuser /app && \ | ||
chmod -R 755 /app && \ | ||
chmod 777 /app/logs /app/state | ||
|
||
USER appuser | ||
|
||
EXPOSE 8080 | ||
|
||
# CMD ["python", "python/liquidation_bot.py"] | ||
# Run the application | ||
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "application:application"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 Euler | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
# Euler Liquidation Bot | ||
|
||
Bot to perform liquidations on the Euler platform. [Liquidation docs.](https://docs.euler.finance/euler-vault-kit-white-paper/#liquidation) | ||
|
||
## How it works | ||
|
||
1. **Account Monitoring**: | ||
- The primary way of finding new accounts is [scanning](app/liquidation/liquidation_bot.py#L950) for `AccountStatusCheck` events emitted by the EVC contract to check for new & modified positions. | ||
- This event is emitted every time a borrow is created or modified, and contains both the account address and vault address. | ||
- Health scores are calculated using the `accountLiquidity` [function](app/liqiudation/liquidation_bot.py#L101) implemented by the vaults themselves. | ||
- Accounts are added to a priority queue based on their health score with a time of next update, with low health accounts being checked most frequently. | ||
- EVC logs are batched on bot startup to catch up to the current block, then scanned for new events at a regular interval. | ||
|
||
2. **Liquidation Opportunity Detection**: | ||
- When an account's health score falls below 1, the bot simulates a liquidation transaction across each collateral asset. | ||
- The bot gets a quote for how much collateral is needed to swap into the debt repay amount, and simulates a liquidation transaction on the Liquidator.sol contract. | ||
- Gas cost is estimated for the liquidation transaction, then checks if the leftover collateral after repaying debt is greater than the gas cost when converted to ETH terms. | ||
- If this is the case, the liquidation is profitable and the bot will attempt to execute the transaction. | ||
|
||
3. **Liquidation Execution - [Liquidator.sol](contracts/Liquidator.sol)**: | ||
- If profitable, the bot constructs a transaction to call the `liquidateSingleCollateral` [function](contracts/Liquidator.sol#L70) on the Liquidator contract. | ||
- The Liquidator contract then executes a batch of actions via the EVC containing the following steps: | ||
1. Enables borrow vault as a controller. | ||
2. Enable collateral vault as a collateral. | ||
3. Call liquidate() on the violator's position in the borrow vault, which seizes both the collateral and debt position. | ||
4. Withdraws specified amount of collateral from the collateral vault to the swapper contract. | ||
5. Calls the swapper contract with a multicall batch to swap the seized collateral, repay the debt, and sweep any remaining dust from the swapper contract. | ||
6. Transfers remaining collateral to the profit receiver. | ||
7. Submit batch to EVC. | ||
|
||
|
||
- There is a secondary flow still being developed to use the liquidator contract as an EVC operator, which would allow the bot to operate on behalf of another account and pull the debt position alongside the collateral to the account directly. This flow will be particularly useful for liquidating positions without swapping the collateral to the debt asset, for things such as permissioned RWA liquidations. | ||
|
||
4. **Swap Quotation**: | ||
- The bot currently uses 1inch API to get quotes for swapping seized collateral to repay debt. | ||
- 1inch unfortunatley does not support exact output swaps, so we perform a binary search to find the optimal swap amount resulting in swapping slightly more collateral than needed to repay debt. | ||
- The bot will eventually have a fallback to uniswap swapping if 1inch is unable to provide a quote, which would also allow for more precise exact output swaps. | ||
|
||
5. **Profit Handling**: | ||
- Any profit (excess collateral after repayment) is sent to a designated receiver address. | ||
- Profit is sent in the form of ETokens of the collateral asset, and is not withdrawn from the vault or converted to any other asset. | ||
|
||
6. **Slack Notifications**: | ||
- The bot can send notifications to a slack channel when unhealthy accounts are detected, when liquidations are performed, and when errors occur. | ||
- The bot also sends a report of all low health accounts at regularly scheduled intervals, which can be configured in the config.yaml file. | ||
- In order to receive notifications, a slack channel must be set up and a webhook URL must be provided in the .env file. | ||
|
||
## How the bot works | ||
|
||
|
||
### Installation | ||
|
||
The bot can be run either via building a docker container or manually. In both instances, it runs via a flask app to expose some endpoints for account health dashboards & metrics. | ||
|
||
Before running either, setup a .env file by copying the .env.example file and updating with the relevant contract addresses, an EOA private key, & API keys. Then, check config.yaml to make sure parameters, contracts, and ABI paths have been set up correctly. | ||
|
||
#### Running locally | ||
To run locally, we need to install some dependencies and build the contracts. This will setup a python virtual environment for installing dependencies. The below command assumes we have foundry installed, which can installed from the [Foundry Book](https://book.getfoundry.sh/). | ||
|
||
Setup: | ||
```bash | ||
foundryup | ||
python3 -m venv venv | ||
source venv/bin/activate | ||
pip install -r requirements.txt | ||
cd redstone_script && npm install && cd .. | ||
forge install && forge build | ||
cd lib/evk-periphery && forge build && cd ../.. | ||
mkdir logs state | ||
``` | ||
|
||
**Run**: | ||
```bash | ||
python flask run --port 8080 | ||
``` | ||
Change the Port number to whatever port is desired for exposing the relevant endpoints from the [routes.py](app/liquidation/routes.py) file. | ||
|
||
#### Docker | ||
After creating the .env file, the below command will create a container, install all dependencies, and start the liquidation bot: | ||
`docker compose build --progress=plain && docker compose up` | ||
|
||
This may require some configuration changes on the Docker image to a basic Python enabled container. | ||
|
||
### Configuration | ||
|
||
- The bot uses variables from both the [config.yaml](config.yaml) file and the [.env](.env.example) file to configure settings and private keys. | ||
- The startup code is contained at the end of the [python/liquidation_bot.py](python/liquidation_bot.py#L1311) file, which also has two variable to set for the bot - `notify` & `execute_liquidation`, which determine if the bot will post to slack and if it will execute the liquidations found. | ||
|
||
Make sure to build the contracts in both src and lib to have the correct ABIs loaded from the evk-periphery installation | ||
|
||
Configuration through `.env` file: | ||
|
||
REQUIRED: | ||
- `LIQUIDATOR_EOA, LIQUIDATOR_PRIVATE_KEY` - public/private key of EOA that will be used to liquidate | ||
|
||
- `RPC_URL` - RPC provider endpoint (Infura, Rivet, Alchemy etc.) | ||
|
||
- `API_KEY_1INCH` - API key for 1inch to help with executing swaps | ||
|
||
OPTIONAL: | ||
- `SLACK_WEBHOOK_URL` - Optional URL to post notifications to slack | ||
- `RISK_DASHBOARD_URL` - Optional, can include a link in slack notifications to manually liquidate a position | ||
- `DEPOSITOR_ADDRESS, DEPOSITOR_PRIVATE_KEY, BORROWER_ADDRESS, BORROWER_PRIVATE_KEY` - Optional, for running | ||
|
||
|
||
Configuration in `config.yaml` file: | ||
|
||
- `LOGS_PATH, SAVE_STATE_PATH` - Path directing to save location for Logs & Save State | ||
- `SAVE_INTERVAL` - How often state should be saved | ||
|
||
- `HS_LOWER_BOUND, HS_UPPER_BOUND` - Bounds below and above which an account should be updated at the min/max update interval | ||
- `MIN_UPDATE_INTERVAL, MAX_UPDATE_INTERVAL` - Min/Max time between account updates | ||
|
||
- `LOW_HEALTH_REPORT_INTERVAL` - Interval between low health reports | ||
- `SLACK_REPORT_HEALTH_SCORE` - Threshold to include an account on the low health report | ||
|
||
- `BATCH_SIZE, BATCH_INTERVAL` - Configuration batching logs on bot startup | ||
|
||
- `SCAN_INTERVAL` - How often to scan for new events during regular operation | ||
|
||
- `NUM_RETRIES, RETRY_DELAY` - Config for how often to retry failing API requests | ||
|
||
- `SWAP_DELTA, MAX_SEARCH_ITERATIONS` - Used to define how much overswapping is accetable when searching 1Inch swaps | ||
|
||
- `EVC_DEPLOYMENT_BLOCK` - Block that the contracs were deployed | ||
|
||
- `WETH, EVC, SWAPPER, SWAP_VERIFIER, LIQUIDATOR_CONTRACT, ORACLE_LENS` - Relevant deployed contract addresses. The liquidator contract has been deployed on Mainnet, but feel free to redeploy. | ||
|
||
- `PROFIT_RECEIVER` - Targeted receiver of any profits from liquidations | ||
|
||
- `EVAULT_ABI_PATH, EVC_ABI_PATH, LIQUIDATOR_ABI_PATH, ORACLE_LENS_ABI_PATH` - Paths to compiled contracts | ||
|
||
- `CHAIN_ID` - Chain ID to run the bot on, Mainnet: 1, Arbitrum: 42161 | ||
|
||
|
||
### Deploying | ||
|
||
If you want to deploy your own version of the liquidator contract, you can run the command below: | ||
|
||
```bash | ||
forge script contracts/DeployLiquidator.sol --rpc-url $RPC_URL --broadcast --ffi -vvv --slow | ||
``` | ||
|
||
To run the basic test script & broadcast to the configured RPC, modify the [LiquidationSetupWithVaultCreated.sol test](test/LiquidationSetupWithVaultCreated.sol) with the correct contract addresses and uncomment the various steps of setting up a position, then run the below commmand: | ||
|
||
```bash | ||
forge script test/LiquidationSetupWithVaultCreated.sol --rpc-url $RPC_URL --broadcast --ffi -vvv --slow --evm-version shanghai | ||
``` | ||
|
||
This test is intended to create a position on an existing vault. To test a liquitation, you can either wait for price fluctuations to happen or manually change the LTV of the vault using the create.euler.finance UI if it is a governed vault that you control. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
""" | ||
Creates and returns main flask app | ||
""" | ||
from flask import Flask, jsonify | ||
from flask_cors import CORS | ||
import threading | ||
from .liquidation.routes import liquidation, start_monitor | ||
|
||
def create_app(): | ||
app = Flask(__name__) | ||
CORS(app) | ||
|
||
@app.route("/health", methods=["GET"]) | ||
def health_check(): | ||
return jsonify({"status": "healthy"}), 200 | ||
|
||
monitor_thread = threading.Thread(target=start_monitor) | ||
monitor_thread.start() | ||
|
||
# Register the rewards blueprint after starting the monitor | ||
app.register_blueprint(liquidation, url_prefix="/liquidation") | ||
|
||
return app |
Empty file.
Oops, something went wrong.