Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(wip): Document Python Package #81

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 38 additions & 35 deletions tycho_simulation_py/Readme.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
# Python bindings for Tycho Simulation

`tycho_simulation_py` - a Python module, implemented as a Rust crate, that allows using Rust EVM simulation module from
Python.
`tycho_simulation_py` - is a Python module that allows using Tycho's Simulation module, originally written in Rust.

## Install
This library is intended for anyone who wants to leverage Tycho's ecosystem and performance, while integrating with their Python applications.

We regularly push wheel to codeartifact. You should be able to install them from there. If there is no wheel for you
arch available, please consider building it (see below) and pushing it.

```
aws --region eu-central-1 codeartifact pip --tool twine --domain propeller --domain-owner 827659017777 --repository protosim
pip install tycho-simulation-py
```

## Summary
### How does it work?

`evm` module from `tycho-simulation` crate implements simulating on-chain transactions. This
crate - `tycho_simulation_py` -
Expand All @@ -23,7 +14,7 @@ wraps `evm` in order to allow using it in Python.
Rust Python
┌────────────────────────────────────────────────────────────────┐ ┌────────────────────────────┐
│ │ │ │
│ tycho_simulation::evm_simulation tycho_simulation_py │ │ tycho_simulation_py │
│ tycho_simulation::evm::simulation tycho_simulation_py │ │ tycho_simulation_py │
│ ┌────────────────────────┐ ┌──────────────────────┐ │ │ ┌──────────────────────┐ │
│ │ │ │ │ │ │ │ │ │
│ │ │ wrap │ │ │ │ │ │ │
Expand All @@ -44,10 +35,24 @@ wraps `evm` in order to allow using it in Python.
└───────┘
```

_Editable
chart [here](https://asciiflow.com/#/share/eJyrVspLzE1VslIqKMovyS%2FOzI0vqFTSUcpJrEwtAopWxyhVxChZWZpY6sQoVQJZRuamQFZJakUJkBOjpBBUWlyiQDkIqCzJyM%2BLicl7NKXn0ZSGIY4mgLxEM59MAAdTE6VBDjWCgElAaZh1sBRiZZValhsPZJTmJJZk5uehqEdKRnisw6cKah1Vg28CihVUMXgCqpeoaio8CHBGDaoUToVQpzWhs3GrJNrq8qLEAlpaHQxPX6556Zl5qQpIobSHiJCEqZm2C9MoHI7DVEdGuGDlUTFc8FhNvygJSi0uzSmhSpRAjSIYJTB1o1GC02o9PT0KogSkmxjHYaobXFEyhXrVxgwUe4gxeA0RRqJ6iwhTp20izlRy2wXomnC1DLCoA1tJxRCnLiImByDFNIk%2BIcF0YDCRHi2YEYBdDSWGJ5Vm5qRAuLjaL6C2U2ZecUliTg5F1hGT0HeBXBWekZqaA9Qwh6aBS6TrZsQo1SrVAgD%2BnnnV)_
## Installation

### Local Installation

1. Install Rust and Python.
2. Install tycho-client-py: [Installation guide](https://github.com/propeller-heads/tycho-indexer/tree/main/tycho-client-py)
3. Install tycho-simulation-py:
`pip install -e .`

## Building and installation
### Using PyPI

On every release, we automatically push the package to PyPI. You can install it using pip:

```shell
pip install tycho-simulation-py
```

## Building

### Build in `manylinux` docker image

Expand All @@ -61,9 +66,6 @@ sudo ./tycho_simulation_py/build_tycho_simulation_wheel.sh

A wheel file will be created in `tycho_simulation_py/target/wheels`.

In the script file, there's a commented out line for pushing the wheel to S3. Execute it if you want to publish the
wheel for defibot to use it. Be careful - this file will be immediately used by defibot CI!

### Build locally

The crate should be built using [maturin](https://www.maturin.rs/) tool.
Expand Down Expand Up @@ -108,22 +110,6 @@ wheel in a different target environment.
- Documentation on using this module to implement a `PoolState` in
defibot: https://github.com/propeller-heads/defibot/blob/master/defibot/swaps/protosim/Readme.md

### Publish to code artifacts

If you have a Mac Silicon or old Mac please build the wheels and publish them. This will help your colleagues as sooner
or later everyone will have to upgrade.

Usually you should have a tycho-simulation-build environment where maturin is already installed.

- Make sure the verison was bumped according to semver.
- If you don't have twine installed yet add it:
`pip install twine`
- Build the wheel in release mode:
`maturin build --release`
- Activate your credentials for aws code-artifact
`aws --region eu-central-1 codeartifact login --tool twine --domain propeller --domain-owner 827659017777 --repository protosim`
- Upload the wheel:
`twine upload --repository codeartifact target/wheels/tycho_simulation_py-[VERSION]*.whl`

### Troubleshooting

Expand All @@ -143,3 +129,20 @@ Alternatively, you can control log level per module e.g. like this: `RUST_LOG=ty
3. On macOS, Try building with MACOSX_DEPLOYMENT_TARGET environment variable set.
See [here](https://www.maturin.rs/environment-variables.html#other-environment-variables)
and [here](https://www.maturin.rs/migration.html?highlight=MACOSX_DEPLOYMENT_TARGET#macos-deployment-target-version-defaults-what-rustc-supports).

## Usage

First, add `tycho-simulation-py` as a dependency on your project:

```
# requirements.txt

tycho-simulation-py==0.32.0 (please update the version accordingly)
```



### What is this used for?

As part of Tycho Ecosystem, this library is intented for users that want to quickly integrate Tycho simulations in their system. If you want to know more about Tycho, please [read Tycho docs](https://docs.propellerheads.xyz/tycho)

3 changes: 0 additions & 3 deletions tycho_simulation_py/build_tycho_simulation_wheel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,3 @@ mkdir -p ./tycho_simulation_py/target/wheels/
docker build -t tycho_simulation_py_build -f tycho_simulation_py/tycho_simulation_py.Dockerfile .
docker run -v $(pwd):/prop-builder tycho_simulation_py_build
chmod -R 777 ./tycho_simulation_py/target/wheels/

# Do this if you want to publish the wheel. Note that CI uses this file!
# aws s3 cp ./tycho_simulation_py/target/wheels/tycho_simulation_py-0.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl s3://defibot-data/tycho_simulation_py-0.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
4 changes: 1 addition & 3 deletions tycho_simulation_py/protosim_evm_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ def test_simulation_db():
if eth_rpc_url is None:
raise Exception("ETH_RPC_URL environment variable not set")

db = SimulationDB(
rpc_url=eth_rpc_url, block=None
)
db = SimulationDB(rpc_url=eth_rpc_url, block=None)
engine = SimulationEngine.new_with_simulation_db(db=db, trace=False)

params = SimulationParameters(
Expand Down
23 changes: 12 additions & 11 deletions tycho_simulation_py/python/test/evm/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from tycho_simulation_py.evm import AccountInfo, StateUpdate, BlockHeader, SimulationEngine
from tycho_simulation_py.evm import (
AccountInfo,
StateUpdate,
BlockHeader,
SimulationEngine,
)
from tycho_simulation_py.evm.constants import MAX_BALANCE
from tycho_simulation_py.evm.utils import exec_rpc_method, get_code_for_address
from tycho_simulation_py.models import Address, EVMBlock


def read_account_storage_from_rpc(
address: Address, block_hash: str, connection_string: str = None
address: Address, block_hash: str, connection_string: str = None
) -> dict[str, str]:
"""Reads complete storage of a contract from a Geth instance.

Expand Down Expand Up @@ -51,10 +56,10 @@ def read_account_storage_from_rpc(


def init_contract_via_rpc(
block: EVMBlock,
contract_address: Address,
engine: SimulationEngine,
connection_string: str,
block: EVMBlock,
contract_address: Address,
engine: SimulationEngine,
connection_string: str,
):
"""Initializes a contract in the simulation engine using data fetched via RPC.

Expand Down Expand Up @@ -97,11 +102,7 @@ def init_contract_via_rpc(
)
engine.init_account(
address=contract_address,
account=AccountInfo(
balance=MAX_BALANCE,
nonce=0,
code=bytecode,
),
account=AccountInfo(balance=MAX_BALANCE, nonce=0, code=bytecode),
mocked=False,
permanent_storage=None,
)
Expand Down
4 changes: 1 addition & 3 deletions tycho_simulation_py/python/test/test_evm_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

from tycho_simulation_py.evm.storage import TychoDBSingleton
from tycho_simulation_py.evm.token import brute_force_slots
from tycho_simulation_py.evm.utils import (
create_engine,
)
from tycho_simulation_py.evm.utils import create_engine
from test.evm.utils import init_contract_via_rpc
from tycho_simulation_py.models import EthereumToken, EVMBlock

Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ def _decode_output(self, fname: str, encoded: list[int]) -> Any:
return eth_abi.decode(types, bytearray(encoded))

def call(
self,
fname: str,
*args: list[Union[int, str, bool, bytes]],
block_number,
timestamp: int = None,
overrides: TStateOverwrites = None,
caller: Address = EXTERNAL_ACCOUNT,
value: int = 0,
self,
fname: str,
*args: list[Union[int, str, bool, bytes]],
block_number,
timestamp: int = None,
overrides: TStateOverwrites = None,
caller: Address = EXTERNAL_ACCOUNT,
value: int = 0,
) -> TychoSimulationResponse:
call_data = self._encode_input(fname, *args)
params = SimulationParameters(
Expand Down Expand Up @@ -131,13 +131,13 @@ def __init__(self, address: Address, engine: SimulationEngine):
super().__init__(address, "ISwapAdapter", engine)

def price(
self,
pair_id: HexStr,
sell_token: EthereumToken,
buy_token: EthereumToken,
amounts: list[int],
block: EVMBlock,
overwrites: TStateOverwrites = None,
self,
pair_id: HexStr,
sell_token: EthereumToken,
buy_token: EthereumToken,
amounts: list[int],
block: EVMBlock,
overwrites: TStateOverwrites = None,
) -> list[Fraction]:
args = [HexBytes(pair_id), sell_token.address, buy_token.address, amounts]
res = self.call(
Expand All @@ -150,14 +150,14 @@ def price(
return list(map(lambda x: Fraction(*x), res.return_value[0]))

def swap(
self,
pair_id: HexStr,
sell_token: EthereumToken,
buy_token: EthereumToken,
is_buy: bool,
amount: Decimal,
block: EVMBlock,
overwrites: TStateOverwrites = None,
self,
pair_id: HexStr,
sell_token: EthereumToken,
buy_token: EthereumToken,
is_buy: bool,
amount: Decimal,
block: EVMBlock,
overwrites: TStateOverwrites = None,
) -> tuple[Trade, dict[str, StateUpdate]]:
args = [
HexBytes(pair_id),
Expand All @@ -177,12 +177,12 @@ def swap(
return Trade(amount, gas, Fraction(*price)), res.simulation_result.state_updates

def get_limits(
self,
pair_id: HexStr,
sell_token: EthereumToken,
buy_token: EthereumToken,
block: EVMBlock,
overwrites: TStateOverwrites = None,
self,
pair_id: HexStr,
sell_token: EthereumToken,
buy_token: EthereumToken,
block: EVMBlock,
overwrites: TStateOverwrites = None,
) -> tuple[int, int]:
args = [HexBytes(pair_id), sell_token.address, buy_token.address]
res = self.call(
Expand All @@ -195,7 +195,7 @@ def get_limits(
return res.return_value[0]

def get_capabilities(
self, pair_id: HexStr, sell_token: EthereumToken, buy_token: EthereumToken
self, pair_id: HexStr, sell_token: EthereumToken, buy_token: EthereumToken
) -> set[Capability]:
args = [HexBytes(pair_id), sell_token.address, buy_token.address]
res = self.call("getCapabilities", args, block_number=1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@

EXTERNAL_ACCOUNT: Final[str] = "0xf847a638E44186F3287ee9F8cAF73FF4d4B80784"
"""This is a dummy address used as a transaction sender"""
UINT256_MAX: Final[int] = 2**256 - 1
UINT256_MAX: Final[int] = 2 ** 256 - 1
MAX_BALANCE: Final[int] = UINT256_MAX // 2
"""0.5 of the maximal possible balance to avoid overflow errors"""
14 changes: 9 additions & 5 deletions tycho_simulation_py/python/tycho_simulation_py/evm/decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ def decode_pool_state(
adapter_contract_path=self.adapter_contract,
trace=self.trace,
manual_updates=manual_updates,
involved_contracts=set(to_checksum_address(b.hex()) for b in component.contract_ids),
involved_contracts=set(
to_checksum_address(b.hex()) for b in component.contract_ids
),
**optional_attributes,
)

Expand Down Expand Up @@ -204,8 +206,8 @@ def decode_balances(
):
balances = {}
for addr, balance in balances_msg.items():
checksum_addr = to_checksum_address(addr)
token = next(t for t in tokens if t.address == checksum_addr)
checksum_addr = addr.hex().lower()
token = next(t for t in tokens if t.address.lower() == checksum_addr)
balances[token.address] = token.from_onchain_amount(
int(balance) # balances are big endian encoded
)
Expand Down Expand Up @@ -237,8 +239,10 @@ def apply_deltas(
pool_id = self.component_pool_id.get(component_id, component_id)
pool = pools[pool_id]
for addr, token_balance in balance_update.items():
checksum_addr = to_checksum_address(addr)
token = next(t for t in pool.tokens if t.address == checksum_addr)
checksum_addr = addr.lower()
token = next(
t for t in pool.tokens if t.address.lower() == checksum_addr
)
balance = token.from_onchain_amount(
int.from_bytes(token_balance.balance, "big", signed=False)
) # balances are big endian encoded
Expand Down
20 changes: 10 additions & 10 deletions tycho_simulation_py/python/tycho_simulation_py/evm/pool_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ def __init__(
contract). If given, balances will be overwritten here instead of on the pool
contract during simulations."""

self.block_lasting_overwrites: defaultdict[Address, dict[int, int]] = (
block_lasting_overwrites or defaultdict(dict)
)
self.block_lasting_overwrites: defaultdict[
Address, dict[int, int]
] = block_lasting_overwrites or defaultdict(dict)
"""Storage overwrites that will be applied to all simulations. They will be cleared
when ``clear_all_cache`` is called, i.e. usually at each block. Hence the name."""

Expand All @@ -100,7 +100,7 @@ def __init__(
"""A set of all contract addresses involved in the simulation of this pool."""

self.token_storage_slots: dict[Address, tuple[ERC20Slots, ContractCompiler]] = (
token_storage_slots or {}
token_storage_slots or {}
)
"""Allows the specification of custom storage slots for token allowances and
balances. This is particularly useful for token contracts involved in protocol
Expand Down Expand Up @@ -181,12 +181,12 @@ def _set_marginal_prices(self):
t1,
[sell_amount],
block=self.block,
overwrites=self._get_overwrites(t0,t1),
overwrites=self._get_overwrites(t0, t1),
)[0]
if Capability.ScaledPrices in self.capabilities:
self.marginal_prices[(t0, t1)] = frac_to_decimal(frac)
else:
scaled = frac * Fraction(10**t0.decimals, 10**t1.decimals)
scaled = frac * Fraction(10 ** t0.decimals, 10 ** t1.decimals)
self.marginal_prices[(t0, t1)] = frac_to_decimal(scaled)

def _ensure_capability(self, capability: Capability):
Expand Down Expand Up @@ -302,11 +302,11 @@ def _get_token_overwrites(
max_amount = sell_token.to_onchain_amount(
self.get_sell_amount_limit(sell_token, buy_token)
)
slots, compiler = self.token_storage_slots.get(sell_token.address, (ERC20Slots(0, 1), ContractCompiler.Solidity))
slots, compiler = self.token_storage_slots.get(
sell_token.address, (ERC20Slots(0, 1), ContractCompiler.Solidity)
)
overwrites = ERC20OverwriteFactory(
sell_token,
token_slots=slots,
compiler=compiler
sell_token, token_slots=slots, compiler=compiler
)
overwrites.set_balance(max_amount, EXTERNAL_ACCOUNT)
overwrites.set_allowance(
Expand Down
Loading
Loading