Skip to content

Commit

Permalink
Merge pull request #2 from multiversx/mxpy-porting
Browse files Browse the repository at this point in the history
Mxpy porting
  • Loading branch information
ovidiuolteanu authored Apr 10, 2023
2 parents 06c7946 + fc3dc2f commit cc93eef
Show file tree
Hide file tree
Showing 101 changed files with 15,231 additions and 1 deletion.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/wallets/*
/venv*
/.idea
/.vscode
/logs/*
/tools/notebooks/env.py
.venv
__pycache__
/deploy/*/tokens.json
/deploy/*/deployed_*.json
59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,59 @@
# mx-sdk-py-exchange
Python utility toolkit for xExchange interactions
Python utility toolkit for xExchange interactions.

Features include:
- Configurable DEX SC setup deployment
- save/load setup
- additive deployment to existing setup
- Interaction with DEX SCs via exposed endpoints
- Data reading from DEX SCs via exposed views
- DEX SC operation trackers
- Attributes decoding tool
- Setup updater tool

### Disclaimer
This is currently a work in progress and should be treated as such.
Modules are under migration process to mxpy, need a lot of cleanup & refactors and may not be functional/used anylonger.

### Virtual environment

Create a virtual environment and install the dependencies:

```
python3 -m venv ./.venv
source ./.venv/bin/activate
pip install -r ./requirements.txt --upgrade
```

### Operation
Start by initializing the environment:
```
export PYTHONPATH=.
```

#### Config
To configure the exchange setup and operation, config.py has to be edited accordingly.
In the config.py file you should configure the following:
- used network
- PEM accounts for exchange operations
- DEX deploy configuration
- DEX contract binaries paths

#### Deploy
The dex_deploy script is used to deploy a configured exchange setup.
An exchange setup consists of several elements usually gathered in a dedicated directory:
- deploy_structure.json - defines the exchange setup structure containing definitions for each contract type
- deployed_*.json - contains already deployed tokens/contracts for this specific exchange setup

To deploy a specific exchange setup, configure the desired exchange setup directory in config.py then run:
```
python3 deploy/dex_deploy.py --deploy-contracts=clean --deploy-tokens=clean
```
The flags `--deploy-contracts` and `--deploy-tokens` can be set to `clean` or `config` to either deploy a clean setup
(ignoring the already deployed contracts) or add on top of an existing one (considering the already deployed contracts).

#### Scenarios
To run a defined scenario, just run the desired scenario script located in /scenarios directory, such as:
```
python3 scenarios/stress_create_positions.py
```
88 changes: 88 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from pathlib import Path

HOME = Path().home()
DEFAULT_WORKSPACE = Path(__file__).parent

# ------------ For normal operation, modify below ------------ #
# Used net
DEFAULT_PROXY = "https://devnet-gateway.multiversx.com" # Proxy to be used for ALL operations
DEFAULT_API = "https://devnet-api.multiversx.com" # API to be used for ALL operations
HISTORY_PROXY = "" # Proxy to be used for history operations; not used for the moment
# TODO: try to override the default issue token price with testnet definition to tidy code up
DEFAULT_ISSUE_TOKEN_PRICE = 50000000000000000 # 0.05 EGLD - change only if different setup on nets

# Operation wallets
DEFAULT_ACCOUNTS = DEFAULT_WORKSPACE.absolute() / "wallets" / "C10.pem" # Accounts to be used for user operations
DEFAULT_OWNER = DEFAULT_WORKSPACE.absolute() / "wallets" / "C1.pem" # DEX owner address
DEFAULT_ADMIN = DEFAULT_WORKSPACE.absolute() / "wallets" / "C1_1.pem" # DEX admin address

# Used DEX deploy configuration
DEFAULT_CONFIG_SAVE_PATH = DEFAULT_WORKSPACE.absolute() / "deploy" / "configs-devnet" # Deploy configuration folder
DEPLOY_STRUCTURE_JSON = DEFAULT_CONFIG_SAVE_PATH / "deploy_structure.json" # Deploy structure - change only if needed

# DEX contract bytecode paths
LOCKED_ASSET_FACTORY_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "factory.wasm"
SIMPLE_LOCK_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "locked-asset" / "simple-lock" / "output" / "simple-lock.wasm"
ROUTER_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "dex" / "router" / "output" / "router.wasm"
PROXY_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "locked-asset" / "proxy_dex" / "output" / "proxy_dex.wasm"
PROXY_V2_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "proxy_dex.wasm"
PAIR_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "dex" / "pair" / "output" / "pair.wasm"
FARM_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "dex" / "farm" / "output" / "farm.wasm"
FARM_LOCKED_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "dex" / "farm_with_lock" / "output" / "farm_with_lock.wasm"
FARM_COMMUNITY_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "dex" / "farm_with_community_rewards" / "output" / "farm_with_community_rewards.wasm"
PRICE_DISCOVERY_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "dex" / "price-discovery" / "output" / "price-discovery.wasm"
STAKING_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "farm-staking" / "farm-staking" / "output" / "farm-staking.wasm"
STAKING_V2_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "farm-staking.wasm"
STAKING_PROXY_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "farm-staking" / "farm-staking-proxy" / "output" / "farm-staking-proxy.wasm"
STAKING_PROXY_V2_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "farm-staking-proxy.wasm"
SIMPLE_LOCK_ENERGY_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "energy-factory.wasm"
UNSTAKER_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "token-unstake.wasm"
FEES_COLLECTOR_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "fees-collector.wasm"
ROUTER_V2_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "router.wasm"
PAIR_V2_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "pair.wasm"
FARM_DEPLOYER_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "proxy-deployer.wasm"
FARM_V2_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "farm-with-locked-rewards.wasm"


# ------------ Generic configuration below; Modify only in case of framework changes ------------ #
TOKENS_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"
ZERO_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"

DEFAULT_GAS_BASE_LIMIT_ISSUE = 60000000
DEFAULT_TOKEN_PREFIX = "TDEX" # limit yourself to max 6 chars to allow automatic ticker build
DEFAULT_TOKEN_SUPPLY_EXP = 27 # supply to be minted in exponents of 10
DEFAULT_TOKEN_DECIMALS = 18 # decimals on minted tokens in exponents of 10
DEFAULT_MINT_VALUE = 1 # EGLD # TODO: don't go sub-unitary cause headaches occur. just don't be cheap for now...

CROSS_SHARD_DELAY = 60
INTRA_SHARD_DELAY = 10

LOCKED_ASSETS = "locked_assets"
PROXIES = "proxies"
PROXIES_V2 = "proxies_v2"
SIMPLE_LOCKS = "simple_locks"
SIMPLE_LOCKS_ENERGY = "simple_locks_energy"
UNSTAKERS = "unstakers"
ROUTER = "router"
ROUTER_V2 = "router_v2"
PAIRS = "pairs"
PAIRS_V2 = "pairs_v2"
PROXY_DEPLOYERS = "proxy_deployers"
FARMS_V2 = "farms_boosted"
FARMS_COMMUNITY = "farms_community"
FARMS_UNLOCKED = "farms_unlocked"
FARMS_LOCKED = "farms_locked"
PRICE_DISCOVERIES = "price_discoveries"
STAKINGS = "stakings"
STAKINGS_V2 = "stakings_v2"
METASTAKINGS = "metastakings"
METASTAKINGS_V2 = "metastakings_v2"
FEES_COLLECTORS = "fees_collectors"


def get_default_tokens_file():
return DEFAULT_CONFIG_SAVE_PATH / "tokens.json"


def get_default_log_file():
return DEFAULT_WORKSPACE / "logs" / "trace.log"
176 changes: 176 additions & 0 deletions context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import random
from datetime import datetime

import config
from contracts.farm_contract import FarmContract
from contracts.metastaking_contract import MetaStakingContract
from contracts.pair_contract import PairContract
from deploy.dex_structure import DeployStructure
from utils.results_logger import ResultsLogger
from utils.utils_tx import NetworkProviders
from trackers.farm_economics_tracking import FarmEconomics, FarmAccountEconomics
from trackers.pair_economics_tracking import PairEconomics
from trackers.staking_economics_tracking import StakingEconomics
from trackers.metastaking_economics_tracking import MetastakingEconomics
from trackers.concrete_observer import Observable
from utils.utils_chain import Account, BunchOfAccounts


class Context:
def __init__(self):

self.deploy_structure = DeployStructure()
self.deployer_account = Account(pem_file=config.DEFAULT_OWNER)
self.accounts = BunchOfAccounts.load_accounts_from_files([config.DEFAULT_ACCOUNTS])
self.nonces_file = config.DEFAULT_WORKSPACE / "_nonces.json"
self.debug_level = 1

self.network_provider = NetworkProviders(config.DEFAULT_API, config.DEFAULT_PROXY)

# logger
self.start_time = datetime.now()
self.results_logger = ResultsLogger(f"{self.start_time.day}_{self.start_time.hour}_{self.start_time.minute}_event_results.json")

self.add_liquidity_max_amount = 0.1
self.remove_liquidity_max_amount = 0.5
self.numEvents = 100 # sys.maxsize
self.pair_slippage = 0.05
self.swap_min_tokens_to_spend = 0
self.swap_max_tokens_to_spend = 0.8

self.enter_farm_max_amount = 0.2
self.exit_farm_max_amount = 0.5

self.enter_metastake_max_amount = 0.1
self.exit_metastake_max_amount = 0.3

# BEGIN DEPLOY
self.deployer_account.sync_nonce(self.network_provider.proxy)

# TOKENS HANDLING
self.deploy_structure.deploy_tokens(self.deployer_account, self.network_provider, False)

# configure contracts and deploy them
# DEPLOY CONTRACTS
self.deploy_structure.deploy_structure(self.deployer_account, self.network_provider, False)

# CONTRACTS START
self.deploy_structure.start_deployed_contracts(self.deployer_account, self.network_provider, False)

# deploy closing
self.deploy_structure.print_deployed_contracts()

self.observable = Observable()
# self.init_observers() # call should be parameterized so that observers can be disabled programmatically

def init_observers(self):

farm_unlocked_contracts = self.deploy_structure.contracts[config.FARMS_UNLOCKED].deployed_contracts
for contract in farm_unlocked_contracts:
contract_dict = contract.get_config_dict()
observer = FarmEconomics(contract_dict['address'], contract_dict['version'], self.network_provider)
self.observable.subscribe(observer)

farm_locked_contracts = self.deploy_structure.contracts[config.FARMS_LOCKED].deployed_contracts
for contract in farm_locked_contracts:
contract_dict = contract.get_config_dict()
observer = FarmEconomics(contract_dict['address'], contract_dict['version'], self.network_provider)
self.observable.subscribe(observer)

for acc in self.accounts.get_all():
account_observer = FarmAccountEconomics(acc.address, self.network_provider)
self.observable.subscribe(account_observer)

pair_contracts = self.deploy_structure.contracts[config.PAIRS].deployed_contracts
for contract in pair_contracts:
contract_dict = contract.get_config_dict()
observer = PairEconomics(contract_dict['address'], contract.firstToken, contract.secondToken, self.network_provider)
self.observable.subscribe(observer)

staking_contracts = self.deploy_structure.contracts[config.STAKINGS].deployed_contracts
for contract in staking_contracts:
contract_dict = contract.get_config_dict()
observer = StakingEconomics(contract_dict['address'], self.network_provider)
self.observable.subscribe(observer)

metastaking_contracts = self.deploy_structure.contracts[config.METASTAKINGS].deployed_contracts
for contract in metastaking_contracts:
contract_dict = contract.get_config_dict()
farm_contract = self.get_farm_contract_by_address(contract_dict['farm_address'])
pair_contract = self.get_pair_contract_by_address(contract_dict['lp_address'])
observer = MetastakingEconomics(contract_dict['address'], contract_dict['stake_address'],
farm_contract, pair_contract, self.network_provider)
self.observable.subscribe(observer)

def get_slippaged_below_value(self, value: int):
return value - int(value * self.pair_slippage)

def get_slippaged_above_value(self, value: int):
return value + int(value * self.pair_slippage)

def set_swap_spend_limits(self, swap_min_spend, swap_max_spend):
self.swap_min_tokens_to_spend = swap_min_spend
self.swap_max_tokens_to_spend = swap_max_spend

def get_router_v2_contract(self, index: int):
return self.deploy_structure.get_deployed_contract_by_index(config.ROUTER_V2, index)

def get_simple_lock_contract(self, index: int):
return self.deploy_structure.get_deployed_contract_by_index(config.SIMPLE_LOCKS, index)

def get_pair_contract(self, index: int):
return self.deploy_structure.get_deployed_contract_by_index(config.PAIRS, index)

def get_pair_v2_contract(self, index: int):
return self.deploy_structure.get_deployed_contract_by_index(config.PAIRS_V2, index)

def get_fee_collector_contract(self, index: int):
return self.deploy_structure.get_deployed_contract_by_index(config.FEES_COLLECTORS, index)

def get_unlocked_farm_contract(self, index: int):
return self.deploy_structure.get_deployed_contract_by_index(config.FARMS_UNLOCKED, index)

def get_locked_farm_contract(self, index: int):
return self.deploy_structure.get_deployed_contract_by_index(config.FARMS_LOCKED, index)

def get_staking_contract(self, index: int):
return self.deploy_structure.get_deployed_contract_by_index(config.STAKINGS, index)

def get_metastaking_contract(self, index: int):
return self.deploy_structure.get_deployed_contract_by_index(config.METASTAKINGS, index)

def get_price_discovery_contract(self, index: int):
return self.deploy_structure.get_deployed_contract_by_index(config.PRICE_DISCOVERIES, index)

def get_contracts(self, contract_label: str):
return self.deploy_structure.get_deployed_contracts(contract_label)

def get_farm_contract_by_address(self, address: str) -> FarmContract:
contract = self.deploy_structure.get_deployed_contract_by_address(config.FARMS_LOCKED, address)
if contract is None:
contract = self.deploy_structure.get_deployed_contract_by_address(config.FARMS_UNLOCKED, address)

return contract

def get_random_farm_contract(self):
return random.choice([random.choice(self.deploy_structure.get_deployed_contracts(config.FARMS_LOCKED)),
random.choice(self.deploy_structure.get_deployed_contracts(config.FARMS_UNLOCKED))])

def get_pair_contract_by_address(self, address: str) -> PairContract:
return self.deploy_structure.get_deployed_contract_by_address(config.PAIRS, address)

def get_random_pair_contract(self):
return random.choice(self.deploy_structure.get_deployed_contracts(config.PAIRS))

def get_random_user_account(self):
account_list = self.accounts.get_all()
return random.choice(account_list)

def get_random_price_discovery_contract(self):
return random.choice(self.deploy_structure.get_deployed_contracts(config.PRICE_DISCOVERIES))

def get_random_metastaking_contract(self) -> MetaStakingContract:
return random.choice(self.deploy_structure.get_deployed_contracts(config.METASTAKINGS))

def get_contract_index(self, contract_label: str, contract):
return self.deploy_structure.get_deployed_contracts(contract_label).index(contract)
Loading

0 comments on commit cc93eef

Please sign in to comment.