feat: Add propeller swap encoders
- Add setup for package - Add docs - Add balancer implementation and test - Add CI: - Add setup action - Add test and format CI - Add CD: Publish python package to AWS
This commit is contained in:
32
.github/actions/setup_env/action.yaml
vendored
Normal file
32
.github/actions/setup_env/action.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Setup/Cache Env
|
||||||
|
description: 'Sets up and caches a python env. Will only install dependencies if no cache was hit.'
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Set up Python 3.9
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.9"
|
||||||
|
|
||||||
|
- name: Cache Env
|
||||||
|
uses: actions/cache@v3
|
||||||
|
id: env-cache
|
||||||
|
with:
|
||||||
|
path: ${{ env.pythonLocation }}
|
||||||
|
key: ${{ env.pythonLocation }}-${{ hashFiles('./requirements/requirements_dev.txt') }}-${{ hashFiles('./requirements/requirements_data.txt') }}-${{ hashFiles('./requirements/requirements_api.txt') }}-${{ hashFiles('./requirements/requirements.txt') }}-${{ hashFiles('./requirements/requirements_internal.txt') }}
|
||||||
|
|
||||||
|
- name: Configure AWS Credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4.0.1
|
||||||
|
with:
|
||||||
|
role-to-assume: arn:aws:iam::827659017777:role/github-actions
|
||||||
|
audience: sts.amazonaws.com
|
||||||
|
aws-region: eu-central-1
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
if: ${{ steps.env-cache.outputs.cache-hit != 'true' }}
|
||||||
|
run: |
|
||||||
|
aws codeartifact login --tool pip --domain propeller --domain-owner 827659017777 --repository protosim
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r propeller-swap-encoders/requirements.txt
|
||||||
|
shell: bash
|
||||||
29
.github/workflows/publish-swap-encoders-package.yaml
vendored
Normal file
29
.github/workflows/publish-swap-encoders-package.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: Publish Propeller Swap Encoders Python Packages to AWS CodeArtifact
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- prereleased
|
||||||
|
- released
|
||||||
|
workflow_dispatch: { }
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish_propeller_solver_core:
|
||||||
|
uses: propeller-heads/ci-cd-templates/.github/workflows/release-python-package.yaml@main
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
with:
|
||||||
|
package_root: "propeller-solver-core"
|
||||||
|
|
||||||
|
publish_propeller_swap_encoders:
|
||||||
|
uses: propeller-heads/ci-cd-templates/.github/workflows/release-python-package.yaml@main
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
with:
|
||||||
|
package_root: "propeller-swap-encoders"
|
||||||
32
.github/workflows/python-tests.yaml
vendored
Normal file
32
.github/workflows/python-tests.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Test code using pytest
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
runs_on:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ubuntu-latest
|
||||||
|
timeout_minutes:
|
||||||
|
required: false
|
||||||
|
type: number
|
||||||
|
default: 15
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-python:
|
||||||
|
runs-on: "${{ inputs.runs_on }}"
|
||||||
|
timeout-minutes: "${{ inputs.timeout_minutes }}"
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Env
|
||||||
|
uses: ./.github/actions/setup_env
|
||||||
|
|
||||||
|
- name: Test with pytest
|
||||||
|
id: tests
|
||||||
|
run: |
|
||||||
|
export PYTHONPATH=$PYTHONPATH:$GITHUB_WORKSPACE/propeller-swap-encoders
|
||||||
|
pytest --disable-warnings ./propeller-swap-encoders
|
||||||
32
.github/workflows/swap-encoders.yaml
vendored
Normal file
32
.github/workflows/swap-encoders.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Swap encoders CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
PYTEST_ADDOPTS: "--color=yes"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
uses: propeller-heads/propeller-protocol-lib/.github/workflows/python-tests.yaml@dc/ENG-3545-make-encoders-lib
|
||||||
|
|
||||||
|
formatting:
|
||||||
|
name: Formatting
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: Check out Repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Env
|
||||||
|
uses: ./.github/actions/setup_env
|
||||||
|
|
||||||
|
- name: Black Formatting
|
||||||
|
run: |
|
||||||
|
black ./propeller-swap-encoders --check --skip-magic-trailing-comma
|
||||||
@@ -28,5 +28,9 @@ For indexing purposes, it is required that you provide a [substreams](https://su
|
|||||||
|
|
||||||
### Execution
|
### Execution
|
||||||
|
|
||||||
For execution purposes, the implementation of the `SwapExecutor` interface is required. Without this component, trades cannot be executed on-chain, making it a critical part of the integration.
|
For execution purposes, the implementation of the `SwapExecutor` and `SwapStructEncoder` interfaces is required. Without these components, trades cannot be executed on-chain, making them critical parts of the integration.
|
||||||
The `SwapExecutor` is responsible for performing swaps by interacting with the underlying liquidity pools, handling token approvals, managing input/output amounts, and ensuring gas-efficient and secure execution. Each protocol must implement its own `SwapExecutor`, tailored to its specific logic and requirements.
|
|
||||||
|
**SwapExecutor**: The SwapExecutor is responsible for performing swaps by interacting with the underlying liquidity pools, handling token approvals, managing input/output amounts, and ensuring gas-efficient and secure execution. Each protocol must implement its own `SwapExecutor`, tailored to its specific logic and requirements.
|
||||||
|
|
||||||
|
**SwapStructEncoder**: The `SwapStructEncoder` encodes the necessary data structures required for the `SwapExecutor` to perform swaps. It ensures that the swap details, including input/output tokens, pool addresses, and other protocol-specific parameters, are correctly formatted and encoded before being passed to the `SwapExecutor`. Each protocol must implement its own `SwapStructEncoder` and ensure compatibility with its `SwapExecutor`.
|
||||||
|
|
||||||
|
|||||||
57
docs/execution/swap-encoder.md
Normal file
57
docs/execution/swap-encoder.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Implementing a SwapExecutor for a Protocol
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `SwapStructEncoder` interface is designed to encode the necessary data for a swap, which will be used by the `SwapExecutor` to interact with a liquidity pool. The encoder is responsible for structuring the swap details, including the input/output tokens, pool addresses, and any additional protocol-specific parameters.
|
||||||
|
|
||||||
|
Each protocol must implement its own `SwapStructEncoder` and ensure that the swap data is correctly encoded for the `SwapExecutor`.
|
||||||
|
|
||||||
|
### Dev environment
|
||||||
|
|
||||||
|
- Run `aws codeartifact login --tool pip --repository protosim --domain propeller` so you can access the propeller packages.
|
||||||
|
- Create the dev environment `conda env create -f propeller-swap-encoders/environment_dev.yaml`
|
||||||
|
- Activate it with `conda activate propeller-swap-encoders`
|
||||||
|
- Install dependencies with `pip install -r propeller-swap-encoders/requirements.txt`
|
||||||
|
- Should you get a pyyaml installation error execute the following command: `pip install "cython<3.0.0" && pip install --no-build-isolation pyyaml==5.4.1`
|
||||||
|
|
||||||
|
You can import the abstract class `SwapStructEncoder` from `propeller-solver-core` in your python code like:
|
||||||
|
```python
|
||||||
|
from core.encoding.interface import SwapStructEncoder
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Methods
|
||||||
|
|
||||||
|
This is the `SwapStructEncoder` interface:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SwapStructEncoder(ABC):
|
||||||
|
"""Encodes a PercentageSwap of a certain protocol to be used in our SwapRouterV2
|
||||||
|
Should be subclassed for each protocol that we support.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def encode_swap_struct(
|
||||||
|
self, swap: dict[str, Any], receiver: Address, **kwargs
|
||||||
|
) -> bytes:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
- **encode_swap_struct**
|
||||||
|
- **Purpose**: To encode the swap details into a bytes object that the SwapExecutor can use to execute the swap.
|
||||||
|
- **Parameters**:
|
||||||
|
- `swap`: A dictionary containing the swap details, such as input/output token addresses, amounts, and pool information.
|
||||||
|
- `receiver`: The address that will receive the output tokens from the swap.
|
||||||
|
- `**kwargs`: Any additional protocol-specific parameters that need to be included in the encoding.
|
||||||
|
- **Returns**: A bytes object containing the encoded swap data.
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. **Define Protocol-Specific Encoding Logic**: Implement the `encode_swap_struct` function to encode the swap details specific to the protocol. This may include encoding token addresses, pool addresses, and other necessary parameters into a bytes format.
|
||||||
|
2. **Compatibility with SwapExecutor**: Ensure that the encoded data is compatible with the `SwapExecutor` implementation for the protocol. The `SwapExecutor` will rely on this data to perform the swap accurately.
|
||||||
|
3. **Testing**: Thoroughly test the encoding process with various swap scenarios to ensure that the encoded data is correct and that the `SwapExecutor` can process it without errors.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Example Implementation
|
||||||
|
|
||||||
|
See the example implementation of a `SwapExecutor` for Balancer [here](../../propeller-swap-encoders/propeller_swap_encoders/balancer.py) and test [here](../../propeller-swap-encoders/propeller_swap_encoders/tests/test_balancer.py).
|
||||||
@@ -7,13 +7,17 @@ It allows for flexible interaction by accepting either the amount of the input t
|
|||||||
as parameters, returning the corresponding swapped amount.
|
as parameters, returning the corresponding swapped amount.
|
||||||
This interface is essential for creating a `SwapExecutor` specific to a protocol.
|
This interface is essential for creating a `SwapExecutor` specific to a protocol.
|
||||||
|
|
||||||
|
The `SwapExecutor` works in conjunction with the `SwapStructEncoder`, which encodes the necessary data required for the swap.
|
||||||
|
This encoded data is passed to the `SwapExecutor`, enabling it to perform the swap according to the protocol's specific logic.
|
||||||
|
|
||||||
## Key Methods
|
## Key Methods
|
||||||
|
|
||||||
- **swap(uint256 givenAmount, bytes calldata data)**
|
- **swap(uint256 givenAmount, bytes calldata data)**
|
||||||
- **Purpose**: To perform a token swap, either specifying the input amount to get the output amount or vice versa.
|
- **Purpose**: To perform a token swap, either specifying the input amount to get the output amount or vice versa.
|
||||||
- **Parameters**:
|
- **Parameters**:
|
||||||
- `givenAmount`: The amount of the token (input or output) for the swap.
|
- `givenAmount`: The amount of the token (input or output) for the swap.
|
||||||
- `data`: Encoded information necessary for the swap (e.g., pool address, token addresses - depends on the protocol).
|
- `data`: Encoded information necessary for the swap (e.g., pool address, token addresses - depends on the protocol),
|
||||||
|
provided by the `SwapStructEncoder`.
|
||||||
- **Returns**: The amount of the token swapped.
|
- **Returns**: The amount of the token swapped.
|
||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|||||||
5
propeller-swap-encoders/environment_dev.yaml
Normal file
5
propeller-swap-encoders/environment_dev.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
name: propeller-swap-encoders
|
||||||
|
channels:
|
||||||
|
- conda-forge
|
||||||
|
dependencies:
|
||||||
|
- python=3.9
|
||||||
33
propeller-swap-encoders/propeller_swap_encoders/balancer.py
Normal file
33
propeller-swap-encoders/propeller_swap_encoders/balancer.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from core.encoding.interface import SwapStructEncoder
|
||||||
|
from core.type_aliases import Address
|
||||||
|
from eth_abi.packed import encode_abi_packed
|
||||||
|
from hexbytes import HexBytes
|
||||||
|
|
||||||
|
|
||||||
|
class BalancerSwapStructEncoder(SwapStructEncoder):
|
||||||
|
def encode_swap_struct(
|
||||||
|
self, swap: dict[str, Any], receiver: Address, exact_out: bool, **kwargs
|
||||||
|
) -> bytes:
|
||||||
|
"""
|
||||||
|
Parameters:
|
||||||
|
----------
|
||||||
|
swap
|
||||||
|
The swap to encode
|
||||||
|
receiver
|
||||||
|
The receiver of the buy token
|
||||||
|
exact_out
|
||||||
|
Whether the amount encoded is the exact amount out
|
||||||
|
"""
|
||||||
|
return encode_abi_packed(
|
||||||
|
["address", "address", "bytes32", "address", "bool", "bool"],
|
||||||
|
[
|
||||||
|
swap["sell_token"].address,
|
||||||
|
swap["buy_token"].address,
|
||||||
|
HexBytes(swap["pool_id"]),
|
||||||
|
receiver,
|
||||||
|
exact_out,
|
||||||
|
swap["token_approval_needed"],
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
from core.models.evm.ethereum_token import EthereumToken
|
||||||
|
from propeller_swap_encoders.balancer import BalancerSwapStructEncoder
|
||||||
|
|
||||||
|
|
||||||
|
def test_encode_balancer():
|
||||||
|
WETH = EthereumToken(
|
||||||
|
symbol="WETH",
|
||||||
|
address="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
||||||
|
decimals=18,
|
||||||
|
gas=0,
|
||||||
|
)
|
||||||
|
DAI = EthereumToken(
|
||||||
|
symbol="DAI", address="0x6b175474e89094c44da98b954eedeac495271d0f", decimals=18
|
||||||
|
)
|
||||||
|
bob = "0x000000000000000000000000000000000000007B"
|
||||||
|
swap = {
|
||||||
|
"pool_id": "0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063",
|
||||||
|
"sell_token": DAI,
|
||||||
|
"buy_token": WETH,
|
||||||
|
"split": 0,
|
||||||
|
"sell_amount": 0,
|
||||||
|
"buy_amount": 100,
|
||||||
|
"token_approval_needed": False,
|
||||||
|
"pool_tokens": (),
|
||||||
|
"pool_type": "BalancerStablePoolState",
|
||||||
|
"curve_v2_pool_type": None,
|
||||||
|
"is_curve_tricrypto": None,
|
||||||
|
"quote": None,
|
||||||
|
"pool_fee": None,
|
||||||
|
}
|
||||||
|
balancer_encoder = BalancerSwapStructEncoder()
|
||||||
|
encoded = balancer_encoder.encode_swap_struct(swap, receiver=bob, exact_out=False)
|
||||||
|
assert (
|
||||||
|
encoded.hex()
|
||||||
|
==
|
||||||
|
# sell token
|
||||||
|
"6b175474e89094c44da98b954eedeac495271d0f"
|
||||||
|
# buy token
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
|
||||||
|
# pool address
|
||||||
|
"06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063"
|
||||||
|
# receiver
|
||||||
|
"000000000000000000000000000000000000007b"
|
||||||
|
# exact_out
|
||||||
|
"00"
|
||||||
|
# token_approval_needed
|
||||||
|
"00"
|
||||||
|
)
|
||||||
35
propeller-swap-encoders/pyproject.toml
Normal file
35
propeller-swap-encoders/pyproject.toml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=60", "setuptools-scm>=8.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "propeller-swap-encoders"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = [
|
||||||
|
{ name = "Propeller Heads" }
|
||||||
|
]
|
||||||
|
keywords = ["propeller-swap-encoders"]
|
||||||
|
classifiers = [
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Natural Language :: English",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
]
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.7"
|
||||||
|
dependencies = [
|
||||||
|
"propeller-solver-core==0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["propeller_swap_encoders"]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
testing = [
|
||||||
|
"pytest",
|
||||||
|
"pytest-runner"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
homepage = "https://github.com/propeller-heads/propeller-protocol-lib"
|
||||||
|
|
||||||
4
propeller-swap-encoders/requirements.txt
Normal file
4
propeller-swap-encoders/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
propeller-solver-core==0.1.0
|
||||||
|
pyyaml==5.4.1
|
||||||
|
pytest==6.2.4
|
||||||
|
black==24.4.2
|
||||||
Reference in New Issue
Block a user