From 63ecf9a3e83a287bcd2098c6728c7d8e70139bdd Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Mon, 27 Feb 2023 14:28:05 +0200 Subject: [PATCH 01/26] initial ported version of base layer --- .gitignore | 4 + README.md | 26 +- config.py | 79 + context.py | 178 ++ contracts/contract_identities.py | 153 ++ contracts/dex_proxy_contract.py | 820 ++++++++ contracts/esdt_contract.py | 67 + contracts/farm_contract.py | 644 +++++++ contracts/fees_collector_contract.py | 265 +++ contracts/locked_asset_contract.py | 227 +++ contracts/metastaking_contract.py | 321 ++++ contracts/pair_contract.py | 608 ++++++ contracts/price_discovery_contract.py | 302 +++ contracts/proxy_deployer_contract.py | 148 ++ contracts/router_contract.py | 291 +++ contracts/simple_lock_contract.py | 169 ++ contracts/simple_lock_energy_contract.py | 559 ++++++ contracts/staking_contract.py | 444 +++++ contracts/unstaker_contract.py | 110 ++ deploy/configs-devnet/deploy_structure.json | 202 ++ .../deployed_farms_boosted.json | 16 + .../deployed_fees_collectors.json | 5 + .../deployed_locked_assets.json | 7 + .../deployed_metastakings_v2.json | 13 + deploy/configs-devnet/deployed_pairs_v2.json | 16 + deploy/configs-devnet/deployed_proxies.json | 12 + .../configs-devnet/deployed_proxies_v2.json | 13 + .../deployed_proxy_deployers.json | 6 + deploy/configs-devnet/deployed_router_v2.json | 6 + .../deployed_simple_locks_energy.json | 9 + .../configs-devnet/deployed_stakings_v2.json | 11 + deploy/configs-devnet/deployed_tokens.json | 5 + deploy/configs-devnet/deployed_unstakers.json | 5 + deploy/configs-mainnet/deploy_structure.json | 420 +++++ .../deployed_farms_boosted.json | 51 + .../deployed_fees_collectors.json | 5 + .../deployed_locked_assets.json | 7 + .../deployed_metastakings_v2.json | 46 + deploy/configs-mainnet/deployed_pairs_v2.json | 51 + deploy/configs-mainnet/deployed_proxies.json | 12 + .../configs-mainnet/deployed_proxies_v2.json | 13 + .../configs-mainnet/deployed_router_v2.json | 6 + .../deployed_simple_locks_energy.json | 9 + .../configs-mainnet/deployed_stakings_v2.json | 65 + deploy/configs-mainnet/deployed_tokens.json | 12 + .../configs-mainnet/deployed_unstakers.json | 5 + deploy/dex_deploy.py | 55 + deploy/dex_structure.py | 1672 +++++++++++++++++ deploy/issue_tokens.py | 96 + deploy/populate_deploy_lists.py | 32 + deploy/sync_tokens.py | 53 + deploy/tokens_tracks.py | 100 + events/event_generators.py | 929 +++++++++ events/farm_events.py | 46 + events/metastake_events.py | 27 + events/price_discovery_events.py | 30 + requirements.txt | 6 + scenarios/pair_admin_owner_check.py | 202 ++ scenarios/pair_v2_check_fee.py | 112 ++ scenarios/scenario_dex_v2_all_in.py | 199 ++ scenarios/scenario_fees_collector.py | 199 ++ scenarios/scenario_simple_lock_energy.py | 392 ++++ scenarios/scenario_swaps.py | 158 ++ scenarios/stress_claim_metastaking.py | 65 + scenarios/stress_create_positions.py | 129 ++ scenarios/stress_many_add_remove_liquidity.py | 68 + scenarios/stress_many_swaps.py | 65 + scenarios/stress_scenario.py | 154 ++ scenarios/stress_scenario_price_discovery.py | 67 + tools/account_state.py | 127 ++ tools/config_contracts_upgrader.py | 80 + tools/contracts_upgrader.py | 1101 +++++++++++ tools/graphql_interactor.py | 38 + tools/manual_issue_token.py | 87 + tools/manual_tools.py | 174 ++ tools/safeprice_monitor.py | 93 + trackers/abstract_observer.py | 21 + trackers/concrete_observer.py | 36 + trackers/farm_economics_tracking.py | 519 +++++ trackers/metastaking_economics_tracking.py | 175 ++ trackers/pair_economics_tracking.py | 215 +++ .../price_discovery_economics_tracking.py | 342 ++++ trackers/simple_lock_energy_tracking.py | 67 + trackers/staking_economics_tracking.py | 227 +++ utils/contract_data_fetchers.py | 258 +++ utils/contract_retrievers.py | 121 ++ utils/decoding_structures.py | 6 + utils/results_logger.py | 122 ++ utils/utils_chain.py | 547 ++++++ utils/utils_generic.py | 280 +++ utils/utils_tx.py | 321 ++++ 91 files changed, 16255 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 config.py create mode 100644 context.py create mode 100644 contracts/contract_identities.py create mode 100644 contracts/dex_proxy_contract.py create mode 100644 contracts/esdt_contract.py create mode 100644 contracts/farm_contract.py create mode 100644 contracts/fees_collector_contract.py create mode 100644 contracts/locked_asset_contract.py create mode 100644 contracts/metastaking_contract.py create mode 100644 contracts/pair_contract.py create mode 100644 contracts/price_discovery_contract.py create mode 100644 contracts/proxy_deployer_contract.py create mode 100644 contracts/router_contract.py create mode 100644 contracts/simple_lock_contract.py create mode 100644 contracts/simple_lock_energy_contract.py create mode 100644 contracts/staking_contract.py create mode 100644 contracts/unstaker_contract.py create mode 100644 deploy/configs-devnet/deploy_structure.json create mode 100644 deploy/configs-devnet/deployed_farms_boosted.json create mode 100644 deploy/configs-devnet/deployed_fees_collectors.json create mode 100644 deploy/configs-devnet/deployed_locked_assets.json create mode 100644 deploy/configs-devnet/deployed_metastakings_v2.json create mode 100644 deploy/configs-devnet/deployed_pairs_v2.json create mode 100644 deploy/configs-devnet/deployed_proxies.json create mode 100644 deploy/configs-devnet/deployed_proxies_v2.json create mode 100644 deploy/configs-devnet/deployed_proxy_deployers.json create mode 100644 deploy/configs-devnet/deployed_router_v2.json create mode 100644 deploy/configs-devnet/deployed_simple_locks_energy.json create mode 100644 deploy/configs-devnet/deployed_stakings_v2.json create mode 100644 deploy/configs-devnet/deployed_tokens.json create mode 100644 deploy/configs-devnet/deployed_unstakers.json create mode 100644 deploy/configs-mainnet/deploy_structure.json create mode 100644 deploy/configs-mainnet/deployed_farms_boosted.json create mode 100644 deploy/configs-mainnet/deployed_fees_collectors.json create mode 100644 deploy/configs-mainnet/deployed_locked_assets.json create mode 100644 deploy/configs-mainnet/deployed_metastakings_v2.json create mode 100644 deploy/configs-mainnet/deployed_pairs_v2.json create mode 100644 deploy/configs-mainnet/deployed_proxies.json create mode 100644 deploy/configs-mainnet/deployed_proxies_v2.json create mode 100644 deploy/configs-mainnet/deployed_router_v2.json create mode 100644 deploy/configs-mainnet/deployed_simple_locks_energy.json create mode 100644 deploy/configs-mainnet/deployed_stakings_v2.json create mode 100644 deploy/configs-mainnet/deployed_tokens.json create mode 100644 deploy/configs-mainnet/deployed_unstakers.json create mode 100644 deploy/dex_deploy.py create mode 100644 deploy/dex_structure.py create mode 100644 deploy/issue_tokens.py create mode 100644 deploy/populate_deploy_lists.py create mode 100644 deploy/sync_tokens.py create mode 100644 deploy/tokens_tracks.py create mode 100644 events/event_generators.py create mode 100644 events/farm_events.py create mode 100644 events/metastake_events.py create mode 100644 events/price_discovery_events.py create mode 100644 requirements.txt create mode 100644 scenarios/pair_admin_owner_check.py create mode 100644 scenarios/pair_v2_check_fee.py create mode 100644 scenarios/scenario_dex_v2_all_in.py create mode 100644 scenarios/scenario_fees_collector.py create mode 100644 scenarios/scenario_simple_lock_energy.py create mode 100644 scenarios/scenario_swaps.py create mode 100644 scenarios/stress_claim_metastaking.py create mode 100644 scenarios/stress_create_positions.py create mode 100644 scenarios/stress_many_add_remove_liquidity.py create mode 100644 scenarios/stress_many_swaps.py create mode 100644 scenarios/stress_scenario.py create mode 100644 scenarios/stress_scenario_price_discovery.py create mode 100644 tools/account_state.py create mode 100644 tools/config_contracts_upgrader.py create mode 100644 tools/contracts_upgrader.py create mode 100644 tools/graphql_interactor.py create mode 100644 tools/manual_issue_token.py create mode 100644 tools/manual_tools.py create mode 100644 tools/safeprice_monitor.py create mode 100644 trackers/abstract_observer.py create mode 100644 trackers/concrete_observer.py create mode 100644 trackers/farm_economics_tracking.py create mode 100644 trackers/metastaking_economics_tracking.py create mode 100644 trackers/pair_economics_tracking.py create mode 100644 trackers/price_discovery_economics_tracking.py create mode 100644 trackers/simple_lock_energy_tracking.py create mode 100644 trackers/staking_economics_tracking.py create mode 100644 utils/contract_data_fetchers.py create mode 100644 utils/contract_retrievers.py create mode 100644 utils/decoding_structures.py create mode 100644 utils/results_logger.py create mode 100644 utils/utils_chain.py create mode 100644 utils/utils_generic.py create mode 100644 utils/utils_tx.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aea8242 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/wallets +/venv* +/.idea +/.vscode \ No newline at end of file diff --git a/README.md b/README.md index 03442f8..d568cbb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,26 @@ # 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 +``` \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..35485d9 --- /dev/null +++ b/config.py @@ -0,0 +1,79 @@ +from pathlib import Path + +HOME = Path().home() + +TOKENS_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" +ZERO_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu" +DEFAULT_WORKSPACE = Path(__file__).parent +DEFAULT_ACCOUNTS = DEFAULT_WORKSPACE.absolute() / "wallets" / "C10.pem" +DEFAULT_OWNER = DEFAULT_WORKSPACE.absolute() / "wallets" / "devnet_wallet.pem" +DEFAULT_ADMIN = DEFAULT_WORKSPACE.absolute() / "wallets" / "C1_1.pem" +DEFAULT_PROXY = "https://devnet-gateway.elrond.com" +DEFAULT_API = "https://devnet-api.elrond.com" +HISTORY_PROXY = "" + +DEFAULT_ISSUE_TOKEN_PRICE = 50000000000000000 # TODO: try to override this with testnet define to tidy code up +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... + +# DEX setup +LOCKED_ASSET_FACTORY_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "locked-asset" / "factory" / "output" / "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" + +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" + +DEFAULT_CONFIG_SAVE_PATH = DEFAULT_WORKSPACE.absolute() / "deploy" / "configs-devnet-0712" +DEPLOY_STRUCTURE_JSON = DEFAULT_CONFIG_SAVE_PATH / "deploy_structure.json" + +CROSS_SHARD_DELAY = 60 +INTRA_SHARD_DELAY = 10 + + +def get_default_contracts_file(): + return DEFAULT_WORKSPACE / "contracts.json" + + +def get_default_tokens_file(): + return DEFAULT_WORKSPACE / "tokens.json" diff --git a/context.py b/context.py new file mode 100644 index 0000000..f04852c --- /dev/null +++ b/context.py @@ -0,0 +1,178 @@ +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 = self.init_observers() + + def init_observers(self): + observable = Observable() + + 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) + 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) + observable.subscribe(observer) + + for acc in self.accounts.get_all(): + account_observer = FarmAccountEconomics(acc.address, self.network_provider) + 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) + 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) + 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) + observable.subscribe(observer) + + return observable + + 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) diff --git a/contracts/contract_identities.py b/contracts/contract_identities.py new file mode 100644 index 0000000..b0879a1 --- /dev/null +++ b/contracts/contract_identities.py @@ -0,0 +1,153 @@ +from abc import abstractmethod, ABCMeta, ABC +from dataclasses import dataclass +from enum import Enum + +from utils.utils_chain import Account +from multiversx_sdk_network_providers import ProxyNetworkProvider + + +class DEXContractIdentityInterface(ABC): + + address: str = NotImplemented + + +class DEXContractInterface(ABC): + + address: str = NotImplemented + + @abstractmethod + def get_config_dict(self) -> dict: + pass + + @classmethod + @abstractmethod + def load_config_dict(cls, config_dict: dict): + pass + + @abstractmethod + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): + pass + + @abstractmethod + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = None): + pass + + @abstractmethod + def print_contract_info(self): + pass + + +class PriceDiscoveryContractIdentity(DEXContractIdentityInterface): + launched_token_id: str + accepted_token: str + redeem_token: str + first_redeem_token_nonce: int + second_redeem_token_nonce: int + address: str + locking_sc_address: str + min_launched_token_price: int + start_block: int + no_limit_phase_duration_blocks: int + linear_penalty_phase_duration_blocks: int + fixed_penalty_phase_duration_blocks: int + unlock_epoch: int + min_penalty_percentage: int + max_penalty_percentage: int + fixed_penalty_percentage: int + + +class ProxyContractVersion(Enum): + V1 = 1 + V2 = 2 + + +class FarmContractVersion(Enum): + V12 = 1 + V14Unlocked = 2 + V14Locked = 3 + V2Boosted = 4 + + +class RouterContractVersion(Enum): + V1 = 1 + V2 = 2 + + +class PairContractVersion(Enum): + V1 = 1 + V2 = 2 + + +class StakingContractVersion(Enum): + V1 = 1 + V2 = 2 + + +class MetaStakingContractVersion(Enum): + V1 = 1 + V2 = 2 + + + +class FarmContractIdentity(DEXContractIdentityInterface): + farmingToken: str + farmToken: str + farmedToken: str + address: str + version: FarmContractVersion + last_token_nonce: int + + +class StakingContractIdentity(DEXContractIdentityInterface): + address: str + farming_token: str + farm_token: str + farmed_token: str + max_apr: int + rewards_per_block: int + unbond_epochs: int + + +class MetaStakingContractIdentity(DEXContractIdentityInterface): + address: str + metastake_token: str + staking_token: str + lp_address: str + farm_address: str + stake_address: str + lp_token: str + farm_token: str + stake_token: str + + +class RouterContractIdentity(DEXContractIdentityInterface): + address: str + + +class PairContractIdentity(DEXContractIdentityInterface): + address: str + first_token: str + second_token: str + lp_token: str + + +class SimpleLockContractIdentity(DEXContractIdentityInterface): + address: str + locked_token: str + lp_proxy_token: str + + +class ProxyContractIdentity(DEXContractIdentityInterface): + address: str + token: str + locked_token: str + proxy_lp_token: str + proxy_farm_token: str + + +class LockedAssetContractIdentity(DEXContractIdentityInterface): + address: str + unlocked_asset: str + locked_asset: str + + diff --git a/contracts/dex_proxy_contract.py b/contracts/dex_proxy_contract.py new file mode 100644 index 0000000..c7d57a7 --- /dev/null +++ b/contracts/dex_proxy_contract.py @@ -0,0 +1,820 @@ +import random +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import DEXContractInterface, ProxyContractVersion +from contracts.farm_contract import FarmContract +from contracts.pair_contract import PairContract +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders +from utils.utils_chain import print_transaction_hash, print_warning, print_test_step_fail, \ + print_test_step_pass, print_test_substep +from erdpy.accounts import Account, Address +from erdpy.contracts import SmartContract, CodeMetadata +from erdpy.proxy import ElrondProxy +from erdpy.transactions import Transaction + + +class DexProxyAddLiquidityEvent: + def __init__(self, pairContract: PairContract, + tokenA: str, nonceA: int, amountA: int, amountAmin: int, + tokenB: str, nonceB: int, amountB: int, amountBmin: int): + self.pairContract = pairContract + self.tokenA = tokenA + self.nonceA = nonceA + self.amountA = amountA + self.amountAmin = amountAmin + self.tokenB = tokenB + self.nonceB = nonceB + self.amountB = amountB + self.amountBmin = amountBmin + + +class DexProxyRemoveLiquidityEvent: + def __init__(self, pairContract: PairContract, amount: int, nonce: int, amountA: int, amountB: int): + self.pairContract = pairContract + self.amount = amount + self.nonce = nonce + self.amountA = amountA + self.amountB = amountB + + +class DexProxyEnterFarmEvent: + def __init__(self, farmContract: FarmContract, + farming_token: str, farming_nonce: int, farming_amount, + farm_token: str, farm_nonce: int, farm_amount): + self.farmContract = farmContract + self.farming_tk = farming_token + self.farming_tk_nonce = farming_nonce + self.farming_tk_amount = farming_amount + self.farm_tk = farm_token + self.farm_tk_nonce = farm_nonce + self.farm_tk_amount = farm_amount + + +class DexProxyExitFarmEvent: + def __init__(self, farmContract: FarmContract, token: str, nonce: int, amount): + self.farmContract = farmContract + self.token = token + self.nonce = nonce + self.amount = amount + + +class DexProxyClaimRewardsEvent: + def __init__(self, farmContract: FarmContract, token: str, nonce: int, amount): + self.farmContract = farmContract + self.token = token + self.nonce = nonce + self.amount = amount + + +class DexProxyCompoundRewardsEvent: + def __init__(self, farmContract: FarmContract, token: str, nonce: int, amount): + self.farmContract = farmContract + self.token = token + self.nonce = nonce + self.amount = amount + + +class DexFarmProxyContract: + def __init__(self, farming_token: str, farm_token: str, address: str): + self.address = address + self.farming_token = farming_token + self.farm_token = farm_token + + def enter_farm_proxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyEnterFarmEvent, lock: int = -1, + initial: bool = False): + print("enterFarmProxy") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + enterFarmFn = "" + if lock == 1: + enterFarmFn = "enterFarmAndLockRewardsProxy" + elif lock == 0: + enterFarmFn = "enterFarmProxy" + else: + if random.randrange(0, 1) == 1: + enterFarmFn = "enterFarmAndLockRewardsProxy" + else: + enterFarmFn = "enterFarmProxy" + + gas_limit = 50000000 + + sc_args = [ + "0x" + Address(self.address).hex(), # proxy address + "0x01" if initial else "0x02", # number of tokens sent + "0x" + event.farming_tk.encode("ascii").hex(), # farming token details + "0x" + "0" + f"{event.farming_tk_nonce:x}" if len( + f"{event.farming_tk_nonce:x}") % 2 else "0x" + f"{event.farming_tk_nonce:x}", + "0x" + "0" + f"{event.farming_tk_amount:x}" if len( + f"{event.farming_tk_amount:x}") % 2 else "0x" + f"{event.farming_tk_amount:x}", + ] + if not initial: + sc_args.extend([ + "0x" + event.farm_tk.encode("ascii").hex(), # farm token details + "0x" + "0" + f"{event.farm_tk_nonce:x}" if len( + f"{event.farm_tk_nonce:x}") % 2 else "0x" + f"{event.farm_tk_nonce:x}", + "0x" + "0" + f"{event.farm_tk_amount:x}" if len( + f"{event.farm_tk_amount:x}") % 2 else "0x" + f"{event.farm_tk_amount:x}", + ]) + sc_args.extend([ + "0x" + enterFarmFn.encode("ascii").hex(), # enterFarm endpoint name + "0x" + Address(event.farmContract.address).hex(), # farm address + ]) + tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = user.address.bech32() # MultiESDTNFTTransfer is issued via self transfers + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + except Exception as ex: + print(ex) + + def exit_farm_proxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyExitFarmEvent): + print("exitFarmProxy") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + + sc_args = [ + "0x" + event.token.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "exitFarmProxy".encode("ascii").hex(), + "0x" + Address(event.farmContract.address).hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + except Exception as ex: + print(ex) + + def claim_rewards_proxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyClaimRewardsEvent): + print("claimRewardsProxy") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + + sc_args = [ + "0x" + event.token.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "claimRewardsProxy".encode("ascii").hex(), + "0x" + Address(event.farmContract.address).hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + except Exception as ex: + print(ex) + + +class DexPairProxyContract: + def __init__(self, p_token_a: str, p_token_b: str, p_lp_token: str, address: str): + self.address = address + self.proxy_token_a = p_token_a + self.proxy_token_b = p_token_b + self.proxy_lp_token = p_lp_token + + def add_liquidity_proxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyAddLiquidityEvent): + print("addLiquidityProxy") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + add_liquidity_fn = "addLiquidityProxy" + + sc_args = [ + "0x" + Address(self.address).hex(), # proxy address + "0x02", # number of tokens sent + "0x" + event.tokenA.encode("ascii").hex(), # farming token details + "0x" + "0" + f"{event.nonceA:x}" if len(f"{event.nonceA:x}") % 2 else "0x" + f"{event.nonceA:x}", + "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", + "0x" + event.tokenB.encode("ascii").hex(), # farm token details + "0x" + "0" + f"{event.nonceB:x}" if len(f"{event.nonceB:x}") % 2 else "0x" + f"{event.nonceB:x}", + "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", + "0x" + add_liquidity_fn.encode("ascii").hex(), # enterFarm endpoint name + "0x" + Address(event.pairContract.address).hex(), # farm address + "0x" + "0" + f"{event.amountAmin:x}" if len(f"{event.amountAmin:x}") % 2 else "0x" + f"{event.amountAmin:x}", + "0x" + "0" + f"{event.amountBmin:x}" if len(f"{event.amountBmin:x}") % 2 else "0x" + f"{event.amountBmin:x}", + ] + + tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) + gas_limit = 1000000000 + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + user.nonce += 1 + print_transaction_hash(txHash, network_provider.proxy.url) + except Exception as ex: + print(ex) + + def remove_liquidity_proxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyRemoveLiquidityEvent): + print("removeLiquidityProxy") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + sc_args = [ + "0x" + context.wrappedLpTokenId.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "removeLiquidityProxy".encode("ascii").hex(), + "0x" + Address(event.pairContract.address).hex(), + "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", + "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + gas_limit = 1000000000 + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + user.nonce += 1 + print_transaction_hash(txHash, network_provider.proxy.url) + except Exception as ex: + print(ex) + + +class DexProxyContract(DEXContractInterface): + def __init__(self, locked_tokens: list, token: str, version: ProxyContractVersion, + address: str = "", proxy_lp_token: str = "", proxy_farm_token: str = ""): + self.address = address + self.proxy_lp_token = proxy_lp_token + self.proxy_farm_token = proxy_farm_token + self.locked_tokens = locked_tokens + self.token = token + self.version = version + + def get_config_dict(self) -> dict: + output_dict = { + "token": self.token, + "locked_tokens": self.locked_tokens, + "proxy_farm_token": self.proxy_farm_token, + "proxy_lp_token": self.proxy_lp_token, + "address": self.address, + "version": self.version.value + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return DexProxyContract(token=config_dict['token'], + locked_tokens=config_dict['locked_tokens'], + proxy_farm_token=config_dict['proxy_farm_token'], + proxy_lp_token=config_dict['proxy_lp_token'], + address=config_dict['address'], + version=ProxyContractVersion(config_dict['version'])) + + def addLiquidityProxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyAddLiquidityEvent): + print("addLiquidityProxy") + print(f"Account: {user.address}") + + self.acceptEsdtPaymentProxy(context, user, event.pairContract, event.tokenA, event.nonceA, event.amountA) + self.acceptEsdtPaymentProxy(context, user, event.pairContract, event.tokenB, event.nonceB, event.amountB) + + contract = SmartContract(address=self.address) + sc_args = [ + "0x" + Address(event.pairContract.address).hex(), + "0x" + event.tokenA.encode("ascii").hex(), + "0x" + "0" + f"{event.nonceA:x}" if len(f"{event.nonceA:x}") % 2 else "0x" + f"{event.nonceA:x}", + "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", + "0x" + "0" + f"{event.amountAmin:x}" if len(f"{event.amountAmin:x}") % 2 else "0x" + f"{event.amountAmin:x}", + "0x" + event.tokenB.encode("ascii").hex(), + "0x" + "0" + f"{event.nonceB:x}" if len(f"{event.nonceB:x}") % 2 else "0x" + f"{event.nonceB:x}", + "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", + "0x" + "0" + f"{event.amountBmin:x}" if len(f"{event.amountBmin:x}") % 2 else "0x" + f"{event.amountBmin:x}", + ] + + tx_data = contract.prepare_execute_transaction_data("addLiquidityProxy", sc_args) + gas_limit = 1000000000 + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + user.nonce += 1 + print_transaction_hash(txHash, network_provider.proxy.url) + except Exception as ex: + print(ex) + + def acceptEsdtPaymentProxy(self, network_provider: NetworkProviders, user: Account, pair: PairContract, + token: str, nonce: int, amount: str): + print("acceptEsdtPaymentProxy") + print(f"Account: {user.address}") + + if nonce == 0: + contract = SmartContract(address=self.address) + sc_args = [ + "0x" + token.encode("ascii").hex(), + "0x" + "0" + f"{amount:x}" if len(f"{amount:x}") % 2 else "0x" + f"{amount:x}", + "0x" + "acceptEsdtPaymentProxy".encode("ascii").hex(), + "0x" + Address(pair.address).hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTTransfer", sc_args) + else: + contract = SmartContract(address=user.address) + sc_args = [ + "0x" + token.encode("ascii").hex(), + "0x" + "0" + f"{nonce:x}" if len(f"{nonce:x}") % 2 else "0x" + f"{nonce:x}", + "0x" + "0" + f"{amount:x}" if len(f"{amount:x}") % 2 else "0x" + f"{amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "acceptEsdtPaymentProxy".encode("ascii").hex(), + "0x" + Address(pair.address).hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = 50000000 + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + except Exception as ex: + print(ex) + + def removeLiquidityProxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyRemoveLiquidityEvent): + print("removeLiquidityProxy") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + sc_args = [ + "0x" + context.wrappedLpTokenId.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "removeLiquidityProxy".encode("ascii").hex(), + "0x" + Address(event.pairContract.address).hex(), + "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", + "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + gas_limit = 1000000000 + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + user.nonce += 1 + print_transaction_hash(txHash, network_provider.proxy.url) + except Exception as ex: + print(ex) + + def enterFarmProxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyEnterFarmEvent, lock: int = -1, initial: bool = False): + print("enterFarmProxy") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + enterFarmFn = "" + if lock == 1: + enterFarmFn = "enterFarmAndLockRewardsProxy" + elif lock == 0: + enterFarmFn = "enterFarmProxy" + else: + if random.randrange(0, 1) == 1: + enterFarmFn = "enterFarmAndLockRewardsProxy" + else: + enterFarmFn = "enterFarmProxy" + + gas_limit = 50000000 + + sc_args = [ + "0x" + Address(self.address).hex(), # proxy address + "0x01" if initial else "0x02", # number of tokens sent + "0x" + event.farming_tk.encode("ascii").hex(), # farming token details + "0x" + "0" + f"{event.farming_tk_nonce:x}" if len(f"{event.farming_tk_nonce:x}") % 2 else "0x" + f"{event.farming_tk_nonce:x}", + "0x" + "0" + f"{event.farming_tk_amount:x}" if len(f"{event.farming_tk_amount:x}") % 2 else "0x" + f"{event.farming_tk_amount:x}", + ] + if not initial: + sc_args.extend([ + "0x" + event.farm_tk.encode("ascii").hex(), # farm token details + "0x" + "0" + f"{event.farm_tk_nonce:x}" if len(f"{event.farm_tk_nonce:x}") % 2 else "0x" + f"{event.farm_tk_nonce:x}", + "0x" + "0" + f"{event.farm_tk_amount:x}" if len(f"{event.farm_tk_amount:x}") % 2 else "0x" + f"{event.farm_tk_amount:x}", + ]) + sc_args.extend([ + "0x" + enterFarmFn.encode("ascii").hex(), # enterFarm endpoint name + "0x" + Address(event.farmContract.address).hex(), # farm address + ]) + tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = user.address.bech32() # MultiESDTNFTTransfer is issued via self transfers + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + except Exception as ex: + print(ex) + + def exitFarmProxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyExitFarmEvent): + print("exitFarmProxy") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + + sc_args = [ + "0x" + event.token.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "exitFarmProxy".encode("ascii").hex(), + "0x" + Address(event.farmContract.address).hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + except Exception as ex: + print(ex) + + def claimRewardsProxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyClaimRewardsEvent): + print("claimRewardsProxy") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + + sc_args = [ + "0x" + event.token.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "claimRewardsProxy".encode("ascii").hex(), + "0x" + Address(event.farmContract.address).hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + except Exception as ex: + print(ex) + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): + """Expecting as args: + type[list]: locked asset factories contract addresses; care for the correct order based on locked tokens list + """ + print_warning("Deploy dex proxy contract") + + if len(args) != 1: + print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") + return "", "" + + if len(self.locked_tokens) != len(args[0]): + print_test_step_fail(f"FAIL: Failed to deploy contract. " + f"Mismatch between locked tokens and factory addresses.") + return "", "" + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 300000000 + value = 0 + address = "" + tx_hash = "" + + arguments = [ + "0x" + self.token.encode("ascii").hex() + ] + + locked_tokens_list = [f"str:{token}" for token in self.locked_tokens] + locked_tokens_args = list(sum(zip(locked_tokens_list, args[0]), ())) + + arguments.extend(locked_tokens_args) + + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, + args: list = [], no_init: bool = False): + """Expecting as args: + type[list]: locked asset factories contract addresses; care for the correct order based on locked tokens list + """ + print_warning("Upgrade dex proxy contract") + + if len(args) != 1 and not no_init: + print_test_step_fail(f"FAIL: Failed to upgrade contract. Args list not as expected.") + return "", "" + + if not no_init and len(self.locked_tokens) != len(args[0]): + print_test_step_fail(f"FAIL: Failed to deploy contract. " + f"Mismatch between locked tokens and factory addresses.") + return "", "" + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 300000000 + value = 0 + tx_hash = "" + + if no_init: + arguments = [] + else: + arguments = [ + "0x" + self.token.encode("ascii").hex() + ] + locked_tokens_list = [f"str:{token}" for token in self.locked_tokens] + locked_tokens_args = list(sum(zip(locked_tokens_list, args[0]), ())) + arguments.extend(locked_tokens_args) + + contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) + tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send upgrade transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash + + return tx_hash + + def register_proxy_farm_token(self, deployer: Account, proxy: ElrondProxy, args: list): + """Expecting as args: + type[str]: token display name + type[str]: token ticker + """ + print_warning("Register proxy farm token") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to register proxy farm token. Args list not as expected.") + return tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + args[0].encode("ascii").hex(), + "0x" + args[1].encode("ascii").hex(), + "0x12" + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "registerProxyFarm", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def register_proxy_lp_token(self, deployer: Account, proxy: ElrondProxy, args: list): + """Expecting as args: + type[str]: token display name + type[str]: token ticker + """ + print_warning("Register proxy lp token") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to register proxy lp token. Args list not as expected.") + return tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + args[0].encode("ascii").hex(), + "0x" + args[1].encode("ascii").hex(), + "0x12" + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "registerProxyPair", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + """Expecting as args: + type[str]: token id + type[str]: contract address to assign roles to + """ + def set_local_roles_proxy_token(self, deployer: Account, proxy: ElrondProxy, args: list): + print_warning("Set local roles for proxy token") + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to set proxy local roles: unexpected number of args.") + return "" + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [ + "0x" + args[0].encode("ascii").hex(), + "0x" + Address(args[1]).hex(), + "0x03", "0x04", "0x05" + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLocalRoles", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_energy_factory_address(self, deployer: Account, proxy: ElrondProxy, energy_address: str): + print_warning("Set energy factory address in proxy contract") + + if energy_address == "": + print_test_step_fail(f"FAIL: Failed to Add pair to intermediate: pair address is empty") + return "" + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [ + "0x" + Address(energy_address).hex(), + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setEnergyFactoryAddress", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def add_pair_to_intermediate(self, deployer: Account, proxy: ElrondProxy, pair_address: str): + print_warning("Add pair to intermediate in proxy contract") + + if pair_address == "": + print_test_step_fail(f"FAIL: Failed to Add pair to intermediate: pair address is empty") + return "" + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [ + "0x" + Address(pair_address).hex(), + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "addPairToIntermediate", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def add_farm_to_intermediate(self, deployer: Account, proxy: ElrondProxy, farm_address: str): + print_warning("Add farm to intermediate in proxy contract") + + if farm_address == "": + print_test_step_fail(f"FAIL: Failed to Add farm to intermediate: farm address is empty") + return "" + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [ + "0x" + Address(farm_address).hex(), + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "addFarmToIntermediate", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + pass + + def print_contract_info(self): + print_test_step_pass(f"Deployed proxy contract: {self.address}") + print_test_substep(f"Token: {self.token}") + print_test_substep(f"Locked tokens: {self.locked_tokens}") + print_test_substep(f"Proxy LP token: {self.proxy_lp_token}") + print_test_substep(f"Proxy Farm token: {self.proxy_farm_token}") diff --git a/contracts/esdt_contract.py b/contracts/esdt_contract.py new file mode 100644 index 0000000..01babe4 --- /dev/null +++ b/contracts/esdt_contract.py @@ -0,0 +1,67 @@ +from enum import Enum + +from utils.utils_tx import send_contract_call_tx, prepare_contract_call_tx +from utils.utils_chain import print_warning, print_test_step_fail +from erdpy.accounts import Account, Address +from erdpy.proxy import ElrondProxy + + +class ESDTRoles(Enum): + ESDTRoleLocalMint = 1 + ESDTRoleLocalBurn = 2 + + +class ESDTContract: + def __init__(self, esdt_address): + self.address = esdt_address + + """ Expected as args: + type[str]: token_id + type[str]: address to assign role to + type[str]: role name + """ + def set_special_role_token(self, token_owner: Account, proxy: ElrondProxy, args: list): + if len(args) != 3: + print_test_step_fail(f"FAIL: Failed to add trusted swap pair. Args list not as expected.") + return "" + token_id = args[0] + address = args[1] + role = args[2] + print_warning(f"Set ESDT role {role} for {token_id} on address {address}") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [ + "0x" + token_id.encode("ascii").hex(), + "0x" + Address(address).hex(), + "0x" + role.encode("ascii").hex() + ] + tx = prepare_contract_call_tx(Address(self.address), token_owner, network_config, gas_limit, + "setSpecialRole", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + token_owner.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def unset_special_role_token(self, token_owner: Account, proxy: ElrondProxy, args: list): + if len(args) != 3: + print_test_step_fail(f"FAIL: Failed to add trusted swap pair. Args list not as expected.") + return "" + token_id = args[0] + address = args[1] + role = args[2] + print_warning(f"Set ESDT role {role} for {token_id} on address {address}") + + network_config = proxy.get_network_config() + gas_limit = 10000000 + sc_args = [ + "0x" + token_id.encode("ascii").hex(), + "0x" + Address(address).hex(), + "0x" + role.encode("ascii").hex() + ] + tx = prepare_contract_call_tx(Address(self.address), token_owner, network_config, gas_limit, + "unSetSpecialRole", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + token_owner.nonce += 1 if tx_hash != "" else 0 + + return tx_hash diff --git a/contracts/farm_contract.py b/contracts/farm_contract.py new file mode 100644 index 0000000..4176bbb --- /dev/null +++ b/contracts/farm_contract.py @@ -0,0 +1,644 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import FarmContractVersion, DEXContractInterface +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders +from erdpy.accounts import Account, Address +from erdpy.contracts import SmartContract, CodeMetadata +from erdpy.proxy import ElrondProxy +from erdpy.transactions import Transaction +from utils.utils_chain import print_transaction_hash, print_test_step_fail, print_test_step_pass, \ + print_test_substep +from utils.utils_chain import print_warning +from events.farm_events import (EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent, + CompoundRewardsFarmEvent, MigratePositionFarmEvent) + + +class FarmContract(DEXContractInterface): + def __init__(self, farming_token, farm_token, farmed_token, address, version: FarmContractVersion, + proxy_contract=None): + self.farmingToken = farming_token + self.farmToken = farm_token + self.farmedToken = farmed_token + self.address = address + self.version = version + self.last_token_nonce = 0 + self.proxyContract = proxy_contract + + def get_config_dict(self) -> dict: + output_dict = { + "farmingToken": self.farmingToken, + "farmToken": self.farmToken, + "farmedToken": self.farmedToken, + "address": self.address, + "version": self.version.value, + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return FarmContract(farming_token=config_dict['farmingToken'], + farm_token=config_dict['farmToken'], + farmed_token=config_dict['farmedToken'], + address=config_dict['address'], + version=FarmContractVersion(config_dict['version'])) + + def has_proxy(self) -> bool: + if self.proxyContract is not None: + return True + return False + + def enterFarm(self, network_provider: NetworkProviders, user: Account, event: EnterFarmEvent, lock: int = 0, initial: bool = False) -> str: + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + enterFarmFn = "enterFarm" + if lock == 1: + enterFarmFn = "enterFarmAndLockRewards" + elif lock == 0: + enterFarmFn = "enterFarm" + + print_warning(f"{enterFarmFn}") + + gas_limit = 50000000 + + sc_args = [ + "0x" + Address(self.address).hex(), # contract address + "0x01" if initial else "0x02", # number of tokens sent + "0x" + event.farming_tk.encode("ascii").hex(), # farming token details + "0x" + "0" + f"{event.farming_tk_nonce:x}" if len( + f"{event.farming_tk_nonce:x}") % 2 else "0x" + f"{event.farming_tk_nonce:x}", + "0x" + "0" + f"{event.farming_tk_amount:x}" if len( + f"{event.farming_tk_amount:x}") % 2 else "0x" + f"{event.farming_tk_amount:x}", + ] + if not initial: + sc_args.extend([ + "0x" + event.farm_tk.encode("ascii").hex(), # farm token details + "0x" + "0" + f"{event.farm_tk_nonce:x}" if len( + f"{event.farm_tk_nonce:x}") % 2 else "0x" + f"{event.farm_tk_nonce:x}", + "0x" + "0" + f"{event.farm_tk_amount:x}" if len( + f"{event.farm_tk_amount:x}") % 2 else "0x" + f"{event.farm_tk_amount:x}", + ]) + sc_args.extend([ + "0x" + enterFarmFn.encode("ascii").hex(), # enterFarm endpoint name + ]) + tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = user.address.bech32() # MultiESDTNFTTransfer is issued via self transfers + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + + return txHash + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def exitFarm(self, network_provider: NetworkProviders, user: Account, event: ExitFarmEvent) -> str: + print_warning(f"exitFarm") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + sc_args = [ + "0x" + self.farmToken.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "exitFarm".encode("ascii").hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + + return txHash + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def claimRewards(self, network_provider: NetworkProviders, user: Account, event: ClaimRewardsFarmEvent) -> str: + print_warning(f"claimRewards") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + sc_args = [ + "0x" + self.farmToken.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "claimRewards".encode("ascii").hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + return txHash + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def compoundRewards(self, network_provider: NetworkProviders, user: Account, event: CompoundRewardsFarmEvent) -> str: + print_warning(f"compoundRewards") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + sc_args = [ + "0x" + self.farmToken.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "compoundRewards".encode("ascii").hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + return txHash + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def migratePosition(self, network_provider: NetworkProviders, user: Account, event: MigratePositionFarmEvent) -> str: + print_warning(f"migratePosition") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + sc_args = [ + "0x" + self.farmToken.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "migrateToNewFarm".encode("ascii").hex(), + "0x" + user.address.hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + return txHash + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + """Expecting as args:percent + type[str]: pair contract address + type[str]: locked asset factory address (only V14Locked) + type[str]: admin address (only V2Boosted) + self.version has to be initialized to correctly attempt the deploy for that specific type of farm. + """ + print_warning("Deploy farm contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 350000000 + value = 0 + address = "" + tx_hash = "" + + if (self.version in [FarmContractVersion.V12, FarmContractVersion.V14Unlocked] and len(args) < 1) or \ + (self.version in [FarmContractVersion.V14Locked, FarmContractVersion.V2Boosted] and len(args) != 2): + print_test_step_fail(f"FAIL: Failed to deploy contract version {self.version.name}. " + f"Args list not as expected.") + return tx_hash, address + + arguments = [ + "0x" + self.farmedToken.encode("ascii").hex(), + "0x" + self.farmingToken.encode("ascii").hex(), + "0xE8D4A51000", + "0x" + Address(args[0]).hex() + ] + if self.version == FarmContractVersion.V14Locked: + arguments.insert(2, "0x" + Address(args[1]).hex()) + if self.version == FarmContractVersion.V2Boosted: + arguments.append("0x" + deployer.address.hex()) + if args[1]: + arguments.append("0x" + Address(args[1]).hex()) + + print(f"Arguments: {arguments}") + + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = [], + no_init: bool = False): + """Expecting as args:percent + type[str]: pair contract address + type[str]: locked asset factory address (only V14Locked) + type[str]: admin address (only V2Boosted) + self.version has to be initialized to correctly attempt the upgrade for that specific type of farm. + """ + print_warning("Upgrade farm contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 350000000 + value = 0 + tx_hash = "" + + if no_init: + arguments = [] + else: + if (self.version in [FarmContractVersion.V12, FarmContractVersion.V14Unlocked] and len(args) < 1) or \ + (self.version in [FarmContractVersion.V14Locked, FarmContractVersion.V2Boosted] and len(args) != 2): + print_test_step_fail(f"FAIL: Failed to deploy contract version {self.version.name}. " + f"Args list not as expected.") + return tx_hash + + arguments = [ + "0x" + self.farmedToken.encode("ascii").hex(), + "0x" + self.farmingToken.encode("ascii").hex(), + "0xE8D4A51000", + "0x" + Address(args[0]).hex() + ] + if self.version == FarmContractVersion.V14Locked: + arguments.insert(2, "0x" + Address(args[1]).hex()) + if self.version == FarmContractVersion.V2Boosted: + arguments.append("0x" + deployer.address.hex()) + if args[1]: + arguments.append("0x" + Address(args[1]).hex()) + + print(f"Arguments: {arguments}") + + contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) + tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash + + return tx_hash + + def register_farm_token(self, deployer: Account, proxy: ElrondProxy, args: list): + """Expecting as args:percent + type[str]: token display name + type[str]: token ticker + """ + print_warning("Register farm token") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to register farm token. Args list not as expected.") + return tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + args[0].encode("ascii").hex(), + "0x" + args[1].encode("ascii").hex(), + "0x12" + ] + + print(f"Arguments: {sc_args}") + + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "registerFarmToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_local_roles_farm_token(self, deployer: Account, proxy: ElrondProxy): + print_warning("Set local roles for farm token") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLocalRolesFarmToken", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_rewards_per_block(self, deployer: Account, proxy: ElrondProxy, rewards_amount: int): + print_warning("Set rewards per block in farm") + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [ + "0x" + "0" + f"{rewards_amount:x}" if len(f"{rewards_amount:x}") % 2 else "0x" + f"{rewards_amount:x}", + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setPerBlockRewardAmount", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_penalty_percent(self, deployer: Account, proxy: ElrondProxy, percent: int): + print_warning("Set penalty percent in farm") + + network_config = proxy.get_network_config() + gas_limit = 20000000 + sc_args = [ + "0x" + "0" + f"{percent:x}" if len(f"{percent:x}") % 2 else "0x" + f"{percent:x}", + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "set_penalty_percent", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_minimum_farming_epochs(self, deployer: Account, proxy: ElrondProxy, epochs: int): + print_warning("Set minimum farming epochs in farm") + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [ + epochs + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "set_minimum_farming_epochs", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_boosted_yields_factors(self, deployer: Account, proxy: ElrondProxy, args: list): + """Only V2Boosted. + Expecting as args: + type[int]: max_rewards_factor + type[int]: user_rewards_energy_const + type[int]: user_rewards_farm_const + type[int]: min_energy_amount + type[int]: min_farm_amount + """ + print_warning("Set boosted yield factors") + + if len(args) != 5: + print_test_step_fail(f"FAIL: Failed to set boosted yield factors. Args list not as expected.") + return "" + + network_config = proxy.get_network_config() + gas_limit = 70000000 + sc_args = args + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setBoostedYieldsFactors", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_boosted_yields_rewards_percentage(self, deployer: Account, proxy: ElrondProxy, percentage: int): + """Only V2Boosted. + """ + print_warning("Set boosted yield rewards percentage") + + network_config = proxy.get_network_config() + gas_limit = 70000000 + sc_args = [percentage] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setBoostedYieldsRewardsPercentage", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_energy_factory_address(self, deployer: Account, proxy: ElrondProxy, energy_factory_address: str): + """Only V2Boosted. + """ + print_warning("Set energy factory address in farm") + + network_config = proxy.get_network_config() + gas_limit = 70000000 + sc_args = [energy_factory_address] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setEnergyFactoryAddress", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_locking_address(self, deployer: Account, proxy: ElrondProxy, locking_address: str): + """Only V2Boosted. + """ + print_warning("Set locking sc address in farm") + + network_config = proxy.get_network_config() + gas_limit = 70000000 + sc_args = [locking_address] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLockingScAddress", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_lock_epochs(self, deployer: Account, proxy: ElrondProxy, lock_epochs: int): + """Only V2Boosted. + """ + print_warning("Set lock epochs in farm") + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [lock_epochs] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLockEpochs", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def add_contract_to_whitelist(self, deployer: Account, proxy: ElrondProxy, whitelisted_sc_address: str): + """Only V2Boosted. + """ + print_warning("Add contract to farm whitelist") + + network_config = proxy.get_network_config() + gas_limit = 70000000 + sc_args = [whitelisted_sc_address] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "addSCAddressToWhitelist", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_transfer_role_farm_token(self, deployer: Account, proxy: ElrondProxy, whitelisted_sc_address: str): + """Only V2Boosted. + """ + print_warning("Set transfer role farm token") + + network_config = proxy.get_network_config() + gas_limit = 70000000 + sc_args = [whitelisted_sc_address] if whitelisted_sc_address else [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setTransferRoleFarmToken", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def resume(self, deployer: Account, proxy: ElrondProxy): + print_warning("Resume farm contract") + + network_config = proxy.get_network_config() + gas_limit = 30000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "resume", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def pause(self, deployer: Account, proxy: ElrondProxy): + print_warning("Pause farm contract") + + network_config = proxy.get_network_config() + gas_limit = 30000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "pause", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def start_produce_rewards(self, deployer: Account, proxy: ElrondProxy): + print_warning("Start producing rewards in farm contract") + + network_config = proxy.get_network_config() + gas_limit = 10000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "startProduceRewards", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def end_produce_rewards(self, deployer: Account, proxy: ElrondProxy): + print_warning("Stop producing rewards in farm contract") + + network_config = proxy.get_network_config() + gas_limit = 10000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "end_produce_rewards", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + _ = self.start_produce_rewards(deployer, proxy) + _ = self.resume(deployer, proxy) + + def print_contract_info(self): + print_test_step_pass(f"Deployed farm contract: {self.address}") + print_test_substep(f"Farming token: {self.farmingToken}") + print_test_substep(f"Farmed token: {self.farmedToken}") + print_test_substep(f"Farm token: {self.farmToken}") diff --git a/contracts/fees_collector_contract.py b/contracts/fees_collector_contract.py new file mode 100644 index 0000000..1d162b0 --- /dev/null +++ b/contracts/fees_collector_contract.py @@ -0,0 +1,265 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import DEXContractInterface +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx +from utils.utils_chain import print_warning, print_transaction_hash, print_test_step_fail, \ + print_test_step_pass, print_test_substep +from erdpy.accounts import Account, Address +from erdpy.contracts import CodeMetadata, SmartContract +from erdpy.proxy import ElrondProxy + + +class FeesCollectorContract(DEXContractInterface): + def __init__(self, address: str = ""): + self.address = address + + def get_config_dict(self) -> dict: + output_dict = { + "address": self.address, + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return FeesCollectorContract(address=config_dict['address']) + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = None): + """ Expected as args: + type[str]: locked token + type[str]: energy factory address + """ + print_warning("Deploy fees collector contract") + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to deploy. Args list not as expected.") + return "" + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + address = "" + tx_hash = "" + + arguments = [ + f"str:{args[0]}", + args[1] + ] + print(f"Arguments: {arguments}") + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def add_known_contracts(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str..]: addresses + """ + print_warning("Add known contract in fees collector contract") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to add know contracts. Args list not as expected.") + return tx_hash + + gas_limit = 10000000 + sc_args = args + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "addKnownContracts", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def add_known_tokens(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str..]: tokens + """ + print_warning("Add known tokens in fees collector contract") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to add know tokens. Args list not as expected.") + return tx_hash + + gas_limit = 10000000 + sc_args = args + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "addKnownTokens", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def remove_known_contracts(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str..]: addresses + """ + print_warning("Remove known contract in fees collector contract") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to remove know contracts. Args list not as expected.") + return tx_hash + + gas_limit = 10000000 + sc_args = args + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "removeKnownContracts", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def remove_known_tokens(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str..]: tokens + """ + print_warning("Remove known tokens in fees collector contract") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to remove know tokens. Args list not as expected.") + return tx_hash + + gas_limit = 10000000 + sc_args = args + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "removeKnownTokens", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_energy_factory_address(self, deployer: Account, proxy: ElrondProxy, factory_address: str): + """ Expected as args: + type[str]: energy factory address + """ + print_warning("Set Energy factory address in fees collector contract") + + network_config = proxy.get_network_config() + tx_hash = "" + + if not factory_address: + print_test_step_fail(f"FAIL: Failed to set Energy factory address. Arg not as expected.") + return tx_hash + + gas_limit = 30000000 + sc_args = [ + "0x" + Address(factory_address).hex() + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setEnergyFactoryAddress", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_locking_address(self, deployer: Account, proxy: ElrondProxy, locking_address: str): + """ Expected as args: + type[str]: locking address + """ + print_warning("Set locking address in fees collector") + + network_config = proxy.get_network_config() + tx_hash = "" + + if not locking_address: + print_test_step_fail(f"FAIL: Failed to set set locking address. Arg not as expected.") + return tx_hash + + gas_limit = 30000000 + sc_args = [ + "0x" + Address(locking_address).hex() + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLockingScAddress", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_lock_epochs(self, deployer: Account, proxy: ElrondProxy, lock_epochs: int): + print_warning("Set lock epochs in fees collector") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 30000000 + sc_args = [ + lock_epochs + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLockEpochs", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_locked_tokens_per_block(self, deployer: Account, proxy: ElrondProxy, locked_tokens_per_block: int): + print_warning("Set locked tokens per block") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 30000000 + sc_args = [ + locked_tokens_per_block + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLockedTokensPerBlock", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def claim_rewards(self, user: Account, proxy: ElrondProxy): + print_warning("Claim rewards from fees collector") + + network_config = proxy.get_network_config() + + gas_limit = 20000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), user, network_config, gas_limit, + "claimRewards", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + user.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = None): + pass + + def print_contract_info(self): + print_test_step_pass(f"Deployed fees collector contract: {self.address}") diff --git a/contracts/locked_asset_contract.py b/contracts/locked_asset_contract.py new file mode 100644 index 0000000..64d91bf --- /dev/null +++ b/contracts/locked_asset_contract.py @@ -0,0 +1,227 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import DEXContractInterface +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx +from utils.utils_chain import print_warning, print_test_step_fail, print_transaction_hash, \ + print_test_step_pass, print_test_substep +from erdpy.accounts import Account, Address +from erdpy.contracts import CodeMetadata, SmartContract +from erdpy.proxy import ElrondProxy + + +class LockedAssetContract(DEXContractInterface): + def __init__(self, unlocked_asset: str, locked_asset: str = "", address: str = ""): + self.address = address + self.unlocked_asset = unlocked_asset + self.locked_asset = locked_asset + + def get_config_dict(self) -> dict: + output_dict = { + "address": self.address, + "unlocked_asset": self.unlocked_asset, + "locked_asset": self.locked_asset + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return LockedAssetContract(address=config_dict['address'], + unlocked_asset=config_dict['unlocked_asset'], + locked_asset=config_dict['locked_asset']) + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): + print_warning("Deploy locked asset contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + address = "" + tx_hash = "" + + arguments = [ + "0x" + self.unlocked_asset.encode("ascii").hex(), + "0x000000000000016D11", + "0x000000000000018B11", + "0x00000000000001A911", + "0x00000000000001C711", + "0x00000000000001E510", + "0x000000000000020310", + ] + + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, + args: list = [], no_init: bool = False): + print_warning("Upgrade locked asset contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + tx_hash = "" + + if no_init: + arguments = [] + else: + arguments = [ + "0x" + self.unlocked_asset.encode("ascii").hex(), + "0x000000000000016D11", + "0x000000000000018B11", + "0x00000000000001A911", + "0x00000000000001C711", + "0x00000000000001E510", + "0x000000000000020310", + ] + + contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) + tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send upgrade transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash + + return tx_hash + + def set_new_factory_address(self, deployer: Account, proxy: ElrondProxy, contract_address: str): + print_warning("Set new factory address") + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [ + "0x" + Address(contract_address).hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setNewFactoryAddress", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def register_locked_asset_token(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: token name + type[str]: token ticker + """ + print_warning("Register locked asset token") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to register locked token. Args list not as expected.") + return tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + args[0].encode("ascii").hex(), + "0x" + args[1].encode("ascii").hex(), + "0x12" + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "registerLockedAssetToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_locked_asset_local_roles(self, deployer: Account, proxy: ElrondProxy, contract: str): + print_warning("Set locked asset token local roles") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [ + "0x" + Address(contract).hex(), + "0x03", + "0x04", + "0x05", + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLocalRolesLockedAssetToken", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def whitelist_contract(self, deployer: Account, proxy: ElrondProxy, contract_to_whitelist: str): + print_warning("Whitelist contract in locked asset contract") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [ + "0x" + Address(contract_to_whitelist).hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "whitelist", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_transfer_role_for_contract(self, deployer: Account, proxy: ElrondProxy, contract_to_whitelist: str): + print_warning("Set transfer role for contract") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [ + "0x" + Address(contract_to_whitelist).hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setTransferRoleForAddress", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_burn_role_for_contract(self, deployer: Account, proxy: ElrondProxy, contract_to_whitelist: str): + print_warning("Set burn role for contract") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [ + "0x" + Address(contract_to_whitelist).hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setBurnRoleForAddress", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + pass + + def print_contract_info(self): + print_test_step_pass(f"Deployed locked asset contract: {self.address}") + print_test_substep(f"Unlocked token: {self.unlocked_asset}") + print_test_substep(f"Locked token: {self.locked_asset}") diff --git a/contracts/metastaking_contract.py b/contracts/metastaking_contract.py new file mode 100644 index 0000000..a70619f --- /dev/null +++ b/contracts/metastaking_contract.py @@ -0,0 +1,321 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import DEXContractInterface, MetaStakingContractIdentity +from events.metastake_events import (EnterMetastakeEvent, + ExitMetastakeEvent, + ClaimRewardsMetastakeEvent) +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders +from erdpy.accounts import Account, Address +from erdpy.contracts import SmartContract, CodeMetadata +from erdpy.proxy import ElrondProxy +from erdpy.transactions import Transaction +from utils.utils_chain import print_transaction_hash, print_test_step_fail, print_test_step_pass, \ + print_test_substep +from utils.utils_chain import print_warning + + +class MetaStakingContract(DEXContractInterface): + def __init__(self, staking_token: str, lp_token: str, farm_token: str, stake_token: str, + lp_address: str, farm_address: str, stake_address: str, + metastake_token: str = "", address: str = ""): + self.address = address + self.metastake_token = metastake_token + self.staking_token = staking_token + self.lp_token = lp_token + self.farm_token = farm_token + self.stake_token = stake_token + self.lp_address = lp_address + self.farm_address = farm_address + self.stake_address = stake_address + + def get_config_dict(self) -> dict: + output_dict = { + "address": self.address, + "metastake_token": self.metastake_token, + "staking_token": self.staking_token, + "lp_token": self.lp_token, + "farm_token": self.farm_token, + "stake_token": self.stake_token, + "lp_address": self.lp_address, + "farm_address": self.farm_address, + "stake_address": self.stake_address + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return MetaStakingContract(address=config_dict['address'], + metastake_token=config_dict['metastake_token'], + staking_token=config_dict['staking_token'], + lp_token=config_dict['lp_token'], + farm_token=config_dict['farm_token'], + stake_token=config_dict['stake_token'], + lp_address=config_dict['lp_address'], + farm_address=config_dict['farm_address'], + stake_address=config_dict['stake_address']) + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): + print_warning("Deploy metastaking contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + address = "" + tx_hash = "" + + arguments = [ + "0x" + Address(self.farm_address).hex(), + "0x" + Address(self.stake_address).hex(), + "0x" + Address(self.lp_address).hex(), + "0x" + self.staking_token.encode("ascii").hex(), + "0x" + self.farm_token.encode("ascii").hex(), + "0x" + self.stake_token.encode("ascii").hex(), + "0x" + self.lp_token.encode("ascii").hex(), + ] + print(f"Arguments: {arguments}") + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, + args: list = [], no_init: bool = False): + print_warning("Upgrade metastaking contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + tx_hash = "" + + if no_init: + arguments = [] + else: + arguments = [ + "0x" + Address(self.farm_address).hex(), + "0x" + Address(self.stake_address).hex(), + "0x" + Address(self.lp_address).hex(), + "0x" + self.staking_token.encode("ascii").hex(), + "0x" + self.farm_token.encode("ascii").hex(), + "0x" + self.stake_token.encode("ascii").hex(), + "0x" + self.lp_token.encode("ascii").hex(), + ] + print(f"Arguments: {arguments}") + contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) + tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send upgrade transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash + + return tx_hash + + def register_dual_yield_token(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: token display name + type[str]: token ticker + """ + print_warning("Register metastaking token") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to register metastake token. Args not as expected.") + return tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + args[0].encode("ascii").hex(), + "0x" + args[1].encode("ascii").hex(), + "0x12" + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "registerDualYieldToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_local_roles_dual_yield_token(self, deployer: Account, proxy: ElrondProxy): + print_warning("Set local roles for metastake token") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLocalRolesDualYieldToken", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + pass + + def print_contract_info(self): + print_test_step_pass(f"Deployed metastaking contract: {self.address}") + print_test_substep(f"Staking token: {self.staking_token}") + print_test_substep(f"Stake address: {self.stake_address}") + print_test_substep(f"Farm address: {self.farm_address}") + print_test_substep(f"LP address: {self.lp_address}") + + def enter_metastake(self, network_provider: NetworkProviders, user: Account, + event: EnterMetastakeEvent, initial: bool = False) -> str: + print_warning('enterMetastaking') + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + metastake_fn = 'stakeFarmTokens' + gas_limit = 50000000 + + sc_args = [ + '0x' + Address(self.address).hex(), # contract address + '0x01' if initial else '0x02', # number of tokens sent + '0x' + event.metastaking_tk.encode('ascii').hex(), # farming token + '0x' + '0' + f'{event.metastaking_tk_nonce:x}' if len(f'{event.metastaking_tk_nonce:x}') % 2 else + '0x' + f'{event.metastaking_tk_nonce:x}', + '0x' + '0' + f'{event.metastaking_tk_amount:x}' if len(f'{event.metastaking_tk_amount:x}') % 2 else + '0x' + f'{event.metastaking_tk_amount:x}' + ] + + if not initial: + sc_args.extend([ + '0x' + event.metastake_tk.encode('ascii').hex(), # farming token + '0x' + '0' + f'{event.metastake_tk_nonce:x}' if len(f'{event.metastake_tk_nonce:x}') % 2 else + '0x' + f'{event.metastake_tk_nonce:x}', + '0x' + '0' + f'{event.metastake_tk_amount:x}' if len(f'{event.metastake_tk_amount:x}') % 2 else + '0x' + f'{event.metastake_tk_amount:x}' + ]) + + sc_args.extend(['0x' + metastake_fn.encode('ascii').hex()]) # endpoint name + tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = user.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + + try: + tx_hash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(tx_hash, network_provider.proxy.url) + user.nonce += 1 + return tx_hash + except Exception as ex: + print(ex) + return '' + + def exit_metastake(self, network_provider: NetworkProviders, user: Account, event: ExitMetastakeEvent): + print_warning('exitMetastaking') + print('Account: ', user.address) + + contract = SmartContract(address=user.address) + gas_limit = 50000000 + exit_metastake_fn = 'unstakeFarmTokens' + + sc_args = [ + '0x' + self.metastake_token.encode('ascii').hex(), + '0x' + '0' + f'{event.nonce:x}' if len(f'{event.nonce:x}') % 2 else '0x' + f'{event.nonce:x}', + '0x' + '0' + f'{event.amount:x}' if len(f'{event.amount:x}') % 2 else '0x' + f'{event.amount:x}', + '0x' + Address(self.address).hex(), + '0x' + exit_metastake_fn.encode('ascii').hex(), + '0x01', # first token slippage + '0x01' # second token slippage + ] + + tx_data = contract.prepare_execute_transaction_data('ESDTNFTTransfer', sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = user.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + + try: + tx_hash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(tx_hash, network_provider.proxy.url) + user.nonce += 1 + + return tx_hash + except Exception as ex: + print(ex) + return '' + + def claim_rewards_metastaking(self, network_provider: NetworkProviders, user: Account, event: ClaimRewardsMetastakeEvent): + print_warning('claimDualYield') + print('Account: ', user.address) + + contract = SmartContract(address=user.address) + gas_limit = 50000000 + claim_fn = 'claimDualYield' + + sc_args = [ + "0x" + self.metastake_token.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + claim_fn.encode('ascii').hex() + ] + + tx_data = contract.prepare_execute_transaction_data('ESDTNFTTransfer', sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = user.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + + try: + tx_hash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(tx_hash, network_provider.proxy.url) + user.nonce += 1 + + return tx_hash + except Exception as ex: + print(ex) + return '' diff --git a/contracts/pair_contract.py b/contracts/pair_contract.py new file mode 100644 index 0000000..90f2e34 --- /dev/null +++ b/contracts/pair_contract.py @@ -0,0 +1,608 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import ( + DEXContractInterface, PairContractVersion) +from utils.utils_tx import (NetworkProviders, + prepare_contract_call_tx, + send_contract_call_tx) +from utils.utils_chain import (dec_to_padded_hex, print_test_step_fail, + print_test_step_pass, + print_test_substep, + print_transaction_hash, + print_warning, string_to_hex) +from erdpy.accounts import Account, Address +from erdpy.contracts import CodeMetadata, SmartContract +from erdpy.proxy import ElrondProxy +from erdpy.transactions import Transaction + + +class SwapFixedInputEvent: + def __init__(self, tokenA: str, amountA: int, tokenB: str, amountBmin: int): + self.tokenA = tokenA + self.amountA = amountA + self.tokenB = tokenB + self.amountBmin = amountBmin + + +class SwapFixedOutputEvent: + def __init__(self, tokenA: str, amountAmax: int, tokenB: str, amountB: int): + self.tokenA = tokenA + self.amountAmax = amountAmax + self.tokenB = tokenB + self.amountB = amountB + + +class AddLiquidityEvent: + def __init__(self, tokenA: str, amountA: int, amountAmin: int, tokenB: str, amountB: int, amountBmin: int): + self.tokenA = tokenA + self.amountA = amountA + self.amountAmin = amountAmin + self.tokenB = tokenB + self.amountB = amountB + self.amountBmin = amountBmin + + +class RemoveLiquidityEvent: + def __init__(self, amount: int, tokenA: str, amountA: int, tokenB: str, amountB: int): + self.amount = amount + self.tokenA = tokenA + self.amountA = amountA + self.tokenB = tokenB + self.amountB = amountB + + +class SetCorrectReservesEvent: + pass + + +class PairContract(DEXContractInterface): + def __init__(self, firstToken: str, secondToken: str, version: PairContractVersion, + lpToken: str = "", address: str = "", proxy_contract=None): + self.firstToken = firstToken + self.secondToken = secondToken + self.version = version + self.lpToken = lpToken + self.address = address + self.proxy_contract = proxy_contract + + def get_config_dict(self) -> dict: + output_dict = { + "firstToken": self.firstToken, + "secondToken": self.secondToken, + "lpToken": self.lpToken, + "address": self.address, + "version": self.version.value + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return PairContract(firstToken=config_dict['firstToken'], + secondToken=config_dict['secondToken'], + lpToken=config_dict['lpToken'], + address=config_dict['address'], + version=PairContractVersion(config_dict['version'])) + + def hasProxy(self) -> bool: + if self.proxy_contract is not None: + return True + return False + + def swapFixedInput(self, network_provider: NetworkProviders, user: Account, event: SwapFixedInputEvent): + print_warning("swapFixedInput") + print(f"Account: {user.address}") + print(f"{event.amountA} {event.tokenA} for minimum {event.amountBmin} {event.tokenB}") + + contract = SmartContract(address=self.address) + + gas_limit = 50000000 + sc_args = [ + "0x" + event.tokenA.encode("ascii").hex(), + "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", + "0x" + "swapTokensFixedInput".encode("ascii").hex(), + "0x" + event.tokenB.encode("ascii").hex(), + "0x" + "0" + f"{event.amountBmin:x}" if len( + f"{event.amountBmin:x}") % 2 else "0x" + f"{event.amountBmin:x}", + ] + tx_data = contract.prepare_execute_transaction_data("ESDTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + + return txHash + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + + def swapFixedOutput(self, network_provider: NetworkProviders, user: Account, event: SwapFixedOutputEvent): + print_warning("swapFixedOutput") + print(f"Account: {user.address}") + + contract = SmartContract(address=self.address) + + gas_limit = 50000000 + sc_args = [ + "0x" + event.tokenA.encode("ascii").hex(), + "0x" + "0" + f"{event.amountAmax:x}" if len( + f"{event.amountAmax:x}") % 2 else "0x" + f"{event.amountAmax:x}", + "0x" + "swapTokensFixedOutput".encode("ascii").hex(), + "0x" + event.tokenB.encode("ascii").hex(), + "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", + ] + tx_data = contract.prepare_execute_transaction_data("ESDTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + + return txHash + except Exception as ex: + ex = ex + + def addLiquidity(self, network_provider: NetworkProviders, user: Account, event: AddLiquidityEvent): + print_warning("addLiquidity") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + sc_args = [ + "0x" + Address(self.address).hex(), + "0x02", + "0x" + string_to_hex(event.tokenA), + "0x00", + "0x" + dec_to_padded_hex(event.amountA), + "0x" + string_to_hex(event.tokenB), + "0x00", + "0x" + dec_to_padded_hex(event.amountB), + "0x" + string_to_hex("addLiquidity"), + "0x" + dec_to_padded_hex(event.amountAmin), + "0x" + dec_to_padded_hex(event.amountBmin) + ] + + tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) + gas_limit = 20000000 + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + + return txHash + except Exception as ex: + print(f'Exception encountered: {ex}') + + def addInitialLiquidity(self, network_provider: NetworkProviders, user: Account, event: AddLiquidityEvent): + print_warning("addInitialLiquidity") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + sc_args = [ + "0x" + Address(self.address).hex(), + "0x02", + "0x" + string_to_hex(event.tokenA), + "0x00", + "0x" + dec_to_padded_hex(event.amountA), + "0x" + string_to_hex(event.tokenB), + "0x00", + "0x" + dec_to_padded_hex(event.amountB), + "0x" + string_to_hex("addInitialLiquidity") + ] + + tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) + gas_limit = 20000000 + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + + def removeLiquidity(self, network_provider: NetworkProviders, user: Account, event: RemoveLiquidityEvent): + print_warning("removeLiquidity") + print(f"Account: {user.address}") + + contract = SmartContract(address=self.address) + + gas_limit = 20000000 + sc_args = [ + "0x" + self.lpToken.encode("ascii").hex(), + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + "removeLiquidity".encode("ascii").hex(), + "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", + "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", + ] + tx_data = contract.prepare_execute_transaction_data("ESDTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + + return txHash + except Exception as ex: + ex = ex + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + """Expecting as args: + type[str]: router address + type[str]: whitelisted owner address + type[str]: initial liquidity adder address (v2 required) + type[any]: fee percentage + type[any]: special fee + type[str..]: admin addresses (v2 required) + """ + print_warning("Deploy pair contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=False, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + address = "" + tx_hash = "" + + if len(args) < 5: + print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") + return tx_hash, address + + arguments = [ + "0x" + self.firstToken.encode("ascii").hex(), + "0x" + self.secondToken.encode("ascii").hex(), + "0x" + Address(args[0]).hex(), + "0x" + Address(args[1]).hex(), + args[3], + args[4], + args[2] + ] + + if self.version == PairContractVersion.V2: + arguments.extend(args[5:]) + + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + """Expecting as args: + type[str]: router address + type[str]: whitelisted owner address + type[str]: initial liquidity adder address (v2 required) + type[any]: fee percentage + type[any]: special fee + type[str..]: admin addresses (v2 required) + """ + print_warning("Upgrade pair contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=False) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + tx_hash = "" + + if len(args) < 5: + print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") + return tx_hash + + arguments = [ + "0x" + self.firstToken.encode("ascii").hex(), + "0x" + self.secondToken.encode("ascii").hex(), + "0x" + Address(args[0]).hex(), + "0x" + Address(args[1]).hex(), + args[3], + args[4], + args[2] + ] + + if self.version == PairContractVersion.V2: + arguments.extend(args[5:]) + + contract = SmartContract(address=Address(self.address), bytecode=bytecode, metadata=metadata) + tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + deployer.nonce += 1 if tx_hash != "" else 0 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash + + return tx_hash + + def contract_deploy_via_router(self, deployer: Account, proxy: ElrondProxy, router_contract, args: list): + """ Expected as args: + type[str]: initial liquidity adder address + type[any]: total fee percentage + type[any]: special fee percentage + type[str..]: admin addresses + """ + pair_args = [self.firstToken, self.secondToken] + pair_args.extend(args) + tx_hash, address = router_contract.pair_contract_deploy(deployer, proxy, pair_args) + return tx_hash, address + + def contract_upgrade_via_router(self, deployer: Account, proxy: ElrondProxy, router_contract, args: list) -> str: + """ Expected as args: + type[int]: total fee percentage + type[int]: special fee percentage + type[str]: initial liquidity adder (only v1 router) + """ + pair_args = [self.firstToken, self.secondToken] + pair_args.extend(args) + tx_hash = router_contract.pair_contract_upgrade(deployer, proxy, pair_args) + return tx_hash + + def issue_lp_token_via_router(self, deployer: Account, proxy: ElrondProxy, router_contract, args: list): + """ Expected as args: + type[str]: token display name + type[str]: token ticker + """ + print_warning("Issue LP token via router") + + if len(args) < 2: + print_test_step_fail(f"FAIL: Failed to issue lp token. Args list not as expected.") + return "" + + tx_hash = router_contract.issue_lp_token(deployer, proxy, [self.address, args[0], args[1]]) + return tx_hash + + def whitelist_contract(self, deployer: Account, proxy: ElrondProxy, contract_to_whitelist: str): + print_warning("Whitelist contract in pair") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [ + "0x" + Address(contract_to_whitelist).hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "whitelist", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def add_trusted_swap_pair(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: trusted swap pair address + type[str]: trusted pair first token identifier + type[str]: trusted pair second token identifier + """ + print_warning("Whitelist contract in pair") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 3: + print_test_step_fail(f"FAIL: Failed to add trusted swap pair. Args list not as expected.") + return tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + Address(args[0]).hex(), + "0x" + args[1].encode("ascii").hex(), + "0x" + args[2].encode("ascii").hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "addTrustedSwapPair", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def add_fees_collector(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: fees collector address + type[str]: fees cut + """ + print_warning("Setup fees collector in pair") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to setup fees collector in pair. Args list not as expected.") + return tx_hash + + gas_limit = 50000000 + sc_args = [ + "0x" + Address(args[0]).hex(), + args[1] + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setupFeesCollector", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_fees_percents(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: total fee percent + type[str]: special fee percent + """ + print_warning("Set fees in pair contract") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to set fees in pair. Args list not as expected.") + return tx_hash + + gas_limit = 50000000 + sc_args = args + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setFeePercents", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_lp_token_local_roles_via_router(self, deployer: Account, proxy: ElrondProxy, router_contract): + print_warning("Set lp token local roles via router") + tx_hash = router_contract.set_lp_token_local_roles(deployer, proxy, self.address) + return tx_hash + + """ Expected as args: + type[str]: address to receive fees + type[str]: expected token + """ + + def set_fee_on_via_router(self, deployer: Account, proxy: ElrondProxy, router_contract, args: list): + print_warning("Set fee on via router") + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to set fee on via router. Args list not as expected.") + return "" + + tx_hash = router_contract.set_fee_on(deployer, proxy, [self.address, args[0], args[1]]) + return tx_hash + + def set_locking_deadline_epoch(self, deployer: Account, proxy: ElrondProxy, epoch: int): + print_warning("Set locking deadline epoch in pool") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 100000000 + sc_args = [ + "0x" + "0" + f"{epoch:x}" if len(f"{epoch:x}") % 2 else "0x" + f"{epoch:x}" + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLockingDeadlineEpoch", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_unlock_epoch(self, deployer: Account, proxy: ElrondProxy, epoch: int): + print_warning("Set unlock epoch in pool") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 100000000 + sc_args = [ + "0x" + "0" + f"{epoch:x}" if len(f"{epoch:x}") % 2 else "0x" + f"{epoch:x}" + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setUnlockEpoch", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_locking_sc_address(self, deployer: Account, proxy: ElrondProxy, locking_address: str): + print_warning("Set locking contract address in pool") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 100000000 + sc_args = [ + "0x" + Address(locking_address).hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLockingScAddress", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def resume(self, deployer: Account, proxy: ElrondProxy): + print_warning("Resume swaps in pool") + + network_config = proxy.get_network_config() + gas_limit = 10000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "resume", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + _ = self.resume(deployer, proxy) + + def print_contract_info(self): + print_test_step_pass(f"Deployed pair contract: {self.address}") + print_test_substep(f"First token: {self.firstToken}") + print_test_substep(f"Second token: {self.secondToken}") + print_test_substep(f"LP token: {self.lpToken}") diff --git a/contracts/price_discovery_contract.py b/contracts/price_discovery_contract.py new file mode 100644 index 0000000..4018811 --- /dev/null +++ b/contracts/price_discovery_contract.py @@ -0,0 +1,302 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import PriceDiscoveryContractIdentity, DEXContractInterface +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders +from events.price_discovery_events import (DepositPDLiquidityEvent, + WithdrawPDLiquidityEvent, RedeemPDLPTokensEvent) +from utils.utils_chain import print_warning, print_transaction_hash, print_test_step_fail, \ + print_test_step_pass, print_test_substep +from erdpy.accounts import Account, Address +from erdpy.contracts import SmartContract, CodeMetadata +from erdpy.proxy import ElrondProxy +from erdpy.transactions import Transaction + + +class PriceDiscoveryContract(DEXContractInterface): + def __init__(self, + launched_token_id: str, + accepted_token_id: str, + redeem_token: str, + first_redeem_token_nonce: int, + second_redeem_token_nonce: int, + address: str, + locking_sc_address: str, + start_block: int, + no_limit_phase_duration_blocks: int, + linear_penalty_phase_duration_blocks: int, + fixed_penalty_phase_duration_blocks: int, + unlock_epoch: int, + min_launched_token_price: int, + min_penalty_percentage: int, + max_penalty_percentage: int, + fixed_penalty_percentage: int + ): + self.launched_token_id = launched_token_id # launched token + self.accepted_token = accepted_token_id # accepted token + self.redeem_token = redeem_token + self.first_redeem_token_nonce = first_redeem_token_nonce # launched token + self.second_redeem_token_nonce = second_redeem_token_nonce # accepted token + self.address = address + self.locking_sc_address = locking_sc_address + self.start_block = start_block + self.no_limit_phase_duration_blocks = no_limit_phase_duration_blocks + self.linear_penalty_phase_duration_blocks = linear_penalty_phase_duration_blocks + self.fixed_penalty_phase_duration_blocks = fixed_penalty_phase_duration_blocks + self.unlock_epoch = unlock_epoch + self.min_launched_token_price = min_launched_token_price + self.min_penalty_percentage = min_penalty_percentage + self.max_penalty_percentage = max_penalty_percentage + self.fixed_penalty_percentage = fixed_penalty_percentage + + def get_config_dict(self) -> dict: + output_dict = { + "launched_token_id": self.launched_token_id, + "accepted_token": self.accepted_token, + "redeem_token": self.redeem_token, + "first_redeem_token_nonce": self.first_redeem_token_nonce, + "second_redeem_token_nonce": self.second_redeem_token_nonce, + "address": self.address, + "locking_sc_address": self.locking_sc_address, + "start_block": self.start_block, + "no_limit_phase_duration_blocks": self.no_limit_phase_duration_blocks, + "linear_penalty_phase_duration_blocks": self.linear_penalty_phase_duration_blocks, + "fixed_penalty_phase_duration_blocks": self.fixed_penalty_phase_duration_blocks, + "unlock_epoch": self.unlock_epoch, + "min_launched_token_price": self.min_launched_token_price, + "min_penalty_percentage": self.min_penalty_percentage, + "max_penalty_percentage": self.max_penalty_percentage, + "fixed_penalty_percentage": self.fixed_penalty_percentage, + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return PriceDiscoveryContract(launched_token_id=config_dict['launched_token_id'], # launched token + accepted_token_id=config_dict['accepted_token'], # accepted token + redeem_token=config_dict['redeem_token'], + first_redeem_token_nonce=config_dict['first_redeem_token_nonce'], + # launched token + second_redeem_token_nonce=config_dict['second_redeem_token_nonce'], + # accepted token + address=config_dict['address'], + locking_sc_address=config_dict['locking_sc_address'], + start_block=config_dict['start_block'], + no_limit_phase_duration_blocks=config_dict['no_limit_phase_duration_blocks'], + linear_penalty_phase_duration_blocks=config_dict[ + 'linear_penalty_phase_duration_blocks'], + fixed_penalty_phase_duration_blocks=config_dict[ + 'fixed_penalty_phase_duration_blocks'], + unlock_epoch=config_dict['unlock_epoch'], + min_launched_token_price=config_dict['min_launched_token_price'], + min_penalty_percentage=config_dict['min_penalty_percentage'], + max_penalty_percentage=config_dict['max_penalty_percentage'], + fixed_penalty_percentage=config_dict['fixed_penalty_percentage']) + + def deposit_liquidity(self, network_provider: NetworkProviders, user: Account, event: DepositPDLiquidityEvent) -> str: + print_warning(f"Deposit Price Discovery liquidity") + print(f"Account: {user.address}") + print(f"Token: {event.deposit_token} Amount: {event.amount}") + + contract = SmartContract(Address(self.address)) + + gas_limit = 10000000 + sc_args = [ + "0x" + event.deposit_token.encode("ascii").hex(), + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + "deposit".encode("ascii").hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + return txHash + + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def withdraw_liquidity(self, network_provider: NetworkProviders, user: Account, event: WithdrawPDLiquidityEvent) -> str: + print_warning(f"Withdraw Price Discovery liquidity") + print(f"Account: {user.address}") + print(f"Token: {event.deposit_lp_token} Nonce: {event.nonce} Amount: {event.amount}") + + contract = SmartContract(address=user.address) + + gas_limit = 10000000 + sc_args = [ + "0x" + event.deposit_lp_token.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "withdraw".encode("ascii").hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + return txHash + + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def redeem_liquidity_position(self, network_provider: NetworkProviders, user: Account, event: RedeemPDLPTokensEvent) -> str: + print_warning(f"Redeem Price Discovery liquidity") + print(f"Account: {user.address}") + print(f"Token: {event.deposit_lp_token} Nonce: {event.nonce} Amount: {event.amount}") + + contract = SmartContract(address=user.address) + + gas_limit = 10000000 + sc_args = [ + "0x" + event.deposit_lp_token.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "redeem".encode("ascii").hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + return txHash + + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + """ Expected as args: + type[str]: whitelisted deposit rewards address + """ + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): + print_warning("Deploy price discovery contract") + + metadata = CodeMetadata(upgradeable=True, payable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 350000000 + value = 0 + address = "" + tx_hash = "" + + arguments = [ + "0x" + self.launched_token_id.encode("ascii").hex(), # launched token id + "0x" + self.accepted_token.encode("ascii").hex(), # accepted token id + "0x12", # launched token decimals + self.min_launched_token_price, + self.start_block, + self.no_limit_phase_duration_blocks, + self.linear_penalty_phase_duration_blocks, + self.fixed_penalty_phase_duration_blocks, + self.unlock_epoch, + self.min_penalty_percentage, + self.max_penalty_percentage, + self.fixed_penalty_percentage, + "0x" + Address(self.locking_sc_address).hex() # locking sc address + ] + + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + """ Expected as args: + type[str]: lp token name + type[str]: lp token ticker + """ + + def issue_redeem_token(self, deployer: Account, proxy: ElrondProxy, redeem_token_ticker: str): + print_warning("Issue price discovery redeem token") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [ + "0x" + redeem_token_ticker.encode("ascii").hex(), + "0x" + redeem_token_ticker.encode("ascii").hex(), + "0x12" + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "issueRedeemToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def create_initial_redeem_tokens(self, deployer: Account, proxy: ElrondProxy): + print_warning("Create initial redeem tokens for price discovery contract") + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "createInitialRedeemTokens", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + pass + + def print_contract_info(self): + print_test_step_pass(f"Deployed price discovery contract: {self.address}") + print_test_substep(f"Redeem token: {self.redeem_token}") diff --git a/contracts/proxy_deployer_contract.py b/contracts/proxy_deployer_contract.py new file mode 100644 index 0000000..b299906 --- /dev/null +++ b/contracts/proxy_deployer_contract.py @@ -0,0 +1,148 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import DEXContractInterface, RouterContractVersion +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event +from utils.utils_chain import print_transaction_hash, print_test_step_fail, print_warning, \ + print_test_step_pass +from erdpy.accounts import Account, Address +from erdpy.contracts import SmartContract, CodeMetadata +from erdpy.proxy import ElrondProxy + + +class ProxyDeployerContract(DEXContractInterface): + def __init__(self, template_name: str, address: str = ""): + """ + template_name: should be one of the defined names in config + """ + self.address = address + self.template = template_name + + def get_config_dict(self) -> dict: + output_dict = { + "address": self.address, + "template": self.template + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return ProxyDeployerContract(address=config_dict['address'], + template_name=config_dict['template']) + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + """Expecting as args: + type[str]: template sc address + """ + print_warning("Deploy proxy deployer contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + address = "" + tx_hash = "" + + if len(args) != 1: + print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") + return tx_hash, address + + arguments = [ + "0x" + Address(args[0]).hex() + ] + + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def farm_contract_deploy(self, deployer: Account, proxy: ElrondProxy, args: list): + """Expecting as args: + type[str]: reward token id + type[str]: farming token id + type[str]: pair contract address + """ + print_warning("Deploy farm via router") + + network_config = proxy.get_network_config() + address, tx_hash = "", "" + + if len(args) < 3: + print_test_step_fail(f"FAIL: Failed to deploy farm via proxy deployer. Args list not as expected.") + return address, tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + args[0].encode("ascii").hex(), + "0x" + args[1].encode("ascii").hex(), + "0x" + Address(args[2]).hex() + ] + + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "deployFarm", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + + # retrieve deployed contract address + if tx_hash != "": + response = proxy.get_transaction(tx_hash, with_results=True) + address = get_deployed_address_from_event(response) + deployer.nonce += 1 + + return tx_hash, address + + def call_farm_endpoint(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: farm address + type[str]: farm endpoint + type[list]: farm endpoint args + """ + print_warning("Call farm endpoint via proxy deployer") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 3: + print_test_step_fail(f"FAIL: Failed to call farm endpoint. Args list not as expected.") + return tx_hash + + print_warning(f"Calling: {args[1]}") + + gas_limit = 20000000 + sc_args = [ + "0x" + Address(args[0]).hex(), + "str:" + args[1], + ] + if type(args[2] != list): + endpoint_args = [args[2]] + else: + endpoint_args = args[2] + sc_args.extend(endpoint_args) + + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "callFarmEndpoint", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + pass + + def print_contract_info(self): + print_test_step_pass(f"Deployed proxy deployer contract: {self.address}") diff --git a/contracts/router_contract.py b/contracts/router_contract.py new file mode 100644 index 0000000..6130d18 --- /dev/null +++ b/contracts/router_contract.py @@ -0,0 +1,291 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import DEXContractInterface, RouterContractVersion +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event +from utils.utils_chain import print_transaction_hash, print_test_step_fail, print_warning, \ + print_test_step_pass +from erdpy.accounts import Account, Address +from erdpy.contracts import SmartContract, CodeMetadata +from erdpy.proxy import ElrondProxy + + +class RouterContract(DEXContractInterface): + def __init__(self, version: RouterContractVersion, address: str = ""): + self.address = address + self.version = version + + def get_config_dict(self) -> dict: + output_dict = { + "address": self.address, + "version": self.version.value + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return RouterContract(address=config_dict['address'], + version=RouterContractVersion(config_dict['version'])) + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + """Expecting as args: + type[str]: pair template address + """ + print_warning("Deploy router contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + address = "" + tx_hash = "" + + if len(args) != 1: + print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") + return tx_hash, address + + arguments = [ + "0x" + Address(args[0]).hex() + ] + + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + """Expecting as args: + type[str]: pair template address + """ + print_warning("Upgrade router contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + tx_hash = "" + + if len(args) != 1: + print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") + return tx_hash + + arguments = [ + "0x" + Address(args[0]).hex() + ] + + contract = SmartContract(address=Address(self.address), bytecode=bytecode, metadata=metadata) + tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + deployer.nonce += 1 if tx_hash != "" else 0 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash + + return tx_hash + + def pair_contract_deploy(self, deployer: Account, proxy: ElrondProxy, args: list): + """Expecting as args: + type[str]: first pair token + type[str]: second pair token + type[str]: address of initial liquidity adder + type[str]: total fee percentage + type[str]: special fee percentage + type[str..]: admin addresses (v2 only) + """ + print_warning("Deploy pair via router") + + network_config = proxy.get_network_config() + address, tx_hash = "", "" + + if len(args) < 5: + print_test_step_fail(f"FAIL: Failed to deploy pair via router. Args list not as expected.") + return address, tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + args[0].encode("ascii").hex(), + "0x" + args[1].encode("ascii").hex(), + "0x" + Address(args[2]).hex(), + args[3], + args[4] + ] + + if self.version == RouterContractVersion.V2: + sc_args.extend(args[5:]) + + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "createPair", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + + # retrieve deployed contract address + if tx_hash != "": + response = proxy.get_transaction(tx_hash, with_results=True) + address = get_deployed_address_from_event(response) + deployer.nonce += 1 + + return tx_hash, address + + def pair_contract_upgrade(self, deployer: Account, proxy: ElrondProxy, args: list) -> str: + """ Expected as args: + type[str]: first token id + type[str]: second token id + type[int]: total fee percent + type[int]: special fee percent + type[str]: initial liquidity adder (only v1) + """ + print_warning("Upgrade pair contract") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) < 4: + print_test_step_fail(f"FAIL: Failed to issue token. Args list not as expected.") + return tx_hash + + gas_limit = 200000000 + sc_args = [ + "str:" + args[0], + "str:" + args[1], + args[2], + args[3] + ] + if self.version == RouterContractVersion.V1: + sc_args.insert(2, args[4]) + + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "upgradePair", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + """ Expected as args: + type[str]: pair address + type[str]: lp token name + type[str]: lp token ticker + """ + def issue_lp_token(self, deployer: Account, proxy: ElrondProxy, args: list): + print_warning("Issue LP token") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 3: + print_test_step_fail(f"FAIL: Failed to issue token. Args list not as expected.") + return tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + Address(args[0]).hex(), + "0x" + args[1].encode("ascii").hex(), + "0x" + args[2].encode("ascii").hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "issueLpToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_lp_token_local_roles(self, deployer: Account, proxy: ElrondProxy, pair_contract: str): + print_warning("Set LP token local roles") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [ + "0x" + Address(pair_contract).hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLocalRoles", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_fee_on(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: pair address to send fees + type[str]: address to receive fees + type[str]: expected token + """ + print_warning("Set fee on for pool") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 3: + print_test_step_fail(f"FAIL: Failed to add trusted swap pair. Args list not as expected.") + return tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + Address(args[0]).hex(), + "0x" + Address(args[1]).hex(), + "0x" + args[2].encode("ascii").hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setFeeOn", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def pair_contract_pause(self, deployer: Account, proxy: ElrondProxy, pair_contract: str): + print_warning("Pause pair contract") + + network_config = proxy.get_network_config() + gas_limit = 30000000 + sc_args = [ + "0x" + Address(pair_contract).hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "pause", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def pair_contract_resume(self, deployer: Account, proxy: ElrondProxy, pair_contract: str): + print_warning("Resume pair contract") + + network_config = proxy.get_network_config() + gas_limit = 30000000 + sc_args = [ + "0x" + Address(pair_contract).hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "resume", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + pass + + def print_contract_info(self): + print_test_step_pass(f"Deployed router contract: {self.address}") diff --git a/contracts/simple_lock_contract.py b/contracts/simple_lock_contract.py new file mode 100644 index 0000000..ed141f3 --- /dev/null +++ b/contracts/simple_lock_contract.py @@ -0,0 +1,169 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import DEXContractInterface +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx +from utils.utils_chain import print_warning, print_transaction_hash, print_test_step_fail, \ + print_test_step_pass, print_test_substep +from erdpy.accounts import Account, Address +from erdpy.contracts import CodeMetadata, SmartContract +from erdpy.proxy import ElrondProxy + + +class SimpleLockContract(DEXContractInterface): + def __init__(self, locked_token: str = "", lp_proxy_token: str = "", address: str = ""): + self.address = address + self.locked_token = locked_token + self.lp_proxy_token = lp_proxy_token + + def get_config_dict(self) -> dict: + output_dict = { + "address": self.address, + "locked_token": self.locked_token, + "lp_proxy_token": self.lp_proxy_token + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return SimpleLockContract(address=config_dict['address'], + locked_token=config_dict['locked_token'], + lp_proxy_token=config_dict['lp_proxy_token']) + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): + print_warning("Deploy simple lock contract") + + metadata = CodeMetadata(upgradeable=True, payable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + address = "" + tx_hash = "" + + arguments = [] + + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def issue_locked_lp_token(self, deployer: Account, proxy: ElrondProxy, locked_lp_token: str): + print_warning("Issue locked LP token") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 100000000 + sc_args = [ + "0x" + locked_lp_token.encode("ascii").hex(), + "0x" + locked_lp_token.encode("ascii").hex(), + "0x12" + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "issueLpProxyToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def issue_locked_token(self, deployer: Account, proxy: ElrondProxy, locked_token: str): + print_warning("Issue locked token") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 100000000 + sc_args = [ + "0x" + locked_token.encode("ascii").hex(), + "0x" + locked_token.encode("ascii").hex(), + "0x12" + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "issueLockedToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_local_roles_locked_token(self, deployer: Account, proxy: ElrondProxy): + print_warning("Set local roles locked token") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 100000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLocalRolesLockedToken", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_local_roles_locked_lp_token(self, deployer: Account, proxy: ElrondProxy): + print_warning("Set local roles locked lp token") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 100000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLocalRolesLpProxyToken", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + """ Expected as args: + type[str]: pair address + type[str]: first token identifier + type[str]: second token identifier + """ + + def add_lp_to_whitelist(self, deployer: Account, proxy: ElrondProxy, args: list): + print_warning("Add LP to Whitelist in simple lock contract") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 3: + print_test_step_fail(f"FAIL: Failed to whitelist lp in simple lock contract. Args list not as expected.") + return tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + Address(args[0]).hex(), + "0x" + args[1].encode("ascii").hex(), + "0x" + args[2].encode("ascii").hex() + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "addLpToWhitelist", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + pass + + def print_contract_info(self): + print_test_step_pass(f"Deployed simple lock contract: {self.address}") + print_test_substep(f"Locked token: {self.locked_token}") + print_test_substep(f"Locked LP token: {self.lp_proxy_token}") diff --git a/contracts/simple_lock_energy_contract.py b/contracts/simple_lock_energy_contract.py new file mode 100644 index 0000000..c6be1d7 --- /dev/null +++ b/contracts/simple_lock_energy_contract.py @@ -0,0 +1,559 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import DEXContractInterface +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, \ + multi_esdt_endpoint_call, endpoint_call +from utils.utils_chain import print_warning, print_transaction_hash, print_test_step_fail, \ + print_test_step_pass, print_test_substep +from erdpy.accounts import Account, Address +from erdpy.contracts import CodeMetadata, SmartContract +from erdpy.proxy import ElrondProxy + + +class SimpleLockEnergyContract(DEXContractInterface): + def __init__(self, base_token: str, locked_token: str = "", lp_proxy_token: str = "", farm_proxy_token: str = "", + address: str = ""): + self.address = address + self.base_token = base_token + self.locked_token = locked_token + self.lp_proxy_token = lp_proxy_token + self.farm_proxy_token = farm_proxy_token + + def get_config_dict(self) -> dict: + output_dict = { + "address": self.address, + "base_token": self.base_token, + "locked_token": self.locked_token, + "lp_proxy_token": self.lp_proxy_token, + "farm_proxy_token": self.farm_proxy_token + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return SimpleLockEnergyContract(address=config_dict['address'], + base_token=config_dict['base_token'], + locked_token=config_dict['locked_token'], + lp_proxy_token=config_dict['lp_proxy_token'], + farm_proxy_token=config_dict['farm_proxy_token']) + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + """Expecting as args: + type[str]: legacy token id + type[str]: locked asset factory address + type[int]: min migrated token locking epochs + type[list]: lock options + type[list]: penalties + """ + print_warning("Deploy simple lock energy contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + address = "" + tx_hash = "" + + if len(args) != 5: + print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") + return tx_hash, address + arguments = [ + "str:" + self.base_token, # base token id + "str:" + args[0], # legacy token id + args[1], # locked asset factory address + args[2] # min migrated token locking epochs + ] + lock_fee_pairs = list(zip(args[3], args[4])) + lock_options = [item for sublist in lock_fee_pairs for item in sublist] + arguments.extend(lock_options) # lock_options + + print(f"Arguments: {arguments}") + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + """Expecting as args: + type[str]: legacy token id + type[str]: locked asset factory address + type[int]: min migrated token locking epochs + type[list]: lock options + type[list]: penalties + """ + print_warning("Upgrade simple lock energy contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + tx_hash = "" + + if len(args) != 5: + print_test_step_fail(f"FAIL: Failed to upgrade contract. Args list not as expected.") + return tx_hash + arguments = [ + "str:" + self.base_token, # base token id + "str:" + args[0], # legacy token id + args[1], # locked asset factory address + args[2] # min migrated token locking epochs + ] + + lock_fee_pairs = list(zip(args[3], args[4])) + lock_options = [item for sublist in lock_fee_pairs for item in sublist] + arguments.extend(lock_options) # lock_options + + contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) + tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash + + return tx_hash + + def issue_locked_lp_token(self, deployer: Account, proxy: ElrondProxy, locked_lp_token: str): + print_warning("Issue locked LP token") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 100000000 + sc_args = [ + "0x" + locked_lp_token.encode("ascii").hex(), + "0x" + locked_lp_token.encode("ascii").hex(), + "0x12" + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "issueLpProxyToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def issue_locked_farm_token(self, deployer: Account, proxy: ElrondProxy, locked_lp_token: str): + print_warning("Issue locked Farm token") + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 100000000 + sc_args = [ + "0x" + locked_lp_token.encode("ascii").hex(), + "0x" + locked_lp_token.encode("ascii").hex(), + "0x12" + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "issueFarmProxyToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def issue_locked_token(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: token display name + type[str]: token ticker + """ + print_warning("Issue locked token") + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to issue locked token in simple lock energy contract. " + f"Args list not as expected.") + return "" + + network_config = proxy.get_network_config() + tx_hash = "" + + gas_limit = 100000000 + sc_args = [ + "0x" + args[0].encode("ascii").hex(), + "0x" + args[1].encode("ascii").hex(), + "0x12" + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "issueLockedToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_transfer_role_locked_token(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: new role address; empty will assign the role to the contract itself + """ + + function_purpose = "Set transfer role locked token" + return endpoint_call(function_purpose, proxy, 100000000, deployer, Address(self.address), + "setTransferRoleLockedToken", args) + + def set_burn_role_locked_token(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: new role address + """ + function_purpose = "set burn roles on locked token for address" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " + f"Args list not as expected.") + return "" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 100000000, deployer, Address(self.address), + "setBurnRoleLockedToken", args) + + def set_old_locked_asset_factory(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: old locked asset factory address + """ + function_purpose = "set old locked asset factory address" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " + f"Args list not as expected.") + return "" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), + "setOldLockedAssetFactoryAddress", args) + + def set_fees_collector(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: fees collector address + """ + function_purpose = "set fees collector address" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " + f"Args list not as expected.") + return "" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), + "setFeesCollectorAddress", args) + + def set_token_unstake(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: token unstake address + """ + function_purpose = "set fees collector address" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " + f"Args list not as expected.") + return "" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), + "setTokenUnstakeAddress", args) + + def add_lock_options(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[int..]: lock options + """ + function_purpose = "add lock options" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " + f"Args list not as expected.") + return "" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), + "addLockOptions", args) + + def remove_lock_options(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[int..]: lock options + """ + function_purpose = "remove lock options" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " + f"Args list not as expected.") + return "" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), + "removeLockOptions", args) + + def set_penalty_percentage(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[int]: min penalty 0 - 10000 + type[int]: max penalty - 10000 + """ + function_purpose = "set penalty percentage" + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " + f"Args list not as expected.") + return "" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), + "setPenaltyPercentage", args) + + def set_fees_burn_percentage(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[int]: burn percentage 0 - 10000 + """ + function_purpose = "set fees burn percentage" + + if len(args) != 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " + f"Args list not as expected.") + return "" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), + "setFeesBurnPercentage", args) + + def add_sc_to_whitelist(self, deployer: Account, proxy: ElrondProxy, contract_address: str): + print_warning("Add SC to Whitelist in simple lock energy contract") + + network_config = proxy.get_network_config() + + gas_limit = 50000000 + sc_args = [ + contract_address + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "addSCAddressToWhitelist", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def remove_sc_from_whitelist(self, deployer: Account, proxy: ElrondProxy, contract_address: str): + """ Expected as args: + type[str]: address + """ + print_warning("Remove SC from Whitelist in simple lock energy contract") + + network_config = proxy.get_network_config() + + gas_limit = 50000000 + sc_args = [ + contract_address + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "removeSCAddressFromWhitelist", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def add_sc_to_token_transfer_whitelist(self, deployer: Account, proxy: ElrondProxy, contract_address: str): + print_warning("Add SC to Token Transfer Whitelist in simple lock energy contract") + + network_config = proxy.get_network_config() + + gas_limit = 50000000 + sc_args = [ + contract_address + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "addToTokenTransferWhitelist", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def remove_sc_from_token_transfer_whitelist(self, deployer: Account, proxy: ElrondProxy, contract_address: str): + """ Expected as args: + type[str]: address + """ + print_warning("Remove SC from Token Transfer Whitelist in simple lock energy contract") + + network_config = proxy.get_network_config() + + gas_limit = 50000000 + sc_args = [ + contract_address + ] + print(f"Arguments: {sc_args}") + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "removeFromTokenTransferWhitelist", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def lock_tokens(self, user: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + type[int]: lock epochs + opt: type[address]: destination address + """ + function_purpose = "lock tokens" + + if len(args) < 2: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return "" + print(f"Arguments: {args}") + return multi_esdt_endpoint_call(function_purpose, proxy, 10000000, + user, Address(self.address), "lockTokens", args) + + def unlock_tokens(self, user: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + opt: type[address]: destination address + """ + function_purpose = "unlock tokens" + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return "" + print(f"Arguments: {args}") + return multi_esdt_endpoint_call(function_purpose, proxy, 10000000, + user, Address(self.address), "unlockTokens", args) + + def unlock_early(self, user: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + """ + function_purpose = "unlock tokens early" + if len(args) != 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return "" + print(f"Arguments: {args}") + return multi_esdt_endpoint_call(function_purpose, proxy, 30000000, + user, Address(self.address), "unlockEarly", args) + + def reduce_lock(self, user: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + type[int]: epochs to reduce + """ + function_purpose = "reduce lock period" + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return "" + print(f"Arguments: {args}") + return multi_esdt_endpoint_call(function_purpose, proxy, 20000000, + user, Address(self.address), "reduceLockPeriod", args) + + def extend_lock(self, user: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + type[int]: new lock option + """ + function_purpose = "extend lock period" + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return "" + print(f"Arguments: {args}") + return multi_esdt_endpoint_call(function_purpose, proxy, 10000000, + user, Address(self.address), "extendLockingPeriod", args) + + def add_liquidity_locked_token(self, user: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + type[int]: first token amount min + type[int]: second token amount min + """ + function_purpose = "add liquidity for locked token" + + if len(args) != 3: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return "" + + return multi_esdt_endpoint_call(function_purpose, proxy, 20000000, + user, Address(self.address), "addLiquidityLockedToken", args) + + def remove_liquidity_locked_token(self, user: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + type[int]: first token amount min + type[int]: second token amount min + """ + function_purpose = "add liquidity for locked token" + + if len(args) != 3: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return "" + print(f"Arguments: {args}") + return multi_esdt_endpoint_call(function_purpose, proxy, 20000000, + user, Address(self.address), "removeLiquidityLockedToken", args) + + def enter_farm_locked_token(self, user: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + """ + function_purpose = "enter farm with locked token" + + if len(args) != 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return "" + print(f"Arguments: {args}") + return multi_esdt_endpoint_call(function_purpose, proxy, 30000000, + user, Address(self.address), "enterFarmLockedToken", args) + + def exit_farm_locked_token(self, user: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + """ + function_purpose = "exit farm with locked token" + + if len(args) != 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return "" + print(f"Arguments: {args}") + return multi_esdt_endpoint_call(function_purpose, proxy, 30000000, + user, Address(self.address), "exitFarmLockedToken", args) + + def claim_farm_locked_token(self, user: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + """ + function_purpose = "claim farm with locked token" + + if len(args) != 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return "" + print(f"Arguments: {args}") + return multi_esdt_endpoint_call(function_purpose, proxy, 30000000, + user, Address(self.address), "farmClaimRewardsLockedToken", args) + + def pause(self, deployer: Account, proxy: ElrondProxy): + function_purpose = "Resume simple lock energy contract" + return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), "pause", []) + + def resume(self, deployer: Account, proxy: ElrondProxy): + function_purpose = "Resume simple lock energy contract" + return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), "unpause", []) + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = None): + self.set_transfer_role_locked_token(deployer, proxy, []) + self.resume(deployer, proxy) + + def print_contract_info(self): + print_test_step_pass(f"Deployed simple lock energy contract: {self.address}") + print_test_substep(f"Base token: {self.base_token}") + print_test_substep(f"Locked token: {self.locked_token}") + print_test_substep(f"Locked LP token: {self.lp_proxy_token}") + print_test_substep(f"Locked Farm token: {self.farm_proxy_token}") diff --git a/contracts/staking_contract.py b/contracts/staking_contract.py new file mode 100644 index 0000000..713ae45 --- /dev/null +++ b/contracts/staking_contract.py @@ -0,0 +1,444 @@ +import sys +import time +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import DEXContractInterface, StakingContractIdentity, \ + StakingContractVersion +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders +from erdpy.accounts import Account, Address +from erdpy.contracts import SmartContract, CodeMetadata +from erdpy.proxy import ElrondProxy +from erdpy.transactions import Transaction +from utils.utils_chain import print_transaction_hash, print_test_step_fail, print_test_step_pass, \ + print_test_substep +from utils.utils_generic import get_continue_confirmation +from utils.utils_chain import print_warning +from events.farm_events import (EnterFarmEvent, ExitFarmEvent, + ClaimRewardsFarmEvent, CompoundRewardsFarmEvent, + MigratePositionFarmEvent) + + +class StakingContract(DEXContractInterface): + def __init__(self, farming_token: str, max_apr: int, rewards_per_block: int, unbond_epochs: int, + version: StakingContractVersion, farm_token: str = "", address: str = ""): + self.farming_token = farming_token + self.farm_token = farm_token + self.farmed_token = farming_token + self.address = address + self.max_apr = max_apr + self.rewards_per_block = rewards_per_block + self.unbond_epochs = unbond_epochs + self.version = version + + def get_config_dict(self) -> dict: + output_dict = { + "farming_token": self.farming_token, + "farm_token": self.farm_token, + "address": self.address, + "max_apr": self.max_apr, + "rewards_per_block": self.rewards_per_block, + "unbond_epochs": self.unbond_epochs, + "version": self.version.value + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return StakingContract(farming_token=config_dict['farming_token'], + farm_token=config_dict['farm_token'], + address=config_dict['address'], + max_apr=config_dict['max_apr'], + rewards_per_block=config_dict['rewards_per_block'], + unbond_epochs=config_dict['unbond_epochs'], + version=StakingContractVersion(config_dict['version'])) + + def stake_farm(self, network_provider: NetworkProviders, user: Account, event: EnterFarmEvent, + initial: bool = False) -> str: + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + stake_farm_fn = "stakeFarm" + + print_warning(f"{stake_farm_fn}") + + gas_limit = 50000000 + + sc_args = [ + "0x" + Address(self.address).hex(), # contract address + "0x01" if initial else "0x02", # number of tokens sent + "0x" + event.farming_tk.encode("ascii").hex(), # farming token details + "0x" + "0" + f"{event.farming_tk_nonce:x}" if len( + f"{event.farming_tk_nonce:x}") % 2 else "0x" + f"{event.farming_tk_nonce:x}", + "0x" + "0" + f"{event.farming_tk_amount:x}" if len( + f"{event.farming_tk_amount:x}") % 2 else "0x" + f"{event.farming_tk_amount:x}", + ] + if not initial: + sc_args.extend([ + "0x" + event.farm_tk.encode("ascii").hex(), # farm token details + "0x" + "0" + f"{event.farm_tk_nonce:x}" if len( + f"{event.farm_tk_nonce:x}") % 2 else "0x" + f"{event.farm_tk_nonce:x}", + "0x" + "0" + f"{event.farm_tk_amount:x}" if len( + f"{event.farm_tk_amount:x}") % 2 else "0x" + f"{event.farm_tk_amount:x}", + ]) + sc_args.extend([ + "0x" + stake_farm_fn.encode("ascii").hex(), # enterFarm endpoint name + ]) + tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = user.address.bech32() # MultiESDTNFTTransfer is issued via self transfers + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + + return txHash + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def unstake_farm(self, network_provider: NetworkProviders, user: Account, event: ExitFarmEvent) -> str: + unstake_fn = 'unstakeFarm' + print_warning(f"{unstake_fn}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + sc_args = [ + "0x" + event.farm_token.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + unstake_fn.encode("ascii").hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + + return txHash + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def claimRewards(self, network_provider: NetworkProviders, user: Account, event: ClaimRewardsFarmEvent) -> str: + print_warning(f"claimRewards") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + sc_args = [ + "0x" + self.farm_token.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "claimRewards".encode("ascii").hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + return txHash + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def compoundRewards(self, network_provider: NetworkProviders, user: Account, event: CompoundRewardsFarmEvent) -> str: + print_warning(f"compoundRewards") + print(f"Account: {user.address}") + + contract = SmartContract(address=user.address) + + gas_limit = 50000000 + sc_args = [ + "0x" + self.farm_token.encode("ascii").hex(), + "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", + "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", + "0x" + Address(self.address).hex(), + "0x" + "compoundRewards".encode("ascii").hex(), + ] + tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) + + tx = Transaction() + tx.nonce = user.nonce + tx.sender = user.address.bech32() + tx.receiver = contract.address.bech32() + tx.gasPrice = network_provider.network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = network_provider.network.chain_id + tx.version = network_provider.network.min_tx_version + tx.sign(user) + try: + txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) + print_transaction_hash(txHash, network_provider.proxy.url) + user.nonce += 1 + return txHash + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + return "" + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): + """Expecting as args:percent + type[str]: owner address - only from v2 + type[str]: admin address - only from v2 + self.version has to be initialized to correctly attempt the deploy for that specific type of farm. + """ + print_warning("Deploy staking contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=False, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + address = "" + tx_hash = "" + + arguments = [ + "0x" + self.farming_token.encode("ascii").hex(), + "0xE8D4A51000", + "0x" + "0" + f"{self.max_apr:x}" if len(f"{self.max_apr:x}") % 2 else "0x" + f"{self.max_apr:x}", + "0x" + "0" + f"{self.unbond_epochs:x}" if len(f"{self.unbond_epochs:x}") % 2 else "0x" + f"{self.unbond_epochs:x}", + ] + if self.version == StakingContractVersion.V2: + arguments.extend(args) + + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = [], + yes: bool = True): + """Expecting as args:percent + type[str]: owner address - only from v2 + type[str]: admin address - only from v2 + self.version has to be initialized to correctly attempt the deploy for that specific type of farm. + """ + print_warning("Upgrade staking contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=False) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + tx_hash = "" + + arguments = [ + "str:" + self.farming_token, + "0xE8D4A51000", + self.max_apr, + self.unbond_epochs, + ] + if self.version == StakingContractVersion.V2: + arguments.extend(args) + + if not get_continue_confirmation(yes): return tx_hash + + contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) + tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send upgrade transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash + + return tx_hash + + def register_farm_token(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[str]: token display name + type[str]: token ticker + """ + print_warning("Register stake token") + + network_config = proxy.get_network_config() + tx_hash = "" + + if len(args) != 2: + print_test_step_fail(f"FAIL: Failed to register stake token. Args list not as expected.") + return tx_hash + + gas_limit = 100000000 + sc_args = [ + "0x" + args[0].encode("ascii").hex(), + "0x" + args[1].encode("ascii").hex(), + "0x12" + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "registerFarmToken", sc_args, value="50000000000000000") + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_local_roles_farm_token(self, deployer: Account, proxy: ElrondProxy): + print_warning("Set local roles for stake token") + + network_config = proxy.get_network_config() + gas_limit = 100000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setLocalRolesFarmToken", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def set_rewards_per_block(self, deployer: Account, proxy: ElrondProxy, rewards_amount: int): + print_warning("Set rewards per block in stake contract") + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [ + "0x" + "0" + f"{rewards_amount:x}" if len(f"{rewards_amount:x}") % 2 else "0x" + f"{rewards_amount:x}", + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "setPerBlockRewardAmount", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def topup_rewards(self, deployer: Account, proxy: ElrondProxy, rewards_amount: int): + print_warning("Topup rewards in stake contract") + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [ + "0x" + self.farmed_token.encode("ascii").hex(), + "0x" + "0" + f"{rewards_amount:x}" if len(f"{rewards_amount:x}") % 2 else "0x" + f"{rewards_amount:x}", + "0x" + "topUpRewards".encode("ascii").hex(), + ] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "ESDTTransfer", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def resume(self, deployer: Account, proxy: ElrondProxy): + print_warning("Resume stake contract") + + network_config = proxy.get_network_config() + gas_limit = 30000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "resume", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def pause(self, deployer: Account, proxy: ElrondProxy): + print_warning("Pause stake contract") + + network_config = proxy.get_network_config() + gas_limit = 30000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "pause", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def start_produce_rewards(self, deployer: Account, proxy: ElrondProxy): + print_warning("Start producing rewards in stake contract") + + network_config = proxy.get_network_config() + gas_limit = 10000000 + sc_args = [] + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + "startProduceRewards", sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def whitelist_contract(self, deployer: Account, proxy: ElrondProxy, contract_to_whitelist: str): + print_warning("Whitelist contract in staking") + + network_config = proxy.get_network_config() + gas_limit = 50000000 + sc_args = [ + "0x" + Address(contract_to_whitelist).hex() + ] + + endpoint_name = "addAddressToWhitelist" if self.version == StakingContractVersion.V1 \ + else "addSCAddressToWhitelist" + tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, + endpoint_name, sc_args) + tx_hash = send_contract_call_tx(tx, proxy) + deployer.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + _ = self.start_produce_rewards(deployer, proxy) + _ = self.resume(deployer, proxy) + + def print_contract_info(self): + print_test_step_pass(f"Deployed staking contract: {self.address}") + print_test_substep(f"Staking token: {self.farming_token}") + print_test_substep(f"Stake token: {self.farm_token}") diff --git a/contracts/unstaker_contract.py b/contracts/unstaker_contract.py new file mode 100644 index 0000000..c6c5533 --- /dev/null +++ b/contracts/unstaker_contract.py @@ -0,0 +1,110 @@ +import sys +import traceback + +from arrows.stress.contracts.contract import load_code_as_hex +from contracts.contract_identities import DEXContractInterface +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, \ + multi_esdt_endpoint_call, endpoint_call +from utils.utils_chain import print_warning, print_transaction_hash, print_test_step_fail, \ + print_test_step_pass, print_test_substep +from erdpy.accounts import Account, Address +from erdpy.contracts import CodeMetadata, SmartContract +from erdpy.proxy import ElrondProxy + + +class UnstakerContract(DEXContractInterface): + def __init__(self, address: str = ""): + self.address = address + + def get_config_dict(self) -> dict: + output_dict = { + "address": self.address + } + return output_dict + + @classmethod + def load_config_dict(cls, config_dict: dict): + return UnstakerContract(address=config_dict['address']) + + def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + """Expecting as args: + type[int]: unbond epochs + type[str]: energy factory address + type[int]: fees burn percentage + type[str]: fees collector address + """ + print_warning("Deploy token unstake contract") + + metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) + bytecode: str = load_code_as_hex(bytecode_path) + network_config = proxy.get_network_config() + gas_limit = 200000000 + value = 0 + address = "" + tx_hash = "" + + if len(args) != 4: + print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") + return tx_hash, address + + arguments = args + # lock_fee_pairs = list(zip(args[4], args[5])) + # lock_options = [item for sublist in lock_fee_pairs for item in sublist] + # arguments.extend(lock_options) # lock_options + print(f"Arguments: {arguments}") + contract = SmartContract(bytecode=bytecode, metadata=metadata) + tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, + network_config.chain_id, network_config.min_tx_version) + + try: + response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) + tx_hash = response.get_hash() + print_transaction_hash(tx_hash, proxy.url, True) + + address = contract.address.bech32() + deployer.nonce += 1 + + except Exception as ex: + print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return tx_hash, address + + return tx_hash, address + + def set_energy_factory_address(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + type[address]: energy factory address + """ + function_purpose = "set energy factory address" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose} in token unstake contract. " + f"Args list not as expected.") + return "" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), + "setEnergyFactoryAddress", args) + + def claim_unlocked_tokens(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + empty + """ + function_purpose = "claim unlocked tokens" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 20000000, deployer, Address(self.address), + "claimUnlockedTokens", []) + + def cancel_unbond(self, deployer: Account, proxy: ElrondProxy, args: list): + """ Expected as args: + empty + """ + function_purpose = "cancel unbond" + print(f"Arguments: {args}") + return endpoint_call(function_purpose, proxy, 20000000, deployer, Address(self.address), + "cancelUnbond", args) + + def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = None): + pass + + def print_contract_info(self): + print_test_step_pass(f"Deployed token unstake contract: {self.address}") diff --git a/deploy/configs-devnet/deploy_structure.json b/deploy/configs-devnet/deploy_structure.json new file mode 100644 index 0000000..36a796a --- /dev/null +++ b/deploy/configs-devnet/deploy_structure.json @@ -0,0 +1,202 @@ +{ + "token": + { + "token_prefix": "E2ETK7", + "number_of_tokens": 3 + }, + + "locked_assets": [ + { + "unlocked_asset": 0, + "locked_asset": "LKMEX", + "locked_asset_name": "LockedMEX" + } + ], + + "simple_locks": [ + + ], + + "simple_locks_energy": [ + { + "base_token": 0, + "locked_token": "ELKMEX", + "locked_token_name": "EnergyLockedMEX", + "locked_asset_factory": 0, + "min_migrated_lock_epochs": 1, + "lock_options": [ + 360, 720, 1440 + ], + "penalties": [ + 5000, 7000, 8000 + ] + } + ], + + "fees_collectors": [ + { + "energy_factory": 0 + } + ], + + "unstakers": [ + { + "energy_factory": 0, + "fees_collector": 0, + "unbond_epochs": 1, + "fees_burn_percentage": 5000 + } + ], + + "proxies": [ + { + "locked_asset": 0, + "proxy_lp": "LKLP", + "proxy_lp_name": "LockedLP", + "proxy_farm": "LKFARM", + "proxy_farm_name": "LockedLPStaked" + } + ], + + "proxies_v2": [ + { + "locked_asset": 0, + "energy_factory": 0, + "proxy_lp": "ELKLP", + "proxy_lp_name": "EnergyLockedLP", + "proxy_farm": "ELKFARM", + "proxy_farm_name": "EnergyLockedLPStaked" + } + ], + + "router": [ + + ], + + "router_v2": [ + { + "empty": 0 + } + ], + + "pairs": [ + + ], + + "pairs_v2": [ + { + "launched_token": 1, + "accepted_token": 0, + "lp_token": "EGLDMEX", + "lp_token_name": "EGLDMEXLP", + "fee_token": 0, + "total_fee": 300, + "special_fee": 100, + "fees_collector": 0, + "fees_collector_cut": 500, + "proxy_v2": 0 + }, + { + "launched_token": 2, + "accepted_token": 1, + "lp_token": "WEBEGLD", + "lp_token_name": "WEBEGLDLP", + "fee_token": 1, + "total_fee": 300, + "special_fee": 100, + "fees_collector": 0, + "fees_collector_cut": 500 + } + ], + + "farms_community": [ + + ], + + "farms_unlocked": [ + + ], + + "farms_locked": [ + + ], + + "proxy_deployers": [ + { + "template": "farms_boosted" + } + ], + + "farms_boosted": [ + { + "deployer": 0, + "farming_pool": 0, + "farm_token": "EGLDMEXFL", + "farm_token_name": "EGLDMEXLPStakedLK", + "farmed_token": 0, + "rpb": 1000000000000000000, + "penalty": 100, + "lock_factory": 0, + "lock_epochs": 1440, + "boosted_rewards": 6000, + "base_const": 10, + "energy_const": 5, + "farm_const": 1, + "min_energy": 1, + "min_farm": 1, + "proxy_v2": 0 + }, + { + "deployer": 0, + "farming_pool": 1, + "farm_token": "WEBEGLDFL", + "farm_token_name": "WEBEGLDLPStakedLK", + "farmed_token": 0, + "rpb": 1000000000000000000, + "penalty": 100, + "lock_factory": 0, + "lock_epochs": 1440, + "boosted_rewards": 6000, + "base_const": 10, + "energy_const": 5, + "farm_const": 1, + "min_energy": 1, + "min_farm": 1 + } + ], + + "price_discoveries": [ + + ], + + "stakings": [ + + ], + + "stakings_v2": [ + { + "staking_token": 2, + "stake_token": "SWEB", + "stake_token_name": "StakedWEB", + "apr": 2500, + "rpb": 4138000000000000000, + "unbond_epochs": 10, + "rewards": 10000000000000000000000000 + } + ], + + "metastakings": [ + + ], + + "metastakings_v2": [ + { + "token": 2, + "pool_v2": 1, + "farm_boosted": 1, + "staking_v2": 0, + "metastake_token": "METAWEBLK", + "metastake_token_name": "MetaStakedWebLK" + } + ] +} \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_farms_boosted.json b/deploy/configs-devnet/deployed_farms_boosted.json new file mode 100644 index 0000000..e30a876 --- /dev/null +++ b/deploy/configs-devnet/deployed_farms_boosted.json @@ -0,0 +1,16 @@ +[ + { + "farmingToken": "EGLDMEX-c29b0e", + "farmToken": "EGLDMEXFL-9fa39d", + "farmedToken": "MEX-dc289c", + "address": "erd1qqqqqqqqqqqqqpgq59hlx9lmclzngxfnxwjpzru3ukm3hfvj0n4sh8pkmp", + "version": 4 + }, + { + "farmingToken": "WEBEGLD-270db7", + "farmToken": "WEBEGLDFL-6094e8", + "farmedToken": "MEX-dc289c", + "address": "erd1qqqqqqqqqqqqqpgq835vpevjysz042t3wnp6q5a7skxaerm90n4sg85ekq", + "version": 4 + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_fees_collectors.json b/deploy/configs-devnet/deployed_fees_collectors.json new file mode 100644 index 0000000..8c05448 --- /dev/null +++ b/deploy/configs-devnet/deployed_fees_collectors.json @@ -0,0 +1,5 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgq82pd37ra5vqnsaq5cc50ll073gzm4ahx0n4s793d9d" + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_locked_assets.json b/deploy/configs-devnet/deployed_locked_assets.json new file mode 100644 index 0000000..8568351 --- /dev/null +++ b/deploy/configs-devnet/deployed_locked_assets.json @@ -0,0 +1,7 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqnqf6qpnd7y3m6wpkur9u8hg37rvhn5ae0n4se7lw39", + "unlocked_asset": "MEX-dc289c", + "locked_asset": "LKMEX-3b7d9a" + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_metastakings_v2.json b/deploy/configs-devnet/deployed_metastakings_v2.json new file mode 100644 index 0000000..fe3dbc5 --- /dev/null +++ b/deploy/configs-devnet/deployed_metastakings_v2.json @@ -0,0 +1,13 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqvh3wazsrlla547padvzt5l0pnasq5cs30n4stnc0et", + "metastake_token": "METAWEBLK-bfa02c", + "staking_token": "WEB-5d08be", + "lp_token": "WEBEGLD-270db7", + "farm_token": "WEBEGLDFL-6094e8", + "stake_token": "SWEB-d9bfe2", + "lp_address": "erd1qqqqqqqqqqqqqpgqzn90c8pp6yd97tqvnadeg8gfl7eutmyk0n4s90ylnz", + "farm_address": "erd1qqqqqqqqqqqqqpgq835vpevjysz042t3wnp6q5a7skxaerm90n4sg85ekq", + "stake_address": "erd1qqqqqqqqqqqqqpgqxpshvxgesuntsq8fk0735s372w2jmmcy0n4s3q9qgr" + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_pairs_v2.json b/deploy/configs-devnet/deployed_pairs_v2.json new file mode 100644 index 0000000..c1fe2c7 --- /dev/null +++ b/deploy/configs-devnet/deployed_pairs_v2.json @@ -0,0 +1,16 @@ +[ + { + "firstToken": "WEGLD-d7c6bb", + "secondToken": "MEX-dc289c", + "lpToken": "EGLDMEX-c29b0e", + "address": "erd1qqqqqqqqqqqqqpgquu5rsa4ee6l4azz6vdu4hjp8z4p6tt8m0n4suht3dy", + "version": 2 + }, + { + "firstToken": "WEB-5d08be", + "secondToken": "WEGLD-d7c6bb", + "lpToken": "WEBEGLD-270db7", + "address": "erd1qqqqqqqqqqqqqpgqzn90c8pp6yd97tqvnadeg8gfl7eutmyk0n4s90ylnz", + "version": 2 + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_proxies.json b/deploy/configs-devnet/deployed_proxies.json new file mode 100644 index 0000000..e646627 --- /dev/null +++ b/deploy/configs-devnet/deployed_proxies.json @@ -0,0 +1,12 @@ +[ + { + "token": "MEX-dc289c", + "locked_tokens": [ + "LKMEX-3b7d9a" + ], + "proxy_farm_token": "LKFARM-b6bd35", + "proxy_lp_token": "LKLP-5a4c28", + "address": "erd1qqqqqqqqqqqqqpgqte2prd032ffa43nyhzae4pdsws62hcnv0n4s242vf5", + "version": 1 + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_proxies_v2.json b/deploy/configs-devnet/deployed_proxies_v2.json new file mode 100644 index 0000000..3705524 --- /dev/null +++ b/deploy/configs-devnet/deployed_proxies_v2.json @@ -0,0 +1,13 @@ +[ + { + "token": "MEX-dc289c", + "locked_tokens": [ + "LKMEX-3b7d9a", + "ELKMEX-cb6a3e" + ], + "proxy_farm_token": "ELKFARM-693c1c", + "proxy_lp_token": "ELKLP-217e80", + "address": "erd1qqqqqqqqqqqqqpgqp33v9y29x384573f8pucemynempfa5uu0n4sg5sfad", + "version": 2 + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_proxy_deployers.json b/deploy/configs-devnet/deployed_proxy_deployers.json new file mode 100644 index 0000000..7a93f57 --- /dev/null +++ b/deploy/configs-devnet/deployed_proxy_deployers.json @@ -0,0 +1,6 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqtzyaqwx5un8qazjqgja0xlkhxh3fvag60n4sa0qcxv", + "template": "farms_boosted" + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_router_v2.json b/deploy/configs-devnet/deployed_router_v2.json new file mode 100644 index 0000000..f5e6814 --- /dev/null +++ b/deploy/configs-devnet/deployed_router_v2.json @@ -0,0 +1,6 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqg2esr6d6tfd250x4n3tkhfkw8cc4p2x50n4swatdz6", + "version": 2 + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_simple_locks_energy.json b/deploy/configs-devnet/deployed_simple_locks_energy.json new file mode 100644 index 0000000..574cef8 --- /dev/null +++ b/deploy/configs-devnet/deployed_simple_locks_energy.json @@ -0,0 +1,9 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqp6qrf7yp4l25c08384vgdghz7wz0f60h0n4s0m88l4", + "base_token": "MEX-dc289c", + "locked_token": "ELKMEX-cb6a3e", + "lp_proxy_token": "", + "farm_proxy_token": "" + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_stakings_v2.json b/deploy/configs-devnet/deployed_stakings_v2.json new file mode 100644 index 0000000..7b4420a --- /dev/null +++ b/deploy/configs-devnet/deployed_stakings_v2.json @@ -0,0 +1,11 @@ +[ + { + "farming_token": "WEB-5d08be", + "farm_token": "SWEB-d9bfe2", + "address": "erd1qqqqqqqqqqqqqpgqxpshvxgesuntsq8fk0735s372w2jmmcy0n4s3q9qgr", + "max_apr": 2500, + "rewards_per_block": 4138000000000000000, + "unbond_epochs": 10, + "version": 2 + } +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_tokens.json b/deploy/configs-devnet/deployed_tokens.json new file mode 100644 index 0000000..1bfff46 --- /dev/null +++ b/deploy/configs-devnet/deployed_tokens.json @@ -0,0 +1,5 @@ +[ + "MEX-dc289c", + "WEGLD-d7c6bb", + "WEB-5d08be" +] \ No newline at end of file diff --git a/deploy/configs-devnet/deployed_unstakers.json b/deploy/configs-devnet/deployed_unstakers.json new file mode 100644 index 0000000..64c2df5 --- /dev/null +++ b/deploy/configs-devnet/deployed_unstakers.json @@ -0,0 +1,5 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqnysvq99c2t4a9pvvv22elnl6p73el8vw0n4spyfv7p" + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deploy_structure.json b/deploy/configs-mainnet/deploy_structure.json new file mode 100644 index 0000000..be60f48 --- /dev/null +++ b/deploy/configs-mainnet/deploy_structure.json @@ -0,0 +1,420 @@ +{ + "token": + { + "token_prefix": "E2ETK7", + "number_of_tokens": 10 + }, + + "locked_assets": [ + { + "unlocked_asset": 0, + "locked_asset": "LKMEX", + "locked_asset_name": "LockedMEX" + } + ], + + "simple_locks": [ + + ], + + "simple_locks_energy": [ + { + "base_token": 0, + "locked_token": "XMEX", + "locked_token_name": "xMEX", + "locked_asset_factory": 0, + "min_migrated_lock_epochs": 0, + "lock_options": [ + 360, 720, 1440 + ], + "penalties": [ + 5000, 7000, 8000 + ] + } + ], + + "fees_collectors": [ + { + "energy_factory": 0, + "lock_epochs": 1440, + "locked_tokens_per_block": 17500000000000000000000 + } + ], + + "unstakers": [ + { + "energy_factory": 0, + "fees_collector": 0, + "unbond_epochs": 10, + "fees_burn_percentage": 5000 + } + ], + + "proxies": [ + { + "locked_asset": 0, + "proxy_lp": "LKLP", + "proxy_lp_name": "LockedLP", + "proxy_farm": "LKFARM", + "proxy_farm_name": "LockedLPStaked" + } + ], + + "proxies_v2": [ + { + "locked_asset": 0, + "energy_factory": 0, + "proxy_lp": "XMEXLP", + "proxy_lp_name": "xMEXLP", + "proxy_farm": "XMEXFARM", + "proxy_farm_name": "xMEXLPStaked" + } + ], + + "router": [ + + ], + + "router_v2": [ + { + "empty": 0 + } + ], + + "pairs": [ + + ], + + "pairs_v2": [ + { + "launched_token": 1, + "accepted_token": 0, + "lp_token": "EGLDMEX", + "lp_token_name": "EGLDMEXLP", + "fee_token": 0, + "total_fee": 300, + "special_fee": 100, + "fees_collector": 0, + "fees_collector_cut": 500, + "proxy_v2": 0 + }, + { + "launched_token": 1, + "accepted_token": 2, + "lp_token": "EGLDUSDC", + "lp_token_name": "EGLDUSDCLP", + "fee_token": 1, + "total_fee": 300, + "special_fee": 100, + "fees_collector": 0, + "fees_collector_cut": 500 + }, + { + "launched_token": 1, + "accepted_token": 3, + "lp_token": "EGLDRIDE", + "lp_token_name": "EGLDRIDELP", + "fee_token": 1, + "total_fee": 300, + "special_fee": 100, + "fees_collector": 0, + "fees_collector_cut": 500 + }, + { + "launched_token": 4, + "accepted_token": 1, + "lp_token": "ITHWEGLD", + "lp_token_name": "ITHWEGLDLP", + "fee_token": 1, + "total_fee": 300, + "special_fee": 100, + "fees_collector": 0, + "fees_collector_cut": 500 + }, + { + "launched_token": 5, + "accepted_token": 1, + "lp_token": "UTKWEGLD", + "lp_token_name": "UTKWEGLDLP", + "fee_token": 1, + "total_fee": 300, + "special_fee": 100, + "fees_collector": 0, + "fees_collector_cut": 500 + }, + { + "launched_token": 6, + "accepted_token": 1, + "lp_token": "CRTWEGLD", + "lp_token_name": "CRTWEGLDLP", + "fee_token": 1, + "total_fee": 300, + "special_fee": 100, + "fees_collector": 0, + "fees_collector_cut": 500 + }, + { + "launched_token": 7, + "accepted_token": 1, + "lp_token": "ASHWEGLD", + "lp_token_name": "ASHWEGLDLP", + "fee_token": 1, + "total_fee": 300, + "special_fee": 100, + "fees_collector": 0, + "fees_collector_cut": 500 + } + ], + + "farms_community": [ + + ], + + "farms_unlocked": [ + + ], + + "farms_locked": [ + + ], + + "proxy_deployers": [ + + ], + + "farms_boosted": [ + { + "farming_pool": 0, + "farm_token": "EGLDMEXFL", + "farm_token_name": "EGLDMEXLPStakedLK", + "farmed_token": 0, + "rpb": 340000000000000000000000, + "penalty": 300, + "min_farming_epochs": 7, + "lock_factory": 0, + "lock_epochs": 1440, + "boosted_rewards": 6000, + "base_const": 2, + "energy_const": 1, + "farm_const": 0, + "min_energy": 1, + "min_farm": 1, + "proxy_v2": 0 + }, + { + "farming_pool": 1, + "farm_token": "EGLDUSDCFL", + "farm_token_name": "EGLDUSDCLPStakedLK", + "farmed_token": 0, + "rpb": 50400000000000000000000, + "penalty": 300, + "min_farming_epochs": 7, + "lock_factory": 0, + "lock_epochs": 1440, + "boosted_rewards": 6000, + "base_const": 2, + "energy_const": 1, + "farm_const": 0, + "min_energy": 1, + "min_farm": 1 + }, + { + "farming_pool": 2, + "farm_token": "EGLDRIDEFL", + "farm_token_name": "EGLDRIDELPStakedLK", + "farmed_token": 0, + "rpb": 31600000000000000000000, + "penalty": 300, + "min_farming_epochs": 7, + "lock_factory": 0, + "lock_epochs": 1440, + "boosted_rewards": 6000, + "base_const": 2, + "energy_const": 1, + "farm_const": 0, + "min_energy": 1, + "min_farm": 1 + }, + { + "farming_pool": 3, + "farm_token": "ITHWEGLDFL", + "farm_token_name": "ITHWEGLDLPStakedLK", + "farmed_token": 0, + "rpb": 21200000000000000000000, + "penalty": 300, + "min_farming_epochs": 7, + "lock_factory": 0, + "lock_epochs": 1440, + "boosted_rewards": 6000, + "base_const": 2, + "energy_const": 1, + "farm_const": 0, + "min_energy": 1, + "min_farm": 1 + }, + { + "farming_pool": 4, + "farm_token": "UTKWEGLDFL", + "farm_token_name": "UTKWEGLDLPStakedLK", + "farmed_token": 0, + "rpb": 21200000000000000000000, + "penalty": 300, + "min_farming_epochs": 7, + "lock_factory": 0, + "lock_epochs": 1440, + "boosted_rewards": 6000, + "base_const": 2, + "energy_const": 1, + "farm_const": 0, + "min_energy": 1, + "min_farm": 1 + }, + { + "farming_pool": 5, + "farm_token": "CRTWEGLDFL", + "farm_token_name": "CRTWEGLDLPStakedLK", + "farmed_token": 0, + "rpb": 15100000000000000000000, + "penalty": 300, + "min_farming_epochs": 7, + "lock_factory": 0, + "lock_epochs": 1440, + "boosted_rewards": 6000, + "base_const": 2, + "energy_const": 1, + "farm_const": 0, + "min_energy": 1, + "min_farm": 1 + }, + { + "farming_pool": 6, + "farm_token": "ASHWEGLDFL", + "farm_token_name": "ASHWEGLDLPStakedLK", + "farmed_token": 0, + "rpb": 21100000000000000000000, + "penalty": 300, + "min_farming_epochs": 7, + "lock_factory": 0, + "lock_epochs": 1440, + "boosted_rewards": 6000, + "base_const": 2, + "energy_const": 1, + "farm_const": 0, + "min_energy": 1, + "min_farm": 1 + } + ], + + "price_discoveries": [ + + ], + + "stakings": [ + + ], + + "stakings_v2": [ + { + "staking_token": 3, + "stake_token": "SRIDE", + "stake_token_name": "SRIDE", + "apr": 2500, + "rpb": 4138000000000000000, + "unbond_epochs": 10, + "rewards": 10000000000000000000000000 + }, + { + "staking_token": 8, + "stake_token": "SZPAY", + "stake_token_name": "SZPAY", + "apr": 2500, + "rpb": 4138000000000000000, + "unbond_epochs": 10, + "rewards": 10000000000000000000000000 + }, + { + "staking_token": 4, + "stake_token": "SITHEUM", + "stake_token_name": "SITHEUM", + "apr": 2500, + "rpb": 4138000000000000000, + "unbond_epochs": 10, + "rewards": 10000000000000000000000000 + }, + { + "staking_token": 9, + "stake_token": "SBHAT", + "stake_token_name": "SBHAT", + "apr": 2500, + "rpb": 4138000000000000000, + "unbond_epochs": 10, + "rewards": 10000000000000000000000000 + }, + { + "staking_token": 5, + "stake_token": "SUTK", + "stake_token_name": "SUTK", + "apr": 2500, + "rpb": 4138000000000000000, + "unbond_epochs": 10, + "rewards": 10000000000000000000000000 + }, + { + "staking_token": 6, + "stake_token": "SCRT", + "stake_token_name": "SCRT", + "apr": 2500, + "rpb": 4138000000000000000, + "unbond_epochs": 10, + "rewards": 10000000000000000000000000 + }, + { + "staking_token": 7, + "stake_token": "SASH", + "stake_token_name": "SASH", + "apr": 2500, + "rpb": 4138000000000000000, + "unbond_epochs": 10, + "rewards": 10000000000000000000000000 + } + ], + + "metastakings": [ + + ], + + "metastakings_v2": [ + { + "token": 3, + "pool_v2": 2, + "farm_boosted": 2, + "staking_v2": 0, + "metastake_token": "METARIDELK", + "metastake_token_name": "MetaStakedRideLK" + }, + { + "token": 4, + "pool_v2": 3, + "farm_boosted": 3, + "staking_v2": 2, + "metastake_token": "METAITHLK", + "metastake_token_name": "MetaStakedItheumLK" + }, + { + "token": 5, + "pool_v2": 4, + "farm_boosted": 4, + "staking_v2": 4, + "metastake_token": "METAUTKLK", + "metastake_token_name": "MetaStakedUtrustLK" + }, + { + "token": 6, + "pool_v2": 5, + "farm_boosted": 5, + "staking_v2": 5, + "metastake_token": "METACRTLK", + "metastake_token_name": "MetaStakedCRTLK" + } + ] +} \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_farms_boosted.json b/deploy/configs-mainnet/deployed_farms_boosted.json new file mode 100644 index 0000000..517df65 --- /dev/null +++ b/deploy/configs-mainnet/deployed_farms_boosted.json @@ -0,0 +1,51 @@ +[ + { + "farmingToken": "EGLDMEX-0be9e5", + "farmToken": "EGLDMEXFL-c2521e", + "farmedToken": "MEX-455c57", + "address": "erd1qqqqqqqqqqqqqpgqapxdp9gjxtg60mjwhle3n6h88zch9e7kkp2s8aqhkg", + "version": 4 + }, + { + "farmingToken": "EGLDUSDC-594e5e", + "farmToken": "EGLDUSDCFL-45109b", + "farmedToken": "MEX-455c57", + "address": "erd1qqqqqqqqqqqqqpgqv0pz5z3fkz54nml6pkzzdruuf020gqzykp2sya7kkv", + "version": 4 + }, + { + "farmingToken": "EGLDRIDE-7bd51a", + "farmToken": "EGLDRIDEFL-7aac94", + "farmedToken": "MEX-455c57", + "address": "erd1qqqqqqqqqqqqqpgqenvn0s3ldc94q2mlkaqx4arj3zfnvnmakp2sxca2h9", + "version": 4 + }, + { + "farmingToken": "ITHWEGLD-1adc53", + "farmToken": "ITHWEGLDFL-7d381f", + "farmedToken": "MEX-455c57", + "address": "erd1qqqqqqqqqqqqqpgqrdq6zvepdxg36rey8pmluwur43q4hcx3kp2su4yltq", + "version": 4 + }, + { + "farmingToken": "UTKWEGLD-c960d1", + "farmToken": "UTKWEGLDFL-ba26d2", + "farmedToken": "MEX-455c57", + "address": "erd1qqqqqqqqqqqqqpgq4acurmluezvmhna8tztgcrnwh0l70a2wkp2sfh6jkp", + "version": 4 + }, + { + "farmingToken": "CRTWEGLD-1fac3f", + "farmToken": "CRTWEGLDFL-4fd518", + "farmedToken": "MEX-455c57", + "address": "erd1qqqqqqqqqqqqqpgquhqfknr9dg5t0xma0zcc55dm8gwv5rpkkp2sq8f40p", + "version": 4 + }, + { + "farmingToken": "ASHWEGLD-38545c", + "farmToken": "ASHWEGLDFL-9612cf", + "farmedToken": "MEX-455c57", + "address": "erd1qqqqqqqqqqqqqpgq6v5ta4memvrhjs4x3gqn90c3pujc77takp2sqhxm9j", + "version": 4 + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_fees_collectors.json b/deploy/configs-mainnet/deployed_fees_collectors.json new file mode 100644 index 0000000..c824c8d --- /dev/null +++ b/deploy/configs-mainnet/deployed_fees_collectors.json @@ -0,0 +1,5 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqjsnxqprks7qxfwkcg2m2v9hxkrchgm9akp2segrswt" + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_locked_assets.json b/deploy/configs-mainnet/deployed_locked_assets.json new file mode 100644 index 0000000..39dc915 --- /dev/null +++ b/deploy/configs-mainnet/deployed_locked_assets.json @@ -0,0 +1,7 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqjpt0qqgsrdhp2xqygpjtfrpwf76f9nvg2jpsg4q7th", + "unlocked_asset": "MEX-455c57", + "locked_asset": "LKMEX-aab910" + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_metastakings_v2.json b/deploy/configs-mainnet/deployed_metastakings_v2.json new file mode 100644 index 0000000..317349c --- /dev/null +++ b/deploy/configs-mainnet/deployed_metastakings_v2.json @@ -0,0 +1,46 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqjvcrk4tun6l438crr0jcz7q3yzp66smwkp2shzmazs", + "metastake_token": "METARIDELK-f0b4bf", + "staking_token": "RIDE-7d18e9", + "lp_token": "EGLDRIDE-7bd51a", + "farm_token": "EGLDRIDEFL-7aac94", + "stake_token": "SRIDE-4ab1d4", + "lp_address": "erd1qqqqqqqqqqqqqpgqav09xenkuqsdyeyy5evqyhuusvu4gl3t2jpss57g8x", + "farm_address": "erd1qqqqqqqqqqqqqpgqenvn0s3ldc94q2mlkaqx4arj3zfnvnmakp2sxca2h9", + "stake_address": "erd1qqqqqqqqqqqqqpgqmqq78c5htmdnws8hm5u4suvags36eq092jpsaxv3e7" + }, + { + "address": "erd1qqqqqqqqqqqqqpgqduftj7u5u5d4ql2znchcmrcet9kcze7ckp2sk4474f", + "metastake_token": "METAITHLK-4c3f2e", + "staking_token": "ITHEUM-df6f26", + "lp_token": "ITHWEGLD-1adc53", + "farm_token": "ITHWEGLDFL-7d381f", + "stake_token": "SITHEUM-e05083", + "lp_address": "erd1qqqqqqqqqqqqqpgqpmud7t8uprrxzgu8eq2mtkl08kesflj62jps9j8dyh", + "farm_address": "erd1qqqqqqqqqqqqqpgqrdq6zvepdxg36rey8pmluwur43q4hcx3kp2su4yltq", + "stake_address": "erd1qqqqqqqqqqqqqpgqzps75vsk97w9nsx2cenv2r2tyxl4fl402jpsx78m9j" + }, + { + "address": "erd1qqqqqqqqqqqqqpgqmgd0eu4z9kzvrputt4l4kw4fqf2wcjsekp2sftan7s", + "metastake_token": "METAUTKLK-112f52", + "staking_token": "UTK-2f80e9", + "lp_token": "UTKWEGLD-c960d1", + "farm_token": "UTKWEGLDFL-ba26d2", + "stake_token": "SUTK-ba35f3", + "lp_address": "erd1qqqqqqqqqqqqqpgq0lzzvt2faev4upyf586tg38s84d7zsaj2jpsglugga", + "farm_address": "erd1qqqqqqqqqqqqqpgq4acurmluezvmhna8tztgcrnwh0l70a2wkp2sfh6jkp", + "stake_address": "erd1qqqqqqqqqqqqqpgqcedkmj8ezme6mtautj79ngv7fez978le2jps8jtawn" + }, + { + "address": "erd1qqqqqqqqqqqqqpgqx6zhjfjnwxpzdcj594htl0y6w60aa3a8kp2se2pdms", + "metastake_token": "METACRTLK-39d11c", + "staking_token": "CRT-52decf", + "lp_token": "CRTWEGLD-1fac3f", + "farm_token": "CRTWEGLDFL-4fd518", + "stake_token": "SCRT-acbd64", + "lp_address": "erd1qqqqqqqqqqqqqpgqf57y8m9krsvrceqxujngzm77p82zqc502jpsnnezqs", + "farm_address": "erd1qqqqqqqqqqqqqpgquhqfknr9dg5t0xma0zcc55dm8gwv5rpkkp2sq8f40p", + "stake_address": "erd1qqqqqqqqqqqqqpgqp2wfzvkhlpkwcdxx25qzznx33345979w2jpsl3gflj" + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_pairs_v2.json b/deploy/configs-mainnet/deployed_pairs_v2.json new file mode 100644 index 0000000..bd7f3d3 --- /dev/null +++ b/deploy/configs-mainnet/deployed_pairs_v2.json @@ -0,0 +1,51 @@ +[ + { + "firstToken": "WEGLD-bd4d79", + "secondToken": "MEX-455c57", + "lpToken": "EGLDMEX-0be9e5", + "address": "erd1qqqqqqqqqqqqqpgqa0fsfshnff4n76jhcye6k7uvd7qacsq42jpsp6shh2", + "version": 2 + }, + { + "firstToken": "WEGLD-bd4d79", + "secondToken": "USDC-c76f1f", + "lpToken": "EGLDUSDC-594e5e", + "address": "erd1qqqqqqqqqqqqqpgqeel2kumf0r8ffyhth7pqdujjat9nx0862jpsg2pqaq", + "version": 2 + }, + { + "firstToken": "WEGLD-bd4d79", + "secondToken": "RIDE-7d18e9", + "lpToken": "EGLDRIDE-7bd51a", + "address": "erd1qqqqqqqqqqqqqpgqav09xenkuqsdyeyy5evqyhuusvu4gl3t2jpss57g8x", + "version": 2 + }, + { + "firstToken": "ITHEUM-df6f26", + "secondToken": "WEGLD-bd4d79", + "lpToken": "ITHWEGLD-1adc53", + "address": "erd1qqqqqqqqqqqqqpgqpmud7t8uprrxzgu8eq2mtkl08kesflj62jps9j8dyh", + "version": 2 + }, + { + "firstToken": "UTK-2f80e9", + "secondToken": "WEGLD-bd4d79", + "lpToken": "UTKWEGLD-c960d1", + "address": "erd1qqqqqqqqqqqqqpgq0lzzvt2faev4upyf586tg38s84d7zsaj2jpsglugga", + "version": 2 + }, + { + "firstToken": "CRT-52decf", + "secondToken": "WEGLD-bd4d79", + "lpToken": "CRTWEGLD-1fac3f", + "address": "erd1qqqqqqqqqqqqqpgqf57y8m9krsvrceqxujngzm77p82zqc502jpsnnezqs", + "version": 2 + }, + { + "firstToken": "ASH-a642d1", + "secondToken": "WEGLD-bd4d79", + "lpToken": "ASHWEGLD-38545c", + "address": "erd1qqqqqqqqqqqqqpgqp5d4x3d263x4alnapwafwujch5xqmvyq2jpsk2xhsy", + "version": 2 + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_proxies.json b/deploy/configs-mainnet/deployed_proxies.json new file mode 100644 index 0000000..ff80809 --- /dev/null +++ b/deploy/configs-mainnet/deployed_proxies.json @@ -0,0 +1,12 @@ +[ + { + "token": "MEX-455c57", + "locked_tokens": [ + "LKMEX-aab910" + ], + "proxy_farm_token": "LKFARM-9d1ea8", + "proxy_lp_token": "LKLP-03a2fa", + "address": "erd1qqqqqqqqqqqqqpgqrc4pg2xarca9z34njcxeur622qmfjp8w2jps89fxnl", + "version": 2 + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_proxies_v2.json b/deploy/configs-mainnet/deployed_proxies_v2.json new file mode 100644 index 0000000..d37a326 --- /dev/null +++ b/deploy/configs-mainnet/deployed_proxies_v2.json @@ -0,0 +1,13 @@ +[ + { + "token": "MEX-455c57", + "locked_tokens": [ + "LKMEX-aab910", + "XMEX-fda355" + ], + "proxy_farm_token": "XMEXFARM-e0a3dc", + "proxy_lp_token": "XMEXLP-2cbdce", + "address": "erd1qqqqqqqqqqqqqpgqt6ltx52ukss9d2qag2k67at28a36xc9lkp2sr06394", + "version": 2 + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_router_v2.json b/deploy/configs-mainnet/deployed_router_v2.json new file mode 100644 index 0000000..66d9b0a --- /dev/null +++ b/deploy/configs-mainnet/deployed_router_v2.json @@ -0,0 +1,6 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqq66xk9gfr4esuhem3jru86wg5hvp33a62jps2fy57p", + "version": 2 + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_simple_locks_energy.json b/deploy/configs-mainnet/deployed_simple_locks_energy.json new file mode 100644 index 0000000..833d50d --- /dev/null +++ b/deploy/configs-mainnet/deployed_simple_locks_energy.json @@ -0,0 +1,9 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgq0tajepcazernwt74820t8ef7t28vjfgukp2sw239f3", + "base_token": "MEX-455c57", + "locked_token": "XMEX-fda355", + "lp_proxy_token": "", + "farm_proxy_token": "" + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_stakings_v2.json b/deploy/configs-mainnet/deployed_stakings_v2.json new file mode 100644 index 0000000..34d990e --- /dev/null +++ b/deploy/configs-mainnet/deployed_stakings_v2.json @@ -0,0 +1,65 @@ +[ + { + "farming_token": "RIDE-7d18e9", + "farm_token": "SRIDE-4ab1d4", + "address": "erd1qqqqqqqqqqqqqpgqmqq78c5htmdnws8hm5u4suvags36eq092jpsaxv3e7", + "max_apr": 2500, + "rewards_per_block": 4138000000000000000, + "unbond_epochs": 10, + "version": 2 + }, + { + "farming_token": "ZPAY-247875", + "farm_token": "SZPAY-9f1b39", + "address": "erd1qqqqqqqqqqqqqpgqr7kdhagkqgxvjrsk7s5333l9wwnenr9g2jps8puq33", + "max_apr": 2500, + "rewards_per_block": 951000000000000000, + "unbond_epochs": 10, + "version": 2 + }, + { + "farming_token": "ITHEUM-df6f26", + "farm_token": "SITHEUM-e05083", + "address": "erd1qqqqqqqqqqqqqpgqzps75vsk97w9nsx2cenv2r2tyxl4fl402jpsx78m9j", + "max_apr": 3000, + "rewards_per_block": 4851000000000000000, + "unbond_epochs": 10, + "version": 2 + }, + { + "farming_token": "BHAT-c1fde3", + "farm_token": "SBHAT-89efd3", + "address": "erd1qqqqqqqqqqqqqpgq45zs77q884ts6y9zj4jyqfn6ydev8ruv2jps3tteqq", + "max_apr": 2500, + "rewards_per_block": 7039000000000000000, + "unbond_epochs": 10, + "version": 2 + }, + { + "farming_token": "UTK-2f80e9", + "farm_token": "SUTK-ba35f3", + "address": "erd1qqqqqqqqqqqqqpgqcedkmj8ezme6mtautj79ngv7fez978le2jps8jtawn", + "max_apr": 2500, + "rewards_per_block": 13318000000000000000, + "unbond_epochs": 10, + "version": 2 + }, + { + "farming_token": "CRT-52decf", + "farm_token": "SCRT-acbd64", + "address": "erd1qqqqqqqqqqqqqpgqp2wfzvkhlpkwcdxx25qzznx33345979w2jpsl3gflj", + "max_apr": 3000, + "rewards_per_block": 4756000000000000000, + "unbond_epochs": 10, + "version": 2 + }, + { + "farming_token": "ASH-a642d1", + "farm_token": "SASH-3671bd", + "address": "erd1qqqqqqqqqqqqqpgqjdlnu9ggwfc79pygn5fjjgmdm6d7vu5e2jpsw59amp", + "max_apr": 1500, + "rewards_per_block": 1426000000000000000, + "unbond_epochs": 10, + "version": 2 + } +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_tokens.json b/deploy/configs-mainnet/deployed_tokens.json new file mode 100644 index 0000000..7596f4c --- /dev/null +++ b/deploy/configs-mainnet/deployed_tokens.json @@ -0,0 +1,12 @@ +[ + "MEX-455c57", + "WEGLD-bd4d79", + "USDC-c76f1f", + "RIDE-7d18e9", + "ITHEUM-df6f26", + "UTK-2f80e9", + "CRT-52decf", + "ASH-a642d1", + "ZPAY-247875", + "BHAT-c1fde3" +] \ No newline at end of file diff --git a/deploy/configs-mainnet/deployed_unstakers.json b/deploy/configs-mainnet/deployed_unstakers.json new file mode 100644 index 0000000..5775325 --- /dev/null +++ b/deploy/configs-mainnet/deployed_unstakers.json @@ -0,0 +1,5 @@ +[ + { + "address": "erd1qqqqqqqqqqqqqpgqxv0y4p6vvszrknaztatycac77yvsxqrrkp2sghd86c" + } +] \ No newline at end of file diff --git a/deploy/dex_deploy.py b/deploy/dex_deploy.py new file mode 100644 index 0000000..c5afe60 --- /dev/null +++ b/deploy/dex_deploy.py @@ -0,0 +1,55 @@ +import sys +from typing import List +from argparse import ArgumentParser + +from deploy.dex_structure import DeployStructure +from utils.utils_tx import NetworkProviders +from utils.utils_chain import Account +import config + + +class DexInfrastructure: + def __init__(self, deploy_structure: DeployStructure, deployer: Account, + proxy_url: str, api_url: str): + self.deploy_structure = deploy_structure + self.deployer_account = deployer + self.network_provider = NetworkProviders(api_url, proxy_url) + + self.deployer_account.sync_nonce(self.network_provider.proxy) + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--deploy-tokens", required=False, default="config") # options: clean | config + parser.add_argument("--deploy-contracts", required=False, default="config") # options: clean | config + parser.add_argument("--force-start", action='store_true', required=False, default=False) # force start all + args = parser.parse_args(cli_args) + + deploy_structure = DeployStructure() + deployer_account = Account(pem_file=config.DEFAULT_OWNER) + + dex_infra = DexInfrastructure(deploy_structure, deployer_account, config.DEFAULT_PROXY, config.DEFAULT_API) + + # TOKENS HANDLING + dex_infra.deploy_structure.deploy_tokens(dex_infra.deployer_account, dex_infra.network_provider, + False if args.deploy_tokens == "config" else True) + + # configure contracts and deploy them + # DEPLOY CONTRACTS + dex_infra.deploy_structure.deploy_structure(dex_infra.deployer_account, dex_infra.network_provider, + False if args.deploy_contracts == "config" else True) + + # CONTRACTS START + start_flag = False + if args.deploy_contracts != "config" or args.force_start: + start_flag = True + dex_infra.deploy_structure.start_deployed_contracts(dex_infra.deployer_account, dex_infra.network_provider, + start_flag) + + # program closing + # dex_infra.save_deployed_structure() + dex_infra.deploy_structure.print_deployed_contracts() + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/deploy/dex_structure.py b/deploy/dex_structure.py new file mode 100644 index 0000000..a5fd866 --- /dev/null +++ b/deploy/dex_structure.py @@ -0,0 +1,1672 @@ +import time +from pathlib import Path +from typing import List, Type, Dict, Optional + +import config +from contracts.fees_collector_contract import FeesCollectorContract +from contracts.proxy_deployer_contract import ProxyDeployerContract +from contracts.simple_lock_energy_contract import SimpleLockEnergyContract +from contracts.unstaker_contract import UnstakerContract +from deploy import sync_tokens, issue_tokens +from deploy.tokens_tracks import BunchOfTracks +from utils.contract_data_fetchers import PairContractDataFetcher, PriceDiscoveryContractDataFetcher, \ + SimpleLockContractDataFetcher, LockedAssetContractDataFetcher, FarmContractDataFetcher, StakingContractDataFetcher, \ + MetaStakingContractDataFetcher, ProxyContractDataFetcher, SimpleLockEnergyContractDataFetcher +from contracts.esdt_contract import ESDTContract +from contracts.farm_contract import FarmContract +from contracts.locked_asset_contract import LockedAssetContract +from contracts.pair_contract import PairContract +from contracts.price_discovery_contract import PriceDiscoveryContract +from contracts.router_contract import RouterContract +from contracts.simple_lock_contract import SimpleLockContract +from contracts.contract_identities import FarmContractVersion, DEXContractInterface, \ + RouterContractVersion, PairContractVersion, ProxyContractVersion, StakingContractVersion +from contracts.metastaking_contract import MetaStakingContract +from contracts.staking_contract import StakingContract +from contracts.dex_proxy_contract import DexProxyContract +from utils.utils_tx import NetworkProviders +from utils.utils_chain import print_test_step_fail, print_test_step_pass, hex_to_string, print_warning +from erdpy.accounts import Account, Address +from utils.utils_generic import write_json_file, read_json_file +from deploy import populate_deploy_lists + + +class ContractStructure: + def __init__(self, label: str, contract_class: Type[DEXContractInterface], bytecode_path: str, deploy_function, + deploy_clean: bool = True): + self.label = label + self.contract_class = contract_class + self.deploy_structure_list = populate_deploy_lists.populate_list(config.DEPLOY_STRUCTURE_JSON, label) + self.deployed_contracts: List[contract_class] = [] + self.deploy_clean = deploy_clean + self.deploy_function = deploy_function + self.bytecode = bytecode_path + + def save_deployed_contracts(self): + if self.deployed_contracts: + dump = [] + for contract in self.deployed_contracts: + dump.append(contract.get_config_dict()) + + filepath = config.DEFAULT_CONFIG_SAVE_PATH / f"deployed_{self.label}.json" + Path(config.DEFAULT_CONFIG_SAVE_PATH).mkdir(parents=True, exist_ok=True) + + write_json_file(filepath, dump) + print_test_step_pass(f"Saved deployed {self.label} contracts.") + + def get_saved_deployed_contracts(self) -> list: + contracts_list = [] + filepath = config.DEFAULT_CONFIG_SAVE_PATH / f"deployed_{self.label}.json" + if not Path(filepath).is_file(): # no config available + return [] + + retrieved_contract_configs = read_json_file(filepath) + + for contract_config in retrieved_contract_configs: + contract = self.contract_class.load_config_dict(contract_config) + contracts_list.append(contract) + + return contracts_list + + def get_deployed_contract_by_address(self, address: str) -> DEXContractInterface or None: + found_contract = None + for contract in self.deployed_contracts: + if contract.address == address: + found_contract = contract + break + + return found_contract + + def get_deployed_contract_by_index(self, index: int) -> DEXContractInterface or None: + if index+1 > len(self.deployed_contracts): + return None + return self.deployed_contracts[index] + + def load_deployed_contracts(self): + contracts_list = self.get_saved_deployed_contracts() + if len(self.deploy_structure_list) == len(contracts_list): + self.deployed_contracts = contracts_list + print_test_step_pass(f"Loaded {len(contracts_list)} {self.label}.") + return + + print_test_step_fail(f"No contracts fetched for: {self.label}; " + f"Either no save available or mismatch between deploy structure and loaded contracts.") + + def print_deployed_contracts(self): + print_test_step_pass(f"{self.label}:") + for contract in self.deployed_contracts: + contract.print_contract_info() + + +class DeployStructure: + def __init__(self): + self.token_prefix = populate_deploy_lists.get_token_prefix(config.DEPLOY_STRUCTURE_JSON) + self.number_of_tokens = populate_deploy_lists.get_number_of_tokens(config.DEPLOY_STRUCTURE_JSON) + self.tokens = [] # will be filled with tokens on network + self.esdt_contract = ESDTContract(config.TOKENS_CONTRACT_ADDRESS) + + self.contracts: Dict[str, ContractStructure] = { + config.LOCKED_ASSETS: + ContractStructure(config.LOCKED_ASSETS, LockedAssetContract, config.LOCKED_ASSET_FACTORY_BYTECODE_PATH, + self.locked_asset_deploy, False), + config.PROXIES: + ContractStructure(config.PROXIES, DexProxyContract, config.PROXY_BYTECODE_PATH, + self.proxy_deploy, False), + config.SIMPLE_LOCKS: + ContractStructure(config.SIMPLE_LOCKS, SimpleLockContract, config.SIMPLE_LOCK_BYTECODE_PATH, + self.simple_lock_deploy, False), + config.SIMPLE_LOCKS_ENERGY: + ContractStructure(config.SIMPLE_LOCKS_ENERGY, SimpleLockEnergyContract, config.SIMPLE_LOCK_ENERGY_BYTECODE_PATH, + self.simple_lock_energy_deploy, True), + config.FEES_COLLECTORS: + ContractStructure(config.FEES_COLLECTORS, FeesCollectorContract, config.FEES_COLLECTOR_BYTECODE_PATH, + self.fees_collector_deploy, True), + config.UNSTAKERS: + ContractStructure(config.UNSTAKERS, UnstakerContract, config.UNSTAKER_BYTECODE_PATH, + self.token_unstake_deploy, True), + config.PROXIES_V2: + ContractStructure(config.PROXIES_V2, DexProxyContract, config.PROXY_V2_BYTECODE_PATH, + self.proxy_deploy, True), + config.ROUTER: + ContractStructure(config.ROUTER, RouterContract, config.ROUTER_BYTECODE_PATH, + self.router_deploy, False), + config.ROUTER_V2: + ContractStructure(config.ROUTER_V2, RouterContract, config.ROUTER_V2_BYTECODE_PATH, + self.router_deploy, False), + config.PAIRS: + ContractStructure(config.PAIRS, PairContract, config.PAIR_BYTECODE_PATH, + self.pool_deploy_from_router, False), + config.PAIRS_V2: + ContractStructure(config.PAIRS_V2, PairContract, config.PAIR_V2_BYTECODE_PATH, + self.pool_deploy_from_router, False), + config.FARMS_COMMUNITY: + ContractStructure(config.FARMS_COMMUNITY, FarmContract, config.FARM_COMMUNITY_BYTECODE_PATH, + self.farm_community_deploy, False), + config.FARMS_UNLOCKED: + ContractStructure(config.FARMS_UNLOCKED, FarmContract, config.FARM_BYTECODE_PATH, + self.farm_deploy, False), + config.FARMS_LOCKED: + ContractStructure(config.FARMS_LOCKED, FarmContract, config.FARM_LOCKED_BYTECODE_PATH, + self.farm_deploy, False), + config.PROXY_DEPLOYERS: + ContractStructure(config.PROXY_DEPLOYERS, ProxyDeployerContract, config.FARM_DEPLOYER_BYTECODE_PATH, + self.proxy_deployer_deploy, False), + config.FARMS_V2: + ContractStructure(config.FARMS_V2, FarmContract, config.FARM_V2_BYTECODE_PATH, + self.farm_boosted_deploy, True), # self.farm_deploy_from_proxy_deployer, True), + config.PRICE_DISCOVERIES: + ContractStructure(config.PRICE_DISCOVERIES, PriceDiscoveryContract, config.PRICE_DISCOVERY_BYTECODE_PATH, + self.price_discovery_deploy, False), + config.STAKINGS: + ContractStructure(config.STAKINGS, StakingContract, config.STAKING_BYTECODE_PATH, + self.staking_deploy, False), + config.STAKINGS_V2: + ContractStructure(config.STAKINGS_V2, StakingContract, config.STAKING_V2_BYTECODE_PATH, + self.staking_deploy, False), + config.METASTAKINGS: + ContractStructure(config.METASTAKINGS, MetaStakingContract, config.STAKING_PROXY_BYTECODE_PATH, + self.metastaking_deploy, False), + config.METASTAKINGS_V2: + ContractStructure(config.METASTAKINGS_V2, MetaStakingContract, config.STAKING_PROXY_V2_BYTECODE_PATH, + self.metastaking_deploy, True) + } + + # main entry method to deploy tokens (either deploy fresh ones or reuse existing ones) + def deploy_tokens(self, deployer_account: Account, network_provider: NetworkProviders, + clean_deploy_override: bool): + if not clean_deploy_override: + if not self.load_deployed_tokens(): + return + else: + # get current tokens, see if they satisfy the request + sync_tokens.main(["--tokens-prefix", self.token_prefix]) + tracks = BunchOfTracks(self.token_prefix).load(config.get_default_tokens_file()) + + # issue tokens if necessary + if len(tracks.accounts_by_token) < self.number_of_tokens: + token_hashes = [] + for i in range(self.number_of_tokens - len(tracks.accounts_by_token)): + hashes = issue_tokens.main(["--tokens-prefix", self.token_prefix, "--yes"]) + token_hashes.extend(hashes) + + for txhash in token_hashes: + network_provider.api.wait_for_tx_finalized(txhash) + + time.sleep(40) + + # get tokens, save them in offline json then load them here + sync_tokens.main(["--tokens-prefix", self.token_prefix]) + tracks = tracks.load(config.get_default_tokens_file()) + + # retrieve from list of tuples (holding address, token) + self.load_tokens_from_individual_asset_tracks(tracks.get_all_individual_assets()) + self.save_deployed_tokens() + + def load_tokens_from_individual_asset_tracks(self, tracks): + # individual_asset_tracks returns an array of tuples(Address, tokenID) + # each array element contains a unique token + # the second element in tuple stores the token ID + for token in tracks: + self.tokens.append(token[1]) + + def save_deployed_tokens(self): + if self.tokens: + filepath = config.DEFAULT_CONFIG_SAVE_PATH / "deployed_tokens.json" + write_json_file(filepath, self.tokens) + print_test_step_pass("Saved deployed tokens.") + else: + print_test_step_fail("No tokens to save!") + + def get_saved_deployed_tokens(self) -> list: + filepath = config.DEFAULT_CONFIG_SAVE_PATH / "deployed_tokens.json" + retrieved_tokens = read_json_file(filepath) + + if retrieved_tokens and len(retrieved_tokens) == self.number_of_tokens: + print_test_step_pass(f"Loaded {len(retrieved_tokens)} tokens.") + return retrieved_tokens + elif retrieved_tokens and len(retrieved_tokens) >= self.number_of_tokens: + print_test_step_fail(f"Loaded {len(retrieved_tokens)} tokens instead of expected {self.number_of_tokens}.") + return retrieved_tokens + else: + print_test_step_fail("No tokens loaded!") + return [] + + def load_deployed_tokens(self) -> bool: + loaded_tokens = self.get_saved_deployed_tokens() + if loaded_tokens and len(loaded_tokens) >= self.number_of_tokens: + self.tokens = loaded_tokens + return True + else: + return False + + def save_deployed_contracts(self): + for contracts in self.contracts.values(): + contracts.save_deployed_contracts() + + def print_deployed_contracts(self): + print_test_step_pass(f"Deployed contracts below:") + for contracts in self.contracts.values(): + contracts.print_deployed_contracts() + print("") + + # main entry method to deploy the DEX contract structure (either fresh deploy or loading existing ones) + def deploy_structure(self, deployer_account: Account, network_provider: NetworkProviders, + clean_deploy_override: bool): + deployer_account.sync_nonce(network_provider.proxy) + for contract_label, contracts in self.contracts.items(): + if not clean_deploy_override and not contracts.deploy_clean: + contracts.load_deployed_contracts() + else: + print_test_step_pass(f"Starting setup process for {contract_label}:") + contracts.deploy_function(contract_label, deployer_account, network_provider) + if len(contracts.deployed_contracts) > 0: + contracts.print_deployed_contracts() + self.contracts[contract_label] = contracts + contracts.save_deployed_contracts() + else: + print_warning(f"No contracts deployed for {contract_label}!") + + # should be run for fresh deployed contracts + def start_deployed_contracts(self, deployer_account: Account, network_provider: NetworkProviders, + clean_deploy_override: bool): + deployer_account.sync_nonce(network_provider.proxy) + for contracts in self.contracts.values(): + if contracts.deploy_clean or clean_deploy_override: + for contract in contracts.deployed_contracts: + contract.contract_start(deployer_account, network_provider.proxy) + + self.global_start_setups(deployer_account, network_provider, clean_deploy_override) + + def global_start_setups(self, deployer_account: Account, network_provider: NetworkProviders, + clean_deploy_override: bool): + self.set_transfer_role_locked_token(deployer_account, network_provider, clean_deploy_override) + # self.set_proxy_v2_in_pairs(deployer_account, network_provider, clean_deploy_override) + + def set_transfer_role_locked_token(self, deployer_account: Account, network_provider: NetworkProviders, + clean_deploy_override: bool): + energy_factory: Optional[SimpleLockEnergyContract] = None + energy_factory = self.get_deployed_contract_by_index(config.SIMPLE_LOCKS_ENERGY, 0) + whitelist = [config.PROXIES_V2, config.FEES_COLLECTORS, + config.UNSTAKERS, config.METASTAKINGS_V2] + + # gather contract addresses to whitelist + addresses = [] + if energy_factory: + for contracts in self.contracts.values(): + if not contracts.deploy_clean and not clean_deploy_override: + continue + if contracts.label not in whitelist: + continue + addresses.extend([contract.address for contract in contracts.deployed_contracts]) + + # whitelist addresses + for address in addresses: + tx_hash = energy_factory.set_transfer_role_locked_token(deployer_account, network_provider.proxy, + [address]) + if not network_provider.check_complex_tx_status(tx_hash, "set transfer role for locked asset on contracts"): + return + + def set_proxy_v2_in_pairs(self, deployer_account: Account, network_providers: NetworkProviders, + clean_deploy_override: bool): + search_label = "proxy_v2" + pair_contracts = self.contracts[config.PAIRS_V2] + + # execute only if proxy is clean or overriden + if not self.contracts[config.PROXIES_V2].deploy_clean and not clean_deploy_override: + return + # execute only if pair contracts weren't cleanly deployed + if pair_contracts.deploy_clean: + return + + # Set proxy in pairs + if len(pair_contracts.deploy_structure_list) != len(pair_contracts.deployed_contracts): + print_test_step_fail(f"Uneven length of pair deployed contracts! Skipping.") + return + for index, config_pair in enumerate(pair_contracts.deploy_structure_list): + if search_label in config_pair: + pair_contract: Optional[PairContract] = None + pair_contract = pair_contracts.get_deployed_contract_by_index(index) + proxy_contract: Optional[DexProxyContract] = None + proxy_contract = self.contracts[config.PROXIES_V2].get_deployed_contract_by_index( + config_pair[search_label]) + + if not proxy_contract: + print_test_step_fail(f"Configured proxy not found for pair: {pair_contract.address}") + + tx_hash = proxy_contract.add_pair_to_intermediate(deployer_account, network_providers.proxy, + pair_contract.address) + if not network_providers.check_simple_tx_status(tx_hash, + "set proxy for pair address"): + return + + def get_deployed_contracts(self, label: str): + return self.contracts[label].deployed_contracts + + def get_deployed_contract_by_index(self, label: str, index: int): + return self.contracts[label].get_deployed_contract_by_index(index) + + def get_deployed_contract_by_address(self, label: str, address: str): + return self.contracts[label].get_deployed_contract_by_address(address) + + # CONTRACT DEPLOYERS ------------------------------ + def locked_asset_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + for config_locked_asset in contract_structure.deploy_structure_list: + # deploy locked asset contract + unlocked_token = self.tokens[config_locked_asset["unlocked_asset"]] + locked_token = config_locked_asset["locked_asset"] + locked_token_name = config_locked_asset["locked_asset_name"] + + # deploy contract + deployed_locked_asset_contract = LockedAssetContract(unlocked_token) + tx_hash, contract_address = deployed_locked_asset_contract.contract_deploy(deployer_account, + network_providers.proxy, + contract_structure.bytecode) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "locked asset"): return + deployed_locked_asset_contract.address = contract_address + print_test_step_pass(f"Locked asset contract address: {contract_address}") + + # register locked token and save it + tx_hash = deployed_locked_asset_contract.register_locked_asset_token(deployer_account, + network_providers.proxy, + [locked_token_name, locked_token]) + if not network_providers.check_complex_tx_status(tx_hash, "register locked token"): return + locked_token_hex = LockedAssetContractDataFetcher(Address(deployed_locked_asset_contract.address), + network_providers.proxy.url).get_data("getLockedAssetTokenId") + deployed_locked_asset_contract.locked_asset = hex_to_string(locked_token_hex) + + # Set special role on unlocked asset + tx_hash = self.esdt_contract.set_special_role_token(deployer_account, + network_providers.proxy, + [unlocked_token, contract_address, + "ESDTRoleLocalMint"]) + if not network_providers.check_complex_tx_status(tx_hash, "set special role on unlocked asset"): return + + deployed_contracts.append(deployed_locked_asset_contract) + + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def proxy_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + """ + locked_asset - mandatory for V1; optional for V2 + energy_factory - mandatory for V2 + """ + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + for config_proxy in contract_structure.deploy_structure_list: + if contracts_index == config.PROXIES: + version = ProxyContractVersion.V1 + elif contracts_index == config.PROXIES_V2: + version = ProxyContractVersion.V2 + else: + print_test_step_fail(f"Aborting deploy: Unsupported proxy label.") + return + + # deploy proxy contract + locked_asset_contract: Optional[LockedAssetContract] = None + energy_contract: Optional[SimpleLockEnergyContract] = None + asset = "" + locked_assets = [] + factory_addresses = [] + + if version == ProxyContractVersion.V1 or ProxyContractVersion.V2: + if 'locked_asset' not in config_proxy and version == ProxyContractVersion.V1: + print_test_step_fail(f"Aborting deploy: locked asset not configured.") + return + + locked_asset_contract = self.contracts[config.LOCKED_ASSETS].\ + get_deployed_contract_by_index(config_proxy["locked_asset"]) + if locked_asset_contract: + asset = locked_asset_contract.unlocked_asset + locked_assets.append(locked_asset_contract.locked_asset) + factory_addresses.append(locked_asset_contract.address) + elif version == ProxyContractVersion.V1: + print_test_step_fail(f"Aborting deploy: locked asset contract not available.") + return + + if version == ProxyContractVersion.V2: + if 'energy_factory' not in config_proxy: + print_test_step_fail(f"Aborting deploy: energy factory not configured.") + return + + energy_contract = self.contracts[config.SIMPLE_LOCKS_ENERGY].\ + get_deployed_contract_by_index(config_proxy["energy_factory"]) + if asset and asset != energy_contract.base_token: + print_test_step_fail(f"Aborting deploy: Mismatch configuration in base tokens.") + return + elif not asset: + asset = energy_contract.base_token + locked_assets.append(energy_contract.locked_token) + factory_addresses.append(energy_contract.address) + + proxy_lp_token = config_proxy["proxy_lp"] + proxy_lp_token_name = config_proxy["proxy_lp_name"] + proxy_farm_token = config_proxy["proxy_farm"] + proxy_farm_token_name = config_proxy["proxy_farm_name"] + + # deploy contract + deployed_proxy_contract = DexProxyContract(locked_assets, asset, version) + tx_hash, contract_address = deployed_proxy_contract.contract_deploy(deployer_account, + network_providers.proxy, + contract_structure.bytecode, + [factory_addresses]) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "proxy"): return + deployed_proxy_contract.address = contract_address + print_test_step_pass(f"Proxy contract address: {contract_address}") + + # register proxy lp token and save it + tx_hash = deployed_proxy_contract.register_proxy_lp_token(deployer_account, + network_providers.proxy, + [proxy_lp_token_name, proxy_lp_token]) + if not network_providers.check_complex_tx_status(tx_hash, "register proxy lp token"): return + proxy_lp_token = ProxyContractDataFetcher(Address(deployed_proxy_contract.address), + network_providers.proxy.url).get_data("getWrappedLpTokenId") + deployed_proxy_contract.proxy_lp_token = hex_to_string(proxy_lp_token) + + # register proxy farm token and save it + tx_hash = deployed_proxy_contract.register_proxy_farm_token(deployer_account, + network_providers.proxy, + [proxy_farm_token_name, proxy_farm_token]) + if not network_providers.check_complex_tx_status(tx_hash, "register proxy farm token"): return + proxy_farm_token = ProxyContractDataFetcher(Address(deployed_proxy_contract.address), + network_providers.proxy.url).get_data("getWrappedFarmTokenId") + deployed_proxy_contract.proxy_farm_token = hex_to_string(proxy_farm_token) + + # Whitelist proxy in locked asset factory contract + if version == ProxyContractVersion.V1 or version == ProxyContractVersion.V2: + tx_hash = locked_asset_contract.whitelist_contract(deployer_account, network_providers.proxy, + deployed_proxy_contract.address) + if not network_providers.check_simple_tx_status(tx_hash, + "whitelist proxy in locked asset contract"): return + if version == ProxyContractVersion.V2: + tx_hash = energy_contract.add_sc_to_token_transfer_whitelist(deployer_account, network_providers.proxy, + deployed_proxy_contract.address) + if not network_providers.check_simple_tx_status(tx_hash, + "whitelist proxy in energy factory contract"): return + + # set energy factory proxy + tx_hash = deployed_proxy_contract.set_energy_factory_address(deployer_account, network_providers.proxy, + energy_contract.address) + if not network_providers.check_simple_tx_status(tx_hash, + "set energy factory in proxy contract"): return + + # Set special roles on unlocked asset token + self.esdt_contract.set_special_role_token(deployer_account, network_providers.proxy, + [asset, deployed_proxy_contract.address, "ESDTRoleLocalMint"]) + if not network_providers.check_complex_tx_status(tx_hash, "set special role on unlocked asset"): return + + self.esdt_contract.set_special_role_token(deployer_account, network_providers.proxy, + [asset, deployed_proxy_contract.address, "ESDTRoleLocalBurn"]) + if not network_providers.check_complex_tx_status(tx_hash, "set special role on unlocked asset"): return + + deployed_contracts.append(deployed_proxy_contract) + + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def simple_lock_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + for config_simple_lock in contract_structure.deploy_structure_list: + # deploy simple lock contract + locked_token = config_simple_lock["locked_token"] + locked_lp_token = config_simple_lock["locked_lp_token"] + + # deploy contract + deployed_simple_lock_contract = SimpleLockContract() + tx_hash, contract_address = deployed_simple_lock_contract.contract_deploy( + deployer_account, network_providers.proxy, contract_structure.bytecode) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "simple lock"): return + deployed_simple_lock_contract.address = contract_address + print_test_step_pass(f"Simple lock contract address: {contract_address}") + + # issue locked token and save it + tx_hash = deployed_simple_lock_contract.issue_locked_token(deployer_account, + network_providers.proxy, locked_token) + if not network_providers.check_complex_tx_status(tx_hash, "issue locked token"): return + locked_token_hex = SimpleLockContractDataFetcher(Address(deployed_simple_lock_contract.address), + network_providers.proxy.url).get_data("getLockedTokenId") + deployed_simple_lock_contract.locked_token = hex_to_string(locked_token_hex) + + # issue locked LP token and save it + tx_hash = deployed_simple_lock_contract.issue_locked_lp_token(deployer_account, + network_providers.proxy, locked_lp_token) + if not network_providers.check_complex_tx_status(tx_hash, "issue locked LP token"): return + locked_lp_token_hex = SimpleLockContractDataFetcher(Address(deployed_simple_lock_contract.address), + network_providers.proxy.url).get_data("getLpProxyTokenId") + deployed_simple_lock_contract.lp_proxy_token = hex_to_string(locked_lp_token_hex) + + deployed_contracts.append(deployed_simple_lock_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def fees_collector_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + for contract_config in contract_structure.deploy_structure_list: + # deploy fees collector contract + energy_factory_contract: Optional[SimpleLockEnergyContract] = None + if 'energy_factory' in contract_config: + energy_factory_contract = self.contracts[config.SIMPLE_LOCKS_ENERGY].get_deployed_contract_by_index( + contract_config['energy_factory'] + ) + else: + print_test_step_fail(f"Aborting deploy: Energy factory not configured! Contract will be dumped.") + return + + # deploy contract + deployed_contract = FeesCollectorContract() + tx_hash, contract_address = deployed_contract.contract_deploy( + deployer_account, network_providers.proxy, contract_structure.bytecode, + [energy_factory_contract.locked_token, energy_factory_contract.address]) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "fees collector"): return + deployed_contract.address = contract_address + print_test_step_pass(f"Fees collector contract address: {contract_address}") + + # set energy factory in fees collector + tx_hash = deployed_contract.set_energy_factory_address(deployer_account, network_providers.proxy, + energy_factory_contract.address) + if not network_providers.check_simple_tx_status(tx_hash, "set energy factory in fees collector"): return + + # set locking address in fees collector + tx_hash = deployed_contract.set_locking_address(deployer_account, network_providers.proxy, + energy_factory_contract.address) + if not network_providers.check_simple_tx_status(tx_hash, "set locking address in fees collector"): return + + # set lock epochs + tx_hash = deployed_contract.set_lock_epochs(deployer_account, network_providers.proxy, + contract_config['lock_epochs']) + if not network_providers.check_simple_tx_status(tx_hash, "set lock epochs in fees collector"): return + + # set locked tokens per block + tx_hash = deployed_contract.set_locked_tokens_per_block(deployer_account, network_providers.proxy, + contract_config['locked_tokens_per_block']) + if not network_providers.check_simple_tx_status(tx_hash, "set locked tokens per block in fees collector"): + return + + # whitelist fees collector in energy contract + tx_hash = energy_factory_contract.add_sc_to_whitelist(deployer_account, network_providers.proxy, + deployed_contract.address) + if not network_providers.check_simple_tx_status(tx_hash, "add fees collector in energy contract"): return + + deployed_contracts.append(deployed_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def simple_lock_energy_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + for contract_config in contract_structure.deploy_structure_list: + # deploy simple lock energy contract + base_token = self.tokens[contract_config['base_token']] + locked_token = contract_config["locked_token"] + locked_token_name = contract_config["locked_token_name"] + + locked_asset_factory: Optional[LockedAssetContract] = None + if 'locked_asset_factory' in contract_config: + locked_asset_factory = self.contracts[config.LOCKED_ASSETS].get_deployed_contract_by_index( + contract_config['locked_asset_factory']) + if locked_asset_factory is None: + print_test_step_fail(f"Aborting deploy: Locked asset factory contract not available! Contract will be dumped.") + return + else: + print_test_step_fail(f"Aborting deploy: Locked asset factory not configured! Contract will be dumped.") + return + + # deploy contract + deployed_contract = SimpleLockEnergyContract(base_token) + tx_hash, contract_address = deployed_contract.contract_deploy( + deployer_account, network_providers.proxy, contract_structure.bytecode, + [locked_asset_factory.locked_asset, locked_asset_factory.address, + contract_config['min_migrated_lock_epochs'], + contract_config['lock_options'], contract_config['penalties']]) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "simple lock energy"): return + deployed_contract.address = contract_address + print_test_step_pass(f"Simple lock energy contract address: {contract_address}") + + # issue locked token and save it + tx_hash = deployed_contract.issue_locked_token(deployer_account, + network_providers.proxy, + [locked_token_name, locked_token]) + if not network_providers.check_complex_tx_status(tx_hash, "issue locked token"): return + locked_token_hex = SimpleLockEnergyContractDataFetcher( + Address(deployed_contract.address), network_providers.proxy.url).get_data("getLockedTokenId") + deployed_contract.locked_token = hex_to_string(locked_token_hex) + + # Set special role on unlocked asset for burning base token + tx_hash = self.esdt_contract.set_special_role_token(deployer_account, + network_providers.proxy, + [base_token, contract_address, + "ESDTRoleLocalBurn"]) + if not network_providers.check_complex_tx_status(tx_hash, "set burn role on unlocked asset"): return + + # Set special role on unlocked asset for base token minting + tx_hash = self.esdt_contract.set_special_role_token(deployer_account, + network_providers.proxy, + [base_token, contract_address, + "ESDTRoleLocalMint"]) + if not network_providers.check_complex_tx_status(tx_hash, "set mint role on unlocked asset"): return + + deployed_contracts.append(deployed_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def token_unstake_deploy(self, contracts_index: str, deployer_account: Account, + network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + for contract_config in contract_structure.deploy_structure_list: + # deploy token unstake contract + + fees_collector: Optional[FeesCollectorContract] = None + if 'fees_collector' in contract_config: + fees_collector = self.contracts[config.FEES_COLLECTORS].get_deployed_contract_by_index( + contract_config["fees_collector"]) + if fees_collector is None: + print_test_step_fail(f"Aborting deploy: Fees collector contract not available! " + f"Contract will be dumped.") + return + else: + print_test_step_fail( + f"Aborting deploy: Fees collector not configured! Contract will be dumped.") + return + + energy_factory: Optional[SimpleLockEnergyContract] = None + if 'energy_factory' in contract_config: + energy_factory = self.contracts[config.SIMPLE_LOCKS_ENERGY].get_deployed_contract_by_index( + contract_config['energy_factory']) + if energy_factory is None: + print_test_step_fail(f"Aborting deploy: Energy factory contract not available! " + f"Contract will be dumped.") + return + else: + print_test_step_fail(f"Aborting deploy: Energy factory not configured! Contract will be dumped.") + return + + # deploy contract + deployed_contract = UnstakerContract() + tx_hash, contract_address = deployed_contract.contract_deploy( + deployer_account, network_providers.proxy, contract_structure.bytecode, + [contract_config['unbond_epochs'], energy_factory.address, contract_config['fees_burn_percentage'], + fees_collector.address]) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "token unstake"): return + deployed_contract.address = contract_address + print_test_step_pass(f"Token unstake contract address: {contract_address}") + + # Set special role on unlocked asset for burning base token + tx_hash = self.esdt_contract.set_special_role_token(deployer_account, + network_providers.proxy, + [energy_factory.base_token, contract_address, + "ESDTRoleLocalBurn"]) + if not network_providers.check_complex_tx_status(tx_hash, "set burn role on unlocked asset"): return + + # Set special role on locked asset for burning locked token + tx_hash = energy_factory.set_burn_role_locked_token(deployer_account, + network_providers.proxy, + [contract_address]) + if not network_providers.check_complex_tx_status(tx_hash, "set burn role on locked asset"): return + + # add token unstake address in energy contract + tx_hash = energy_factory.set_token_unstake(deployer_account, network_providers.proxy, [contract_address]) + if not network_providers.check_simple_tx_status(tx_hash, "set unstake address in energy contract"): return + + tx_hash = energy_factory.add_sc_to_whitelist(deployer_account, network_providers.proxy, contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "whitelist unstake address in energy contract"): return + + # whitelist unstaker address in fees collector (unstake sends fees from energy factory) + if 'fees_collector' in contract_config: + tx_hash = fees_collector.add_known_contracts(deployer_account, network_providers.proxy, + [contract_address]) + if not network_providers.check_simple_tx_status(tx_hash, + "whitelist energy address in fees collector"): return + + tx_hash = fees_collector.add_known_tokens(deployer_account, network_providers.proxy, + [f"str:{energy_factory.locked_token}"]) + if not network_providers.check_simple_tx_status(tx_hash, + "whitelist locked token in fees collector"): return + + deployed_contracts.append(deployed_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def router_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + version = RouterContractVersion.V1 if contracts_index == config.ROUTER else RouterContractVersion.V2 + + for _ in contract_structure.deploy_structure_list: + # deploy template pair + if version == RouterContractVersion.V1: + pair_version = PairContractVersion.V1 + pair_bytecode = self.contracts[config.PAIRS].bytecode + else: + pair_version = PairContractVersion.V2 + pair_bytecode = self.contracts[config.PAIRS_V2].bytecode + + template_pair_contract = PairContract(self.tokens[0], self.tokens[1], pair_version) + tx_hash, contract_address = template_pair_contract.contract_deploy( + deployer_account, network_providers.proxy, pair_bytecode, + [config.ZERO_CONTRACT_ADDRESS, config.ZERO_CONTRACT_ADDRESS, + config.ZERO_CONTRACT_ADDRESS, 0, 0, config.ZERO_CONTRACT_ADDRESS]) + + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "pair contract"): return + template_pair_contract.address = contract_address + + # deploy router + router_contract = RouterContract(version) + tx_hash, contract_address = router_contract.contract_deploy( + deployer_account, network_providers.proxy, self.contracts[contracts_index].bytecode, + [template_pair_contract.address]) + + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "router"): return + router_contract.address = contract_address + print_test_step_pass(f"Router contract address: {contract_address}") + + deployed_contracts.append(router_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def pool_deploy_from_router(self, contracts_index: str, deployer_account: Account, + network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + version = PairContractVersion.V1 if contracts_index == config.PAIRS else PairContractVersion.V2 + + for config_pool in contract_structure.deploy_structure_list: + # deploy pair contract from router + first_token = self.tokens[config_pool['launched_token']] + second_token = self.tokens[config_pool['accepted_token']] + lp_token = config_pool['lp_token'] + lp_token_name = config_pool['lp_token_name'] + if version == PairContractVersion.V1: + used_router_label = config.ROUTER + else: + used_router_label = config.ROUTER_V2 + + router_contract = self.contracts[used_router_label].get_deployed_contract_by_index(0) + if router_contract is None: + print_test_step_fail(f"Aborting deploy: Router contract not available! Contract will be dumped.") + return + + # deploy contract + total_fee = config_pool['total_fee'] + special_fee = config_pool['special_fee'] + initial_liquidity_provider = config_pool[ + 'liquidity_provider'] if 'liquidity_provider' in config_pool else config.ZERO_CONTRACT_ADDRESS + admins = config_pool['admins'] if 'admins' in config_pool else [] + + deployed_pair_contract = PairContract(first_token, second_token, version) + args = [initial_liquidity_provider, total_fee, special_fee] + args.extend(admins) + tx_hash, contract_address = deployed_pair_contract.contract_deploy_via_router( + deployer_account, network_providers.proxy, router_contract, args) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "pair via router"): return + deployed_pair_contract.address = contract_address + print_test_step_pass(f"Pair contract address: {contract_address}") + + # issue LP token and save it + tx_hash = deployed_pair_contract.issue_lp_token_via_router(deployer_account, network_providers.proxy, + router_contract, [lp_token_name, lp_token]) + if not network_providers.check_complex_tx_status(tx_hash, "issue LP token"): return + lp_token_hex = PairContractDataFetcher(Address(deployed_pair_contract.address), + network_providers.proxy.url).get_data("getLpTokenIdentifier") + deployed_pair_contract.lpToken = hex_to_string(lp_token_hex) + + # Set LP Token local roles + tx_hash = deployed_pair_contract.set_lp_token_local_roles_via_router(deployer_account, + network_providers.proxy, + router_contract) + if not network_providers.check_simple_tx_status(tx_hash, "set lp token local roles via router"): return + + # Set proxy if applicable + if "proxy" in config_pool or "proxy_v2" in config_pool: + proxy_contract: Optional[DexProxyContract] = None + if "proxy" in config_pool: + proxy_contract = self.contracts[config.PROXIES].\ + get_deployed_contract_by_index(config_pool['proxy']) + elif "proxy_v2" in config_pool: + proxy_contract = self.contracts[config.PROXIES_V2].\ + get_deployed_contract_by_index(config_pool["proxy_v2"]) + if proxy_contract is None: + print_test_step_fail(f"Aborting setup: Proxy contract not available! Contract will be dumped.") + return + tx_hash = proxy_contract.add_pair_to_intermediate(deployer_account, network_providers.proxy, + contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "set pair to intermediate in proxy"): return + + # Set simple lock if applicable + if "simple_lock" in config_pool: + current_epoch = network_providers.extended_proxy.get_network_status(0).current_epoch + locking_deadline_epoch = current_epoch + 3 + unlock_epoch = current_epoch + 3 + + # set locking deadline in pair + tx_hash = deployed_pair_contract.set_locking_deadline_epoch(deployer_account, network_providers.proxy, + locking_deadline_epoch) + if not network_providers.check_simple_tx_status(tx_hash, "set locking deadline epoch in pair"): return + + # set unlock epoch in pair + tx_hash = deployed_pair_contract.set_unlock_epoch(deployer_account, network_providers.proxy, + unlock_epoch) + if not network_providers.check_simple_tx_status(tx_hash, "set unlock epoch in pair"): return + + deployed_simple_lock: Optional[SimpleLockContract] = None + deployed_simple_lock = self.contracts[config.SIMPLE_LOCKS].get_deployed_contract_by_index(config_pool['simple_lock']) + if deployed_simple_lock is None: + print_test_step_fail(f"Aborting setup: Simple lock contract not available! Contract will be dumped.") + return + # add simple lock address in pair + tx_hash = deployed_pair_contract.set_locking_sc_address(deployer_account, network_providers.proxy, + deployed_simple_lock.address) + if not network_providers.check_simple_tx_status(tx_hash, "set simple locking sc address in pair"): return + + # whitelist in simple lock contract + tx_hash = deployed_simple_lock.add_lp_to_whitelist(deployer_account, network_providers.proxy, + [deployed_pair_contract.address, first_token, + second_token]) + if not network_providers.check_simple_tx_status(tx_hash, "whitelist pair in simple locking contract"): return + + # Set fees collector if applicable + if "fees_collector" in config_pool and version == PairContractVersion.V2: + fees_collector: Optional[FeesCollectorContract] = None + fees_collector = self.contracts[config.FEES_COLLECTORS].get_deployed_contract_by_index(config_pool['fees_collector']) + if fees_collector is None: + print_test_step_fail(f"Aborting setup: Fees collector contract not available! Contract will be dumped.") + return + if 'fees_collector_cut' not in config_pool: + print_test_step_fail(f"Aborting setup: fees_collector_cut not available in config! Contract will be dumped.") + return + fees_cut = config_pool['fees_collector_cut'] + + # setup fees collector in pair + tx_hash = deployed_pair_contract.add_fees_collector(deployer_account, network_providers.proxy, + [fees_collector.address, fees_cut]) + if not network_providers.check_simple_tx_status(tx_hash, "set fees collector in pair"): return + + # add pair address in fees collector + _ = fees_collector.add_known_contracts(deployer_account, network_providers.proxy, + [contract_address]) + _ = fees_collector.add_known_tokens(deployer_account, network_providers.proxy, + [f"str:{deployed_pair_contract.firstToken}", + f"str:{deployed_pair_contract.secondToken}"]) + + deployed_contracts.append(deployed_pair_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def farm_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + for config_farm in contract_structure.deploy_structure_list: + # deploy farm contract + lp_address = config.ZERO_CONTRACT_ADDRESS + lp_contract: Optional[PairContract] = None + locked_asset_address = config.ZERO_CONTRACT_ADDRESS + locked_asset_contract: Optional[LockedAssetContract] = None + version = FarmContractVersion.V14Locked if contracts_index == config.FARMS_LOCKED else \ + FarmContractVersion.V14Unlocked + + if version == FarmContractVersion.V14Locked: + if not self.contracts[config.LOCKED_ASSETS].deployed_contracts: + print_test_step_fail("Aborting deploy for farm locked. Locked asset contract not existing.") + return + locked_asset_contract = self.contracts[config.LOCKED_ASSETS].deployed_contracts[0] + locked_asset_address = locked_asset_contract.address + + farmed_token = self.tokens[config_farm['farmed_token']] + farm_token = config_farm['farm_token'] + if 'farming_token' in config_farm: + farming_token = self.tokens[config_farm['farming_token']] + elif 'farming_pool' in config_farm: + # TODO: add check to verify existence of pair contract as prerequisite + lp_contract = self.contracts[config.PAIRS].deployed_contracts[config_farm['farming_pool']] + farming_token = lp_contract.lpToken + lp_address = lp_contract.address + else: + print_test_step_fail(f'Aborting deploy: farming token/pool not configured!') + return + + # deploy contract + deployed_farm_contract = FarmContract(farming_token=farming_token, + farm_token="", + farmed_token=farmed_token, + address="", + version=version, + ) + tx_hash, contract_address = deployed_farm_contract.contract_deploy( + deployer_account, network_providers.proxy, contract_structure.bytecode, + [lp_address, locked_asset_address]) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "farm"): return + deployed_farm_contract.address = contract_address + print_test_step_pass(f"Farm contract address: {contract_address}") + + # register farm token and save it + tx_hash = deployed_farm_contract.register_farm_token(deployer_account, network_providers.proxy, farm_token) + if not network_providers.check_complex_tx_status(tx_hash, "register farm token"): return + farm_token_hex = FarmContractDataFetcher(Address(deployed_farm_contract.address), + network_providers.proxy.url).get_data("getFarmTokenId") + deployed_farm_contract.farmToken = hex_to_string(farm_token_hex) + + # Whitelist farm in pool if it's linked to pool + if lp_contract is not None: + tx_hash = lp_contract.whitelist_contract(deployer_account, network_providers.proxy, contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "whitelist farm in pool"): return + + # Whitelist farm in locked asset contract or provide mint role for rewards + if locked_asset_contract is not None: + tx_hash = locked_asset_contract.whitelist_contract(deployer_account, network_providers.proxy, + contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "whitelist farm in locked asset contract"): return + else: + tx_hash = self.esdt_contract.set_special_role_token(deployer_account, network_providers.proxy, + [farmed_token, contract_address, + "ESDTRoleLocalMint"]) + if not network_providers.check_complex_tx_status(tx_hash, "set special role on farmed token"): return + + # set rewards per block + tx_hash = deployed_farm_contract.set_rewards_per_block(deployer_account, network_providers.proxy, + config_farm['rpb']) + if not network_providers.check_simple_tx_status(tx_hash, "set rewards per block in farm"): return + + # set penalty percent + tx_hash = deployed_farm_contract.set_penalty_percent(deployer_account, network_providers.proxy, 0) + if not network_providers.check_simple_tx_status(tx_hash, "set penalty percent in farm"): return + + # Set proxy if applicable + if "proxy" in config_farm or "proxy_v2" in config_farm: + proxy_contract: Optional[DexProxyContract] = None + if "proxy" in config_farm: + proxy_contract = self.contracts[config.PROXIES].\ + get_deployed_contract_by_index(config_farm['proxy']) + elif "proxy_v2" in config_farm: + proxy_contract = self.contracts[config.PROXIES_V2].\ + get_deployed_contract_by_index(config_farm['proxy_v2']) + tx_hash = proxy_contract.add_farm_to_intermediate(deployer_account, network_providers.proxy, + contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "set farm to intermediate in proxy"): return + + deployed_contracts.append(deployed_farm_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def proxy_deployer_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + + for contract_config in contract_structure.deploy_structure_list: + # deploy template contract + if "template" not in contract_config: + print_test_step_fail(f"Aborting deploy: template not configured") + return + + template_name = contract_config['template'] + if template_name in self.contracts: + contract_bytecode = self.contracts[contract_config['template']].bytecode + else: + print_test_step_fail("Aborting deploy: Template for proxy deployer not valid.") + return + + if template_name == config.FARMS_V2: + version = FarmContractVersion.V2Boosted + else: + print_test_step_fail(f"Aborting deploy: invalid template configured") + return + + template_contract = FarmContract( + self.tokens[0], + "", + self.tokens[0], + "", + version) + tx_hash, template_address = template_contract.contract_deploy( + deployer_account, network_providers.proxy, contract_bytecode, + [config.ZERO_CONTRACT_ADDRESS, config.ZERO_CONTRACT_ADDRESS]) + + if not network_providers.check_deploy_tx_status(tx_hash, template_address, "template farm contract"): return + template_contract.address = template_address + + # deploy proxy deployer + contract = ProxyDeployerContract(template_name) + tx_hash, contract_address = contract.contract_deploy( + deployer_account, network_providers.proxy, self.contracts[contracts_index].bytecode, + [template_contract.address]) + + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "proxy deployer"): return + contract.address = contract_address + print_test_step_pass(f"Proxy deployer contract address: {contract_address}") + + deployed_contracts.append(contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account: Account, + network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + + for contract_config in contract_structure.deploy_structure_list: + # deploy farm contract from proxy deployer + # get deployer proxy contract + if "deployer" not in contract_config: + print_test_step_fail(f"Aborting deploy: deployer not configured") + return + + deployer_contract: Optional[ProxyDeployerContract] = \ + self.contracts[config.PROXY_DEPLOYERS].get_deployed_contract_by_index( + contract_config['deployer']) + + if deployer_contract is None: + print_test_step_fail(f"Aborting deploy: deployer contract not available") + + # determine version + version = None + if deployer_contract.template == config.FARMS_V2: + version = FarmContractVersion.V2Boosted + + # get lock factory + if 'lock_factory' not in contract_config: + print_test_step_fail("Aborting deploy: Locked factory contract not existing!") + return + locking_contract: Optional[SimpleLockEnergyContract] = None + locking_contract = self.contracts[config.SIMPLE_LOCKS_ENERGY].get_deployed_contract_by_index( + contract_config['lock_factory'] + ) + + # get contract config + farmed_token = self.tokens[contract_config['farmed_token']] + farm_token = contract_config['farm_token'] + farm_token_name = contract_config['farm_token_name'] + lp_contract: Optional[PairContract] = None + lp_address = config.ZERO_CONTRACT_ADDRESS + if 'farming_token' in contract_config: + farming_token = self.tokens[contract_config['farming_token']] + elif 'farming_pool' in contract_config: + lp_contract = self.contracts[config.PAIRS_V2].get_deployed_contract_by_index( + contract_config['farming_pool']) + if lp_contract is None: + print_test_step_fail(f'Aborting deploy: farming pool v2 not existing!') + return + farming_token = lp_contract.lpToken + lp_address = lp_contract.address + else: + print_test_step_fail(f'Aborting deploy: farming token/pool not configured!') + return + + # deploy contract + deployed_contract = FarmContract(farming_token=farming_token, + farm_token="", + farmed_token=farmed_token, + address="", + version=version, + ) + tx_hash, contract_address = deployer_contract.farm_contract_deploy(deployer_account, network_providers.proxy, + [farmed_token, farming_token, lp_address]) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "boosted farm"): return + deployed_contract.address = contract_address + print_test_step_pass(f"Farm contract address: {contract_address}") + + # register farm token and save it + tx_hash = deployed_contract.register_farm_token(deployer_account, network_providers.proxy, + [farm_token_name, farm_token]) + if not network_providers.check_complex_tx_status(tx_hash, "register farm token"): return + farm_token_hex = FarmContractDataFetcher(Address(deployed_contract.address), + network_providers.proxy.url).get_data("getFarmTokenId") + deployed_contract.farmToken = hex_to_string(farm_token_hex) + + # Whitelist farm in pool if it's linked to pool + if lp_contract is not None: + tx_hash = lp_contract.whitelist_contract(deployer_account, network_providers.proxy, contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "whitelist farm in pool"): return + + # Set energy contract + if locking_contract is not None: + # tx_hash = deployed_contract.set_energy_factory_address(deployer_account, network_providers.proxy, + # locking_contract.address) + # TODO: we should get rid of proxy calls for consistency + tx_hash = deployer_contract.call_farm_endpoint(deployer_account, network_providers.proxy, + [deployed_contract.address, + "setEnergyFactoryAddress", + locking_contract.address]) + if not network_providers.check_simple_tx_status(tx_hash, "set energy address in farm"): return + else: + print_test_step_fail(f"Failed to set up energy contract in farm. Energy contract not available!") + return + + # Set locking contract + if locking_contract is not None: + # tx_hash = deployed_contract.set_locking_address(deployer_account, network_providers.proxy, + # locking_contract.address) + # TODO: we should get rid of proxy calls for consistency + tx_hash = deployer_contract.call_farm_endpoint(deployer_account, network_providers.proxy, + [deployed_contract.address, + "setLockingScAddress", + locking_contract.address]) + if not network_providers.check_simple_tx_status(tx_hash, "set locking address in farm"): return + else: + print_test_step_fail(f"Failed to set up locking contract in farm. Locking contract not available!") + return + + # Set lock epochs + if 'lock_epochs' not in contract_config: + lock_epochs = 1440 + print_test_step_fail(f"Rewards per block not configured! Setting default: {lock_epochs}") + else: + lock_epochs = contract_config['lock_epochs'] + # tx_hash = deployed_contract.set_lock_epochs(deployer_account, network_providers.proxy, + # lock_epochs) + # TODO: we should get rid of proxy calls for consistency + tx_hash = deployer_contract.call_farm_endpoint(deployer_account, network_providers.proxy, + [deployed_contract.address, + "setLockEpochs", + lock_epochs]) + if not network_providers.check_simple_tx_status(tx_hash, "set lock epochs in farm"): return + + # Set boosted yields rewards percentage + if 'boosted_rewards' not in contract_config: + boosted_rewards = 6000 + print_test_step_fail(f"Boosted yields rewards percentage configured! Setting default: {boosted_rewards}") + else: + boosted_rewards = contract_config['boosted_rewards'] + tx_hash = deployed_contract.set_boosted_yields_rewards_percentage(deployer_account, network_providers.proxy, + boosted_rewards) + if not network_providers.check_simple_tx_status(tx_hash, "set boosted yields rewards percentage in farm"): + return + + # Set boosted yields factors + if "base_const" not in contract_config or \ + "energy_const" not in contract_config or \ + "farm_const" not in contract_config or \ + "min_energy" not in contract_config or \ + "min_farm" not in contract_config: + print_test_step_fail(f"Aborting deploy: Boosted yields factors not configured!") + tx_hash = deployed_contract.set_boosted_yields_factors(deployer_account, network_providers.proxy, + [contract_config['base_const'], + contract_config['energy_const'], + contract_config['farm_const'], + contract_config['min_energy'], + contract_config['min_farm']]) + if not network_providers.check_simple_tx_status(tx_hash, "set boosted yields factors in farm"): + return + + # set rewards per block + if 'rpb' not in contract_config: + rpb = 10000 + print_test_step_fail(f"Rewards per block not configured! Setting default: {rpb}") + else: + rpb = contract_config['rpb'] + tx_hash = deployed_contract.set_rewards_per_block(deployer_account, network_providers.proxy, + rpb) + if not network_providers.check_simple_tx_status(tx_hash, "set rewards per block in farm"): return + + # set penalty percent + if 'penalty' not in contract_config: + print_test_step_fail(f"Penalty percent not configured! Setting default: 0") + penalty = 0 + else: + penalty = contract_config['penalty'] + # tx_hash = deployed_contract.set_penalty_percent(deployer_account, network_providers.proxy, + # penalty) + # TODO: we should get rid of proxy calls for consistency + tx_hash = deployer_contract.call_farm_endpoint(deployer_account, network_providers.proxy, + [deployed_contract.address, + "set_penalty_percent", + penalty]) + if not network_providers.check_simple_tx_status(tx_hash, "set penalty percent in farm"): return + + deployed_contracts.append(deployed_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + + for contract_config in contract_structure.deploy_structure_list: + # get lock factory + if 'lock_factory' not in contract_config: + print_test_step_fail("Aborting deploy: Locked factory contract not existing!") + return + locking_contract: Optional[SimpleLockEnergyContract] = None + locking_contract = self.contracts[config.SIMPLE_LOCKS_ENERGY].get_deployed_contract_by_index( + contract_config['lock_factory'] + ) + + # get contract config + farmed_token = self.tokens[contract_config['farmed_token']] + farm_token = contract_config['farm_token'] + farm_token_name = contract_config['farm_token_name'] + lp_contract: Optional[PairContract] = None + lp_address = config.ZERO_CONTRACT_ADDRESS + if 'farming_token' in contract_config: + farming_token = self.tokens[contract_config['farming_token']] + elif 'farming_pool' in contract_config: + lp_contract = self.contracts[config.PAIRS_V2].get_deployed_contract_by_index( + contract_config['farming_pool']) + if lp_contract is None: + print_test_step_fail(f'Aborting deploy: farming pool v2 not existing!') + return + farming_token = lp_contract.lpToken + lp_address = lp_contract.address + else: + print_test_step_fail(f'Aborting deploy: farming token/pool not configured!') + return + + version = FarmContractVersion.V2Boosted + + # deploy contract + deployed_contract = FarmContract(farming_token=farming_token, + farm_token="", + farmed_token=farmed_token, + address="", + version=version, + ) + tx_hash, contract_address = deployed_contract.contract_deploy(deployer_account, network_providers.proxy, + contract_structure.bytecode, + [lp_address, + deployer_account.address.bech32()]) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "boosted farm"): return + deployed_contract.address = contract_address + print_test_step_pass(f"Farm contract address: {contract_address}") + + # register farm token and save it + tx_hash = deployed_contract.register_farm_token(deployer_account, network_providers.proxy, + [farm_token_name, farm_token]) + if not network_providers.check_complex_tx_status(tx_hash, "register farm token"): return + farm_token_hex = FarmContractDataFetcher(Address(deployed_contract.address), + network_providers.proxy.url).get_data("getFarmTokenId") + deployed_contract.farmToken = hex_to_string(farm_token_hex) + + # Whitelist farm in pool if it's linked to pool + if lp_contract is not None: + tx_hash = lp_contract.whitelist_contract(deployer_account, network_providers.proxy, contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "whitelist farm in pool"): return + + # Set energy contract + if locking_contract is not None: + tx_hash = deployed_contract.set_energy_factory_address(deployer_account, network_providers.proxy, + locking_contract.address) + if not network_providers.check_simple_tx_status(tx_hash, "set energy address in farm"): return + else: + print_test_step_fail(f"Failed to set up energy contract in farm. Energy contract not available!") + return + + # Set locking contract + if locking_contract is not None: + tx_hash = deployed_contract.set_locking_address(deployer_account, network_providers.proxy, + locking_contract.address) + if not network_providers.check_simple_tx_status(tx_hash, "set locking address in farm"): return + else: + print_test_step_fail(f"Failed to set up locking contract in farm. Locking contract not available!") + return + + # Set lock epochs + if 'lock_epochs' not in contract_config: + lock_epochs = 1440 + print_test_step_fail(f"Rewards per block not configured! Setting default: {lock_epochs}") + else: + lock_epochs = contract_config['lock_epochs'] + + tx_hash = deployed_contract.set_lock_epochs(deployer_account, network_providers.proxy, + lock_epochs) + if not network_providers.check_simple_tx_status(tx_hash, "set lock epochs in farm"): return + + # Set boosted yields rewards percentage + if 'boosted_rewards' not in contract_config: + boosted_rewards = 6000 + print_test_step_fail(f"Boosted yields rewards percentage configured! Setting default: {boosted_rewards}") + else: + boosted_rewards = contract_config['boosted_rewards'] + tx_hash = deployed_contract.set_boosted_yields_rewards_percentage(deployer_account, network_providers.proxy, + boosted_rewards) + if not network_providers.check_simple_tx_status(tx_hash, "set boosted yields rewards percentage in farm"): + return + + # Set boosted yields factors + if "base_const" not in contract_config or \ + "energy_const" not in contract_config or \ + "farm_const" not in contract_config or \ + "min_energy" not in contract_config or \ + "min_farm" not in contract_config: + print_test_step_fail(f"Aborting deploy: Boosted yields factors not configured!") + tx_hash = deployed_contract.set_boosted_yields_factors(deployer_account, network_providers.proxy, + [contract_config['base_const'], + contract_config['energy_const'], + contract_config['farm_const'], + contract_config['min_energy'], + contract_config['min_farm']]) + if not network_providers.check_simple_tx_status(tx_hash, "set boosted yields factors in farm"): + return + + # set rewards per block + if 'rpb' not in contract_config: + rpb = 10000 + print_test_step_fail(f"Rewards per block not configured! Setting default: {rpb}") + else: + rpb = contract_config['rpb'] + tx_hash = deployed_contract.set_rewards_per_block(deployer_account, network_providers.proxy, + rpb) + if not network_providers.check_simple_tx_status(tx_hash, "set rewards per block in farm"): return + + # set penalty percent + if 'penalty' not in contract_config: + print_test_step_fail(f"Penalty percent not configured! Setting default: 0") + penalty = 0 + else: + penalty = contract_config['penalty'] + tx_hash = deployed_contract.set_penalty_percent(deployer_account, network_providers.proxy, + penalty) + if not network_providers.check_simple_tx_status(tx_hash, "set penalty percent in farm"): return + + # set minimum farming epochs + if 'min_farming_epochs' not in contract_config: + print_test_step_fail(f"Penalty percent not configured! Setting default: 7") + min_epochs = 7 + else: + min_epochs = contract_config['min_farming_epochs'] + tx_hash = deployed_contract.set_minimum_farming_epochs(deployer_account, network_providers.proxy, + min_epochs) + if not network_providers.check_simple_tx_status(tx_hash, "set min farming epochs in farm"): return + + # Set proxy if applicable + if "proxy" in contract_config or "proxy_v2" in contract_config: + proxy_contract: Optional[DexProxyContract] = None + if "proxy" in contract_config: + proxy_contract = self.contracts[config.PROXIES]. \ + get_deployed_contract_by_index(contract_config['proxy']) + elif "proxy_v2" in contract_config: + proxy_contract = self.contracts[config.PROXIES_V2]. \ + get_deployed_contract_by_index(contract_config['proxy_v2']) + tx_hash = proxy_contract.add_farm_to_intermediate(deployer_account, network_providers.proxy, + contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "set farm to intermediate in proxy"): return + + # whitelist proxy in farm + tx_hash = deployed_contract.add_contract_to_whitelist(deployer_account, network_providers.proxy, + proxy_contract.address) + if not network_providers.check_simple_tx_status(tx_hash, "whitelist proxy in farm"): return + + deployed_contracts.append(deployed_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def farm_community_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + contract_structure = self.contracts[contracts_index] + deployed_contracts = [] + for config_farm in contract_structure.deploy_structure_list: + # deploy farm contract + lp_address = config.ZERO_CONTRACT_ADDRESS + lp_contract: Optional[PairContract] = None + locked_asset_address = config.ZERO_CONTRACT_ADDRESS + locked_asset_contract: Optional[LockedAssetContract] = None + version = FarmContractVersion.V14Unlocked + + farmed_token = self.tokens[config_farm['farmed_token']] + farm_token = config_farm['farm_token'] + if 'farming_token' in config_farm: + farming_token = self.tokens[config_farm['farming_token']] + else: + # TODO: add check to verify existence of pair contract as prerequisite + lp_contract = self.contracts[config.PAIRS].deployed_contracts[config_farm['farming_pool']] + farming_token = lp_contract.lpToken + lp_address = lp_contract.address + + # deploy contract + deployed_farm_contract = FarmContract(farming_token=farming_token, + farm_token="", + farmed_token=farmed_token, + address="", + version=version, + ) + tx_hash, contract_address = deployed_farm_contract.contract_deploy( + deployer_account, network_providers.proxy, contract_structure.bytecode, + [lp_address, locked_asset_address]) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "farm"): return + deployed_farm_contract.address = contract_address + print_test_step_pass(f"Farm contract address: {contract_address}") + + # register farm token and save it + tx_hash = deployed_farm_contract.register_farm_token(deployer_account, network_providers.proxy, farm_token) + if not network_providers.check_complex_tx_status(tx_hash, "register farm token"): return + farm_token_hex = FarmContractDataFetcher(Address(deployed_farm_contract.address), + network_providers.proxy.url).get_data("getFarmTokenId") + deployed_farm_contract.farmToken = hex_to_string(farm_token_hex) + + # Whitelist farm in pool if it's linked to pool + if lp_contract is not None: + tx_hash = lp_contract.whitelist_contract(deployer_account, network_providers.proxy, contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "whitelist farm in pool"): return + + # Whitelist farm in locked asset contract or provide mint role for rewards + if locked_asset_contract is not None: + tx_hash = locked_asset_contract.whitelist_contract(deployer_account, network_providers.proxy, + contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "whitelist farm in locked asset contract"): return + else: + tx_hash = self.esdt_contract.set_special_role_token(deployer_account, network_providers.proxy, + [farmed_token, contract_address, + "ESDTRoleLocalMint"]) + if not network_providers.check_complex_tx_status(tx_hash, "set special role on farmed token"): return + + # set rewards per block + tx_hash = deployed_farm_contract.set_rewards_per_block(deployer_account, network_providers.proxy, + config_farm['rpb']) + if not network_providers.check_simple_tx_status(tx_hash, "set rewards per block in farm"): return + + # Set proxy if applicable + if "proxy" in config_farm: + proxy_contract: DexProxyContract + proxy_contract = self.contracts[config.PROXIES].deployed_contracts[config_farm['proxy']] + tx_hash = proxy_contract.add_farm_to_intermediate(deployer_account, network_providers.proxy, + contract_address) + if not network_providers.check_simple_tx_status(tx_hash, "set farm to intermediate in proxy"): return + + deployed_contracts.append(deployed_farm_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def price_discovery_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + deployed_contracts = [] + contract_structure = self.contracts[contracts_index] + for config_pd in contract_structure.deploy_structure_list: + config_pd_pool = self.contracts[config.PAIRS].deploy_structure_list[config_pd["pool"]] + if not self.contracts[config.SIMPLE_LOCKS].deployed_contracts: + print_test_step_fail("Skipped deploy for price discovery. Simple lock contract not existing.") + return + deployed_simple_lock: SimpleLockContract + deployed_simple_lock = self.contracts[config.SIMPLE_LOCKS].deployed_contracts[0] + simple_lock_sc_address = deployed_simple_lock.address + # start_block = dex_infra.extended_proxy.get_round() + 10 + deployer_shard = network_providers.api.get_address_details(deployer_account.address.bech32())['shard'] + start_block = network_providers.extended_proxy.get_network_status(deployer_shard).current_nonce + 10 + unlock_epoch = network_providers.extended_proxy.get_network_status(deployer_shard).current_epoch + 1 + phase_time = 50 + + launched_token = self.tokens[config_pd_pool['launched_token']] + accepted_token = self.tokens[config_pd_pool['accepted_token']] + redeem_token = config_pd['redeem_token'] + + # contract is set to start 10 blocks after deploy (1 minute) + # each phase lasts 150 blocks (15 minutes) + # deploy contract + deployed_pd_contract = PriceDiscoveryContract( + launched_token_id=launched_token, + accepted_token_id=accepted_token, + redeem_token="", # will be filled after token issue + first_redeem_token_nonce=1, + second_redeem_token_nonce=2, + address="", # will be filled after deploy + locking_sc_address=simple_lock_sc_address, + start_block=start_block, + no_limit_phase_duration_blocks=phase_time, + linear_penalty_phase_duration_blocks=phase_time, + fixed_penalty_phase_duration_blocks=phase_time, + unlock_epoch=unlock_epoch, + min_launched_token_price=10000000000000000000, # 10:1 ratio + min_penalty_percentage=1000000000000, # 10% + max_penalty_percentage=2000000000000, + fixed_penalty_percentage=5000000000000, + ) + + tx_hash, contract_address = deployed_pd_contract.contract_deploy( + deployer_account, network_providers.proxy, contract_structure.bytecode) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "price discovery"): return + deployed_pd_contract.address = contract_address + print_test_step_pass(f"Price discovery contract address: {contract_address}") + + # issue redeem token + tx_hash = deployed_pd_contract.issue_redeem_token(deployer_account, network_providers.proxy, redeem_token) + if not network_providers.check_complex_tx_status(tx_hash, "issue redeem token"): return + redeem_token_hex = PriceDiscoveryContractDataFetcher(Address(deployed_pd_contract.address), + network_providers.proxy.url).get_data("getRedeemTokenId") + if hex_to_string(redeem_token_hex) == "EGLD": + print_test_step_fail(f"FAIL: contract failed to set the issued token!") + return + deployed_pd_contract.redeem_token = hex_to_string(redeem_token_hex) + + # create initial redeem tokens + tx_hash = deployed_pd_contract.create_initial_redeem_tokens(deployer_account, network_providers.proxy) + if not network_providers.check_complex_tx_status(tx_hash, "create initial redeem tokens"): return + + deployed_contracts.append(deployed_pd_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def staking_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + deployed_contracts = [] + contract_structure = self.contracts[contracts_index] + for config_staking in contract_structure.deploy_structure_list: + staking_token = self.tokens[config_staking['staking_token']] + stake_token = config_staking['stake_token'] + stake_token_name = config_staking['stake_token_name'] + max_apr = config_staking['apr'] + rewards_per_block = config_staking['rpb'] + unbond_epochs = config_staking['unbond_epochs'] + topup_rewards = config_staking['rewards'] + version = StakingContractVersion.V1 if contracts_index == config.STAKINGS else StakingContractVersion.V2 + + # deploy contract + deployed_staking_contract = StakingContract( + farming_token=staking_token, + max_apr=max_apr, + rewards_per_block=rewards_per_block, + unbond_epochs=unbond_epochs, + version=version + ) + + args = [] + if version == StakingContractVersion.V2: + args.append(deployer_account.address.bech32()) + if 'admin' in config_staking: + args.append(config_staking['admin']) + + tx_hash, contract_address = deployed_staking_contract.contract_deploy( + deployer_account, network_providers.proxy, contract_structure.bytecode, args) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "stake contract"): return + deployed_staking_contract.address = contract_address + print_test_step_pass(f"Stake contract address: {contract_address}") + + # register farm token and save it + tx_hash = deployed_staking_contract.register_farm_token(deployer_account, network_providers.proxy, + [stake_token_name, stake_token]) + if not network_providers.check_complex_tx_status(tx_hash, "register stake token"): return + farm_token_hex = StakingContractDataFetcher(Address(deployed_staking_contract.address), + network_providers.proxy.url).get_data("getFarmTokenId") + deployed_staking_contract.farm_token = hex_to_string(farm_token_hex) + + # set rewards per block + tx_hash = deployed_staking_contract.set_rewards_per_block(deployer_account, network_providers.proxy, + rewards_per_block) + if not network_providers.check_simple_tx_status(tx_hash, "set rewards per block in stake contract"): return + + # topup rewards + tx_hash = deployed_staking_contract.topup_rewards(deployer_account, network_providers.proxy, topup_rewards) + if not network_providers.check_simple_tx_status(tx_hash, "topup rewards in stake contract"): return + + deployed_contracts.append(deployed_staking_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts + + def metastaking_deploy(self, contracts_index: str, deployer_account: Account, network_providers: NetworkProviders): + deployed_contracts = [] + contract_structure = self.contracts[contracts_index] + for config_metastaking in contract_structure.deploy_structure_list: + staking_token = self.tokens[config_metastaking['token']] + metastake_token = config_metastaking['metastake_token'] + metastake_token_name = config_metastaking['metastake_token_name'] + lp: Optional[PairContract] = None + if 'pool' in config_metastaking: + lp = self.contracts[config.PAIRS].get_deployed_contract_by_index(config_metastaking['pool']) + elif 'pool_v2' in config_metastaking: + lp = self.contracts[config.PAIRS_V2].get_deployed_contract_by_index(config_metastaking['pool_v2']) + else: + print_test_step_fail(f"Aborting deploy: no farm pool for metastaking deploy") + return + farm: Optional[FarmContract] = None + if 'farm_unlocked' in config_metastaking: + farm = self.contracts[config.FARMS_UNLOCKED].get_deployed_contract_by_index( + config_metastaking['farm_unlocked']) + elif 'farm_locked' in config_metastaking: + farm = self.contracts[config.FARMS_LOCKED].get_deployed_contract_by_index( + config_metastaking['farm_locked']) + elif 'farm_boosted' in config_metastaking: + farm = self.contracts[config.FARMS_V2].get_deployed_contract_by_index( + config_metastaking['farm_boosted']) + else: + print_test_step_fail(f"Aborting deploy: no farm configured for metastaking deploy") + return + + staking: Optional[StakingContract] = None + if 'staking' in config_metastaking: + staking = self.contracts[config.STAKINGS].get_deployed_contract_by_index(config_metastaking['staking']) + elif 'staking_v2' in config_metastaking: + staking = self.contracts[config.STAKINGS_V2].get_deployed_contract_by_index( + config_metastaking['staking_v2']) + + # deploy contract + deployed_metastaking_contract = MetaStakingContract( + staking_token=staking_token, + lp_token=lp.lpToken, + farm_token=farm.farmToken, + stake_token=staking.farm_token, + lp_address=lp.address, + farm_address=farm.address, + stake_address=staking.address + ) + + tx_hash, contract_address = deployed_metastaking_contract.contract_deploy( + deployer_account, network_providers.proxy, contract_structure.bytecode) + # check for deployment success and save the deployed address + if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "metastake"): + return + deployed_metastaking_contract.address = contract_address + print_test_step_pass(f"Metastake contract address: {contract_address}") + + # register metastake token and save it + tx_hash = deployed_metastaking_contract.register_dual_yield_token(deployer_account, network_providers.proxy, + [metastake_token_name, metastake_token]) + if not network_providers.check_complex_tx_status(tx_hash, "register metastake token"): + return + farm_token_hex = MetaStakingContractDataFetcher(Address(deployed_metastaking_contract.address), + network_providers.proxy.url).get_data("getDualYieldTokenId") + deployed_metastaking_contract.metastake_token = hex_to_string(farm_token_hex) + + # whitelist in pair contract + tx_hash = lp.whitelist_contract(deployer_account, network_providers.proxy, contract_address) + if not network_providers.check_simple_tx_status(tx_hash, + "whitelist metastaking contract in pair contract"): + return + + # whitelist in farm contract + tx_hash = farm.add_contract_to_whitelist(deployer_account, network_providers.proxy, contract_address) + if not network_providers.check_simple_tx_status(tx_hash, + "whitelist metastaking contract in farm contract"): + return + + # whitelist in staking contract + tx_hash = staking.whitelist_contract(deployer_account, network_providers.proxy, contract_address) + if not network_providers.check_simple_tx_status(tx_hash, + "whitelist metastaking contract in staking contract"): + return + + deployed_contracts.append(deployed_metastaking_contract) + self.contracts[contracts_index].deployed_contracts = deployed_contracts diff --git a/deploy/issue_tokens.py b/deploy/issue_tokens.py new file mode 100644 index 0000000..b222f3d --- /dev/null +++ b/deploy/issue_tokens.py @@ -0,0 +1,96 @@ +import logging +import sys +from argparse import ArgumentParser +from typing import List + +import config +from arrows.stress.esdtnft.shared import (build_token_name, build_token_ticker, + load_contracts, make_call_arg_ascii, + make_call_arg_pubkey) +from arrows.stress.shared import BunchOfAccounts, broadcast_transactions +from erdpy.contracts import SmartContract +from erdpy.proxy.core import ElrondProxy +from erdpy.transactions import Transaction + + +def main(cli_args: List[str]): + logging.basicConfig(level=logging.ERROR) + + parser = ArgumentParser() + parser.add_argument("--proxy", default=config.DEFAULT_PROXY) + parser.add_argument("--accounts", default=config.DEFAULT_OWNER) + parser.add_argument("--contracts", default=config.get_default_contracts_file()) + parser.add_argument("--sleep-between-chunks", type=int, default=5) + parser.add_argument("--chunk-size", type=int, default=400) + parser.add_argument("--from-shard") + parser.add_argument("--via-shard") + parser.add_argument("--base-gas-limit", type=int, default=config.DEFAULT_GAS_BASE_LIMIT_ISSUE) + parser.add_argument("--gas-limit", type=int, default=0) + parser.add_argument("--num-tokens", type=int, default=1) + parser.add_argument("--num-decimals", type=int, default=18) + parser.add_argument("--supply-exp", type=int, default=27) + parser.add_argument("--tokens-prefix", default=config.DEFAULT_TOKEN_PREFIX) + parser.add_argument("--value", default=str(config.DEFAULT_ISSUE_TOKEN_PRICE)) + parser.add_argument("--yes", action="store_true", default=False) + parser.add_argument("--mode", choices=["direct", "via"], default="direct") + + args = parser.parse_args(cli_args) + + proxy = ElrondProxy(args.proxy) + network = proxy.get_network_config() + + bunch_of_accounts = BunchOfAccounts.load_accounts_from_files([args.accounts]) + # bunch_of_accounts.sync_nonces(proxy) + accounts = bunch_of_accounts.get_all() if args.from_shard is None else bunch_of_accounts.get_in_shard(int(args.from_shard)) + account = accounts[0] # issue tokens only for SC owner account to improve times on large number of accounts + account.sync_nonce(proxy) + + tokens_system_contract = SmartContract(address=config.TOKENS_CONTRACT_ADDRESS) + + supply = pow(10, args.supply_exp) + num_decimals = args.num_decimals + prefix = args.tokens_prefix + print("Supply: ", supply, "Decimals: ", num_decimals, "Prefix: ", prefix) + print("Number of tokens: ", args.num_tokens) + + def issue_token(): + for i in range(0, args.num_tokens): + account = accounts[i] + token_name, token_name_hex = build_token_name(account.address, prefix) + token_ticker, token_ticker_hex = build_token_ticker(account.address, prefix) + sc_args = [token_name_hex, token_ticker_hex, supply, num_decimals] + tx_data = tokens_system_contract.prepare_execute_transaction_data("issue", sc_args) + + gas_limit = args.gas_limit or args.base_gas_limit + 50000 + 1500 * len(tx_data) + value = args.value + + tx = Transaction() + tx.nonce = account.nonce + tx.value = value + tx.sender = account.address.bech32() + tx.receiver = tokens_system_contract.address.bech32() + tx.gasPrice = network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = str(network.chain_id) + tx.version = network.min_tx_version + tx.sign(account) + + print("Holder account: ", account.address) + print("Token name: ", token_name) + print("Token ticker: ", token_ticker) + + transactions.append(tx) + account.nonce += 1 + + transactions: List[Transaction] = [] + + issue_token() + + hashes = broadcast_transactions(transactions, proxy, args.chunk_size, sleep=args.sleep_between_chunks, confirm_yes=args.yes) + + return hashes + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/deploy/populate_deploy_lists.py b/deploy/populate_deploy_lists.py new file mode 100644 index 0000000..9f5b3c3 --- /dev/null +++ b/deploy/populate_deploy_lists.py @@ -0,0 +1,32 @@ +import json + + +def load_json_as_dict(json_file: str) -> json: + with open(json_file, 'r') as file: + json_dict = json.load(file) + + return json_dict + + +def populate_list(json_file: str, key: str) -> list: + json_dict = load_json_as_dict(json_file) + values = [] + + for i in range(len(json_dict[key])): + values.append(json_dict[key][i]) + + return values + + +def get_token_prefix(json_file: str) -> str: + json_dict = load_json_as_dict(json_file) + + prefix = json_dict['token']['token_prefix'] + return prefix + + +def get_number_of_tokens(json_file: str) -> int: + json_dict = load_json_as_dict(json_file) + + number_of_tokens = json_dict['token']['number_of_tokens'] + return number_of_tokens diff --git a/deploy/sync_tokens.py b/deploy/sync_tokens.py new file mode 100644 index 0000000..d0bdb6d --- /dev/null +++ b/deploy/sync_tokens.py @@ -0,0 +1,53 @@ +import logging +import sys +from argparse import ArgumentParser +from multiprocessing.dummy import Pool +from typing import List + +import config +from deploy.tokens_tracks import BunchOfTracks +from arrows.stress.shared import BunchOfAccounts +from erdpy.accounts import Address +from erdpy.errors import ProxyRequestError +from erdpy.proxy.core import ElrondProxy + + +def main(cli_args: List[str]): + logging.basicConfig(level=logging.ERROR) + + parser = ArgumentParser() + parser.add_argument("--proxy", default=config.DEFAULT_PROXY) + parser.add_argument("--accounts", default=config.DEFAULT_OWNER) + parser.add_argument("--tokens", default=config.get_default_tokens_file()) + parser.add_argument("--tokens-prefix", default=config.DEFAULT_TOKEN_PREFIX) + + args = parser.parse_args(cli_args) + + proxy = ElrondProxy(args.proxy) + print(proxy.url) + bunch_of_accounts = BunchOfAccounts.load_accounts_from_files([args.accounts]) + accounts = bunch_of_accounts.get_all() + # sync tokens only for SC owner to optimize time + addresses = [item.address for item in accounts] + print(f"Will fetch tokens for {len(accounts)} users. Total: {len(addresses)} addresses.") + + tracks = BunchOfTracks(args.tokens_prefix) + + def get_for_address(address: Address): + try: + tokens = proxy.get_esdt_tokens(address) + tracks.put_for_account(address, tokens) + except ProxyRequestError as error: + print(error) + + Pool(25).map(get_for_address, addresses) + + # tracks.put_all_tokens(proxy.get_all_tokens()) + tracks.save(args.tokens) + + print("Num all_tokens", len(tracks.get_all_tokens())) + print("Num all entries", len(tracks.get_all_individual_assets())) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/deploy/tokens_tracks.py b/deploy/tokens_tracks.py new file mode 100644 index 0000000..e403552 --- /dev/null +++ b/deploy/tokens_tracks.py @@ -0,0 +1,100 @@ + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, Dict, List, Tuple + +from erdpy import utils +from erdpy.accounts import Address + + +class BunchOfTracks: + def __init__(self, prefix: str = "") -> None: + self.accounts_by_token: Dict[str, Any] = dict() + self.tokens_by_holder: Dict[List[str]] = dict() + self.all_tokens: List[str] = [] + self.prefix = prefix.upper() + + def put_for_account(self, address: Address, tokens: Dict[str, Any]): + for token in tokens: + if not token.startswith(self.prefix): + continue + + balance = tokens[token]["balance"] + + if token not in self.accounts_by_token: + self.accounts_by_token[token] = dict() + + self.accounts_by_token[token][address.bech32()] = balance + + def put_all_tokens(self, tokens: List[str]): + self.all_tokens = [token for token in tokens if token.startswith(self.prefix)] + + def get_all_tokens(self) -> List[str]: + return self.all_tokens + + def get_all_individual_assets(self) -> List[Tuple[Address, str]]: + result: List[Tuple[Address, str]] = [] + + for key in self.accounts_by_token: + address = self.accounts_by_token[key] + result.append((address, key)) + + return result + + def get_whale(self, identifier: str) -> Address: + holders = self.accounts_by_token[identifier] + + max_balance = 0 + whale = None + + for holder in holders: + balance = int(holders[holder]) + + if balance > max_balance: + max_balance = balance + whale = holder + + return Address(whale) + + @classmethod + def load(cls, file: str) -> BunchOfTracks: + instance = cls() + if not Path(file).exists(): + return instance + + with open(file) as f: + data = json.load(f) + + instance.accounts_by_token = data.get("accounts_by_token", dict()) + instance.all_tokens = data.get("all_tokens", []) + instance.build_index_tokens_by_holder() + + return instance + + def save(self, file: str): + print(file) + utils.ensure_folder(Path(file).parent) + utils.write_json_file(str(file), { + "accounts_by_token": self.accounts_by_token, + "all_tokens": self.all_tokens + }) + + def build_index_tokens_by_holder(self): + print("build_index_tokens_by_holder - begin") + + for token in self.accounts_by_token: + holders = self.accounts_by_token[token] + + for address in holders: + if address not in self.tokens_by_holder: + self.tokens_by_holder[address] = [] + + self.tokens_by_holder[address].append(token) + + print("build_index_tokens_by_holder - end") + + def get_tokens_by_holder(self, address: Address): + result = self.tokens_by_holder.get(address.bech32(), []) + return result diff --git a/events/event_generators.py b/events/event_generators.py new file mode 100644 index 0000000..bb23a10 --- /dev/null +++ b/events/event_generators.py @@ -0,0 +1,929 @@ +import random +import sys +import traceback + +import config +from context import Context +from contracts.dex_proxy_contract import (DexProxyAddLiquidityEvent, DexProxyClaimRewardsEvent, + DexProxyCompoundRewardsEvent, DexProxyEnterFarmEvent, + DexProxyExitFarmEvent, DexProxyRemoveLiquidityEvent) +from contracts.farm_contract import FarmContract + +from contracts.metastaking_contract import MetaStakingContract +from contracts.staking_contract import StakingContract +from contracts.pair_contract import (AddLiquidityEvent, RemoveLiquidityEvent, SwapFixedInputEvent, + SwapFixedOutputEvent, PairContract, SetCorrectReservesEvent) +from contracts.price_discovery_contract import PriceDiscoveryContract +from events.farm_events import (ClaimRewardsFarmEvent, CompoundRewardsFarmEvent, SetTokenBalanceEvent, + EnterFarmEvent, ExitFarmEvent, MigratePositionFarmEvent) +from events.metastake_events import (EnterMetastakeEvent, ExitMetastakeEvent, + ClaimRewardsMetastakeEvent) +from events.price_discovery_events import (DepositPDLiquidityEvent, RedeemPDLPTokensEvent, + WithdrawPDLiquidityEvent) +from utils.contract_data_fetchers import PairContractDataFetcher +from utils.results_logger import FarmEventResultLogData +from utils.utils_chain import (prevent_spam_crash_elrond_proxy_go, + get_token_details_for_address, get_all_token_nonces_details_for_account, + print_test_step_fail, decode_merged_attributes, dec_to_padded_hex) +from erdpy.accounts import Account, Address + + +def generate_add_liquidity_event(context: Context, user_account: Account, pair_contract: PairContract): + print('Attempt addLiquidityEvent') + txhash = '' + try: + contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.proxy.url) + + tokens = [pair_contract.firstToken, pair_contract.secondToken] + + _, amount_token_a, _ = get_token_details_for_address(tokens[0], user_account.address.bech32(), context.proxy) + _, amount_token_b, _ = get_token_details_for_address(tokens[1], user_account.address.bech32(), context.proxy) + + if amount_token_a <= 0 or amount_token_b <= 0: + print_test_step_fail(f"Skipped add liquidity because needed tokens NOT found in account.") + return + + max_amount_a = int(amount_token_a * context.add_liquidity_max_amount) + # should do a try except block on get equivalent + equivalent_amount_b = contract_data_fetcher.get_data("getEquivalent", + ["0x" + tokens[0].encode('utf-8').hex(), + max_amount_a]) + + if equivalent_amount_b <= 0 or equivalent_amount_b > amount_token_b: + print_test_step_fail(f'Minimum token equivalent amount not satisfied.') + return + + amount_token_b_min = context.get_slippaged_below_value(equivalent_amount_b) + amount_token_a_min = context.get_slippaged_below_value(max_amount_a) + + event = AddLiquidityEvent( + tokens[0], max_amount_a, amount_token_a_min, + tokens[1], equivalent_amount_b, amount_token_b_min + ) + + set_reserves_event = SetCorrectReservesEvent() + context.observable.set_event(pair_contract, user_account, set_reserves_event, '') + + txhash = pair_contract.addLiquidity(context.network_provider, user_account, event) + context.observable.set_event(pair_contract, user_account, event, txhash) + + except Exception as ex: + print(f'Exception encountered: {ex}') + + return txhash + + +def generateRandomAddLiquidityEvent(context: Context): + userAccount = context.get_random_user_account() + pairContract = context.get_random_pair_contract() + generate_add_liquidity_event(context, userAccount, pairContract) + + +def generate_add_initial_liquidity_event(context: Context, user_account: Account, pair_contract: PairContract): + tokens = [pair_contract.firstToken, pair_contract.secondToken] + + event = AddLiquidityEvent( + tokens[0], 2000, 1, + tokens[1], 2000, 1 + ) + pair_contract.addInitialLiquidity(context.network_provider, user_account, event) + + +def generate_remove_liquidity_event(context: Context, user_account: Account, pair_contract: PairContract): + print('Attempt removeLiquidityEvent') + txhash = '' + try: + contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.proxy.url) + # userAccount = context.get_random_user_account() + # pairContract = context.get_random_pair_contract() + + _, amount_lp_token, _ = get_token_details_for_address(pair_contract.lpToken, user_account.address.bech32(), context.proxy) + if amount_lp_token <= 0: + print(f"Skipped swap because no {pair_contract.lpToken} found in account.") + return + + amount = random.randrange(int(amount_lp_token * context.remove_liquidity_max_amount)) + token_amounts = contract_data_fetcher.get_data("getTokensForGivenPosition", + [amount]) + decoding_schema = { + 'token_id': 'string', + 'token_nonce': 'u64', + 'amount': 'biguint' + } + + first_token_deserialized = decode_merged_attributes(token_amounts[0], decoding_schema) + second_token_deserialized = decode_merged_attributes(token_amounts[1], decoding_schema) + + event = RemoveLiquidityEvent( + amount, + pair_contract.firstToken, + context.get_slippaged_below_value(first_token_deserialized['amount']), + pair_contract.secondToken, + context.get_slippaged_below_value(second_token_deserialized['amount']) + ) + + set_reserves_event = SetCorrectReservesEvent() + context.observable.set_event(pair_contract, user_account, set_reserves_event, '') + + txhash = pair_contract.removeLiquidity(context.network_provider, user_account, event) + context.observable.set_event(pair_contract, user_account, event, txhash) + + except Exception as ex: + print(f'Exception encountered: {ex}') + + return txhash + + +def generate_swap_fixed_input(context: Context, user_account: Account, pair_contract: PairContract): + print('Attempt swapFixedInputEvent') + txhash = '' + try: + contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.proxy.url) + + tokens = [pair_contract.firstToken, pair_contract.secondToken] + random.shuffle(tokens) + + _, amount_token_a, _ = get_token_details_for_address(tokens[0], user_account.address.bech32(), context.proxy) + if amount_token_a <= 0: + print(f"Skipped swap because no {tokens[0]} found in account.") + return + amount_token_a_swapped = random.randrange(int(amount_token_a * context.swap_min_tokens_to_spend), + int(amount_token_a * context.swap_max_tokens_to_spend)) + + equivalent_amount_token_b = contract_data_fetcher.get_data("getAmountOut", + ["0x"+tokens[0].encode('utf-8').hex(), + amount_token_a_swapped]) + + if equivalent_amount_token_b <= 0: + print_test_step_fail(f'Minimum token equivalent amount not satisfied. Token amount: {equivalent_amount_token_b}') + return + + amount_token_b_min = context.get_slippaged_below_value(equivalent_amount_token_b) + + event = SwapFixedInputEvent( + tokens[0], amount_token_a_swapped, tokens[1], amount_token_b_min + ) + + set_reserves_event = SetCorrectReservesEvent() + context.observable.set_event(pair_contract, user_account, set_reserves_event, '') + + txhash = pair_contract.swapFixedInput(context.network_provider, user_account, event) + context.observable.set_event(pair_contract, user_account, event, txhash) + + except Exception as ex: + print(f'Exception encountered: {ex}') + + return txhash + + +def generate_random_swap_fixed_input(context: Context): + userAccount = context.get_random_user_account() + pairContract = context.get_random_pair_contract() + generate_swap_fixed_input(context, userAccount, pairContract) + + +def generate_swap_fixed_output(context: Context, user_account: Account, pair_contract: PairContract): + print('Attempt swapFixedOutputEvent') + txhash = '' + try: + contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.proxy.url) + + tokens = [pair_contract.firstToken, pair_contract.secondToken] + random.shuffle(tokens) + + _, amount_token_a, _ = get_token_details_for_address(tokens[0], user_account.address.bech32(), context.proxy) + if amount_token_a <= 0: + print(f"Skipped swap because no {tokens[0]} found in account.") + return + amount_token_a_max = random.randrange(int(amount_token_a * context.swap_max_tokens_to_spend)) + + # TODO: switch to getAmountIn + equivalent_amount_token_b = contract_data_fetcher.get_data("getAmountOut", + ["0x"+tokens[0].encode('utf-8').hex(), + amount_token_a_max]) + # TODO: apply slippage on token A + event = SwapFixedOutputEvent( + tokens[0], amount_token_a_max, tokens[1], context.get_slippaged_below_value(equivalent_amount_token_b) + ) + + set_reserves_event = SetCorrectReservesEvent() + context.observable.set_event(pair_contract, user_account, set_reserves_event, '') + + txhash = pair_contract.swapFixedOutput(context.network_provider, user_account, event) + context.observable.set_event(pair_contract, user_account, event, txhash) + + except Exception as ex: + print(f'Exception encountered: {ex}') + + return txhash + + +def generate_random_swap_fixed_output(context: Context): + userAccount = context.get_random_user_account() + pairContract = context.get_random_pair_contract() + generate_swap_fixed_output(context, userAccount, pairContract) + + +def generateEnterFarmEvent(context: Context, userAccount: Account, farmContract: FarmContract, lockRewards: int = 0): + """lockRewards: -1 - random; 0 - unlocked rewards; 1 - locked rewards;""" + print("Attempt generateEnterFarmEvent") + tx_hash = "" + try: + farmToken = farmContract.farmToken + farming_token = farmContract.farmingToken + + farmingTkNonce, farmingTkAmount, _ = get_token_details_for_address(farming_token, userAccount.address, context.proxy) + farmTkNonce, farmTkAmount, _ = get_token_details_for_address(farmToken, userAccount.address, context.proxy) + + if farmingTkNonce == 0 and farmingTkAmount == 0: + print_test_step_fail(f"SKIPPED: No tokens found!") + return + + initial = True if farmTkNonce == 0 else False + + # set correct token balance in case it has been changed since the init of observers + set_token_balance_event = SetTokenBalanceEvent(farming_token, farmingTkAmount, farmingTkNonce) + context.observable.set_event(None, userAccount, set_token_balance_event, '') + + # amount to add into farm + farmingTkAmount = random.randrange(int(farmingTkAmount * context.enter_farm_max_amount)) + event = EnterFarmEvent( + farming_token, farmingTkNonce, farmingTkAmount, farmToken, farmTkNonce, farmTkAmount + ) + + # pre-event logging + event_log = FarmEventResultLogData() + event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) + event_log.set_pre_event_data(context.proxy) + + tx_hash = farmContract.enterFarm(context.network_provider, userAccount, event, lockRewards, initial) + context.observable.set_event(farmContract, userAccount, event, tx_hash) + + # post-event logging + event_log.set_post_event_data(tx_hash, context.proxy) + context.results_logger.add_event_log(event_log) + + except Exception as ex: + print("Exception encountered:", ex) + traceback.print_exception(*sys.exc_info()) + + return tx_hash + + +def generateEnterStakingEvent(context: Context, user: Account, staking_contract: StakingContract): + print('Attempt generateEnterStakingEvent') + tx_hash = '' + try: + staking_token = staking_contract.farming_token + farm_token = staking_contract.farm_token + + staking_token_nonce, staking_token_amount, _ = get_token_details_for_address(staking_token, + user.address, + context.proxy) + farm_token_nonce, farm_token_amount, _ = get_token_details_for_address(farm_token, + user.address, + context.proxy) + if not staking_token_amount: + print_test_step_fail('SKIPPED enterStakingEvent: No tokens found!') + return + + # set correct token balance in case it has been changed since the init of observers + set_token_balance_event = SetTokenBalanceEvent(staking_token, staking_token_amount, staking_token_nonce) + context.observable.set_event(None, user, set_token_balance_event, '') + + staking_token_amount = random.randrange(int(staking_token_amount * context.enter_metastake_max_amount)) + + event = EnterFarmEvent( + staking_token, staking_token_nonce, staking_token_amount, + farm_token, farm_token_nonce, farm_token_amount + ) + + tx_hash = staking_contract.stake_farm(context.network_provider, user, event, True) + context.observable.set_event(staking_contract, user, event, tx_hash) + + except Exception as ex: + print(f'Exception encountered: {ex}') + + return tx_hash + + +def generateEnterMetastakeEvent(context: Context, user: Account, metastake_contract: MetaStakingContract): + print('Attempt generateEnterMetastakeEvent') + tx_hash = "" + try: + metastake_token = metastake_contract.metastake_token + staking_token = metastake_contract.farm_token + + staking_token_nonce, staking_token_amount, _ = get_token_details_for_address(staking_token, + user.address, + context.proxy) + metastake_token_nonce, metastake_token_amount, _ = get_token_details_for_address(metastake_token, + user.address, + context.proxy) + + if staking_token_nonce == 0 and staking_token_amount == 0: + print_test_step_fail(f"SKIPPED: No tokens found!") + return + + initial = True if metastake_token_nonce == 0 else False + + # set correct token balance in case it has been changed since the init of observers + set_token_balance_event = SetTokenBalanceEvent(staking_token, staking_token_amount, staking_token_nonce) + context.observable.set_event(None, user, set_token_balance_event, '') + + # update data for staking, farm and pair trackers inside metastaking tracker + update_data_event = SetCorrectReservesEvent() + context.observable.set_event(metastake_contract, user, update_data_event, '') + + # amount to enter metastake + staking_token_amount = random.randrange(int(staking_token_amount * context.enter_metastake_max_amount)) + + event = EnterMetastakeEvent(staking_token, staking_token_nonce, staking_token_amount, + metastake_token, metastake_token_nonce, metastake_token_amount) + + tx_hash = metastake_contract.enter_metastake(context.network_provider, user, event, initial) + context.observable.set_event(metastake_contract, user, event, tx_hash) + + except Exception as ex: + print('Exception encountered: ', ex) + + return tx_hash + + +def generateEnterFarmv12Event(context: Context, userAccount: Account, farmContract: FarmContract): + lockRewards = random.randint(0, 1) + generateEnterFarmEvent(context, userAccount, farmContract, lockRewards) + + +def generateRandomEnterFarmEvent(context: Context): + userAccount = context.get_random_user_account() + farmContract = context.get_random_farm_contract() + generateEnterFarmEvent(context, userAccount, farmContract) + + +def generateExitFarmEvent(context: Context, userAccount: Account, farmContract: FarmContract): + print("Attempt generateExitFarmEvent") + tx_hash = "" + try: + farmTkNonce, farmTkAmount, farmTkAttr = get_token_details_for_address(farmContract.farmToken, + userAccount.address, context.proxy) + if farmTkNonce == 0: + print(f"Skipped exit farm event. No token retrieved.") + return + + # set correct token balance in case it has been changed since the init of observers + set_token_balance_event = SetTokenBalanceEvent(farmContract.farmToken, farmTkAmount, farmTkNonce) + context.observable.set_event(None, userAccount, set_token_balance_event, '') + + # amount to exit from farm + farmTkAmount = random.randrange(int(farmTkAmount * context.exit_farm_max_amount)) + event = ExitFarmEvent(farmContract.farmToken, farmTkAmount, farmTkNonce, farmTkAttr) + + # pre-event logging + event_log = FarmEventResultLogData() + event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) + event_log.set_pre_event_data(context.proxy) + + tx_hash = farmContract.exitFarm(context.network_provider, userAccount, event) + context.observable.set_event(farmContract, userAccount, event, tx_hash) + + # post-event logging + event_log.set_post_event_data(tx_hash, context.proxy) + context.results_logger.add_event_log(event_log) + + except Exception as ex: + print("Exception encountered:", ex) + traceback.print_exception(*sys.exc_info()) + + return tx_hash + + +def generateUnstakeEvent(context: Context, user: Account, staking_contract: StakingContract): + print('Attempt unstakingEvent') + tx_hash = '' + try: + stake_token = staking_contract.farm_token + stake_token_nonce, stake_token_amount, stake_token_attr = get_token_details_for_address(stake_token, + user.address, + context.proxy) + if not stake_token_nonce: + print_test_step_fail('SKIPPED unstakingEvent: No tokens to unstake!') + return + + # set correct token balance in case it has been changed since the init of observers + set_token_balance_event = SetTokenBalanceEvent(stake_token, stake_token_amount, stake_token_nonce) + context.observable.set_event(None, user, set_token_balance_event, '') + + unstake_token_amount = random.randrange(int(stake_token_amount * context.exit_metastake_max_amount)) + + event = ExitFarmEvent( + stake_token, unstake_token_amount, stake_token_nonce, stake_token_attr + ) + + tx_hash = staking_contract.unstake_farm(context.network_provider, user, event) + context.observable.set_event(staking_contract, user, event, tx_hash) + + except Exception as ex: + print(f'Exception encountered: {ex}') + + return tx_hash + + +def get_lp_from_metastake_token_attributes(token_attributes): + """LP amount is the same as FarmTokenAmount""" + + attributes_schema_proxy_staked_tokens = { + 'lp_farm_token_nonce': 'u64', + 'lp_farm_token_amount': 'biguint', + 'staking_farm_token_nonce': 'u64', + 'staking_farm_token_amount': 'biguint', + } + + lp_position = decode_merged_attributes(token_attributes, attributes_schema_proxy_staked_tokens) + return lp_position + + +def generateExitMetastakeEvent(context: Context, user: Account, metastake_contract: MetaStakingContract): + print('Attempt generateExitMetastakeEvent') + tx_hash = "" + try: + metastake_token = metastake_contract.metastake_token + metastake_token_nonce, metastake_token_amount, metastake_token_attributes = get_token_details_for_address( + metastake_token, user.address, context.proxy + ) + if metastake_token_nonce == 0: + print_test_step_fail(f"SKIPPED: No tokens found!") + return + + # set correct token balance in case it has been changed since the init of observers + set_token_balance_event = SetTokenBalanceEvent(metastake_token, metastake_token_amount, metastake_token_nonce) + context.observable.set_event(None, user, set_token_balance_event, '') + + # update data for staking, farm and pair trackers inside metastaking tracker + update_data_event = SetCorrectReservesEvent() + context.observable.set_event(metastake_contract, user, update_data_event, '') + + decoded_metastake_tk_attributes = get_lp_from_metastake_token_attributes(metastake_token_attributes) + + farm_tk_details = context.api.get_nft_data( + metastake_contract.farm_token + '-' + + dec_to_padded_hex(decoded_metastake_tk_attributes['lp_farm_token_nonce']) + ) + + full_metastake_amount = metastake_token_amount + # amount to exit metastake + metastake_token_amount = random.randrange(int(metastake_token_amount * context.exit_metastake_max_amount)) + + event = ExitMetastakeEvent(metastake_contract.metastake_token, metastake_token_amount, + metastake_token_nonce, metastake_token_attributes, full_metastake_amount, + farm_tk_details) + + tx_hash = metastake_contract.exit_metastake(context.network_provider, user, event) + context.observable.set_event(metastake_contract, user, event, tx_hash) + + except Exception as ex: + print('Exception encountered: ', ex) + + return tx_hash + + +def generateRandomExitFarmEvent(context: Context): + userAccount = context.get_random_user_account() + farmContract = context.get_random_farm_contract() + generateExitFarmEvent(context, userAccount, farmContract) + + +def generateClaimRewardsEvent(context: Context, userAccount: Account, farmContract: FarmContract): + print("Attempt generateClaimRewardsEvent") + tx_hash = "" + try: + farmTkNonce, farmTkAmount, farmTkAttributes = get_token_details_for_address(farmContract.farmToken, + userAccount.address, + context.proxy) + if farmTkNonce == 0: + print(f"Skipped claim rewards farm event. No token retrieved.") + return + + farmedTkNonce, farmedTkAmount, _ = get_token_details_for_address(farmContract.farmedToken, + userAccount.address, context.proxy) + + # set correct token balance in case it has been changed since the init of observers + set_token_balance_event = SetTokenBalanceEvent(farmContract.farmedToken, farmedTkAmount, farmedTkNonce) + context.observable.set_event(None, userAccount, set_token_balance_event, '') + + event = ClaimRewardsFarmEvent(farmTkAmount, farmTkNonce, farmTkAttributes) + + # pre-event logging + event_log = FarmEventResultLogData() + event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) + event_log.set_pre_event_data(context.proxy) + + tx_hash = farmContract.claimRewards(context.network_provider, userAccount, event) + context.observable.set_event(farmContract, userAccount, event, tx_hash) + + # post-event logging + event_log.set_post_event_data(tx_hash, context.proxy) + context.results_logger.add_event_log(event_log) + + except Exception as ex: + print("Exception encountered:", ex) + traceback.print_exception(*sys.exc_info()) + + return tx_hash + + +def generateClaimStakingRewardsEvent(context: Context, user: Account, staking_contract: StakingContract): + print('Attemp claimStakingRewardsEvent') + tx_hash = '' + try: + stake_token = staking_contract.farm_token + stake_token_nonce, stake_token_amount, attributes = get_token_details_for_address(stake_token, + user.address, + context.proxy) + if not stake_token_nonce: + print('SKIPPED claimStakingRewardsEvent: No token retrieved!') + return + + # set correct token balance in case it has been changed since the init of observers + set_token_balance_event = SetTokenBalanceEvent(stake_token, stake_token_amount, stake_token_nonce) + context.observable.set_event(None, user, set_token_balance_event, '') + + event = ClaimRewardsFarmEvent(stake_token_amount, stake_token_nonce, attributes) + + tx_hash = staking_contract.claimRewards(context.network_provider, user, event) + context.observable.set_event(staking_contract, user, event, tx_hash) + + except Exception as ex: + print(f'Exception encountered: {ex}') + + return tx_hash + + +def generateClaimMetastakeRewardsEvent(context: Context, user: Account, metastake_contract: MetaStakingContract): + print('Attempt generateClaimMetastakeRewardsEvent') + tx_hash = "" + try: + metastake_token = metastake_contract.metastake_token + metastake_token_nonce, metastake_token_amount, metastake_token_attributes = get_token_details_for_address( + metastake_token, + user.address, + context.proxy + ) + if metastake_token_nonce == 0: + print_test_step_fail(f"SKIPPED: No tokens found!") + return + + # set correct token balance in case it has been changed since the init of observers + set_token_balance_event = SetTokenBalanceEvent(metastake_token, metastake_token_amount, metastake_token_nonce) + context.observable.set_event(None, user, set_token_balance_event, '') + + farm_position = get_lp_from_metastake_token_attributes(metastake_token_attributes) + farm_token_details = context.api.get_nft_data( + metastake_contract.farm_token + '-' + dec_to_padded_hex(farm_position['lp_farm_token_nonce']) + ) + + # update data for staking, farm and pair trackers inside metastaking tracker + update_data_event = SetCorrectReservesEvent() + context.observable.set_event(metastake_contract, user, update_data_event, '') + + event = ClaimRewardsMetastakeEvent(metastake_token_amount, metastake_token_nonce, farm_token_details) + + tx_hash = metastake_contract.claim_rewards_metastaking(context.network_provider, user, event) + context.observable.set_event(metastake_contract, user, event, tx_hash) + + except Exception as ex: + print('Exception encountered: ', ex) + + return tx_hash + + +def generateRandomClaimRewardsEvent(context: Context): + userAccount = context.get_random_user_account() + farmContract = context.get_random_farm_contract() + generateClaimRewardsEvent(context, userAccount, farmContract) + + +def generateCompoundRewardsEvent(context: Context, userAccount: Account, farmContract: FarmContract): + print("Attempt generateCompoundRewardsEvent") + tx_hash = "" + try: + farmTkNonce, farmTkAmount, _ = get_token_details_for_address(farmContract.farmToken, + userAccount.address, context.proxy) + if farmTkNonce == 0: + print(f"Skipped compound rewards farm event. No token retrieved.") + return + + event = CompoundRewardsFarmEvent(farmTkAmount, farmTkNonce) + + # pre-event logging + event_log = FarmEventResultLogData() + event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) + event_log.set_pre_event_data(context.proxy) + + tx_hash = farmContract.compoundRewards(context.network_provider, userAccount, event) + + # post-event logging + event_log.set_post_event_data(tx_hash, context.proxy) + context.results_logger.add_event_log(event_log) + + except Exception as ex: + print("Exception encountered:", ex) + traceback.print_exception(*sys.exc_info()) + + return tx_hash + + +def generateRandomCompoundRewardsEvent(context: Context): + userAccount = context.get_random_user_account() + farmContract = context.get_random_farm_contract() + generateCompoundRewardsEvent(context, userAccount, farmContract) + + +def generate_migrate_farm_event(context: Context, userAccount: Account, farmContract: FarmContract): + print("Attempt generateMigrateFarmEvent") + try: + farmTkNonce, farmTkAmount, _ = get_token_details_for_address(farmContract.farmToken, + userAccount.address, + context.proxy) + if farmTkNonce == 0: + return + + event = MigratePositionFarmEvent(farmTkAmount, farmTkNonce) + + # pre-event logging + event_log = FarmEventResultLogData() + event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) + event_log.set_pre_event_data(context.proxy) + + tx_hash = farmContract.migratePosition(context.network_provider, userAccount, event) + + # post-event logging + event_log.set_post_event_data(tx_hash, context.proxy) + context.results_logger.add_event_log(event_log) + + except Exception as ex: + print("Exception encountered:", ex) + traceback.print_exception(*sys.exc_info()) + + +def generateAddLiquidityProxyEvent(context: Context): + userAccount = context.get_random_user_account() + pairContract = context.pairs[0] + + tokenA = context.wrapped_egld_tkid + tokenB = context.lkmex_tkid + + amount = random.randrange(context.addLiquidityMaxValue) + amountMin = int(amount / 100) + 1 + + nonce = 0 + try: + tokens = context.proxy.get_account_tokens(userAccount.address) + prevent_spam_crash_elrond_proxy_go() + + for token in tokens['esdts'].keys(): + if tokenB in token: + nonce = int(tokens['esdts'][token]['nonce']) + break + + if nonce == 0: + return + + except Exception as ex: + ex = ex + + event = DexProxyAddLiquidityEvent( + pairContract, tokenA, 0, amount, amountMin, + tokenB, nonce, amount, amountMin + ) + context.dexProxyContract.addLiquidityProxy(context, userAccount, event) + + +def generateRemoveLiquidityProxyEvent(context: Context): + userAccount = context.get_random_user_account() + pairContract = context.pairs[0] + + amount = random.randrange(context.removeLiquidityMaxValue) + amountA = int(amount / 100) + 1 + amountB = amountA + + nonce = 0 + try: + tokens = context.proxy.get_account_tokens(userAccount.address) + prevent_spam_crash_elrond_proxy_go() + + for token in tokens['esdts'].keys(): + if context.wrappedLpTokenId in token: + nonce = int(tokens['esdts'][token]['nonce']) + break + + if nonce == 0: + return + + except Exception as ex: + ex = ex + + event = DexProxyRemoveLiquidityEvent( + pairContract, amount, nonce, amountA, amountB + ) + context.dexProxyContract.removeLiquidityProxy(context, userAccount, event) + + +def generateEnterFarmProxyEvent(context: Context, user_account: Account, farm_contract: FarmContract, lock_rewards: int = 0): + + try: + farm_token = farm_contract.proxyContract.farm_token + underlying_farm_token = farm_contract.farmToken + farming_token = farm_contract.proxyContract.farming_token + + farming_tk_nonce, farming_tk_amount, _ = get_token_details_for_address(farming_token, user_account.address, context.proxy) + farm_tk_nonce, farm_tk_amount, _ = get_token_details_for_address(farm_token, user_account.address, context.proxy, underlying_farm_token) + + if farming_tk_nonce == 0: + return + + initial_enter_farm = True if farm_tk_nonce == 0 else False + + # amount to add into farm + farming_tk_amount = random.randrange(min(farming_tk_amount, context.enterFarmMaxValue)) + + event = DexProxyEnterFarmEvent( + farm_contract, farming_token, farming_tk_nonce, farming_tk_amount, farm_token, farm_tk_nonce, farm_tk_amount + ) + + context.dexProxyContract.enterFarmProxy(context, user_account, event, lock_rewards, initial_enter_farm) + + except Exception as ex: + print("Exception encountered:", ex) + + +def generateRandomEnterFarmProxyEvent(context: Context): + userAccount = context.get_random_user_account() + farmContract = context.get_random_farm_contract() + generateEnterFarmProxyEvent(context, userAccount, farmContract, -1) + + +def generateExitFarmProxyEvent(context: Context, userAccount: Account, farmContract: FarmContract): + + try: + farm_token = farmContract.proxyContract.farm_token + underlying_token = farmContract.farmToken + + farm_tk_nonce, farm_tk_amount, _ = get_token_details_for_address(farm_token, userAccount.address, + context.proxy, underlying_token) + if farm_tk_nonce == 0: + return + + # amount to exit from farm + farm_tk_amount = random.randrange(farm_tk_amount) + + event = DexProxyExitFarmEvent( + farmContract, farm_token, farm_tk_nonce, farm_tk_amount + ) + context.dexProxyContract.exitFarmProxy(context, userAccount, event) + + except Exception as ex: + print(ex) + + +def generateRandomExitFarmProxyEvent(context: Context): + userAccount = context.get_random_user_account() + farmContract = context.get_random_farm_contract() + generateExitFarmProxyEvent(context, userAccount, farmContract) + + +def generateClaimRewardsProxyEvent(context: Context, userAccount: Account, farmContract: FarmContract): + + try: + farm_token = farmContract.proxyContract.farm_token + underlying_token = farmContract.farmToken + + farm_tk_nonce, farm_tk_amount, _ = get_token_details_for_address(farm_token, userAccount.address, + context.proxy, underlying_token) + if farm_tk_nonce == 0: + return + + event = DexProxyClaimRewardsEvent( + farmContract, farm_token, farm_tk_nonce, farm_tk_amount + ) + context.dexProxyContract.claimRewardsProxy(context, userAccount, event) + + except Exception as ex: + print(ex) + + +def generateRandomClaimRewardsProxyEvent(context: Context): + userAccount = context.get_random_user_account() + farmContract = context.get_random_farm_contract() + generateClaimRewardsProxyEvent(context, userAccount, farmContract) + + +def generateCompoundRewardsProxyEvent(context: Context, userAccount: Account, farmContract: FarmContract): + + try: + farm_token = farmContract.proxyContract.farm_token + underlying_token = farmContract.farmToken + + farmTkNonce, farmTkAmount, _ = get_token_details_for_address(farm_token, userAccount.address, + context.proxy, underlying_token) + if farmTkNonce == 0: + return + + event = DexProxyCompoundRewardsEvent( + farmContract, farm_token, farmTkNonce, farmTkAmount + ) + context.dexProxyContract.claimRewardsProxy(context, userAccount, event) + + except Exception as ex: + print(ex) + + +def generateRandomCompoundRewardsProxyEvent(context: Context): + userAccount = context.get_random_user_account() + farmContract = context.farms[len(context.farms) - 1] + generateCompoundRewardsProxyEvent(context, userAccount, farmContract) + + +def generate_deposit_pd_liquidity_event(context: Context, user_account: Account, pd_contract: PriceDiscoveryContract): + tokens = [pd_contract.launched_token_id, pd_contract.accepted_token] + # TODO: find a smarter/more configurable method of choosing which token to use + # Option1: Based on account balance (after smart funds distribution e.g. 10% tokenA, 80% tokenB, 10%, mixed tokens) + random.shuffle(tokens) + deposited_token = tokens[0] + + _, amount, _ = get_token_details_for_address(deposited_token, user_account.address, context.proxy) + amount = random.randrange(amount) + + event = DepositPDLiquidityEvent(deposited_token, amount) + tx_hash = pd_contract.deposit_liquidity(context.network_provider, user_account, event) + + # track and check event results + if hasattr(context, 'price_discovery_trackers'): + index = context.get_contract_index(config.PRICE_DISCOVERIES, pd_contract) + context.price_discovery_trackers[index].deposit_event_tracking( + event, user_account.address, tx_hash + ) + + +def generate_random_deposit_pd_liquidity_event(context: Context): + user_account = context.get_random_user_account() + pd_contract = context.get_random_price_discovery_contract() + generate_deposit_pd_liquidity_event(context, user_account, pd_contract) + + +def generate_withdraw_pd_liquidity_event(context: Context, user_account: Account, pd_contract: PriceDiscoveryContract): + # TODO: find a smarter/more configurable method of choosing which token to use + tokens = get_all_token_nonces_details_for_account(pd_contract.redeem_token, user_account.address, context.proxy) + if len(tokens) == 0: + print_test_step_fail(f"Generate withdraw price discovery liquidity failed! No redeem tokens available.") + return + + random.shuffle(tokens) + deposit_token = tokens[0] + nonce = int(deposit_token['nonce']) + amount = random.randrange(int(deposit_token['balance'])) + + event = WithdrawPDLiquidityEvent(pd_contract.redeem_token, nonce, amount) + tx_hash = pd_contract.withdraw_liquidity(context.network_provider, user_account, event) + + # track and check event results + if hasattr(context, 'price_discovery_trackers'): + index = context.get_contract_index(config.PRICE_DISCOVERIES, pd_contract) + context.price_discovery_trackers[index].withdraw_event_tracking( + event, user_account.address, tx_hash + ) + + +def generate_random_withdraw_pd_liquidity_event(context: Context): + user_account = context.get_random_user_account() + pd_contract = context.get_random_price_discovery_contract() + generate_withdraw_pd_liquidity_event(context, user_account, pd_contract) + + +def generate_redeem_pd_liquidity_event(context: Context, user_account: Account, pd_contract: PriceDiscoveryContract): + # TODO: find a smarter/more configurable method of choosing which token to use and how much + tokens = get_all_token_nonces_details_for_account(pd_contract.redeem_token, user_account.address, context.proxy) + if len(tokens) == 0: + print_test_step_fail(f"Generate redeem price discovery liquidity failed! No redeem tokens available.") + return + + random.shuffle(tokens) + deposit_token = tokens[0] + nonce = int(deposit_token['nonce']) + amount = random.randrange(int(deposit_token['balance'])) + + event = RedeemPDLPTokensEvent(pd_contract.redeem_token, nonce, amount) + tx_hash = pd_contract.redeem_liquidity_position(context.network_provider, user_account, event) + + # track and check event results + if hasattr(context, 'price_discovery_trackers'): + index = context.get_contract_index(config.PRICE_DISCOVERIES, pd_contract) + context.price_discovery_trackers[index].redeem_event_tracking( + event, user_account.address, tx_hash + ) + + +def generate_random_redeem_pd_liquidity_event(context: Context): + user_account = context.get_random_user_account() + pd_contract = context.get_random_price_discovery_contract() + generate_redeem_pd_liquidity_event(context, user_account, pd_contract) diff --git a/events/farm_events.py b/events/farm_events.py new file mode 100644 index 0000000..78bd4f9 --- /dev/null +++ b/events/farm_events.py @@ -0,0 +1,46 @@ + + +class EnterFarmEvent: + def __init__(self, + farming_token: str, farming_nonce: int, farming_amount, + farm_token: str, farm_nonce: int, farm_amount): + self.farming_tk = farming_token + self.farming_tk_nonce = farming_nonce + self.farming_tk_amount = farming_amount + self.farm_tk = farm_token + self.farm_tk_nonce = farm_nonce + self.farm_tk_amount = farm_amount + + +class ExitFarmEvent: + def __init__(self, farm_token: str, amount: int, nonce: int, attributes: str): + self.farm_token = farm_token + self.amount = amount + self.nonce = nonce + self.attributes = attributes # hex + + +class ClaimRewardsFarmEvent: + def __init__(self, amount: int, nonce: int, attributes): + self.amount = amount + self.nonce = nonce + self.attributes = attributes + + +class CompoundRewardsFarmEvent: + def __init__(self, amount: int, nonce: int): + self.amount = amount + self.nonce = nonce + + +class MigratePositionFarmEvent: + def __init__(self, amount: int, nonce: int): + self.amount = amount + self.nonce = nonce + + +class SetTokenBalanceEvent: + def __init__(self, token: str, balance: int, nonce: int): + self.token = token + self.balance = balance + self.nonce = nonce diff --git a/events/metastake_events.py b/events/metastake_events.py new file mode 100644 index 0000000..d6ccee6 --- /dev/null +++ b/events/metastake_events.py @@ -0,0 +1,27 @@ +class EnterMetastakeEvent: + def __init__(self, metastaking_token: str, metastaking_nonce: int, metastaking_amount: int, + metastake_token: str, metastake_nonce: int, metastake_amount: int): + self.metastaking_tk = metastaking_token + self.metastaking_tk_nonce = metastaking_nonce + self.metastaking_tk_amount = metastaking_amount + self.metastake_tk = metastake_token + self.metastake_tk_nonce = metastake_nonce + self.metastake_tk_amount = metastake_amount + + +class ExitMetastakeEvent: + def __init__(self, metastake_tk: str, amount: int, nonce: int, attributes, whole_metastake_amount: int, + farm_tk_details: dict): + self.metastake_token = metastake_tk + self.amount = amount + self.nonce = nonce + self.metastake_token_attributes = attributes + self.whole_metastake_token_amount = whole_metastake_amount + self.farm_token_details = farm_tk_details + + +class ClaimRewardsMetastakeEvent: + def __init__(self, amount: int, nonce: int, farm_tk_details: dict): + self.amount = amount + self.nonce = nonce + self.farm_token_details = farm_tk_details diff --git a/events/price_discovery_events.py b/events/price_discovery_events.py new file mode 100644 index 0000000..8a61b86 --- /dev/null +++ b/events/price_discovery_events.py @@ -0,0 +1,30 @@ + +class DepositPDLiquidityEvent: + def __init__(self, + deposit_token: str, + deposit_token_amount: int + ): + self.deposit_token = deposit_token + self.amount = deposit_token_amount + + +class WithdrawPDLiquidityEvent: + def __init__(self, + deposit_lp_token: str, + deposit_lp_token_nonce: int, + deposit_lp_token_amount: int + ): + self.deposit_lp_token = deposit_lp_token + self.nonce = deposit_lp_token_nonce + self.amount = deposit_lp_token_amount + + +class RedeemPDLPTokensEvent: + def __init__(self, + deposit_lp_token: str, + deposit_lp_token_nonce: int, + deposit_lp_token_amount: int + ): + self.deposit_lp_token = deposit_lp_token + self.nonce = deposit_lp_token_nonce + self.amount = deposit_lp_token_amount diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a4f23a2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +ipykernel +multiversx-sdk-core~=0.3.0 +multiversx-sdk-wallet~=0.4.2 +multiversx-sdk-network-providers~=0.6.7 +toml +debugpy \ No newline at end of file diff --git a/scenarios/pair_admin_owner_check.py b/scenarios/pair_admin_owner_check.py new file mode 100644 index 0000000..fadf0c8 --- /dev/null +++ b/scenarios/pair_admin_owner_check.py @@ -0,0 +1,202 @@ +import random +import sys +import time +from typing import List + +import config +from context import Context +from events.event_generators import ( + generate_add_initial_liquidity_event, generate_add_liquidity_event, + generate_swap_fixed_input) +from arrows.stress.send_token_from_minter import main as send_token_from_minter +from arrows.stress.shared import get_shard_of_address +from erdpy.accounts import Account + + +def main(cli_args: List[str]): + context = Context() + create_nonce_file(context) + # send_tokens(context=context) + + owner_account = context.accounts.get_all()[0] + admin_account = context.accounts.get_all()[1] # erd1gvkklm20rk9vg0xnyq0aq3ae3cnle8qxa7eqcevnhfthe5gj9z4s293za5 + user_account = context.accounts.get_all()[3] + + fee_collector_contract = context.get_fee_collector_contract(0) + pair_contract = context.get_pair_v2_contract(0) + router_contract = context.get_router_v2_contract(0) + simple_lock_contract = context.get_simple_lock_contract(0) + + + ### Regular User doesn't have access to Owner specific endpoints ###### + tx_hash = pair_contract.whitelist_contract(user_account, context.proxy, user_account.address) + print("Whitelist Test RegularUser NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + args_add_trusted_swap_pair = [pair_contract.address, pair_contract.firstToken, pair_contract.secondToken] + tx_hash = pair_contract.add_trusted_swap_pair(user_account, context.proxy, args_add_trusted_swap_pair) + print("Add Trusted Swap Pair Test RegularUser NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + args_add_fees_collector = [fee_collector_contract.address, 500] + tx_hash = pair_contract.add_fees_collector(user_account, context.proxy, args_add_fees_collector) + print("Add Fees Collector Test RegularUser NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + args_set_fee_on_via_router = [fee_collector_contract.address, pair_contract.firstToken] + tx_hash = pair_contract.set_fee_on_via_router(user_account, context.proxy, router_contract, args_set_fee_on_via_router) + print("Set Fee On Test RegularUser NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Endpoint can only be called by owner") else "Failed!") + + tx_hash = pair_contract.set_fee_percents(user_account, context.proxy, 500, 300) + print("Set Fee Percent Test RegularUser NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + tx_hash = pair_contract.set_locking_deadline_epoch(user_account, context.proxy, 1000) + print("Set Locking Deadline Test RegularUser NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + tx_hash = pair_contract.set_unlock_epoch(user_account, context.proxy, 1000) + print("Set Unlock Epoch Test RegularUser NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + tx_hash = pair_contract.set_lp_token_identifier(user_account, context.proxy, pair_contract.lpToken) + print("Set LP Token ID Test RegularUser NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + tx_hash = pair_contract.set_extern_swap_gas_limit(user_account, context.proxy, 100000000) + print("Set Extern Swap Gas Limit Test RegularUser NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + # Simple lock energy contract must be deployed + tx_hash = pair_contract.set_locking_sc_address(user_account, context.proxy, simple_lock_contract.address) + print("Set Unlock Epoch Test RegularUser NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + + ### Admin doesn't have access to Owner specific endpoints + tx_hash = pair_contract.whitelist_contract(admin_account, context.proxy, user_account.address) + print("Whitelist Test Admin NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + args_add_trusted_swap_pair = [pair_contract.address, pair_contract.firstToken, pair_contract.secondToken] + tx_hash = pair_contract.add_trusted_swap_pair(admin_account, context.proxy, args_add_trusted_swap_pair) + print("Add Trusted Swap Pair Test Admin NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + args_add_fees_collector = [fee_collector_contract.address, 500] + tx_hash = pair_contract.add_fees_collector(admin_account, context.proxy, args_add_fees_collector) + print("Add Fees Collector Test Admin NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + args_set_fee_on_via_router = [fee_collector_contract.address, pair_contract.firstToken] + tx_hash = pair_contract.set_fee_on_via_router(admin_account, context.proxy, router_contract, args_set_fee_on_via_router) + print("Add Fees Collector Test Admin NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Endpoint can only be called by owner") else "Failed!") + + tx_hash = pair_contract.set_locking_deadline_epoch(admin_account, context.proxy, 1000) + print("Set Locking Deadline Test Admin NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + tx_hash = pair_contract.set_unlock_epoch(admin_account, context.proxy, 1000) + print("Set Unlock Epoch Test Admin NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + tx_hash = pair_contract.set_lp_token_identifier(admin_account, context.proxy, pair_contract.lpToken) + print("Set LP Token ID Test Admin NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + tx_hash = pair_contract.set_extern_swap_gas_limit(admin_account, context.proxy, 100000000) + print("Set Extern Swap Gas Limit Test Admin NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + # Simple lock energy contract must be deployed + tx_hash = pair_contract.set_locking_sc_address(admin_account, context.proxy, simple_lock_contract.address) + print("Set Unlock Epoch Test Admin NO Access -", "Success!" if context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + + ### Owner Has access + tx_hash = pair_contract.whitelist_contract(owner_account, context.proxy, user_account.address) + print("Whitelist Test Owner Has Access -", "Success!" if context.network_provider.check_simple_tx_status(tx_hash, "Owner TX") else "Failed!") + + args_add_trusted_swap_pair = [pair_contract.address, pair_contract.firstToken, pair_contract.secondToken] + tx_hash = pair_contract.add_trusted_swap_pair(owner_account, context.proxy, args_add_trusted_swap_pair) + print("Add Trusted Swap Pair Test Owner Has Access -", "Success!" if context.network_provider.check_simple_tx_status(tx_hash, "Owner TX") else "Failed!") + + args_add_fees_collector = [fee_collector_contract.address, 500] + tx_hash = pair_contract.add_fees_collector(owner_account, context.proxy, args_add_fees_collector) + print("Add Fees Collector Test Owner Has Access -", "Success!" if context.network_provider.check_simple_tx_status(tx_hash, "Owner TX") else "Failed!") + + args_set_fee_on_via_router = [fee_collector_contract.address, pair_contract.firstToken] + tx_hash = pair_contract.set_fee_on_via_router(owner_account, context.proxy, router_contract, args_set_fee_on_via_router) + print("Add Fees Collector Test Owner Has Access -", "Success!" if context.network_provider.check_simple_tx_status(tx_hash, "Owner TX") else "Failed!") + + tx_hash = pair_contract.set_locking_deadline_epoch(owner_account, context.proxy, 1000) + print("Set Locking Deadline Test Owner Has Access -", "Success!" if context.network_provider.check_simple_tx_status(tx_hash, "Owner TX") else "Failed!") + + tx_hash = pair_contract.set_unlock_epoch(owner_account, context.proxy, 1000) + print("Set Unlock Epoch Test Owner Has Access -", "Success!" if context.network_provider.check_simple_tx_status(tx_hash, "Owner TX") else "Failed!") + + tx_hash = pair_contract.set_lp_token_identifier(owner_account, context.proxy, pair_contract.lpToken) + print("Set LP Token ID Test Owner Has Access -", "Success!" if not context.network_provider.check_for_error_operation(tx_hash, "Permission denied") else "Failed!") + + tx_hash = pair_contract.set_extern_swap_gas_limit(owner_account, context.proxy, 100000000) + print("Set Extern Swap Gas Limit Test Owner Has Access -", "Success!" if context.network_provider.check_simple_tx_status(tx_hash, "Owner TX") else "Failed!") + + # Simple lock energy contract must be deployed + tx_hash = pair_contract.set_locking_sc_address(owner_account, context.proxy, simple_lock_contract.address) + print("Set Unlock Epoch Test Owner Has Access -", "Success!" if context.network_provider.check_simple_tx_status(tx_hash, "Owner TX") else "Failed!") + + return + + +def add_initial_liquidity(context: Context, account: Account): + for pair_contract in context.get_contracts(config.PAIRS_V2): + generate_add_initial_liquidity_event(context, context.deployer_account, pair_contract) + time.sleep(config.INTRA_SHARD_DELAY) + pair_contract.resume(context.deployer_account, context.network_provider.proxy) + time.sleep(config.INTRA_SHARD_DELAY) + + +def add_liquidity(context: Context, account: Account): + min_time = 2 + max_time = 10 + deployer_shard = get_shard_of_address(context.deployer_account.address) + sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + else config.INTRA_SHARD_DELAY + + account.sync_nonce(context.network_provider.proxy) + + pair_contract = context.get_pair_v2_contract(0) + generate_add_liquidity_event(context, account, pair_contract) + time.sleep(sleep_time) + + wait_time = random.randint(min_time, max_time) + + print(f'Finished repeat execution. Waiting for {wait_time}s.') + time.sleep(wait_time) + + +def swap(context: Context, account: Account): + min_time = 2 + max_time = 10 + deployer_shard = get_shard_of_address(context.deployer_account.address) + sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + else config.INTRA_SHARD_DELAY + + account.sync_nonce(context.network_provider.proxy) + + pair_contract = context.get_pair_v2_contract(0) + generate_swap_fixed_input(context, account, pair_contract) + time.sleep(sleep_time) + + wait_time = random.randint(min_time, max_time) + + print(f'Finished repeat execution. Waiting for {wait_time}s.') + time.sleep(wait_time) + + +def create_nonce_file(context: Context): + context.accounts.sync_nonces(context.proxy) + context.accounts.store_nonces(context.nonces_file) + context.deployer_account.sync_nonce(context.proxy) + + +def send_tokens(context: Context): + proxy_url = config.DEFAULT_PROXY + accounts = config.DEFAULT_ACCOUNTS + minter = config.DEFAULT_OWNER + amount = 3000000000000000000 + + for token in context.deploy_structure.tokens: + print(f"Funding each account with {amount} {token} tokens.") + args = [f'--proxy={proxy_url}', f'--accounts={accounts}', + f'--minter={minter}', f'--token={token}', f'--amount-atoms={amount}'] + send_token_from_minter(args) + time.sleep(7) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scenarios/pair_v2_check_fee.py b/scenarios/pair_v2_check_fee.py new file mode 100644 index 0000000..8d6a80e --- /dev/null +++ b/scenarios/pair_v2_check_fee.py @@ -0,0 +1,112 @@ +import random +import sys +import time +from typing import List + +import config +from context import Context +from events.event_generators import ( + generate_add_initial_liquidity_event, generate_add_liquidity_event, + generate_swap_fixed_input) +from utils.contract_data_fetchers import ( + FeeCollectorContractDataFetcher, PairContractDataFetcher) +from arrows.stress.send_token_from_minter import main as send_token_from_minter +from arrows.stress.shared import get_shard_of_address +from erdpy.accounts import Account, Address + + +def main(cli_args: List[str]): + context = Context() + create_nonce_file(context) + # send_tokens(context=context) + + user_account = context.accounts.get_all()[0] + pair_contract = context.get_pair_v2_contract(0) + fee_collector_contract = context.get_fee_collector_contract(0) + contract_data_fetcher = FeeCollectorContractDataFetcher(Address(fee_collector_contract.address), context.proxy.url) + tokens = [pair_contract.firstToken, pair_contract.secondToken] + + # add_initial_liquidity(context=context, account=user_account) + # add_liquidity(context=context, account=user_account) + + initial_amount_token = contract_data_fetcher.get_token_reserve(tokens[0]) + swap(context=context, account=user_account) + final_amount_token = contract_data_fetcher.get_token_reserve(tokens[0]) + + print("initial_amount_token = ", initial_amount_token, " final_amount_token = ", final_amount_token) + if final_amount_token <= initial_amount_token: + # TODO Proper throw error + print("Error! Amount didn't increase!") + return -1 + + return + + +def add_initial_liquidity(context: Context, account: Account): + for pair_contract in context.get_contracts(config.PAIRS_V2): + generate_add_initial_liquidity_event(context, context.deployer_account, pair_contract) + time.sleep(config.INTRA_SHARD_DELAY) + pair_contract.resume(context.deployer_account, context.network_provider.proxy) + time.sleep(config.INTRA_SHARD_DELAY) + + +def add_liquidity(context: Context, account: Account): + min_time = 2 + max_time = 10 + deployer_shard = get_shard_of_address(context.deployer_account.address) + sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + else config.INTRA_SHARD_DELAY + + account.sync_nonce(context.network_provider.proxy) + + pair_contract = context.get_pair_v2_contract(0) + generate_add_liquidity_event(context, account, pair_contract) + time.sleep(sleep_time) + + wait_time = random.randint(min_time, max_time) + + print(f'Finished repeat execution. Waiting for {wait_time}s.') + time.sleep(wait_time) + + +def swap(context: Context, account: Account): + min_time = 2 + max_time = 10 + deployer_shard = get_shard_of_address(context.deployer_account.address) + sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + else config.INTRA_SHARD_DELAY + + account.sync_nonce(context.network_provider.proxy) + + pair_contract = context.get_pair_v2_contract(0) + generate_swap_fixed_input(context, account, pair_contract) + time.sleep(sleep_time) + + wait_time = random.randint(min_time, max_time) + + print(f'Finished repeat execution. Waiting for {wait_time}s.') + time.sleep(wait_time) + + +def create_nonce_file(context: Context): + context.accounts.sync_nonces(context.proxy) + context.accounts.store_nonces(context.nonces_file) + context.deployer_account.sync_nonce(context.proxy) + + +def send_tokens(context: Context): + proxy_url = config.DEFAULT_PROXY + accounts = config.DEFAULT_ACCOUNTS + minter = config.DEFAULT_OWNER + amount = 3000000000000000000 + + for token in context.deploy_structure.tokens: + print(f"Funding each account with {amount} {token} tokens.") + args = [f'--proxy={proxy_url}', f'--accounts={accounts}', + f'--minter={minter}', f'--token={token}', f'--amount-atoms={amount}'] + send_token_from_minter(args) + time.sleep(7) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scenarios/scenario_dex_v2_all_in.py b/scenarios/scenario_dex_v2_all_in.py new file mode 100644 index 0000000..d9b2d89 --- /dev/null +++ b/scenarios/scenario_dex_v2_all_in.py @@ -0,0 +1,199 @@ +import concurrent.futures +import random +import sys +import time +import traceback + +import pytest +from itertools import count +from typing import List +from argparse import ArgumentParser + +import config +from context import Context +from contracts.fees_collector_contract import FeesCollectorContract +from contracts.pair_contract import PairContract, AddLiquidityEvent +from contracts.simple_lock_energy_contract import SimpleLockEnergyContract +from events.event_generators import (generate_add_initial_liquidity_event, + generate_add_liquidity_event, + generateEnterFarmEvent, + generateEnterMetastakeEvent, + generateClaimMetastakeRewardsEvent, + generateExitMetastakeEvent, generate_swap_fixed_input) +from utils.contract_data_fetchers import PairContractDataFetcher, SimpleLockEnergyContractDataFetcher +from utils.utils_tx import ESDTToken +from utils.utils_chain import print_test_step_pass, print_condition_assert, nominated_amount, \ + print_test_step_fail, TestStepConditions, get_token_details_for_address, get_all_token_nonces_details_for_account +from arrows.stress.send_token_from_minter import main as send_token_from_minter +from arrows.stress.send_egld_from_minter import main as send_egld_from_minter +from arrows.stress.shared import get_shard_of_address +from erdpy.accounts import Account, Address + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--threads", required=False, default="2") # number of concurrent threads to execute operations + parser.add_argument("--repeats", required=False, default="0") # number of total operations to execute; 0 - infinite + parser.add_argument("--skip-minting", action="store_true", default=False) + args = parser.parse_args(cli_args) + + context = Context() + + if not args.skip_minting: + send_tokens(context) + wait_time = 40 + print(f"Minting accounts done. Waiting for the dust to settle: {wait_time}s until execution start") + time.sleep(wait_time) + + create_nonce_file(context) + + # stress generator for adding liquidity, enter farm, enter metastaking, claim metastaking, exit metastaking + scenarios(context, int(args.threads), int(args.repeats)) + + +def create_nonce_file(context: Context): + context.accounts.sync_nonces(context.proxy) + context.accounts.store_nonces(context.nonces_file) + context.deployer_account.sync_nonce(context.proxy) + + +def send_tokens(context: Context): + proxy_url = config.DEFAULT_PROXY + accounts = config.DEFAULT_ACCOUNTS + minter = config.DEFAULT_OWNER + tk_amount = nominated_amount(100000) + egld_amount = nominated_amount(1) + + for token in context.deploy_structure.tokens: + print(f"Funding each account with {tk_amount} {token} tokens.") + args = [f'--proxy={proxy_url}', f'--accounts={accounts}', + f'--minter={minter}', f'--token={token}', f'--amount-atoms={tk_amount}'] + send_token_from_minter(args) + time.sleep(7) + + print(f"Funding each account with {egld_amount} eGLD tokens.") + args = [f'--proxy={proxy_url}', f'--accounts={accounts}', + f'--minter={minter}', f'--value-atoms={egld_amount}'] + send_egld_from_minter(args) + time.sleep(7) + + +def add_initial_liquidity(context: Context): + # add initial liquidity + pair_contract: PairContract + for pair_contract in context.get_contracts(config.PAIRS_V2): + pair_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.network_provider.proxy.url) + first_token_liquidity = pair_data_fetcher.get_token_reserve(pair_contract.firstToken) + if first_token_liquidity == 0: + event = AddLiquidityEvent( + pair_contract.firstToken, nominated_amount(1000000), 1, + pair_contract.secondToken, nominated_amount(1000000), 1 + ) + pair_contract.addLiquidity(context.network_provider, context.deployer_account, event) + time.sleep(6) + + +def scenarios(context: Context, threads: int, repeats: int): + accounts = context.accounts.get_all() + + context.swap_min_tokens_to_spend = 0.0001 + context.swap_max_tokens_to_spend = 0.001 + + # add initial liquidity in contract if necessary + add_initial_liquidity(context) + + if threads == 1: + """Sequential run""" + for _ in range(repeats if repeats != 0 else 99999999): + scenarios_per_account(context, random.choice(accounts)) + elif threads > 1: + """ Concurential run """ + with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: + jobs = min(threads, repeats) if repeats != 0 else threads + finished_jobs = 0 + futures = [executor.submit(scenarios_per_account, context, random.choice(accounts)) + for _ in range(jobs)] + + # feed the thread workers as they complete each job + while finished_jobs < repeats or repeats == 0: + try: + for future in concurrent.futures.as_completed(futures): + finished_jobs += 1 + print_test_step_pass(f"Finished {finished_jobs} repeats.") + futures.remove(future) + # spawn a new job + futures.append(executor.submit(scenarios_per_account, context, random.choice(accounts))) + if future.exception() is not None: + print_test_step_fail(f"Thread failed: {future.exception()}") + except Exception as ex: + traceback.print_exception(*sys.exc_info()) + print_test_step_fail(f"Something failed: {ex}") + else: + print_test_step_fail(f"Number of threads must be minimum 1!") + return + + +def scenarios_per_account(context: Context, account: Account): + min_time = 10 + max_time = 30 + deployer_shard = get_shard_of_address(context.deployer_account.address) + sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + else 6 + + account.sync_nonce(context.network_provider.proxy) + + pair_contract: PairContract + pair_contract = random.choice(context.get_contracts(config.PAIRS_V2)) + + fees_collector_contract: FeesCollectorContract + fees_collector_contract = context.get_contracts(config.FEES_COLLECTORS)[0] + + simple_lock_energy_contract: SimpleLockEnergyContract + simple_lock_energy_contract = context.get_contracts(config.SIMPLE_LOCKS_ENERGY)[0] + simple_lock_energy_data_fetcher = SimpleLockEnergyContractDataFetcher(Address(simple_lock_energy_contract.address), + context.network_provider.proxy.url) + lock_options = simple_lock_energy_data_fetcher.get_data('getLockOptions') + + # lock tokens + amount = random.randint(1, 10) + lock_period = random.choice(lock_options) + token = ESDTToken(simple_lock_energy_contract.base_token, 0, nominated_amount(amount)) + args = [[token], lock_period] + _ = simple_lock_energy_contract.lock_tokens(account, context.network_provider.proxy, args) + print(f"User: {account.address.bech32()}") + print(f"Locked {amount} tokens for {lock_period} epochs.") + time.sleep(sleep_time) + + # swap tokens + generate_swap_fixed_input(context, account, pair_contract) + time.sleep(sleep_time) + + # unlock tokens early - 20% chance + if random.randint(0, 100) <= 20: + user_tokens = get_all_token_nonces_details_for_account(simple_lock_energy_contract.locked_token, + account.address.bech32(), + context.network_provider.proxy) + if len(user_tokens) > 0: # if user has locked tokens, unlock some from one of them + token_to_unlock = random.choice(user_tokens) + amount = random.randint(1, int(token_to_unlock['balance'])) + token = ESDTToken(simple_lock_energy_contract.locked_token, + token_to_unlock['nonce'], + amount) + _ = simple_lock_energy_contract.unlock_early(account, context.network_provider.proxy, [[token]]) + print(f"User: {account.address.bech32()}") + print(f"Unlocked {amount} tokens {token.get_full_token_name()}") + time.sleep(sleep_time) + + # claim rewards once in a while - 35% chance + if random.randint(0, 100) <= 35: + _ = fees_collector_contract.claim_rewards(account, context.network_provider.proxy) + print(f"User: {account.address.bech32()}") + time.sleep(sleep_time) + + wait_time = random.randint(min_time, max_time) + print(f'Finished repeat execution. Waiting for {wait_time}s.') + time.sleep(wait_time) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scenarios/scenario_fees_collector.py b/scenarios/scenario_fees_collector.py new file mode 100644 index 0000000..d9b2d89 --- /dev/null +++ b/scenarios/scenario_fees_collector.py @@ -0,0 +1,199 @@ +import concurrent.futures +import random +import sys +import time +import traceback + +import pytest +from itertools import count +from typing import List +from argparse import ArgumentParser + +import config +from context import Context +from contracts.fees_collector_contract import FeesCollectorContract +from contracts.pair_contract import PairContract, AddLiquidityEvent +from contracts.simple_lock_energy_contract import SimpleLockEnergyContract +from events.event_generators import (generate_add_initial_liquidity_event, + generate_add_liquidity_event, + generateEnterFarmEvent, + generateEnterMetastakeEvent, + generateClaimMetastakeRewardsEvent, + generateExitMetastakeEvent, generate_swap_fixed_input) +from utils.contract_data_fetchers import PairContractDataFetcher, SimpleLockEnergyContractDataFetcher +from utils.utils_tx import ESDTToken +from utils.utils_chain import print_test_step_pass, print_condition_assert, nominated_amount, \ + print_test_step_fail, TestStepConditions, get_token_details_for_address, get_all_token_nonces_details_for_account +from arrows.stress.send_token_from_minter import main as send_token_from_minter +from arrows.stress.send_egld_from_minter import main as send_egld_from_minter +from arrows.stress.shared import get_shard_of_address +from erdpy.accounts import Account, Address + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--threads", required=False, default="2") # number of concurrent threads to execute operations + parser.add_argument("--repeats", required=False, default="0") # number of total operations to execute; 0 - infinite + parser.add_argument("--skip-minting", action="store_true", default=False) + args = parser.parse_args(cli_args) + + context = Context() + + if not args.skip_minting: + send_tokens(context) + wait_time = 40 + print(f"Minting accounts done. Waiting for the dust to settle: {wait_time}s until execution start") + time.sleep(wait_time) + + create_nonce_file(context) + + # stress generator for adding liquidity, enter farm, enter metastaking, claim metastaking, exit metastaking + scenarios(context, int(args.threads), int(args.repeats)) + + +def create_nonce_file(context: Context): + context.accounts.sync_nonces(context.proxy) + context.accounts.store_nonces(context.nonces_file) + context.deployer_account.sync_nonce(context.proxy) + + +def send_tokens(context: Context): + proxy_url = config.DEFAULT_PROXY + accounts = config.DEFAULT_ACCOUNTS + minter = config.DEFAULT_OWNER + tk_amount = nominated_amount(100000) + egld_amount = nominated_amount(1) + + for token in context.deploy_structure.tokens: + print(f"Funding each account with {tk_amount} {token} tokens.") + args = [f'--proxy={proxy_url}', f'--accounts={accounts}', + f'--minter={minter}', f'--token={token}', f'--amount-atoms={tk_amount}'] + send_token_from_minter(args) + time.sleep(7) + + print(f"Funding each account with {egld_amount} eGLD tokens.") + args = [f'--proxy={proxy_url}', f'--accounts={accounts}', + f'--minter={minter}', f'--value-atoms={egld_amount}'] + send_egld_from_minter(args) + time.sleep(7) + + +def add_initial_liquidity(context: Context): + # add initial liquidity + pair_contract: PairContract + for pair_contract in context.get_contracts(config.PAIRS_V2): + pair_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.network_provider.proxy.url) + first_token_liquidity = pair_data_fetcher.get_token_reserve(pair_contract.firstToken) + if first_token_liquidity == 0: + event = AddLiquidityEvent( + pair_contract.firstToken, nominated_amount(1000000), 1, + pair_contract.secondToken, nominated_amount(1000000), 1 + ) + pair_contract.addLiquidity(context.network_provider, context.deployer_account, event) + time.sleep(6) + + +def scenarios(context: Context, threads: int, repeats: int): + accounts = context.accounts.get_all() + + context.swap_min_tokens_to_spend = 0.0001 + context.swap_max_tokens_to_spend = 0.001 + + # add initial liquidity in contract if necessary + add_initial_liquidity(context) + + if threads == 1: + """Sequential run""" + for _ in range(repeats if repeats != 0 else 99999999): + scenarios_per_account(context, random.choice(accounts)) + elif threads > 1: + """ Concurential run """ + with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: + jobs = min(threads, repeats) if repeats != 0 else threads + finished_jobs = 0 + futures = [executor.submit(scenarios_per_account, context, random.choice(accounts)) + for _ in range(jobs)] + + # feed the thread workers as they complete each job + while finished_jobs < repeats or repeats == 0: + try: + for future in concurrent.futures.as_completed(futures): + finished_jobs += 1 + print_test_step_pass(f"Finished {finished_jobs} repeats.") + futures.remove(future) + # spawn a new job + futures.append(executor.submit(scenarios_per_account, context, random.choice(accounts))) + if future.exception() is not None: + print_test_step_fail(f"Thread failed: {future.exception()}") + except Exception as ex: + traceback.print_exception(*sys.exc_info()) + print_test_step_fail(f"Something failed: {ex}") + else: + print_test_step_fail(f"Number of threads must be minimum 1!") + return + + +def scenarios_per_account(context: Context, account: Account): + min_time = 10 + max_time = 30 + deployer_shard = get_shard_of_address(context.deployer_account.address) + sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + else 6 + + account.sync_nonce(context.network_provider.proxy) + + pair_contract: PairContract + pair_contract = random.choice(context.get_contracts(config.PAIRS_V2)) + + fees_collector_contract: FeesCollectorContract + fees_collector_contract = context.get_contracts(config.FEES_COLLECTORS)[0] + + simple_lock_energy_contract: SimpleLockEnergyContract + simple_lock_energy_contract = context.get_contracts(config.SIMPLE_LOCKS_ENERGY)[0] + simple_lock_energy_data_fetcher = SimpleLockEnergyContractDataFetcher(Address(simple_lock_energy_contract.address), + context.network_provider.proxy.url) + lock_options = simple_lock_energy_data_fetcher.get_data('getLockOptions') + + # lock tokens + amount = random.randint(1, 10) + lock_period = random.choice(lock_options) + token = ESDTToken(simple_lock_energy_contract.base_token, 0, nominated_amount(amount)) + args = [[token], lock_period] + _ = simple_lock_energy_contract.lock_tokens(account, context.network_provider.proxy, args) + print(f"User: {account.address.bech32()}") + print(f"Locked {amount} tokens for {lock_period} epochs.") + time.sleep(sleep_time) + + # swap tokens + generate_swap_fixed_input(context, account, pair_contract) + time.sleep(sleep_time) + + # unlock tokens early - 20% chance + if random.randint(0, 100) <= 20: + user_tokens = get_all_token_nonces_details_for_account(simple_lock_energy_contract.locked_token, + account.address.bech32(), + context.network_provider.proxy) + if len(user_tokens) > 0: # if user has locked tokens, unlock some from one of them + token_to_unlock = random.choice(user_tokens) + amount = random.randint(1, int(token_to_unlock['balance'])) + token = ESDTToken(simple_lock_energy_contract.locked_token, + token_to_unlock['nonce'], + amount) + _ = simple_lock_energy_contract.unlock_early(account, context.network_provider.proxy, [[token]]) + print(f"User: {account.address.bech32()}") + print(f"Unlocked {amount} tokens {token.get_full_token_name()}") + time.sleep(sleep_time) + + # claim rewards once in a while - 35% chance + if random.randint(0, 100) <= 35: + _ = fees_collector_contract.claim_rewards(account, context.network_provider.proxy) + print(f"User: {account.address.bech32()}") + time.sleep(sleep_time) + + wait_time = random.randint(min_time, max_time) + print(f'Finished repeat execution. Waiting for {wait_time}s.') + time.sleep(wait_time) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scenarios/scenario_simple_lock_energy.py b/scenarios/scenario_simple_lock_energy.py new file mode 100644 index 0000000..658cdde --- /dev/null +++ b/scenarios/scenario_simple_lock_energy.py @@ -0,0 +1,392 @@ +import concurrent.futures +import random +import sys +import time +import pytest +from itertools import count +from typing import List +from argparse import ArgumentParser + +import config +from context import Context +from contracts.simple_lock_energy_contract import SimpleLockEnergyContract +from events.event_generators import (generate_add_initial_liquidity_event, + generate_add_liquidity_event, + generateEnterFarmEvent, + generateEnterMetastakeEvent, + generateClaimMetastakeRewardsEvent, + generateExitMetastakeEvent) +from utils.utils_tx import ESDTToken +from utils.utils_chain import print_test_step_pass, print_condition_assert, nominated_amount, \ + print_test_step_fail, TestStepConditions, get_token_details_for_address +from arrows.stress.send_token_from_minter import main as send_token_from_minter +from arrows.stress.shared import get_shard_of_address +from erdpy.accounts import Account + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--threads", required=False, default="1") # number of concurrent threads to execute operations + parser.add_argument("--repeats", required=False, default="1") # number of total operations to execute; 0 - infinite + parser.add_argument("--skip-minting", action="store_true", default=True) + args = parser.parse_args(cli_args) + + context = Context() + + if not args.skip_minting: + send_tokens(context) + wait_time = 40 + print(f"Minting accounts done. Waiting for the dust to settle: {wait_time}s until execution start") + time.sleep(wait_time) + + create_nonce_file(context) + + # stress generator for adding liquidity, enter farm, enter metastaking, claim metastaking, exit metastaking + scenarios(context, int(args.threads), int(args.repeats)) + + +def create_nonce_file(context: Context): + context.accounts.sync_nonces(context.proxy) + context.accounts.store_nonces(context.nonces_file) + context.deployer_account.sync_nonce(context.proxy) + + +def send_tokens(context: Context): + proxy_url = config.DEFAULT_PROXY + accounts = config.DEFAULT_ACCOUNTS + minter = config.DEFAULT_OWNER + amount = nominated_amount(1000000) + + for token in context.deploy_structure.tokens: + print(f"Funding each account with {amount} {token} tokens.") + args = [f'--proxy={proxy_url}', f'--accounts={accounts}', + f'--minter={minter}', f'--token={token}', f'--amount-atoms={amount}'] + send_token_from_minter(args) + time.sleep(7) + + +def scenarios(context: Context, threads: int, repeats: int): + accounts = context.accounts.get_all() + + if threads == 1: + """Sequential run""" + for _ in range(repeats if repeats != 0 else 9999): + scenarios_per_account(context, random.choice(accounts)) + elif threads > 1: + """ Concurential run """ + with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: + jobs = min(threads, repeats) if repeats != 0 else threads + finished_jobs = 0 + futures = [[executor.submit(scenarios_per_account, context, random.choice(accounts)) + for _ in range(jobs)]] + + # feed the thread workers as they complete each job + while finished_jobs < repeats or repeats == 0: + try: + for _ in concurrent.futures.as_completed(futures): + finished_jobs += 1 + print_test_step_pass(f"Finished {finished_jobs} repeats.") + + if repeats == 0 or repeats > finished_jobs: + # spawn a new job + futures.append(executor.submit(scenarios_per_account, context, random.choice(accounts))) + except Exception as ex: + pass + else: + print_test_step_fail(f"Number of threads must be minimum 1!") + return + + +def scenarios_per_account(context: Context, account: Account): + min_time = 2 + max_time = 10 + deployer_shard = get_shard_of_address(context.deployer_account.address) + sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + else 1 + + account.sync_nonce(context.network_provider.proxy) + + simple_lock_energy_contract: SimpleLockEnergyContract + simple_lock_energy_contract = context.get_contracts(config.SIMPLE_LOCKS_ENERGY)[0] + + # unlock early - pass + test_unlock_early_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # lock tokens with valid lock option - pass + test_lock_tokens_valid_option_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # lock tokens with valid lock option - pass + test_lock_tokens_valid_option_2(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # lock tokens with invalid lock option - fail + test_lock_tokens_invalid_option_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # lock other tokens - fail + test_lock_other_tokens_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # unlock early - pass + test_unlock_early_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # unlock early w. invalid token - fail + test_unlock_early_invalid_token_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # unlock early w. invalid similar token - fail + test_unlock_early_invalid_token_2(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # reduce lock - fail + test_reduce_lock_invalid_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # reduce lock - fail + test_reduce_lock_invalid_too_much_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # reduce lock - pass + test_reduce_and_unlock_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # reduce lock - pass + test_reduce_lock_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + # unlock tokens - fail + test_unlock_invalid_1(context, account, simple_lock_energy_contract) + time.sleep(sleep_time) + + wait_time = random.randint(min_time, max_time) + print(f'Finished repeat execution. Waiting for {wait_time}s.') + time.sleep(wait_time) + + +def test_lock_tokens_valid_option_1(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + token = ESDTToken(simple_lock_energy_contract.base_token, 0, nominated_amount(1000)) + expected_token = ESDTToken(simple_lock_energy_contract.locked_token, 1, token.token_amount) + args = [[token], 2] + + tx_hash = simple_lock_energy_contract.lock_tokens(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(context.network_provider.check_simple_tx_status(tx_hash), + "lock tokens with valid lock option tx pass") + teststep.add_condition(context.network_provider.check_for_burn_operation(tx_hash, token), + "base token burn") + teststep.add_condition(context.network_provider.check_for_add_quantity_operation(tx_hash, expected_token), + "add quantity locked token") + teststep.add_condition(context.network_provider.check_for_transfer_operation(tx_hash, expected_token, + destination=account.address.bech32()), + "send locked token to user") + teststep.assert_conditions() + + +def test_lock_tokens_valid_option_2(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + token = ESDTToken(simple_lock_energy_contract.base_token, 0, nominated_amount(1000)) + expected_token = ESDTToken(simple_lock_energy_contract.locked_token, 2, token.token_amount) + args = [[token], 6] + + tx_hash = simple_lock_energy_contract.lock_tokens(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(context.network_provider.check_simple_tx_status(tx_hash), + "lock tokens with valid lock option tx pass") + teststep.add_condition(context.network_provider.check_for_burn_operation(tx_hash, token), + "base token burn") + teststep.add_condition(context.network_provider.check_for_add_quantity_operation(tx_hash, expected_token), + "add quantity locked token") + teststep.add_condition(context.network_provider.check_for_transfer_operation(tx_hash, expected_token, + destination=account.address.bech32()), + "send locked token to user") + teststep.assert_conditions() + + +def test_lock_tokens_invalid_option_1(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + token = ESDTToken(simple_lock_energy_contract.base_token, 0, nominated_amount(100)) + args = [[token], 5] + + tx_hash = simple_lock_energy_contract.lock_tokens(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(not context.network_provider.check_simple_tx_status(tx_hash), + "lock tokens with invalid lock option") + teststep.add_condition(context.network_provider.check_for_error_operation(tx_hash, "Invalid lock choice"), + "tx fail with expected error") + teststep.assert_conditions() + + +def test_lock_other_tokens_1(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + token = ESDTToken(context.deploy_structure.tokens[1], 0, nominated_amount(100)) + args = [[token], 2] + + tx_hash = simple_lock_energy_contract.lock_tokens(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(not context.network_provider.check_simple_tx_status(tx_hash), + "lock other tokens") + teststep.add_condition(context.network_provider.check_for_error_operation(tx_hash, "Invalid payment token"), + "tx fail with expected error") + teststep.assert_conditions() + + +def test_unlock_early_1(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + token = ESDTToken(simple_lock_energy_contract.locked_token, 2, nominated_amount(100)) + expected_token = ESDTToken(simple_lock_energy_contract.base_token, 0, token.token_amount) + args = [[token]] + + tx_hash = simple_lock_energy_contract.unlock_early(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(context.network_provider.check_simple_tx_status(tx_hash), + "unlock early") + teststep.add_condition(context.network_provider.check_for_burn_operation(tx_hash, token), + "locked token burn") + teststep.add_condition(context.network_provider.check_for_mint_operation(tx_hash, expected_token), + "mint base token") + # TODO: calculate burned penalties, rest of penalties sent to fees collector, rest sent to user + penalty = context.simple_lock_tracker.get_expected_penalty(token) + + teststep.add_condition(context.network_provider.check_for_transfer_operation(tx_hash, expected_token, + destination=account.address.bech32()), + "send base token to user") + teststep.assert_conditions() + + +def test_unlock_early_invalid_token_1(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + token = ESDTToken(simple_lock_energy_contract.base_token, 0, nominated_amount(100)) + args = [[token]] + + tx_hash = simple_lock_energy_contract.unlock_early(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(not context.network_provider.check_simple_tx_status(tx_hash), + "unlock early with invalid token") + teststep.add_condition(context.network_provider.check_for_error_operation(tx_hash, "Invalid payment token"), + "tx fail with expected error") + teststep.assert_conditions() + + +def test_unlock_early_invalid_token_2(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + forged_contract: SimpleLockEnergyContract + forged_contract = context.get_contracts(config.SIMPLE_LOCKS_ENERGY)[1] + # create forged token + token = ESDTToken(context.deploy_structure.tokens[0], 0, nominated_amount(1)) + args = [[token], 30] + forged_token = ESDTToken(forged_contract.locked_token, 1, 1) + tx_hash = forged_contract.lock_tokens(account, context.network_provider.proxy, args) + teststep = TestStepConditions() + teststep.add_condition(context.network_provider.check_simple_tx_status(tx_hash), + "lock forged token pass") + teststep.add_condition(context.network_provider.check_for_transfer_operation(tx_hash, forged_token, + destination=account.address.bech32()), + "send base token to user") + teststep.assert_conditions() + + # attempt unlocking of forged token in tested contract + token = forged_token + args = [[token]] + tx_hash = simple_lock_energy_contract.unlock_early(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(not context.network_provider.check_simple_tx_status(tx_hash), + "unlock early with invalid similar token tx fail") + teststep.add_condition(context.network_provider.check_for_error_operation(tx_hash, "Invalid payment token"), + "tx fail with expected error") + teststep.assert_conditions() + + +def test_reduce_lock_invalid_1(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + token = ESDTToken(simple_lock_energy_contract.locked_token, 2, nominated_amount(100)) + args = [[token], 1] + + tx_hash = simple_lock_energy_contract.reduce_lock(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(not context.network_provider.check_simple_tx_status(tx_hash), + "reduce lock tx fail") + teststep.add_condition(context.network_provider.check_for_error_operation(tx_hash, "May only reduce by multiples of months (30 epochs)"), + "tx fail with expected error") + teststep.assert_conditions() + + +def test_reduce_lock_invalid_too_much_1(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + token = ESDTToken(simple_lock_energy_contract.locked_token, 1, nominated_amount(100)) + args = [[token], 3] + + tx_hash = simple_lock_energy_contract.reduce_lock(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(not context.network_provider.check_simple_tx_status(tx_hash), + "reduce lock tx fail") + teststep.add_condition(context.network_provider.check_for_error_operation(tx_hash, "Invalid epochs to reduce"), + "tx fail with expected error") + teststep.assert_conditions() + + +def test_reduce_and_unlock_1(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + # check complete unlocking via reduce (when reduce period = remaining period) + token = ESDTToken(simple_lock_energy_contract.locked_token, 1, nominated_amount(100)) + expected_token = ESDTToken(simple_lock_energy_contract.base_token, 0, token.token_amount) + args = [[token], 2] + + tx_hash = simple_lock_energy_contract.reduce_lock(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(context.network_provider.check_simple_tx_status(tx_hash), + "reduce lock tx pass") + teststep.add_condition(context.network_provider.check_for_burn_operation(tx_hash, token), + "locked token burn") + teststep.add_condition(context.network_provider.check_for_mint_operation(tx_hash, expected_token), + "mint base token") + # TODO: apply expected penalty calculation - burned penalties, rest of penalties sent to fees collector, rest sent to user + teststep.add_condition(context.network_provider.check_for_transfer_operation(tx_hash, expected_token, + destination=account.address.bech32()), + "send new base token to user") + teststep.assert_conditions() + + +def test_reduce_lock_1(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + token = ESDTToken(simple_lock_energy_contract.locked_token, 2, nominated_amount(100)) + expected_token = ESDTToken(simple_lock_energy_contract.locked_token, 1, token.token_amount) + penalties_token = ESDTToken(simple_lock_energy_contract.base_token, 0, token.token_amount) + args = [[token], 3] + + tx_hash = simple_lock_energy_contract.reduce_lock(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(context.network_provider.check_simple_tx_status(tx_hash), + "reduce lock tx pass") + teststep.add_condition(context.network_provider.check_for_burn_operation(tx_hash, token), + "locked token burn") + # TODO: apply expected penalty calculation - mint, burned penalties, rest of penalties sent to fees collector, rest sent to user + teststep.add_condition(context.network_provider.check_for_add_quantity_operation(tx_hash, expected_token), + "add quantity new locked token") + teststep.add_condition(context.network_provider.check_for_add_quantity_operation(tx_hash, penalties_token), + "mint base token") + teststep.add_condition(context.network_provider.check_for_transfer_operation(tx_hash, expected_token, + destination=account.address.bech32()), + "send new locked token to user") + teststep.assert_conditions() + + +def test_unlock_invalid_1(context, account: Account, simple_lock_energy_contract: SimpleLockEnergyContract): + token = ESDTToken(simple_lock_energy_contract.locked_token, 1, nominated_amount(100)) + args = [[token]] + + tx_hash = simple_lock_energy_contract.unlock_tokens(account, context.network_provider.proxy, args) + + teststep = TestStepConditions() + teststep.add_condition(not context.network_provider.check_simple_tx_status(tx_hash), + "unlock tokens tx fail") + teststep.add_condition(context.network_provider.check_for_error_operation(tx_hash, "Cannot unlock yet"), + "tx fail with expected error") + teststep.assert_conditions() + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scenarios/scenario_swaps.py b/scenarios/scenario_swaps.py new file mode 100644 index 0000000..3b91589 --- /dev/null +++ b/scenarios/scenario_swaps.py @@ -0,0 +1,158 @@ +import concurrent.futures +import random +import sys +import time +import traceback + +import pytest +from itertools import count +from typing import List +from argparse import ArgumentParser + +import config +from context import Context +from contracts.fees_collector_contract import FeesCollectorContract +from contracts.pair_contract import PairContract, AddLiquidityEvent +from contracts.simple_lock_energy_contract import SimpleLockEnergyContract +from events.event_generators import (generate_add_initial_liquidity_event, + generate_add_liquidity_event, + generateEnterFarmEvent, + generateEnterMetastakeEvent, + generateClaimMetastakeRewardsEvent, + generateExitMetastakeEvent, generate_swap_fixed_input) +from utils.contract_data_fetchers import PairContractDataFetcher, SimpleLockEnergyContractDataFetcher +from utils.utils_tx import ESDTToken +from utils.utils_chain import print_test_step_pass, print_condition_assert, nominated_amount, \ + print_test_step_fail, TestStepConditions, get_token_details_for_address, get_all_token_nonces_details_for_account +from arrows.stress.send_token_from_minter import main as send_token_from_minter +from arrows.stress.send_egld_from_minter import main as send_egld_from_minter +from arrows.stress.shared import get_shard_of_address +from erdpy.accounts import Account, Address + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--threads", required=False, default="2") # number of concurrent threads to execute operations + parser.add_argument("--repeats", required=False, default="0") # number of total operations to execute; 0 - infinite + parser.add_argument("--skip-minting", action="store_true", default=False) + args = parser.parse_args(cli_args) + + context = Context() + + if not args.skip_minting: + send_tokens(context) + wait_time = 40 + print(f"Minting accounts done. Waiting for the dust to settle: {wait_time}s until execution start") + time.sleep(wait_time) + + create_nonce_file(context) + + # stress generator for adding liquidity, enter farm, enter metastaking, claim metastaking, exit metastaking + scenarios(context, int(args.threads), int(args.repeats)) + + +def create_nonce_file(context: Context): + context.accounts.sync_nonces(context.proxy) + context.accounts.store_nonces(context.nonces_file) + context.deployer_account.sync_nonce(context.proxy) + + +def send_tokens(context: Context): + proxy_url = config.DEFAULT_PROXY + accounts = config.DEFAULT_ACCOUNTS + minter = config.DEFAULT_OWNER + tk_amount = nominated_amount(100000) + egld_amount = nominated_amount(1) + + for token in context.deploy_structure.tokens: + print(f"Funding each account with {tk_amount} {token} tokens.") + args = [f'--proxy={proxy_url}', f'--accounts={accounts}', + f'--minter={minter}', f'--token={token}', f'--amount-atoms={tk_amount}'] + send_token_from_minter(args) + time.sleep(7) + + print(f"Funding each account with {egld_amount} eGLD tokens.") + args = [f'--proxy={proxy_url}', f'--accounts={accounts}', + f'--minter={minter}', f'--value-atoms={egld_amount}'] + send_egld_from_minter(args) + time.sleep(7) + + +def add_initial_liquidity(context: Context): + # add initial liquidity + pair_contract: PairContract + for pair_contract in context.get_contracts(config.PAIRS): + pair_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.network_provider.proxy.url) + first_token_liquidity = pair_data_fetcher.get_token_reserve(pair_contract.firstToken) + if first_token_liquidity == 0: + event = AddLiquidityEvent( + pair_contract.firstToken, nominated_amount(1000000), 1, + pair_contract.secondToken, nominated_amount(1000000), 1 + ) + pair_contract.addLiquidity(context.network_provider, context.deployer_account, event) + time.sleep(6) + + +def scenarios(context: Context, threads: int, repeats: int): + accounts = context.accounts.get_all() + + context.swap_min_tokens_to_spend = 0.0001 + context.swap_max_tokens_to_spend = 0.001 + + # add initial liquidity in contract if necessary + add_initial_liquidity(context) + + if threads == 1: + """Sequential run""" + for _ in range(repeats if repeats != 0 else 99999999): + scenarios_per_account(context, random.choice(accounts)) + elif threads > 1: + """ Concurential run """ + with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: + jobs = min(threads, repeats) if repeats != 0 else threads + finished_jobs = 0 + futures = [executor.submit(scenarios_per_account, context, random.choice(accounts)) + for _ in range(jobs)] + + # feed the thread workers as they complete each job + while finished_jobs < repeats or repeats == 0: + try: + for future in concurrent.futures.as_completed(futures): + finished_jobs += 1 + print_test_step_pass(f"Finished {finished_jobs} repeats.") + futures.remove(future) + # spawn a new job + futures.append(executor.submit(scenarios_per_account, context, random.choice(accounts))) + if future.exception() is not None: + print_test_step_fail(f"Thread failed: {future.exception()}") + except Exception as ex: + traceback.print_exception(*sys.exc_info()) + print_test_step_fail(f"Something failed: {ex}") + else: + print_test_step_fail(f"Number of threads must be minimum 1!") + return + + +def scenarios_per_account(context: Context, account: Account): + min_time = 10 + max_time = 30 + deployer_shard = get_shard_of_address(context.deployer_account.address) + sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + else 6 + + account.sync_nonce(context.network_provider.proxy) + + pair_contract: PairContract + pair_contract = random.choice(context.get_contracts(config.PAIRS)) + + # swap tokens + generate_swap_fixed_input(context, account, pair_contract) + time.sleep(sleep_time) + + wait_time = random.randint(min_time, max_time) + print(f'Finished repeat execution. Waiting for {wait_time}s.') + time.sleep(wait_time) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scenarios/stress_claim_metastaking.py b/scenarios/stress_claim_metastaking.py new file mode 100644 index 0000000..9343cea --- /dev/null +++ b/scenarios/stress_claim_metastaking.py @@ -0,0 +1,65 @@ +import sys +import time +from argparse import ArgumentParser +from typing import List +from arrows.stress.contracts.transaction_builder import number_as_arg, string_as_arg +from arrows.stress.shared import broadcast_transactions +from erdpy.accounts import Account, Address +from erdpy.proxy.core import ElrondProxy +from erdpy.proxy.messages import NetworkConfig +from erdpy.transactions import Transaction + + +def claim_metastaking_rewards(caller: Account, contract_addr: Address, number_of_tokens: int, token_identifier: str, + token_nonce: str, token_quantity: int, network: NetworkConfig, method: str) -> Transaction: + + tx_data = f'MultiESDTNFTTransfer@{contract_addr.hex()}@{number_as_arg(number_of_tokens)}@{string_as_arg(token_identifier)}' \ + f'@{token_nonce}@{number_as_arg(token_quantity)}@{string_as_arg(method)}' + + transaction = Transaction() + transaction.nonce = caller.nonce + transaction.sender = caller.address.bech32() + transaction.receiver = caller.address.bech32() + transaction.value = '0' + transaction.data = tx_data + transaction.gasPrice = network.min_gas_price + transaction.gasLimit = 40000000 + transaction.chainID = network.chain_id + transaction.version = network.min_tx_version + transaction.sign(caller) + + return transaction + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--proxy", required=True) + parser.add_argument("--account", required=True) + parser.add_argument("--token", required=True) + parser.add_argument("--token-nonce", required=True) + parser.add_argument('--contract-address', required=True) + args = parser.parse_args(cli_args) + + proxy = ElrondProxy(args.proxy) + network = proxy.get_network_config() + account = Account(pem_file=args.account) + account.sync_nonce(proxy) + token_identifier = args.token + token_nonce = args.token_nonce + token_quantity = 1000 + method = 'claimDualYield' + contract_address = Address(args.contract_address) + + txs = [] + for _ in range(5): + tx = claim_metastaking_rewards(account, contract_address, 1, token_identifier, token_nonce, token_quantity, + network, method) + + txs.append(tx) + account.nonce += 1 + + broadcast_transactions(txs, proxy, 5, confirm_yes=True) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/scenarios/stress_create_positions.py b/scenarios/stress_create_positions.py new file mode 100644 index 0000000..96e30a6 --- /dev/null +++ b/scenarios/stress_create_positions.py @@ -0,0 +1,129 @@ +import concurrent.futures +import random +import sys +import time +from itertools import count +from typing import List +from argparse import ArgumentParser + +import config +from context import Context +from events.event_generators import (generate_add_initial_liquidity_event, + generate_add_liquidity_event, + generateEnterFarmEvent, + generateEnterMetastakeEvent, + generateClaimMetastakeRewardsEvent, + generateExitMetastakeEvent, + generateExitFarmEvent) +from utils.utils_chain import print_test_step_pass +from arrows.stress.send_token_from_minter import main as send_token_from_minter +from arrows.stress.shared import get_shard_of_address +from erdpy.accounts import Account + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--threads", required=False, default="3") # number of concurrent threads to execute operations + parser.add_argument("--repeats", required=False, default="0") # number of total operations to execute; 0 - infinite + parser.add_argument("--skip-minting", action="store_true", default=False) + args = parser.parse_args(cli_args) + + context = Context() + + if not args.skip_minting: + send_tokens(context) + wait_time = 40 + print(f"Minting accounts done. Waiting for the dust to settle: {wait_time}s until execution start") + time.sleep(wait_time) + + create_nonce_file(context) + + # stress generator for adding liquidity, enter farm, enter metastaking, claim metastaking, exit metastaking + stress(context, int(args.threads), int(args.repeats)) + + +def create_nonce_file(context: Context): + context.accounts.sync_nonces(context.proxy) + context.accounts.store_nonces(context.nonces_file) + context.deployer_account.sync_nonce(context.proxy) + + +def send_tokens(context: Context): + proxy_url = config.DEFAULT_PROXY + accounts = config.DEFAULT_ACCOUNTS + minter = config.DEFAULT_OWNER + amount = 3000000000000000000 + + for token in context.deploy_structure.tokens: + print(f"Funding each account with {amount} {token} tokens.") + args = [f'--proxy={proxy_url}', f'--accounts={accounts}', + f'--minter={minter}', f'--token={token}', f'--amount-atoms={amount}'] + send_token_from_minter(args) + time.sleep(7) + + +def stress(context: Context, threads: int, repeats: int): + # set initial liquidity and start pair contract + # TODO: should be done only once + """ + for pair_contract in context.get_contracts(config.PAIRS): + generate_add_initial_liquidity_event(context, context.deployer_account, pair_contract) + time.sleep(config.INTRA_SHARD_DELAY) + pair_contract.resume(context.deployer_account, context.network_provider.proxy) + time.sleep(config.INTRA_SHARD_DELAY) + """ + accounts = context.accounts.get_all() + + with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: + jobs = min(threads, repeats) if repeats != 0 else threads + finished_jobs = 0 + futures = [[executor.submit(stress_per_account, context, random.choice(accounts)) + for _ in range(jobs)]] + + # feed the thread workers as they complete each job + while finished_jobs < repeats or repeats == 0: + try: + for _ in concurrent.futures.as_completed(futures): + finished_jobs += 1 + print_test_step_pass(f"Finished {finished_jobs} repeats.") + + if repeats == 0 or repeats > finished_jobs: + # spawn a new job + futures.append(executor.submit(stress_per_account, context, random.choice(accounts))) + except Exception as ex: + pass + + +def stress_per_account(context: Context, account: Account): + min_time = 2 + max_time = 10 + deployer_shard = get_shard_of_address(context.deployer_account.address) + sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + else config.INTRA_SHARD_DELAY + + account.sync_nonce(context.network_provider.proxy) + + for metastaking_contract in context.get_contracts(config.METASTAKINGS): + farm_contract = context.get_farm_contract_by_address(metastaking_contract.farm_address) + pair_contract = context.get_pair_contract_by_address(metastaking_contract.lp_address) + + generate_add_liquidity_event(context, account, pair_contract) + time.sleep(sleep_time) + + for _ in range(1): + if generateEnterFarmEvent(context, account, farm_contract): + time.sleep(sleep_time) + if generateEnterMetastakeEvent(context, account, metastaking_contract): + time.sleep(sleep_time) + if generateClaimMetastakeRewardsEvent(context, account, metastaking_contract): + time.sleep(sleep_time) + if generateExitMetastakeEvent(context, account, metastaking_contract): + time.sleep(sleep_time) + + wait_time = random.randint(min_time, max_time) + print(f'Finished repeat execution. Waiting for {wait_time}s.') + time.sleep(wait_time) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scenarios/stress_many_add_remove_liquidity.py b/scenarios/stress_many_add_remove_liquidity.py new file mode 100644 index 0000000..d9a0c6b --- /dev/null +++ b/scenarios/stress_many_add_remove_liquidity.py @@ -0,0 +1,68 @@ + +import sys +import time +from argparse import ArgumentParser +from pathlib import Path +from typing import List + +from arrows.stress.contracts.transaction_builder import \ + transfer_multi_esdt_and_execute +from arrows.stress.shared import BunchOfAccounts, broadcast_transactions +from erdpy.accounts import Account, Address +from erdpy.proxy.core import ElrondProxy +from erdpy.proxy.messages import NetworkConfig +from erdpy.transactions import Transaction + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--proxy", required=True) + parser.add_argument("--accounts", required=True) + parser.add_argument("--token-one", required=True) + parser.add_argument("--token-two", required=True) + parser.add_argument("--token-lp", required=True) + parser.add_argument("--pair", required=True) + args = parser.parse_args(cli_args) + + proxy = ElrondProxy(args.proxy) + network = proxy.get_network_config() + pair = Address(args.pair) + accounts = BunchOfAccounts.load_accounts_from_files([Path(args.accounts)]) + + for _ in range(0, 1000): + accounts.sync_nonces(proxy) + + transactions: List[Transaction] = [] + + for account in accounts.get_all() * 10: + transactions.append(create_add_liquidity(pair, account, args.token_one, args.token_two, network)) + account.nonce += 1 + transactions.append(create_remove_liquidity(pair, account, args.token_lp, network)) + account.nonce += 1 + + broadcast_transactions(transactions, proxy, 1000, confirm_yes=True) + time.sleep(60 * 1) + + +def create_add_liquidity(pair: Address, caller: Account, token_one: str, token_two: str, network: NetworkConfig) -> Transaction: + given_one = 100000 + given_two = 100000 + transfers = [(token_one, given_one), (token_two, given_two)] + args = [1, 1] + gas_limit = 8400000 + transaction = transfer_multi_esdt_and_execute(pair, caller, transfers, "addLiquidity", args, gas_limit, network) + return transaction + + +def create_remove_liquidity(pair: Address, caller: Account, token_lp: str, network: NetworkConfig) -> Transaction: + taken_one = 1 + taken_two = 1 + transfers = [(token_lp, 1)] + args = [taken_one, taken_two] + gas_limit = 750000 + transaction = transfer_multi_esdt_and_execute(pair, caller, transfers, "removeLiquidity", args, gas_limit, network) + return transaction + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scenarios/stress_many_swaps.py b/scenarios/stress_many_swaps.py new file mode 100644 index 0000000..4825d2b --- /dev/null +++ b/scenarios/stress_many_swaps.py @@ -0,0 +1,65 @@ + +import sys +import time +from argparse import ArgumentParser +from pathlib import Path +from typing import List + +from arrows.stress.contracts.transaction_builder import (number_as_arg, + string_as_arg, + token_id_as_arg) +from arrows.stress.shared import BunchOfAccounts, broadcast_transactions +from erdpy.accounts import Account, Address +from erdpy.proxy.core import ElrondProxy +from erdpy.proxy.messages import NetworkConfig +from erdpy.transactions import Transaction + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--proxy", required=True) + parser.add_argument("--accounts", required=True) + parser.add_argument("--token-one", required=True) + parser.add_argument("--token-two", required=True) + parser.add_argument("--pair", required=True) + args = parser.parse_args(cli_args) + + proxy = ElrondProxy(args.proxy) + network = proxy.get_network_config() + pair = Address(args.pair) + accounts = BunchOfAccounts.load_accounts_from_files([Path(args.accounts)]) + + for _ in range(0, 100): + accounts.sync_nonces(proxy) + + transactions: List[Transaction] = [] + + for account in accounts.get_all(): + transactions.append(create_swap_fixed_input(pair, account, args.token_one, args.token_two, network)) + transactions.append(create_swap_fixed_input(pair, account, args.token_two, args.token_one, network)) + + broadcast_transactions(transactions, proxy, 1000, confirm_yes=True) + time.sleep(60 * 3) + + +def create_swap_fixed_input(pair: Address, caller: Account, token_from: str, token_to: str, network: NetworkConfig) -> Transaction: + amount_from = 100000 + amount_to_min = 1 + tx_data = f"ESDTTransfer@{token_id_as_arg(token_from)}@{number_as_arg(amount_from)}@{string_as_arg('swapTokensFixedInput')}@{token_id_as_arg(token_to)}@{number_as_arg(amount_to_min)}" + + transaction = Transaction() + transaction.nonce = caller.nonce + transaction.sender = caller.address.bech32() + transaction.receiver = pair.bech32() + transaction.value = "0" + transaction.data = tx_data + transaction.gasPrice = network.min_gas_price + transaction.gasLimit = 8000000 + transaction.chainID = network.chain_id + transaction.version = network.min_tx_version + transaction.sign(caller) + return transaction + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scenarios/stress_scenario.py b/scenarios/stress_scenario.py new file mode 100644 index 0000000..a55ad1d --- /dev/null +++ b/scenarios/stress_scenario.py @@ -0,0 +1,154 @@ +import logging +import random +import sys +import time +import traceback + +from context import Context +from contracts.farm_contract import FarmContract +from events.event_generators import (generate_add_liquidity_event, generate_swap_fixed_input, + generate_migrate_farm_event, generate_remove_liquidity_event, + generate_random_swap_fixed_input, + generate_random_swap_fixed_output, generateRandomEnterFarmEvent, + generateRandomExitFarmEvent, generateRandomClaimRewardsEvent, + generateRandomCompoundRewardsEvent, + generateAddLiquidityProxyEvent, + generateRemoveLiquidityProxyEvent, + generateRandomEnterFarmProxyEvent, + generateRandomExitFarmProxyEvent, + generateRandomClaimRewardsProxyEvent, + generateRandomCompoundRewardsProxyEvent, generateEnterFarmEvent, + generateExitFarmEvent, generateClaimRewardsEvent, + generateEnterFarmv12Event, generate_add_initial_liquidity_event) +from erdpy.accounts import Account + + +def main(): + logging.basicConfig(level=logging.ERROR) + context = Context() + create_nonce_file(context) + + # stress generator for SafePrice scenario + safeprice_stress(context) + + context.results_logger.save_log() + + +def safeprice_stress(context: Context): + # generate random pool transactions in both directions to stimulate the SafePrice mechanism + min_time = 2 + max_time = 10 + pair_contract = context.get_pair_contract(0) + + generate_add_initial_liquidity_event(context, context.deployer_account, pair_contract) + + while 1: + account = context.accounts.get_all()[0] + generate_add_liquidity_event(context, account, pair_contract) + + print("Dump tx") + context.set_swap_spend_limits(0.7, 0.8) + generate_swap_fixed_input(context, account, pair_contract) + + for i in range(15): + print("Noise tx") + context.set_swap_spend_limits(0, 0.01) + generate_swap_fixed_input(context, account, pair_contract) + time.sleep(5) + + wait_time = random.randrange(min_time, max_time) + print(f"Waiting for {wait_time}s until next swap") + time.sleep(wait_time) + + +def migration_stress(context: Context): + # generate farm events on v1.2 farm + for i in range(1, context.numEvents): + account = context.get_random_user_account() + + # TODO: care for the locked rewards handling + generate_random_farm_v12_event(context, account, context.get_unlocked_farm_contract(0)) + time.sleep(7) + + # wait for migration setup + input("Setup the migration on contracts then press Enter to continue...") + + # migrate some accounts + account_list = context.accounts.get_all() + migrated_accounts = random.sample(account_list, random.randrange(1, len(account_list))) # random subset of accounts + for account in migrated_accounts: + generate_migrate_farm_event(context, account, context.get_unlocked_farm_contract(0)) + + # context.migrate_farm_economics(0, 1, 2) + + # generate farm events on v1.4 farms + for i in range(1, context.numEvents): + account = random.choice(migrated_accounts) + + generate_random_farm_event(context, account, context.get_unlocked_farm_contract(1)) + time.sleep(7) + generate_random_farm_event(context, account, context.get_unlocked_farm_contract(2)) + time.sleep(7) + + +def create_nonce_file(context: Context): + context.accounts.sync_nonces(context.proxy) + context.accounts.store_nonces(context.nonces_file) + + +def weighted_random_choice(choices): + max = sum(choices.values()) + pick = random.uniform(0, max) + current = 0 + for key, value in choices.items(): + current += value + if current > pick: + return key + + +def generateRandomEvent(context: Context): + events = { + generate_add_liquidity_event: 2, + generate_remove_liquidity_event: 2, + generate_random_swap_fixed_input: 6, + generate_random_swap_fixed_output: 6, + generateRandomEnterFarmEvent: 6, + generateRandomExitFarmEvent: 4, + generateRandomClaimRewardsEvent: 4, + generateRandomCompoundRewardsEvent: 4, + generateAddLiquidityProxyEvent: 3, + generateRemoveLiquidityProxyEvent: 3, + generateRandomEnterFarmProxyEvent: 4, + generateRandomExitFarmProxyEvent: 2, + generateRandomClaimRewardsProxyEvent: 2, + generateRandomCompoundRewardsProxyEvent: 2, + } + + eventFunction = weighted_random_choice(events) + eventFunction(context) + + +def generate_random_farm_event(context: Context, user_account: Account, farm: FarmContract): + events = { + generateEnterFarmEvent: 6, + generateExitFarmEvent: 2, + generateClaimRewardsEvent: 6, + } + + event_function = weighted_random_choice(events) + event_function(context, user_account, farm) + + +def generate_random_farm_v12_event(context: Context, user_account: Account, farm: FarmContract): + events = { + generateEnterFarmv12Event: 6, + generateExitFarmEvent: 2, + generateClaimRewardsEvent: 6, + } + + event_function = weighted_random_choice(events) + event_function(context, user_account, farm) + + +if __name__ == "__main__": + main() diff --git a/scenarios/stress_scenario_price_discovery.py b/scenarios/stress_scenario_price_discovery.py new file mode 100644 index 0000000..f3f5cf7 --- /dev/null +++ b/scenarios/stress_scenario_price_discovery.py @@ -0,0 +1,67 @@ +import logging +import random +import time + +from context import (Context) +from contracts.price_discovery_contract import PriceDiscoveryContract +from events.event_generators import generate_deposit_pd_liquidity_event, \ + generate_withdraw_pd_liquidity_event, generate_redeem_pd_liquidity_event, \ + generate_random_deposit_pd_liquidity_event, generate_random_withdraw_pd_liquidity_event, \ + generate_random_redeem_pd_liquidity_event + + +def main(): + logging.basicConfig(level=logging.ERROR) + context = Context() + create_nonce_file(context) + + price_discovery_contract: PriceDiscoveryContract + price_discovery_contract = context.get_random_price_discovery_contract() + + print(f"Started waiting for deposit start epoch: {price_discovery_contract.start_block}") + context.extended_proxy.wait_for_nonce_in_shard(1, price_discovery_contract.start_block) # TODO: nice shard + + for i in range(1, context.numEvents): + # generate_random_event(context) + generate_deposit_pd_liquidity_event(context, context.accounts.get_all()[0], price_discovery_contract) + time.sleep(6) + generate_withdraw_pd_liquidity_event(context, context.accounts.get_all()[0], price_discovery_contract) + time.sleep(6) + + print(f"Started waiting for deposit end epoch: {price_discovery_contract.deposit_end_epoch}") + context.extended_proxy.wait_for_epoch(price_discovery_contract.deposit_end_epoch) + + for i in range(1, context.numEvents): + generate_redeem_pd_liquidity_event(context, context.accounts.get_all()[0], price_discovery_contract) + + +def create_nonce_file(context: Context): + accounts = context.accounts.get_all() + for account in accounts: + account.sync_nonce(context.proxy) + context.accounts.store_nonces(context.nonces_file) + + +def generate_random_event(context: Context): + events = { + generate_random_deposit_pd_liquidity_event: 6, + generate_random_withdraw_pd_liquidity_event: 2, + generate_random_redeem_pd_liquidity_event: 6 + } + + eventFunction = weighted_random_choice(events) + eventFunction(context) + + +def weighted_random_choice(choices): + max = sum(choices.values()) + pick = random.uniform(0, max) + current = 0 + for key, value in choices.items(): + current += value + if current > pick: + return key + + +if __name__ == "__main__": + main() diff --git a/tools/account_state.py b/tools/account_state.py new file mode 100644 index 0000000..e379a20 --- /dev/null +++ b/tools/account_state.py @@ -0,0 +1,127 @@ +import json +import os +import sys +from argparse import ArgumentParser + +from typing import Dict, Any, Tuple, List + +from utils.utils_chain import print_test_step_fail, print_test_step_pass, print_warning +from erdpy.proxy.http_facade import do_get + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--folder", required=True) + parser.add_argument("--left-prefix", required=True) + parser.add_argument("--right-prefix", required=True) + parser.add_argument("--verbose", action="store_true", default=False) + args = parser.parse_args(cli_args) + + report_key_files_compare(args.folder, args.left_prefix, args.right_prefix, args.verbose) + + +def get_account_keys_online(address: str, proxy_url: str, block_number: int = 0, with_save_in: str = "") -> Dict[str, Any]: + if block_number == 0: + url = f"{proxy_url}/address/{address}/keys" + else: + url = f"{proxy_url}/address/{address}/keys?blockNonce={block_number}" + + response = do_get(url) + keys = response.get("pairs", dict()) + + if keys and with_save_in: + dir_path = os.path.dirname(with_save_in) + if not os.path.exists(dir_path): + os.mkdir(dir_path) + + with open(with_save_in, 'w') as state_writer: + json.dump(keys, state_writer, indent=4) + print(f'Dumped the retrieved contact state in: {with_save_in}') + + return keys + + +def compare_keys(left_state: dict, right_state: dict) -> Tuple[bool, dict, dict, dict, dict]: + """ + Returns: + identical: True/False + keys & values only in left file + keys & values only in right file + common keys with different values + common keys with identical values + """ + keys_in_left = {} + common_keys_different_values = {} + common_keys = {} + identical = False + + for left_key, left_value in left_state.items(): + if left_key in right_state: + right_value = right_state[left_key] + if left_value != right_value: + # different values on key + common_keys_different_values[left_key] = [left_value, right_value] + else: + # same key and value + common_keys[left_key] = left_value + # remove found key from right + right_state.pop(left_key) + else: + # key only in left + keys_in_left[left_key] = left_value + + # remaining keys in right are unique to right only + + if len(keys_in_left) == len(right_state) == len(common_keys_different_values) == 0: + identical = True + + return identical, keys_in_left, right_state, common_keys_different_values, common_keys + + +def report_key_files_compare(folder_path: str, left_prefix: str, right_prefix: str, verbose: bool = False): + compare_count = 0 + if not os.path.exists(folder_path): + print_test_step_fail(f"Given folder path doesn't exist.") + + for file in os.listdir(folder_path): + if f"{left_prefix}" not in file: + continue + + sub_name = file[len(left_prefix):] + right_file = f"{right_prefix}{sub_name}" + if not os.path.exists(os.path.join(folder_path, right_file)): + continue + + with open(os.path.join(folder_path, file)) as reader: + left_state = json.load(reader) + with open(os.path.join(folder_path, right_file)) as reader: + right_state = json.load(reader) + + identical, keys_in_left, keys_in_right, common_keys_diff_values, common_keys = compare_keys(left_state, + right_state) + + if identical: + print_test_step_pass(f"\n{file} and {right_file} are identical.") + else: + print_test_step_fail(f"\n{file} and {right_file} are not identical.") + if verbose: + if keys_in_left: + for key, value in keys_in_left.items(): + print_warning(f"Data only in {left_prefix}: {key}: {value}") + print(f"Decoded key: {bytearray.fromhex(key).decode('iso-8859-1')}") + if keys_in_right: + for key, value in keys_in_right.items(): + print_warning(f"Data only in {right_prefix}: {key}: {value}") + print(f"Decoded key: {bytearray.fromhex(key).decode('iso-8859-1')}") + if common_keys_diff_values: + for key, value in common_keys_diff_values.items(): + print_warning(f"Common key with different values: {key}: {value}") + print(f"Decoded key: {bytearray.fromhex(key).decode('iso-8859-1')}") + + compare_count += 1 + + print(f"\nFound and compared {compare_count} account state file pairs.") + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/tools/config_contracts_upgrader.py b/tools/config_contracts_upgrader.py new file mode 100644 index 0000000..0c5760c --- /dev/null +++ b/tools/config_contracts_upgrader.py @@ -0,0 +1,80 @@ +from pathlib import Path + +HOME = Path().home() +# For Windows: +# HOME = Path("/mnt/c/Users/...") + +TOKENS_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" +ZERO_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu" +DEFAULT_WORKSPACE = Path(__file__).parent +DEFAULT_OWNER = Path(__file__).parent.absolute() / ".." / ".." / "snippets" / "workspace" / "wallets" / "C1.pem" +DEFAULT_PROXY = "https://devnet-gateway.multiversx.com" +DEFAULT_API = "https://devnet-api.multiversx.com" +GRAPHQL = 'https://graph.xexchange.com/graphql' + +# DEX setup +LOCKED_ASSET_FACTORY_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "locked-asset" / "factory" / "output" / "factory.wasm" +PROXY_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "locked-asset" / "proxy_dex" / "output" / "proxy_dex.wasm" +STAKING_V2_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "sc-dex-rs" / "farm-staking" / "farm-staking" / "output" / "farm-staking.wasm" +STAKING_PROXY_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "dex" / "farm-staking-proxy" / "output" / "farm-staking-proxy.wasm" +ROUTER_V2_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "sc-dex-rs" / "dex" / "router" / "output" / "router.wasm" +PAIR_V2_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "sc-dex-rs" / "dex" / "pair" / "output" / "pair.wasm" +FARM_V12_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "legacy-rs" / "farm.wasm" +FARM_V13_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "legacy-rs" / "farm_with_lock.wasm" + +PROXY_DEX_CONTRACT = "erd1qqqqqqqqqqqqqpgqrc4pg2xarca9z34njcxeur622qmfjp8w2jps89fxnl" +LOCKED_ASSET_FACTORY_CONTRACT = "erd1qqqqqqqqqqqqqpgqjpt0qqgsrdhp2xqygpjtfrpwf76f9nvg2jpsg4q7th" +ROUTER_CONTRACT = "erd1qqqqqqqqqqqqqpgqq66xk9gfr4esuhem3jru86wg5hvp33a62jps2fy57p" +FEES_COLLECTOR_CONTRACT = "" +DEX_OWNER = "" # only needed for shadowfork + +OUTPUT_FOLDER = Path(__file__).parent.absolute() / "outputs_main_dry" + +# --------------------------- DO NOT MODIFY BELOW ------------------------------------------- + +HISTORY_PROXY = "" + +DEFAULT_ISSUE_TOKEN_PRICE = 50000000000000000 # TODO: try to override this with testnet define to tidy code up +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... +DEFAULT_FLOW = "full" + +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" + +DEFAULT_CONFIG_SAVE_PATH = Path(__file__).parent.absolute() / "deploy" / "configs-devnet" +DEPLOY_STRUCTURE_JSON = DEFAULT_CONFIG_SAVE_PATH / "deploy_structure_main.json" + +CROSS_SHARD_DELAY = 60 +INTRA_SHARD_DELAY = 10 + + +def get_default_contracts_file(): + return DEFAULT_WORKSPACE / "contracts.json" + + +def get_default_tokens_file(): + return DEFAULT_WORKSPACE / "tokens.json" + diff --git a/tools/contracts_upgrader.py b/tools/contracts_upgrader.py new file mode 100644 index 0000000..358c618 --- /dev/null +++ b/tools/contracts_upgrader.py @@ -0,0 +1,1101 @@ +import binascii +import json +import sys, os +import time + +import requests +from argparse import ArgumentParser +from typing import List + +from arrows.AutomaticTests.ElasticIndexer import ElasticIndexer +from arrows.AutomaticTests.ProxyExtension import ProxyExtension +from contracts.contract_identities import RouterContractVersion, PairContractVersion, \ + StakingContractVersion, ProxyContractVersion, FarmContractVersion +from contracts.dex_proxy_contract import DexProxyContract +from contracts.farm_contract import FarmContract +from contracts.fees_collector_contract import FeesCollectorContract +from contracts.locked_asset_contract import LockedAssetContract +from contracts.metastaking_contract import MetaStakingContract +from contracts.staking_contract import StakingContract +from tools.account_state import get_account_keys_online, report_key_files_compare +from utils.contract_retrievers import retrieve_router_by_address, retrieve_pair_by_address, \ + retrieve_staking_by_address, retrieve_simple_lock_energy_by_address +from utils.contract_data_fetchers import RouterContractDataFetcher, PairContractDataFetcher, \ + StakingContractDataFetcher, FarmContractDataFetcher +from utils.utils_tx import NetworkProviders +from utils.utils_chain import base64_to_hex, print_test_step_fail +from tools import config_contracts_upgrader as config +from erdpy.accounts import Address, Account +from erdpy.proxy import ElrondProxy +from pathlib import Path + +PROXY = config.DEFAULT_PROXY +API = config.DEFAULT_API + +GRAPHQL = config.GRAPHQL + +PROXY_DEX_CONTRACT = config.PROXY_DEX_CONTRACT +LOCKED_ASSET_FACTORY_CONTRACT = config.LOCKED_ASSET_FACTORY_CONTRACT +ROUTER_CONTRACT = config.ROUTER_CONTRACT +FEES_COLLECTOR_CONTRACT = config.FEES_COLLECTOR_CONTRACT +DEX_OWNER = config.DEX_OWNER # only needed for shadowfork + +OUTPUT_FOLDER = config.OUTPUT_FOLDER + + +LOCKED_ASSET_LABEL = "locked_asset" +PROXY_DEX_LABEL = "proxy_dex" +ROUTER_LABEL = "router" +TEMPLATE_PAIR_LABEL = "template_pair" +PAIRS_LABEL = "pairs" +STAKINGS_LABEL = "stakings" +METASTAKINGS_LABEL = "metastakings" +FARMSV13_LABEL = "farmsv13" +FARMSV12_LABEL = "farmsv12" + +OUTPUT_PAIR_CONTRACTS_FILE = OUTPUT_FOLDER / "pairs_data.json" +OUTPUT_STAKING_CONTRACTS_FILE = OUTPUT_FOLDER / "staking_data.json" +OUTPUT_METASTAKING_CONTRACTS_FILE = OUTPUT_FOLDER / "metastaking_data.json" +OUTPUT_FARMV13_CONTRACTS_FILE = OUTPUT_FOLDER / "farmv13_data.json" +OUTPUT_FARMV13LOCKED_CONTRACTS_FILE = OUTPUT_FOLDER / "farmv13locked_data.json" +OUTPUT_FARMV12_CONTRACTS_FILE = OUTPUT_FOLDER / "farmv12_data.json" + +OUTPUT_PAUSE_STATES = OUTPUT_FOLDER / "contract_pause_states.json" +SHADOWFORK = False if "shadowfork" not in PROXY else True + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--fetch-contracts", action="store_true", default=False) + parser.add_argument("--fetch-pause-state", action="store_true", default=False) + parser.add_argument("--pause-pairs", action="store_true", default=False) + parser.add_argument("--resume-pairs", action="store_true", default=False) + parser.add_argument("--pause-farms", action="store_true", default=False) + parser.add_argument("--resume-farms", action="store_true", default=False) + parser.add_argument("--stop-produce-rewards-farms", action="store_true", default=False) + parser.add_argument("--remove-penalty-farms", action="store_true", default=False) + parser.add_argument("--pause-stakings", action="store_true", default=False) + parser.add_argument("--resume-stakings", action="store_true", default=False) + parser.add_argument("--upgrade-locked-asset", action="store_true", default=False) + parser.add_argument("--upgrade-proxy-dex", action="store_true", default=False) + parser.add_argument("--upgrade-template", action="store_true", default=False) + parser.add_argument("--upgrade-router", action="store_true", default=False) + parser.add_argument("--upgrade-pairs", action="store_true", default=False) + parser.add_argument("--upgrade-farmsv12", action="store_true", default=False) + parser.add_argument("--upgrade-farmsv13", action="store_true", default=False) + parser.add_argument("--transfer-role-farmsv13", action="store_true", default=False) + parser.add_argument("--upgrade-stakings", action="store_true", default=False) + parser.add_argument("--upgrade-stakings-fix", action="store_true", default=False) + parser.add_argument("--upgrade-metastakings", action="store_true", default=False) + parser.add_argument("--set-pairs-in-fees-collector", action="store_true", default=False) + parser.add_argument("--remove-pairs-from-fees-collector", action="store_true", default=False) + parser.add_argument("--set-fees-collector-in-pairs", action="store_true", default=False) + parser.add_argument("--update-fees-in-pairs", action="store_true", default=False) + parser.add_argument("--fetch-state", required=False, default="") + args = parser.parse_args(cli_args) + + api = ElasticIndexer(API) + proxy = ElrondProxy(PROXY) + extended_proxy = ProxyExtension(PROXY) + network_providers = NetworkProviders(api, proxy, extended_proxy) + + owner = Account(pem_file=config.DEFAULT_OWNER) + if SHADOWFORK: + owner.address = Address(DEX_OWNER) # ONLY FOR SHADOWFORK + owner.sync_nonce(network_providers.proxy) + + if args.fetch_contracts: + fetch_and_save_pairs_from_chain(proxy) + time.sleep(3) + # fetch_and_save_stakings_from_chain(proxy) + time.sleep(3) + # fetch_and_save_metastakings_from_chain(proxy) + time.sleep(3) + # fetch_and_save_farms_from_chain(proxy) + + elif args.fetch_pause_state: + fetch_and_save_pause_state(network_providers) + + elif args.pause_pairs: + pause_pair_contracts(owner, network_providers) + + elif args.pause_farms: + pause_farm_contracts(owner, network_providers) + + elif args.pause_stakings: + pause_staking_contracts(owner, network_providers) + + elif args.stop_produce_rewards_farms: + stop_produce_rewards_farms(owner, network_providers) + + elif args.remove_penalty_farms: + remove_penalty_farms(owner, network_providers) + + elif args.upgrade_locked_asset: + upgrade_locked_asset_contracts(owner, network_providers) + + elif args.upgrade_proxy_dex: + upgrade_proxy_dex_contracts(owner, network_providers) + + elif args.upgrade_router: + upgrade_router_contract(owner, network_providers) + + elif args.upgrade_template: + upgrade_template_pair_contract(owner, network_providers) + + elif args.upgrade_pairs: + upgrade_pair_contracts(owner, network_providers) + + elif args.upgrade_farmsv12: + upgrade_farmv12_contracts(owner, network_providers) + + elif args.upgrade_farmsv13: + upgrade_farmv13_contracts(owner, network_providers) + + elif args.transfer_role_farmsv13: + set_transfer_role_farmv13_contracts(owner, network_providers) + + elif args.upgrade_stakings: + upgrade_staking_contracts(owner, network_providers) + + elif args.upgrade_stakings_fix: + upgrade_fix_staking_contracts(owner, network_providers) + + elif args.upgrade_metastakings: + upgrade_metastaking_contracts(owner, network_providers) + + elif args.set_pairs_in_fees_collector: + set_pairs_in_fees_collector(owner, network_providers) + + elif args.remove_pairs_from_fees_collector: + remove_pairs_from_fees_collector(owner, network_providers) + + elif args.set_fees_collector_in_pairs: + set_fees_collector_in_pairs(owner, network_providers) + + elif args.resume_pairs: + resume_pair_contracts(owner, network_providers) + + elif args.resume_farms: + resume_farm_contracts(owner, network_providers) + + elif args.resume_stakings: + resume_staking_contracts(owner, network_providers) + + elif args.update_fees_in_pairs: + update_fees_percentage(owner, network_providers) + + if args.fetch_state: + fetch_contract_states(args.fetch_state, network_providers) + + +def save_wasm(code_data_hex: str, code_hash: str): + binary_string = binascii.unhexlify(code_data_hex) + + if not os.path.exists(OUTPUT_FOLDER): + os.mkdir(OUTPUT_FOLDER) + + output_file = os.path.join(OUTPUT_FOLDER, f"{code_hash}.wasm") + with open(f"{output_file}", 'wb') as b: + b.write(binary_string) + + print(f"Created wasm binary in: {output_file}") + + +def fetch_and_save_contracts(contract_addresses: list, contract_label: str, save_path: Path, proxy: ElrondProxy): + pairs_data = {} + for address in contract_addresses: + contract_addr = Address(address) + account_data = proxy.get_account(contract_addr) + code_hash = base64_to_hex(account_data['codeHash']) + + if code_hash not in pairs_data: + pairs_data[code_hash] = { + contract_label: [], + "code": account_data['code'] + } + save_wasm(account_data['code'], code_hash) + pairs_data[code_hash][contract_label].append(contract_addr.bech32()) + + with open(save_path, "w") as writer: + json.dump(pairs_data, writer, indent=4) + print(f"Dumped {contract_label} data in {save_path}") + + +def fetch_and_save_pairs_from_chain(proxy: ElrondProxy): + router_data_fetcher = RouterContractDataFetcher(Address(ROUTER_CONTRACT), PROXY) + registered_pairs = router_data_fetcher.get_data("getAllPairsManagedAddresses") + fetch_and_save_contracts(registered_pairs, PAIRS_LABEL, OUTPUT_PAIR_CONTRACTS_FILE, proxy) + + +def fetch_and_save_farms_from_chain(proxy: ElrondProxy): + farmsv13 = get_farm_addresses_from_chain("v1.3") + farmsv13locked = get_farm_addresses_locked_from_chain() + farmsv12 = get_farm_addresses_from_chain("v1.2") + fetch_and_save_contracts(farmsv13, FARMSV13_LABEL, OUTPUT_FARMV13_CONTRACTS_FILE, proxy) + fetch_and_save_contracts(farmsv13locked, FARMSV13_LABEL, OUTPUT_FARMV13LOCKED_CONTRACTS_FILE, proxy) + fetch_and_save_contracts(farmsv12, FARMSV12_LABEL, OUTPUT_FARMV12_CONTRACTS_FILE, proxy) + + +def fetch_and_save_stakings_from_chain(proxy: ElrondProxy): + stakings = get_staking_addresses_from_chain() + fetch_and_save_contracts(stakings, STAKINGS_LABEL, OUTPUT_STAKING_CONTRACTS_FILE, proxy) + + +def fetch_and_save_metastakings_from_chain(proxy: ElrondProxy): + metastakings = get_metastaking_addresses_from_chain() + fetch_and_save_contracts(metastakings, METASTAKINGS_LABEL, OUTPUT_METASTAKING_CONTRACTS_FILE, proxy) + + +def run_graphql_query(uri, query): + headers = {} + statusCode = 200 + request = requests.post(uri, json={'query': query}, headers=headers) + if request.status_code == statusCode: + return request.json() + else: + raise Exception(f"Unexpected status code returned: {request.status_code}") + + +def get_farm_addresses_from_chain(version: str) -> list: + """ + version: v1.3 | v1.2 + """ + query = """ + { farms { + address + version + } } + """ + + result = run_graphql_query(GRAPHQL, query) + + address_list = [] + for entry in result['data']['farms']: + if entry['version'] == version: + address_list.append(entry['address']) + + return address_list + + +def get_farm_addresses_locked_from_chain() -> list: + query = """ + { farms { + address + version + rewardType + } } + """ + + result = run_graphql_query(GRAPHQL, query) + + address_list = [] + for entry in result['data']['farms']: + if entry['version'] == 'v1.3' and entry['rewardType'] == 'lockedRewards': + address_list.append(entry['address']) + + return address_list + + +def get_staking_addresses_from_chain() -> list: + query = """ + { stakingFarms { address } } + """ + + result = run_graphql_query(GRAPHQL, query) + + address_list = [] + for entry in result['data']['stakingFarms']: + address_list.append(entry['address']) + + return address_list + + +def get_metastaking_addresses_from_chain() -> list: + query = """ + { stakingProxies { address } } + """ + + result = run_graphql_query(GRAPHQL, query) + + address_list = [] + for entry in result['data']['stakingProxies']: + address_list.append(entry['address']) + + return address_list + + +def get_saved_contracts_data(saved_file: Path) -> dict: + if not os.path.exists(saved_file): + raise f"Saved contract data from mainnet not available!" + + print("Reading data...") + with open(saved_file) as reader: + contracts_data = json.load(reader) + return contracts_data + + +def get_saved_contract_addresses(contract_label: str, saved_file: Path) -> list: + contracts_data = get_saved_contracts_data(saved_file) + contracts_addresses = [] + for bytecode_version in contracts_data.values(): + contracts_addresses.extend(bytecode_version[contract_label]) + return contracts_addresses + + +def get_all_pair_addresses() -> list: + return get_saved_contract_addresses(PAIRS_LABEL, OUTPUT_PAIR_CONTRACTS_FILE) + + +def get_pairs_for_fees_addresses() -> list: + query = """ + { pairs (limit:100) { + address + lockedValueUSD + type + } } + """ + + result = run_graphql_query(GRAPHQL, query) + pairs = result['data']['pairs'] + sorted_pairs = [] + + for entry in pairs: + if entry['type'] == 'Core' or entry['type'] == 'Ecosystem': + sorted_pairs.append(entry['address']) + + return sorted_pairs + + +def get_all_farm_v13_addresses() -> list: + return get_saved_contract_addresses(FARMSV13_LABEL, OUTPUT_FARMV13_CONTRACTS_FILE) + + +def get_all_farm_v13locked_addresses() -> list: + return get_saved_contract_addresses(FARMSV13_LABEL, OUTPUT_FARMV13LOCKED_CONTRACTS_FILE) + + +def get_all_farm_v12_addresses() -> list: + return get_saved_contract_addresses(FARMSV12_LABEL, OUTPUT_FARMV12_CONTRACTS_FILE) + + +def get_all_staking_addresses() -> list: + return get_saved_contract_addresses(STAKINGS_LABEL, OUTPUT_STAKING_CONTRACTS_FILE) + + +def get_all_metastaking_addresses() -> list: + return get_saved_contract_addresses(METASTAKINGS_LABEL, OUTPUT_METASTAKING_CONTRACTS_FILE) + + +def fetch_and_save_pause_state(network_providers: NetworkProviders): + pair_addresses = get_all_pair_addresses() + staking_addresses = get_all_staking_addresses() + farm_addresses = get_all_farm_v13_addresses() + + contract_states = {} + for pair_address in pair_addresses: + data_fetcher = PairContractDataFetcher(pair_address, network_providers.proxy.url) + contract_state = data_fetcher.get_data("getState") + contract_states[pair_address] = contract_state + + for staking_address in staking_addresses: + data_fetcher = StakingContractDataFetcher(staking_address, network_providers.proxy.url) + contract_state = data_fetcher.get_data("getState") + contract_states[staking_address] = contract_state + + for farm_address in farm_addresses: + data_fetcher = FarmContractDataFetcher(farm_address, network_providers.proxy.url) + contract_state = data_fetcher.get_data("getState") + contract_states[farm_address] = contract_state + + with open(OUTPUT_PAUSE_STATES, 'w') as writer: + json.dump(contract_states, writer, indent=4) + print(f"Dumped contract pause states in {OUTPUT_PAUSE_STATES}") + + +def pause_pair_contracts(dex_owner: Account, network_providers: NetworkProviders): + pair_addresses = get_all_pair_addresses() + router_contract = retrieve_router_by_address(ROUTER_CONTRACT) + + # pause all the pairs + count = 1 + for pair_address in pair_addresses: + print(f"Processing contract {count} / {len(pair_addresses)}: {pair_address}") + data_fetcher = PairContractDataFetcher(pair_address, network_providers.proxy.url) + contract_state = data_fetcher.get_data("getState") + if contract_state != 0: + tx_hash = router_contract.pair_contract_pause(dex_owner, network_providers.proxy, pair_address) + if not network_providers.check_simple_tx_status(tx_hash, f"pause pair contract: {pair_address}"): + if not get_user_continue(): + return + else: + print(f"Contract {pair_address} already inactive. Current state: {contract_state}") + + count += 1 + + +def resume_pair_contracts(dex_owner: Account, network_providers: NetworkProviders): + if not os.path.exists(OUTPUT_PAUSE_STATES): + print("Contract initial states not found! Cannot proceed safely without altering initial state.") + + with open(OUTPUT_PAUSE_STATES) as reader: + contract_states = json.load(reader) + + pair_addresses = get_all_pair_addresses() + router_contract = retrieve_router_by_address(ROUTER_CONTRACT) + + # pause all the pairs + count = 1 + for pair_address in pair_addresses: + print(f"Processing contract {count} / {len(pair_addresses)}: {pair_address}") + if pair_address not in contract_states: + print(f"Contract {pair_address} wasn't touched for no available initial state!") + continue + # resume only if the pool was active + if contract_states[pair_address] == 1: + tx_hash = router_contract.pair_contract_resume(dex_owner, network_providers.proxy, pair_address) + if not network_providers.check_simple_tx_status(tx_hash, f"resume pair contract: {pair_address}"): + if not get_user_continue(): + return + else: + print(f"Contract {pair_address} wasn't touched because of initial state: {contract_states[pair_address]}") + + count += 1 + + +def pause_staking_contracts(dex_owner: Account, network_providers: NetworkProviders): + staking_addresses = get_all_staking_addresses() + + # pause all the stakings + count = 1 + for staking_address in staking_addresses: + print(f"Processing contract {count} / {len(staking_addresses)}: {staking_address}") + data_fetcher = StakingContractDataFetcher(staking_address, network_providers.proxy.url) + contract_state = data_fetcher.get_data("getState") + contract = StakingContract("", 0, 0, 0, StakingContractVersion.V1, "", staking_address) + if contract_state != 0: + tx_hash = contract.pause(dex_owner, network_providers.proxy) + if not network_providers.check_simple_tx_status(tx_hash, f"pause staking contract: {staking_address}"): + if not get_user_continue(): + return + else: + print(f"Contract {staking_address} already inactive. Current state: {contract_state}") + + count += 1 + + +def resume_staking_contracts(dex_owner: Account, network_providers: NetworkProviders): + if not os.path.exists(OUTPUT_PAUSE_STATES): + print("Contract initial states not found! Cannot proceed safely without altering initial state.") + + with open(OUTPUT_PAUSE_STATES) as reader: + contract_states = json.load(reader) + + staking_addresses = get_all_staking_addresses() + + # pause all the staking contracts + count = 1 + for staking_address in staking_addresses: + print(f"Processing contract {count} / {len(staking_addresses)}: {staking_address}") + if staking_address not in contract_states: + print(f"Contract {staking_address} wasn't touched for no available initial state!") + continue + # resume only if the staking contract was active + if contract_states[staking_address] == 1: + contract = StakingContract("", 0, 0, 0, StakingContractVersion.V1, "", staking_address) + tx_hash = contract.resume(dex_owner, network_providers.proxy) + if not network_providers.check_simple_tx_status(tx_hash, f"resume staking contract: {staking_address}"): + if not get_user_continue(): + return + else: + print(f"Contract {staking_address} wasn't touched because of initial state: " + f"{contract_states[staking_address]}") + + count += 1 + + +def pause_farm_contracts(dex_owner: Account, network_providers: NetworkProviders): + farm_addresses = get_all_farm_v13_addresses() + + # pause all the farms + count = 1 + for farm_address in farm_addresses: + print(f"Processing contract {count} / {len(farm_addresses)}: {farm_address}") + data_fetcher = FarmContractDataFetcher(farm_address, network_providers.proxy.url) + contract_state = data_fetcher.get_data("getState") + contract = FarmContract("", "", "", farm_address, FarmContractVersion.V14Locked) + if contract_state != 0: + tx_hash = contract.pause(dex_owner, network_providers.proxy) + if not network_providers.check_simple_tx_status(tx_hash, f"pause farm contract: {farm_address}"): + if not get_user_continue(): + return + else: + print(f"Contract {farm_address} already inactive. Current state: {contract_state}") + + count += 1 + + +def resume_farm_contracts(dex_owner: Account, network_providers: NetworkProviders): + if not os.path.exists(OUTPUT_PAUSE_STATES): + print("Contract initial states not found! Cannot proceed safely without altering initial state.") + + with open(OUTPUT_PAUSE_STATES) as reader: + contract_states = json.load(reader) + + farm_addresses = get_all_farm_v13_addresses() + + # pause all the farm contracts + count = 1 + for farm_address in farm_addresses: + print(f"Processing contract {count} / {len(farm_addresses)}: {farm_address}") + if farm_address not in contract_states: + print(f"Contract {farm_address} wasn't touched for no available initial state!") + continue + # resume only if the farm contract was active + if contract_states[farm_address] == 1: + contract = FarmContract("", "", "", farm_address, FarmContractVersion.V14Locked) + tx_hash = contract.resume(dex_owner, network_providers.proxy) + if not network_providers.check_simple_tx_status(tx_hash, f"resume farm contract: {farm_address}"): + if not get_user_continue(): + return + else: + print(f"Contract {farm_address} wasn't touched because of initial state: " + f"{contract_states[farm_address]}") + + count += 1 + + +def stop_produce_rewards_farms(dex_owner: Account, network_providers: NetworkProviders): + farm_addresses = get_all_farm_v13_addresses() + + # stop rewards in all the farms + count = 1 + for farm_address in farm_addresses: + print(f"Processing contract {count} / {len(farm_addresses)}: {farm_address}") + contract = FarmContract("", "", "", farm_address, FarmContractVersion.V14Locked) + tx_hash = contract.end_produce_rewards(dex_owner, network_providers.proxy) + if not network_providers.check_simple_tx_status(tx_hash, f"stop produce rewards farm contract: {farm_address}"): + if not get_user_continue(): + return + + count += 1 + + +def remove_penalty_farms(dex_owner: Account, network_providers: NetworkProviders): + farm_addresses = get_all_farm_v13_addresses() + + # remove penalty in all the farms + count = 1 + for farm_address in farm_addresses: + print(f"Processing contract {count} / {len(farm_addresses)}: {farm_address}") + contract = FarmContract("", "", "", farm_address, FarmContractVersion.V14Locked) + tx_hash = contract.set_penalty_percent(dex_owner, network_providers.proxy, 0) + if not network_providers.check_simple_tx_status(tx_hash, f"remove penalty farm contract: {farm_address}"): + if not get_user_continue(): + return + + count += 1 + + +def upgrade_template_pair_contract(owner: Account, network_providers: NetworkProviders): + router_data_fetcher = RouterContractDataFetcher(Address(ROUTER_CONTRACT), network_providers.proxy.url) + template_pair_address = Address(router_data_fetcher.get_data("getPairTemplateAddress")).bech32() + template_pair = retrieve_pair_by_address(template_pair_address) + + template_pair.version = PairContractVersion.V2 + args = [config.ZERO_CONTRACT_ADDRESS, config.ZERO_CONTRACT_ADDRESS, + config.ZERO_CONTRACT_ADDRESS, 0, 0, config.ZERO_CONTRACT_ADDRESS] + tx_hash = template_pair.contract_upgrade(owner, network_providers.proxy, config.PAIR_V2_BYTECODE_PATH, args) + + if not network_providers.check_complex_tx_status(tx_hash, f"upgrade template pair contract: {template_pair_address}"): + if not get_user_continue(): + return + + fetch_new_and_compare_contract_states(TEMPLATE_PAIR_LABEL, template_pair_address, network_providers) + + +def upgrade_router_contract(dex_owner: Account, network_providers: NetworkProviders): + router_contract = retrieve_router_by_address(ROUTER_CONTRACT) + router_data_fetcher = RouterContractDataFetcher(Address(ROUTER_CONTRACT), network_providers.proxy.url) + template_pair_address = Address(router_data_fetcher.get_data("getPairTemplateAddress")).bech32() + + # change router version & upgrade router contract + router_contract.version = RouterContractVersion.V2 + tx_hash = router_contract.contract_upgrade(dex_owner, network_providers.proxy, config.ROUTER_V2_BYTECODE_PATH, + [template_pair_address]) + + if not network_providers.check_complex_tx_status(tx_hash, f"upgrade router contract {ROUTER_CONTRACT}"): + return + + fetch_new_and_compare_contract_states(ROUTER_LABEL, ROUTER_CONTRACT, network_providers) + + +def upgrade_pair_contracts(dex_owner: Account, network_providers: NetworkProviders): + router_contract = retrieve_router_by_address(ROUTER_CONTRACT) + router_contract.version = RouterContractVersion.V2 + pair_addresses = get_all_pair_addresses() + + count = 1 + for pair_address in pair_addresses: + print(f"Processing contract {count} / {len(pair_addresses)}: {pair_address}") + pair_contract = retrieve_pair_by_address(pair_address) + pair_data_fetcher = PairContractDataFetcher(Address(pair_address), network_providers.proxy.url) + total_fee_percentage = pair_data_fetcher.get_data("getTotalFeePercent") + special_fee_percentage = pair_data_fetcher.get_data("getSpecialFee") + + pair_contract.version = PairContractVersion.V2 + tx_hash = pair_contract.contract_upgrade_via_router(dex_owner, network_providers.proxy, router_contract, + [total_fee_percentage, special_fee_percentage]) + + if not network_providers.check_complex_tx_status(tx_hash, f"upgrade pair contract: {pair_address}"): + if not get_user_continue(): + return + + fetch_new_and_compare_contract_states(PAIRS_LABEL, pair_address, network_providers) + + if not get_user_continue(): + return + + count += 1 + + +def update_fees_percentage(dex_owner: Account, network_providers: NetworkProviders): + pair_addresses = get_depositing_addresses() + + count = 1 + for pair_address in pair_addresses: + print(f"Processing contract {count} / {len(pair_addresses)}: {pair_address}") + pair_contract = retrieve_pair_by_address(pair_address) + pair_data_fetcher = PairContractDataFetcher(Address(pair_address), network_providers.proxy.url) + total_fee_percentage = pair_data_fetcher.get_data("getTotalFeePercent") + special_fee_percentage = 100 + + pair_contract.version = PairContractVersion.V2 + tx_hash = pair_contract.set_fees_percents(dex_owner, network_providers.proxy, + [total_fee_percentage, special_fee_percentage]) + + if not network_providers.check_complex_tx_status(tx_hash, f"set fees percentages: {pair_address}"): + if not get_user_continue(): + return + + fetch_new_and_compare_contract_states(PAIRS_LABEL, pair_address, network_providers) + + if not get_user_continue(): + return + + count += 1 + + +def upgrade_energy_contract(dex_owner: Account, network_providers: NetworkProviders): + pass + + +def upgrade_staking_contracts(dex_owner: Account, network_providers: NetworkProviders): + staking_addresses = get_all_staking_addresses() + + count = 1 + for staking_address in staking_addresses: + print(f"Processing contract {count} / {len(staking_addresses)}: {staking_address}") + staking_contract = retrieve_staking_by_address(staking_address) + + staking_contract.version = StakingContractVersion.V2 + tx_hash = staking_contract.contract_upgrade(dex_owner, network_providers.proxy, config.STAKING_V2_BYTECODE_PATH, + [dex_owner.address.bech32()]) + + if not network_providers.check_complex_tx_status(tx_hash, f"upgrade staking contract: {staking_address}"): + if not get_user_continue(): + return + + fetch_new_and_compare_contract_states(STAKINGS_LABEL, staking_address, network_providers) + + if not get_user_continue(): + return + + count += 1 + + +def upgrade_fix_staking_contracts(dex_owner: Account, network_providers: NetworkProviders): + staking_addresses = get_all_staking_addresses() + + count = 1 + for staking_address in staking_addresses: + print(f"Processing contract {count} / {len(staking_addresses)}: {staking_address}") + staking_contract = retrieve_staking_by_address(staking_address) + + staking_contract.version = StakingContractVersion.V2 + + print(f"Processing contract for: {staking_contract.farming_token}") + block_number = input(f"Previous upgrade block number:\n") + new_supply = input(f"New farm token supply:\n") + + tx_hash = staking_contract.contract_upgrade(dex_owner, network_providers.proxy, + config.STAKING_V2_BYTECODE_PATH, + [block_number, new_supply, dex_owner.address.bech32()], + False) + + if not network_providers.check_complex_tx_status(tx_hash, f"upgrade staking contract: {staking_address}"): + if not get_user_continue(): + return + + fetch_new_and_compare_contract_states(STAKINGS_LABEL, staking_address, network_providers) + + if not get_user_continue(): + return + + count += 1 + + +def upgrade_metastaking_contracts(dex_owner: Account, network_providers: NetworkProviders): + metastaking_addresses = get_all_metastaking_addresses() + + count = 1 + for metastaking_address in metastaking_addresses: + print(f"Processing contract {count} / {len(metastaking_addresses)}: {metastaking_address}") + metastaking_contract = MetaStakingContract("", "", "", "", "", "", "", "", metastaking_address) + + tx_hash = metastaking_contract.contract_upgrade(dex_owner, network_providers.proxy, + config.STAKING_PROXY_BYTECODE_PATH, [], + no_init=True) + + if not network_providers.check_complex_tx_status(tx_hash, f"upgrade metastaking contract: {metastaking_address}"): + if not get_user_continue(): + return + + fetch_new_and_compare_contract_states(METASTAKINGS_LABEL, metastaking_address, network_providers) + + if not get_user_continue(): + return + + count += 1 + + +def upgrade_farmv12_contracts(dex_owner: Account, network_providers: NetworkProviders): + all_addresses = get_all_farm_v12_addresses() + + count = 1 + for address in all_addresses: + print(f"Processing contract {count} / {len(all_addresses)}: {address}") + contract = FarmContract("", "", "", address, FarmContractVersion.V12) + + tx_hash = contract.contract_upgrade(dex_owner, network_providers.proxy, + config.FARM_V12_BYTECODE_PATH, [], + no_init=True) + + if not network_providers.check_complex_tx_status(tx_hash, f"upgrade farm v12 contract: {address}"): + if not get_user_continue(): + return + + fetch_new_and_compare_contract_states(FARMSV12_LABEL, address, network_providers) + + if not get_user_continue(): + return + + count += 1 + + +def upgrade_farmv13_contracts(dex_owner: Account, network_providers: NetworkProviders): + all_addresses = get_all_farm_v13locked_addresses() + + count = 1 + for address in all_addresses: + print(f"Processing contract {count} / {len(all_addresses)}: {address}") + contract = FarmContract("", "", "", address, FarmContractVersion.V14Locked) + + tx_hash = contract.contract_upgrade(dex_owner, network_providers.proxy, + config.FARM_V13_BYTECODE_PATH, [], + no_init=True) + + if not network_providers.check_complex_tx_status(tx_hash, f"upgrade farm v13 contract: {address}"): + if not get_user_continue(): + return + + fetch_new_and_compare_contract_states(FARMSV13_LABEL, address, network_providers) + + if not get_user_continue(): + return + + count += 1 + + +def set_transfer_role_farmv13_contracts(dex_owner: Account, network_providers: NetworkProviders): + all_addresses = get_all_farm_v13locked_addresses() + + count = 1 + for address in all_addresses: + print(f"Processing contract {count} / {len(all_addresses)}: {address}") + contract = FarmContract("", "", "", address, FarmContractVersion.V14Locked) + + tx_hash = contract.set_transfer_role_farm_token(dex_owner, network_providers.proxy, "") + + _ = network_providers.check_complex_tx_status(tx_hash, f"set transfer role farm v13 locked contract: {address}") + + if not get_user_continue(): + return + + count += 1 + + +def upgrade_locked_asset_contracts(dex_owner: Account, network_providers: NetworkProviders): + print(f"Processing contract {LOCKED_ASSET_FACTORY_CONTRACT}") + locked_asset_contract = LockedAssetContract("", "", LOCKED_ASSET_FACTORY_CONTRACT) + + tx_hash = locked_asset_contract.contract_upgrade(dex_owner, network_providers.proxy, + config.LOCKED_ASSET_FACTORY_BYTECODE_PATH, [], + no_init=True) + + if not network_providers.check_complex_tx_status(tx_hash, f"upgrade locked asset factory contract: " + f"{LOCKED_ASSET_FACTORY_CONTRACT}"): + if not get_user_continue(): + return + + fetch_new_and_compare_contract_states(LOCKED_ASSET_LABEL, LOCKED_ASSET_FACTORY_CONTRACT, network_providers) + + if not get_user_continue(): + return + + +def upgrade_proxy_dex_contracts(dex_owner: Account, network_providers: NetworkProviders): + print(f"Processing contract {PROXY_DEX_CONTRACT}") + proxy_dex_contract = DexProxyContract([], "", ProxyContractVersion.V1, address=PROXY_DEX_CONTRACT) + + tx_hash = proxy_dex_contract.contract_upgrade(dex_owner, network_providers.proxy, + config.PROXY_BYTECODE_PATH, [], + no_init=True) + + if not network_providers.check_complex_tx_status(tx_hash, f"upgrade proxy-dex contract: " + f"{PROXY_DEX_CONTRACT}"): + if not get_user_continue(): + return + + # fetch_new_and_compare_contract_states(PROXY_DEX_LABEL, PROXY_DEX_CONTRACT, network_providers) + + if not get_user_continue(): + return + + +def set_pairs_in_fees_collector(dex_owner: Account, network_providers: NetworkProviders): + pair_addresses = get_all_pair_addresses() + fees_collector = FeesCollectorContract(FEES_COLLECTOR_CONTRACT) + + count = 1 + for pair_address in pair_addresses: + print(f"Processing contract {count} / {len(pair_addresses)}: {pair_address}") + pair_contract = retrieve_pair_by_address(pair_address) + + # add pair address in fees collector + _ = fees_collector.add_known_contracts(dex_owner, network_providers.proxy, + [pair_address]) + _ = fees_collector.add_known_tokens(dex_owner, network_providers.proxy, + [f"str:{pair_contract.firstToken}", + f"str:{pair_contract.secondToken}"]) + + if not get_user_continue(): + return + + count += 1 + + +def remove_pairs_from_fees_collector(dex_owner: Account, network_providers: NetworkProviders): + fees_collector = FeesCollectorContract(FEES_COLLECTOR_CONTRACT) + pair_addresses = get_all_pair_addresses() + depositing_addresses = get_depositing_addresses() + + whitelisted_tokens = [] + + print("Aligning planets...") + for address in depositing_addresses: + if address in pair_addresses: + pair_addresses.remove(address) + + pair_contract = retrieve_pair_by_address(address) + whitelisted_tokens.append(pair_contract.firstToken) + whitelisted_tokens.append(pair_contract.secondToken) + + removable_addresses = [] + removable_tokens = [] + for pair_address in pair_addresses: + pair_contract = retrieve_pair_by_address(pair_address) + + removable_addresses.append(pair_address) + if pair_contract.firstToken not in whitelisted_tokens and pair_contract.firstToken not in removable_tokens: + removable_tokens.append(pair_contract.firstToken) + if pair_contract.secondToken not in whitelisted_tokens and pair_contract.secondToken not in removable_tokens: + removable_tokens.append(pair_contract.secondToken) + + print(f"Will remove {len(removable_addresses)} pairs from fees collector.") + print(f"Will remove {len(removable_tokens)} tokens from fees collector.") + print(removable_tokens) + if not get_user_continue(): + return + + count = 1 + # remove pair address in fees collector + for address in removable_addresses: + print(f"Processing contract {count} / {len(removable_addresses)}") + tx_hash = fees_collector.remove_known_contracts(dex_owner, network_providers.proxy, [address]) + if not network_providers.check_simple_tx_status(tx_hash, f"remove pair addresses"): + if not get_user_continue(): + return + count += 1 + + count = 1 + # remove token in fees collector + for token in removable_tokens: + print(f"Processing token {count} / {len(removable_tokens)}") + tx_hash = fees_collector.remove_known_tokens(dex_owner, network_providers.proxy, [f"str:{token}"]) + if not network_providers.check_simple_tx_status(tx_hash, f"remove token"): + if not get_user_continue(): + return + count += 1 + + if not get_user_continue(): + return + + +def get_depositing_addresses() -> list: + pair_addresses = get_pairs_for_fees_addresses() + + if not os.path.exists(OUTPUT_PAUSE_STATES): + print("Contract initial states not found! Cannot proceed due to risk of whitelisting inactive pairs.") + return [] + + with open(OUTPUT_PAUSE_STATES) as reader: + contract_states = json.load(reader) + + whitelist = [] + print(f"Whitelisted pairs:") + # filter whitelistable contracts + for pair_address in pair_addresses: + if pair_address not in contract_states: + print(f"Contract {pair_address} will be skipped. No available initial pause state!") + continue + # whitelist only if pool was active + if contract_states[pair_address] == 1: + whitelist.append(pair_address) + pair_contract = retrieve_pair_by_address(pair_address) + print(f"{pair_contract.firstToken} / {pair_contract.secondToken}") + + print(f"Number of contracts: {len(whitelist)}") + + if not get_user_continue(): + return [] + + return whitelist + + +def set_fees_collector_in_pairs(dex_owner: Account, network_providers: NetworkProviders): + fees_collector = FeesCollectorContract(FEES_COLLECTOR_CONTRACT) + whitelist = get_depositing_addresses() + + if not whitelist: + return + + count = 1 + for pair_address in whitelist: + print(f"Processing contract {count} / {len(whitelist)}: {pair_address}") + pair_contract = retrieve_pair_by_address(pair_address) + fees_cut = 50000 + + # setup fees collector in pair + tx_hash = pair_contract.add_fees_collector(dex_owner, network_providers.proxy, + [fees_collector.address, fees_cut]) + _ = network_providers.check_simple_tx_status(tx_hash, "set fees collector in pair") + + if not get_user_continue(): + return + + count += 1 + + +def fetch_contract_states(prefix: str, network_providers: NetworkProviders): + # get locked asset state + if LOCKED_ASSET_FACTORY_CONTRACT: + filename = get_contract_save_name(LOCKED_ASSET_LABEL, LOCKED_ASSET_FACTORY_CONTRACT, prefix) + get_account_keys_online(LOCKED_ASSET_FACTORY_CONTRACT, network_providers.proxy.url, + with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) + else: + print_test_step_fail(f"Locked asset factory address not available. No state saved for this!") + + # get proxy dex state + # filename = get_contract_save_name(PROXY_DEX_LABEL, PROXY_DEX_CONTRACT, prefix) + # get_account_keys_online(PROXY_DEX_CONTRACT, network_providers.proxy.url, + # with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) + + # get router state + if ROUTER_CONTRACT: + filename = get_contract_save_name(ROUTER_LABEL, ROUTER_CONTRACT, prefix) + get_account_keys_online(ROUTER_CONTRACT, network_providers.proxy.url, + with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) + else: + print_test_step_fail(f"Router address not available. No state saved for this!") + + # get template state + router_data_fetcher = RouterContractDataFetcher(Address(ROUTER_CONTRACT), network_providers.proxy.url) + template_pair_address = Address(router_data_fetcher.get_data("getPairTemplateAddress")).bech32() + filename = get_contract_save_name(TEMPLATE_PAIR_LABEL, template_pair_address, prefix) + get_account_keys_online(template_pair_address, network_providers.proxy.url, + with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) + + # get pairs contract states + pair_addresses = get_all_pair_addresses() + for pair_address in pair_addresses: + filename = get_contract_save_name(PAIRS_LABEL, pair_address, prefix) + get_account_keys_online(pair_address, network_providers.proxy.url, + with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) + + # get staking states + staking_addresses = get_all_staking_addresses() + for staking_address in staking_addresses: + filename = get_contract_save_name(STAKINGS_LABEL, staking_address, prefix) + get_account_keys_online(staking_address, network_providers.proxy.url, + with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) + + # get metastaking states + metastaking_addresses = get_all_metastaking_addresses() + for metastaking_address in metastaking_addresses: + filename = get_contract_save_name(METASTAKINGS_LABEL, metastaking_address, prefix) + get_account_keys_online(metastaking_address, network_providers.proxy.url, + with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) + + # get farm v12 states + farm_v12_addresses = get_all_farm_v12_addresses() + for farm_address in farm_v12_addresses: + filename = get_contract_save_name(FARMSV12_LABEL, farm_address, prefix) + get_account_keys_online(farm_address, network_providers.proxy.url, + with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) + + # get farm v13 states + farm_v13_addresses = get_all_farm_v13_addresses() + for farm_address in farm_v13_addresses: + filename = get_contract_save_name(FARMSV13_LABEL, farm_address, prefix) + get_account_keys_online(farm_address, network_providers.proxy.url, + with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) + + +def fetch_contract_state(contract_address: str, save_name: str, network_providers: NetworkProviders): + get_account_keys_online(contract_address, network_providers.proxy.url, + with_save_in=str(OUTPUT_FOLDER / f"{save_name}.json")) + + +def get_contract_save_name(contract_type: str, address: str, prefix: str): + return f"{prefix}_{contract_type}_{address}" + + +def fetch_new_and_compare_contract_states(contract_type: str, contract_address, network_providers: NetworkProviders): + old_state_filename = get_contract_save_name(contract_type, contract_address, "pre") + new_state_filename = get_contract_save_name(contract_type, contract_address, "mid") + fetch_contract_state(contract_address, new_state_filename, network_providers) + report_key_files_compare(str(OUTPUT_FOLDER), old_state_filename, new_state_filename, True) + + +def get_user_continue() -> bool: + typed = input(f"Continue? y/n\n") + while typed != "y" and typed != "n": + typed = input(f"Wrong choice. Continue? y/n\n") + if typed == "n": + return False + return True + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/tools/graphql_interactor.py b/tools/graphql_interactor.py new file mode 100644 index 0000000..bde5529 --- /dev/null +++ b/tools/graphql_interactor.py @@ -0,0 +1,38 @@ +import sys +from typing import List + +import graphene +import requests + + +def run_query(uri, query, statusCode, headers): + request = requests.post(uri, json={'query': query}, headers=headers) + if request.status_code == statusCode: + return request.json() + else: + raise Exception(f"Unexpected status code returned: {request.status_code}") + + +def main(cli_args: List[str]): + uri = 'https://graph.maiar.exchange/graphql' + token = 'string' + headers = {} + status_code = 200 + + query = """ + { + stakingProxies{ + address + dualYieldToken { + name + } + } + } + """ + + result = run_query(uri, query, status_code, headers) + print(result) + + +if __name__ == "__main__": + main(sys.argv[1:]) \ No newline at end of file diff --git a/tools/manual_issue_token.py b/tools/manual_issue_token.py new file mode 100644 index 0000000..36e2037 --- /dev/null +++ b/tools/manual_issue_token.py @@ -0,0 +1,87 @@ +import logging +import sys +from argparse import ArgumentParser +from typing import List + +import config +from arrows.stress.esdtnft.shared import (build_token_name, build_token_ticker, + load_contracts, make_call_arg_ascii, + make_call_arg_pubkey) +from arrows.stress.shared import BunchOfAccounts, broadcast_transactions +from erdpy.contracts import SmartContract +from erdpy.proxy.core import ElrondProxy +from erdpy.transactions import Transaction + + +def main(cli_args: List[str]): + logging.basicConfig(level=logging.ERROR) + + parser = ArgumentParser() + parser.add_argument("--proxy", default=config.DEFAULT_PROXY) + parser.add_argument("--accounts", default=config.DEFAULT_OWNER) + parser.add_argument("--sleep-between-chunks", type=int, default=5) + parser.add_argument("--chunk-size", type=int, default=400) + parser.add_argument("--from-shard") + parser.add_argument("--via-shard") + parser.add_argument("--base-gas-limit", type=int, default=config.DEFAULT_GAS_BASE_LIMIT_ISSUE) + parser.add_argument("--gas-limit", type=int, default=0) + parser.add_argument("--supply", type=int, default=1000000000000000000) + parser.add_argument("--token-id") + parser.add_argument("--yes", action="store_true", default=False) + parser.add_argument("--mode", choices=["direct", "via"], default="direct") + + args = parser.parse_args(cli_args) + + proxy = ElrondProxy(args.proxy) + network = proxy.get_network_config() + + bunch_of_accounts = BunchOfAccounts.load_accounts_from_files([args.accounts]) + # bunch_of_accounts.sync_nonces(proxy) + accounts = bunch_of_accounts.get_all() if args.from_shard is None else bunch_of_accounts.get_in_shard(int(args.from_shard)) + account = accounts[0] # issue tokens only for SC owner account to improve times on large number of accounts + account.sync_nonce(proxy) + + supply = args.supply + token_id = args.token_id + print("Token id: ", token_id) + print("Supply: ", supply) + + def issue_token(): + for account in accounts: + sc_args = [f'str:{token_id}', supply] + + receiver_address = SmartContract(address=account.address) + tx_data = receiver_address.prepare_execute_transaction_data("ESDTLocalMint", sc_args) + + gas_limit = args.gas_limit or args.base_gas_limit + 50000 + 1500 * len(tx_data) + value = "0" + + tx = Transaction() + tx.nonce = account.nonce + tx.value = value + tx.sender = account.address.bech32() + tx.receiver = receiver_address.address.bech32() + tx.gasPrice = network.min_gas_price + tx.gasLimit = gas_limit + tx.data = tx_data + tx.chainID = str(network.chain_id) + tx.version = network.min_tx_version + tx.sign(account) + + print("Holder account: ", account.address) + print("Token id: ", token_id) + + transactions.append(tx) + account.nonce += 1 + + transactions: List[Transaction] = [] + + issue_token() + + hashes = broadcast_transactions(transactions, proxy, args.chunk_size, sleep=args.sleep_between_chunks, confirm_yes=args.yes) + + return hashes + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/tools/manual_tools.py b/tools/manual_tools.py new file mode 100644 index 0000000..208af91 --- /dev/null +++ b/tools/manual_tools.py @@ -0,0 +1,174 @@ +import sys, requests, re +import traceback +from argparse import ArgumentParser +from typing import List + +from utils.utils_chain import decode_merged_attributes, base64_to_hex + +PROXY = "https://testnet-gateway.elrond.com" +API = "https://testnet-api.elrond.com" + + +def main(cli_args: List[str]): + parser = ArgumentParser() + parser.add_argument("--type", required=False) # needed in case of --attrs + parser.add_argument("--attrs", required=False, default="") # optional, either this or --token + parser.add_argument("--encoding", default="base64") + parser.add_argument("--token", required=False, default="") # optional, either this or --attrs + args = parser.parse_args(cli_args) + + # export PYTHONPATH=. + # python3 arrows/stress/dex/manual_tools.py --type=staked --attrs=AAAABwQh8UGLGBkAAAAAAAPY5gAAAAAAAAAIlfxdqMQuIxQ= + # python3 arrows/stress/dex/manual_tools.py --token=RIDESTAKE-1e7cef-6422 + + attributes_schema_staked_tokens = { + 'reward_per_share': 'biguint', + 'compounded_reward': 'biguint', + 'current_farm_amount': 'biguint', + } + + attributes_schema_unstaked_tokens = { + 'unlock_epoch': 'u64', + } + + attributes_schema_proxy_staked_tokens = { + 'lp_farm_token_nonce': 'u64', + 'lp_farm_token_amount': 'biguint', + 'staking_farm_token_nonce': 'u64', + 'staking_farm_token_amount': 'biguint', + } + + attributes_schema_farmv12_tokens = { + 'rewards_per_share': 'biguint', + 'entering_epoch': 'u64', + 'original_entering_epoch': 'u64', + 'apr_multiplier': 'u8', + 'locked_rewards': 'u8', + 'initial_farming_amount': 'biguint', + 'compounded_rewards': 'biguint', + 'current_farm_amount': 'biguint', + } + + attributes_schema_farmv14_tokens = { + 'rewards_per_share': 'biguint', + 'entering_epoch': 'u64', + 'original_entering_epoch': 'u64', + 'apr_multiplier': 'u8', + 'locked_rewards': 'u8', + 'initial_farming_amount': 'biguint', + 'compounded_rewards': 'biguint', + 'current_farm_amount': 'biguint', + } + + esdt_token_payment_schema = { + 'token_type': 'u8', + 'token_identifier': 'string', + 'token_nonce': 'u64', + 'amount': 'biguint', + } + + simple_locked_token_schema = { + 'token_identifier': 'string', + 'original_token_nonce': 'u64', + 'unlock_epoch': 'u64', + } + + simple_locked_lp_schema = { + 'lp_token_id': 'string', + 'first_token_id': 'string', + 'first_token_locked_nonce': 'u64', + 'second_token_id': 'string', + 'second_token_locked_nonce': 'u64', + } + + simple_locked_farm_schema = { + 'farm_type': 'u8', + 'farm_token_id': 'string', + 'farm_token_nonce': 'u64', + 'farming_token_id': 'string', + 'farming_token_locked_nonce': 'u64', + } + + launchpad_epoch_config = { + 'confirmation': 'u64', + 'winners': 'u64', + 'claim': 'u64', + } + + unlock_schedule_schema = { + 'unlock_schedule_list': { + 'unlock_epoch': 'u64', + 'unlock_percent': 'u64' + }, + 'merged': 'u8' + } + + tokens_schema_mapper = { + "RIDESTAKE-1e7cef": attributes_schema_staked_tokens, + "SWEB-d9bfe2": attributes_schema_staked_tokens, + "METARIDE-4bd193": attributes_schema_proxy_staked_tokens, + "METARIDELK-b28dc3": attributes_schema_proxy_staked_tokens, + "SLKTK1-475419": simple_locked_token_schema, + "LKLPS1-7ad725": simple_locked_lp_schema, + "LKFARM-a74e31": simple_locked_farm_schema, + "EGLDMEXFL-ef2065": attributes_schema_farmv14_tokens, + } + + decoded_attrs = {} + attributes_schema = {} + if args.type == "staked": + attributes_schema = attributes_schema_staked_tokens + if args.type == "proxystaked": + attributes_schema = attributes_schema_proxy_staked_tokens + if args.type == "unstaked": + attributes_schema = attributes_schema_unstaked_tokens + if args.type == "farmv12": + attributes_schema = attributes_schema_farmv12_tokens + if args.type == "farmv14": + attributes_schema = attributes_schema_farmv14_tokens + if args.type == "tokenpayment": + attributes_schema = esdt_token_payment_schema + if args.type == "simplelocked": + attributes_schema = simple_locked_token_schema + if args.type == "simplelockedlp": + attributes_schema = simple_locked_lp_schema + if args.type == "simplelockedfarm": + attributes_schema = simple_locked_farm_schema + if args.type == "launchpadconfig": + attributes_schema = launchpad_epoch_config + if args.type == "lockedtokens": + attributes_schema = unlock_schedule_schema + + attrs = "" + # handling for passed attributes + if args.attrs != "": + if args.encoding == "hex": + attrs = args.attrs + else: + attrs = base64_to_hex(args.attrs) + + # handling for fetched token attributes directly from network + if args.token != "": + try: + response = requests.get(f"{API}/nfts/{args.token}").json() + if "attributes" in response: + attrs = base64_to_hex(response["attributes"]) + token_ticker = re.match('[^-]*-[^-]*', args.token).group(0) + attributes_schema = tokens_schema_mapper[token_ticker] + else: + print("given token not found or something's not robust enough in this piece of code (dooooh)") + return + except Exception as ex: + print(ex) + traceback.print_exception(*sys.exc_info()) + + if attributes_schema != {}: + decoded_attrs = decode_merged_attributes(attrs, attributes_schema) + print(decoded_attrs) + else: + print(f"Unknown type given.") + # TODO: replace input array with attributes schema + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/tools/safeprice_monitor.py b/tools/safeprice_monitor.py new file mode 100644 index 0000000..7e05acb --- /dev/null +++ b/tools/safeprice_monitor.py @@ -0,0 +1,93 @@ +import csv +import sys +import time +from typing import List + +from utils.contract_data_fetchers import PairContractDataFetcher +from erdpy.accounts import Address +from erdpy.proxy.core import ElrondProxy +from utils.utils_chain import decode_merged_attributes + +PROXY = "https://gateway.elrond.com" +PAIR_ADDRESS = "erd1qqqqqqqqqqqqqpgqav09xenkuqsdyeyy5evqyhuusvu4gl3t2jpss57g8x" +TOKEN_IN = "RIDE-7d18e9" +TOKEN_IN_REF_AMOUNT = "0de0b6b3a7640000" +NUM_BLOCKS_OBSERVED = 5000 +SAMPLE_INTERVAL = 3 +LOG_FILENAME = "arrows/stress/dex/results/safe_price_observations.csv" + +MODEL_SAMPLES = 100 + + +def main(cli_args: List[str]): + contract_data_fetcher = PairContractDataFetcher(Address(PAIR_ADDRESS), PROXY) + proxy = ElrondProxy(PROXY) + + esdt_token_payment_schema = { + 'token_type': 'u8', + 'token_identifier': 'string', + 'token_nonce': 'u64', + 'amount': 'biguint', + } + + csv_header = ["block", "safe_price", "spot_price"] + with open(LOG_FILENAME, 'w') as f: + file_writer = csv.writer(f) + file_writer.writerow(csv_header) + + i = NUM_BLOCKS_OBSERVED + while i: + hex_val = contract_data_fetcher.get_data("updateAndGetSafePrice", + ["0x000000000b524944452d3764313865390000000000000000000000080de0b6b3a7640000"]) + decoded_attrs = decode_merged_attributes(hex_val, esdt_token_payment_schema) + spot_price = contract_data_fetcher.get_data("getEquivalent", + ["0x" + TOKEN_IN.encode('utf-8').hex(), "0x" + TOKEN_IN_REF_AMOUNT]) + last_block = proxy.get_last_block_nonce(1) + + print(f"Token: {decoded_attrs['token_identifier']} SAFE PRICE: {decoded_attrs['amount']}") + print(f"Token: {decoded_attrs['token_identifier']} SPOT PRICE: {spot_price}") + + with open(LOG_FILENAME, 'a') as f: + file_writer = csv.writer(f) + file_writer.writerow([last_block, decoded_attrs['amount'], spot_price]) + + time.sleep(SAMPLE_INTERVAL) + i -= 1 + + +class PriceSample: + price: int + block: int + + def __init__(self, price, block): + self.price = price + self.block = block + + +class OfflineModel: + observations: List[PriceSample] + current_index: int + last_computed_price: PriceSample + + def __init__(self): + self.current_index = 0 + self.last_computed_price = PriceSample(0, 0) + + def add_observation(self, price, block): + observation = PriceSample(price, block) + + if self.current_index == MODEL_SAMPLES: + self.observations.pop(0) + else: + self.current_index += 1 + + self.observations.append(observation) + + def compute_averaged_price(self): + for observation in self.observations: + elapsed_blocks = observation.block - self.last_computed_price.block + #TODO: finish math here + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/trackers/abstract_observer.py b/trackers/abstract_observer.py new file mode 100644 index 0000000..2bcd181 --- /dev/null +++ b/trackers/abstract_observer.py @@ -0,0 +1,21 @@ +from abc import ABC, abstractmethod + + +class Subscriber(ABC): + @abstractmethod + def update(self, publisher: "Publisher"): + pass + + +class Publisher(ABC): + @abstractmethod + def subscribe(self, subscriber: Subscriber): + pass + + @abstractmethod + def unsubscribe(self, subscriber: Subscriber): + pass + + @abstractmethod + def notify(self): + pass diff --git a/trackers/concrete_observer.py b/trackers/concrete_observer.py new file mode 100644 index 0000000..e0922be --- /dev/null +++ b/trackers/concrete_observer.py @@ -0,0 +1,36 @@ +from typing import List, Any +from .abstract_observer import Publisher, Subscriber +from ..contracts.contract_identities import DEXContractInterface +from erdpy.accounts import Account + + +class Observable(Publisher): + observers: List[Subscriber] = [] + contract: DEXContractInterface + user: Account + event: Any + tx_hash: str + + def subscribe(self, subscriber: Subscriber): + print('A new observer has subscribed') + self.observers.append(subscriber) + + def unsubscribe(self, subscriber: Subscriber): + print('A observer has unsubscribed') + self.observers.remove(subscriber) + + def notify(self): + for observer in self.observers: + observer.update(self) + + def set_event(self, contract: DEXContractInterface, user: Account, event_data: Any, txhash: str): + self.contract = contract + self.user = user + self.event = event_data + self.tx_hash = txhash + self.notify() + + +class Observer(Subscriber): + def update(self, publisher: Observable): + pass diff --git a/trackers/farm_economics_tracking.py b/trackers/farm_economics_tracking.py new file mode 100644 index 0000000..d7a0290 --- /dev/null +++ b/trackers/farm_economics_tracking.py @@ -0,0 +1,519 @@ +from typing import Dict +from utils.contract_data_fetchers import FarmContractDataFetcher, ChainDataFetcher +from utils.utils_tx import NetworkProviders +from erdpy.accounts import Account, Address +from utils.utils_chain import (print_test_step_pass, print_test_step_fail, print_test_substep, + DecodedTokenAttributes) +from events.farm_events import (EnterFarmEvent, ExitFarmEvent, + ClaimRewardsFarmEvent, SetTokenBalanceEvent) +from trackers.abstract_observer import Subscriber +from trackers.concrete_observer import Observable +from utils.utils_chain import get_current_tokens_for_address +from contracts.contract_identities import FarmContractVersion + + +class FarmAccountEconomics(Subscriber): + + def __init__(self, address: Address, network_provider: NetworkProviders): + self.address = address + self.network_provider = network_provider + self.tokens = get_current_tokens_for_address(self.address, self.network_provider.proxy) + self.report_current_tokens() + + def enter_farm(self, event: EnterFarmEvent) -> None: + old_farming_token_balance = int(self.tokens[event.farming_tk]['balance']) + farm_token = self.tokens.get(event.farm_tk, None) + old_farm_token_balance = farm_token['balance'] if farm_token else 0 + old_farm_token_nonce = farm_token['nonce'] if farm_token else 0 + + self.tokens = get_current_tokens_for_address(self.address, self.network_provider.proxy) + + new_farming_token_balance = int(self.tokens[event.farming_tk]['balance']) + new_farm_token_balance = int(self.tokens[event.farm_tk]['balance']) + new_farm_token_nonce = int(self.tokens[event.farm_tk]['nonce']) + + expected_farming_token_balance = int(old_farming_token_balance) - event.farming_tk_amount + expected_farm_token_balance = int(old_farm_token_balance) + event.farming_tk_amount + + if old_farm_token_nonce >= new_farm_token_nonce: + print_test_step_fail(f'Farm token nonce did not increase for {self.address.bech32()}') + print_test_substep(f'Old farm token nonce: {old_farm_token_nonce}') + print_test_substep(f'New farm token nonce: {new_farm_token_nonce}') + + if new_farming_token_balance != expected_farming_token_balance: + print_test_step_fail(f'Farming token balance did not decrease for account {self.address.bech32()}') + print_test_substep(f'Old farming token amount: {old_farming_token_balance}') + print_test_substep(f'New farming token amount: {new_farming_token_balance}') + print_test_substep(f'Expected farming token amount: {expected_farming_token_balance}') + + if farm_token is not None: + if new_farm_token_balance != expected_farm_token_balance: + print_test_step_fail(f'Farm token balance did not increase for account {self.address.bech32()}') + print_test_substep(f'Old farm token amount: {old_farm_token_balance}') + print_test_substep(f'New farm token amount: {new_farm_token_balance}') + print_test_substep(f'Expected farm token amount: {expected_farm_token_balance}') + + print_test_step_pass('Checked enter farm event economics for account') + + def exit_farm(self, contract, event: ExitFarmEvent) -> None: + farm_token = self.tokens.get(event.farm_token, None) + old_farm_token_balance = farm_token['balance'] if farm_token else 0 + + farming_token = self.tokens.get(contract.farmingToken, None) + old_farming_token_balance = farming_token['balance'] if farming_token else 0 + + farmed_token = self.tokens.get(contract.farmedToken, None) + old_farmed_token_balance = farmed_token['balance'] if farmed_token else 0 + + self.tokens = get_current_tokens_for_address(self.address, self.network_provider.proxy) + + new_farm_token_balance = int(self.tokens[event.farm_token]['balance']) + expected_farm_token_balance = int(old_farm_token_balance) - event.amount + + new_farming_token_balance = int(self.tokens[contract.farmingToken]['balance']) + expected_farming_token_balance = old_farming_token_balance + event.amount + + new_farmed_token_balance = int(self.tokens[contract.farmedToken]['balance']) + + if new_farm_token_balance != expected_farm_token_balance: + print_test_step_fail(f'Farm token amount did not decrease for account {self.address.bech32()}') + print_test_substep(f'Old farm token amount: {old_farm_token_balance}') + print_test_substep(f'New farm token amount: {new_farm_token_balance}') + print_test_substep(f'Expected farm token amount: {expected_farm_token_balance}') + + if new_farming_token_balance != expected_farming_token_balance: + print_test_step_fail(f'Farming token amount did not increase for account {self.address.bech32()}') + print_test_substep(f'Old farming token amount {old_farming_token_balance}') + print_test_substep(f'New farming token amount {new_farming_token_balance}') + print_test_substep(f'Expected farming token amount {expected_farming_token_balance}') + + if old_farmed_token_balance >= new_farmed_token_balance: + print_test_step_fail(f'Farmed token amount did not increase for account {self.address.bech32()}') + print_test_substep(f'Old farmed token amount {old_farmed_token_balance}') + print_test_substep(f'New farmed token amount {new_farmed_token_balance}') + + print_test_step_pass('Checked exit farm event economics for account') + + def claim_rewards(self, contract, event: ClaimRewardsFarmEvent) -> None: + farmed_token = self.tokens.get(contract.farmedToken, None) + old_farmed_token_balance = farmed_token['balance'] if farmed_token else 0 + + self.tokens = get_current_tokens_for_address(self.address, self.network_provider.proxy) + + new_farmed_token_balance = int(self.tokens[contract.farmedToken]['balance']) + expected_farmed_token_balance = int(old_farmed_token_balance) + event.amount + + if new_farmed_token_balance != expected_farmed_token_balance: + print_test_step_fail(f'Farmed token amount did not increase for account {self.address.bech32()}') + print_test_substep(f'Old farm token amount: {old_farmed_token_balance}') + print_test_substep(f'New farm token amount: {new_farmed_token_balance}') + print_test_substep(f'Expected farm token amount: {expected_farmed_token_balance}') + + print_test_step_pass('Checked claim rewards event economics for account') + + def report_current_tokens(self): + print_test_step_pass(f'All tokens for account {self.address}') + for key in self.tokens: + print_test_substep(f'{key}: {self.tokens[key]["balance"]}') + + def set_token_balance(self, event: SetTokenBalanceEvent): + if event.token not in self.tokens: + token = {event.token: {'nonce': event.nonce, 'balance': event.balance}} + + self.tokens.update(token) + else: + self.tokens[event.token]['nonce'] = event.nonce + self.tokens[event.token]['balance'] = event.balance + + print_test_step_pass(f'Updated token balance for account {self.address.bech32()}') + + def update(self, publisher: Observable): + if publisher.user.address == self.address: + if publisher.tx_hash: + self.network_provider.api.wait_for_tx_finalized(publisher.tx_hash) + if type(publisher.event) == EnterFarmEvent: + self.enter_farm(publisher.event) + elif type(publisher.event) == ExitFarmEvent: + self.exit_farm(publisher.contract, publisher.event) + elif type(publisher.event) == ClaimRewardsFarmEvent: + self.claim_rewards(publisher.contract, publisher.event) + elif type(publisher.event) == SetTokenBalanceEvent: + self.set_token_balance(publisher.event) + + +class FarmEconomics(Subscriber): + + def __init__(self, contract_address: str, version: FarmContractVersion, network_provider: NetworkProviders): + self.contract_address = Address(contract_address) + self.version = version + self.network_provider = network_provider + self.farm_data_fetcher = FarmContractDataFetcher(self.contract_address, network_provider.proxy.url) + self.chain_data_fetcher = ChainDataFetcher(network_provider.proxy.url) + self.accounts: Dict[str, FarmAccountEconomics] = {} + + self.rewards_per_block = self.farm_data_fetcher.get_data("getPerBlockRewardAmount") + self.farm_token_supply = self.farm_data_fetcher.get_data("getFarmTokenSupply") + self.rewards_reserve = self.farm_data_fetcher.get_data("getRewardReserve") + self.rewards_per_share = self.farm_data_fetcher.get_data("getRewardPerShare") + self.last_rewards_block_nonce = self.farm_data_fetcher.get_data("getLastRewardBlockNonce") + self.last_block_calculated_rewards = self.last_rewards_block_nonce # initialize with the last one from contract + self.division_safety_constant = self.farm_data_fetcher.get_data("getDivisionSafetyConstant") + self.rewards_per_share_wout_division = 0 + + # only v1.2 farms + self.farm_token_supply_locked = 0 + self.farm_token_supply_unlocked = 0 + + self.report_current_tracking_data() + + def report_current_tracking_data(self): + print(f"Farm: {self.contract_address.bech32()}") + print(f"Farm token supply: {self.farm_token_supply}") + print(f"Rewards per block: {self.rewards_per_block}") + print(f"Rewards reserve: {self.rewards_reserve}") + print(f"Rewards per share: {self.rewards_per_share}") + print(f"Last rewards block nonce: {self.last_rewards_block_nonce}") + print(f"Last block offline calculated rewards: {self.last_block_calculated_rewards}") + + # checks and updates the farm contract invariant properties + def check_invariant_properties(self): + # TODO: replace test reporting with logger + new_rewards_per_share = self.farm_data_fetcher.get_data("getRewardPerShare") + new_last_rewards_block_nonce = self.farm_data_fetcher.get_data("getLastRewardBlockNonce") + chain_rewards_per_block = self.farm_data_fetcher.get_data("getPerBlockRewardAmount") + chain_division_safety_constant = self.farm_data_fetcher.get_data("getDivisionSafetyConstant") + + if self.rewards_per_share > new_rewards_per_share: + print_test_step_fail("TEST CHECK FAIL: Rewards per share decreased!") + print_test_substep(f"Old rewards per share: {self.rewards_per_share}") + print_test_substep(f"New rewards per share: {new_rewards_per_share}") + if self.last_rewards_block_nonce > new_last_rewards_block_nonce: + print_test_step_fail("TEST CHECK FAIL: Last rewards block nonce decreased!") + print_test_substep(f"Old rewards block nonce: {self.last_rewards_block_nonce}") + print_test_substep(f"New rewards block nonce: {new_last_rewards_block_nonce}") + if self.rewards_per_block != chain_rewards_per_block: + print_test_step_fail("TEST CHECK FAIL: Rewards per block has changed!") + print_test_substep(f"Old rewards per block: {self.rewards_per_block}") + print_test_substep(f"New rewards per block: {chain_rewards_per_block}") + if self.division_safety_constant != chain_division_safety_constant: + print_test_step_fail("TEST CHECK FAIL: Division safety constant has changed!") + print_test_substep(f"Old division safety constant: {self.division_safety_constant}") + print_test_substep(f"New division safety constant: {chain_division_safety_constant}") + + print_test_step_pass("Checked invariant properties!") + + def check_enter_farm_properties(self): + # track event dependent properties + new_farm_token_supply = self.farm_data_fetcher.get_data("getFarmTokenSupply") + new_rewards_reserve = self.farm_data_fetcher.get_data("getRewardReserve") + + ENTER_FARM_FARM_TK_SUPPLY_FAIL = "TEST CHECK FAIL: Farm token supply did not increase!" + ENTER_FARM_REWARDS_RESERVE_FAIL = "TEST CHECK FAIL: Rewards reserve decreased!" + + if self.farm_token_supply >= new_farm_token_supply: + print_test_step_fail(ENTER_FARM_FARM_TK_SUPPLY_FAIL) + print_test_substep(f"Old Farm token supply: {self.farm_token_supply}") + print_test_substep(f"New Farm token supply: {new_farm_token_supply}") + if self.rewards_reserve >= new_rewards_reserve: + print_test_step_fail(ENTER_FARM_REWARDS_RESERVE_FAIL) + print_test_substep(f"Old Rewards reserve: {self.rewards_reserve}") + print_test_substep(f"New Rewards reserve: {new_rewards_reserve}") + + print_test_step_pass("Checked enter farm properties!") + + def check_enter_farm_tx_data(self, event: EnterFarmEvent, txhash: str): + new_contract_farm_token_supply = self.farm_data_fetcher.get_data("getFarmTokenSupply") + new_contract_rewards_reserve = self.farm_data_fetcher.get_data("getRewardReserve") + new_contract_rewards_per_share = self.farm_data_fetcher.get_data("getRewardPerShare") + new_last_rewards_block_nonce = self.farm_data_fetcher.get_data("getLastRewardBlockNonce") + tx_block = self.chain_data_fetcher.get_tx_block_nonce(txhash) + new_exp_farm_token_supply = self.farm_token_supply + event.farming_tk_amount # farming token amount is converted to farm token amount + + aggregated_rewards = (tx_block - self.last_block_calculated_rewards) * self.rewards_per_block + new_exp_rewards_reserve = self.rewards_reserve + aggregated_rewards + # NOTE: rewards per share value applies a division safety constant in calculus which is later taken out when actual rewards are calculated + if self.farm_token_supply != 0: + new_exp_rewards_per_share = self.rewards_per_share + \ + (self.division_safety_constant * aggregated_rewards // self.farm_token_supply) + self.rewards_per_share_wout_division = new_exp_rewards_reserve // self.farm_token_supply + # todo: clarify if rewards per share should not consider the new farm tokens + else: + new_exp_rewards_per_share = 0 + + def report_generic_fails(): + print_test_substep(f"Old Rewards reserve: {self.rewards_reserve}") + print_test_substep(f"New Rewards reserve in contract: {new_contract_rewards_reserve}") + print_test_substep(f"Expected Rewards reserve: {new_exp_rewards_reserve}") + print_test_substep(f"Aggregated rewards: {aggregated_rewards}") + print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + print_test_substep(f"Rewards per block: {self.rewards_per_block}") + print_test_substep(f"TX hash: {txhash}") + print_test_substep(f"TX block nonce: {tx_block}") + print_test_substep(f"Last calculated rewards at block nonce:{self.last_block_calculated_rewards}") + + # check for FARM TOKEN SUPPLY integrity + if new_contract_farm_token_supply != new_exp_farm_token_supply: + print_test_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") + print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + report_generic_fails() + + # check for REWARDS PER SHARE integrity + if new_contract_rewards_per_share != new_exp_rewards_per_share: + print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + print_test_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") + print_test_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") + report_generic_fails() + + # check for REWARDS RESERVE integrity + if new_contract_rewards_reserve != new_exp_rewards_reserve: + print_test_step_fail(f"TEST CHECK FAIL: Rewards reserve not as expected!") + report_generic_fails() + + # check for LAST REWARD BLOCK integrity + if tx_block != new_last_rewards_block_nonce: + print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + print_test_substep(f"Expected last reward block nonce: {tx_block}") + + # TODO: decide afterwards if this is a good place for this update, or whether it should be moved with the others + self.last_block_calculated_rewards = tx_block + + print_test_step_pass("Checked enter farm tx data!") + + def check_exit_farm_properties(self): + # track event dependent properties + new_farm_token_supply = self.farm_data_fetcher.get_data("getFarmTokenSupply") + + EXIT_FARM_FARM_TK_SUPPLY_FAIL = "TEST CHECK FAIL: Farm token supply did not decrease!" + + if self.farm_token_supply <= new_farm_token_supply: + print_test_step_fail(f"{EXIT_FARM_FARM_TK_SUPPLY_FAIL}") + print_test_substep(f"Old Farm token supply: {self.farm_token_supply}") + print_test_substep(f"New Farm token supply: {new_farm_token_supply}") + + """moved into exit farm check as it can differ from case to case based on side of exit position + if self.rewards_reserve < new_rewards_reserve: # TODO: have to check whether rewards should be given and if it increased + print_test_step_fail(f"{EXIT_FARM_REWARDS_RESERVE_FAIL}") + print_test_substep(f"Old Rewards reserve: {self.rewards_reserve}") + print_test_substep(f"New Rewards reserve: {new_rewards_reserve}") + """ + + print_test_step_pass(f"Checked exit farm properties!") + + def check_exit_farm_tx_data(self, event: ExitFarmEvent, txhash: str): + # TODO: check burned tokens if penalty applies + # TODO: care for compounding as well when calculating penalty + new_contract_farm_token_supply = self.farm_data_fetcher.get_data("getFarmTokenSupply") + new_contract_rewards_reserve = self.farm_data_fetcher.get_data("getRewardReserve") + new_contract_rewards_per_share = self.farm_data_fetcher.get_data("getRewardPerShare") + new_last_rewards_block_nonce = self.farm_data_fetcher.get_data("getLastRewardBlockNonce") + tx_block = self.chain_data_fetcher.get_tx_block_nonce(txhash) + new_exp_farm_token_supply = self.farm_token_supply - event.amount + + aggregated_rewards = (tx_block - self.last_block_calculated_rewards) * self.rewards_per_block + # NOTE: rewards per share value applies a division safety constant in calculus which is later taken out when actual rewards are calculated + new_exp_rewards_per_share = self.rewards_per_share + \ + (self.division_safety_constant * aggregated_rewards // self.farm_token_supply) + + # TODO: store a snapshot of farm tokens amounts at event creation and use it to compare afterwards for results + + decoded_attrs = DecodedTokenAttributes(event.attributes, self.version) + # TODO: check what's the correct variant of calculating the drop in Rewards reserve and Rewards for exit position + exp_rewards = (new_exp_rewards_per_share - decoded_attrs.rewards_per_share) // self.division_safety_constant * event.amount + new_exp_rewards_reserve = self.rewards_reserve + aggregated_rewards - exp_rewards + self.rewards_per_share_wout_division = new_exp_rewards_reserve // self.farm_token_supply + + # todo: clarify if rewards per share should not consider the new farm tokens + + def report_generic_fails(): + print_test_substep(f"Old Rewards reserve: {self.rewards_reserve}") + print_test_substep(f"New Rewards reserve in contract: {new_contract_rewards_reserve}") + print_test_substep(f"Expected Rewards reserve: {new_exp_rewards_reserve}") + print_test_substep(f"Aggregated rewards: {aggregated_rewards}") + print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + print_test_substep(f"Rewards per block: {self.rewards_per_block}") + print_test_substep(f"TX hash: {txhash}") + print_test_substep(f"TX block nonce: {tx_block}") + print_test_substep(f"Last calculated rewards at block nonce:{self.last_block_calculated_rewards}") + + # check for FARM TOKEN SUPPLY integrity + if new_contract_farm_token_supply != new_exp_farm_token_supply: + print_test_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") + print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + report_generic_fails() + + # check for REWARDS PER SHARE integrity + if new_contract_rewards_per_share != new_exp_rewards_per_share: + print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + print_test_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") + print_test_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") + report_generic_fails() + + # check for REWARDS RESERVE integrity + if new_contract_rewards_reserve != new_exp_rewards_reserve: + print_test_step_fail(f"TEST CHECK FAIL: Rewards reserve not as expected!") + print_test_substep(f"Rewards per share in contract: {new_contract_rewards_per_share}") + print_test_substep(f"Rewards per share in exit position: {decoded_attrs.rewards_per_share}") + print_test_substep(f"Exit position amount: {event.amount}") + print_test_substep(f"Expected rewards per position: {exp_rewards}") + report_generic_fails() + + # check for LAST REWARD BLOCK integrity + if tx_block != new_last_rewards_block_nonce: + print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + print_test_substep(f"Expected last reward block nonce: {tx_block}") + + # TODO: decide afterwards if this is a good place for this update, or whether it should be moved with the others + self.last_block_calculated_rewards = tx_block + + print_test_step_pass("Checked exit farm tx data!") + + def check_claim_rewards_properties(self): + # track event dependent properties + new_farm_token_supply = self.farm_data_fetcher.get_data("getFarmTokenSupply") + + CLAIM_REWARDS_FARM_TK_SUPPLY_FAIL = "TEST CHECK FAIL: Farm token supply modified!" + + if self.farm_token_supply != new_farm_token_supply: + print_test_step_fail(CLAIM_REWARDS_FARM_TK_SUPPLY_FAIL) + print_test_substep(f"Old Farm token supply: {self.farm_token_supply}") + print_test_substep(f"New Farm token supply: {new_farm_token_supply}") + + print_test_step_pass("Checked claim rewards properties!") + + def check_claim_rewards_farm_tx_data(self, event: ClaimRewardsFarmEvent, txhash: str): + # TODO: check burned tokens if penalty applies + # TODO: care for compounding as well when calculating penalty + new_contract_farm_token_supply = self.farm_data_fetcher.get_data("getFarmTokenSupply") + new_contract_rewards_reserve = self.farm_data_fetcher.get_data("getRewardReserve") + new_contract_rewards_per_share = self.farm_data_fetcher.get_data("getRewardPerShare") + new_last_rewards_block_nonce = self.farm_data_fetcher.get_data("getLastRewardBlockNonce") + tx_block = self.chain_data_fetcher.get_tx_block_nonce(txhash) + new_exp_farm_token_supply = self.farm_token_supply + + aggregated_rewards = (tx_block - self.last_block_calculated_rewards) * self.rewards_per_block + # NOTE: rewards per share value applies a division safety constant in calculus which is later taken out when actual rewards are calculated + new_exp_rewards_per_share = self.rewards_per_share + \ + (self.division_safety_constant * aggregated_rewards // self.farm_token_supply) + + # TODO: store a snapshot of farm tokens amounts at event creation and use it to compare afterwards for results + + decoded_attrs = DecodedTokenAttributes(event.attributes, self.version) + # TODO: check what's the correct variant of calculating the drop in Rewards reserve and Rewards for exit position + exp_rewards = (new_exp_rewards_per_share - decoded_attrs.rewards_per_share) // self.division_safety_constant * event.amount + new_exp_rewards_reserve = self.rewards_reserve + aggregated_rewards - exp_rewards + self.rewards_per_share_wout_division = new_exp_rewards_reserve // self.farm_token_supply + + # todo: clarify if rewards per share should not consider the new farm tokens + + def report_generic_fails(): + print_test_substep(f"Old Rewards reserve: {self.rewards_reserve}") + print_test_substep(f"New Rewards reserve in contract: {new_contract_rewards_reserve}") + print_test_substep(f"Expected Rewards reserve: {new_exp_rewards_reserve}") + print_test_substep(f"Aggregated rewards: {aggregated_rewards}") + print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + print_test_substep(f"Rewards per block: {self.rewards_per_block}") + print_test_substep(f"TX hash: {txhash}") + print_test_substep(f"TX block nonce: {tx_block}") + print_test_substep(f"Last calculated rewards at block nonce:{self.last_block_calculated_rewards}") + + # check for FARM TOKEN SUPPLY integrity + if new_contract_farm_token_supply != new_exp_farm_token_supply: + print_test_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") + print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + report_generic_fails() + + # check for REWARDS PER SHARE integrity + if new_contract_rewards_per_share != new_exp_rewards_per_share: + print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + print_test_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") + print_test_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") + report_generic_fails() + + # check for REWARDS RESERVE integrity + if new_contract_rewards_reserve != new_exp_rewards_reserve: + print_test_step_fail(f"TEST CHECK FAIL: Rewards reserve not as expected!") + print_test_substep(f"Rewards per share in contract: {new_contract_rewards_per_share}") + print_test_substep(f"Rewards per share in exit position: {decoded_attrs.rewards_per_share}") + print_test_substep(f"Exit position amount: {event.amount}") + print_test_substep(f"Expected rewards per position: {exp_rewards}") + report_generic_fails() + + # check for LAST REWARD BLOCK integrity + if tx_block != new_last_rewards_block_nonce: + print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + print_test_substep(f"Expected last reward block nonce: {tx_block}") + + # TODO: decide afterwards if this is a good place for this update, or whether it should be moved with the others + self.last_block_calculated_rewards = tx_block + + print_test_step_pass("Checked claim rewards tx data!") + + def __enter_farm_event_for_account(self, account: Account, block: int, lp_staked: int): + if account.address.bech32() not in self.accounts: + self.accounts[account.address.bech32()] = FarmAccountEconomics(account.address) + self.accounts[account.address.bech32()].enter_farm(self.rewards_per_share, block, lp_staked) + # TODO: integrate this tracker in the enter farm event and care for the proper initialization of LP staked when + # there are already existing positions in farm + + def update_tracking_data(self): + new_rewards_per_share = self.farm_data_fetcher.get_data("getRewardPerShare") + new_last_rewards_block_nonce = self.farm_data_fetcher.get_data("getLastRewardBlockNonce") + chain_rewards_per_block = self.farm_data_fetcher.get_data("getPerBlockRewardAmount") + new_farm_token_supply = self.farm_data_fetcher.get_data("getFarmTokenSupply") + new_rewards_reserve = self.farm_data_fetcher.get_data("getRewardReserve") + new_division_safety_constant = self.farm_data_fetcher.get_data("getDivisionSafetyConstant") + + # TODO: update only the data that doesn't match + self.last_rewards_block_nonce = new_last_rewards_block_nonce + self.rewards_per_share = new_rewards_per_share + self.rewards_per_block = chain_rewards_per_block + self.farm_token_supply = new_farm_token_supply + self.rewards_reserve = new_rewards_reserve + self.division_safety_constant = new_division_safety_constant + + print_test_step_pass("Updated farm tracking data!") + + def enter_farm_event_tracking(self, account: Account, event: EnterFarmEvent, txhash: str, lock: int = 0): + # TODO: replace error reporting with logger + # track invariant properties + self.check_invariant_properties() + self.check_enter_farm_properties() + self.check_enter_farm_tx_data(event, txhash) + self.update_tracking_data() + + def exit_farm_event_tracking(self, account: Account, event: ExitFarmEvent, txhash: str): + # TODO: replace error reporting with logger + # track invariant properties + self.check_invariant_properties() + self.check_exit_farm_properties() + self.check_exit_farm_tx_data(event, txhash) + self.update_tracking_data() + + def claim_rewards_farm_event_tracking(self, account: Account, event: ExitFarmEvent, txhash: str): + self.check_invariant_properties() + self.check_claim_rewards_properties() + self.check_claim_rewards_farm_tx_data(event, txhash) + self.update_tracking_data() + + def update(self, publisher: Observable): + if publisher.contract is not None: + if str(self.contract_address) == publisher.contract.address: + self.network_provider.api.wait_for_tx_finalized(publisher.tx_hash) + if type(publisher.event) == EnterFarmEvent: + self.enter_farm_event_tracking(publisher.user, publisher.event, publisher.tx_hash) + elif type(publisher.event) == ExitFarmEvent: + self.exit_farm_event_tracking(publisher.user, publisher.event, publisher.tx_hash) + elif type(publisher.event) == ClaimRewardsFarmEvent: + self.claim_rewards_farm_event_tracking(publisher.user, publisher.event, publisher.tx_hash) diff --git a/trackers/metastaking_economics_tracking.py b/trackers/metastaking_economics_tracking.py new file mode 100644 index 0000000..11c2da0 --- /dev/null +++ b/trackers/metastaking_economics_tracking.py @@ -0,0 +1,175 @@ +from erdpy.accounts import Address +from utils.utils_tx import NetworkProviders +from utils.utils_chain import print_test_step_pass +from utils.contract_data_fetchers import MetaStakingContractDataFetcher, ChainDataFetcher +from events.metastake_events import (EnterMetastakeEvent, + ExitMetastakeEvent, + ClaimRewardsMetastakeEvent) +from trackers.abstract_observer import Subscriber +from trackers.concrete_observer import Observable +from contracts.farm_contract import FarmContract +from trackers.farm_economics_tracking import FarmEconomics +from trackers.pair_economics_tracking import PairEconomics +from trackers.staking_economics_tracking import StakingEconomics +from events.farm_events import EnterFarmEvent, ClaimRewardsFarmEvent, ExitFarmEvent +from utils.utils_chain import decode_merged_attributes, base64_to_hex +from contracts.pair_contract import PairContract, RemoveLiquidityEvent, SetCorrectReservesEvent + + +class MetastakingEconomics(Subscriber): + def __init__(self, contract_address: str, staking_address: str, farm_contract: FarmContract, + pair_contract: PairContract, network_provider: NetworkProviders): + self.contract_address = Address(contract_address) + self.farm_contract = farm_contract + self.pair_contract = pair_contract + self.network_provider = network_provider + self.data_fetcher = MetaStakingContractDataFetcher(self.contract_address, self.network_provider.proxy.url) + self.chain_data_fetcher = ChainDataFetcher(self.network_provider.proxy.url) + + self.staking_tracker = StakingEconomics(staking_address, self.network_provider) + self.farm_tracker = FarmEconomics(farm_contract.address, farm_contract.version, self.network_provider) + self.pair_tracker = PairEconomics(pair_contract.address, pair_contract.firstToken, pair_contract.secondToken, + self.network_provider) + self.staking_tracker.report_current_tracking_data() + + def check_enter_metastaking_data(self, publisher: Observable): + """Farm Token Supply check might fail, the fix should come in a later PR""" + + farm_tk_amount = publisher.event.metastaking_tk_amount + lp_tokens_amount = self.pair_tracker.pair_data_fetcher.get_data( + 'updateAndGetTokensForGivenPositionWithSafePrice', [farm_tk_amount] + ) + first_token, second_token = self.__get_tokens_for_lp_amount(lp_tokens_amount) + + if first_token['token_id'] == publisher.contract.staking_token: + event = EnterFarmEvent(first_token['token_id'], first_token['token_nonce'], first_token['amount'], + '', 0, 0) + else: + event = EnterFarmEvent(second_token['token_id'], second_token['token_nonce'], second_token['amount'], + '', 0, 0) + + self.staking_tracker.check_enter_staking_data(event, publisher.tx_hash) + + def __get_lp_from_metastake_token_attributes(self, token_attributes): + """LP amount is the same as FarmTokenAmount""" + + attributes_schema_proxy_staked_tokens = { + 'lp_farm_token_nonce': 'u64', + 'lp_farm_token_amount': 'biguint', + 'staking_farm_token_nonce': 'u64', + 'staking_farm_token_amount': 'biguint', + } + + lp_position = decode_merged_attributes(token_attributes, attributes_schema_proxy_staked_tokens) + return lp_position + + def __get_tokens_for_lp_amount(self, lp_amount: list): + """Returns tokens from LP position""" + + attribute_schema_lp_tokens = { + 'token_id': 'string', + 'token_nonce': 'u64', + 'amount': 'biguint' + } + + first_token = decode_merged_attributes(lp_amount[0], attribute_schema_lp_tokens) + second_token = decode_merged_attributes(lp_amount[1], attribute_schema_lp_tokens) + + return first_token, second_token + + def check_claim_rewards_data(self, publisher: Observable): + """Farm Token Supply check might fail, the fix should come in a later PR""" + + claim_farm_rewards_event = ClaimRewardsFarmEvent( + int(publisher.event.farm_token_details['supply']), publisher.event.farm_token_details['nonce'], + base64_to_hex(publisher.event.farm_token_details['attributes']) + ) + + self.staking_tracker.check_claim_rewards_data(publisher.tx_hash) + self.farm_tracker.check_claim_rewards_farm_tx_data(claim_farm_rewards_event, publisher.tx_hash) + + def __rule_of_three(self, first_amount, first_equivalent, second_amount): + return (second_amount * first_equivalent) // first_amount + + def __compute_tokens_slippage(self, first_token: dict, second_token: dict, slippage: float): + """This function receives as the input parameters the two tokens from an LP position + and the slippage and returns the amounts for each token""" + first_token_amount = first_token['amount'] - int(first_token['amount'] * slippage) + second_token_amount = second_token['amount'] - int(second_token['amount'] * slippage) + return first_token_amount, second_token_amount + + def check_exit_metastaking_data(self, publisher: Observable): + decoded_metastake_tk_attributes = self.__get_lp_from_metastake_token_attributes( + publisher.event.metastake_token_attributes) + + exit_staking_event = ExitFarmEvent(publisher.contract.staking_token, + publisher.event.amount, + decoded_metastake_tk_attributes['staking_farm_token_nonce'], '') + self.staking_tracker.check_exit_staking_data(exit_staking_event, publisher.tx_hash) + + farm_token_amount = self.__rule_of_three(publisher.event.whole_metastake_token_amount, + decoded_metastake_tk_attributes['lp_farm_token_amount'], + publisher.event.amount) + + exit_farm_event = ExitFarmEvent(publisher.contract.farm_token, + farm_token_amount, + publisher.event.farm_token_details['nonce'], + base64_to_hex(publisher.event.farm_token_details['attributes'])) + self.farm_tracker.exit_farm_event_tracking(publisher.user, exit_farm_event, publisher.tx_hash) + + lp_amount = farm_token_amount + token_amounts = self.pair_tracker.pair_data_fetcher.get_data("getTokensForGivenPosition", [lp_amount]) + + decoding_schema = { + 'token_id': 'string', + 'token_nonce': 'u64', + 'amount': 'biguint' + } + + first_token_deserialized = decode_merged_attributes(token_amounts[0], decoding_schema) + second_token_deserialized = decode_merged_attributes(token_amounts[1], decoding_schema) + + first_tk_amount, second_tk_amount = self.__compute_tokens_slippage(first_token_deserialized, + second_token_deserialized, + 0.05) + + remove_liquidity_event = RemoveLiquidityEvent(lp_amount, first_token_deserialized['token_id'], first_tk_amount, + second_token_deserialized['token_id'], second_tk_amount) + self.pair_tracker.check_remove_liquidity(remove_liquidity_event) + + def check_enter_metastaking(self, publisher: Observable): + self.staking_tracker.check_invariant_properties() + self.staking_tracker.check_enter_staking_properties() + self.check_enter_metastaking_data(publisher) + print_test_step_pass('Checked enter metastaking event economics!') + + def check_exit_metastaking(self, publisher: Observable): + self.staking_tracker.check_invariant_properties() + self.staking_tracker.check_exit_staking_properties() + self.check_exit_metastaking_data(publisher) + print_test_step_pass('Checked exit metastaking event economics!') + + def check_claim_rewards(self, publisher: Observable): + self.staking_tracker.check_invariant_properties() + self.staking_tracker.check_claim_rewards_properties() + self.check_claim_rewards_data(publisher) + print_test_step_pass('Checked claim metastaking rewards event economics!') + + def update_trackers_data(self): + self.staking_tracker.update_data() + self.farm_tracker.update_tracking_data() + self.pair_tracker._get_tokens_reserve_and_total_supply() + + def update(self, publisher: Observable): + if publisher.contract is not None: + if str(self.contract_address) == publisher.contract.address: + if publisher.tx_hash: + self.network_provider.api.wait_for_tx_finalized(publisher.tx_hash) + if isinstance(publisher.event, SetCorrectReservesEvent): + self.update_trackers_data() + elif isinstance(publisher.event, EnterMetastakeEvent): + self.check_enter_metastaking(publisher) + elif isinstance(publisher.event, ExitMetastakeEvent): + self.check_exit_metastaking(publisher) + elif isinstance(publisher.event, ClaimRewardsMetastakeEvent): + self.check_claim_rewards(publisher) diff --git a/trackers/pair_economics_tracking.py b/trackers/pair_economics_tracking.py new file mode 100644 index 0000000..000276f --- /dev/null +++ b/trackers/pair_economics_tracking.py @@ -0,0 +1,215 @@ +from erdpy.accounts import Address +from utils.utils_tx import NetworkProviders +from trackers.abstract_observer import Subscriber +from trackers.concrete_observer import Observable +from utils.contract_data_fetchers import PairContractDataFetcher +from utils.utils_chain import print_test_step_pass, print_test_step_fail, print_test_substep +from contracts.pair_contract import (AddLiquidityEvent, + RemoveLiquidityEvent, + SwapFixedInputEvent, + SwapFixedOutputEvent, + SetCorrectReservesEvent) + + +class PairEconomics(Subscriber): + + def __init__(self, contract_address: str, first_token: str, second_token: str, network_provider: NetworkProviders): + self.contract_address = Address(contract_address) + self.network_provider = network_provider + self.pair_data_fetcher = PairContractDataFetcher(self.contract_address, self.network_provider.proxy.url) + self._get_tokens_reserve_and_total_supply() + self.fee = 0 + self.report_current_tracking_data() + self.first_token = first_token + self.second_token = second_token + + def _get_tokens_reserve_and_total_supply(self): + reserves_and_total_supply = self.pair_data_fetcher.get_data("getReservesAndTotalSupply") + if reserves_and_total_supply: + self.first_token_reserve = reserves_and_total_supply[0] + self.second_token_reserve = reserves_and_total_supply[1] + self.total_supply = reserves_and_total_supply[2] + else: + self.first_token_reserve = 0 + self.second_token_reserve = 0 + self.total_supply = 0 + + def report_current_tracking_data(self): + print(f'Pair contract address: {self.contract_address.bech32()}') + print(f'First token reserve: {self.first_token_reserve}') + print(f'Second token reserve: {self.second_token_reserve}') + print(f'Total supply: {self.total_supply}') + + def check_add_liquidity(self, event: AddLiquidityEvent): + old_first_token_reserve = self.first_token_reserve + old_second_token_reserve = self.second_token_reserve + old_total_supply = self.total_supply + + self._get_tokens_reserve_and_total_supply() + + new_first_token_reserve = self.first_token_reserve + new_second_token_reserve = self.second_token_reserve + new_total_supply = self.total_supply + + if event.tokenA == self.first_token: + expected_first_token_reserve = old_first_token_reserve + event.amountAmin + expected_second_token_reserve = old_second_token_reserve + event.amountBmin + else: + expected_first_token_reserve = old_first_token_reserve + event.amountBmin + expected_second_token_reserve = old_second_token_reserve + event.amountAmin + + if old_first_token_reserve >= new_first_token_reserve: + print_test_step_fail(f'First token reserve did not increase for pair {self.contract_address.bech32()}') + print_test_substep(f'Old first token reserve: {old_first_token_reserve}') + print_test_substep(f'New first token reserve: {new_first_token_reserve}') + print_test_substep(f'Minimum first token reserve expected: {expected_first_token_reserve}') + + if old_second_token_reserve >= new_second_token_reserve: + print_test_step_fail(f'Second token reserve did not increase for pair {self.contract_address.bech32()}') + print_test_substep(f'Old second token reserve: {old_second_token_reserve}') + print_test_substep(f'New second token reserve: {new_second_token_reserve}') + print_test_substep(f'Minimum second token reserve expected: {expected_second_token_reserve}') + + if old_total_supply >= new_total_supply: + print_test_step_fail(f'Total supply did not increase for pair {self.contract_address.bech32()}') + print_test_substep(f'Old supply: {old_total_supply}') + print_test_substep(f'New supply: {new_total_supply}') + + print_test_step_pass('Checked addLiquidityEvent economics!') + + def check_remove_liquidity(self, event: RemoveLiquidityEvent): + old_first_token_reserve = self.first_token_reserve + old_second_token_reserve = self.second_token_reserve + old_total_supply = self.total_supply + + self._get_tokens_reserve_and_total_supply() + + new_first_token_reserve = self.first_token_reserve + new_second_token_reserve = self.second_token_reserve + new_total_supply = self.total_supply + + if event.tokenA == self.first_token: + expected_first_token_reserve = old_first_token_reserve - event.amountA + expected_second_token_reserve = old_second_token_reserve - event.amountB + else: + expected_first_token_reserve = old_first_token_reserve - event.amountB + expected_second_token_reserve = old_second_token_reserve - event.amountA + + if old_first_token_reserve <= new_first_token_reserve: + print_test_step_fail(f'First token reserve did not decrease for pair {self.contract_address.bech32()}') + print_test_substep(f'Old first token reserve: {old_first_token_reserve}') + print_test_substep(f'New first token reserve: {new_first_token_reserve}') + print_test_substep(f'Maximum first token reserve expected: {expected_first_token_reserve}') + + if old_second_token_reserve <= new_second_token_reserve: + print_test_step_fail(f'Second token reserve did not decrease for pair {self.contract_address.bech32()}') + print_test_substep(f'Old second token reserve: {old_second_token_reserve}') + print_test_substep(f'New second token reserve: {new_second_token_reserve}') + print_test_substep(f'Maximum second token reserve expected: {expected_second_token_reserve}') + + if old_total_supply <= new_total_supply: + print_test_step_fail(f'Total supply did not decrease for pair {self.contract_address.bech32()}') + print_test_substep(f'Old supply: {old_total_supply}') + print_test_substep(f'New supply: {new_total_supply}') + + print_test_step_pass('Checked removeLiquidityEvent economics!') + + def check_swap_fixed_input(self, event: SwapFixedInputEvent): + old_first_token_reserve = self.first_token_reserve + old_second_token_reserve = self.second_token_reserve + + self._get_tokens_reserve_and_total_supply() + + new_first_token_reserve = self.first_token_reserve + new_second_token_reserve = self.second_token_reserve + + if event.tokenA == self.first_token: + expected_first_token_reserve = old_first_token_reserve + event.amountA + expected_second_token_reserve = old_second_token_reserve - event.amountBmin + + if old_first_token_reserve >= new_first_token_reserve: + print_test_step_fail(f'First token reserve did not increase for pair {self.contract_address.bech32()}') + print_test_substep(f'Old first token reserve: {old_first_token_reserve}') + print_test_substep(f'New first token reserve: {new_first_token_reserve}') + print_test_substep(f'Expected first token reserve: {expected_first_token_reserve}') + + if old_second_token_reserve <= new_second_token_reserve: + print_test_step_fail(f'Second token reserve did not decrease for pair {self.contract_address.bech32()}') + print_test_substep(f'Old second token reserve: {old_second_token_reserve}') + print_test_substep(f'New second token reserve: {new_second_token_reserve}') + print_test_substep(f'Maximum expected second token reserve: {expected_second_token_reserve}') + else: + expected_first_token_reserve = old_first_token_reserve - event.amountBmin + expected_second_token_reserve = old_second_token_reserve + event.amountA + + if old_first_token_reserve <= new_first_token_reserve: + print_test_step_fail(f'First token reserve did not decrease for pair {self.contract_address.bech32()}') + print_test_substep(f'Old first token reserve: {old_first_token_reserve}') + print_test_substep(f'New first token reserve: {new_first_token_reserve}') + print_test_substep(f'Expected first token reserve: {expected_first_token_reserve}') + + if old_second_token_reserve >= new_second_token_reserve: + print_test_step_fail(f'Second token reserve did not increase for pair {self.contract_address.bech32()}') + print_test_substep(f'Old second token reserve: {old_second_token_reserve}') + print_test_substep(f'New second token reserve: {new_second_token_reserve}') + print_test_substep(f'Maximum expected second token reserve: {expected_second_token_reserve}') + + print_test_step_pass(f'Checked swapFixedInputEvent economics') + + def check_swap_fixed_output(self, event: SwapFixedOutputEvent): + old_first_token_reserve = self.first_token_reserve + old_second_token_reserve = self.second_token_reserve + + self._get_tokens_reserve_and_total_supply() + + new_first_token_reserve = self.first_token_reserve + new_second_token_reserve = self.second_token_reserve + + if event.tokenA == self.first_token: + expected_first_token_reserve = old_first_token_reserve + event.amountAmax + expected_second_token_reserve = old_second_token_reserve - event.amountB + + if old_first_token_reserve >= new_first_token_reserve: + print_test_step_fail(f'First token reserve did not increase for pair {self.contract_address.bech32()}') + print_test_substep(f'Old first token reserve: {old_first_token_reserve}') + print_test_substep(f'New first token reserve: {new_first_token_reserve}') + print_test_substep(f'Maximum expected first token reserve: {expected_first_token_reserve}') + + if old_second_token_reserve <= new_second_token_reserve: + print_test_step_fail(f'Second token reserve did not decrease for pair {self.contract_address.bech32()}') + print_test_substep(f'Old second token reserve: {old_second_token_reserve}') + print_test_substep(f'New second token reserve: {new_second_token_reserve}') + print_test_substep(f'Expected second token reserve: {expected_second_token_reserve}') + else: + expected_first_token_reserve = old_first_token_reserve - event.amountB + expected_second_token_reserve = old_second_token_reserve + event.amountAmax + + if old_first_token_reserve <= new_first_token_reserve: + print_test_step_fail(f'First token reserve did not decrease for pair {self.contract_address.bech32()}') + print_test_substep(f'Old first token reserve: {old_first_token_reserve}') + print_test_substep(f'New first token reserve: {new_first_token_reserve}') + print_test_substep(f'Maximum expected first token reserve: {expected_first_token_reserve}') + + if old_second_token_reserve >= new_second_token_reserve: + print_test_step_fail(f'Second token reserve did not increase for pair {self.contract_address.bech32()}') + print_test_substep(f'Old second token reserve: {old_second_token_reserve}') + print_test_substep(f'New second token reserve: {new_second_token_reserve}') + print_test_substep(f'Expected second token reserve: {expected_second_token_reserve}') + + print_test_step_pass(f'Checked swapFixedOutputEvent economics') + + def update(self, publisher: Observable): + if publisher.contract is not None: + if self.contract_address.bech32() == publisher.contract.address: + if publisher.tx_hash: + self.network_provider.api.wait_for_tx_finalized(publisher.tx_hash) + if type(publisher.event) == AddLiquidityEvent: + self.check_add_liquidity(publisher.event) + elif type(publisher.event) == RemoveLiquidityEvent: + self.check_remove_liquidity(publisher.event) + elif type(publisher.event) == SwapFixedInputEvent: + self.check_swap_fixed_input(publisher.event) + elif type(publisher.event) == SwapFixedOutputEvent: + self.check_swap_fixed_output(publisher.event) + elif type(publisher.event) == SetCorrectReservesEvent: + self._get_tokens_reserve_and_total_supply() diff --git a/trackers/price_discovery_economics_tracking.py b/trackers/price_discovery_economics_tracking.py new file mode 100644 index 0000000..2213fc6 --- /dev/null +++ b/trackers/price_discovery_economics_tracking.py @@ -0,0 +1,342 @@ +from typing import Dict + +from utils.contract_data_fetchers import PriceDiscoveryContractDataFetcher +from events.price_discovery_events import (DepositPDLiquidityEvent, + WithdrawPDLiquidityEvent, RedeemPDLPTokensEvent) +from contracts.contract_identities import PriceDiscoveryContractIdentity +from utils.utils_chain import print_test_step_fail, print_test_substep, print_test_step_pass, \ + get_all_token_nonces_details_for_account, get_token_details_for_address +from erdpy.accounts import Address +from erdpy.proxy import ElrondProxy + + +class PriceDiscoveryAccountEconomics: + def __init__(self, address: Address): + self.address = address + self.first_token_deposited = 0 + self.second_token_deposited = 0 + self.first_redeem_tokens_owned = 0 + self.second_redeem_tokens_owned = 0 + self.lp_tokens_redeemed = 0 + + # these can be retrieved at init to compare against them afterwards + # e.g. first_token_owned_init - first_token_deposited == current account balance on first token + self.first_token_owned_init = 0 + self.second_token_owned_init = 0 + + +class PriceDiscoveryEconomics: + def __init__(self, contract_identity: PriceDiscoveryContractIdentity, proxy_url: str): + self.proxy = ElrondProxy(proxy_url) + self.pd_contract_identity = contract_identity + self.contract_data_fetcher = PriceDiscoveryContractDataFetcher(Address(contract_identity.address), proxy_url) + + self.first_token_reserve = 0 + self.second_token_reserve = 0 + self.first_redeem_tokens_reserve = 0 + self.second_redeem_tokens_reserve = 0 + self.lp_tokens_reserve = 0 + # after sending tokens to pool, this will be equal to lp_tokens_reserve. Will be kept fixed for calculations. + self.total_lp_tokens_received = 0 + # after sending tokens to pool, these will be kept fixed as final token reserves for further calculations of LPs + self.final_first_token_reserve = 0 + self.final_second_token_reserve = 0 + + self.account_tracker: Dict[str, PriceDiscoveryAccountEconomics] = {} + + def __check_deposit_event(self, event: DepositPDLiquidityEvent): + chain_first_token_reserve = self.contract_data_fetcher.get_token_reserve(self.pd_contract_identity.launched_token_id) + chain_second_token_reserve = self.contract_data_fetcher.get_token_reserve(self.pd_contract_identity.accepted_token) + + if event.deposit_token == self.pd_contract_identity.launched_token_id: + new_first_token_reserve = self.first_token_reserve + event.amount + new_second_token_reserve = self.second_token_reserve + else: + new_first_token_reserve = self.first_token_reserve + new_second_token_reserve = self.second_token_reserve + event.amount + + if chain_first_token_reserve != new_first_token_reserve: + print_test_step_fail("TEST CHECK FAIL: First token reserve not as expected!") + print_test_substep(f"Chain first token reserve: {chain_first_token_reserve}") + print_test_substep(f"Expected first token reserve: {new_first_token_reserve}") + + if chain_second_token_reserve != new_second_token_reserve: + print_test_step_fail("TEST CHECK FAIL: Second token reserve not as expected!") + print_test_substep(f"Chain second token reserve: {chain_second_token_reserve}") + print_test_substep(f"Expected second token reserve: {new_second_token_reserve}") + + print_test_step_pass("Checked deposit event data!") + + def __deposit_event_account_tracking(self, event: DepositPDLiquidityEvent, user_address: Address): + if user_address.bech32() not in self.account_tracker.keys(): + self.account_tracker[user_address.bech32()] = PriceDiscoveryAccountEconomics(user_address) + + # TODO: check first/second_token_deposited conformity starting from an account init + if event.deposit_token == self.pd_contract_identity.launched_token_id: + self.account_tracker[user_address.bech32()].first_token_deposited += event.amount + exp_first_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].first_redeem_tokens_owned + event.amount + exp_second_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].second_redeem_tokens_owned + else: + self.account_tracker[user_address.bech32()].second_token_deposited += event.amount + exp_first_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].first_redeem_tokens_owned + exp_second_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].second_redeem_tokens_owned + event.amount + + chain_tokens_on_account = get_all_token_nonces_details_for_account(self.pd_contract_identity.redeem_token, + user_address.bech32(), + self.proxy + ) + # check for redeem tokens conformity + first_redeem_token_found = False + second_redeem_token_found = False + chain_first_redeem_tokens = 0 + chain_second_redeem_tokens = 0 + for chain_token in chain_tokens_on_account: + if chain_token['nonce'] == self.pd_contract_identity.first_redeem_token_nonce: + chain_first_redeem_tokens = int(chain_token['balance']) + if chain_first_redeem_tokens != exp_first_redeem_tokens_owned: + print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + print_test_substep(f"Chain first redeem tokens: {chain_first_redeem_tokens}") + print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + first_redeem_token_found = True + if chain_token['nonce'] == self.pd_contract_identity.second_redeem_token_nonce: + chain_second_redeem_tokens = int(chain_token['balance']) + if chain_second_redeem_tokens != exp_second_redeem_tokens_owned: + print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + print_test_substep(f"Chain second redeem tokens: {chain_second_redeem_tokens}") + print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + second_redeem_token_found = True + + if not first_redeem_token_found and exp_first_redeem_tokens_owned > 0: + print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + print_test_substep(f"Chain first redeem tokens: 0") + print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + if not second_redeem_token_found and exp_second_redeem_tokens_owned > 0: + print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + print_test_substep(f"Chain second redeem tokens: 0") + print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + + + # update redeem tokens data on account + self.account_tracker[user_address.bech32()].first_redeem_tokens_owned = chain_first_redeem_tokens + self.account_tracker[user_address.bech32()].second_redeem_tokens_owned = chain_second_redeem_tokens + + print_test_step_pass("Tracked and checked deposit account data!") + + def deposit_event_tracking(self, event: DepositPDLiquidityEvent, user_address: Address, tx_hash: str): + # TODO: check state based on tx_hash success + self.__check_deposit_event(event) + self.__deposit_event_account_tracking(event, user_address) + + if event.deposit_token == self.pd_contract_identity.launched_token_id: + self.first_token_reserve += event.amount + self.first_redeem_tokens_reserve += event.amount + else: + self.second_token_reserve += event.amount + self.second_redeem_tokens_reserve += event.amount + + print_test_step_pass("Tracked deposit data!") + + def __check_withdraw_event(self, event: WithdrawPDLiquidityEvent): + chain_first_token_reserve = self.contract_data_fetcher.get_token_reserve(self.pd_contract_identity.launched_token_id) + chain_second_token_reserve = self.contract_data_fetcher.get_token_reserve(self.pd_contract_identity.accepted_token) + + if event.nonce == self.pd_contract_identity.first_redeem_token_nonce: + new_first_token_reserve = self.first_token_reserve - event.amount + new_second_token_reserve = self.second_token_reserve + else: + new_first_token_reserve = self.first_token_reserve + new_second_token_reserve = self.second_token_reserve - event.amount + + if chain_first_token_reserve != new_first_token_reserve: + print_test_step_fail("TEST CHECK FAIL: First token reserve not as expected!") + print_test_substep(f"Chain first token reserve: {chain_first_token_reserve}") + print_test_substep(f"Expected first token reserve: {new_first_token_reserve}") + + if chain_second_token_reserve != new_second_token_reserve: + print_test_step_fail("TEST CHECK FAIL: Second token reserve not as expected!") + print_test_substep(f"Chain second token reserve: {chain_second_token_reserve}") + print_test_substep(f"Expected second token reserve: {new_second_token_reserve}") + + print_test_step_pass("Checked withdraw event data!") + + def __withdraw_event_account_tracking(self, event: WithdrawPDLiquidityEvent, user_address: Address): + if user_address.bech32() not in self.account_tracker.keys(): + self.account_tracker[user_address.bech32()] = PriceDiscoveryAccountEconomics(user_address) + + # TODO: check first/second_token_deposited conformity starting from an account init + if event.nonce == self.pd_contract_identity.first_redeem_token_nonce: + self.account_tracker[user_address.bech32()].first_token_deposited -= event.amount + exp_first_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].first_redeem_tokens_owned - event.amount + exp_second_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].second_redeem_tokens_owned + else: + self.account_tracker[user_address.bech32()].second_token_deposited -= event.amount + exp_first_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].first_redeem_tokens_owned + exp_second_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].second_redeem_tokens_owned - event.amount + + chain_tokens_on_account = get_all_token_nonces_details_for_account(self.pd_contract_identity.redeem_token, + user_address.bech32(), + self.proxy + ) + # check for redeem tokens match + first_redeem_token_found = False + second_redeem_token_found = False + chain_first_redeem_tokens = 0 + chain_second_redeem_tokens = 0 + for chain_token in chain_tokens_on_account: + if chain_token['nonce'] == self.pd_contract_identity.first_redeem_token_nonce: + chain_first_redeem_tokens = int(chain_token['balance']) + if chain_first_redeem_tokens != exp_first_redeem_tokens_owned: + print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + print_test_substep(f"Chain first redeem tokens: {chain_first_redeem_tokens}") + print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + first_redeem_token_found = True + if chain_token['nonce'] == self.pd_contract_identity.second_redeem_token_nonce: + chain_second_redeem_tokens = int(chain_token['balance']) + if chain_second_redeem_tokens != exp_second_redeem_tokens_owned: + print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + print_test_substep(f"Chain second redeem tokens: {chain_second_redeem_tokens}") + print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + second_redeem_token_found = True + + if not first_redeem_token_found and exp_first_redeem_tokens_owned > 0: + print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + print_test_substep(f"Chain first redeem tokens: 0") + print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + if not second_redeem_token_found and exp_second_redeem_tokens_owned > 0: + print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + print_test_substep(f"Chain second redeem tokens: 0") + print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + + + # update redeem tokens data on account + self.account_tracker[user_address.bech32()].first_redeem_tokens_owned = chain_first_redeem_tokens + self.account_tracker[user_address.bech32()].second_redeem_tokens_owned = chain_second_redeem_tokens + + print_test_step_pass("Tracked and checked withdraw account data!") + + def withdraw_event_tracking(self, event: WithdrawPDLiquidityEvent, user_address: Address, tx_hash: str): + # TODO: check state based on tx_hash success + self.__check_withdraw_event(event) + self.__withdraw_event_account_tracking(event, user_address) + + if event.nonce == self.pd_contract_identity.first_redeem_token_nonce: + self.first_token_reserve -= event.amount + self.first_redeem_tokens_reserve -= event.amount + else: + self.second_token_reserve -= event.amount + self.second_redeem_tokens_reserve -= event.amount + + # TODO: method to check deposit of initial liquidity and amount of LPs received + + def __get_exp_lp_tokens_redeemed(self, redeem_tk_amount: int, final_token_reserve: int) -> int: + exp_lp_tokens_redeemed = redeem_tk_amount * self.total_lp_tokens_received // final_token_reserve // 2 + return exp_lp_tokens_redeemed + + def __check_redeem_event(self, event: RedeemPDLPTokensEvent): + chain_lp_tokens_reserve = self.contract_data_fetcher.get_token_reserve(self.pd_contract_identity.lp_token) + + if event.nonce == self.pd_contract_identity.first_redeem_token_nonce: + exp_lp_tokens_reserve = self.lp_tokens_reserve - \ + self.__get_exp_lp_tokens_redeemed(event.amount, self.final_first_token_reserve) + else: + exp_lp_tokens_reserve = self.lp_tokens_reserve - \ + self.__get_exp_lp_tokens_redeemed(event.amount, self.second_redeem_tokens_reserve) + + if chain_lp_tokens_reserve != exp_lp_tokens_reserve: + print_test_step_fail("TEST CHECK FAIL: LP tokens reserve not as expected!") + print_test_substep(f"Chain LP tokens reserve: {chain_lp_tokens_reserve}") + print_test_substep(f"Expected LP tokens reserve: {exp_lp_tokens_reserve}") + + # update LP tokens tracking data + self.lp_tokens_reserve = chain_lp_tokens_reserve + + print_test_step_pass("Checked redeem event data!") + + def __redeem_event_account_tracking(self, event: RedeemPDLPTokensEvent, user_address: Address): + if user_address.bech32() not in self.account_tracker.keys(): + self.account_tracker[user_address.bech32()] = PriceDiscoveryAccountEconomics(user_address) + + # TODO: check first/second_token_deposited conformity starting from an account init + if event.nonce == self.pd_contract_identity.first_redeem_token_nonce: + exp_first_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].first_redeem_tokens_owned - event.amount + exp_second_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].second_redeem_tokens_owned + exp_lp_tokens_owned = self.account_tracker[user_address.bech32()].lp_tokens_redeemed + \ + self.__get_exp_lp_tokens_redeemed(event.amount, self.final_first_token_reserve) + else: + exp_first_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].first_redeem_tokens_owned + exp_second_redeem_tokens_owned = self.account_tracker[ + user_address.bech32()].second_redeem_tokens_owned - event.amount + exp_lp_tokens_owned = self.account_tracker[user_address.bech32()].lp_tokens_redeemed + \ + self.__get_exp_lp_tokens_redeemed(event.amount, self.second_redeem_tokens_reserve) + + chain_redeem_tokens_on_account = get_all_token_nonces_details_for_account(self.pd_contract_identity.redeem_token, + user_address.bech32(), + self.proxy + ) + _, chain_lp_tokens_on_account, _ = get_token_details_for_address(self.pd_contract_identity.lp_token, + user_address.bech32(), + self.proxy) + + # check for redeem tokens proper decrease (kinda overkill) + first_redeem_token_found = False + second_redeem_token_found = False + chain_first_redeem_tokens = 0 + chain_second_redeem_tokens = 0 + for chain_token in chain_redeem_tokens_on_account: + if chain_token['nonce'] == self.pd_contract_identity.first_redeem_token_nonce: + chain_first_redeem_tokens = int(chain_token['balance']) + if chain_first_redeem_tokens != exp_first_redeem_tokens_owned: + print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + print_test_substep(f"Chain first redeem tokens: {chain_first_redeem_tokens}") + print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + first_redeem_token_found = True + if chain_token['nonce'] == self.pd_contract_identity.second_redeem_token_nonce: + chain_second_redeem_tokens = int(chain_token['balance']) + if chain_second_redeem_tokens != exp_second_redeem_tokens_owned: + print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + print_test_substep(f"Chain second redeem tokens: {chain_second_redeem_tokens}") + print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + second_redeem_token_found = True + + if not first_redeem_token_found and exp_first_redeem_tokens_owned > 0: + print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + print_test_substep(f"Chain first redeem tokens: 0") + print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + if not second_redeem_token_found and exp_second_redeem_tokens_owned > 0: + print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + print_test_substep(f"Chain second redeem tokens: 0") + print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + + # check for lp tokens retrieved - the real deal + if chain_lp_tokens_on_account != exp_lp_tokens_owned: + print_test_step_fail("TEST CHECK FAIL: LP tokens on account not as expected!") + print_test_substep(f"Chain LP tokens on account: {chain_lp_tokens_on_account}") + print_test_substep(f"Expected LP on account: {exp_lp_tokens_owned}") + + # update redeem tokens data on account + self.account_tracker[user_address.bech32()].first_redeem_tokens_owned = chain_first_redeem_tokens + self.account_tracker[user_address.bech32()].second_redeem_tokens_owned = chain_second_redeem_tokens + self.account_tracker[user_address.bech32()].lp_tokens_redeemed = chain_lp_tokens_on_account + + print_test_step_pass("Tracked and checked redeem account data!") + + def redeem_event_tracking(self, event: RedeemPDLPTokensEvent, user_address: Address, tx_hash: str): + # TODO: check state based on tx_hash success + self.__check_redeem_event(event) + self.__redeem_event_account_tracking(event, user_address) + + if event.nonce == self.pd_contract_identity.first_redeem_token_nonce: + self.first_redeem_tokens_reserve -= event.amount + else: + self.second_redeem_tokens_reserve -= event.amount diff --git a/trackers/simple_lock_energy_tracking.py b/trackers/simple_lock_energy_tracking.py new file mode 100644 index 0000000..b7a58f4 --- /dev/null +++ b/trackers/simple_lock_energy_tracking.py @@ -0,0 +1,67 @@ +from contracts.simple_lock_energy_contract import SimpleLockEnergyContract +from utils.contract_data_fetchers import SimpleLockEnergyContractDataFetcher +from utils.utils_tx import ESDTToken, NetworkProviders +from utils.utils_chain import get_token_details_for_address, decode_merged_attributes, base64_to_hex +from erdpy.accounts import Account, Address + + +class SimpleLockEnergyTokenAttributes: + token_identifier: str + original_token_nonce: int + unlock_epoch: int + + def __init__(self, decoded_dict: dict): + for key, value in decoded_dict.items(): + setattr(self, key, value) + + +class SimpleLockEnergyTracker: + token_decode_structure = { + 'token_identifier': 'string', + 'original_token_nonce': 'u64', + 'unlock_epoch': 'u64', + } + + def __init__(self, address: str, network_provider: NetworkProviders): + self.address = address + self.network_provider = network_provider + self.contract_data_fetcher = SimpleLockEnergyContractDataFetcher(Address(address), network_provider.proxy.url) + self.base_token = self.contract_data_fetcher.get_data("getBaseAssetTokenId") + self.locked_token = self.contract_data_fetcher.get_data("getLockedTokenId") + + # penalties_hex = self.contract_data_fetcher.get_data("getPenaltyPercentage") + # penalties_decoded = decode_merged_attributes(penalties_hex, {'min': 'u16', 'max': 'u16'}) + # self.min_penalty = penalties_decoded['min'] + # self.max_penalty = penalties_decoded['max'] + self.fees_burn_percentage = self.contract_data_fetcher.get_data("getFeesBurnPercentage") + + self.lock_options: list = self.contract_data_fetcher.get_data("getLockOptions") + self.lock_options.sort() + + def get_locked_token_attributes(self, token: ESDTToken) -> SimpleLockEnergyTokenAttributes: + # fetch token attributes + raw_attributes = self.network_provider.api.get_nft_data(token.get_full_token_name())['attributes'] + # decode token attributes + decoded_attributes = decode_merged_attributes(base64_to_hex(raw_attributes), self.token_decode_structure) + token_attributes = SimpleLockEnergyTokenAttributes(decoded_attributes) + return token_attributes + + def get_expected_penalty(self, token: ESDTToken, reduce_epochs: int = -1) -> int: + """ + Token: full token details on which the penalty should be applied + reduce_epochs: how many epochs; if -1 or default, will consider reduce on remaining period + """ + token_attributes = self.get_locked_token_attributes(token) + current_epoch = self.network_provider.proxy.get_epoch() + + remaining_lock_epochs = token_attributes.unlock_epoch - current_epoch + max_unlock_epochs = max(remaining_lock_epochs, 0) + + if reduce_epochs < 0: + unlock_epochs = max_unlock_epochs + else: + unlock_epochs = min(reduce_epochs, max_unlock_epochs) + + penalty = self.min_penalty + (self.max_penalty - self.min_penalty) * unlock_epochs // self.lock_options[-1] + + return penalty diff --git a/trackers/staking_economics_tracking.py b/trackers/staking_economics_tracking.py new file mode 100644 index 0000000..00a9552 --- /dev/null +++ b/trackers/staking_economics_tracking.py @@ -0,0 +1,227 @@ +from erdpy.accounts import Address +from utils.utils_tx import NetworkProviders +from trackers.abstract_observer import Subscriber +from trackers.concrete_observer import Observable +from events.farm_events import EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent +from utils.contract_data_fetchers import StakingContractDataFetcher, ChainDataFetcher +from utils.utils_chain import print_test_step_pass, print_test_step_fail, print_test_substep + + +class StakingEconomics(Subscriber): + def __init__(self, address: str, network_provider: NetworkProviders): + self.contract_address = Address(address) + self.network_provider = network_provider + self.data_fetcher = StakingContractDataFetcher(self.contract_address, self.network_provider.proxy.url) + self.chain_data_fetcher = ChainDataFetcher(self.network_provider.proxy.url) + + self.min_unbond_epochs = None + self.division_safety_constant = None + self.rewards_per_share = None + self.rewards_capacity = None + self.annual_percentage_rewards = None + self.rewards_per_block = None + self.last_rewards_block_nonce = None + self.token_supply = None + + self.update_data() + self.last_block_calculated_rewards = self.last_rewards_block_nonce + + self.report_current_tracking_data() + + def update_data(self): + self.token_supply = self.data_fetcher.get_data('getFarmTokenSupply') + self.last_rewards_block_nonce = self.data_fetcher.get_data('getLastRewardBlockNonce') + self.rewards_per_block = self.data_fetcher.get_data('getPerBlockRewardAmount') + self.annual_percentage_rewards = self.data_fetcher.get_data('getAnnualPercentageRewards') + self.rewards_capacity = self.data_fetcher.get_data('getRewardCapacity') + self.rewards_per_share = self.data_fetcher.get_data('getRewardPerShare') + self.min_unbond_epochs = self.data_fetcher.get_data('getMinUnbondEpochs') + self.division_safety_constant = self.data_fetcher.get_data('getDivisionSafetyConstant') + + def report_current_tracking_data(self): + print(f"Staking contract address: {self.contract_address.bech32()}") + print(f"Staking farm token supply: {self.token_supply}") + print(f"Rewards per block: {self.rewards_per_block}") + print(f"Last rewards block nonce: {self.last_rewards_block_nonce}") + print(f"Annual percentage rewards: {self.annual_percentage_rewards}") + print(f"Rewards capacity: {self.rewards_capacity}") + print(f"Rewards per share: {self.rewards_per_share}") + + def check_invariant_properties(self): + new_rewards_per_share = self.data_fetcher.get_data("getRewardPerShare") + new_last_rewards_block_nonce = self.data_fetcher.get_data("getLastRewardBlockNonce") + chain_rewards_per_block = self.data_fetcher.get_data("getPerBlockRewardAmount") + chain_division_safety_constant = self.data_fetcher.get_data("getDivisionSafetyConstant") + + if self.rewards_per_share > new_rewards_per_share: + print_test_step_fail("TEST CHECK FAIL: Rewards per share decreased!") + print_test_substep(f"Old rewards per share: {self.rewards_per_share}") + print_test_substep(f"New rewards per share: {new_rewards_per_share}") + if self.last_rewards_block_nonce > new_last_rewards_block_nonce: + print_test_step_fail("TEST CHECK FAIL: Last rewards block nonce decreased!") + print_test_substep(f"Old rewards block nonce: {self.last_rewards_block_nonce}") + print_test_substep(f"New rewards block nonce: {new_last_rewards_block_nonce}") + if self.rewards_per_block != chain_rewards_per_block: + print_test_step_fail("TEST CHECK FAIL: Rewards per block has changed!") + print_test_substep(f"Old rewards per block: {self.rewards_per_block}") + print_test_substep(f"New rewards per block: {chain_rewards_per_block}") + if self.division_safety_constant != chain_division_safety_constant: + print_test_step_fail("TEST CHECK FAIL: Division safety constant has changed!") + print_test_substep(f"Old division safety constant: {self.division_safety_constant}") + print_test_substep(f"New division safety constant: {chain_division_safety_constant}") + + print_test_step_pass("Checked invariant properties!") + + def check_enter_staking_properties(self): + new_token_supply = self.data_fetcher.get_data("getFarmTokenSupply") + if self.token_supply >= new_token_supply: + print_test_step_fail('Staking farm token supply did not increase') + print_test_substep(f"Old token supply: {self.token_supply}") + print_test_substep(f"New token supply: {new_token_supply}") + + print_test_step_pass('Checked enter staking properties!') + + def check_enter_staking_data(self, event: EnterFarmEvent, tx_hash: str): + new_staking_token_supply = self.data_fetcher.get_data("getFarmTokenSupply") + new_contract_rewards_per_share = self.data_fetcher.get_data("getRewardPerShare") + new_last_rewards_block_nonce = self.data_fetcher.get_data("getLastRewardBlockNonce") + tx_block = self.chain_data_fetcher.get_tx_block_nonce(tx_hash) + + aggregated_rewards = (tx_block - self.last_block_calculated_rewards) * self.rewards_per_block + expected_token_supply = self.token_supply + event.farming_tk_amount + + if self.token_supply: + new_exp_rewards_per_share = self.rewards_per_share + \ + (self.division_safety_constant * aggregated_rewards // self.token_supply) + else: + new_exp_rewards_per_share = 0 + + if new_staking_token_supply != expected_token_supply: + print_test_step_fail('TEST CHECK FAIL: Staking token supply not as expected!') + print_test_substep(f"Old Staking token supply: {self.token_supply}") + print_test_substep(f"New Staking token supply: {new_staking_token_supply}") + print_test_substep(f"Expected Staking token supply: {expected_token_supply}") + + if new_contract_rewards_per_share != new_exp_rewards_per_share: + print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + print_test_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") + print_test_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") + + if tx_block != new_last_rewards_block_nonce: + print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + print_test_substep(f"Expected last reward block nonce: {tx_block}") + + self.last_block_calculated_rewards = tx_block + print_test_step_pass('Checked enter staking data!') + + def check_exit_staking_properties(self): + new_token_supply = self.data_fetcher.get_data('getFarmTokenSupply') + if self.token_supply <= new_token_supply: + print_test_step_fail('Staking farm token supply did not decrease') + print_test_substep(f"Old token supply: {self.token_supply}") + print_test_substep(f"New token supply: {new_token_supply}") + + print_test_step_pass('Checked exit staking properties') + + def check_exit_staking_data(self, event: ExitFarmEvent, tx_hash: str): + new_token_supply = self.data_fetcher.get_data('getFarmTokenSupply') + new_contract_rewards_per_share = self.data_fetcher.get_data("getRewardPerShare") + new_last_rewards_block_nonce = self.data_fetcher.get_data("getLastRewardBlockNonce") + tx_block = self.chain_data_fetcher.get_tx_block_nonce(tx_hash) + expected_token_supply = self.token_supply - event.amount + + aggregated_rewards = (tx_block - self.last_block_calculated_rewards) * self.rewards_per_block + + expected_rewards_per_share = self.rewards_per_share +\ + (self.division_safety_constant * aggregated_rewards // self.token_supply) + + if new_token_supply != expected_token_supply: + print_test_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") + print_test_substep(f"Farm token supply in contract: {new_token_supply}") + print_test_substep(f"Expected Farm token supply: {expected_token_supply}") + + if new_contract_rewards_per_share != expected_rewards_per_share: + print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + print_test_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") + print_test_substep(f"Expected Rewards per share: {expected_rewards_per_share}") + + if tx_block != new_last_rewards_block_nonce: + print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + print_test_substep(f"Expected last reward block nonce: {tx_block}") + + self.last_block_calculated_rewards = tx_block + print_test_step_pass('Checked exit staking data') + + def check_claim_rewards_properties(self): + new_token_supply = self.data_fetcher.get_data('getFarmTokenSupply') + if self.token_supply != new_token_supply: + print_test_step_fail('Token supply modified!') + print_test_substep(f"Old Farm token supply: {self.token_supply}") + print_test_substep(f"New Farm token supply: {new_token_supply}") + + print_test_step_pass("Checked claim rewards properties!") + + def check_claim_rewards_data(self, tx_hash): + new_token_supply = self.data_fetcher.get_data('getFarmTokenSupply') + new_rewards_per_share = self.data_fetcher.get_data("getRewardPerShare") + new_last_rewards_block_nonce = self.data_fetcher.get_data("getLastRewardBlockNonce") + tx_block = self.chain_data_fetcher.get_tx_block_nonce(tx_hash) + aggregated_rewards = (tx_block - self.last_block_calculated_rewards) * self.rewards_per_block + + new_exp_rewards_per_share = self.rewards_per_share + \ + (self.division_safety_constant * aggregated_rewards // self.token_supply) + + if new_token_supply != self.token_supply: + print_test_step_fail(f"TEST CHECK FAIL: Token supply not as expected!") + print_test_substep(f"Farm token supply in contract: {new_token_supply}") + print_test_substep(f"Expected Farm token supply: {self.token_supply}") + + if new_rewards_per_share != new_exp_rewards_per_share: + print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + print_test_substep(f"New Rewards per share in contract: {new_rewards_per_share}") + print_test_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") + + if tx_block != new_last_rewards_block_nonce: + print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + print_test_substep(f"Expected last reward block nonce: {tx_block}") + + self.last_block_calculated_rewards = tx_block + print_test_step_pass("Checked claim staking rewards data!") + + def enter_staking_event(self, event: EnterFarmEvent, tx_hash): + self.check_invariant_properties() + self.check_enter_staking_properties() + self.check_enter_staking_data(event, tx_hash) + self.update_data() + self.report_current_tracking_data() + + def exit_staking_event(self, event: ExitFarmEvent, tx_hash): + self.check_invariant_properties() + self.check_exit_staking_properties() + self.check_exit_staking_data(event, tx_hash) + self.update_data() + self.report_current_tracking_data() + + def claim_rewards_staking_event(self, tx_hash): + self.check_invariant_properties() + self.check_claim_rewards_properties() + self.check_claim_rewards_data(tx_hash) + self.update_data() + self.report_current_tracking_data() + + def update(self, publisher: Observable): + if publisher.contract is not None: + if self.contract_address.bech32() == publisher.contract.address: + self.network_provider.api.wait_for_tx_finalized(publisher.tx_hash) + if type(publisher.event) == EnterFarmEvent: + self.enter_staking_event(publisher.event, publisher.tx_hash) + elif type(publisher.event) == ExitFarmEvent: + self.exit_staking_event(publisher.event, publisher.tx_hash) + elif type(publisher.event) == ClaimRewardsFarmEvent: + self.claim_rewards_staking_event(publisher.tx_hash) diff --git a/utils/contract_data_fetchers.py b/utils/contract_data_fetchers.py new file mode 100644 index 0000000..ec1039e --- /dev/null +++ b/utils/contract_data_fetchers.py @@ -0,0 +1,258 @@ +import sys +import traceback + +from multiversx_sdk_core import Address, ContractQueryBuilder +from multiversx_sdk_network_providers import ProxyNetworkProvider + +from utils.utils_chain import base64_to_hex + + +class DataFetcher: + def __init__(self, contract_address: Address, proxy_url: str): + self.proxy = ProxyNetworkProvider(proxy_url) + self.contract_address = contract_address + self.view_handler_map = {} + + def get_data(self, view_name: str, attrs: list = []): + if view_name in self.view_handler_map: + return self.view_handler_map[view_name](view_name, attrs) + else: + raise ValueError(f"View name not registered in {type(self).__name__}") + + def _query_contract(self, view_name: str, attrs: list = []): + builder = ContractQueryBuilder( + contract=self.contract_address, + function=view_name, + call_arguments=attrs + ) + query = builder.build() + return self.proxy.query_contract(query) + + def _get_int_view(self, view_name: str, attrs) -> int: + try: + result = self._query_contract(view_name, attrs) + if result.return_data == '': + return 0 + return int(base64_to_hex(result.return_data), base=16) + except Exception as ex: + print(f"Exception encountered on view name {view_name}: {ex}") + traceback.print_exception(*sys.exc_info()) + return -1 + + def _get_int_list_view(self, view_name: str, attrs) -> list: + try: + result = self._query_contract(view_name, attrs) + return [int(base64_to_hex(elem), base=16) for elem in result.return_data] + except Exception as ex: + print(f"Exception encountered on view name {view_name}: {ex}") + traceback.print_exception(*sys.exc_info()) + return [] + + def _get_hex_view(self, view_name: str, attrs) -> str: + try: + result = self._query_contract(view_name, attrs) + return base64_to_hex(result.return_data) + except Exception as ex: + print(f"Exception encountered on view name {view_name}: {ex}") + traceback.print_exception(*sys.exc_info()) + return "" + + def _get_hex_list_view(self, view_name: str, attrs) -> list: + try: + result = self._query_contract(view_name, attrs) + return [base64_to_hex(elem) for elem in result.return_data] + except Exception as ex: + print(f"Exception encountered on view name {view_name}: {ex}") + traceback.print_exception(*sys.exc_info()) + return [] + + +class LockedAssetContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "getLockedAssetTokenId": self._get_hex_view, + "getAssetTokenId": self._get_hex_view, + } + + +class ProxyContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "getWrappedLpTokenId": self._get_hex_view, + "getWrappedFarmTokenId": self._get_hex_view, + "getAssetTokenId": self._get_hex_view, + "getLockedTokenIds": self._get_hex_list_view, + } + + +class SimpleLockContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "getLockedTokenId": self._get_hex_view, + "getLpProxyTokenId": self._get_hex_view, + "getFarmProxyTokenId": self._get_hex_view, + "getLockOptions": self._get_hex_list_view, + "getPenaltyPercentage": self._get_int_list_view, + "getFeesBurnPercentage": self._get_int_view, + "getEnergyAmountForUser": self._get_int_view, + "getEnergyEntryForUser": self._get_int_view, + "getFeesCollectorAddress": self._get_hex_view, + } + + +class SimpleLockEnergyContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "getLockedTokenId": self._get_hex_view, + "getLpProxyTokenId": self._get_hex_view, + "getFarmProxyTokenId": self._get_hex_view, + "getBaseAssetTokenId": self._get_hex_view, + "getEnergyAmountForUser": self._get_int_view, + "getEnergyEntryForUser": self._get_int_view, + "getFeesBurnPercentage": self._get_int_view, + "getPenaltyPercentage": self._get_hex_view, + "getLockOptions": self._get_int_list_view, + } + + +class RouterContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "getAllPairsManagedAddresses": self._get_hex_list_view, + "getPairTemplateAddress": self._get_hex_view + } + + +class PriceDiscoveryContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "totalLpTokensReceived": self._get_int_view, + "getAcceptedTokenFinalAmount": self._get_int_view, + "getLaunchedTokenFinalAmount": self._get_int_view, + "getStartEpoch": self._get_int_view, + "getEndEpoch": self._get_int_view, + "getRedeemTokenId": self._get_hex_view, + } + + def get_token_reserve(self, token_ticker: str) -> int: + data = self.proxy.get_fungible_token_of_account(self.contract_address, token_ticker) + return data.balance + + +class FarmContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "getFarmTokenSupply": self._get_int_view, + "getFarmingTokenReserve": self._get_int_view, + "getLastRewardBlockNonce": self._get_int_view, + "getPerBlockRewardAmount": self._get_int_view, + "getRewardPerShare": self._get_int_view, + "getRewardReserve": self._get_int_view, + "getUndistributedFees": self._get_int_view, + "getCurrentBlockFee": self._get_int_view, + "getDivisionSafetyConstant": self._get_int_view, + "getFarmTokenId": self._get_hex_view, + "getFarmingTokenId": self._get_hex_view, + "getRewardTokenId": self._get_hex_view, + "getState": self._get_int_view, + } + + +class PairContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "getAmountOut": self._get_int_view, + "getEquivalent": self._get_int_view, + "getTotalFeePercent": self._get_int_view, + "getSpecialFee": self._get_int_view, + "updateAndGetSafePrice": self._get_hex_view, + "getLpTokenIdentifier": self._get_hex_view, + "getFirstTokenId": self._get_hex_view, + "getSecondTokenId": self._get_hex_view, + "getTokensForGivenPosition": self._get_int_list_view, + "getState": self._get_int_view, + "getReservesAndTotalSupply": self._get_int_list_view, + "updateAndGetTokensForGivenPositionWithSafePrice": self._get_hex_list_view + } + + def get_token_reserve(self, token_ticker: str) -> int: + data = self.proxy.get_fungible_token_of_account(self.contract_address, token_ticker) + return data.balance + + +class FeeCollectorContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "getAmountOut": self._get_int_view, + "getEquivalent": self._get_int_view, + "updateAndGetSafePrice": self._get_hex_view, + "getLpTokenIdentifier": self._get_hex_view, + "getTokensForGivenPosition": self._get_int_list_view, + "getReservesAndTotalSupply": self._get_int_list_view, + } + + def get_token_reserve(self, token_ticker: str) -> int: + data = self.proxy.get_fungible_token_of_account(self.contract_address, token_ticker) + return data.balance + + +class StakingContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "getFarmTokenSupply": self._get_int_view, + "getLastRewardBlockNonce": self._get_int_view, + "getPerBlockRewardAmount": self._get_int_view, + "getAnnualPercentageRewards": self._get_int_view, + "getRewardCapacity": self._get_int_view, + "getRewardPerShare": self._get_int_view, + "getMinUnbondEpochs": self._get_int_view, + "getDivisionSafetyConstant": self._get_int_view, + "getFarmTokenId": self._get_hex_view, + "getFarmingTokenId": self._get_hex_view, + "getState": self._get_int_view, + } + + +class MetaStakingContractDataFetcher(DataFetcher): + def __init__(self, contract_address: Address, proxy_url: str): + super().__init__(contract_address, proxy_url) + self.view_handler_map = { + "getDualYieldTokenId": self._get_hex_view, + } + + +class ChainDataFetcher: + def __init__(self, proxy_url: str): + self.proxy = ProxyNetworkProvider(proxy_url) + + def get_tx_block_nonce(self, txhash: str) -> int: + if txhash == "": + print("No hash provided") + return 0 + try: + response = self.proxy.get_transaction(txhash) + return response.block_nonce + + except Exception as ex: + print("Exception encountered:", ex) + traceback.print_exception(*sys.exc_info()) + return 0 + + def get_current_block_nonce(self) -> int: + try: + response = self.proxy.get_network_status(1) + return response.highest_final_nonce + except Exception as ex: + print("Exception encountered:", ex) + traceback.print_exception(*sys.exc_info()) + return 0 diff --git a/utils/contract_retrievers.py b/utils/contract_retrievers.py new file mode 100644 index 0000000..760abe2 --- /dev/null +++ b/utils/contract_retrievers.py @@ -0,0 +1,121 @@ +from typing import Optional + +from contracts.contract_identities import PairContractVersion, RouterContractVersion, \ + FarmContractVersion, StakingContractVersion, ProxyContractVersion +from contracts.dex_proxy_contract import DexProxyContract +from contracts.farm_contract import FarmContract +from contracts.fees_collector_contract import FeesCollectorContract +from contracts.metastaking_contract import MetaStakingContract +from contracts.pair_contract import PairContract +import config +from contracts.router_contract import RouterContract +from contracts.simple_lock_energy_contract import SimpleLockEnergyContract +from contracts.staking_contract import StakingContract +from contracts.unstaker_contract import UnstakerContract +from utils.contract_data_fetchers import PairContractDataFetcher, RouterContractDataFetcher, \ + FarmContractDataFetcher, SimpleLockEnergyContractDataFetcher, StakingContractDataFetcher, \ + MetaStakingContractDataFetcher, ProxyContractDataFetcher +from utils.utils_chain import hex_to_string, WrapperAddress as Address + + +def retrieve_pair_by_address(address: str) -> Optional[PairContract]: + data_fetcher = PairContractDataFetcher(Address(address), config.DEFAULT_PROXY) + first_token = hex_to_string(data_fetcher.get_data("getFirstTokenId")) + second_token = hex_to_string(data_fetcher.get_data("getSecondTokenId")) + lp_token = hex_to_string(data_fetcher.get_data("getLpTokenIdentifier")) + version = PairContractVersion.V1 # TODO: find a way to determine this automatically + + if not first_token or not second_token: + return None + + contract = PairContract(first_token, second_token, version, lp_token, address) + return contract + + +def retrieve_farm_by_address(address: str) -> Optional[FarmContract]: + data_fetcher = FarmContractDataFetcher(Address(address), config.DEFAULT_PROXY) + farming_token = hex_to_string(data_fetcher.get_data("getFarmingTokenId")) + farm_token = hex_to_string(data_fetcher.get_data("getFarmTokenId")) + farmed_token = hex_to_string(data_fetcher.get_data("getRewardTokenId")) + version = FarmContractVersion.V2Boosted # TODO: find a way to determine this automatically + + if not farming_token or not farmed_token: + return None + + contract = FarmContract(farming_token, farm_token, farmed_token, address, version) + return contract + + +def retrieve_router_by_address(address: str) -> Optional[RouterContract]: + version = RouterContractVersion.V1 # TODO: find a way to determine this automatically + + contract = RouterContract(version, address) + return contract + + +def retrieve_simple_lock_energy_by_address(address: str) -> Optional[SimpleLockEnergyContract]: + data_fetcher = SimpleLockEnergyContractDataFetcher(Address(address), config.DEFAULT_PROXY) + base_token = hex_to_string(data_fetcher.get_data("getBaseAssetTokenId")) + locked_token = hex_to_string(data_fetcher.get_data("getLockedTokenId")) + + contract = SimpleLockEnergyContract(base_token=base_token, locked_token=locked_token, address=address) + return contract + + +def retrieve_unstaker_by_address(address: str) -> Optional[UnstakerContract]: + contract = UnstakerContract(address) + return contract + + +def retrieve_fees_collector_by_address(address: str) -> Optional[FeesCollectorContract]: + contract = FeesCollectorContract(address) + return contract + + +def retrieve_staking_by_address(address: str) -> Optional[StakingContract]: + data_fetcher = StakingContractDataFetcher(Address(address), config.DEFAULT_PROXY) + farming_token = hex_to_string(data_fetcher.get_data("getFarmingTokenId")) + farm_token = hex_to_string(data_fetcher.get_data("getFarmTokenId")) + division_constant = data_fetcher.get_data("getDivisionSafetyConstant") + max_apr = data_fetcher.get_data("getAnnualPercentageRewards") + unbond_epochs = data_fetcher.get_data("getMinUnbondEpochs") + rewards_per_block = data_fetcher.get_data("getPerBlockRewardAmount") + + contract = StakingContract(farming_token, max_apr, rewards_per_block, unbond_epochs, StakingContractVersion.V1, + farm_token, address) + return contract + + +def retrieve_proxy_by_address(address: str) -> Optional[DexProxyContract]: + data_fetcher = ProxyContractDataFetcher(Address(address), config.DEFAULT_PROXY) + locked_tokens = [hex_to_string(res) for res in data_fetcher.get_data("getLockedTokenIds")] + token = hex_to_string(data_fetcher.get_data("getAssetTokenId")) + proxy_lp_token = data_fetcher.get_data("getWrappedLpTokenId") + proxy_farm_token = data_fetcher.get_data("getWrappedFarmTokenId") + version = ProxyContractVersion.V2 + + contract = DexProxyContract(locked_tokens, token, version, address, proxy_lp_token, proxy_farm_token) + return contract + + +def retrieve_contract_by_address(address: str, contract_type: type): + if contract_type == PairContract: + return retrieve_pair_by_address(address) + + if contract_type == RouterContract: + return retrieve_router_by_address(address) + + if contract_type == FarmContract: + return retrieve_farm_by_address(address) + + if contract_type == SimpleLockEnergyContract: + return retrieve_simple_lock_energy_by_address(address) + + if contract_type == UnstakerContract: + return retrieve_unstaker_by_address(address) + + if contract_type == FeesCollectorContract: + return retrieve_fees_collector_by_address(address) + + if contract_type == DexProxyContract: + return retrieve_proxy_by_address(address) diff --git a/utils/decoding_structures.py b/utils/decoding_structures.py new file mode 100644 index 0000000..e59a9d3 --- /dev/null +++ b/utils/decoding_structures.py @@ -0,0 +1,6 @@ + + +LOCK_OPTIONS = { + 'lock_epochs': 'u64', + 'penalty_start_percentage': 'u64' +} \ No newline at end of file diff --git a/utils/results_logger.py b/utils/results_logger.py new file mode 100644 index 0000000..d0a544b --- /dev/null +++ b/utils/results_logger.py @@ -0,0 +1,122 @@ +from pathlib import Path +from typing import Any + +from multiversx_sdk_network_providers import ProxyNetworkProvider +from contracts.farm_contract import FarmContract +from utils.contract_data_fetchers import FarmContractDataFetcher +from utils.utils_chain import WrapperAddress as Address, get_all_token_nonces_details_for_account +from utils.utils_generic import ensure_folder, dump_out_json + + +class AccountSnapshotLogData: + + def __init__(self, user_address: str, token_list: list, proxy: ProxyNetworkProvider): + self.tokens = [] + + for token in token_list: + self.tokens.append(get_all_token_nonces_details_for_account(token, user_address, proxy)) + + +class FarmContractSnapshotLogData: + farm_token_supply: int + per_block_rewards: int + last_reward_block_nonce: int + rewards_per_share: int + rewards_reserve: int + division_safety_constant: int + undistributed_fees: int + current_block_fee: int + + def __init__(self, contract_address: str, proxy: ProxyNetworkProvider): + data_fetcher = FarmContractDataFetcher(Address(contract_address), proxy_url=proxy.url) + self.farm_token_supply = data_fetcher.get_data("getFarmTokenSupply") + self.per_block_rewards = data_fetcher.get_data("getPerBlockRewardAmount") + self.last_reward_block_nonce = data_fetcher.get_data("getLastRewardBlockNonce") + self.rewards_per_share = data_fetcher.get_data("getRewardPerShare") + self.rewards_reserve = data_fetcher.get_data("getRewardReserve") + self.division_safety_constant = data_fetcher.get_data("getDivisionSafetyConstant") + # self.undistributed_fees = data_fetcher.get_data("getUndistributedFees") + # self.current_block_fee = data_fetcher.get_data("getCurrentBlockFee") + + +class FarmEventResultLogData: + # TODO (longterm): individual members can be replaced with a freely expandable list for scalability and reusability + event_name: str + tx_hash: str + event: dict + account: str + farm: dict + account_pre_snapshot: dict + account_post_snapshot: dict + contract_post_snapshot: dict + # internal usage data + _farm: FarmContract + _token_list: list + + # TODO (longterm): all following methods can be abstracted and ported towards "subscriptable" event model + def set_generic_event_data(self, event: Any, account_address: str, farm_identity: FarmContract): + self.event_name = type(event).__name__ + self.event = event.__dict__ + self.account = account_address + self.farm = farm_identity.__dict__ + self._farm = farm_identity + # TODO: add the reward token in here + self._token_list = [farm_identity.farmingToken, farm_identity.farmToken] + + def set_pre_event_data(self, proxy: ProxyNetworkProvider): + self.account_pre_snapshot = AccountSnapshotLogData(self.account, self._token_list, proxy).__dict__ + + def set_post_event_data(self, tx_hash: str, proxy: ProxyNetworkProvider): + self.tx_hash = tx_hash + self.account_post_snapshot = AccountSnapshotLogData(self.account, self._token_list, proxy).__dict__ + self.contract_post_snapshot = FarmContractSnapshotLogData(self._farm.address, proxy).__dict__ + + def clear_internal_data(self): + self.__delattr__("_farm") + self.__delattr__("_token_list") + + +class ResultsLogger: + def __init__(self, filename: str): + self.data_dump = [] + self.filename = filename + # added the flag below to stop logger errors + self.active = False + + + def add_event_log(self, log_event: FarmEventResultLogData): + # TODO: replace log_event type hint with EventResultLogData abstract + if self.active: + log_event.clear_internal_data() + self.data_dump.append(log_event.__dict__) + self.__save_backup_log(log_event.__dict__) + + def save_log(self): + if self.active: + print(f"Saving results log in file: {self.filename}") + + # out_filename = filename + "_" + str(run_time.day) + str(run_time.hour) + str(run_time.minute) + str(run_time.second) + out_filename = self.filename + filepath = "arrows/stress/dex/results/" + out_filename + + ensure_folder(Path(filepath).parent) + with open(filepath, "a") as f: + dump_out_json(self.data_dump, f) + + def __save_backup_log(self, log_event: dict): + if self.active: + filepath = f"arrows/stress/dex/results/{self.filename}_backup.json" + ensure_folder(Path(filepath).parent) + with open(filepath, "a") as f: + dump_out_json(log_event, f) + + +"""Procedure to use results logger" +- instantiate ResultsLogger obj at program start +- create a FarmEventResultLogData obj in event that needs to be logged +- FarmEventResultLogData.set_generic_event_data at init +- FarmEventResultLogData.set_pre_event_data before event execution +- FarmEventResultLogData.set_post_event_data after event execution +- ResultsLogger.add_event_log(FarmEventResultLogData) after event execution +- ResultsLogger.save_log before program end (or once in a while) +""" \ No newline at end of file diff --git a/utils/utils_chain.py b/utils/utils_chain.py new file mode 100644 index 0000000..42c8932 --- /dev/null +++ b/utils/utils_chain.py @@ -0,0 +1,547 @@ +import base64 +import time +import logging +from multiprocessing import Pool +from os import path +from pathlib import Path +from typing import List, Dict, Any, Optional, Set, cast + +from enum import Enum + +from multiversx_sdk_network_providers.tokens import FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork + +from contracts.contract_identities import FarmContractVersion +from multiversx_sdk_core import Address, Transaction +from multiversx_sdk_core.interfaces import ISignature +from multiversx_sdk_wallet import UserSigner, pem_format +from multiversx_sdk_network_providers import ProxyNetworkProvider + +from utils import utils_generic + +logger = logging.getLogger("accounts") + + +class WrapperAddress(Address): + def __init__(self, address: str): + self_instance = Address.from_bech32(address) + super().__init__(self_instance.pubkey, "erd") + + def __str__(self): + return self.bech32() + + def __repr__(self): + return self.bech32() + + +class Account: + def __init__(self, + address: str = None, + pem_file: Optional[str] = None, + pem_index: int = 0, + key_file: str = "", + password: str = "", + ledger: bool = False): + self.address = Address.from_bech32(address) if address else None + self.pem_file = pem_file + self.pem_index = int(pem_index) + self.nonce: int = 0 + self.ledger = ledger + + if self.pem_file: + self.signer = UserSigner.from_pem_file(Path(self.pem_file), self.pem_index) + self.address = Address.from_hex(self.signer.get_pubkey().hex(), "erd") + elif key_file and password: + self.signer = UserSigner.from_wallet(Path(key_file), password) + self.address = Address.from_hex(self.signer.get_pubkey().hex(), "erd") + + def sync_nonce(self, proxy: ProxyNetworkProvider): + logger.info("Account.sync_nonce()") + self.nonce = proxy.get_account(self.address).nonce + logger.info(f"Account.sync_nonce() done: {self.nonce}") + + def sign_transaction(self, transaction: Transaction) -> ISignature: + return self.signer.sign(transaction) + + +class BunchOfAccounts: + def __init__(self, items: List[Account]) -> None: + self.accounts = items + + @classmethod + def load_accounts_from_files(cls, files: List[Path]): + loaded: List[Account] = [] + + for file in files: + # Assume multi-account PEM files. + pem_entries = len(pem_format.parse_all(file)) + for index in range(pem_entries): + account = Account(pem_file=str(file), pem_index=index) + loaded.append(account) + + # Perform some deduplication (workaround) + addresses: Set[str] = set() + deduplicated: List[Account] = [] + for account in loaded: + address = account.address.bech32() + if address not in addresses: + addresses.add(address) + deduplicated.append(account) + + print(f"loaded {len(deduplicated)} accounts from {len(files)} PEM files.") + return BunchOfAccounts(deduplicated) + + def get_account(self, address: Address) -> Account: + return next(account for account in self.accounts if account.address.bech32() == address.bech32()) + + def get_all(self) -> List[Account]: + return self.accounts + + def __len__(self): + return len(self.accounts) + + def get_not_in_shard(self, shard: int): + return [account for account in self.accounts if account.address.get_shard() != shard] + + def get_in_shard(self, shard: int) -> List[Account]: + return [account for account in self.accounts if account.address.get_shard() == shard] + + def sync_nonces(self, proxy: ProxyNetworkProvider): + print("Sync nonces for", len(self.accounts), "accounts") + + def sync_nonce(account: Account): + account.sync_nonce(proxy) + + Pool(100).map(sync_nonce, self.accounts) + + print("Done") + + def store_nonces(self, file: str): + # We load the previously stored data in order to display a nice delta (for debugging purposes) + data: Any = utils_generic.read_json_file(file) or dict() if path.exists(file) else dict() + + for account in self.accounts: + address = account.address.bech32() + previous_nonce = data.get(address, 0) + current_nonce = account.nonce + data[address] = current_nonce + + if previous_nonce != current_nonce: + print("Nonce delta", current_nonce - previous_nonce, "for", address) + + utils_generic.write_json_file(file, data) + + def load_nonces(self, file: Path): + if not path.exists(file): + print("no nonces to load") + return + + data = utils_generic.read_json_file(file) or dict() + + for account in self.accounts: + address = account.address.bech32() + account.nonce = data.get(address, 0) + + print("Loaded nonces for", len(self.accounts), "accounts") + + +def prevent_spam_crash_elrond_proxy_go(): + time.sleep(1) + + +def hex_to_base64(s): + return base64.b64encode(s.encode('hex')) + + +def base64_to_hex(b): + return base64.b64decode(b).hex() + + +def string_to_base64(s): + return base64.b64encode(s.encode('utf-8')) + + +def base64_to_string(b): + return base64.b64decode(b).decode('utf-8') + + +def denominated_amount(amount): + return amount / 1000000000000000000 + + +def nominated_amount(amount): + return amount * 1000000000000000000 + + +def dec_to_padded_hex(i): + return "0" + f"{i:x}" if len(f"{i:x}") % 2 else f"{i:x}" + + +def string_to_hex(s): + return s.encode("ascii").hex() + + +def hex_to_string(s): + return bytearray.fromhex(s).decode("utf-8") + + +def _get_all_esdts_for_account(address: str, proxy: ProxyNetworkProvider): + # TODO: this is only to support old code that needs to be refactored to mxpy + url = f'address/{address}/esdt' + response = proxy.do_get_generic(url) + prevent_spam_crash_elrond_proxy_go() + + esdts = response.get('esdts') + return esdts + + +def _get_fungibles_from_esdts(items: Dict): + esdts = [items[key] for key in items.keys() if items[key].get('nonce', '') == ''] + tokens = map(FungibleTokenOfAccountOnNetwork.from_http_response, esdts) + return tokens + + +def _get_non_fungibles_from_esdts(items: Dict): + nfts = [items[key] for key in items.keys() if items[key].get('nonce', -1) > 0] + tokens = map(NonFungibleTokenOfAccountOnNetwork.from_http_response, nfts) + return tokens + + +def get_all_token_nonces_details_for_account(in_token: str, address: str, proxy: ProxyNetworkProvider): + """ + Result list will contain all in_tokens with following indexes: + ['nonce'] + ['balance'] + ['tokenIdentifier'] + ['attributes'] - if existent + ['creator'] + """ + # TODO: to refactor into supporting the new mxpy sdk + esdts = cast(List, _get_all_esdts_for_account(address, proxy)) + filtered_tokens_list = [] + + for token in esdts: + if in_token not in token: + continue + if 'nonce' not in esdts[token]: + esdts[token]['nonce'] = 0 + filtered_tokens_list.append(esdts[token]) + + return filtered_tokens_list + + +def get_current_tokens_for_address(address: Address, proxy: ProxyNetworkProvider): + # TODO: This is a temporary adaptor between new specs of mxpy sdk and old specs of the rest of the code. + # Went with this granular approach to reduce api calls (one call for all esdts instead + # of two calls for fungibles and non-fungibles). + esdts = _get_all_esdts_for_account(address.bech32(), proxy) + fungibles = _get_fungibles_from_esdts(esdts) + non_fungibles = _get_non_fungibles_from_esdts(esdts) + + tokens_dict = {} + for token in fungibles: + identifier = token.identifier + tokens_dict[identifier] = { + 'nonce': 0, + 'balance': token.balance, + } + for token in non_fungibles: + identifier = token.collection + tokens_dict[identifier] = { + 'nonce': token.nonce, + 'balance': token.balance + } + + return tokens_dict + + +def get_token_details_for_address(in_token: str, address: str, proxy: ProxyNetworkProvider, underlying_tk: str = ""): + # TODO: This is a temporary adaptor between new specs of mxpy sdk and old specs of the rest of the code. + prevent_spam_crash_elrond_proxy_go() + tokens = cast(List, _get_all_esdts_for_account(address, proxy)) + + for token in tokens: + if in_token not in token: + continue + + attributes_hex = "" + if 'attributes' in tokens[token]: + attributes_hex = base64_to_hex(tokens[token]['attributes']) + + underlying_tk_exists = False + if underlying_tk: + underlying_tk_hex = underlying_tk.encode('utf-8').hex() + if underlying_tk_hex in attributes_hex: + underlying_tk_exists = True + + if underlying_tk == "" or underlying_tk_exists: + nonce = tokens[token]['nonce'] if "nonce" in tokens[token] else 0 + amount = int(tokens[token]['balance']) + return nonce, amount, attributes_hex + + print("Token not found:", in_token) + return 0, 0, "" + + +class DecodedTokenAttributes: + rewards_per_share: int + original_entering_epoch: int + entering_epoch: int + apr_multiplier: int + locked_rewards: bool + initial_farming_amount: int + compounded_rewards: int + current_farm_amount: int + + def __init__(self, attributes_hex: str, attr_version: FarmContractVersion = None): + def slide_indexes(i, j, no_bytes: int): + index_f = j + index_l = j + (no_bytes * 2) + return index_f, index_l + + self.rewards_per_share = 0 + self.apr_multiplier = 0 + self.locked_rewards = False + self.current_farm_amount = 0 + self.compounded_rewards = 0 + self.initial_farming_amount = 0 + + # decode rewards per share BigUInt + index_first = 0 + index_last = 8 + payload_size = int(attributes_hex[index_first:index_last], 16) + if payload_size: + index_first, index_last = slide_indexes(index_first, index_last, payload_size) + self.rewards_per_share = int(attributes_hex[index_first:index_last], 16) + + # decode original entering epoch U64 + index_first, index_last = slide_indexes(index_first, index_last, 8) + self.entering_epoch = int(attributes_hex[index_first:index_last], 16) + + # decode entering epoch U64 + index_first, index_last = slide_indexes(index_first, index_last, 8) + self.original_entering_epoch = int(attributes_hex[index_first:index_last], 16) + + if attr_version == FarmContractVersion.V12: + # decode APR multiplier U8 + index_first, index_last = slide_indexes(index_first, index_last, 1) + self.apr_multiplier = int(attributes_hex[index_first:index_last], 16) + + # decode Locked Rewards U8 + index_first, index_last = slide_indexes(index_first, index_last, 1) + self.locked_rewards = bool(int(attributes_hex[index_first:index_last], 16)) + + # decode Initial Farming amount BigUInt + index_first, index_last = slide_indexes(index_first, index_last, 4) + payload_size = int(attributes_hex[index_first:index_last], 16) + if payload_size: + index_first, index_last = slide_indexes(index_first, index_last, payload_size) + self.initial_farming_amount = int(attributes_hex[index_first:index_last], 16) + + # decode Compounded Rewards BigUInt + index_first, index_last = slide_indexes(index_first, index_last, 4) + payload_size = int(attributes_hex[index_first:index_last], 16) + if payload_size: + index_first, index_last = slide_indexes(index_first, index_last, payload_size) + self.compounded_rewards = int(attributes_hex[index_first:index_last], 16) + + # decode Current Farm amount BigUInt + index_first, index_last = slide_indexes(index_first, index_last, 4) + payload_size = int(attributes_hex[index_first:index_last], 16) + if payload_size: + index_first, index_last = slide_indexes(index_first, index_last, payload_size) + self.current_farm_amount = int(attributes_hex[index_first:index_last], 16) + + +def decode_merged_attributes(attributes_hex: str, decode_struct: dict) -> dict: + def slide_indexes(j, no_bytes: int): + index_f = j + index_l = j + (no_bytes * 2) + return index_f, index_l + + def fixed_length_primitive(attributes: str, start_index: int, primitive_len: int): + index_first, index_last = slide_indexes(start_index, primitive_len) + result_hex = attributes[index_first:index_last] + result_int = int(result_hex, 16) + return result_int, result_hex, index_last + + def u8(attributes: str, start_index: int): + result, _, index = fixed_length_primitive(attributes, start_index, 1) + return result, index + + def u16(attributes: str, start_index: int): + result, _, index = fixed_length_primitive(attributes, start_index, 2) + return result, index + + def u32(attributes: str, start_index: int): + result, _, index = fixed_length_primitive(attributes, start_index, 4) + return result, index + + def u64(attributes: str, start_index: int): + result, _, index = fixed_length_primitive(attributes, start_index, 8) + return result, index + + def biguint(attributes: str, start_index: int): + payload_size, _, index = fixed_length_primitive(attributes, start_index, 4) + result = 0 + if payload_size: + result, _, index = fixed_length_primitive(attributes, index, payload_size) + return result, index + + def string(attributes: str, start_index: int): + payload_size, _, index = fixed_length_primitive(attributes, start_index, 4) + result_string = "" + if payload_size: + _, result, index = fixed_length_primitive(attributes, index, payload_size) + result_string = bytearray.fromhex(result).decode() + return result_string, index + + results_dict = {} + sliding_index = 0 + implemented_primitives = {'u8': u8, + 'u16': u16, + 'u32': u32, + 'u64': u64, + 'biguint': biguint, + 'string': string} + + for key, primitive in decode_struct.items(): + if type(primitive) is dict: + # primitive now becomes a dictionary of keys/primitives + list_decode_fields = primitive + list_len, sliding_index = implemented_primitives['u32'](attributes_hex, sliding_index) + decoded_list = [] + for _ in range(list_len): + decoded_list_fields = {} + for field_key, field_primitive in list_decode_fields.items(): + if field_primitive in implemented_primitives: + decoded_result, sliding_index = implemented_primitives[field_primitive]( + attributes_hex, sliding_index) + decoded_list_fields[field_key] = decoded_result + + decoded_list.append(decoded_list_fields) + + results_dict[key] = decoded_list + + elif primitive in implemented_primitives: + decoded_result, sliding_index = implemented_primitives[primitive](attributes_hex, sliding_index) + results_dict[key] = decoded_result + + return results_dict + + +def encode_merged_attributes(encode_data: dict, encode_struct: dict): + def u8(data): + padding = 2 + return f"{data:0{padding}X}" + + def u64(data): + padding = 16 + return f"{data:0{padding}X}" + + def biguint(data): + padding_data = 2 + padding_length = 8 + hex_data = f"{data:0{padding_data}X}" + data_length = len(hex_data) // 2 + hex_data_length = f"{data_length:0{padding_length}X}" + return f"{hex_data_length}{hex_data}" + + def string(data): + padding_data = 2 + padding_length = 8 + hex_data = data.encode("utf-8").hex() + data_length = len(hex_data) // 2 + hex_data_length = f"{data_length:0{padding_length}X}" + return f"{hex_data_length}{hex_data}" + + implemented_primitives = {'u8': u8, + 'u64': u64, + 'biguint': biguint, + 'string': string} + encoded_data = "" + for key, primitive in encode_struct.items(): + if primitive in implemented_primitives: + encoded_data += implemented_primitives[primitive](encode_data[key]) + + return encoded_data + + +def print_transaction_hash(hash: str, proxy: str, debug_level=True): + explorer = "" + if proxy == "https://testnet-gateway.multiversx.com": + explorer = "https://testnet-explorer.multiversx.com" + if proxy == "https://devnet-gateway.multiversx.com": + explorer = "https://devnet-explorer.multiversx.com" + if proxy == "https://gateway.multiversx.com": + explorer = "https://explorer.multiversx.com" + + if debug_level: + print(explorer+"/transactions/"+hash) + + +class PrintColors(Enum): + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + RED = '\033[91m' + YELLOW = '\033[93m' + WARNING = '\033[93m' + FAIL = '\033[91m' + PASS = '\033[92m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + +def print_color(msg, color: PrintColors): + print(f"{color.value}{msg}{PrintColors.ENDC.value}") + + +def print_test_step_fail(msg): + print_color(msg, PrintColors.FAIL) + + +def print_test_step_pass(msg): + print_color(msg, PrintColors.PASS) + + +def print_test_substep(msg): + sub_step_print_header = " ├ " + print(f"{sub_step_print_header}{msg}") + + +def print_warning(msg): + print_color(msg, PrintColors.WARNING) + + +def print_condition_assert(conditions: Dict[bool, str]): + for condition, message in conditions.items(): + if condition: + print_test_step_pass(f"PASS: {message}") + else: + print_test_step_fail(f"FAIL: {message}") + + +class TestStepCondition: + def __init__(self, condition: bool, message: str): + self.condition = condition + self.message = message + + +class TestStepConditions: + conditions: List[TestStepCondition] + + def __init__(self): + self.conditions = [] + + def add_condition(self, condition: bool, message: str): + self.conditions.append(TestStepCondition(condition, message)) + + def assert_conditions(self): + for condition in self.conditions: + if condition.condition: + print_test_step_pass(f"PASS: {condition.message}") + else: + print_test_step_fail(f"FAIL: {condition.message}") + diff --git a/utils/utils_generic.py b/utils/utils_generic.py new file mode 100644 index 0000000..12873d7 --- /dev/null +++ b/utils/utils_generic.py @@ -0,0 +1,280 @@ +import json +import logging +import os +import pathlib +import shutil +import stat +import sys +import tarfile +import zipfile +import toml +from pathlib import Path +from typing import Any, List, Union, Optional, cast, IO, Dict + +logger = logging.getLogger("utils") + + +class ISerializable: + def to_dictionary(self) -> Dict[str, Any]: + return self.__dict__ + + +class Object(ISerializable): + def __repr__(self): + return str(self.__dict__) + + def to_dictionary(self): + return dict(self.__dict__) + + def to_json(self): + data_json = json.dumps(self.__dict__, indent=4) + return data_json + + +class BasicEncoder(json.JSONEncoder): + def default(self, o: Any): + if isinstance(o, ISerializable): + return o.to_dictionary() + return json.JSONEncoder.default(self, o) + + +def omit_fields(data: Any, fields: List[str] = []): + if isinstance(data, dict): + for field in fields: + data.pop(field, None) + return data + raise TypeError("omit_fields: only dictionaries are supported.") + + +def untar(archive_path: Path, destination_folder: Path) -> None: + logger.debug(f"untar [{archive_path}] to [{destination_folder}].") + + ensure_folder(destination_folder) + tar = tarfile.open(str(archive_path)) + tar.extractall(path=str(destination_folder)) + tar.close() + + logger.debug("untar done.") + + +def unzip(archive_path: Path, destination_folder: Path): + logger.debug(f"unzip [{archive_path}] to [{destination_folder}].") + + ensure_folder(destination_folder) + with zipfile.ZipFile(str(archive_path), "r") as my_zip: + my_zip.extractall(str(destination_folder)) + + logger.debug("unzip done.") + + +def ensure_folder(folder: Union[str, Path]): + pathlib.Path(folder).mkdir(parents=True, exist_ok=True) + + +def uniquify(path: Path) -> Path: + '''Generates the next available non-already-existing filename, by adding a _1, _2, _3, etc. suffix before the extension if necessary''' + i = 1 + stem = path.stem + while path.exists(): + path = path.with_name(f"{stem}_{i}").with_suffix(path.suffix) + i += 1 + return path + + +def read_lines(file: Path) -> List[str]: + with open(file) as f: + lines = f.readlines() + lines = [line.strip() for line in lines] + lines = [line for line in lines if line] + return lines + + +# TODO delete this function, it is too generic +# TODO find usages in legolas +def read_file(f: Any, binary: bool = False) -> Union[str, bytes]: + if isinstance(f, str) or isinstance(f, pathlib.PosixPath): + path = Path(f) + if binary: + return read_binary_file(path) + return read_text_file(path) + + file = cast(IO, f) + result = file.read() + assert isinstance(result, str) or isinstance(result, bytes) + return result + + +def read_binary_file(path: Path) -> bytes: + try: + with open(path, 'rb') as binary_file: + return binary_file.read() + except Exception as err: + raise OSError(str(path), err) from None + + +def read_text_file(path: Path) -> str: + try: + with open(path, 'r') as text_file: + return text_file.read() + except Exception as err: + raise OSError(str(path), err) from None + + +def write_file(file_path: Path, text: str): + with open(file_path, "w") as file: + return file.write(text) + + +def read_toml_file(filename): + return toml.load(str(filename)) + + +def write_toml_file(filename, data): + with open(str(filename), "w") as f: + toml.dump(data, f) + + +def read_json_file(filename: Union[str, Path]) -> Any: + with open(filename) as f: + return json.load(f) + + +def write_json_file(filename: str, data: Any): + with open(filename, "w") as f: + json.dump(data, f, indent=4) + + +def dump_out_json(data: Any, outfile: Any = None): + if not outfile: + outfile = sys.stdout + + json.dump(data, outfile, indent=4, cls=BasicEncoder) + outfile.write("\n") + + +def prettify_json_file(filename: str): + data = read_json_file(filename) + write_json_file(filename, data) + + +def get_subfolders(folder: Path) -> List[str]: + return [item.name for item in os.scandir(folder) if item.is_dir() and not item.name.startswith(".")] + + +def mark_executable(file: str) -> None: + logger.debug(f"Mark [{file}] as executable") + st = os.stat(file) + os.chmod(file, st.st_mode | stat.S_IEXEC) + + +def find_in_dictionary(dictionary, compound_path): + keys = compound_path.split(".") + node = dictionary + for key in keys: + node = node.get(key) + if node is None: + break + + return node + + +def list_files(folder: Path, suffix: Optional[str] = None) -> List[Path]: + folder = folder.expanduser() + files: List[Path] = [folder / file for file in os.listdir(folder)] + files = [file for file in files if file.is_file()] + + if suffix: + files = [file for file in files if str(file).lower().endswith(suffix.lower())] + + return files + + +def remove_folder(folder: Union[str, Path]): + shutil.rmtree(folder, ignore_errors=True) + + +def symlink(real: str, link: str) -> None: + if os.path.islink(link): + os.remove(link) + os.symlink(real, link) + + +def as_object(data: Object) -> Object: + if isinstance(data, dict): + result = Object() + result.__dict__.update(data) + return result + + return data + + +def is_arg_present(args: List[str], key: str) -> bool: + for arg in args: + if arg.find("--data") != -1: + continue + if arg.find(key) != -1: + return True + + return False + + +def str_int_to_hex_str(number_str: str) -> str: + num_of_bytes = 1 + if len(number_str) > 2: + num_of_bytes = int(len(number_str) / 2) + int_str = int(number_str) + int_bytes = int_str.to_bytes(num_of_bytes, byteorder="big") + bytes_str = int_bytes.hex() + return bytes_str + + +def parse_keys(bls_public_keys): + keys = bls_public_keys.split(',') + parsed_keys = '' + for key in keys: + parsed_keys += '@' + key + return parsed_keys, len(keys) + + +# https://code.visualstudio.com/docs/python/debugging +def breakpoint(): + import debugpy + debugpy.listen(5678) + print("Waiting for debugger attach") + debugpy.wait_for_client() + debugpy.breakpoint() + + +def log_explorer(chain, name, path, details): + networks = { + "1": ("Elrond Mainnet Explorer", "https://explorer.elrond.com"), + "T": ("Elrond Testnet Explorer", "https://testnet-explorer.elrond.com"), + "D": ("Elrond Devnet Explorer", "https://devnet-explorer.elrond.com"), + } + try: + explorer_name, explorer_url = networks[chain] + logger.info(f"View this {name} in the {explorer_name}: {explorer_url}/{path}/{details}") + except KeyError: + return + + +def log_explorer_contract_address(chain, address): + log_explorer(chain, "contract address", "accounts", address) + + +def log_explorer_transaction(chain, transaction_hash): + log_explorer(chain, "transaction", "transactions", transaction_hash) + + +def get_continue_confirmation(force_continue: bool = False) -> bool: + if force_continue: + typed = "y" + else: + typed = input(f"Continue? y/n\n") + + while typed != "y" and typed != "n": + typed = input(f"Wrong choice. Continue? y/n\n") + + if typed == "n": + return False + return True diff --git a/utils/utils_tx.py b/utils/utils_tx.py new file mode 100644 index 0000000..b3a7efc --- /dev/null +++ b/utils/utils_tx.py @@ -0,0 +1,321 @@ +import sys +import time +import traceback +from typing import List, Dict + +from multiversx_sdk_core import Transaction, TokenPayment, Address +from multiversx_sdk_network_providers import ProxyNetworkProvider, ApiNetworkProvider +from multiversx_sdk_network_providers.network_config import NetworkConfig +from multiversx_sdk_network_providers.tokens import FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork +from multiversx_sdk_network_providers.transactions import TransactionOnNetwork +from multiversx_sdk_core.transaction_builders import ContractCallBuilder, DefaultTransactionBuildersConfiguration, \ + MultiESDTNFTTransferBuilder +from utils.utils_chain import Account, print_transaction_hash, print_test_step_fail, print_warning + +TX_CACHE: Dict[str, dict] = {} + + +class ESDTToken: + token_id: str + token_nonce: int + token_amount: int + + def __init__(self, token_id: str, token_nonce: int, token_amount: int): + self.token_id = token_id + self.token_nonce = token_nonce + self.token_amount = token_amount + + def get_token_data(self) -> tuple: + return self.token_id, self.token_nonce, self.token_amount + + def get_full_token_name(self) -> str: + if self.token_nonce != 0: + nonce_str = "0" + f"{self.token_nonce:x}" if len(f"{self.token_nonce:x}") % 2 else f"{self.token_nonce:x}" + return f"{self.token_id}-{nonce_str}" + else: + return f"{self.token_id}" + + @classmethod + def from_token_payment(cls, token_payment: TokenPayment): + return cls(token_payment.token_identifier, token_payment.token_nonce, token_payment.amount_as_integer) + + @classmethod + def from_fungible_on_network(cls, token: FungibleTokenOfAccountOnNetwork): + return cls(token.identifier, 0, token.balance) + + @classmethod + def from_non_fungible_on_network(cls, token: NonFungibleTokenOfAccountOnNetwork): + return cls(token.identifier, token.nonce, token.balance) + + def to_token_payment(self) -> TokenPayment: + return TokenPayment(self.token_id, self.token_nonce, self.token_amount, 18) + + +class NetworkProviders: + def __init__(self, api: str, proxy: str): + self.api = ApiNetworkProvider(api) + self.proxy = ProxyNetworkProvider(proxy) + self.network = self.proxy.get_network_config() + + def wait_for_tx_executed(self, tx_hash: str): + time.sleep(2) # temporary fix for the api returning the wrong status + while True: + status = self.api.get_transaction_status(tx_hash) + if status.is_executed(): + break + time.sleep(self.network.round_duration) + + def check_deploy_tx_status(self, tx_hash: str, address: str, msg_label: str = "") -> bool: + if not tx_hash: + if msg_label: + print_test_step_fail(f"FAIL: no tx hash for {msg_label} contract deployment!") + return False + + status = self.api.get_transaction_status(tx_hash) + if status.is_failed() or address == "": + if msg_label: + print_test_step_fail(f"FAIL: transaction for {msg_label} contract deployment failed " + f"or couldn't retrieve address!") + return False + return True + + def check_complex_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: + if not tx_hash: + if msg_label: + print_test_step_fail(f"FAIL: no tx hash for {msg_label} transaction!") + return False + + self.wait_for_tx_executed(tx_hash) + return self.check_simple_tx_status(tx_hash, msg_label) + + def check_simple_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: + if not tx_hash: + if msg_label: + print_test_step_fail(f"FAIL: no tx hash for {msg_label} transaction!") + return False + + results = self.api.get_transaction_status(tx_hash) + if results.is_failed(): + if msg_label: + print_test_step_fail(f"Transaction to {msg_label} failed!") + return False + return True + + def get_tx_operations(self, tx_hash: str) -> list: + if tx_hash not in TX_CACHE: + # TODO replace with get_transaction after operations are added to the transaction object + transaction = self.api.do_get_generic(f'transactions/{tx_hash}') + TX_CACHE[tx_hash] = transaction # add it into the hash cache to avoid fetching it again + else: + transaction = TX_CACHE[tx_hash] # take it from hash cache + + if 'operations' in transaction: + return transaction['operations'] + + return [] + + def check_for_burn_operation(self, tx_hash: str, token: ESDTToken) -> bool: + operations = self.get_tx_operations(tx_hash) + if not operations: + return False + + for operation in operations: + if (operation['action'] == "localBurn" or operation['action'] == "burn") \ + and operation['identifier'] == token.get_full_token_name() \ + and operation['value'] == str(token.token_amount): + return True + return False + + def check_for_add_quantity_operation(self, tx_hash: str, token: ESDTToken) -> bool: + operations = self.get_tx_operations(tx_hash) + if not operations: + return False + + for operation in operations: + if operation['action'] == "addQuantity" \ + and operation['identifier'] == token.get_full_token_name() \ + and operation['value'] == str(token.token_amount): + return True + return False + + def check_for_mint_operation(self, tx_hash: str, token: ESDTToken) -> bool: + operations = self.get_tx_operations(tx_hash) + if not operations: + return False + + for operation in operations: + if operation['action'] == "localMint" \ + and operation['identifier'] == token.get_full_token_name() \ + and operation['value'] == str(token.token_amount): + return True + return False + + def check_for_transfer_operation(self, tx_hash: str, token: ESDTToken, sender: str = "", destination: str = ""): + operations = self.get_tx_operations(tx_hash) + if not operations: + return False + + for operation in operations: + if operation['action'] == "transfer" \ + and operation['identifier'] == token.get_full_token_name() \ + and operation['value'] == str(token.token_amount) \ + and (operation['sender'] == sender or sender == "") \ + and (operation['receiver'] == destination or destination == ""): + return True + return False + + def check_for_error_operation(self, tx_hash: str, message: str): + operations = self.get_tx_operations(tx_hash) + if not operations: + return False + + for operation in operations: + if operation['action'] == "signalError" \ + and operation['message'] == message: + return True + return False + + +def prepare_contract_call_tx(contract_address: Address, deployer: Account, + network_config: NetworkConfig, gas_limit: int, + function: str, args: list, value: str = "0") -> Transaction: + + config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) + builder = ContractCallBuilder( + config=config, + contract=contract_address, + function_name=function, + caller=deployer.address, + call_arguments=args, + value=value, + gas_limit=gas_limit, + nonce=deployer.nonce, + ) + tx = builder.build() + tx.signature = deployer.sign_transaction(tx) + + return tx + + +def prepare_multiesdtnfttransfer_to_endpoint_call_tx(contract_address: Address, user: Account, + network_config: NetworkConfig, gas_limit: int, + endpoint: str, endpoint_args: list, tokens: List[ESDTToken], + value: str = "0") -> Transaction: + config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) + payment_tokens = [token.to_token_payment() for token in tokens] + builder = ContractCallBuilder( + config=config, + contract=contract_address, + function_name=endpoint, + caller=user.address, + call_arguments=endpoint_args, + value=value, + gas_limit=gas_limit, + nonce=user.nonce, + esdt_transfers=payment_tokens + ) + tx = builder.build() + tx.signature = user.sign_transaction(tx) + + return tx + + +def prepare_multiesdtnfttransfer_tx(destination: Address, user: Account, + network_config: NetworkConfig, gas_limit: int, + tokens: List[ESDTToken], value: str = "0") -> Transaction: + config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) + payment_tokens = [token.to_token_payment() for token in tokens] + builder = MultiESDTNFTTransferBuilder( + config=config, + sender=user.address, + destination=destination, + payments=payment_tokens, + gas_limit=gas_limit, + value=value, + nonce=user.nonce, + ) + + tx = builder.build() + tx.signature = user.sign_transaction(tx) + return tx + + +def send_contract_call_tx(tx: Transaction, proxy: ProxyNetworkProvider) -> str: + try: + tx_hash = proxy.send_transaction(tx) + # TODO: check if needed to wait for tx to be processed + print_transaction_hash(tx_hash, proxy.url, True) + except Exception as ex: + print_test_step_fail(f"Failed to send tx due to: {ex}") + traceback.print_exception(*sys.exc_info()) + return "" + + return tx_hash + + +def multi_esdt_endpoint_call(function_purpose: str, proxy: ProxyNetworkProvider, gas: int, + user: Account, contract: Address, endpoint: str, args: list): + """ Expected as args: + type[List[ESDTToken]]: tokens list + opt: type[str..]: endpoint arguments + """ + print_warning(function_purpose) + network_config = proxy.get_network_config() # TODO: find solution to avoid this call + tx_hash = "" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return tx_hash + + ep_args = args[1:] if len(args) != 1 else [] + tx = prepare_multiesdtnfttransfer_to_endpoint_call_tx(contract, user, network_config, + gas, endpoint, ep_args, args[0]) + tx_hash = send_contract_call_tx(tx, proxy) + user.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + +def multi_esdt_tx(function_purpose: str, proxy: ProxyNetworkProvider, gas: int, + user: Account, dest: Address, args: list): + """ Expected as args: + type[ESDTToken...]: tokens list + """ + print_warning(function_purpose) + network_config = proxy.get_network_config() # TODO: find solution to avoid this call + tx_hash = "" + + if len(args) < 1: + print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + return tx_hash + + tx = prepare_multiesdtnfttransfer_tx(dest, user, network_config, gas, args) + tx_hash = send_contract_call_tx(tx, proxy) + user.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + +def endpoint_call(function_purpose: str, proxy: ProxyNetworkProvider, gas: int, + user: Account, contract: Address, endpoint: str, args: list, value: str = "0"): + """ Expected as args: + opt: type[str..]: endpoint arguments + """ + print_warning(function_purpose) + network_config = proxy.get_network_config() # TODO: find solution to avoid this call + + tx = prepare_contract_call_tx(contract, user, network_config, gas, endpoint, args, value) + tx_hash = send_contract_call_tx(tx, proxy) + user.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + +def get_deployed_address_from_event(tx_result: TransactionOnNetwork) -> str: + searched_event_id = "SCDeploy" + deploy_event = tx_result.logs.find_first_or_none_event(searched_event_id) + if deploy_event is None: + return "" + + address = deploy_event.address.bech32() + return address From 5f9835db900079428875afca885f52ece496a78a Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Mon, 27 Feb 2023 15:37:12 +0200 Subject: [PATCH 02/26] move functions to more appropriate destination --- contracts/dex_proxy_contract.py | 4 +- contracts/esdt_contract.py | 2 +- contracts/farm_contract.py | 5 +- contracts/fees_collector_contract.py | 4 +- contracts/locked_asset_contract.py | 4 +- contracts/metastaking_contract.py | 5 +- contracts/pair_contract.py | 8 +-- contracts/price_discovery_contract.py | 4 +- contracts/proxy_deployer_contract.py | 4 +- contracts/router_contract.py | 4 +- contracts/simple_lock_contract.py | 4 +- contracts/simple_lock_energy_contract.py | 4 +- contracts/staking_contract.py | 7 +- contracts/unstaker_contract.py | 4 +- deploy/dex_structure.py | 7 +- events/event_generators.py | 3 +- scenarios/scenario_dex_v2_all_in.py | 5 +- scenarios/scenario_fees_collector.py | 5 +- scenarios/scenario_simple_lock_energy.py | 5 +- scenarios/scenario_swaps.py | 5 +- scenarios/stress_create_positions.py | 2 +- tools/account_state.py | 2 +- tools/contracts_upgrader.py | 3 +- trackers/farm_economics_tracking.py | 4 +- trackers/metastaking_economics_tracking.py | 2 +- trackers/pair_economics_tracking.py | 2 +- .../price_discovery_economics_tracking.py | 4 +- trackers/staking_economics_tracking.py | 2 +- utils/utils_chain.py | 69 ------------------- utils/utils_generic.py | 68 ++++++++++++++++++ utils/utils_tx.py | 3 +- 31 files changed, 128 insertions(+), 126 deletions(-) diff --git a/contracts/dex_proxy_contract.py b/contracts/dex_proxy_contract.py index c7d57a7..ea7d24c 100644 --- a/contracts/dex_proxy_contract.py +++ b/contracts/dex_proxy_contract.py @@ -7,8 +7,8 @@ from contracts.farm_contract import FarmContract from contracts.pair_contract import PairContract from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders -from utils.utils_chain import print_transaction_hash, print_warning, print_test_step_fail, \ - print_test_step_pass, print_test_substep +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import SmartContract, CodeMetadata from erdpy.proxy import ElrondProxy diff --git a/contracts/esdt_contract.py b/contracts/esdt_contract.py index 01babe4..a868977 100644 --- a/contracts/esdt_contract.py +++ b/contracts/esdt_contract.py @@ -1,7 +1,7 @@ from enum import Enum from utils.utils_tx import send_contract_call_tx, prepare_contract_call_tx -from utils.utils_chain import print_warning, print_test_step_fail +from utils.utils_generic import print_test_step_fail, print_warning from erdpy.accounts import Account, Address from erdpy.proxy import ElrondProxy diff --git a/contracts/farm_contract.py b/contracts/farm_contract.py index 4176bbb..d055fd8 100644 --- a/contracts/farm_contract.py +++ b/contracts/farm_contract.py @@ -8,9 +8,8 @@ from erdpy.contracts import SmartContract, CodeMetadata from erdpy.proxy import ElrondProxy from erdpy.transactions import Transaction -from utils.utils_chain import print_transaction_hash, print_test_step_fail, print_test_step_pass, \ - print_test_substep -from utils.utils_chain import print_warning +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from events.farm_events import (EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent, CompoundRewardsFarmEvent, MigratePositionFarmEvent) diff --git a/contracts/fees_collector_contract.py b/contracts/fees_collector_contract.py index 1d162b0..d8ab7c2 100644 --- a/contracts/fees_collector_contract.py +++ b/contracts/fees_collector_contract.py @@ -4,8 +4,8 @@ from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx -from utils.utils_chain import print_warning, print_transaction_hash, print_test_step_fail, \ - print_test_step_pass, print_test_substep +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract from erdpy.proxy import ElrondProxy diff --git a/contracts/locked_asset_contract.py b/contracts/locked_asset_contract.py index 64d91bf..1d17120 100644 --- a/contracts/locked_asset_contract.py +++ b/contracts/locked_asset_contract.py @@ -4,8 +4,8 @@ from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx -from utils.utils_chain import print_warning, print_test_step_fail, print_transaction_hash, \ - print_test_step_pass, print_test_substep +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract from erdpy.proxy import ElrondProxy diff --git a/contracts/metastaking_contract.py b/contracts/metastaking_contract.py index a70619f..e77b0a3 100644 --- a/contracts/metastaking_contract.py +++ b/contracts/metastaking_contract.py @@ -11,9 +11,8 @@ from erdpy.contracts import SmartContract, CodeMetadata from erdpy.proxy import ElrondProxy from erdpy.transactions import Transaction -from utils.utils_chain import print_transaction_hash, print_test_step_fail, print_test_step_pass, \ - print_test_substep -from utils.utils_chain import print_warning +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning class MetaStakingContract(DEXContractInterface): diff --git a/contracts/pair_contract.py b/contracts/pair_contract.py index 90f2e34..44eca80 100644 --- a/contracts/pair_contract.py +++ b/contracts/pair_contract.py @@ -7,11 +7,9 @@ from utils.utils_tx import (NetworkProviders, prepare_contract_call_tx, send_contract_call_tx) -from utils.utils_chain import (dec_to_padded_hex, print_test_step_fail, - print_test_step_pass, - print_test_substep, - print_transaction_hash, - print_warning, string_to_hex) +from utils.utils_chain import (dec_to_padded_hex, print_transaction_hash, + string_to_hex) +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract from erdpy.proxy import ElrondProxy diff --git a/contracts/price_discovery_contract.py b/contracts/price_discovery_contract.py index 4018811..4e33ef6 100644 --- a/contracts/price_discovery_contract.py +++ b/contracts/price_discovery_contract.py @@ -6,8 +6,8 @@ from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders from events.price_discovery_events import (DepositPDLiquidityEvent, WithdrawPDLiquidityEvent, RedeemPDLPTokensEvent) -from utils.utils_chain import print_warning, print_transaction_hash, print_test_step_fail, \ - print_test_step_pass, print_test_substep +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import SmartContract, CodeMetadata from erdpy.proxy import ElrondProxy diff --git a/contracts/proxy_deployer_contract.py b/contracts/proxy_deployer_contract.py index b299906..7bf5cb0 100644 --- a/contracts/proxy_deployer_contract.py +++ b/contracts/proxy_deployer_contract.py @@ -4,8 +4,8 @@ from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface, RouterContractVersion from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event -from utils.utils_chain import print_transaction_hash, print_test_step_fail, print_warning, \ - print_test_step_pass +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import SmartContract, CodeMetadata from erdpy.proxy import ElrondProxy diff --git a/contracts/router_contract.py b/contracts/router_contract.py index 6130d18..8130fa8 100644 --- a/contracts/router_contract.py +++ b/contracts/router_contract.py @@ -4,8 +4,8 @@ from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface, RouterContractVersion from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event -from utils.utils_chain import print_transaction_hash, print_test_step_fail, print_warning, \ - print_test_step_pass +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import SmartContract, CodeMetadata from erdpy.proxy import ElrondProxy diff --git a/contracts/simple_lock_contract.py b/contracts/simple_lock_contract.py index ed141f3..c47b9b4 100644 --- a/contracts/simple_lock_contract.py +++ b/contracts/simple_lock_contract.py @@ -4,8 +4,8 @@ from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx -from utils.utils_chain import print_warning, print_transaction_hash, print_test_step_fail, \ - print_test_step_pass, print_test_substep +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract from erdpy.proxy import ElrondProxy diff --git a/contracts/simple_lock_energy_contract.py b/contracts/simple_lock_energy_contract.py index c6be1d7..4141cdb 100644 --- a/contracts/simple_lock_energy_contract.py +++ b/contracts/simple_lock_energy_contract.py @@ -5,8 +5,8 @@ from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, \ multi_esdt_endpoint_call, endpoint_call -from utils.utils_chain import print_warning, print_transaction_hash, print_test_step_fail, \ - print_test_step_pass, print_test_substep +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract from erdpy.proxy import ElrondProxy diff --git a/contracts/staking_contract.py b/contracts/staking_contract.py index 713ae45..9776b66 100644 --- a/contracts/staking_contract.py +++ b/contracts/staking_contract.py @@ -10,10 +10,9 @@ from erdpy.contracts import SmartContract, CodeMetadata from erdpy.proxy import ElrondProxy from erdpy.transactions import Transaction -from utils.utils_chain import print_transaction_hash, print_test_step_fail, print_test_step_pass, \ - print_test_substep -from utils.utils_generic import get_continue_confirmation -from utils.utils_chain import print_warning +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import get_continue_confirmation, print_test_step_fail, print_test_step_pass, \ + print_test_substep, print_warning from events.farm_events import (EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent, CompoundRewardsFarmEvent, MigratePositionFarmEvent) diff --git a/contracts/unstaker_contract.py b/contracts/unstaker_contract.py index c6c5533..a97b890 100644 --- a/contracts/unstaker_contract.py +++ b/contracts/unstaker_contract.py @@ -5,8 +5,8 @@ from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, \ multi_esdt_endpoint_call, endpoint_call -from utils.utils_chain import print_warning, print_transaction_hash, print_test_step_fail, \ - print_test_step_pass, print_test_substep +from utils.utils_chain import print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract from erdpy.proxy import ElrondProxy diff --git a/deploy/dex_structure.py b/deploy/dex_structure.py index a5fd866..3480333 100644 --- a/deploy/dex_structure.py +++ b/deploy/dex_structure.py @@ -25,9 +25,10 @@ from contracts.staking_contract import StakingContract from contracts.dex_proxy_contract import DexProxyContract from utils.utils_tx import NetworkProviders -from utils.utils_chain import print_test_step_fail, print_test_step_pass, hex_to_string, print_warning -from erdpy.accounts import Account, Address -from utils.utils_generic import write_json_file, read_json_file +from utils.utils_chain import hex_to_string +from utils.utils_chain import Account, WrapperAddress as Address +from utils.utils_generic import write_json_file, read_json_file, print_test_step_fail, print_test_step_pass, \ + print_warning from deploy import populate_deploy_lists diff --git a/events/event_generators.py b/events/event_generators.py index bb23a10..f46aa5b 100644 --- a/events/event_generators.py +++ b/events/event_generators.py @@ -24,7 +24,8 @@ from utils.results_logger import FarmEventResultLogData from utils.utils_chain import (prevent_spam_crash_elrond_proxy_go, get_token_details_for_address, get_all_token_nonces_details_for_account, - print_test_step_fail, decode_merged_attributes, dec_to_padded_hex) + decode_merged_attributes, dec_to_padded_hex) +from utils.utils_generic import print_test_step_fail from erdpy.accounts import Account, Address diff --git a/scenarios/scenario_dex_v2_all_in.py b/scenarios/scenario_dex_v2_all_in.py index d9b2d89..bb382d4 100644 --- a/scenarios/scenario_dex_v2_all_in.py +++ b/scenarios/scenario_dex_v2_all_in.py @@ -22,8 +22,9 @@ generateExitMetastakeEvent, generate_swap_fixed_input) from utils.contract_data_fetchers import PairContractDataFetcher, SimpleLockEnergyContractDataFetcher from utils.utils_tx import ESDTToken -from utils.utils_chain import print_test_step_pass, print_condition_assert, nominated_amount, \ - print_test_step_fail, TestStepConditions, get_token_details_for_address, get_all_token_nonces_details_for_account +from utils.utils_chain import nominated_amount, \ + get_token_details_for_address, get_all_token_nonces_details_for_account +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_condition_assert, TestStepConditions from arrows.stress.send_token_from_minter import main as send_token_from_minter from arrows.stress.send_egld_from_minter import main as send_egld_from_minter from arrows.stress.shared import get_shard_of_address diff --git a/scenarios/scenario_fees_collector.py b/scenarios/scenario_fees_collector.py index d9b2d89..bb382d4 100644 --- a/scenarios/scenario_fees_collector.py +++ b/scenarios/scenario_fees_collector.py @@ -22,8 +22,9 @@ generateExitMetastakeEvent, generate_swap_fixed_input) from utils.contract_data_fetchers import PairContractDataFetcher, SimpleLockEnergyContractDataFetcher from utils.utils_tx import ESDTToken -from utils.utils_chain import print_test_step_pass, print_condition_assert, nominated_amount, \ - print_test_step_fail, TestStepConditions, get_token_details_for_address, get_all_token_nonces_details_for_account +from utils.utils_chain import nominated_amount, \ + get_token_details_for_address, get_all_token_nonces_details_for_account +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_condition_assert, TestStepConditions from arrows.stress.send_token_from_minter import main as send_token_from_minter from arrows.stress.send_egld_from_minter import main as send_egld_from_minter from arrows.stress.shared import get_shard_of_address diff --git a/scenarios/scenario_simple_lock_energy.py b/scenarios/scenario_simple_lock_energy.py index 658cdde..f8e553c 100644 --- a/scenarios/scenario_simple_lock_energy.py +++ b/scenarios/scenario_simple_lock_energy.py @@ -17,8 +17,9 @@ generateClaimMetastakeRewardsEvent, generateExitMetastakeEvent) from utils.utils_tx import ESDTToken -from utils.utils_chain import print_test_step_pass, print_condition_assert, nominated_amount, \ - print_test_step_fail, TestStepConditions, get_token_details_for_address +from utils.utils_chain import nominated_amount, \ + get_token_details_for_address +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_condition_assert, TestStepConditions from arrows.stress.send_token_from_minter import main as send_token_from_minter from arrows.stress.shared import get_shard_of_address from erdpy.accounts import Account diff --git a/scenarios/scenario_swaps.py b/scenarios/scenario_swaps.py index 3b91589..f81338b 100644 --- a/scenarios/scenario_swaps.py +++ b/scenarios/scenario_swaps.py @@ -22,8 +22,9 @@ generateExitMetastakeEvent, generate_swap_fixed_input) from utils.contract_data_fetchers import PairContractDataFetcher, SimpleLockEnergyContractDataFetcher from utils.utils_tx import ESDTToken -from utils.utils_chain import print_test_step_pass, print_condition_assert, nominated_amount, \ - print_test_step_fail, TestStepConditions, get_token_details_for_address, get_all_token_nonces_details_for_account +from utils.utils_chain import nominated_amount, \ + get_token_details_for_address, get_all_token_nonces_details_for_account +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_condition_assert, TestStepConditions from arrows.stress.send_token_from_minter import main as send_token_from_minter from arrows.stress.send_egld_from_minter import main as send_egld_from_minter from arrows.stress.shared import get_shard_of_address diff --git a/scenarios/stress_create_positions.py b/scenarios/stress_create_positions.py index 96e30a6..46ad500 100644 --- a/scenarios/stress_create_positions.py +++ b/scenarios/stress_create_positions.py @@ -15,7 +15,7 @@ generateClaimMetastakeRewardsEvent, generateExitMetastakeEvent, generateExitFarmEvent) -from utils.utils_chain import print_test_step_pass +from utils.utils_generic import print_test_step_pass from arrows.stress.send_token_from_minter import main as send_token_from_minter from arrows.stress.shared import get_shard_of_address from erdpy.accounts import Account diff --git a/tools/account_state.py b/tools/account_state.py index e379a20..94b37a1 100644 --- a/tools/account_state.py +++ b/tools/account_state.py @@ -5,7 +5,7 @@ from typing import Dict, Any, Tuple, List -from utils.utils_chain import print_test_step_fail, print_test_step_pass, print_warning +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning from erdpy.proxy.http_facade import do_get diff --git a/tools/contracts_upgrader.py b/tools/contracts_upgrader.py index 358c618..c034e60 100644 --- a/tools/contracts_upgrader.py +++ b/tools/contracts_upgrader.py @@ -23,7 +23,8 @@ from utils.contract_data_fetchers import RouterContractDataFetcher, PairContractDataFetcher, \ StakingContractDataFetcher, FarmContractDataFetcher from utils.utils_tx import NetworkProviders -from utils.utils_chain import base64_to_hex, print_test_step_fail +from utils.utils_chain import base64_to_hex +from utils.utils_generic import print_test_step_fail from tools import config_contracts_upgrader as config from erdpy.accounts import Address, Account from erdpy.proxy import ElrondProxy diff --git a/trackers/farm_economics_tracking.py b/trackers/farm_economics_tracking.py index d7a0290..508d088 100644 --- a/trackers/farm_economics_tracking.py +++ b/trackers/farm_economics_tracking.py @@ -2,8 +2,8 @@ from utils.contract_data_fetchers import FarmContractDataFetcher, ChainDataFetcher from utils.utils_tx import NetworkProviders from erdpy.accounts import Account, Address -from utils.utils_chain import (print_test_step_pass, print_test_step_fail, print_test_substep, - DecodedTokenAttributes) +from utils.utils_chain import (DecodedTokenAttributes) +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep from events.farm_events import (EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent, SetTokenBalanceEvent) from trackers.abstract_observer import Subscriber diff --git a/trackers/metastaking_economics_tracking.py b/trackers/metastaking_economics_tracking.py index 11c2da0..ad652af 100644 --- a/trackers/metastaking_economics_tracking.py +++ b/trackers/metastaking_economics_tracking.py @@ -1,6 +1,6 @@ from erdpy.accounts import Address from utils.utils_tx import NetworkProviders -from utils.utils_chain import print_test_step_pass +from utils.utils_generic import print_test_step_pass from utils.contract_data_fetchers import MetaStakingContractDataFetcher, ChainDataFetcher from events.metastake_events import (EnterMetastakeEvent, ExitMetastakeEvent, diff --git a/trackers/pair_economics_tracking.py b/trackers/pair_economics_tracking.py index 000276f..f117e54 100644 --- a/trackers/pair_economics_tracking.py +++ b/trackers/pair_economics_tracking.py @@ -3,7 +3,7 @@ from trackers.abstract_observer import Subscriber from trackers.concrete_observer import Observable from utils.contract_data_fetchers import PairContractDataFetcher -from utils.utils_chain import print_test_step_pass, print_test_step_fail, print_test_substep +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep from contracts.pair_contract import (AddLiquidityEvent, RemoveLiquidityEvent, SwapFixedInputEvent, diff --git a/trackers/price_discovery_economics_tracking.py b/trackers/price_discovery_economics_tracking.py index 2213fc6..0039c24 100644 --- a/trackers/price_discovery_economics_tracking.py +++ b/trackers/price_discovery_economics_tracking.py @@ -4,8 +4,8 @@ from events.price_discovery_events import (DepositPDLiquidityEvent, WithdrawPDLiquidityEvent, RedeemPDLPTokensEvent) from contracts.contract_identities import PriceDiscoveryContractIdentity -from utils.utils_chain import print_test_step_fail, print_test_substep, print_test_step_pass, \ - get_all_token_nonces_details_for_account, get_token_details_for_address +from utils.utils_chain import get_all_token_nonces_details_for_account, get_token_details_for_address +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep from erdpy.accounts import Address from erdpy.proxy import ElrondProxy diff --git a/trackers/staking_economics_tracking.py b/trackers/staking_economics_tracking.py index 00a9552..b812ef2 100644 --- a/trackers/staking_economics_tracking.py +++ b/trackers/staking_economics_tracking.py @@ -4,7 +4,7 @@ from trackers.concrete_observer import Observable from events.farm_events import EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent from utils.contract_data_fetchers import StakingContractDataFetcher, ChainDataFetcher -from utils.utils_chain import print_test_step_pass, print_test_step_fail, print_test_substep +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep class StakingEconomics(Subscriber): diff --git a/utils/utils_chain.py b/utils/utils_chain.py index 42c8932..f5fa53b 100644 --- a/utils/utils_chain.py +++ b/utils/utils_chain.py @@ -6,8 +6,6 @@ from pathlib import Path from typing import List, Dict, Any, Optional, Set, cast -from enum import Enum - from multiversx_sdk_network_providers.tokens import FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork from contracts.contract_identities import FarmContractVersion @@ -478,70 +476,3 @@ def print_transaction_hash(hash: str, proxy: str, debug_level=True): if debug_level: print(explorer+"/transactions/"+hash) - -class PrintColors(Enum): - HEADER = '\033[95m' - BLUE = '\033[94m' - CYAN = '\033[96m' - GREEN = '\033[92m' - RED = '\033[91m' - YELLOW = '\033[93m' - WARNING = '\033[93m' - FAIL = '\033[91m' - PASS = '\033[92m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' - - -def print_color(msg, color: PrintColors): - print(f"{color.value}{msg}{PrintColors.ENDC.value}") - - -def print_test_step_fail(msg): - print_color(msg, PrintColors.FAIL) - - -def print_test_step_pass(msg): - print_color(msg, PrintColors.PASS) - - -def print_test_substep(msg): - sub_step_print_header = " ├ " - print(f"{sub_step_print_header}{msg}") - - -def print_warning(msg): - print_color(msg, PrintColors.WARNING) - - -def print_condition_assert(conditions: Dict[bool, str]): - for condition, message in conditions.items(): - if condition: - print_test_step_pass(f"PASS: {message}") - else: - print_test_step_fail(f"FAIL: {message}") - - -class TestStepCondition: - def __init__(self, condition: bool, message: str): - self.condition = condition - self.message = message - - -class TestStepConditions: - conditions: List[TestStepCondition] - - def __init__(self): - self.conditions = [] - - def add_condition(self, condition: bool, message: str): - self.conditions.append(TestStepCondition(condition, message)) - - def assert_conditions(self): - for condition in self.conditions: - if condition.condition: - print_test_step_pass(f"PASS: {condition.message}") - else: - print_test_step_fail(f"FAIL: {condition.message}") - diff --git a/utils/utils_generic.py b/utils/utils_generic.py index 12873d7..0633a45 100644 --- a/utils/utils_generic.py +++ b/utils/utils_generic.py @@ -8,6 +8,7 @@ import tarfile import zipfile import toml +from enum import Enum from pathlib import Path from typing import Any, List, Union, Optional, cast, IO, Dict @@ -278,3 +279,70 @@ def get_continue_confirmation(force_continue: bool = False) -> bool: if typed == "n": return False return True + + +class PrintColors(Enum): + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + RED = '\033[91m' + YELLOW = '\033[93m' + WARNING = '\033[93m' + FAIL = '\033[91m' + PASS = '\033[92m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + +def print_color(msg, color: PrintColors): + print(f"{color.value}{msg}{PrintColors.ENDC.value}") + + +def print_test_step_fail(msg): + print_color(msg, PrintColors.FAIL) + + +def print_test_step_pass(msg): + print_color(msg, PrintColors.PASS) + + +def print_test_substep(msg): + sub_step_print_header = " ├ " + print(f"{sub_step_print_header}{msg}") + + +def print_warning(msg): + print_color(msg, PrintColors.WARNING) + + +def print_condition_assert(conditions: Dict[bool, str]): + for condition, message in conditions.items(): + if condition: + print_test_step_pass(f"PASS: {message}") + else: + print_test_step_fail(f"FAIL: {message}") + + +class TestStepCondition: + def __init__(self, condition: bool, message: str): + self.condition = condition + self.message = message + + +class TestStepConditions: + conditions: List[TestStepCondition] + + def __init__(self): + self.conditions = [] + + def add_condition(self, condition: bool, message: str): + self.conditions.append(TestStepCondition(condition, message)) + + def assert_conditions(self): + for condition in self.conditions: + if condition.condition: + print_test_step_pass(f"PASS: {condition.message}") + else: + print_test_step_fail(f"FAIL: {condition.message}") diff --git a/utils/utils_tx.py b/utils/utils_tx.py index b3a7efc..c0039a3 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -10,7 +10,8 @@ from multiversx_sdk_network_providers.transactions import TransactionOnNetwork from multiversx_sdk_core.transaction_builders import ContractCallBuilder, DefaultTransactionBuildersConfiguration, \ MultiESDTNFTTransferBuilder -from utils.utils_chain import Account, print_transaction_hash, print_test_step_fail, print_warning +from utils.utils_chain import Account, print_transaction_hash +from utils.utils_generic import print_test_step_fail, print_warning TX_CACHE: Dict[str, dict] = {} From 5bc51d8a3efd24f8d8f09e8f24f491ce6ecdfcbc Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Tue, 28 Feb 2023 11:30:41 +0200 Subject: [PATCH 03/26] deploy section port to mxpy --- deploy/issue_tokens.py | 68 ++++++++++++++++++++++------------------- deploy/sync_tokens.py | 14 ++++----- deploy/tokens_tracks.py | 15 ++++----- utils/utils_chain.py | 13 ++++++++ utils/utils_generic.py | 5 +++ utils/utils_tx.py | 29 ++++++++++++++++-- 6 files changed, 96 insertions(+), 48 deletions(-) diff --git a/deploy/issue_tokens.py b/deploy/issue_tokens.py index b222f3d..6c028f9 100644 --- a/deploy/issue_tokens.py +++ b/deploy/issue_tokens.py @@ -4,13 +4,12 @@ from typing import List import config -from arrows.stress.esdtnft.shared import (build_token_name, build_token_ticker, - load_contracts, make_call_arg_ascii, - make_call_arg_pubkey) -from arrows.stress.shared import BunchOfAccounts, broadcast_transactions -from erdpy.contracts import SmartContract -from erdpy.proxy.core import ElrondProxy -from erdpy.transactions import Transaction +from utils.utils_chain import (Account, build_token_name, build_token_ticker) +from multiversx_sdk_network_providers import ProxyNetworkProvider +from multiversx_sdk_core import Address, TokenPayment, Transaction +from multiversx_sdk_core.transaction_builders import ContractCallBuilder, DefaultTransactionBuildersConfiguration + +from utils.utils_tx import broadcast_transactions def main(cli_args: List[str]): @@ -18,7 +17,7 @@ def main(cli_args: List[str]): parser = ArgumentParser() parser.add_argument("--proxy", default=config.DEFAULT_PROXY) - parser.add_argument("--accounts", default=config.DEFAULT_OWNER) + parser.add_argument("--account", default=config.DEFAULT_OWNER) parser.add_argument("--contracts", default=config.get_default_contracts_file()) parser.add_argument("--sleep-between-chunks", type=int, default=5) parser.add_argument("--chunk-size", type=int, default=400) @@ -36,16 +35,14 @@ def main(cli_args: List[str]): args = parser.parse_args(cli_args) - proxy = ElrondProxy(args.proxy) + proxy = ProxyNetworkProvider(args.proxy) network = proxy.get_network_config() + builder_config = DefaultTransactionBuildersConfiguration(network.chain_id) - bunch_of_accounts = BunchOfAccounts.load_accounts_from_files([args.accounts]) - # bunch_of_accounts.sync_nonces(proxy) - accounts = bunch_of_accounts.get_all() if args.from_shard is None else bunch_of_accounts.get_in_shard(int(args.from_shard)) - account = accounts[0] # issue tokens only for SC owner account to improve times on large number of accounts + account = Account(pem_file=args.account) account.sync_nonce(proxy) - tokens_system_contract = SmartContract(address=config.TOKENS_CONTRACT_ADDRESS) + tokens_system_contract = Address.from_bech32(config.TOKENS_CONTRACT_ADDRESS) supply = pow(10, args.supply_exp) num_decimals = args.num_decimals @@ -55,28 +52,34 @@ def main(cli_args: List[str]): def issue_token(): for i in range(0, args.num_tokens): - account = accounts[i] token_name, token_name_hex = build_token_name(account.address, prefix) token_ticker, token_ticker_hex = build_token_ticker(account.address, prefix) - sc_args = [token_name_hex, token_ticker_hex, supply, num_decimals] - tx_data = tokens_system_contract.prepare_execute_transaction_data("issue", sc_args) - - gas_limit = args.gas_limit or args.base_gas_limit + 50000 + 1500 * len(tx_data) value = args.value - tx = Transaction() + builder = ContractCallBuilder( + config=builder_config, + contract=tokens_system_contract, + caller=account.address, + function_name="issue", + call_arguments=[ + token_name, + token_ticker, + supply, + num_decimals + ], + value=TokenPayment.egld_from_integer(value) + ) + + # calculate precise gas limit + tx_data = builder.build_payload() + gas_limit = args.gas_limit or args.base_gas_limit + 50000 + 1500 * tx_data.length() + builder.gas_limit = gas_limit + + tx = builder.build() tx.nonce = account.nonce - tx.value = value - tx.sender = account.address.bech32() - tx.receiver = tokens_system_contract.address.bech32() - tx.gasPrice = network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = str(network.chain_id) - tx.version = network.min_tx_version - tx.sign(account) - - print("Holder account: ", account.address) + tx.signature = account.sign_transaction(tx) + + print("Holder account: ", account.address.bech32()) print("Token name: ", token_name) print("Token ticker: ", token_ticker) @@ -87,7 +90,8 @@ def issue_token(): issue_token() - hashes = broadcast_transactions(transactions, proxy, args.chunk_size, sleep=args.sleep_between_chunks, confirm_yes=args.yes) + hashes = broadcast_transactions(transactions, proxy, args.chunk_size, + sleep=args.sleep_between_chunks, confirm_yes=args.yes) return hashes diff --git a/deploy/sync_tokens.py b/deploy/sync_tokens.py index d0bdb6d..13897d0 100644 --- a/deploy/sync_tokens.py +++ b/deploy/sync_tokens.py @@ -6,10 +6,10 @@ import config from deploy.tokens_tracks import BunchOfTracks -from arrows.stress.shared import BunchOfAccounts -from erdpy.accounts import Address -from erdpy.errors import ProxyRequestError -from erdpy.proxy.core import ElrondProxy +from utils.utils_chain import BunchOfAccounts +from utils.utils_chain import WrapperAddress as Address +from multiversx_sdk_network_providers.errors import GenericError +from multiversx_sdk_network_providers import ProxyNetworkProvider def main(cli_args: List[str]): @@ -23,7 +23,7 @@ def main(cli_args: List[str]): args = parser.parse_args(cli_args) - proxy = ElrondProxy(args.proxy) + proxy = ProxyNetworkProvider(args.proxy) print(proxy.url) bunch_of_accounts = BunchOfAccounts.load_accounts_from_files([args.accounts]) accounts = bunch_of_accounts.get_all() @@ -35,9 +35,9 @@ def main(cli_args: List[str]): def get_for_address(address: Address): try: - tokens = proxy.get_esdt_tokens(address) + tokens = proxy.get_fungible_tokens_of_account(address) tracks.put_for_account(address, tokens) - except ProxyRequestError as error: + except GenericError as error: print(error) Pool(25).map(get_for_address, addresses) diff --git a/deploy/tokens_tracks.py b/deploy/tokens_tracks.py index e403552..08b1474 100644 --- a/deploy/tokens_tracks.py +++ b/deploy/tokens_tracks.py @@ -5,8 +5,9 @@ from pathlib import Path from typing import Any, Dict, List, Tuple -from erdpy import utils -from erdpy.accounts import Address +from utils import utils_generic as utils +from utils.utils_chain import WrapperAddress as Address +from multiversx_sdk_network_providers.tokens import FungibleTokenOfAccountOnNetwork class BunchOfTracks: @@ -16,17 +17,17 @@ def __init__(self, prefix: str = "") -> None: self.all_tokens: List[str] = [] self.prefix = prefix.upper() - def put_for_account(self, address: Address, tokens: Dict[str, Any]): + def put_for_account(self, address: Address, tokens: List[FungibleTokenOfAccountOnNetwork]): for token in tokens: - if not token.startswith(self.prefix): + if not token.identifier.startswith(self.prefix): continue - balance = tokens[token]["balance"] + balance = token.balance if token not in self.accounts_by_token: - self.accounts_by_token[token] = dict() + self.accounts_by_token[token.identifier] = dict() - self.accounts_by_token[token][address.bech32()] = balance + self.accounts_by_token[token.identifier][address.bech32()] = balance def put_all_tokens(self, tokens: List[str]): self.all_tokens = [token for token in tokens if token.startswith(self.prefix)] diff --git a/utils/utils_chain.py b/utils/utils_chain.py index f5fa53b..c13b2e8 100644 --- a/utils/utils_chain.py +++ b/utils/utils_chain.py @@ -280,6 +280,19 @@ def get_token_details_for_address(in_token: str, address: str, proxy: ProxyNetwo return 0, 0, "" +def build_token_name(owner: Address, prefix: str = ""): + prefix = prefix or "" + prefix = (prefix + owner.bech32()[4:14]).upper() + hex = "0x" + prefix.encode("utf8").hex() + return prefix, hex + + +def build_token_ticker(owner: Address, prefix: str = ""): + prefix = (prefix + owner.bech32()[4:8]).upper() + hex = "0x" + prefix.encode("utf8").hex() + return prefix, hex + + class DecodedTokenAttributes: rewards_per_share: int original_entering_epoch: int diff --git a/utils/utils_generic.py b/utils/utils_generic.py index 0633a45..d54e22b 100644 --- a/utils/utils_generic.py +++ b/utils/utils_generic.py @@ -39,6 +39,11 @@ def default(self, o: Any): return json.JSONEncoder.default(self, o) +def split_to_chunks(items: Any, chunk_size: int): + for i in range(0, len(items), chunk_size): + yield items[i:i + chunk_size] + + def omit_fields(data: Any, fields: List[str] = []): if isinstance(data, dict): for field in fields: diff --git a/utils/utils_tx.py b/utils/utils_tx.py index c0039a3..5274f59 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -1,7 +1,7 @@ import sys import time import traceback -from typing import List, Dict +from typing import List, Dict, Any from multiversx_sdk_core import Transaction, TokenPayment, Address from multiversx_sdk_network_providers import ProxyNetworkProvider, ApiNetworkProvider @@ -11,7 +11,7 @@ from multiversx_sdk_core.transaction_builders import ContractCallBuilder, DefaultTransactionBuildersConfiguration, \ MultiESDTNFTTransferBuilder from utils.utils_chain import Account, print_transaction_hash -from utils.utils_generic import print_test_step_fail, print_warning +from utils.utils_generic import print_test_step_fail, print_warning, split_to_chunks, get_continue_confirmation TX_CACHE: Dict[str, dict] = {} @@ -320,3 +320,28 @@ def get_deployed_address_from_event(tx_result: TransactionOnNetwork) -> str: address = deploy_event.address.bech32() return address + + +def broadcast_transactions(transactions: List[Transaction], proxy: ProxyNetworkProvider, + chunk_size: int, sleep: int = 0, confirm_yes: bool = False): + chunks = list(split_to_chunks(transactions, chunk_size)) + + print(f"{len(transactions)} transactions have been prepared, in {len(chunks)} chunks of size {chunk_size}") + get_continue_confirmation(confirm_yes) + + chunk_index = 0 + hashes = [] + for chunk in chunks: + print("... chunk", chunk_index, "out of", len(chunks)) + + num_sent, sent_hashes = proxy.send_transactions(chunk) + if len(chunk) != num_sent: + print(f"sent {num_sent} instead of {len(chunk)}") + + chunk_index += 1 + hashes.extend(sent_hashes) + + if sleep is not None: + time.sleep(sleep) + + return hashes From 8985f2133e3952ed70afe735241a218855f2b9e3 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Thu, 2 Mar 2023 14:08:18 +0200 Subject: [PATCH 04/26] logger, contract ports, refactors --- .gitignore | 5 +- contracts/contract_identities.py | 4 +- contracts/dex_proxy_contract.py | 723 ++++------------------- contracts/farm_contract.py | 16 +- contracts/fees_collector_contract.py | 4 +- contracts/locked_asset_contract.py | 6 +- contracts/metastaking_contract.py | 12 +- contracts/pair_contract.py | 16 +- contracts/price_discovery_contract.py | 10 +- contracts/proxy_deployer_contract.py | 4 +- contracts/router_contract.py | 6 +- contracts/simple_lock_contract.py | 4 +- contracts/simple_lock_energy_contract.py | 6 +- contracts/staking_contract.py | 14 +- contracts/unstaker_contract.py | 4 +- events/event_generators.py | 90 +-- utils/logger.py | 23 + utils/utils_chain.py | 31 +- utils/utils_generic.py | 33 +- utils/utils_tx.py | 133 ++++- 20 files changed, 413 insertions(+), 731 deletions(-) create mode 100644 utils/logger.py diff --git a/.gitignore b/.gitignore index aea8242..f6720d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -/wallets +/wallets/* /venv* /.idea -/.vscode \ No newline at end of file +/.vscode +/logs/* \ No newline at end of file diff --git a/contracts/contract_identities.py b/contracts/contract_identities.py index b0879a1..9481310 100644 --- a/contracts/contract_identities.py +++ b/contracts/contract_identities.py @@ -1,5 +1,4 @@ -from abc import abstractmethod, ABCMeta, ABC -from dataclasses import dataclass +from abc import abstractmethod, ABC from enum import Enum from utils.utils_chain import Account @@ -88,7 +87,6 @@ class MetaStakingContractVersion(Enum): V2 = 2 - class FarmContractIdentity(DEXContractIdentityInterface): farmingToken: str farmToken: str diff --git a/contracts/dex_proxy_contract.py b/contracts/dex_proxy_contract.py index ea7d24c..e65dc66 100644 --- a/contracts/dex_proxy_contract.py +++ b/contracts/dex_proxy_contract.py @@ -1,18 +1,22 @@ +import logging import random import sys import traceback -from arrows.stress.contracts.contract import load_code_as_hex +import config from contracts.contract_identities import DEXContractInterface, ProxyContractVersion from contracts.farm_contract import FarmContract from contracts.pair_contract import PairContract -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders -from utils.utils_chain import print_transaction_hash -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning -from erdpy.accounts import Account, Address -from erdpy.contracts import SmartContract, CodeMetadata -from erdpy.proxy import ElrondProxy -from erdpy.transactions import Transaction +from utils.logger import get_logger +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders, deploy, upgrade_call, \ + endpoint_call, multi_esdt_endpoint_call, ESDTToken +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning, \ + log_unexpected_args +from utils.utils_chain import Account, WrapperAddress as Address, log_explorer_transaction +from multiversx_sdk_network_providers import ProxyNetworkProvider +from multiversx_sdk_core import Transaction, CodeMetadata + +logger = get_logger(__name__) class DexProxyAddLiquidityEvent: @@ -76,228 +80,6 @@ def __init__(self, farmContract: FarmContract, token: str, nonce: int, amount): self.amount = amount -class DexFarmProxyContract: - def __init__(self, farming_token: str, farm_token: str, address: str): - self.address = address - self.farming_token = farming_token - self.farm_token = farm_token - - def enter_farm_proxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyEnterFarmEvent, lock: int = -1, - initial: bool = False): - print("enterFarmProxy") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) - - enterFarmFn = "" - if lock == 1: - enterFarmFn = "enterFarmAndLockRewardsProxy" - elif lock == 0: - enterFarmFn = "enterFarmProxy" - else: - if random.randrange(0, 1) == 1: - enterFarmFn = "enterFarmAndLockRewardsProxy" - else: - enterFarmFn = "enterFarmProxy" - - gas_limit = 50000000 - - sc_args = [ - "0x" + Address(self.address).hex(), # proxy address - "0x01" if initial else "0x02", # number of tokens sent - "0x" + event.farming_tk.encode("ascii").hex(), # farming token details - "0x" + "0" + f"{event.farming_tk_nonce:x}" if len( - f"{event.farming_tk_nonce:x}") % 2 else "0x" + f"{event.farming_tk_nonce:x}", - "0x" + "0" + f"{event.farming_tk_amount:x}" if len( - f"{event.farming_tk_amount:x}") % 2 else "0x" + f"{event.farming_tk_amount:x}", - ] - if not initial: - sc_args.extend([ - "0x" + event.farm_tk.encode("ascii").hex(), # farm token details - "0x" + "0" + f"{event.farm_tk_nonce:x}" if len( - f"{event.farm_tk_nonce:x}") % 2 else "0x" + f"{event.farm_tk_nonce:x}", - "0x" + "0" + f"{event.farm_tk_amount:x}" if len( - f"{event.farm_tk_amount:x}") % 2 else "0x" + f"{event.farm_tk_amount:x}", - ]) - sc_args.extend([ - "0x" + enterFarmFn.encode("ascii").hex(), # enterFarm endpoint name - "0x" + Address(event.farmContract.address).hex(), # farm address - ]) - tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = user.address.bech32() # MultiESDTNFTTransfer is issued via self transfers - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) - user.nonce += 1 - except Exception as ex: - print(ex) - - def exit_farm_proxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyExitFarmEvent): - print("exitFarmProxy") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) - - gas_limit = 50000000 - - sc_args = [ - "0x" + event.token.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "exitFarmProxy".encode("ascii").hex(), - "0x" + Address(event.farmContract.address).hex(), - ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) - user.nonce += 1 - except Exception as ex: - print(ex) - - def claim_rewards_proxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyClaimRewardsEvent): - print("claimRewardsProxy") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) - - gas_limit = 50000000 - - sc_args = [ - "0x" + event.token.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "claimRewardsProxy".encode("ascii").hex(), - "0x" + Address(event.farmContract.address).hex(), - ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) - user.nonce += 1 - except Exception as ex: - print(ex) - - -class DexPairProxyContract: - def __init__(self, p_token_a: str, p_token_b: str, p_lp_token: str, address: str): - self.address = address - self.proxy_token_a = p_token_a - self.proxy_token_b = p_token_b - self.proxy_lp_token = p_lp_token - - def add_liquidity_proxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyAddLiquidityEvent): - print("addLiquidityProxy") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) - add_liquidity_fn = "addLiquidityProxy" - - sc_args = [ - "0x" + Address(self.address).hex(), # proxy address - "0x02", # number of tokens sent - "0x" + event.tokenA.encode("ascii").hex(), # farming token details - "0x" + "0" + f"{event.nonceA:x}" if len(f"{event.nonceA:x}") % 2 else "0x" + f"{event.nonceA:x}", - "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", - "0x" + event.tokenB.encode("ascii").hex(), # farm token details - "0x" + "0" + f"{event.nonceB:x}" if len(f"{event.nonceB:x}") % 2 else "0x" + f"{event.nonceB:x}", - "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", - "0x" + add_liquidity_fn.encode("ascii").hex(), # enterFarm endpoint name - "0x" + Address(event.pairContract.address).hex(), # farm address - "0x" + "0" + f"{event.amountAmin:x}" if len(f"{event.amountAmin:x}") % 2 else "0x" + f"{event.amountAmin:x}", - "0x" + "0" + f"{event.amountBmin:x}" if len(f"{event.amountBmin:x}") % 2 else "0x" + f"{event.amountBmin:x}", - ] - - tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) - gas_limit = 1000000000 - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - user.nonce += 1 - print_transaction_hash(txHash, network_provider.proxy.url) - except Exception as ex: - print(ex) - - def remove_liquidity_proxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyRemoveLiquidityEvent): - print("removeLiquidityProxy") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) - sc_args = [ - "0x" + context.wrappedLpTokenId.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "removeLiquidityProxy".encode("ascii").hex(), - "0x" + Address(event.pairContract.address).hex(), - "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", - "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", - ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - gas_limit = 1000000000 - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - user.nonce += 1 - print_transaction_hash(txHash, network_provider.proxy.url) - except Exception as ex: - print(ex) - - class DexProxyContract(DEXContractInterface): def __init__(self, locked_tokens: list, token: str, version: ProxyContractVersion, address: str = "", proxy_lp_token: str = "", proxy_farm_token: str = ""): @@ -328,257 +110,98 @@ def load_config_dict(cls, config_dict: dict): address=config_dict['address'], version=ProxyContractVersion(config_dict['version'])) - def addLiquidityProxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyAddLiquidityEvent): - print("addLiquidityProxy") - print(f"Account: {user.address}") + def add_liquidity_proxy(self, user: Account, proxy: ProxyNetworkProvider, event: DexProxyAddLiquidityEvent): + function_purpose = "add liquidity via proxy" + logger.debug(f"Executing {function_purpose} for user {user.address} with event {event.__dict__}") - self.acceptEsdtPaymentProxy(context, user, event.pairContract, event.tokenA, event.nonceA, event.amountA) - self.acceptEsdtPaymentProxy(context, user, event.pairContract, event.tokenB, event.nonceB, event.amountB) + tokens = [ESDTToken(event.tokenA, event.nonceA, event.amountA), + ESDTToken(event.tokenB, event.nonceB, event.amountB) + ] - contract = SmartContract(address=self.address) sc_args = [ - "0x" + Address(event.pairContract.address).hex(), - "0x" + event.tokenA.encode("ascii").hex(), - "0x" + "0" + f"{event.nonceA:x}" if len(f"{event.nonceA:x}") % 2 else "0x" + f"{event.nonceA:x}", - "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", - "0x" + "0" + f"{event.amountAmin:x}" if len(f"{event.amountAmin:x}") % 2 else "0x" + f"{event.amountAmin:x}", - "0x" + event.tokenB.encode("ascii").hex(), - "0x" + "0" + f"{event.nonceB:x}" if len(f"{event.nonceB:x}") % 2 else "0x" + f"{event.nonceB:x}", - "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", - "0x" + "0" + f"{event.amountBmin:x}" if len(f"{event.amountBmin:x}") % 2 else "0x" + f"{event.amountBmin:x}", + tokens, + Address(event.pairContract.address), + event.amountAmin, + event.amountBmin ] + gas_limit = 40000000 + + return multi_esdt_endpoint_call(function_purpose, proxy, gas_limit, user, Address(self.address), + "addLiquidityProxy", sc_args) + + def remove_liquidity_proxy(self, user: Account, proxy: ProxyNetworkProvider, event: DexProxyRemoveLiquidityEvent): + function_purpose = "remove liquidity via proxy" + logger.debug(f"Executing {function_purpose} for user {user.address} with event {event.__dict__}") + + tokens = [ESDTToken(self.proxy_lp_token, event.nonce, event.amount)] - tx_data = contract.prepare_execute_transaction_data("addLiquidityProxy", sc_args) - gas_limit = 1000000000 - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - user.nonce += 1 - print_transaction_hash(txHash, network_provider.proxy.url) - except Exception as ex: - print(ex) - - def acceptEsdtPaymentProxy(self, network_provider: NetworkProviders, user: Account, pair: PairContract, - token: str, nonce: int, amount: str): - print("acceptEsdtPaymentProxy") - print(f"Account: {user.address}") - - if nonce == 0: - contract = SmartContract(address=self.address) - sc_args = [ - "0x" + token.encode("ascii").hex(), - "0x" + "0" + f"{amount:x}" if len(f"{amount:x}") % 2 else "0x" + f"{amount:x}", - "0x" + "acceptEsdtPaymentProxy".encode("ascii").hex(), - "0x" + Address(pair.address).hex(), - ] - tx_data = contract.prepare_execute_transaction_data("ESDTTransfer", sc_args) - else: - contract = SmartContract(address=user.address) - sc_args = [ - "0x" + token.encode("ascii").hex(), - "0x" + "0" + f"{nonce:x}" if len(f"{nonce:x}") % 2 else "0x" + f"{nonce:x}", - "0x" + "0" + f"{amount:x}" if len(f"{amount:x}") % 2 else "0x" + f"{amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "acceptEsdtPaymentProxy".encode("ascii").hex(), - "0x" + Address(pair.address).hex(), - ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = 50000000 - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) - user.nonce += 1 - except Exception as ex: - print(ex) - - def removeLiquidityProxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyRemoveLiquidityEvent): - print("removeLiquidityProxy") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) sc_args = [ - "0x" + context.wrappedLpTokenId.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "removeLiquidityProxy".encode("ascii").hex(), - "0x" + Address(event.pairContract.address).hex(), - "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", - "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", + tokens, + Address(event.pairContract.address), + event.amountA, + event.amountB ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - gas_limit = 1000000000 - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - user.nonce += 1 - print_transaction_hash(txHash, network_provider.proxy.url) - except Exception as ex: - print(ex) - - def enterFarmProxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyEnterFarmEvent, lock: int = -1, initial: bool = False): - print("enterFarmProxy") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) - - enterFarmFn = "" - if lock == 1: - enterFarmFn = "enterFarmAndLockRewardsProxy" - elif lock == 0: - enterFarmFn = "enterFarmProxy" - else: - if random.randrange(0, 1) == 1: - enterFarmFn = "enterFarmAndLockRewardsProxy" - else: - enterFarmFn = "enterFarmProxy" + gas_limit = 40000000 + + return multi_esdt_endpoint_call(function_purpose, proxy, gas_limit, user, Address(self.address), + "removeLiquidityProxy", sc_args) + + def enter_farm_proxy(self, user: Account, proxy: ProxyNetworkProvider, event: DexProxyEnterFarmEvent): + function_purpose = "enter farm via proxy" + logger.debug(f"Executing {function_purpose} for user {user.address} with event {event.__dict__}") gas_limit = 50000000 + tokens = [ESDTToken(event.farming_tk, event.farming_tk_nonce, event.farming_tk_amount)] + if event.farm_tk != "": + tokens.append(ESDTToken(event.farm_tk, event.farm_tk_nonce, event.farm_tk_amount)) + sc_args = [ - "0x" + Address(self.address).hex(), # proxy address - "0x01" if initial else "0x02", # number of tokens sent - "0x" + event.farming_tk.encode("ascii").hex(), # farming token details - "0x" + "0" + f"{event.farming_tk_nonce:x}" if len(f"{event.farming_tk_nonce:x}") % 2 else "0x" + f"{event.farming_tk_nonce:x}", - "0x" + "0" + f"{event.farming_tk_amount:x}" if len(f"{event.farming_tk_amount:x}") % 2 else "0x" + f"{event.farming_tk_amount:x}", + tokens, + Address(event.farmContract.address) ] - if not initial: - sc_args.extend([ - "0x" + event.farm_tk.encode("ascii").hex(), # farm token details - "0x" + "0" + f"{event.farm_tk_nonce:x}" if len(f"{event.farm_tk_nonce:x}") % 2 else "0x" + f"{event.farm_tk_nonce:x}", - "0x" + "0" + f"{event.farm_tk_amount:x}" if len(f"{event.farm_tk_amount:x}") % 2 else "0x" + f"{event.farm_tk_amount:x}", - ]) - sc_args.extend([ - "0x" + enterFarmFn.encode("ascii").hex(), # enterFarm endpoint name - "0x" + Address(event.farmContract.address).hex(), # farm address - ]) - tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = user.address.bech32() # MultiESDTNFTTransfer is issued via self transfers - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) - user.nonce += 1 - except Exception as ex: - print(ex) - - def exitFarmProxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyExitFarmEvent): - print("exitFarmProxy") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) + return multi_esdt_endpoint_call(function_purpose, proxy, gas_limit, user, Address(self.address), + "enterFarmProxy", sc_args) + + def exit_farm_proxy(self, user: Account, proxy: ProxyNetworkProvider, event: DexProxyExitFarmEvent): + function_purpose = "exit farm via proxy" + logger.debug(f"Executing {function_purpose} for user {user.address} with event {event.__dict__}") gas_limit = 50000000 + tokens = [ESDTToken(event.token, event.nonce, event.amount)] + sc_args = [ - "0x" + event.token.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "exitFarmProxy".encode("ascii").hex(), - "0x" + Address(event.farmContract.address).hex(), + tokens, + Address(event.farmContract.address), + event.amount ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) - user.nonce += 1 - except Exception as ex: - print(ex) - - def claimRewardsProxy(self, network_provider: NetworkProviders, user: Account, event: DexProxyClaimRewardsEvent): - print("claimRewardsProxy") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) + return multi_esdt_endpoint_call(function_purpose, proxy, gas_limit, user, Address(self.address), + "exitFarmProxy", sc_args) + + def claim_rewards_proxy(self, user: Account, proxy: ProxyNetworkProvider, event: DexProxyClaimRewardsEvent): + function_purpose = "claim rewards via proxy" + logger.debug(f"Executing {function_purpose} for user {user.address} with event {event.__dict__}") gas_limit = 50000000 + tokens = [ESDTToken(event.token, event.nonce, event.amount)] sc_args = [ - "0x" + event.token.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "claimRewardsProxy".encode("ascii").hex(), - "0x" + Address(event.farmContract.address).hex(), + tokens, + Address(event.farmContract.address) ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) - user.nonce += 1 - except Exception as ex: - print(ex) - - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): + return multi_esdt_endpoint_call(function_purpose, proxy, gas_limit, user, Address(self.address), + "claimRewardsProxy", sc_args) + + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = []): """Expecting as args: type[list]: locked asset factories contract addresses; care for the correct order based on locked tokens list """ - print_warning("Deploy dex proxy contract") + function_purpose = f"deploy {type(self).__name__} contract" + logging.info(function_purpose) if len(args) != 1: - print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "", "" if len(self.locked_tokens) != len(args[0]): @@ -586,230 +209,146 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, f"Mismatch between locked tokens and factory addresses.") return "", "" - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 300000000 - value = 0 - address = "" - tx_hash = "" - - arguments = [ - "0x" + self.token.encode("ascii").hex() - ] - - locked_tokens_list = [f"str:{token}" for token in self.locked_tokens] - locked_tokens_args = list(sum(zip(locked_tokens_list, args[0]), ())) + arguments = [self.token] + locked_tokens_args = list(sum(zip(self.locked_tokens, args[0]), ())) arguments.extend(locked_tokens_args) - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, args) return tx_hash, address - def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, + def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = [], no_init: bool = False): """Expecting as args: type[list]: locked asset factories contract addresses; care for the correct order based on locked tokens list """ - print_warning("Upgrade dex proxy contract") + function_purpose = f"upgrade {type(self).__name__} contract" + logging.info(function_purpose) if len(args) != 1 and not no_init: - print_test_step_fail(f"FAIL: Failed to upgrade contract. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "", "" if not no_init and len(self.locked_tokens) != len(args[0]): - print_test_step_fail(f"FAIL: Failed to deploy contract. " + print_test_step_fail(f"FAIL: Failed to upgrade contract. " f"Mismatch between locked tokens and factory addresses.") return "", "" - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 300000000 - value = 0 - tx_hash = "" if no_init: arguments = [] else: - arguments = [ - "0x" + self.token.encode("ascii").hex() - ] - locked_tokens_list = [f"str:{token}" for token in self.locked_tokens] - locked_tokens_args = list(sum(zip(locked_tokens_list, args[0]), ())) + arguments = [self.token] + locked_tokens_args = list(sum(zip(self.locked_tokens, args[0]), ())) arguments.extend(locked_tokens_args) - contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) - tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) - - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send upgrade transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash + tx_hash = upgrade_call(type(self).__name__, proxy, gas_limit, deployer, Address(self.address), + bytecode_path, metadata, arguments) return tx_hash - def register_proxy_farm_token(self, deployer: Account, proxy: ElrondProxy, args: list): + def register_proxy_farm_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """Expecting as args: type[str]: token display name type[str]: token ticker """ - print_warning("Register proxy farm token") - - network_config = proxy.get_network_config() + function_purpose = "Register proxy farm token" tx_hash = "" if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to register proxy farm token. Args list not as expected.") + log_unexpected_args(function_purpose, args) return tx_hash gas_limit = 100000000 sc_args = [ - "0x" + args[0].encode("ascii").hex(), - "0x" + args[1].encode("ascii").hex(), - "0x12" + args[0], + args[1], + "18" ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "registerProxyFarm", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), "registerProxyFarm", + sc_args, value=config.DEFAULT_ISSUE_TOKEN_PRICE) - def register_proxy_lp_token(self, deployer: Account, proxy: ElrondProxy, args: list): + def register_proxy_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """Expecting as args: type[str]: token display name type[str]: token ticker """ - print_warning("Register proxy lp token") - - network_config = proxy.get_network_config() + function_purpose = "Register proxy lp token" tx_hash = "" if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to register proxy lp token. Args list not as expected.") + log_unexpected_args(function_purpose, args) return tx_hash gas_limit = 100000000 sc_args = [ - "0x" + args[0].encode("ascii").hex(), - "0x" + args[1].encode("ascii").hex(), - "0x12" + args[0], + args[1], + "18" ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "registerProxyPair", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), "registerProxyPair", + sc_args, value=config.DEFAULT_ISSUE_TOKEN_PRICE) """Expecting as args: type[str]: token id type[str]: contract address to assign roles to """ - def set_local_roles_proxy_token(self, deployer: Account, proxy: ElrondProxy, args: list): - print_warning("Set local roles for proxy token") + def set_local_roles_proxy_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): + function_purpose = "Set local roles for proxy token" if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to set proxy local roles: unexpected number of args.") + log_unexpected_args(function_purpose, args) return "" - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [ - "0x" + args[0].encode("ascii").hex(), - "0x" + Address(args[1]).hex(), - "0x03", "0x04", "0x05" + args[0], + args[1], + 3, 4, 5 ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLocalRoles", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - return tx_hash + return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), "setLocalRoles", + sc_args) - def set_energy_factory_address(self, deployer: Account, proxy: ElrondProxy, energy_address: str): - print_warning("Set energy factory address in proxy contract") + def set_energy_factory_address(self, deployer: Account, proxy: ProxyNetworkProvider, energy_address: str): + function_purpose = "Set energy factory address in proxy contract" if energy_address == "": - print_test_step_fail(f"FAIL: Failed to Add pair to intermediate: pair address is empty") + log_unexpected_args(function_purpose, energy_address) return "" - network_config = proxy.get_network_config() gas_limit = 50000000 - sc_args = [ - "0x" + Address(energy_address).hex(), - ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setEnergyFactoryAddress", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), + "setEnergyFactoryAddress", [energy_address]) - return tx_hash - - def add_pair_to_intermediate(self, deployer: Account, proxy: ElrondProxy, pair_address: str): - print_warning("Add pair to intermediate in proxy contract") + def add_pair_to_intermediate(self, deployer: Account, proxy: ProxyNetworkProvider, pair_address: str): + function_purpose = "Add pair to intermediate in proxy contract" if pair_address == "": - print_test_step_fail(f"FAIL: Failed to Add pair to intermediate: pair address is empty") + log_unexpected_args(function_purpose, pair_address) return "" - network_config = proxy.get_network_config() gas_limit = 50000000 - sc_args = [ - "0x" + Address(pair_address).hex(), - ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "addPairToIntermediate", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), + "addPairToIntermediate", [pair_address]) - def add_farm_to_intermediate(self, deployer: Account, proxy: ElrondProxy, farm_address: str): - print_warning("Add farm to intermediate in proxy contract") + def add_farm_to_intermediate(self, deployer: Account, proxy: ProxyNetworkProvider, farm_address: str): + function_purpose = "Add farm to intermediate in proxy contract" if farm_address == "": - print_test_step_fail(f"FAIL: Failed to Add farm to intermediate: farm address is empty") + log_unexpected_args(function_purpose, farm_address) return "" - network_config = proxy.get_network_config() gas_limit = 50000000 - sc_args = [ - "0x" + Address(farm_address).hex(), - ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "addFarmToIntermediate", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), + "addFarmToIntermediate", [farm_address]) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): pass def print_contract_info(self): diff --git a/contracts/farm_contract.py b/contracts/farm_contract.py index d055fd8..9f78e71 100644 --- a/contracts/farm_contract.py +++ b/contracts/farm_contract.py @@ -8,7 +8,7 @@ from erdpy.contracts import SmartContract, CodeMetadata from erdpy.proxy import ElrondProxy from erdpy.transactions import Transaction -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from events.farm_events import (EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent, CompoundRewardsFarmEvent, MigratePositionFarmEvent) @@ -97,7 +97,7 @@ def enterFarm(self, network_provider: NetworkProviders, user: Account, event: En tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -134,7 +134,7 @@ def exitFarm(self, network_provider: NetworkProviders, user: Account, event: Exi tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -171,7 +171,7 @@ def claimRewards(self, network_provider: NetworkProviders, user: Account, event: tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash except Exception as ex: @@ -207,7 +207,7 @@ def compoundRewards(self, network_provider: NetworkProviders, user: Account, eve tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash except Exception as ex: @@ -244,7 +244,7 @@ def migratePosition(self, network_provider: NetworkProviders, user: Account, eve tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash except Exception as ex: @@ -297,7 +297,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 @@ -357,7 +357,7 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) deployer.nonce += 1 diff --git a/contracts/fees_collector_contract.py b/contracts/fees_collector_contract.py index d8ab7c2..52b5564 100644 --- a/contracts/fees_collector_contract.py +++ b/contracts/fees_collector_contract.py @@ -4,7 +4,7 @@ from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract @@ -56,7 +56,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 diff --git a/contracts/locked_asset_contract.py b/contracts/locked_asset_contract.py index 1d17120..1425518 100644 --- a/contracts/locked_asset_contract.py +++ b/contracts/locked_asset_contract.py @@ -4,7 +4,7 @@ from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract @@ -59,7 +59,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 @@ -102,7 +102,7 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) deployer.nonce += 1 diff --git a/contracts/metastaking_contract.py b/contracts/metastaking_contract.py index e77b0a3..782a8c0 100644 --- a/contracts/metastaking_contract.py +++ b/contracts/metastaking_contract.py @@ -11,7 +11,7 @@ from erdpy.contracts import SmartContract, CodeMetadata from erdpy.proxy import ElrondProxy from erdpy.transactions import Transaction -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning @@ -83,7 +83,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 @@ -126,7 +126,7 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) deployer.nonce += 1 @@ -232,7 +232,7 @@ def enter_metastake(self, network_provider: NetworkProviders, user: Account, try: tx_hash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(tx_hash, network_provider.proxy.url) + log_explorer_transaction(tx_hash, network_provider.proxy.url) user.nonce += 1 return tx_hash except Exception as ex: @@ -272,7 +272,7 @@ def exit_metastake(self, network_provider: NetworkProviders, user: Account, even try: tx_hash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(tx_hash, network_provider.proxy.url) + log_explorer_transaction(tx_hash, network_provider.proxy.url) user.nonce += 1 return tx_hash @@ -311,7 +311,7 @@ def claim_rewards_metastaking(self, network_provider: NetworkProviders, user: Ac try: tx_hash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(tx_hash, network_provider.proxy.url) + log_explorer_transaction(tx_hash, network_provider.proxy.url) user.nonce += 1 return tx_hash diff --git a/contracts/pair_contract.py b/contracts/pair_contract.py index 44eca80..51b15d6 100644 --- a/contracts/pair_contract.py +++ b/contracts/pair_contract.py @@ -7,7 +7,7 @@ from utils.utils_tx import (NetworkProviders, prepare_contract_call_tx, send_contract_call_tx) -from utils.utils_chain import (dec_to_padded_hex, print_transaction_hash, +from utils.utils_chain import (dec_to_padded_hex, log_explorer_transaction, string_to_hex) from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address @@ -118,7 +118,7 @@ def swapFixedInput(self, network_provider: NetworkProviders, user: Account, even tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -155,7 +155,7 @@ def swapFixedOutput(self, network_provider: NetworkProviders, user: Account, eve tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -196,7 +196,7 @@ def addLiquidity(self, network_provider: NetworkProviders, user: Account, event: tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -235,7 +235,7 @@ def addInitialLiquidity(self, network_provider: NetworkProviders, user: Account, tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 except Exception as ex: print(ex) @@ -269,7 +269,7 @@ def removeLiquidity(self, network_provider: NetworkProviders, user: Account, eve tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -319,7 +319,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 @@ -373,7 +373,7 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) deployer.nonce += 1 if tx_hash != "" else 0 except Exception as ex: diff --git a/contracts/price_discovery_contract.py b/contracts/price_discovery_contract.py index 4e33ef6..a37e551 100644 --- a/contracts/price_discovery_contract.py +++ b/contracts/price_discovery_contract.py @@ -6,7 +6,7 @@ from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders from events.price_discovery_events import (DepositPDLiquidityEvent, WithdrawPDLiquidityEvent, RedeemPDLPTokensEvent) -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import SmartContract, CodeMetadata @@ -122,7 +122,7 @@ def deposit_liquidity(self, network_provider: NetworkProviders, user: Account, e try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -161,7 +161,7 @@ def withdraw_liquidity(self, network_provider: NetworkProviders, user: Account, try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -200,7 +200,7 @@ def redeem_liquidity_position(self, network_provider: NetworkProviders, user: Ac try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -247,7 +247,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 diff --git a/contracts/proxy_deployer_contract.py b/contracts/proxy_deployer_contract.py index 7bf5cb0..aedbd1d 100644 --- a/contracts/proxy_deployer_contract.py +++ b/contracts/proxy_deployer_contract.py @@ -4,7 +4,7 @@ from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface, RouterContractVersion from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import SmartContract, CodeMetadata @@ -60,7 +60,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 diff --git a/contracts/router_contract.py b/contracts/router_contract.py index 8130fa8..af20160 100644 --- a/contracts/router_contract.py +++ b/contracts/router_contract.py @@ -4,7 +4,7 @@ from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface, RouterContractVersion from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import SmartContract, CodeMetadata @@ -57,7 +57,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 @@ -97,7 +97,7 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) deployer.nonce += 1 if tx_hash != "" else 0 except Exception as ex: diff --git a/contracts/simple_lock_contract.py b/contracts/simple_lock_contract.py index c47b9b4..01ebc34 100644 --- a/contracts/simple_lock_contract.py +++ b/contracts/simple_lock_contract.py @@ -4,7 +4,7 @@ from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract @@ -51,7 +51,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 diff --git a/contracts/simple_lock_energy_contract.py b/contracts/simple_lock_energy_contract.py index 4141cdb..ca09f81 100644 --- a/contracts/simple_lock_energy_contract.py +++ b/contracts/simple_lock_energy_contract.py @@ -5,7 +5,7 @@ from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, \ multi_esdt_endpoint_call, endpoint_call -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract @@ -78,7 +78,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 @@ -128,7 +128,7 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) deployer.nonce += 1 diff --git a/contracts/staking_contract.py b/contracts/staking_contract.py index 9776b66..d51f38f 100644 --- a/contracts/staking_contract.py +++ b/contracts/staking_contract.py @@ -10,7 +10,7 @@ from erdpy.contracts import SmartContract, CodeMetadata from erdpy.proxy import ElrondProxy from erdpy.transactions import Transaction -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import get_continue_confirmation, print_test_step_fail, print_test_step_pass, \ print_test_substep, print_warning from events.farm_events import (EnterFarmEvent, ExitFarmEvent, @@ -98,7 +98,7 @@ def stake_farm(self, network_provider: NetworkProviders, user: Account, event: E tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -135,7 +135,7 @@ def unstake_farm(self, network_provider: NetworkProviders, user: Account, event: tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash @@ -172,7 +172,7 @@ def claimRewards(self, network_provider: NetworkProviders, user: Account, event: tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash except Exception as ex: @@ -208,7 +208,7 @@ def compoundRewards(self, network_provider: NetworkProviders, user: Account, eve tx.sign(user) try: txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - print_transaction_hash(txHash, network_provider.proxy.url) + log_explorer_transaction(txHash, network_provider.proxy.url) user.nonce += 1 return txHash except Exception as ex: @@ -248,7 +248,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 @@ -294,7 +294,7 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) deployer.nonce += 1 except Exception as ex: diff --git a/contracts/unstaker_contract.py b/contracts/unstaker_contract.py index a97b890..9d0cf47 100644 --- a/contracts/unstaker_contract.py +++ b/contracts/unstaker_contract.py @@ -5,7 +5,7 @@ from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, \ multi_esdt_endpoint_call, endpoint_call -from utils.utils_chain import print_transaction_hash +from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning from erdpy.accounts import Account, Address from erdpy.contracts import CodeMetadata, SmartContract @@ -59,7 +59,7 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, try: response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) tx_hash = response.get_hash() - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) address = contract.address.bech32() deployer.nonce += 1 diff --git a/events/event_generators.py b/events/event_generators.py index f46aa5b..ee8f1e1 100644 --- a/events/event_generators.py +++ b/events/event_generators.py @@ -26,19 +26,19 @@ get_token_details_for_address, get_all_token_nonces_details_for_account, decode_merged_attributes, dec_to_padded_hex) from utils.utils_generic import print_test_step_fail -from erdpy.accounts import Account, Address +from utils.utils_chain import Account, WrapperAddress as Address def generate_add_liquidity_event(context: Context, user_account: Account, pair_contract: PairContract): print('Attempt addLiquidityEvent') txhash = '' try: - contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.proxy.url) + contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.network_provider.proxy.url) tokens = [pair_contract.firstToken, pair_contract.secondToken] - _, amount_token_a, _ = get_token_details_for_address(tokens[0], user_account.address.bech32(), context.proxy) - _, amount_token_b, _ = get_token_details_for_address(tokens[1], user_account.address.bech32(), context.proxy) + _, amount_token_a, _ = get_token_details_for_address(tokens[0], user_account.address.bech32(), context.network_provider.proxy) + _, amount_token_b, _ = get_token_details_for_address(tokens[1], user_account.address.bech32(), context.network_provider.proxy) if amount_token_a <= 0 or amount_token_b <= 0: print_test_step_fail(f"Skipped add liquidity because needed tokens NOT found in account.") @@ -94,11 +94,11 @@ def generate_remove_liquidity_event(context: Context, user_account: Account, pai print('Attempt removeLiquidityEvent') txhash = '' try: - contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.proxy.url) + contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.network_provider.proxy.url) # userAccount = context.get_random_user_account() # pairContract = context.get_random_pair_contract() - _, amount_lp_token, _ = get_token_details_for_address(pair_contract.lpToken, user_account.address.bech32(), context.proxy) + _, amount_lp_token, _ = get_token_details_for_address(pair_contract.lpToken, user_account.address.bech32(), context.network_provider.proxy) if amount_lp_token <= 0: print(f"Skipped swap because no {pair_contract.lpToken} found in account.") return @@ -139,12 +139,12 @@ def generate_swap_fixed_input(context: Context, user_account: Account, pair_cont print('Attempt swapFixedInputEvent') txhash = '' try: - contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.proxy.url) + contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.network_provider.proxy.url) tokens = [pair_contract.firstToken, pair_contract.secondToken] random.shuffle(tokens) - _, amount_token_a, _ = get_token_details_for_address(tokens[0], user_account.address.bech32(), context.proxy) + _, amount_token_a, _ = get_token_details_for_address(tokens[0], user_account.address.bech32(), context.network_provider.proxy) if amount_token_a <= 0: print(f"Skipped swap because no {tokens[0]} found in account.") return @@ -187,12 +187,12 @@ def generate_swap_fixed_output(context: Context, user_account: Account, pair_con print('Attempt swapFixedOutputEvent') txhash = '' try: - contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.proxy.url) + contract_data_fetcher = PairContractDataFetcher(Address(pair_contract.address), context.network_provider.proxy.url) tokens = [pair_contract.firstToken, pair_contract.secondToken] random.shuffle(tokens) - _, amount_token_a, _ = get_token_details_for_address(tokens[0], user_account.address.bech32(), context.proxy) + _, amount_token_a, _ = get_token_details_for_address(tokens[0], user_account.address.bech32(), context.network_provider.proxy) if amount_token_a <= 0: print(f"Skipped swap because no {tokens[0]} found in account.") return @@ -233,8 +233,8 @@ def generateEnterFarmEvent(context: Context, userAccount: Account, farmContract: farmToken = farmContract.farmToken farming_token = farmContract.farmingToken - farmingTkNonce, farmingTkAmount, _ = get_token_details_for_address(farming_token, userAccount.address, context.proxy) - farmTkNonce, farmTkAmount, _ = get_token_details_for_address(farmToken, userAccount.address, context.proxy) + farmingTkNonce, farmingTkAmount, _ = get_token_details_for_address(farming_token, userAccount.address, context.network_provider.proxy) + farmTkNonce, farmTkAmount, _ = get_token_details_for_address(farmToken, userAccount.address, context.network_provider.proxy) if farmingTkNonce == 0 and farmingTkAmount == 0: print_test_step_fail(f"SKIPPED: No tokens found!") @@ -255,13 +255,13 @@ def generateEnterFarmEvent(context: Context, userAccount: Account, farmContract: # pre-event logging event_log = FarmEventResultLogData() event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) - event_log.set_pre_event_data(context.proxy) + event_log.set_pre_event_data(context.network_provider.proxy) tx_hash = farmContract.enterFarm(context.network_provider, userAccount, event, lockRewards, initial) context.observable.set_event(farmContract, userAccount, event, tx_hash) # post-event logging - event_log.set_post_event_data(tx_hash, context.proxy) + event_log.set_post_event_data(tx_hash, context.network_provider.proxy) context.results_logger.add_event_log(event_log) except Exception as ex: @@ -280,10 +280,10 @@ def generateEnterStakingEvent(context: Context, user: Account, staking_contract: staking_token_nonce, staking_token_amount, _ = get_token_details_for_address(staking_token, user.address, - context.proxy) + context.network_provider.proxy) farm_token_nonce, farm_token_amount, _ = get_token_details_for_address(farm_token, user.address, - context.proxy) + context.network_provider.proxy) if not staking_token_amount: print_test_step_fail('SKIPPED enterStakingEvent: No tokens found!') return @@ -317,10 +317,10 @@ def generateEnterMetastakeEvent(context: Context, user: Account, metastake_contr staking_token_nonce, staking_token_amount, _ = get_token_details_for_address(staking_token, user.address, - context.proxy) + context.network_provider.proxy) metastake_token_nonce, metastake_token_amount, _ = get_token_details_for_address(metastake_token, user.address, - context.proxy) + context.network_provider.proxy) if staking_token_nonce == 0 and staking_token_amount == 0: print_test_step_fail(f"SKIPPED: No tokens found!") @@ -367,7 +367,7 @@ def generateExitFarmEvent(context: Context, userAccount: Account, farmContract: tx_hash = "" try: farmTkNonce, farmTkAmount, farmTkAttr = get_token_details_for_address(farmContract.farmToken, - userAccount.address, context.proxy) + userAccount.address, context.network_provider.proxy) if farmTkNonce == 0: print(f"Skipped exit farm event. No token retrieved.") return @@ -383,13 +383,13 @@ def generateExitFarmEvent(context: Context, userAccount: Account, farmContract: # pre-event logging event_log = FarmEventResultLogData() event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) - event_log.set_pre_event_data(context.proxy) + event_log.set_pre_event_data(context.network_provider.proxy) tx_hash = farmContract.exitFarm(context.network_provider, userAccount, event) context.observable.set_event(farmContract, userAccount, event, tx_hash) # post-event logging - event_log.set_post_event_data(tx_hash, context.proxy) + event_log.set_post_event_data(tx_hash, context.network_provider.proxy) context.results_logger.add_event_log(event_log) except Exception as ex: @@ -406,7 +406,7 @@ def generateUnstakeEvent(context: Context, user: Account, staking_contract: Stak stake_token = staking_contract.farm_token stake_token_nonce, stake_token_amount, stake_token_attr = get_token_details_for_address(stake_token, user.address, - context.proxy) + context.network_provider.proxy) if not stake_token_nonce: print_test_step_fail('SKIPPED unstakingEvent: No tokens to unstake!') return @@ -450,7 +450,7 @@ def generateExitMetastakeEvent(context: Context, user: Account, metastake_contra try: metastake_token = metastake_contract.metastake_token metastake_token_nonce, metastake_token_amount, metastake_token_attributes = get_token_details_for_address( - metastake_token, user.address, context.proxy + metastake_token, user.address, context.network_provider.proxy ) if metastake_token_nonce == 0: print_test_step_fail(f"SKIPPED: No tokens found!") @@ -500,13 +500,13 @@ def generateClaimRewardsEvent(context: Context, userAccount: Account, farmContra try: farmTkNonce, farmTkAmount, farmTkAttributes = get_token_details_for_address(farmContract.farmToken, userAccount.address, - context.proxy) + context.network_provider.proxy) if farmTkNonce == 0: print(f"Skipped claim rewards farm event. No token retrieved.") return farmedTkNonce, farmedTkAmount, _ = get_token_details_for_address(farmContract.farmedToken, - userAccount.address, context.proxy) + userAccount.address, context.network_provider.proxy) # set correct token balance in case it has been changed since the init of observers set_token_balance_event = SetTokenBalanceEvent(farmContract.farmedToken, farmedTkAmount, farmedTkNonce) @@ -517,13 +517,13 @@ def generateClaimRewardsEvent(context: Context, userAccount: Account, farmContra # pre-event logging event_log = FarmEventResultLogData() event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) - event_log.set_pre_event_data(context.proxy) + event_log.set_pre_event_data(context.network_provider.proxy) tx_hash = farmContract.claimRewards(context.network_provider, userAccount, event) context.observable.set_event(farmContract, userAccount, event, tx_hash) # post-event logging - event_log.set_post_event_data(tx_hash, context.proxy) + event_log.set_post_event_data(tx_hash, context.network_provider.proxy) context.results_logger.add_event_log(event_log) except Exception as ex: @@ -540,7 +540,7 @@ def generateClaimStakingRewardsEvent(context: Context, user: Account, staking_co stake_token = staking_contract.farm_token stake_token_nonce, stake_token_amount, attributes = get_token_details_for_address(stake_token, user.address, - context.proxy) + context.network_provider.proxy) if not stake_token_nonce: print('SKIPPED claimStakingRewardsEvent: No token retrieved!') return @@ -568,7 +568,7 @@ def generateClaimMetastakeRewardsEvent(context: Context, user: Account, metastak metastake_token_nonce, metastake_token_amount, metastake_token_attributes = get_token_details_for_address( metastake_token, user.address, - context.proxy + context.network_provider.proxy ) if metastake_token_nonce == 0: print_test_step_fail(f"SKIPPED: No tokens found!") @@ -609,7 +609,7 @@ def generateCompoundRewardsEvent(context: Context, userAccount: Account, farmCon tx_hash = "" try: farmTkNonce, farmTkAmount, _ = get_token_details_for_address(farmContract.farmToken, - userAccount.address, context.proxy) + userAccount.address, context.network_provider.proxy) if farmTkNonce == 0: print(f"Skipped compound rewards farm event. No token retrieved.") return @@ -619,12 +619,12 @@ def generateCompoundRewardsEvent(context: Context, userAccount: Account, farmCon # pre-event logging event_log = FarmEventResultLogData() event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) - event_log.set_pre_event_data(context.proxy) + event_log.set_pre_event_data(context.network_provider.proxy) tx_hash = farmContract.compoundRewards(context.network_provider, userAccount, event) # post-event logging - event_log.set_post_event_data(tx_hash, context.proxy) + event_log.set_post_event_data(tx_hash, context.network_provider.proxy) context.results_logger.add_event_log(event_log) except Exception as ex: @@ -645,7 +645,7 @@ def generate_migrate_farm_event(context: Context, userAccount: Account, farmCont try: farmTkNonce, farmTkAmount, _ = get_token_details_for_address(farmContract.farmToken, userAccount.address, - context.proxy) + context.network_provider.proxy) if farmTkNonce == 0: return @@ -654,12 +654,12 @@ def generate_migrate_farm_event(context: Context, userAccount: Account, farmCont # pre-event logging event_log = FarmEventResultLogData() event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) - event_log.set_pre_event_data(context.proxy) + event_log.set_pre_event_data(context.network_provider.proxy) tx_hash = farmContract.migratePosition(context.network_provider, userAccount, event) # post-event logging - event_log.set_post_event_data(tx_hash, context.proxy) + event_log.set_post_event_data(tx_hash, context.network_provider.proxy) context.results_logger.add_event_log(event_log) except Exception as ex: @@ -679,7 +679,7 @@ def generateAddLiquidityProxyEvent(context: Context): nonce = 0 try: - tokens = context.proxy.get_account_tokens(userAccount.address) + tokens = context.network_provider.proxy.get_account_tokens(userAccount.address) prevent_spam_crash_elrond_proxy_go() for token in tokens['esdts'].keys(): @@ -710,7 +710,7 @@ def generateRemoveLiquidityProxyEvent(context: Context): nonce = 0 try: - tokens = context.proxy.get_account_tokens(userAccount.address) + tokens = context.network_provider.proxy.get_account_tokens(userAccount.address) prevent_spam_crash_elrond_proxy_go() for token in tokens['esdts'].keys(): @@ -737,8 +737,8 @@ def generateEnterFarmProxyEvent(context: Context, user_account: Account, farm_co underlying_farm_token = farm_contract.farmToken farming_token = farm_contract.proxyContract.farming_token - farming_tk_nonce, farming_tk_amount, _ = get_token_details_for_address(farming_token, user_account.address, context.proxy) - farm_tk_nonce, farm_tk_amount, _ = get_token_details_for_address(farm_token, user_account.address, context.proxy, underlying_farm_token) + farming_tk_nonce, farming_tk_amount, _ = get_token_details_for_address(farming_token, user_account.address, context.network_provider.proxy) + farm_tk_nonce, farm_tk_amount, _ = get_token_details_for_address(farm_token, user_account.address, context.network_provider.proxy, underlying_farm_token) if farming_tk_nonce == 0: return @@ -771,7 +771,7 @@ def generateExitFarmProxyEvent(context: Context, userAccount: Account, farmContr underlying_token = farmContract.farmToken farm_tk_nonce, farm_tk_amount, _ = get_token_details_for_address(farm_token, userAccount.address, - context.proxy, underlying_token) + context.network_provider.proxy, underlying_token) if farm_tk_nonce == 0: return @@ -800,7 +800,7 @@ def generateClaimRewardsProxyEvent(context: Context, userAccount: Account, farmC underlying_token = farmContract.farmToken farm_tk_nonce, farm_tk_amount, _ = get_token_details_for_address(farm_token, userAccount.address, - context.proxy, underlying_token) + context.network_provider.proxy, underlying_token) if farm_tk_nonce == 0: return @@ -826,7 +826,7 @@ def generateCompoundRewardsProxyEvent(context: Context, userAccount: Account, fa underlying_token = farmContract.farmToken farmTkNonce, farmTkAmount, _ = get_token_details_for_address(farm_token, userAccount.address, - context.proxy, underlying_token) + context.network_provider.proxy, underlying_token) if farmTkNonce == 0: return @@ -852,7 +852,7 @@ def generate_deposit_pd_liquidity_event(context: Context, user_account: Account, random.shuffle(tokens) deposited_token = tokens[0] - _, amount, _ = get_token_details_for_address(deposited_token, user_account.address, context.proxy) + _, amount, _ = get_token_details_for_address(deposited_token, user_account.address, context.network_provider.proxy) amount = random.randrange(amount) event = DepositPDLiquidityEvent(deposited_token, amount) @@ -874,7 +874,7 @@ def generate_random_deposit_pd_liquidity_event(context: Context): def generate_withdraw_pd_liquidity_event(context: Context, user_account: Account, pd_contract: PriceDiscoveryContract): # TODO: find a smarter/more configurable method of choosing which token to use - tokens = get_all_token_nonces_details_for_account(pd_contract.redeem_token, user_account.address, context.proxy) + tokens = get_all_token_nonces_details_for_account(pd_contract.redeem_token, user_account.address, context.network_provider.proxy) if len(tokens) == 0: print_test_step_fail(f"Generate withdraw price discovery liquidity failed! No redeem tokens available.") return @@ -903,7 +903,7 @@ def generate_random_withdraw_pd_liquidity_event(context: Context): def generate_redeem_pd_liquidity_event(context: Context, user_account: Account, pd_contract: PriceDiscoveryContract): # TODO: find a smarter/more configurable method of choosing which token to use and how much - tokens = get_all_token_nonces_details_for_account(pd_contract.redeem_token, user_account.address, context.proxy) + tokens = get_all_token_nonces_details_for_account(pd_contract.redeem_token, user_account.address, context.network_provider.proxy) if len(tokens) == 0: print_test_step_fail(f"Generate redeem price discovery liquidity failed! No redeem tokens available.") return diff --git a/utils/logger.py b/utils/logger.py new file mode 100644 index 0000000..fc331be --- /dev/null +++ b/utils/logger.py @@ -0,0 +1,23 @@ +import logging + + +def get_logger(name: str) -> logging.Logger: + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s : %(message)s") + + # console handler + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.DEBUG) + console_handler.setFormatter(formatter) + + # file handler + file_handler = logging.FileHandler(f"trace.log") + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + + logger.addHandler(console_handler) + logger.addHandler(file_handler) + + return logger diff --git a/utils/utils_chain.py b/utils/utils_chain.py index c13b2e8..98cc3fa 100644 --- a/utils/utils_chain.py +++ b/utils/utils_chain.py @@ -15,6 +15,7 @@ from multiversx_sdk_network_providers import ProxyNetworkProvider from utils import utils_generic +from utils.utils_generic import logger logger = logging.getLogger("accounts") @@ -477,15 +478,25 @@ def string(data): return encoded_data -def print_transaction_hash(hash: str, proxy: str, debug_level=True): - explorer = "" - if proxy == "https://testnet-gateway.multiversx.com": - explorer = "https://testnet-explorer.multiversx.com" - if proxy == "https://devnet-gateway.multiversx.com": - explorer = "https://devnet-explorer.multiversx.com" - if proxy == "https://gateway.multiversx.com": - explorer = "https://explorer.multiversx.com" +def log_explorer(proxy: str, name: str, path: str, details: str): + networks = { + "https://testnet-gateway.multiversx.com": + ("MultiversX Testnet Explorer", "https://testnet-explorer.multiversx.com"), + "https://devnet-gateway.multiversx.com": + ("MultiversX Devnet Explorer", "https://devnet-explorer.multiversx.com"), + "https://gateway.multiversx.com": + ("MultiversX Mainnet Explorer", "https://explorer.multiversx.com"), + } + try: + explorer_name, explorer_url = networks[proxy] + logger.info(f"View this {name} in the {explorer_name}: {explorer_url}/{path}/{details}") + except KeyError: + logger.info(f"No explorer known for {proxy}. {name} raw path: {path}/{details}") - if debug_level: - print(explorer+"/transactions/"+hash) +def log_explorer_contract_address(address: str, proxy_url: str): + log_explorer(proxy_url, "contract address", "accounts", address) + + +def log_explorer_transaction(tx_hash: str, proxy_url: str): + log_explorer(proxy_url, "transaction", "transactions", tx_hash) diff --git a/utils/utils_generic.py b/utils/utils_generic.py index d54e22b..d6d6ace 100644 --- a/utils/utils_generic.py +++ b/utils/utils_generic.py @@ -1,5 +1,4 @@ import json -import logging import os import pathlib import shutil @@ -7,12 +6,16 @@ import sys import tarfile import zipfile +from builtins import function + import toml from enum import Enum from pathlib import Path +from utils.logger import get_logger from typing import Any, List, Union, Optional, cast, IO, Dict -logger = logging.getLogger("utils") + +logger = get_logger(__name__) class ISerializable: @@ -251,27 +254,6 @@ def breakpoint(): debugpy.breakpoint() -def log_explorer(chain, name, path, details): - networks = { - "1": ("Elrond Mainnet Explorer", "https://explorer.elrond.com"), - "T": ("Elrond Testnet Explorer", "https://testnet-explorer.elrond.com"), - "D": ("Elrond Devnet Explorer", "https://devnet-explorer.elrond.com"), - } - try: - explorer_name, explorer_url = networks[chain] - logger.info(f"View this {name} in the {explorer_name}: {explorer_url}/{path}/{details}") - except KeyError: - return - - -def log_explorer_contract_address(chain, address): - log_explorer(chain, "contract address", "accounts", address) - - -def log_explorer_transaction(chain, transaction_hash): - log_explorer(chain, "transaction", "transactions", transaction_hash) - - def get_continue_confirmation(force_continue: bool = False) -> bool: if force_continue: typed = "y" @@ -322,6 +304,11 @@ def print_warning(msg): print_color(msg, PrintColors.WARNING) +def log_unexpected_args(function_purpose: str, args: Any): + logger.error(f"Failed to {function_purpose} due to unexpected number of arguments received!") + logger.debug(f"Unexpected arguments: {args}") + + def print_condition_assert(conditions: Dict[bool, str]): for condition, message in conditions.items(): if condition: diff --git a/utils/utils_tx.py b/utils/utils_tx.py index 5274f59..8ef6492 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -1,19 +1,23 @@ import sys import time import traceback +from pathlib import Path from typing import List, Dict, Any from multiversx_sdk_core import Transaction, TokenPayment, Address +from multiversx_sdk_core.interfaces import ICodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider, ApiNetworkProvider from multiversx_sdk_network_providers.network_config import NetworkConfig from multiversx_sdk_network_providers.tokens import FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork from multiversx_sdk_network_providers.transactions import TransactionOnNetwork from multiversx_sdk_core.transaction_builders import ContractCallBuilder, DefaultTransactionBuildersConfiguration, \ - MultiESDTNFTTransferBuilder -from utils.utils_chain import Account, print_transaction_hash + MultiESDTNFTTransferBuilder, ContractDeploymentBuilder, ContractUpgradeBuilder +from utils.logger import get_logger +from utils.utils_chain import Account, log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_warning, split_to_chunks, get_continue_confirmation TX_CACHE: Dict[str, dict] = {} +logger = get_logger(__name__) class ESDTToken: @@ -177,11 +181,73 @@ def check_for_error_operation(self, tx_hash: str, message: str): return False +def _prep_args_for_addresses(args: List): + # TODO: remove this when the SDK supports bech32 addresses... or refactor the thing entirely + new_args = [] + for item in args: + if type(item) is str and "erd" in item: + item = Address.from_bech32(item, "erd") + new_args.append(item) + return new_args + + +def prepare_deploy_tx(deployer: Account, network_config: NetworkConfig, + gas_limit: int, contract_file: Path, code_metadata: ICodeMetadata, + args: list = None) -> Transaction: + config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) + args = _prep_args_for_addresses(args) + builder = ContractDeploymentBuilder( + config=config, + owner=deployer.address, + gas_limit=gas_limit, + code_metadata=code_metadata, + code=contract_file.read_bytes(), + deploy_arguments=args, + nonce=deployer.nonce + ) + + tx = builder.build() + tx.signature = deployer.sign_transaction(tx) + + logger.debug(f"Deploy arguments: {args}") + logger.debug(f"Transaction: {tx.to_dictionary()}") + logger.debug(f"Transaction data: {tx.data}") + + return tx + + +def prepare_upgrade_tx(deployer: Account, contract_address: Address, network_config: NetworkConfig, + gas_limit: int, contract_file: Path, code_metadata: ICodeMetadata, + args: list = None) -> Transaction: + config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) + args = _prep_args_for_addresses(args) + builder = ContractUpgradeBuilder( + config=config, + contract=contract_address, + owner=deployer.address, + gas_limit=gas_limit, + code_metadata=code_metadata, + code=contract_file.read_bytes(), + upgrade_arguments=args, + nonce=deployer.nonce + ) + + tx = builder.build() + tx.signature = deployer.sign_transaction(tx) + + logger.debug(f"Upgrade arguments: {args}") + logger.debug(f"Transaction: {tx.to_dictionary()}") + logger.debug(f"Transaction data: {tx.data}") + + return tx + + def prepare_contract_call_tx(contract_address: Address, deployer: Account, network_config: NetworkConfig, gas_limit: int, function: str, args: list, value: str = "0") -> Transaction: config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) + args = _prep_args_for_addresses(args) builder = ContractCallBuilder( config=config, contract=contract_address, @@ -204,6 +270,7 @@ def prepare_multiesdtnfttransfer_to_endpoint_call_tx(contract_address: Address, value: str = "0") -> Transaction: config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) payment_tokens = [token.to_token_payment() for token in tokens] + endpoint_args = _prep_args_for_addresses(endpoint_args) builder = ContractCallBuilder( config=config, contract=contract_address, @@ -241,11 +308,23 @@ def prepare_multiesdtnfttransfer_tx(destination: Address, user: Account, return tx +def send_deploy_tx(tx: Transaction, proxy: ProxyNetworkProvider) -> str: + try: + tx_hash = proxy.send_transaction(tx) + log_explorer_transaction(tx_hash, proxy.url) + except Exception as ex: + print_test_step_fail(f"Failed to deploy due to: {ex}") + traceback.print_exception(*sys.exc_info()) + tx_hash = "" + + return tx_hash + + def send_contract_call_tx(tx: Transaction, proxy: ProxyNetworkProvider) -> str: try: tx_hash = proxy.send_transaction(tx) # TODO: check if needed to wait for tx to be processed - print_transaction_hash(tx_hash, proxy.url, True) + log_explorer_transaction(tx_hash, proxy.url) except Exception as ex: print_test_step_fail(f"Failed to send tx due to: {ex}") traceback.print_exception(*sys.exc_info()) @@ -277,8 +356,8 @@ def multi_esdt_endpoint_call(function_purpose: str, proxy: ProxyNetworkProvider, return tx_hash -def multi_esdt_tx(function_purpose: str, proxy: ProxyNetworkProvider, gas: int, - user: Account, dest: Address, args: list): +def multi_esdt_transfer(function_purpose: str, proxy: ProxyNetworkProvider, gas: int, + user: Account, dest: Address, args: list): """ Expected as args: type[ESDTToken...]: tokens list """ @@ -312,6 +391,38 @@ def endpoint_call(function_purpose: str, proxy: ProxyNetworkProvider, gas: int, return tx_hash +def deploy(contract_label: str, proxy: ProxyNetworkProvider, gas: int, + owner: Account, bytecode_path: Path, metadata: ICodeMetadata, args: list) -> (str, str): + print_warning(f"Deploy {contract_label} contract") + network_config = proxy.get_network_config() # TODO: find solution to avoid this call + tx_hash, contract_address = "", "" + + tx = prepare_deploy_tx(owner, network_config, gas, bytecode_path, metadata, args) + tx_hash = send_deploy_tx(tx, proxy) + + if tx_hash: + while not proxy.get_transaction_status(tx_hash).is_executed(): + time.sleep(2) + contract_address = get_deployed_address_from_tx(tx_hash, proxy) + owner.nonce += 1 + + return tx_hash, contract_address + + +def upgrade_call(contract_label: str, proxy: ProxyNetworkProvider, gas: int, + owner: Account, contract: Address, bytecode_path: Path, metadata: ICodeMetadata, + args: list) -> str: + print_warning(f"Upgrade {contract_label} contract") + network_config = proxy.get_network_config() # TODO: find solution to avoid this call + tx_hash = "" + + tx = prepare_upgrade_tx(owner, contract, network_config, gas, bytecode_path, metadata, args) + tx_hash = send_contract_call_tx(tx, proxy) + owner.nonce += 1 if tx_hash != "" else 0 + + return tx_hash + + def get_deployed_address_from_event(tx_result: TransactionOnNetwork) -> str: searched_event_id = "SCDeploy" deploy_event = tx_result.logs.find_first_or_none_event(searched_event_id) @@ -322,6 +433,18 @@ def get_deployed_address_from_event(tx_result: TransactionOnNetwork) -> str: return address +def get_deployed_address_from_tx(tx_hash: str, proxy: ProxyNetworkProvider) -> str: + try: + tx = proxy.get_transaction(tx_hash) + contract_address = get_deployed_address_from_event(tx) + except Exception as ex: + print_test_step_fail(f"Failed to get contract address due to: {ex}") + traceback.print_exception(*sys.exc_info()) + contract_address = "" + + return contract_address + + def broadcast_transactions(transactions: List[Transaction], proxy: ProxyNetworkProvider, chunk_size: int, sleep: int = 0, confirm_yes: bool = False): chunks = list(split_to_chunks(transactions, chunk_size)) From 9f9f7a8bdccb9aea96f08091ff2af13bc6332892 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Wed, 8 Mar 2023 18:58:52 +0200 Subject: [PATCH 05/26] contract ports to mxpy stack and refactors --- config.py | 4 +- contracts/dex_proxy_contract.py | 44 +- contracts/esdt_contract.py | 65 +- contracts/farm_contract.py | 583 ++++++------------ contracts/fees_collector_contract.py | 224 +++---- contracts/locked_asset_contract.py | 207 +++---- contracts/metastaking_contract.py | 289 +++------ contracts/pair_contract.py | 515 +++++----------- contracts/price_discovery_contract.py | 226 ++----- contracts/proxy_deployer_contract.py | 101 ++- contracts/router_contract.py | 236 +++---- contracts/simple_lock_contract.py | 158 ++--- contracts/simple_lock_energy_contract.py | 406 +++++------- contracts/staking_contract.py | 407 ++++-------- contracts/unstaker_contract.py | 80 +-- events/event_generators.py | 10 +- scenarios/scenario_dex_v2_all_in.py | 2 +- scenarios/scenario_fees_collector.py | 2 +- scenarios/scenario_swaps.py | 2 +- scenarios/stress_many_add_remove_liquidity.py | 2 +- utils/utils_chain.py | 5 +- utils/utils_generic.py | 1 - utils/utils_tx.py | 25 +- 23 files changed, 1144 insertions(+), 2450 deletions(-) diff --git a/config.py b/config.py index 35485d9..de5f252 100644 --- a/config.py +++ b/config.py @@ -20,7 +20,7 @@ DEFAULT_MINT_VALUE = 1 # EGLD # TODO: don't go sub-unitary cause headaches occur. just don't be cheap for now... # DEX setup -LOCKED_ASSET_FACTORY_BYTECODE_PATH = Path().home() / "dev" / "dex" / "sc-dex-rs" / "locked-asset" / "factory" / "output" / "factory.wasm" +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" @@ -64,7 +64,7 @@ METASTAKINGS_V2 = "metastakings_v2" FEES_COLLECTORS = "fees_collectors" -DEFAULT_CONFIG_SAVE_PATH = DEFAULT_WORKSPACE.absolute() / "deploy" / "configs-devnet-0712" +DEFAULT_CONFIG_SAVE_PATH = DEFAULT_WORKSPACE.absolute() / "deploy" / "configs-testruns" DEPLOY_STRUCTURE_JSON = DEFAULT_CONFIG_SAVE_PATH / "deploy_structure.json" CROSS_SHARD_DELAY = 60 diff --git a/contracts/dex_proxy_contract.py b/contracts/dex_proxy_contract.py index e65dc66..86150a6 100644 --- a/contracts/dex_proxy_contract.py +++ b/contracts/dex_proxy_contract.py @@ -1,20 +1,15 @@ -import logging -import random -import sys -import traceback - import config from contracts.contract_identities import DEXContractInterface, ProxyContractVersion from contracts.farm_contract import FarmContract from contracts.pair_contract import PairContract from utils.logger import get_logger -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders, deploy, upgrade_call, \ +from utils.utils_tx import deploy, upgrade_call, \ endpoint_call, multi_esdt_endpoint_call, ESDTToken -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning, \ +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, \ log_unexpected_args -from utils.utils_chain import Account, WrapperAddress as Address, log_explorer_transaction +from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_network_providers import ProxyNetworkProvider -from multiversx_sdk_core import Transaction, CodeMetadata +from multiversx_sdk_core import CodeMetadata logger = get_logger(__name__) @@ -198,7 +193,7 @@ def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, byteco type[list]: locked asset factories contract addresses; care for the correct order based on locked tokens list """ function_purpose = f"deploy {type(self).__name__} contract" - logging.info(function_purpose) + logger.info(function_purpose) if len(args) != 1: log_unexpected_args(function_purpose, args) @@ -226,7 +221,7 @@ def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytec type[list]: locked asset factories contract addresses; care for the correct order based on locked tokens list """ function_purpose = f"upgrade {type(self).__name__} contract" - logging.info(function_purpose) + logger.info(function_purpose) if len(args) != 1 and not no_init: log_unexpected_args(function_purpose, args) @@ -258,6 +253,7 @@ def register_proxy_farm_token(self, deployer: Account, proxy: ProxyNetworkProvid type[str]: token ticker """ function_purpose = "Register proxy farm token" + logger.info(function_purpose) tx_hash = "" if len(args) != 2: @@ -270,8 +266,8 @@ def register_proxy_farm_token(self, deployer: Account, proxy: ProxyNetworkProvid args[1], "18" ] - return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), "registerProxyFarm", - sc_args, value=config.DEFAULT_ISSUE_TOKEN_PRICE) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "registerProxyFarm", sc_args, + value=config.DEFAULT_ISSUE_TOKEN_PRICE) def register_proxy_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """Expecting as args: @@ -279,6 +275,7 @@ def register_proxy_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider type[str]: token ticker """ function_purpose = "Register proxy lp token" + logger.info(function_purpose) tx_hash = "" if len(args) != 2: @@ -291,8 +288,8 @@ def register_proxy_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider args[1], "18" ] - return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), "registerProxyPair", - sc_args, value=config.DEFAULT_ISSUE_TOKEN_PRICE) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "registerProxyPair", sc_args, + value=config.DEFAULT_ISSUE_TOKEN_PRICE) """Expecting as args: type[str]: token id @@ -300,6 +297,7 @@ def register_proxy_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider """ def set_local_roles_proxy_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): function_purpose = "Set local roles for proxy token" + logger.info(function_purpose) if len(args) != 2: log_unexpected_args(function_purpose, args) @@ -312,41 +310,41 @@ def set_local_roles_proxy_token(self, deployer: Account, proxy: ProxyNetworkProv 3, 4, 5 ] - return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), "setLocalRoles", - sc_args) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLocalRoles", sc_args) def set_energy_factory_address(self, deployer: Account, proxy: ProxyNetworkProvider, energy_address: str): function_purpose = "Set energy factory address in proxy contract" + logger.info(function_purpose) if energy_address == "": log_unexpected_args(function_purpose, energy_address) return "" gas_limit = 50000000 - return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), - "setEnergyFactoryAddress", [energy_address]) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setEnergyFactoryAddress", + [energy_address]) def add_pair_to_intermediate(self, deployer: Account, proxy: ProxyNetworkProvider, pair_address: str): function_purpose = "Add pair to intermediate in proxy contract" + logger.info(function_purpose) if pair_address == "": log_unexpected_args(function_purpose, pair_address) return "" gas_limit = 50000000 - return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), - "addPairToIntermediate", [pair_address]) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "addPairToIntermediate", [pair_address]) def add_farm_to_intermediate(self, deployer: Account, proxy: ProxyNetworkProvider, farm_address: str): function_purpose = "Add farm to intermediate in proxy contract" + logger.info(function_purpose) if farm_address == "": log_unexpected_args(function_purpose, farm_address) return "" gas_limit = 50000000 - return endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), - "addFarmToIntermediate", [farm_address]) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "addFarmToIntermediate", [farm_address]) def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): pass diff --git a/contracts/esdt_contract.py b/contracts/esdt_contract.py index a868977..4d29811 100644 --- a/contracts/esdt_contract.py +++ b/contracts/esdt_contract.py @@ -1,9 +1,12 @@ from enum import Enum -from utils.utils_tx import send_contract_call_tx, prepare_contract_call_tx -from utils.utils_generic import print_test_step_fail, print_warning -from erdpy.accounts import Account, Address -from erdpy.proxy import ElrondProxy +from utils.logger import get_logger +from utils.utils_tx import endpoint_call +from utils.utils_generic import log_unexpected_args +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_network_providers import ProxyNetworkProvider + +logger = get_logger(__name__) class ESDTRoles(Enum): @@ -15,53 +18,45 @@ class ESDTContract: def __init__(self, esdt_address): self.address = esdt_address - """ Expected as args: - type[str]: token_id - type[str]: address to assign role to - type[str]: role name - """ - def set_special_role_token(self, token_owner: Account, proxy: ElrondProxy, args: list): + def set_special_role_token(self, token_owner: Account, proxy: ProxyNetworkProvider, args: list): + """ Expected as args: + type[str]: token_id + type[str]: address to assign role to + type[str]: role name + """ + function_purpose = "set special role for token" + logger.info(function_purpose) if len(args) != 3: - print_test_step_fail(f"FAIL: Failed to add trusted swap pair. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" token_id = args[0] address = args[1] role = args[2] - print_warning(f"Set ESDT role {role} for {token_id} on address {address}") + logger.info(f"Setting ESDT role {role} for {token_id} on address {address}") - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [ - "0x" + token_id.encode("ascii").hex(), - "0x" + Address(address).hex(), - "0x" + role.encode("ascii").hex() + token_id, + Address(address), + role ] - tx = prepare_contract_call_tx(Address(self.address), token_owner, network_config, gas_limit, - "setSpecialRole", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - token_owner.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, token_owner, Address(self.address), "setSpecialRole", sc_args) - def unset_special_role_token(self, token_owner: Account, proxy: ElrondProxy, args: list): + def unset_special_role_token(self, token_owner: Account, proxy: ProxyNetworkProvider, args: list): + function_purpose = "unset special role for token" + logger.info(function_purpose) if len(args) != 3: - print_test_step_fail(f"FAIL: Failed to add trusted swap pair. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" token_id = args[0] address = args[1] role = args[2] - print_warning(f"Set ESDT role {role} for {token_id} on address {address}") + logger.info(f"Set ESDT role {role} for {token_id} on address {address}") - network_config = proxy.get_network_config() gas_limit = 10000000 sc_args = [ - "0x" + token_id.encode("ascii").hex(), - "0x" + Address(address).hex(), - "0x" + role.encode("ascii").hex() + token_id, + Address(address), + role ] - tx = prepare_contract_call_tx(Address(self.address), token_owner, network_config, gas_limit, - "unSetSpecialRole", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - token_owner.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, token_owner, Address(self.address), "unsetSpecialRole", sc_args) diff --git a/contracts/farm_contract.py b/contracts/farm_contract.py index 9f78e71..0dfcce3 100644 --- a/contracts/farm_contract.py +++ b/contracts/farm_contract.py @@ -1,17 +1,20 @@ import sys import traceback -from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import FarmContractVersion, DEXContractInterface -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders -from erdpy.accounts import Account, Address -from erdpy.contracts import SmartContract, CodeMetadata -from erdpy.proxy import ElrondProxy -from erdpy.transactions import Transaction +from utils.logger import get_logger +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders, ESDTToken, \ + multi_esdt_endpoint_call, deploy, upgrade_call, endpoint_call +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning, \ + log_unexpected_args from events.farm_events import (EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent, - CompoundRewardsFarmEvent, MigratePositionFarmEvent) + CompoundRewardsFarmEvent, MigratePositionFarmEvent) + +logger = get_logger(__name__) class FarmContract(DEXContractInterface): @@ -49,9 +52,10 @@ def has_proxy(self) -> bool: return False def enterFarm(self, network_provider: NetworkProviders, user: Account, event: EnterFarmEvent, lock: int = 0, initial: bool = False) -> str: - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) + # TODO: remove initial parameter by using the event data + function_purpose = "enter farm" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") enterFarmFn = "enterFarm" if lock == 1: @@ -59,257 +63,112 @@ def enterFarm(self, network_provider: NetworkProviders, user: Account, event: En elif lock == 0: enterFarmFn = "enterFarm" - print_warning(f"{enterFarmFn}") + logger.info(f"Calling {enterFarmFn} endpoint...") gas_limit = 50000000 - sc_args = [ - "0x" + Address(self.address).hex(), # contract address - "0x01" if initial else "0x02", # number of tokens sent - "0x" + event.farming_tk.encode("ascii").hex(), # farming token details - "0x" + "0" + f"{event.farming_tk_nonce:x}" if len( - f"{event.farming_tk_nonce:x}") % 2 else "0x" + f"{event.farming_tk_nonce:x}", - "0x" + "0" + f"{event.farming_tk_amount:x}" if len( - f"{event.farming_tk_amount:x}") % 2 else "0x" + f"{event.farming_tk_amount:x}", - ] + tokens = [ESDTToken(event.farming_tk, event.farming_tk_nonce, event.farming_tk_amount)] if not initial: - sc_args.extend([ - "0x" + event.farm_tk.encode("ascii").hex(), # farm token details - "0x" + "0" + f"{event.farm_tk_nonce:x}" if len( - f"{event.farm_tk_nonce:x}") % 2 else "0x" + f"{event.farm_tk_nonce:x}", - "0x" + "0" + f"{event.farm_tk_amount:x}" if len( - f"{event.farm_tk_amount:x}") % 2 else "0x" + f"{event.farm_tk_amount:x}", - ]) - sc_args.extend([ - "0x" + enterFarmFn.encode("ascii").hex(), # enterFarm endpoint name - ]) - tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = user.address.bech32() # MultiESDTNFTTransfer is issued via self transfers - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - - return txHash - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" + tokens.append(ESDTToken(event.farm_tk, event.farm_tk_nonce, event.farm_tk_amount)) - def exitFarm(self, network_provider: NetworkProviders, user: Account, event: ExitFarmEvent) -> str: - print_warning(f"exitFarm") - print(f"Account: {user.address}") + sc_args = [tokens] + + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, user, + Address(self.address), enterFarmFn, sc_args) - contract = SmartContract(address=user.address) + def exitFarm(self, network_provider: NetworkProviders, user: Account, event: ExitFarmEvent) -> str: + function_purpose = f"exit farm" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") gas_limit = 50000000 + tokens = [ESDTToken(self.farmToken, event.nonce, event.amount)] sc_args = [ - "0x" + self.farmToken.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "exitFarm".encode("ascii").hex(), + tokens ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - - return txHash - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" - def claimRewards(self, network_provider: NetworkProviders, user: Account, event: ClaimRewardsFarmEvent) -> str: - print_warning(f"claimRewards") - print(f"Account: {user.address}") + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, user, + Address(self.address), "exitFarm", sc_args) - contract = SmartContract(address=user.address) + def claimRewards(self, network_provider: NetworkProviders, user: Account, event: ClaimRewardsFarmEvent) -> str: + function_purpose = f"claimRewards" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") gas_limit = 50000000 + tokens = [ESDTToken(self.farmToken, event.nonce, event.amount)] sc_args = [ - "0x" + self.farmToken.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "claimRewards".encode("ascii").hex(), + tokens ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - return txHash - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, user, + Address(self.address), "claimRewards", sc_args) def compoundRewards(self, network_provider: NetworkProviders, user: Account, event: CompoundRewardsFarmEvent) -> str: - print_warning(f"compoundRewards") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) + function_purpose = f"compoundRewards" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") gas_limit = 50000000 + tokens = [ESDTToken(self.farmToken, event.nonce, event.amount)] sc_args = [ - "0x" + self.farmToken.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "compoundRewards".encode("ascii").hex(), + tokens ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - return txHash - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, user, + Address(self.address), "compoundRewards", sc_args) def migratePosition(self, network_provider: NetworkProviders, user: Account, event: MigratePositionFarmEvent) -> str: - print_warning(f"migratePosition") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) + function_purpose = f"migratePosition" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") gas_limit = 50000000 + tokens = [ESDTToken(self.farmToken, event.nonce, event.amount)] sc_args = [ - "0x" + self.farmToken.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "migrateToNewFarm".encode("ascii").hex(), - "0x" + user.address.hex(), + tokens, + user.address ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - return txHash - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, user, + Address(self.address), "migratePosition", sc_args) - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): """Expecting as args:percent type[str]: pair contract address type[str]: locked asset factory address (only V14Locked) type[str]: admin address (only V2Boosted) self.version has to be initialized to correctly attempt the deploy for that specific type of farm. """ - print_warning("Deploy farm contract") + function_purpose = f"deploy {type(self).__name__} contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 350000000 - value = 0 address = "" tx_hash = "" if (self.version in [FarmContractVersion.V12, FarmContractVersion.V14Unlocked] and len(args) < 1) or \ (self.version in [FarmContractVersion.V14Locked, FarmContractVersion.V2Boosted] and len(args) != 2): - print_test_step_fail(f"FAIL: Failed to deploy contract version {self.version.name}. " - f"Args list not as expected.") + log_unexpected_args(f"{function_purpose} version {self.version.name}", args) return tx_hash, address arguments = [ - "0x" + self.farmedToken.encode("ascii").hex(), - "0x" + self.farmingToken.encode("ascii").hex(), - "0xE8D4A51000", - "0x" + Address(args[0]).hex() + self.farmedToken, + self.farmingToken, + 1000000000000, + Address(args[0]) ] if self.version == FarmContractVersion.V14Locked: - arguments.insert(2, "0x" + Address(args[1]).hex()) + arguments.insert(2, Address(args[1])) if self.version == FarmContractVersion.V2Boosted: - arguments.append("0x" + deployer.address.hex()) + arguments.append(deployer.address) if args[1]: - arguments.append("0x" + Address(args[1]).hex()) - - print(f"Arguments: {arguments}") + arguments.append(Address(args[1])) - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address + logger.debug(f"Arguments: {arguments}") + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = [], + def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = [], no_init: bool = False): """Expecting as args:percent type[str]: pair contract address @@ -317,13 +176,11 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, type[str]: admin address (only V2Boosted) self.version has to be initialized to correctly attempt the upgrade for that specific type of farm. """ - print_warning("Upgrade farm contract") + function_purpose = f"upgrade {type(self).__name__} contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 350000000 - value = 0 tx_hash = "" if no_init: @@ -331,135 +188,94 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, else: if (self.version in [FarmContractVersion.V12, FarmContractVersion.V14Unlocked] and len(args) < 1) or \ (self.version in [FarmContractVersion.V14Locked, FarmContractVersion.V2Boosted] and len(args) != 2): - print_test_step_fail(f"FAIL: Failed to deploy contract version {self.version.name}. " - f"Args list not as expected.") + log_unexpected_args(f"{function_purpose} version {self.version.name}", args) return tx_hash arguments = [ - "0x" + self.farmedToken.encode("ascii").hex(), - "0x" + self.farmingToken.encode("ascii").hex(), - "0xE8D4A51000", - "0x" + Address(args[0]).hex() + self.farmedToken, + self.farmingToken, + 1000000000000, + Address(args[0]) ] if self.version == FarmContractVersion.V14Locked: - arguments.insert(2, "0x" + Address(args[1]).hex()) + arguments.insert(2, Address(args[1])) if self.version == FarmContractVersion.V2Boosted: - arguments.append("0x" + deployer.address.hex()) + arguments.append(deployer.address) if args[1]: - arguments.append("0x" + Address(args[1]).hex()) - - print(f"Arguments: {arguments}") - - contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) - tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - deployer.nonce += 1 + arguments.append(Address(args[1])) - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash + logger.debug(f"Arguments: {arguments}") + tx_hash = upgrade_call(type(self).__name__, proxy, gas_limit, deployer, Address(self.address), + bytecode_path, metadata, arguments) return tx_hash - def register_farm_token(self, deployer: Account, proxy: ElrondProxy, args: list): + def register_farm_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """Expecting as args:percent type[str]: token display name type[str]: token ticker """ - print_warning("Register farm token") - - network_config = proxy.get_network_config() + function_purpose = "Register farm token" + logger.info(function_purpose) tx_hash = "" if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to register farm token. Args list not as expected.") + log_unexpected_args(function_purpose, args) return tx_hash gas_limit = 100000000 sc_args = [ - "0x" + args[0].encode("ascii").hex(), - "0x" + args[1].encode("ascii").hex(), - "0x12" + args[0], + args[1], + 18 ] - print(f"Arguments: {sc_args}") + logger.debug(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "registerFarmToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "registerFarmToken", sc_args) - def set_local_roles_farm_token(self, deployer: Account, proxy: ElrondProxy): - print_warning("Set local roles for farm token") + def set_local_roles_farm_token(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = "Set local roles for farm token" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLocalRolesFarmToken", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLocalRolesFarmToken", sc_args) - return tx_hash - - def set_rewards_per_block(self, deployer: Account, proxy: ElrondProxy, rewards_amount: int): - print_warning("Set rewards per block in farm") + def set_rewards_per_block(self, deployer: Account, proxy: ProxyNetworkProvider, rewards_amount: int): + function_purpose = "Set rewards per block in farm" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 50000000 sc_args = [ - "0x" + "0" + f"{rewards_amount:x}" if len(f"{rewards_amount:x}") % 2 else "0x" + f"{rewards_amount:x}", + rewards_amount ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setPerBlockRewardAmount", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + logger.debug(f"Arguments: {sc_args}") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setRewardsPerBlock", sc_args) - def set_penalty_percent(self, deployer: Account, proxy: ElrondProxy, percent: int): - print_warning("Set penalty percent in farm") + def set_penalty_percent(self, deployer: Account, proxy: ProxyNetworkProvider, percent: int): + function_purpose = "Set penalty percent in farm" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 20000000 sc_args = [ - "0x" + "0" + f"{percent:x}" if len(f"{percent:x}") % 2 else "0x" + f"{percent:x}", + percent ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "set_penalty_percent", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + logger.debug(f"Arguments: {sc_args}") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setPenaltyPercent", sc_args) - return tx_hash - - def set_minimum_farming_epochs(self, deployer: Account, proxy: ElrondProxy, epochs: int): - print_warning("Set minimum farming epochs in farm") + def set_minimum_farming_epochs(self, deployer: Account, proxy: ProxyNetworkProvider, epochs: int): + function_purpose = "Set minimum farming epochs in farm" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 50000000 sc_args = [ epochs ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "set_minimum_farming_epochs", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + logger.debug(f"Arguments: {sc_args}") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setMinimumFarmingEpochs", sc_args) - def set_boosted_yields_factors(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_boosted_yields_factors(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """Only V2Boosted. Expecting as args: type[int]: max_rewards_factor @@ -468,171 +284,118 @@ def set_boosted_yields_factors(self, deployer: Account, proxy: ElrondProxy, args type[int]: min_energy_amount type[int]: min_farm_amount """ - print_warning("Set boosted yield factors") + function_purpose = "Set boosted yield factors" + logger.info(function_purpose) if len(args) != 5: - print_test_step_fail(f"FAIL: Failed to set boosted yield factors. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - network_config = proxy.get_network_config() gas_limit = 70000000 sc_args = args - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setBoostedYieldsFactors", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + logger.debug(f"Arguments: {sc_args}") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setBoostedYieldsFactors", sc_args) - return tx_hash - - def set_boosted_yields_rewards_percentage(self, deployer: Account, proxy: ElrondProxy, percentage: int): + def set_boosted_yields_rewards_percentage(self, deployer: Account, proxy: ProxyNetworkProvider, percentage: int): """Only V2Boosted. """ - print_warning("Set boosted yield rewards percentage") + function_purpose = "Set boosted yield rewards percentage" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 70000000 sc_args = [percentage] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setBoostedYieldsRewardsPercentage", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + logger.debug(f"Arguments: {sc_args}") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setBoostedYieldsRewardsPercentage", + sc_args) - def set_energy_factory_address(self, deployer: Account, proxy: ElrondProxy, energy_factory_address: str): + def set_energy_factory_address(self, deployer: Account, proxy: ProxyNetworkProvider, energy_factory_address: str): """Only V2Boosted. """ - print_warning("Set energy factory address in farm") + function_purpose = "Set energy factory address in farm" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 70000000 - sc_args = [energy_factory_address] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setEnergyFactoryAddress", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + sc_args = [Address(energy_factory_address)] + logger.debug(f"Arguments: {sc_args}") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setEnergyFactoryAddress", sc_args) - return tx_hash - - def set_locking_address(self, deployer: Account, proxy: ElrondProxy, locking_address: str): + def set_locking_address(self, deployer: Account, proxy: ProxyNetworkProvider, locking_address: str): """Only V2Boosted. """ - print_warning("Set locking sc address in farm") + function_purpose = "Set locking sc address in farm" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 70000000 sc_args = [locking_address] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLockingScAddress", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + logger.debug(f"Arguments: {sc_args}") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLockingAddress", sc_args) - def set_lock_epochs(self, deployer: Account, proxy: ElrondProxy, lock_epochs: int): + def set_lock_epochs(self, deployer: Account, proxy: ProxyNetworkProvider, lock_epochs: int): """Only V2Boosted. """ - print_warning("Set lock epochs in farm") - - network_config = proxy.get_network_config() + function_purpose = "Set lock epochs in farm" + logger.info(function_purpose) + gas_limit = 50000000 sc_args = [lock_epochs] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLockEpochs", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + logger.debug(f"Arguments: {sc_args}") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLockEpochs", sc_args) - def add_contract_to_whitelist(self, deployer: Account, proxy: ElrondProxy, whitelisted_sc_address: str): + def add_contract_to_whitelist(self, deployer: Account, proxy: ProxyNetworkProvider, whitelisted_sc_address: str): """Only V2Boosted. """ - print_warning("Add contract to farm whitelist") - - network_config = proxy.get_network_config() + function_purpose = "Add contract to farm whitelist" + logger.info(function_purpose) + gas_limit = 70000000 sc_args = [whitelisted_sc_address] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "addSCAddressToWhitelist", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + logger.debug(f"Arguments: {sc_args}") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "addSCAddressToWhitelist", sc_args) - return tx_hash - - def set_transfer_role_farm_token(self, deployer: Account, proxy: ElrondProxy, whitelisted_sc_address: str): + def set_transfer_role_farm_token(self, deployer: Account, proxy: ProxyNetworkProvider, whitelisted_sc_address: str): """Only V2Boosted. """ - print_warning("Set transfer role farm token") + function_purpose = "Set transfer role farm token" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 70000000 sc_args = [whitelisted_sc_address] if whitelisted_sc_address else [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setTransferRoleFarmToken", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + logger.debug(f"Arguments: {sc_args}") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setTransferRoleFarmToken", sc_args) - def resume(self, deployer: Account, proxy: ElrondProxy): - print_warning("Resume farm contract") - - network_config = proxy.get_network_config() + def resume(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = "Resume farm contract" + logger.info(function_purpose) + gas_limit = 30000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "resume", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash - - def pause(self, deployer: Account, proxy: ElrondProxy): - print_warning("Pause farm contract") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "resume", sc_args) - network_config = proxy.get_network_config() + def pause(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = "Pause farm contract" + logger.info(function_purpose) + gas_limit = 30000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "pause", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "pause", sc_args) - return tx_hash - - def start_produce_rewards(self, deployer: Account, proxy: ElrondProxy): - print_warning("Start producing rewards in farm contract") - - network_config = proxy.get_network_config() + def start_produce_rewards(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = "Start producing rewards in farm contract" + logger.info(function_purpose) + gas_limit = 10000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "startProduceRewards", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash - - def end_produce_rewards(self, deployer: Account, proxy: ElrondProxy): - print_warning("Stop producing rewards in farm contract") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "startProduceRewards", sc_args) - network_config = proxy.get_network_config() + def end_produce_rewards(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = "Stop producing rewards in farm contract" + logger.info(function_purpose) + gas_limit = 10000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "end_produce_rewards", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "end_produce_rewards", sc_args) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): _ = self.start_produce_rewards(deployer, proxy) _ = self.resume(deployer, proxy) diff --git a/contracts/fees_collector_contract.py b/contracts/fees_collector_contract.py index 52b5564..3095321 100644 --- a/contracts/fees_collector_contract.py +++ b/contracts/fees_collector_contract.py @@ -1,14 +1,16 @@ import sys import traceback -from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx -from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning -from erdpy.accounts import Account, Address -from erdpy.contracts import CodeMetadata, SmartContract -from erdpy.proxy import ElrondProxy +from utils.logger import get_logger +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, deploy, endpoint_call +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning, log_unexpected_args +from utils.utils_chain import Account, WrapperAddress as Address, log_explorer_transaction +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider + + +logger = get_logger(__name__) class FeesCollectorContract(DEXContractInterface): @@ -25,240 +27,152 @@ def get_config_dict(self) -> dict: def load_config_dict(cls, config_dict: dict): return FeesCollectorContract(address=config_dict['address']) - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = None): + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = None): """ Expected as args: type[str]: locked token type[str]: energy factory address """ - print_warning("Deploy fees collector contract") + function_purpose = f"deploy {type(self).__name__} contract" + logger.info(function_purpose) if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to deploy. Args list not as expected.") - return "" + log_unexpected_args(function_purpose, args) + return "", "" - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 200000000 - value = 0 - address = "" - tx_hash = "" arguments = [ - f"str:{args[0]}", - args[1] + args[0], + Address(args[1]) ] - print(f"Arguments: {arguments}") - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - def add_known_contracts(self, deployer: Account, proxy: ElrondProxy, args: list): + def add_known_contracts(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str..]: addresses """ - print_warning("Add known contract in fees collector contract") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Add known contract in fees collector contract" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to add know contracts. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 10000000 sc_args = args print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "addKnownContracts", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "addKnownContracts", sc_args) - def add_known_tokens(self, deployer: Account, proxy: ElrondProxy, args: list): + def add_known_tokens(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str..]: tokens """ - print_warning("Add known tokens in fees collector contract") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Add known tokens in fees collector contract" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to add know tokens. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 10000000 - sc_args = args - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "addKnownTokens", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "addKnownTokens", args) - def remove_known_contracts(self, deployer: Account, proxy: ElrondProxy, args: list): + def remove_known_contracts(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str..]: addresses """ - print_warning("Remove known contract in fees collector contract") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Remove known contract in fees collector contract" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to remove know contracts. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 10000000 sc_args = args - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "removeKnownContracts", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "removeKnownContracts", sc_args) - return tx_hash - - def remove_known_tokens(self, deployer: Account, proxy: ElrondProxy, args: list): + def remove_known_tokens(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str..]: tokens """ - print_warning("Remove known tokens in fees collector contract") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Remove known tokens in fees collector contract" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to remove know tokens. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 10000000 sc_args = args - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "removeKnownTokens", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "removeKnownTokens", sc_args) - def set_energy_factory_address(self, deployer: Account, proxy: ElrondProxy, factory_address: str): + def set_energy_factory_address(self, deployer: Account, proxy: ProxyNetworkProvider, factory_address: str): """ Expected as args: type[str]: energy factory address """ - print_warning("Set Energy factory address in fees collector contract") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Set Energy factory address in fees collector contract" + logger.info(function_purpose) if not factory_address: - print_test_step_fail(f"FAIL: Failed to set Energy factory address. Arg not as expected.") - return tx_hash + log_unexpected_args(function_purpose, factory_address) + return "" gas_limit = 30000000 sc_args = [ - "0x" + Address(factory_address).hex() + Address(factory_address) ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setEnergyFactoryAddress", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setEnergyFactoryAddress", sc_args) - return tx_hash - - def set_locking_address(self, deployer: Account, proxy: ElrondProxy, locking_address: str): + def set_locking_address(self, deployer: Account, proxy: ProxyNetworkProvider, locking_address: str): """ Expected as args: type[str]: locking address """ - print_warning("Set locking address in fees collector") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Set locking address in fees collector" + logger.info(function_purpose) if not locking_address: - print_test_step_fail(f"FAIL: Failed to set set locking address. Arg not as expected.") - return tx_hash + log_unexpected_args(function_purpose, locking_address) + return "" gas_limit = 30000000 sc_args = [ - "0x" + Address(locking_address).hex() + Address(locking_address) ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLockingScAddress", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLockingScAddress", sc_args) - return tx_hash - - def set_lock_epochs(self, deployer: Account, proxy: ElrondProxy, lock_epochs: int): - print_warning("Set lock epochs in fees collector") - - network_config = proxy.get_network_config() - tx_hash = "" + def set_lock_epochs(self, deployer: Account, proxy: ProxyNetworkProvider, lock_epochs: int): + function_purpose = f"Set lock epochs in fees collector" + logger.info(function_purpose) gas_limit = 30000000 sc_args = [ lock_epochs ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLockEpochs", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash - - def set_locked_tokens_per_block(self, deployer: Account, proxy: ElrondProxy, locked_tokens_per_block: int): - print_warning("Set locked tokens per block") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLockEpochs", sc_args) - network_config = proxy.get_network_config() - tx_hash = "" + def set_locked_tokens_per_block(self, deployer: Account, proxy: ProxyNetworkProvider, locked_tokens_per_block: int): + function_purpose = f"Set locked tokens per block" + logger.info(function_purpose) gas_limit = 30000000 sc_args = [ locked_tokens_per_block ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLockedTokensPerBlock", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLockedTokensPerBlock", sc_args) - def claim_rewards(self, user: Account, proxy: ElrondProxy): - print_warning("Claim rewards from fees collector") - - network_config = proxy.get_network_config() + def claim_rewards(self, user: Account, proxy: ProxyNetworkProvider): + function_purpose = f"Claim rewards from fees collector" + logger.info(function_purpose) gas_limit = 20000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), user, network_config, gas_limit, - "claimRewards", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - user.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, user, Address(self.address), "claimRewards", sc_args) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = None): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = None): pass def print_contract_info(self): diff --git a/contracts/locked_asset_contract.py b/contracts/locked_asset_contract.py index 1425518..09a5d9b 100644 --- a/contracts/locked_asset_contract.py +++ b/contracts/locked_asset_contract.py @@ -1,14 +1,15 @@ import sys import traceback -from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx -from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning -from erdpy.accounts import Account, Address -from erdpy.contracts import CodeMetadata, SmartContract -from erdpy.proxy import ElrondProxy +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, deploy, upgrade_call, endpoint_call +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, log_unexpected_args +from utils.utils_chain import Account, WrapperAddress as Address, log_explorer_transaction +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider +from utils.logger import get_logger + +logger = get_logger(__name__) class LockedAssetContract(DEXContractInterface): @@ -31,194 +32,130 @@ def load_config_dict(cls, config_dict: dict): unlocked_asset=config_dict['unlocked_asset'], locked_asset=config_dict['locked_asset']) - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): - print_warning("Deploy locked asset contract") + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = []): + function_purpose = f"deploy {type(self).__name__} contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 200000000 - value = 0 - address = "" - tx_hash = "" arguments = [ - "0x" + self.unlocked_asset.encode("ascii").hex(), - "0x000000000000016D11", - "0x000000000000018B11", - "0x00000000000001A911", - "0x00000000000001C711", - "0x00000000000001E510", - "0x000000000000020310", + self.unlocked_asset, + 93457, + 101137, + 108817, + 116497, + 124176, + 131856, ] - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address - + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, + def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = [], no_init: bool = False): - print_warning("Upgrade locked asset contract") + function_purpose = "Upgrade locked asset contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 200000000 - value = 0 tx_hash = "" if no_init: arguments = [] else: arguments = [ - "0x" + self.unlocked_asset.encode("ascii").hex(), - "0x000000000000016D11", - "0x000000000000018B11", - "0x00000000000001A911", - "0x00000000000001C711", - "0x00000000000001E510", - "0x000000000000020310", + self.unlocked_asset, + 93457, + 101137, + 108817, + 116497, + 124176, + 131856, ] - contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) - tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send upgrade transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash + return upgrade_call(type(self).__name__, proxy, gas_limit, deployer, Address(self.address), + bytecode_path, metadata, arguments) - return tx_hash + def set_new_factory_address(self, deployer: Account, proxy: ProxyNetworkProvider, contract_address: str): + function_purpose = "Set new factory address" + logger.info(function_purpose) - def set_new_factory_address(self, deployer: Account, proxy: ElrondProxy, contract_address: str): - print_warning("Set new factory address") - - network_config = proxy.get_network_config() gas_limit = 50000000 sc_args = [ - "0x" + Address(contract_address).hex() + Address(contract_address) ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setNewFactoryAddress", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setNewFactoryAddress", sc_args) - def register_locked_asset_token(self, deployer: Account, proxy: ElrondProxy, args: list): + def register_locked_asset_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: token name type[str]: token ticker """ - print_warning("Register locked asset token") + function_purpose = "Register locked asset token" + logger.info(function_purpose) - network_config = proxy.get_network_config() tx_hash = "" if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to register locked token. Args list not as expected.") + log_unexpected_args(function_purpose, args) return tx_hash gas_limit = 100000000 sc_args = [ - "0x" + args[0].encode("ascii").hex(), - "0x" + args[1].encode("ascii").hex(), - "0x12" + args[0], + args[1], + 18 ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "registerLockedAssetToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), + "registerLockedAssetToken", sc_args, value="50000000000000000") - def set_locked_asset_local_roles(self, deployer: Account, proxy: ElrondProxy, contract: str): - print_warning("Set locked asset token local roles") + def set_locked_asset_local_roles(self, deployer: Account, proxy: ProxyNetworkProvider, contract: str): + function_purpose = "Set locked asset token local roles" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [ - "0x" + Address(contract).hex(), - "0x03", - "0x04", - "0x05", + Address(contract), + 3, 4, 5, ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLocalRolesLockedAssetToken", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), + "setLocalRolesLockedAssetToken", sc_args) - return tx_hash + def whitelist_contract(self, deployer: Account, proxy: ProxyNetworkProvider, contract_to_whitelist: str): + function_purpose = "Whitelist contract in locked asset contract" + logger.info(function_purpose) - def whitelist_contract(self, deployer: Account, proxy: ElrondProxy, contract_to_whitelist: str): - print_warning("Whitelist contract in locked asset contract") - - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [ "0x" + Address(contract_to_whitelist).hex() ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "whitelist", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), + "whitelist", sc_args) - def set_transfer_role_for_contract(self, deployer: Account, proxy: ElrondProxy, contract_to_whitelist: str): - print_warning("Set transfer role for contract") + def set_transfer_role_for_contract(self, deployer: Account, proxy: ProxyNetworkProvider, contract_to_whitelist: str): + function_purpose = "Set transfer role for contract" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [ - "0x" + Address(contract_to_whitelist).hex() + Address(contract_to_whitelist) ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setTransferRoleForAddress", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), + "setTransferRoleForAddress", sc_args) - def set_burn_role_for_contract(self, deployer: Account, proxy: ElrondProxy, contract_to_whitelist: str): - print_warning("Set burn role for contract") + def set_burn_role_for_contract(self, deployer: Account, proxy: ProxyNetworkProvider, contract_to_whitelist: str): + function_purpose = "Set burn role for contract" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [ - "0x" + Address(contract_to_whitelist).hex() + Address(contract_to_whitelist) ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setBurnRoleForAddress", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), + "setBurnRoleForAddress", sc_args) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): pass def print_contract_info(self): diff --git a/contracts/metastaking_contract.py b/contracts/metastaking_contract.py index 782a8c0..03f7bbe 100644 --- a/contracts/metastaking_contract.py +++ b/contracts/metastaking_contract.py @@ -1,18 +1,20 @@ import sys import traceback +from operator import ne -from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface, MetaStakingContractIdentity -from events.metastake_events import (EnterMetastakeEvent, - ExitMetastakeEvent, - ClaimRewardsMetastakeEvent) -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders -from erdpy.accounts import Account, Address -from erdpy.contracts import SmartContract, CodeMetadata -from erdpy.proxy import ElrondProxy -from erdpy.transactions import Transaction +from events.metastake_events import (EnterMetastakeEvent, ExitMetastakeEvent, ClaimRewardsMetastakeEvent) +from utils.logger import get_logger +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders, deploy, upgrade_call, \ + endpoint_call, ESDTToken, multi_esdt_endpoint_call +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning, \ + log_unexpected_args + +logger = get_logger(__name__) class MetaStakingContract(DEXContractInterface): @@ -55,130 +57,79 @@ def load_config_dict(cls, config_dict: dict): farm_address=config_dict['farm_address'], stake_address=config_dict['stake_address']) - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): - print_warning("Deploy metastaking contract") + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = []): + function_purpose = f"Deploy metastaking contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 200000000 - value = 0 - address = "" - tx_hash = "" arguments = [ - "0x" + Address(self.farm_address).hex(), - "0x" + Address(self.stake_address).hex(), - "0x" + Address(self.lp_address).hex(), - "0x" + self.staking_token.encode("ascii").hex(), - "0x" + self.farm_token.encode("ascii").hex(), - "0x" + self.stake_token.encode("ascii").hex(), - "0x" + self.lp_token.encode("ascii").hex(), + Address(self.farm_address), + Address(self.stake_address), + Address(self.lp_address), + self.staking_token, + self.farm_token, + self.stake_token, + self.lp_token, ] - print(f"Arguments: {arguments}") - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, + def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = [], no_init: bool = False): - print_warning("Upgrade metastaking contract") + function_purpose = f"Upgrade metastaking contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 200000000 - value = 0 - tx_hash = "" if no_init: arguments = [] else: arguments = [ - "0x" + Address(self.farm_address).hex(), - "0x" + Address(self.stake_address).hex(), - "0x" + Address(self.lp_address).hex(), - "0x" + self.staking_token.encode("ascii").hex(), - "0x" + self.farm_token.encode("ascii").hex(), - "0x" + self.stake_token.encode("ascii").hex(), - "0x" + self.lp_token.encode("ascii").hex(), + Address(self.farm_address), + Address(self.stake_address), + Address(self.lp_address), + self.staking_token, + self.farm_token, + self.stake_token, + self.lp_token, ] - print(f"Arguments: {arguments}") - contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) - tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) + return upgrade_call(type(self).__name__, proxy, gas_limit, deployer, Address(self.address), + bytecode_path, metadata, arguments) - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send upgrade transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash - - return tx_hash - - def register_dual_yield_token(self, deployer: Account, proxy: ElrondProxy, args: list): + def register_dual_yield_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: token display name type[str]: token ticker """ - print_warning("Register metastaking token") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Register metastaking token" + logger.info(function_purpose) if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to register metastake token. Args not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) gas_limit = 100000000 sc_args = [ - "0x" + args[0].encode("ascii").hex(), - "0x" + args[1].encode("ascii").hex(), - "0x12" + args[0], + args[1], + 18 ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "registerDualYieldToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "registerDualYieldToken", sc_args, + value="50000000000000000") - def set_local_roles_dual_yield_token(self, deployer: Account, proxy: ElrondProxy): - print_warning("Set local roles for metastake token") + def set_local_roles_dual_yield_token(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = f"Set local roles for metastake token" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLocalRolesDualYieldToken", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.metastake_token), + "setLocalRolesDualYieldToken", sc_args) - return tx_hash - - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): pass def print_contract_info(self): @@ -190,131 +141,49 @@ def print_contract_info(self): def enter_metastake(self, network_provider: NetworkProviders, user: Account, event: EnterMetastakeEvent, initial: bool = False) -> str: - print_warning('enterMetastaking') - print(f"Account: {user.address}") + # TODO: remove initial parameter by using the event data + function_purpose = f"enterMetastaking" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") - contract = SmartContract(address=user.address) metastake_fn = 'stakeFarmTokens' gas_limit = 50000000 - sc_args = [ - '0x' + Address(self.address).hex(), # contract address - '0x01' if initial else '0x02', # number of tokens sent - '0x' + event.metastaking_tk.encode('ascii').hex(), # farming token - '0x' + '0' + f'{event.metastaking_tk_nonce:x}' if len(f'{event.metastaking_tk_nonce:x}') % 2 else - '0x' + f'{event.metastaking_tk_nonce:x}', - '0x' + '0' + f'{event.metastaking_tk_amount:x}' if len(f'{event.metastaking_tk_amount:x}') % 2 else - '0x' + f'{event.metastaking_tk_amount:x}' - ] - + tokens = [ESDTToken(event.metastaking_tk, event.metastaking_tk_nonce, event.metastaking_tk_amount)] if not initial: - sc_args.extend([ - '0x' + event.metastake_tk.encode('ascii').hex(), # farming token - '0x' + '0' + f'{event.metastake_tk_nonce:x}' if len(f'{event.metastake_tk_nonce:x}') % 2 else - '0x' + f'{event.metastake_tk_nonce:x}', - '0x' + '0' + f'{event.metastake_tk_amount:x}' if len(f'{event.metastake_tk_amount:x}') % 2 else - '0x' + f'{event.metastake_tk_amount:x}' - ]) - - sc_args.extend(['0x' + metastake_fn.encode('ascii').hex()]) # endpoint name - tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = user.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - - try: - tx_hash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(tx_hash, network_provider.proxy.url) - user.nonce += 1 - return tx_hash - except Exception as ex: - print(ex) - return '' + tokens.append(ESDTToken(event.metastake_tk, event.metastake_tk_nonce, event.metastake_tk_amount)) + + sc_args = [tokens] + + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, + user, Address(self.address), metastake_fn, sc_args) def exit_metastake(self, network_provider: NetworkProviders, user: Account, event: ExitMetastakeEvent): - print_warning('exitMetastaking') - print('Account: ', user.address) + function_purpose = f"exitMetastaking" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") - contract = SmartContract(address=user.address) gas_limit = 50000000 exit_metastake_fn = 'unstakeFarmTokens' - sc_args = [ - '0x' + self.metastake_token.encode('ascii').hex(), - '0x' + '0' + f'{event.nonce:x}' if len(f'{event.nonce:x}') % 2 else '0x' + f'{event.nonce:x}', - '0x' + '0' + f'{event.amount:x}' if len(f'{event.amount:x}') % 2 else '0x' + f'{event.amount:x}', - '0x' + Address(self.address).hex(), - '0x' + exit_metastake_fn.encode('ascii').hex(), - '0x01', # first token slippage - '0x01' # second token slippage - ] + tokens = [ESDTToken(self.metastake_token, event.nonce, event.amount)] + sc_args = [tokens, + 1, # first token slippage + 1 # second token slippage + ] - tx_data = contract.prepare_execute_transaction_data('ESDTNFTTransfer', sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = user.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - - try: - tx_hash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(tx_hash, network_provider.proxy.url) - user.nonce += 1 - - return tx_hash - except Exception as ex: - print(ex) - return '' + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, + user, Address(self.address), exit_metastake_fn, sc_args) def claim_rewards_metastaking(self, network_provider: NetworkProviders, user: Account, event: ClaimRewardsMetastakeEvent): - print_warning('claimDualYield') - print('Account: ', user.address) + function_purpose = f"claimDualYield" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") - contract = SmartContract(address=user.address) gas_limit = 50000000 claim_fn = 'claimDualYield' - sc_args = [ - "0x" + self.metastake_token.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + claim_fn.encode('ascii').hex() - ] + tokens = [ESDTToken(self.metastake_token, event.nonce, event.amount)] - tx_data = contract.prepare_execute_transaction_data('ESDTNFTTransfer', sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = user.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - - try: - tx_hash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(tx_hash, network_provider.proxy.url) - user.nonce += 1 - - return tx_hash - except Exception as ex: - print(ex) - return '' + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, + user, Address(self.address), claim_fn, tokens) diff --git a/contracts/pair_contract.py b/contracts/pair_contract.py index 51b15d6..d1ebdc8 100644 --- a/contracts/pair_contract.py +++ b/contracts/pair_contract.py @@ -1,19 +1,16 @@ import sys import traceback -from arrows.stress.contracts.contract import load_code_as_hex -from contracts.contract_identities import ( - DEXContractInterface, PairContractVersion) -from utils.utils_tx import (NetworkProviders, - prepare_contract_call_tx, - send_contract_call_tx) -from utils.utils_chain import (dec_to_padded_hex, log_explorer_transaction, - string_to_hex) -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning -from erdpy.accounts import Account, Address -from erdpy.contracts import CodeMetadata, SmartContract -from erdpy.proxy import ElrondProxy -from erdpy.transactions import Transaction +from contracts.contract_identities import (DEXContractInterface, PairContractVersion) +from utils.logger import get_logger +from utils.utils_tx import NetworkProviders, endpoint_call, upgrade_call, deploy, ESDTToken, multi_esdt_endpoint_call +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, log_unexpected_args +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider + + +logger = get_logger(__name__) class SwapFixedInputEvent: @@ -88,195 +85,82 @@ def hasProxy(self) -> bool: return True return False - def swapFixedInput(self, network_provider: NetworkProviders, user: Account, event: SwapFixedInputEvent): - print_warning("swapFixedInput") - print(f"Account: {user.address}") - print(f"{event.amountA} {event.tokenA} for minimum {event.amountBmin} {event.tokenB}") - - contract = SmartContract(address=self.address) + def swap_fixed_input(self, network_provider: NetworkProviders, user: Account, event: SwapFixedInputEvent): + function_purpose = f"swapFixedInput" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") + logger.debug(f"{event.amountA} {event.tokenA} for minimum {event.amountBmin} {event.tokenB}") gas_limit = 50000000 - sc_args = [ - "0x" + event.tokenA.encode("ascii").hex(), - "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", - "0x" + "swapTokensFixedInput".encode("ascii").hex(), - "0x" + event.tokenB.encode("ascii").hex(), - "0x" + "0" + f"{event.amountBmin:x}" if len( - f"{event.amountBmin:x}") % 2 else "0x" + f"{event.amountBmin:x}", - ] - tx_data = contract.prepare_execute_transaction_data("ESDTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - - return txHash - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - - def swapFixedOutput(self, network_provider: NetworkProviders, user: Account, event: SwapFixedOutputEvent): - print_warning("swapFixedOutput") - print(f"Account: {user.address}") - - contract = SmartContract(address=self.address) + + tokens = [ESDTToken(event.tokenA, 0, event.amountA)] + sc_args = [tokens, + event.tokenB, + event.amountBmin] + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, + user, Address(self.address), "swapTokensFixedInput", sc_args) + + def swap_fixed_output(self, network_provider: NetworkProviders, user: Account, event: SwapFixedOutputEvent): + function_purpose = f"swap tokens fixed output" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") + logger.debug(f"Maximum {event.amountAmax} {event.tokenA} for {event.amountB} {event.tokenB}") gas_limit = 50000000 - sc_args = [ - "0x" + event.tokenA.encode("ascii").hex(), - "0x" + "0" + f"{event.amountAmax:x}" if len( - f"{event.amountAmax:x}") % 2 else "0x" + f"{event.amountAmax:x}", - "0x" + "swapTokensFixedOutput".encode("ascii").hex(), - "0x" + event.tokenB.encode("ascii").hex(), - "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", - ] - tx_data = contract.prepare_execute_transaction_data("ESDTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - - return txHash - except Exception as ex: - ex = ex - - def addLiquidity(self, network_provider: NetworkProviders, user: Account, event: AddLiquidityEvent): - print_warning("addLiquidity") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) - sc_args = [ - "0x" + Address(self.address).hex(), - "0x02", - "0x" + string_to_hex(event.tokenA), - "0x00", - "0x" + dec_to_padded_hex(event.amountA), - "0x" + string_to_hex(event.tokenB), - "0x00", - "0x" + dec_to_padded_hex(event.amountB), - "0x" + string_to_hex("addLiquidity"), - "0x" + dec_to_padded_hex(event.amountAmin), - "0x" + dec_to_padded_hex(event.amountBmin) - ] + tokens = [ESDTToken(event.tokenA, 0, event.amountAmax)] + sc_args = [tokens, + event.tokenB, + event.amountB] + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, + user, Address(self.address), "swapTokensFixedOutput", sc_args) - tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) - gas_limit = 20000000 - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - - return txHash - except Exception as ex: - print(f'Exception encountered: {ex}') - - def addInitialLiquidity(self, network_provider: NetworkProviders, user: Account, event: AddLiquidityEvent): - print_warning("addInitialLiquidity") - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) + def add_liquidity(self, network_provider: NetworkProviders, user: Account, event: AddLiquidityEvent): + function_purpose = f"addLiquidity" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") + tokens = [ESDTToken(event.tokenA, 0, event.amountA), + ESDTToken(event.tokenB, 0, event.amountB)] sc_args = [ - "0x" + Address(self.address).hex(), - "0x02", - "0x" + string_to_hex(event.tokenA), - "0x00", - "0x" + dec_to_padded_hex(event.amountA), - "0x" + string_to_hex(event.tokenB), - "0x00", - "0x" + dec_to_padded_hex(event.amountB), - "0x" + string_to_hex("addInitialLiquidity") + tokens, + event.amountAmin, + event.amountBmin, ] - tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) - gas_limit = 20000000 - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - - def removeLiquidity(self, network_provider: NetworkProviders, user: Account, event: RemoveLiquidityEvent): - print_warning("removeLiquidity") - print(f"Account: {user.address}") - - contract = SmartContract(address=self.address) + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, 20000000, + user, Address(self.address), "addLiquidity", sc_args) + + def add_initial_liquidity(self, network_provider: NetworkProviders, user: Account, event: AddLiquidityEvent): + function_purpose = f"addInitialLiquidity" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") + + tokens = [ESDTToken(event.tokenA, 0, event.amountA), + ESDTToken(event.tokenB, 0, event.amountB)] + + sc_args = [tokens] + + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, 20000000, + user, Address(self.address), "addInitialLiquidity", sc_args) + + def remove_liquidity(self, network_provider: NetworkProviders, user: Account, event: RemoveLiquidityEvent): + function_purpose = f"remove liquidity" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") gas_limit = 20000000 - sc_args = [ - "0x" + self.lpToken.encode("ascii").hex(), - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + "removeLiquidity".encode("ascii").hex(), - "0x" + "0" + f"{event.amountA:x}" if len(f"{event.amountA:x}") % 2 else "0x" + f"{event.amountA:x}", - "0x" + "0" + f"{event.amountB:x}" if len(f"{event.amountB:x}") % 2 else "0x" + f"{event.amountB:x}", - ] - tx_data = contract.prepare_execute_transaction_data("ESDTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - - return txHash - except Exception as ex: - ex = ex - - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + + tokens = [ESDTToken(self.lpToken, 0, event.amount)] + sc_args = [tokens, + event.amountA, # slippage first token + event.amountB # slippage second token + ] + + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, + user, Address(self.address), "removeLiquidity", sc_args) + + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): """Expecting as args: type[str]: router address type[str]: whitelisted owner address @@ -285,19 +169,16 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, type[any]: special fee type[str..]: admin addresses (v2 required) """ - print_warning("Deploy pair contract") + function_purpose = f"Deploy pair contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=False, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=False, readable=True) + gas_limit = 200000000 - value = 0 - address = "" - tx_hash = "" if len(args) < 5: print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") - return tx_hash, address + return "", "" arguments = [ "0x" + self.firstToken.encode("ascii").hex(), @@ -312,26 +193,10 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, if self.version == PairContractVersion.V2: arguments.extend(args[5:]) - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address - + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, args) return tx_hash, address - def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): """Expecting as args: type[str]: router address type[str]: whitelisted owner address @@ -340,24 +205,22 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, type[any]: special fee type[str..]: admin addresses (v2 required) """ - print_warning("Upgrade pair contract") + function_purpose = f"Upgrade pair contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=False) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=False, readable=True) + gas_limit = 200000000 - value = 0 - tx_hash = "" if len(args) < 5: - print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" arguments = [ - "0x" + self.firstToken.encode("ascii").hex(), - "0x" + self.secondToken.encode("ascii").hex(), - "0x" + Address(args[0]).hex(), - "0x" + Address(args[1]).hex(), + self.firstToken, + self.secondToken, + Address(args[0]), + Address(args[1]), args[3], args[4], args[2] @@ -366,24 +229,10 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, if self.version == PairContractVersion.V2: arguments.extend(args[5:]) - contract = SmartContract(address=Address(self.address), bytecode=bytecode, metadata=metadata) - tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - deployer.nonce += 1 if tx_hash != "" else 0 + return upgrade_call(type(self).__name__, proxy, gas_limit, deployer, Address(self.address), + bytecode_path, metadata, arguments) - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash - - return tx_hash - - def contract_deploy_via_router(self, deployer: Account, proxy: ElrondProxy, router_contract, args: list): + def contract_deploy_via_router(self, deployer: Account, proxy: ProxyNetworkProvider, router_contract, args: list): """ Expected as args: type[str]: initial liquidity adder address type[any]: total fee percentage @@ -395,7 +244,7 @@ def contract_deploy_via_router(self, deployer: Account, proxy: ElrondProxy, rout tx_hash, address = router_contract.pair_contract_deploy(deployer, proxy, pair_args) return tx_hash, address - def contract_upgrade_via_router(self, deployer: Account, proxy: ElrondProxy, router_contract, args: list) -> str: + def contract_upgrade_via_router(self, deployer: Account, proxy: ProxyNetworkProvider, router_contract, args: list) -> str: """ Expected as args: type[int]: total fee percentage type[int]: special fee percentage @@ -406,197 +255,147 @@ def contract_upgrade_via_router(self, deployer: Account, proxy: ElrondProxy, rou tx_hash = router_contract.pair_contract_upgrade(deployer, proxy, pair_args) return tx_hash - def issue_lp_token_via_router(self, deployer: Account, proxy: ElrondProxy, router_contract, args: list): + def issue_lp_token_via_router(self, deployer: Account, proxy: ProxyNetworkProvider, router_contract, args: list): """ Expected as args: type[str]: token display name type[str]: token ticker """ - print_warning("Issue LP token via router") + function_purpose = f"Issue LP token via router" + logger.info(function_purpose) if len(args) < 2: - print_test_step_fail(f"FAIL: Failed to issue lp token. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" tx_hash = router_contract.issue_lp_token(deployer, proxy, [self.address, args[0], args[1]]) return tx_hash - def whitelist_contract(self, deployer: Account, proxy: ElrondProxy, contract_to_whitelist: str): - print_warning("Whitelist contract in pair") + def whitelist_contract(self, deployer: Account, proxy: ProxyNetworkProvider, contract_to_whitelist: str): + function_purpose = f"Whitelist contract in pair" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [ - "0x" + Address(contract_to_whitelist).hex() + Address(contract_to_whitelist) ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "whitelist", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "whitelist", sc_args) - def add_trusted_swap_pair(self, deployer: Account, proxy: ElrondProxy, args: list): + def add_trusted_swap_pair(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: trusted swap pair address type[str]: trusted pair first token identifier type[str]: trusted pair second token identifier """ - print_warning("Whitelist contract in pair") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Whitelist contract in pair" + logger.info(function_purpose) if len(args) != 3: - print_test_step_fail(f"FAIL: Failed to add trusted swap pair. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 100000000 sc_args = [ - "0x" + Address(args[0]).hex(), - "0x" + args[1].encode("ascii").hex(), - "0x" + args[2].encode("ascii").hex() + Address(args[0]), + args[1], + args[2] ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "addTrustedSwapPair", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "addTrustedSwapPair", sc_args) - return tx_hash - - def add_fees_collector(self, deployer: Account, proxy: ElrondProxy, args: list): + def add_fees_collector(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: fees collector address type[str]: fees cut """ - print_warning("Setup fees collector in pair") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Setup fees collector in pair" + logger.info(function_purpose) if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to setup fees collector in pair. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 50000000 sc_args = [ - "0x" + Address(args[0]).hex(), + Address(args[0]), args[1] ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setupFeesCollector", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setupFeesCollector", sc_args) - def set_fees_percents(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_fees_percents(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: total fee percent type[str]: special fee percent """ - print_warning("Set fees in pair contract") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Set fees in pair contract" + logger.info(function_purpose) if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to set fees in pair. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 50000000 sc_args = args - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setFeePercents", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setFeePercents", sc_args) - def set_lp_token_local_roles_via_router(self, deployer: Account, proxy: ElrondProxy, router_contract): - print_warning("Set lp token local roles via router") + def set_lp_token_local_roles_via_router(self, deployer: Account, proxy: ProxyNetworkProvider, router_contract): + function_purpose = f"Set lp token local roles via router" + logger.info(function_purpose) tx_hash = router_contract.set_lp_token_local_roles(deployer, proxy, self.address) return tx_hash - """ Expected as args: - type[str]: address to receive fees - type[str]: expected token - """ - - def set_fee_on_via_router(self, deployer: Account, proxy: ElrondProxy, router_contract, args: list): - print_warning("Set fee on via router") + def set_fee_on_via_router(self, deployer: Account, proxy: ProxyNetworkProvider, router_contract, args: list): + """ Expected as args: + type[str]: address to receive fees + type[str]: expected token + """ + function_purpose = f"Set fee on via router" + logger.info(function_purpose) if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to set fee on via router. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" tx_hash = router_contract.set_fee_on(deployer, proxy, [self.address, args[0], args[1]]) return tx_hash - def set_locking_deadline_epoch(self, deployer: Account, proxy: ElrondProxy, epoch: int): - print_warning("Set locking deadline epoch in pool") - - network_config = proxy.get_network_config() - tx_hash = "" + def set_locking_deadline_epoch(self, deployer: Account, proxy: ProxyNetworkProvider, epoch: int): + function_purpose = f"Set locking deadline epoch in pool" + logger.info(function_purpose) gas_limit = 100000000 sc_args = [ - "0x" + "0" + f"{epoch:x}" if len(f"{epoch:x}") % 2 else "0x" + f"{epoch:x}" + epoch ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLockingDeadlineEpoch", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLockingDeadlineEpoch", sc_args) - def set_unlock_epoch(self, deployer: Account, proxy: ElrondProxy, epoch: int): - print_warning("Set unlock epoch in pool") - - network_config = proxy.get_network_config() - tx_hash = "" + def set_unlock_epoch(self, deployer: Account, proxy: ProxyNetworkProvider, epoch: int): + function_purpose = f"Set unlock epoch in pool" + logger.info(function_purpose) gas_limit = 100000000 sc_args = [ - "0x" + "0" + f"{epoch:x}" if len(f"{epoch:x}") % 2 else "0x" + f"{epoch:x}" + epoch ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setUnlockEpoch", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash - - def set_locking_sc_address(self, deployer: Account, proxy: ElrondProxy, locking_address: str): - print_warning("Set locking contract address in pool") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setUnlockEpoch", sc_args) - network_config = proxy.get_network_config() - tx_hash = "" + def set_locking_sc_address(self, deployer: Account, proxy: ProxyNetworkProvider, locking_address: str): + function_purpose = f"Set locking contract address in pool" + logger.info(function_purpose) gas_limit = 100000000 sc_args = [ - "0x" + Address(locking_address).hex() + Address(locking_address) ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLockingScAddress", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLockingScAddress", sc_args) - return tx_hash - - def resume(self, deployer: Account, proxy: ElrondProxy): - print_warning("Resume swaps in pool") + def resume(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = f"Resume swaps in pool" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 10000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "resume", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "resume", sc_args) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): _ = self.resume(deployer, proxy) def print_contract_info(self): diff --git a/contracts/price_discovery_contract.py b/contracts/price_discovery_contract.py index a37e551..a3913ac 100644 --- a/contracts/price_discovery_contract.py +++ b/contracts/price_discovery_contract.py @@ -1,17 +1,21 @@ import sys import traceback -from arrows.stress.contracts.contract import load_code_as_hex -from contracts.contract_identities import PriceDiscoveryContractIdentity, DEXContractInterface -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders +import config +from contracts.contract_identities import DEXContractInterface +from utils.logger import get_logger +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders, ESDTToken, \ + multi_esdt_endpoint_call, deploy, endpoint_call from events.price_discovery_events import (DepositPDLiquidityEvent, - WithdrawPDLiquidityEvent, RedeemPDLPTokensEvent) + WithdrawPDLiquidityEvent, RedeemPDLPTokensEvent) from utils.utils_chain import log_explorer_transaction from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning -from erdpy.accounts import Account, Address -from erdpy.contracts import SmartContract, CodeMetadata -from erdpy.proxy import ElrondProxy -from erdpy.transactions import Transaction +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider + + +logger = get_logger(__name__) class PriceDiscoveryContract(DEXContractInterface): @@ -95,139 +99,53 @@ def load_config_dict(cls, config_dict: dict): fixed_penalty_percentage=config_dict['fixed_penalty_percentage']) def deposit_liquidity(self, network_provider: NetworkProviders, user: Account, event: DepositPDLiquidityEvent) -> str: - print_warning(f"Deposit Price Discovery liquidity") - print(f"Account: {user.address}") - print(f"Token: {event.deposit_token} Amount: {event.amount}") - - contract = SmartContract(Address(self.address)) + function_purpose = f"Deposit Price Discovery liquidity" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") + logger.debug(f"Token: {event.deposit_token} Amount: {event.amount}") gas_limit = 10000000 - sc_args = [ - "0x" + event.deposit_token.encode("ascii").hex(), - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + "deposit".encode("ascii").hex(), - ] - tx_data = contract.prepare_execute_transaction_data("ESDTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - return txHash - - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" + tokens = [ESDTToken(event.deposit_token, 0, event.amount)] + sc_args = [tokens] + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, user, + Address(self.address), "deposit", sc_args) def withdraw_liquidity(self, network_provider: NetworkProviders, user: Account, event: WithdrawPDLiquidityEvent) -> str: - print_warning(f"Withdraw Price Discovery liquidity") - print(f"Account: {user.address}") - print(f"Token: {event.deposit_lp_token} Nonce: {event.nonce} Amount: {event.amount}") - - contract = SmartContract(address=user.address) + function_purpose = f"Withdraw Price Discovery liquidity" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") + logger.debug(f"Token: {event.deposit_lp_token} Nonce: {event.nonce} Amount: {event.amount}") gas_limit = 10000000 - sc_args = [ - "0x" + event.deposit_lp_token.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "withdraw".encode("ascii").hex(), - ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - return txHash - - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" + tokens = [ESDTToken(event.deposit_lp_token, event.nonce, event.amount)] + sc_args = [tokens] + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, user, + Address(self.address), "withdraw", sc_args) def redeem_liquidity_position(self, network_provider: NetworkProviders, user: Account, event: RedeemPDLPTokensEvent) -> str: - print_warning(f"Redeem Price Discovery liquidity") - print(f"Account: {user.address}") - print(f"Token: {event.deposit_lp_token} Nonce: {event.nonce} Amount: {event.amount}") - - contract = SmartContract(address=user.address) + function_purpose = f"Redeem Price Discovery liquidity" + logger.info(function_purpose) + logger.debug(f"Account: {user.address}") + logger.debug(f"Token: {event.deposit_lp_token} Nonce: {event.nonce} Amount: {event.amount}") gas_limit = 10000000 - sc_args = [ - "0x" + event.deposit_lp_token.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "redeem".encode("ascii").hex(), - ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - return txHash + tokens = [ESDTToken(event.deposit_lp_token, event.nonce, event.amount)] + sc_args = [tokens] + return multi_esdt_endpoint_call(function_purpose, network_provider.proxy, gas_limit, user, + Address(self.address), "redeem", sc_args) - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = []): + function_purpose = f"Deploy price discovery contract" + logger.info(function_purpose) - """ Expected as args: - type[str]: whitelisted deposit rewards address - """ - - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): - print_warning("Deploy price discovery contract") - - metadata = CodeMetadata(upgradeable=True, payable=True) - bytecode: str = load_code_as_hex(bytecode_path) + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True) network_config = proxy.get_network_config() gas_limit = 350000000 - value = 0 - address = "" - tx_hash = "" arguments = [ - "0x" + self.launched_token_id.encode("ascii").hex(), # launched token id - "0x" + self.accepted_token.encode("ascii").hex(), # accepted token id - "0x12", # launched token decimals + self.launched_token_id, # launched token id + self.accepted_token, # accepted token id + 18, # launched token decimals self.min_launched_token_price, self.start_block, self.no_limit_phase_duration_blocks, @@ -237,64 +155,38 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, self.min_penalty_percentage, self.max_penalty_percentage, self.fixed_penalty_percentage, - "0x" + Address(self.locking_sc_address).hex() # locking sc address + Address(self.locking_sc_address) # locking sc address ] - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address - + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - """ Expected as args: + def issue_redeem_token(self, deployer: Account, proxy: ProxyNetworkProvider, redeem_token_ticker: str): + """ Expected as args: type[str]: lp token name type[str]: lp token ticker """ + function_purpose = f"Issue price discovery redeem token" + logger.info(function_purpose) - def issue_redeem_token(self, deployer: Account, proxy: ElrondProxy, redeem_token_ticker: str): - print_warning("Issue price discovery redeem token") - - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [ - "0x" + redeem_token_ticker.encode("ascii").hex(), - "0x" + redeem_token_ticker.encode("ascii").hex(), - "0x12" + redeem_token_ticker, + redeem_token_ticker, + 18, ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "issueRedeemToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "issueRedeemToken", sc_args, + value=config.DEFAULT_ISSUE_TOKEN_PRICE) - return tx_hash + def create_initial_redeem_tokens(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = f"Create initial redeem tokens for price discovery contract" + logger.info(function_purpose) - def create_initial_redeem_tokens(self, deployer: Account, proxy: ElrondProxy): - print_warning("Create initial redeem tokens for price discovery contract") - - network_config = proxy.get_network_config() gas_limit = 50000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "createInitialRedeemTokens", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "createInitialRedeemTokens", sc_args) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): pass def print_contract_info(self): diff --git a/contracts/proxy_deployer_contract.py b/contracts/proxy_deployer_contract.py index aedbd1d..fbed254 100644 --- a/contracts/proxy_deployer_contract.py +++ b/contracts/proxy_deployer_contract.py @@ -1,14 +1,18 @@ import sys import traceback -from arrows.stress.contracts.contract import load_code_as_hex -from contracts.contract_identities import DEXContractInterface, RouterContractVersion -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event +from contracts.contract_identities import DEXContractInterface +from utils.logger import get_logger +from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event, deploy, \ + endpoint_call, get_deployed_address_from_tx from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning -from erdpy.accounts import Account, Address -from erdpy.contracts import SmartContract, CodeMetadata -from erdpy.proxy import ElrondProxy +from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning, log_unexpected_args +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider + + +logger = get_logger(__name__) class ProxyDeployerContract(DEXContractInterface): @@ -31,117 +35,88 @@ def load_config_dict(cls, config_dict: dict): return ProxyDeployerContract(address=config_dict['address'], template_name=config_dict['template']) - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): """Expecting as args: type[str]: template sc address """ - print_warning("Deploy proxy deployer contract") + function_purpose = f"Deploy proxy deployer contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 200000000 - value = 0 - address = "" - tx_hash = "" if len(args) != 1: - print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") - return tx_hash, address + log_unexpected_args(function_purpose, args) + return "", "" arguments = [ - "0x" + Address(args[0]).hex() + Address(args[0]) ] - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address - + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - def farm_contract_deploy(self, deployer: Account, proxy: ElrondProxy, args: list): + def farm_contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """Expecting as args: type[str]: reward token id type[str]: farming token id type[str]: pair contract address """ - print_warning("Deploy farm via router") + function_purpose = f"Deploy farm via router" + logger.info(function_purpose) - network_config = proxy.get_network_config() address, tx_hash = "", "" if len(args) < 3: - print_test_step_fail(f"FAIL: Failed to deploy farm via proxy deployer. Args list not as expected.") + log_unexpected_args(function_purpose, args) return address, tx_hash gas_limit = 100000000 sc_args = [ - "0x" + args[0].encode("ascii").hex(), - "0x" + args[1].encode("ascii").hex(), - "0x" + Address(args[2]).hex() + args[0], + args[1], + Address(args[2]) ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "deployFarm", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) + tx_hash = endpoint_call(proxy, gas_limit, deployer, Address(self.address), "deployFarm", sc_args) # retrieve deployed contract address if tx_hash != "": - response = proxy.get_transaction(tx_hash, with_results=True) - address = get_deployed_address_from_event(response) - deployer.nonce += 1 + address = get_deployed_address_from_tx(tx_hash, proxy) return tx_hash, address - def call_farm_endpoint(self, deployer: Account, proxy: ElrondProxy, args: list): + def call_farm_endpoint(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: farm address type[str]: farm endpoint type[list]: farm endpoint args """ - print_warning("Call farm endpoint via proxy deployer") + function_purpose = f"Call farm endpoint via proxy deployer" + logger.info(function_purpose) - network_config = proxy.get_network_config() tx_hash = "" if len(args) != 3: - print_test_step_fail(f"FAIL: Failed to call farm endpoint. Args list not as expected.") + log_unexpected_args(function_purpose, args) return tx_hash - print_warning(f"Calling: {args[1]}") + logger.debug(f"Calling remote farm endpoint: {args[1]}") gas_limit = 20000000 sc_args = [ - "0x" + Address(args[0]).hex(), - "str:" + args[1], + Address(args[0]), + args[1], ] - if type(args[2] != list): + if type(args[2]) != list: endpoint_args = [args[2]] else: endpoint_args = args[2] sc_args.extend(endpoint_args) - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "callFarmEndpoint", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "callFarmEndpoint", sc_args) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): pass def print_contract_info(self): diff --git a/contracts/router_contract.py b/contracts/router_contract.py index af20160..7778842 100644 --- a/contracts/router_contract.py +++ b/contracts/router_contract.py @@ -1,14 +1,14 @@ -import sys -import traceback - -from arrows.stress.contracts.contract import load_code_as_hex +import config from contracts.contract_identities import DEXContractInterface, RouterContractVersion -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event -from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning -from erdpy.accounts import Account, Address -from erdpy.contracts import SmartContract, CodeMetadata -from erdpy.proxy import ElrondProxy +from utils.logger import get_logger +from utils.utils_tx import deploy, upgrade_call, get_deployed_address_from_tx, endpoint_call +from utils.utils_generic import print_test_step_pass, log_unexpected_args +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider + + +logger = get_logger(__name__) class RouterContract(DEXContractInterface): @@ -28,86 +28,51 @@ def load_config_dict(cls, config_dict: dict): return RouterContract(address=config_dict['address'], version=RouterContractVersion(config_dict['version'])) - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): """Expecting as args: type[str]: pair template address """ - print_warning("Deploy router contract") + function_purpose = f"Deploy router contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True) gas_limit = 200000000 - value = 0 - address = "" - tx_hash = "" if len(args) != 1: - print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") - return tx_hash, address + log_unexpected_args(function_purpose, args) + return "", "" arguments = [ - "0x" + Address(args[0]).hex() + Address(args[0]) ] - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address - + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): """Expecting as args: type[str]: pair template address """ - print_warning("Upgrade router contract") + function_purpose = f"Upgrade router contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True) gas_limit = 200000000 - value = 0 - tx_hash = "" if len(args) != 1: - print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" arguments = [ - "0x" + Address(args[0]).hex() + Address(args[0]) ] - contract = SmartContract(address=Address(self.address), bytecode=bytecode, metadata=metadata) - tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - deployer.nonce += 1 if tx_hash != "" else 0 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash + tx_hash = upgrade_call(type(self).__name__, proxy, gas_limit, deployer, Address(self.address), + bytecode_path, metadata, arguments) return tx_hash - def pair_contract_deploy(self, deployer: Account, proxy: ElrondProxy, args: list): + def pair_contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """Expecting as args: type[str]: first pair token type[str]: second pair token @@ -116,20 +81,20 @@ def pair_contract_deploy(self, deployer: Account, proxy: ElrondProxy, args: list type[str]: special fee percentage type[str..]: admin addresses (v2 only) """ - print_warning("Deploy pair via router") + function_purpose = f"Deploy pair via router" + logger.info(function_purpose) - network_config = proxy.get_network_config() address, tx_hash = "", "" if len(args) < 5: - print_test_step_fail(f"FAIL: Failed to deploy pair via router. Args list not as expected.") + log_unexpected_args(function_purpose, args) return address, tx_hash gas_limit = 100000000 sc_args = [ - "0x" + args[0].encode("ascii").hex(), - "0x" + args[1].encode("ascii").hex(), - "0x" + Address(args[2]).hex(), + args[0], + args[1], + Address(args[2]), args[3], args[4] ] @@ -137,19 +102,15 @@ def pair_contract_deploy(self, deployer: Account, proxy: ElrondProxy, args: list if self.version == RouterContractVersion.V2: sc_args.extend(args[5:]) - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "createPair", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) + tx_hash = endpoint_call(proxy, gas_limit, deployer, Address(self.address), "deployPair", sc_args) # retrieve deployed contract address if tx_hash != "": - response = proxy.get_transaction(tx_hash, with_results=True) - address = get_deployed_address_from_event(response) - deployer.nonce += 1 + address = get_deployed_address_from_tx(tx_hash, proxy) return tx_hash, address - def pair_contract_upgrade(self, deployer: Account, proxy: ElrondProxy, args: list) -> str: + def pair_contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, args: list) -> str: """ Expected as args: type[str]: first token id type[str]: second token id @@ -157,134 +118,103 @@ def pair_contract_upgrade(self, deployer: Account, proxy: ElrondProxy, args: lis type[int]: special fee percent type[str]: initial liquidity adder (only v1) """ - print_warning("Upgrade pair contract") + function_purpose = f"Upgrade pair contract" + logger.info(function_purpose) - network_config = proxy.get_network_config() tx_hash = "" if len(args) < 4: - print_test_step_fail(f"FAIL: Failed to issue token. Args list not as expected.") + log_unexpected_args(function_purpose, args) return tx_hash gas_limit = 200000000 sc_args = [ - "str:" + args[0], - "str:" + args[1], + args[0], + args[1], args[2], args[3] ] if self.version == RouterContractVersion.V1: sc_args.insert(2, args[4]) - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "upgradePair", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + tx_hash = endpoint_call(proxy, gas_limit, deployer, Address(self.address), "upgradePair", sc_args) return tx_hash - """ Expected as args: - type[str]: pair address - type[str]: lp token name - type[str]: lp token ticker - """ - def issue_lp_token(self, deployer: Account, proxy: ElrondProxy, args: list): - print_warning("Issue LP token") - - network_config = proxy.get_network_config() - tx_hash = "" + def issue_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): + """ Expected as args: + type[str]: pair address + type[str]: lp token name + type[str]: lp token ticker + """ + function_purpose = f"Issue LP token" + logger.info(function_purpose) if len(args) != 3: - print_test_step_fail(f"FAIL: Failed to issue token. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 100000000 sc_args = [ - "0x" + Address(args[0]).hex(), - "0x" + args[1].encode("ascii").hex(), - "0x" + args[2].encode("ascii").hex() + Address(args[0]).hex(), + args[1], + args[2] ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "issueLpToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "issueLpToken", sc_args, + value=config.DEFAULT_ISSUE_TOKEN_PRICE) - def set_lp_token_local_roles(self, deployer: Account, proxy: ElrondProxy, pair_contract: str): - print_warning("Set LP token local roles") + def set_lp_token_local_roles(self, deployer: Account, proxy: ProxyNetworkProvider, pair_contract: str): + function_purpose = f"Set LP token local roles" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 100000000 sc_args = [ - "0x" + Address(pair_contract).hex() + Address(pair_contract) ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLocalRoles", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLocalRoles", sc_args) - return tx_hash - - def set_fee_on(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_fee_on(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: pair address to send fees type[str]: address to receive fees type[str]: expected token """ - print_warning("Set fee on for pool") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Set fee on for pool" + logger.info(function_purpose) if len(args) != 3: - print_test_step_fail(f"FAIL: Failed to add trusted swap pair. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 100000000 sc_args = [ - "0x" + Address(args[0]).hex(), - "0x" + Address(args[1]).hex(), - "0x" + args[2].encode("ascii").hex() + Address(args[0]).hex(), + Address(args[1]).hex(), + args[2] ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setFeeOn", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setFeeOn", sc_args) - def pair_contract_pause(self, deployer: Account, proxy: ElrondProxy, pair_contract: str): - print_warning("Pause pair contract") + def pair_contract_pause(self, deployer: Account, proxy: ProxyNetworkProvider, pair_contract: str): + function_purpose = f"Pause pair contract" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 30000000 sc_args = [ - "0x" + Address(pair_contract).hex() + Address(pair_contract) ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "pause", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "pause", sc_args) - return tx_hash + def pair_contract_resume(self, deployer: Account, proxy: ProxyNetworkProvider, pair_contract: str): + function_purpose = f"Resume pair contract" + logger.info(function_purpose) - def pair_contract_resume(self, deployer: Account, proxy: ElrondProxy, pair_contract: str): - print_warning("Resume pair contract") - - network_config = proxy.get_network_config() gas_limit = 30000000 sc_args = [ - "0x" + Address(pair_contract).hex() + Address(pair_contract) ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "resume", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "resume", sc_args) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): pass def print_contract_info(self): diff --git a/contracts/simple_lock_contract.py b/contracts/simple_lock_contract.py index 01ebc34..8fb8db7 100644 --- a/contracts/simple_lock_contract.py +++ b/contracts/simple_lock_contract.py @@ -1,14 +1,14 @@ -import sys -import traceback - -from arrows.stress.contracts.contract import load_code_as_hex +import config from contracts.contract_identities import DEXContractInterface -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx -from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning -from erdpy.accounts import Account, Address -from erdpy.contracts import CodeMetadata, SmartContract -from erdpy.proxy import ElrondProxy +from utils.logger import get_logger +from utils.utils_tx import deploy, endpoint_call +from utils.utils_generic import print_test_step_pass, print_test_substep, log_unexpected_args +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider + + +logger = get_logger(__name__) class SimpleLockContract(DEXContractInterface): @@ -31,136 +31,82 @@ def load_config_dict(cls, config_dict: dict): locked_token=config_dict['locked_token'], lp_proxy_token=config_dict['lp_proxy_token']) - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): - print_warning("Deploy simple lock contract") + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = []): + function_purpose = f"Deploy simple lock contract" + logger.info(function_purpose) metadata = CodeMetadata(upgradeable=True, payable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() gas_limit = 200000000 - value = 0 - address = "" - tx_hash = "" arguments = [] - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address - + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - def issue_locked_lp_token(self, deployer: Account, proxy: ElrondProxy, locked_lp_token: str): - print_warning("Issue locked LP token") - - network_config = proxy.get_network_config() - tx_hash = "" + def issue_locked_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider, locked_lp_token: str): + function_purpose = f"Issue locked LP token" + logger.info(function_purpose) gas_limit = 100000000 sc_args = [ - "0x" + locked_lp_token.encode("ascii").hex(), - "0x" + locked_lp_token.encode("ascii").hex(), - "0x12" + locked_lp_token, + locked_lp_token, + 18 ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "issueLpProxyToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "issueLpProxyToken", sc_args, + value=config.DEFAULT_ISSUE_TOKEN_PRICE) - def issue_locked_token(self, deployer: Account, proxy: ElrondProxy, locked_token: str): - print_warning("Issue locked token") - - network_config = proxy.get_network_config() - tx_hash = "" + def issue_locked_token(self, deployer: Account, proxy: ProxyNetworkProvider, locked_token: str): + function_purpose = f"Issue locked token" + logger.info(function_purpose) gas_limit = 100000000 sc_args = [ - "0x" + locked_token.encode("ascii").hex(), - "0x" + locked_token.encode("ascii").hex(), - "0x12" + locked_token, + locked_token, + 18 ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "issueLockedToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash - - def set_local_roles_locked_token(self, deployer: Account, proxy: ElrondProxy): - print_warning("Set local roles locked token") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "issueLockedToken", sc_args, + value=config.DEFAULT_ISSUE_TOKEN_PRICE) - network_config = proxy.get_network_config() - tx_hash = "" + def set_local_roles_locked_token(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = f"Set local roles locked token" + logger.info(function_purpose) gas_limit = 100000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLocalRolesLockedToken", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash - - def set_local_roles_locked_lp_token(self, deployer: Account, proxy: ElrondProxy): - print_warning("Set local roles locked lp token") + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLocalRolesLockedToken", sc_args) - network_config = proxy.get_network_config() - tx_hash = "" + def set_local_roles_locked_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = f"Set local roles locked lp token" + logger.info(function_purpose) gas_limit = 100000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLocalRolesLpProxyToken", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLocalRolesLpProxyToken", sc_args) - return tx_hash - - """ Expected as args: - type[str]: pair address - type[str]: first token identifier - type[str]: second token identifier + def add_lp_to_whitelist(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): + """ Expected as args: + type[str]: pair address + type[str]: first token identifier + type[str]: second token identifier """ - - def add_lp_to_whitelist(self, deployer: Account, proxy: ElrondProxy, args: list): - print_warning("Add LP to Whitelist in simple lock contract") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Add LP to Whitelist in simple lock contract" + logger.info(function_purpose) if len(args) != 3: - print_test_step_fail(f"FAIL: Failed to whitelist lp in simple lock contract. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 100000000 sc_args = [ - "0x" + Address(args[0]).hex(), - "0x" + args[1].encode("ascii").hex(), - "0x" + args[2].encode("ascii").hex() + Address(args[0]), + args[1], + args[2] ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "addLpToWhitelist", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "addLpToWhitelist", sc_args) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): pass def print_contract_info(self): diff --git a/contracts/simple_lock_energy_contract.py b/contracts/simple_lock_energy_contract.py index ca09f81..67bc13d 100644 --- a/contracts/simple_lock_energy_contract.py +++ b/contracts/simple_lock_energy_contract.py @@ -1,15 +1,13 @@ -import sys -import traceback - -from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, \ - multi_esdt_endpoint_call, endpoint_call -from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning -from erdpy.accounts import Account, Address -from erdpy.contracts import CodeMetadata, SmartContract -from erdpy.proxy import ElrondProxy +from utils.logger import get_logger +from utils.utils_tx import multi_esdt_endpoint_call, endpoint_call, deploy, upgrade_call +from utils.utils_generic import print_test_step_pass, print_test_substep, log_unexpected_args +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider + + +logger = get_logger(__name__) class SimpleLockEnergyContract(DEXContractInterface): @@ -39,7 +37,7 @@ def load_config_dict(cls, config_dict: dict): lp_proxy_token=config_dict['lp_proxy_token'], farm_proxy_token=config_dict['farm_proxy_token']) - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): """Expecting as args: type[str]: legacy token id type[str]: locked asset factory address @@ -47,50 +45,30 @@ def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, type[list]: lock options type[list]: penalties """ - print_warning("Deploy simple lock energy contract") + function_purpose = "Deploy simple lock energy contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 200000000 - value = 0 - address = "" - tx_hash = "" if len(args) != 5: - print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") - return tx_hash, address + log_unexpected_args(function_purpose, args) + return "", "" arguments = [ - "str:" + self.base_token, # base token id - "str:" + args[0], # legacy token id - args[1], # locked asset factory address + self.base_token, # base token id + args[0], # legacy token id + Address(args[1]), # locked asset factory address args[2] # min migrated token locking epochs ] lock_fee_pairs = list(zip(args[3], args[4])) lock_options = [item for sublist in lock_fee_pairs for item in sublist] arguments.extend(lock_options) # lock_options - print(f"Arguments: {arguments}") - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): """Expecting as args: type[str]: legacy token id type[str]: locked asset factory address @@ -98,21 +76,18 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, type[list]: lock options type[list]: penalties """ - print_warning("Upgrade simple lock energy contract") + function_purpose = "Upgrade simple lock energy contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 200000000 - value = 0 - tx_hash = "" if len(args) != 5: - print_test_step_fail(f"FAIL: Failed to upgrade contract. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" arguments = [ - "str:" + self.base_token, # base token id - "str:" + args[0], # legacy token id + self.base_token, # base token id + args[0], # legacy token id args[1], # locked asset factory address args[2] # min migrated token locking epochs ] @@ -121,433 +96,356 @@ def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, lock_options = [item for sublist in lock_fee_pairs for item in sublist] arguments.extend(lock_options) # lock_options - contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) - tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) + return upgrade_call(type(self).__name__, proxy, gas_limit, deployer, Address(self.address), + bytecode_path, metadata, arguments) - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash - - return tx_hash - - def issue_locked_lp_token(self, deployer: Account, proxy: ElrondProxy, locked_lp_token: str): - print_warning("Issue locked LP token") - - network_config = proxy.get_network_config() - tx_hash = "" + def issue_locked_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider, locked_lp_token: str): + function_purpose = "Issue locked LP token" + logger.info(function_purpose) gas_limit = 100000000 sc_args = [ - "0x" + locked_lp_token.encode("ascii").hex(), - "0x" + locked_lp_token.encode("ascii").hex(), - "0x12" + locked_lp_token, + locked_lp_token, + 18 ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "issueLpProxyToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "issueLpProxyToken", sc_args, + value="50000000000000000") - def issue_locked_farm_token(self, deployer: Account, proxy: ElrondProxy, locked_lp_token: str): - print_warning("Issue locked Farm token") - - network_config = proxy.get_network_config() - tx_hash = "" + def issue_locked_farm_token(self, deployer: Account, proxy: ProxyNetworkProvider, locked_lp_token: str): + function_purpose = "Issue locked Farm token" + logger.info(function_purpose) gas_limit = 100000000 sc_args = [ - "0x" + locked_lp_token.encode("ascii").hex(), - "0x" + locked_lp_token.encode("ascii").hex(), - "0x12" + locked_lp_token, + locked_lp_token, + 18 ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "issueFarmProxyToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "issueFarmProxyToken", sc_args, + value="50000000000000000") - def issue_locked_token(self, deployer: Account, proxy: ElrondProxy, args: list): + def issue_locked_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: token display name type[str]: token ticker """ - print_warning("Issue locked token") + function_purpose = "Issue locked token" + logger.info(function_purpose) if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to issue locked token in simple lock energy contract. " - f"Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - network_config = proxy.get_network_config() - tx_hash = "" - gas_limit = 100000000 sc_args = [ - "0x" + args[0].encode("ascii").hex(), - "0x" + args[1].encode("ascii").hex(), - "0x12" + args[0], + args[1], + 18 ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "issueLockedToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "issueLockedToken", sc_args, + value="50000000000000000") - def set_transfer_role_locked_token(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_transfer_role_locked_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: new role address; empty will assign the role to the contract itself """ function_purpose = "Set transfer role locked token" - return endpoint_call(function_purpose, proxy, 100000000, deployer, Address(self.address), - "setTransferRoleLockedToken", args) + logger.info(function_purpose) + return endpoint_call(proxy, 100000000, deployer, Address(self.address), "setTransferRoleLockedToken", args) - def set_burn_role_locked_token(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_burn_role_locked_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: new role address """ function_purpose = "set burn roles on locked token for address" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " - f"Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 100000000, deployer, Address(self.address), - "setBurnRoleLockedToken", args) + return endpoint_call(proxy, 100000000, deployer, Address(self.address), "setBurnRoleLockedToken", args) - def set_old_locked_asset_factory(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_old_locked_asset_factory(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: old locked asset factory address """ function_purpose = "set old locked asset factory address" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " - f"Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), - "setOldLockedAssetFactoryAddress", args) + return endpoint_call(proxy, 10000000, deployer, Address(self.address), "setOldLockedAssetFactoryAddress", args) - def set_fees_collector(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_fees_collector(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: fees collector address """ function_purpose = "set fees collector address" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " - f"Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), - "setFeesCollectorAddress", args) + return endpoint_call(proxy, 10000000, deployer, Address(self.address), "setFeesCollectorAddress", args) - def set_token_unstake(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_token_unstake(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: token unstake address """ function_purpose = "set fees collector address" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " - f"Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), - "setTokenUnstakeAddress", args) + return endpoint_call(proxy, 10000000, deployer, Address(self.address), "setTokenUnstakeAddress", args) - def add_lock_options(self, deployer: Account, proxy: ElrondProxy, args: list): + def add_lock_options(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[int..]: lock options """ function_purpose = "add lock options" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " - f"Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), - "addLockOptions", args) + return endpoint_call(proxy, 10000000, deployer, Address(self.address), "addLockOptions", args) - def remove_lock_options(self, deployer: Account, proxy: ElrondProxy, args: list): + def remove_lock_options(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[int..]: lock options """ function_purpose = "remove lock options" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " - f"Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), - "removeLockOptions", args) + return endpoint_call(proxy, 10000000, deployer, Address(self.address), "removeLockOptions", args) - def set_penalty_percentage(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_penalty_percentage(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[int]: min penalty 0 - 10000 type[int]: max penalty - 10000 """ function_purpose = "set penalty percentage" + logger.info(function_purpose) if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " - f"Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), - "setPenaltyPercentage", args) + return endpoint_call(proxy, 10000000, deployer, Address(self.address), "setPenaltyPercentage", args) - def set_fees_burn_percentage(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_fees_burn_percentage(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[int]: burn percentage 0 - 10000 """ function_purpose = "set fees burn percentage" + logger.info(function_purpose) if len(args) != 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose} in simple lock energy contract. " - f"Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), - "setFeesBurnPercentage", args) + return endpoint_call(proxy, 10000000, deployer, Address(self.address), "setFeesBurnPercentage", args) - def add_sc_to_whitelist(self, deployer: Account, proxy: ElrondProxy, contract_address: str): - print_warning("Add SC to Whitelist in simple lock energy contract") - - network_config = proxy.get_network_config() + def add_sc_to_whitelist(self, deployer: Account, proxy: ProxyNetworkProvider, contract_address: str): + function_purpose = "Add SC to Whitelist in simple lock energy contract" + logger.info(function_purpose) gas_limit = 50000000 sc_args = [ - contract_address + Address(contract_address) ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "addSCAddressToWhitelist", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "addSCAddressToWhitelist", sc_args) - def remove_sc_from_whitelist(self, deployer: Account, proxy: ElrondProxy, contract_address: str): + def remove_sc_from_whitelist(self, deployer: Account, proxy: ProxyNetworkProvider, contract_address: str): """ Expected as args: type[str]: address """ - print_warning("Remove SC from Whitelist in simple lock energy contract") - - network_config = proxy.get_network_config() + function_purpose = "Remove SC from Whitelist in simple lock energy contract" + logger.info(function_purpose) gas_limit = 50000000 sc_args = [ - contract_address + Address(contract_address) ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "removeSCAddressFromWhitelist", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "removeSCAddressFromWhitelist", sc_args) - def add_sc_to_token_transfer_whitelist(self, deployer: Account, proxy: ElrondProxy, contract_address: str): - print_warning("Add SC to Token Transfer Whitelist in simple lock energy contract") - - network_config = proxy.get_network_config() + def add_sc_to_token_transfer_whitelist(self, deployer: Account, proxy: ProxyNetworkProvider, contract_address: str): + function_purpose = "Add SC to Token Transfer Whitelist in simple lock energy contract" + logger.info(function_purpose) gas_limit = 50000000 sc_args = [ - contract_address + Address(contract_address) ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "addToTokenTransferWhitelist", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), + "addToTokenTransferWhitelist", sc_args) - def remove_sc_from_token_transfer_whitelist(self, deployer: Account, proxy: ElrondProxy, contract_address: str): + def remove_sc_from_token_transfer_whitelist(self, deployer: Account, proxy: ProxyNetworkProvider, contract_address: str): """ Expected as args: type[str]: address """ - print_warning("Remove SC from Token Transfer Whitelist in simple lock energy contract") - - network_config = proxy.get_network_config() + function_purpose = "Remove SC from Token Transfer Whitelist in simple lock energy contract" + logger.info(function_purpose) gas_limit = 50000000 sc_args = [ - contract_address + Address(contract_address) ] - print(f"Arguments: {sc_args}") - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "removeFromTokenTransferWhitelist", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), + "removeFromTokenTransferWhitelist", sc_args) - def lock_tokens(self, user: Account, proxy: ElrondProxy, args: list): + def lock_tokens(self, user: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[List[ESDTToken]]: tokens list type[int]: lock epochs opt: type[address]: destination address """ function_purpose = "lock tokens" + logger.info(function_purpose) if len(args) < 2: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") + return multi_esdt_endpoint_call(function_purpose, proxy, 10000000, user, Address(self.address), "lockTokens", args) - def unlock_tokens(self, user: Account, proxy: ElrondProxy, args: list): + def unlock_tokens(self, user: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[List[ESDTToken]]: tokens list opt: type[address]: destination address """ function_purpose = "unlock tokens" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") return multi_esdt_endpoint_call(function_purpose, proxy, 10000000, user, Address(self.address), "unlockTokens", args) - def unlock_early(self, user: Account, proxy: ElrondProxy, args: list): + def unlock_early(self, user: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[List[ESDTToken]]: tokens list """ function_purpose = "unlock tokens early" + logger.info(function_purpose) if len(args) != 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") return multi_esdt_endpoint_call(function_purpose, proxy, 30000000, user, Address(self.address), "unlockEarly", args) - def reduce_lock(self, user: Account, proxy: ElrondProxy, args: list): + def reduce_lock(self, user: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[List[ESDTToken]]: tokens list type[int]: epochs to reduce """ function_purpose = "reduce lock period" + logger.info(function_purpose) if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") return multi_esdt_endpoint_call(function_purpose, proxy, 20000000, user, Address(self.address), "reduceLockPeriod", args) - def extend_lock(self, user: Account, proxy: ElrondProxy, args: list): + def extend_lock(self, user: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[List[ESDTToken]]: tokens list type[int]: new lock option """ function_purpose = "extend lock period" + logger.info(function_purpose) if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") return multi_esdt_endpoint_call(function_purpose, proxy, 10000000, user, Address(self.address), "extendLockingPeriod", args) - def add_liquidity_locked_token(self, user: Account, proxy: ElrondProxy, args: list): + def add_liquidity_locked_token(self, user: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[List[ESDTToken]]: tokens list type[int]: first token amount min type[int]: second token amount min """ function_purpose = "add liquidity for locked token" - + logger.info(function_purpose) if len(args) != 3: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" return multi_esdt_endpoint_call(function_purpose, proxy, 20000000, user, Address(self.address), "addLiquidityLockedToken", args) - def remove_liquidity_locked_token(self, user: Account, proxy: ElrondProxy, args: list): + def remove_liquidity_locked_token(self, user: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[List[ESDTToken]]: tokens list type[int]: first token amount min type[int]: second token amount min """ function_purpose = "add liquidity for locked token" - + logger.info(function_purpose) if len(args) != 3: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") return multi_esdt_endpoint_call(function_purpose, proxy, 20000000, user, Address(self.address), "removeLiquidityLockedToken", args) - def enter_farm_locked_token(self, user: Account, proxy: ElrondProxy, args: list): + def enter_farm_locked_token(self, user: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[List[ESDTToken]]: tokens list """ function_purpose = "enter farm with locked token" - + logger.info(function_purpose) if len(args) != 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") return multi_esdt_endpoint_call(function_purpose, proxy, 30000000, user, Address(self.address), "enterFarmLockedToken", args) - def exit_farm_locked_token(self, user: Account, proxy: ElrondProxy, args: list): + def exit_farm_locked_token(self, user: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[List[ESDTToken]]: tokens list """ function_purpose = "exit farm with locked token" - + logger.info(function_purpose) if len(args) != 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") return multi_esdt_endpoint_call(function_purpose, proxy, 30000000, user, Address(self.address), "exitFarmLockedToken", args) - def claim_farm_locked_token(self, user: Account, proxy: ElrondProxy, args: list): + def claim_farm_locked_token(self, user: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[List[ESDTToken]]: tokens list """ function_purpose = "claim farm with locked token" - + logger.info(function_purpose) if len(args) != 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") return multi_esdt_endpoint_call(function_purpose, proxy, 30000000, user, Address(self.address), "farmClaimRewardsLockedToken", args) - def pause(self, deployer: Account, proxy: ElrondProxy): + def pause(self, deployer: Account, proxy: ProxyNetworkProvider): function_purpose = "Resume simple lock energy contract" - return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), "pause", []) + logger.info(function_purpose) + return endpoint_call(proxy, 10000000, deployer, Address(self.address), "pause", []) - def resume(self, deployer: Account, proxy: ElrondProxy): + def resume(self, deployer: Account, proxy: ProxyNetworkProvider): function_purpose = "Resume simple lock energy contract" - return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), "unpause", []) + logger.info(function_purpose) + return endpoint_call(proxy, 10000000, deployer, Address(self.address), "unpause", []) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = None): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = None): self.set_transfer_role_locked_token(deployer, proxy, []) self.resume(deployer, proxy) diff --git a/contracts/staking_contract.py b/contracts/staking_contract.py index d51f38f..40c21c5 100644 --- a/contracts/staking_contract.py +++ b/contracts/staking_contract.py @@ -1,21 +1,16 @@ -import sys -import time -import traceback - -from arrows.stress.contracts.contract import load_code_as_hex -from contracts.contract_identities import DEXContractInterface, StakingContractIdentity, \ - StakingContractVersion -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders -from erdpy.accounts import Account, Address -from erdpy.contracts import SmartContract, CodeMetadata -from erdpy.proxy import ElrondProxy -from erdpy.transactions import Transaction -from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import get_continue_confirmation, print_test_step_fail, print_test_step_pass, \ - print_test_substep, print_warning +import config +from contracts.contract_identities import DEXContractInterface, StakingContractVersion +from utils.logger import get_logger +from utils.utils_tx import NetworkProviders, ESDTToken, multi_esdt_endpoint_call, deploy, upgrade_call, endpoint_call +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider +from utils.utils_generic import print_test_step_pass, print_test_substep, log_unexpected_args from events.farm_events import (EnterFarmEvent, ExitFarmEvent, - ClaimRewardsFarmEvent, CompoundRewardsFarmEvent, - MigratePositionFarmEvent) + ClaimRewardsFarmEvent, CompoundRewardsFarmEvent) + + +logger = get_logger(__name__) class StakingContract(DEXContractInterface): @@ -54,386 +49,192 @@ def load_config_dict(cls, config_dict: dict): def stake_farm(self, network_provider: NetworkProviders, user: Account, event: EnterFarmEvent, initial: bool = False) -> str: - print(f"Account: {user.address}") - - contract = SmartContract(address=user.address) - stake_farm_fn = "stakeFarm" - - print_warning(f"{stake_farm_fn}") + logger.info(f"{stake_farm_fn}") + logger.debug(f"Account: {user.address}") gas_limit = 50000000 - sc_args = [ - "0x" + Address(self.address).hex(), # contract address - "0x01" if initial else "0x02", # number of tokens sent - "0x" + event.farming_tk.encode("ascii").hex(), # farming token details - "0x" + "0" + f"{event.farming_tk_nonce:x}" if len( - f"{event.farming_tk_nonce:x}") % 2 else "0x" + f"{event.farming_tk_nonce:x}", - "0x" + "0" + f"{event.farming_tk_amount:x}" if len( - f"{event.farming_tk_amount:x}") % 2 else "0x" + f"{event.farming_tk_amount:x}", - ] + tokens = [ESDTToken(event.farming_tk, event.farming_tk_nonce, event.farming_tk_amount)] if not initial: - sc_args.extend([ - "0x" + event.farm_tk.encode("ascii").hex(), # farm token details - "0x" + "0" + f"{event.farm_tk_nonce:x}" if len( - f"{event.farm_tk_nonce:x}") % 2 else "0x" + f"{event.farm_tk_nonce:x}", - "0x" + "0" + f"{event.farm_tk_amount:x}" if len( - f"{event.farm_tk_amount:x}") % 2 else "0x" + f"{event.farm_tk_amount:x}", - ]) - sc_args.extend([ - "0x" + stake_farm_fn.encode("ascii").hex(), # enterFarm endpoint name - ]) - tx_data = contract.prepare_execute_transaction_data("MultiESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = user.address.bech32() # MultiESDTNFTTransfer is issued via self transfers - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - - return txHash - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" + tokens.append(ESDTToken(event.farm_tk, event.farm_tk_nonce, event.farm_tk_amount)) + args = [tokens] + + return multi_esdt_endpoint_call(stake_farm_fn, network_provider.proxy, gas_limit, user, + Address(self.address), stake_farm_fn, args) def unstake_farm(self, network_provider: NetworkProviders, user: Account, event: ExitFarmEvent) -> str: unstake_fn = 'unstakeFarm' - print_warning(f"{unstake_fn}") - - contract = SmartContract(address=user.address) + logger.info(f"{unstake_fn}") + logger.debug(f"Account: {user.address}") gas_limit = 50000000 - sc_args = [ - "0x" + event.farm_token.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + unstake_fn.encode("ascii").hex(), - ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - - return txHash - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" - def claimRewards(self, network_provider: NetworkProviders, user: Account, event: ClaimRewardsFarmEvent) -> str: - print_warning(f"claimRewards") - print(f"Account: {user.address}") + tokens = [ESDTToken(event.farm_token, event.nonce, event.amount)] + args = [tokens] - contract = SmartContract(address=user.address) + return multi_esdt_endpoint_call(unstake_fn, network_provider.proxy, gas_limit, user, + Address(self.address), unstake_fn, args) + + def claimRewards(self, network_provider: NetworkProviders, user: Account, event: ClaimRewardsFarmEvent) -> str: + claim_fn = 'claimRewards' + logger.info(f"{claim_fn}") + logger.debug(f"Account: {user.address}") gas_limit = 50000000 - sc_args = [ - "0x" + self.farm_token.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "claimRewards".encode("ascii").hex(), - ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - return txHash - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" - def compoundRewards(self, network_provider: NetworkProviders, user: Account, event: CompoundRewardsFarmEvent) -> str: - print_warning(f"compoundRewards") - print(f"Account: {user.address}") + tokens = [ESDTToken(self.farm_token, event.nonce, event.amount)] + sc_args = [tokens] + return multi_esdt_endpoint_call(claim_fn, network_provider.proxy, gas_limit, user, + Address(self.address), claim_fn, sc_args) - contract = SmartContract(address=user.address) + def compound_rewards(self, network_provider: NetworkProviders, user: Account, event: CompoundRewardsFarmEvent) -> str: + compound_fn = 'compoundRewards' + logger.info(f"{compound_fn}") + logger.debug(f"Account: {user.address}") gas_limit = 50000000 - sc_args = [ - "0x" + self.farm_token.encode("ascii").hex(), - "0x" + "0" + f"{event.nonce:x}" if len(f"{event.nonce:x}") % 2 else "0x" + f"{event.nonce:x}", - "0x" + "0" + f"{event.amount:x}" if len(f"{event.amount:x}") % 2 else "0x" + f"{event.amount:x}", - "0x" + Address(self.address).hex(), - "0x" + "compoundRewards".encode("ascii").hex(), - ] - tx_data = contract.prepare_execute_transaction_data("ESDTNFTTransfer", sc_args) - - tx = Transaction() - tx.nonce = user.nonce - tx.sender = user.address.bech32() - tx.receiver = contract.address.bech32() - tx.gasPrice = network_provider.network.min_gas_price - tx.gasLimit = gas_limit - tx.data = tx_data - tx.chainID = network_provider.network.chain_id - tx.version = network_provider.network.min_tx_version - tx.sign(user) - try: - txHash = network_provider.proxy.send_transaction(tx.to_dictionary()) - log_explorer_transaction(txHash, network_provider.proxy.url) - user.nonce += 1 - return txHash - except Exception as ex: - print(ex) - traceback.print_exception(*sys.exc_info()) - return "" + tokens = [ESDTToken(self.farm_token, event.nonce, event.amount)] + sc_args = [tokens] + return multi_esdt_endpoint_call(compound_fn, network_provider.proxy, gas_limit, user, + Address(self.address), compound_fn, sc_args) - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = []): + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = []): """Expecting as args:percent type[str]: owner address - only from v2 type[str]: admin address - only from v2 self.version has to be initialized to correctly attempt the deploy for that specific type of farm. """ - print_warning("Deploy staking contract") + function_purpose = f"Deploy staking contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=False, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=False, readable=True) gas_limit = 200000000 - value = 0 - address = "" - tx_hash = "" arguments = [ - "0x" + self.farming_token.encode("ascii").hex(), - "0xE8D4A51000", - "0x" + "0" + f"{self.max_apr:x}" if len(f"{self.max_apr:x}") % 2 else "0x" + f"{self.max_apr:x}", - "0x" + "0" + f"{self.unbond_epochs:x}" if len(f"{self.unbond_epochs:x}") % 2 else "0x" + f"{self.unbond_epochs:x}", + self.farming_token, + 1000000000000, + self.max_apr, + self.unbond_epochs ] if self.version == StakingContractVersion.V2: arguments.extend(args) - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address - + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - def contract_upgrade(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list = [], + def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list = [], yes: bool = True): """Expecting as args:percent type[str]: owner address - only from v2 type[str]: admin address - only from v2 self.version has to be initialized to correctly attempt the deploy for that specific type of farm. """ - print_warning("Upgrade staking contract") + function_purpose = f"Upgrade staking contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=False) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=False, readable=True) gas_limit = 200000000 - value = 0 - tx_hash = "" arguments = [ - "str:" + self.farming_token, - "0xE8D4A51000", + self.farming_token, + 1000000000000, self.max_apr, self.unbond_epochs, ] if self.version == StakingContractVersion.V2: arguments.extend(args) - if not get_continue_confirmation(yes): return tx_hash - - contract = SmartContract(bytecode=bytecode, metadata=metadata, address=Address(self.address)) - tx = contract.upgrade(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) + return upgrade_call(type(self).__name__, proxy, gas_limit, deployer, Address(self.address), + bytecode_path, metadata, arguments) - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send upgrade transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash - - return tx_hash - - def register_farm_token(self, deployer: Account, proxy: ElrondProxy, args: list): + def register_farm_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[str]: token display name type[str]: token ticker """ - print_warning("Register stake token") - - network_config = proxy.get_network_config() - tx_hash = "" + function_purpose = f"Register stake token" + logger.info(function_purpose) if len(args) != 2: - print_test_step_fail(f"FAIL: Failed to register stake token. Args list not as expected.") - return tx_hash + log_unexpected_args(function_purpose, args) + return "" gas_limit = 100000000 sc_args = [ - "0x" + args[0].encode("ascii").hex(), - "0x" + args[1].encode("ascii").hex(), - "0x12" + args[0], + args[1], + 18 ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "registerFarmToken", sc_args, value="50000000000000000") - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "registerFarmToken", sc_args, + value=config.DEFAULT_ISSUE_TOKEN_PRICE) - return tx_hash - - def set_local_roles_farm_token(self, deployer: Account, proxy: ElrondProxy): - print_warning("Set local roles for stake token") - - network_config = proxy.get_network_config() + def set_local_roles_farm_token(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = f"Set local roles for stake token" + logger.info(function_purpose) gas_limit = 100000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setLocalRolesFarmToken", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLocalRolesFarmToken", sc_args) - def set_rewards_per_block(self, deployer: Account, proxy: ElrondProxy, rewards_amount: int): - print_warning("Set rewards per block in stake contract") + def set_rewards_per_block(self, deployer: Account, proxy: ProxyNetworkProvider, rewards_amount: int): + function_purpose = f"Set rewards per block in stake contract" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 50000000 sc_args = [ - "0x" + "0" + f"{rewards_amount:x}" if len(f"{rewards_amount:x}") % 2 else "0x" + f"{rewards_amount:x}", + rewards_amount ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "setPerBlockRewardAmount", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setPerBlockRewardAmount", sc_args) - return tx_hash + def topup_rewards(self, deployer: Account, proxy: ProxyNetworkProvider, rewards_amount: int): + function_purpose = f"Topup rewards in stake contract" + logger.info(function_purpose) - def topup_rewards(self, deployer: Account, proxy: ElrondProxy, rewards_amount: int): - print_warning("Topup rewards in stake contract") - - network_config = proxy.get_network_config() gas_limit = 50000000 - sc_args = [ - "0x" + self.farmed_token.encode("ascii").hex(), - "0x" + "0" + f"{rewards_amount:x}" if len(f"{rewards_amount:x}") % 2 else "0x" + f"{rewards_amount:x}", - "0x" + "topUpRewards".encode("ascii").hex(), - ] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "ESDTTransfer", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - return tx_hash + tokens = [ESDTToken(self.farm_token, 0, rewards_amount)] + sc_args = [tokens] + return multi_esdt_endpoint_call(function_purpose, proxy, gas_limit, deployer, + Address(self.address), "topUpRewards", sc_args) - def resume(self, deployer: Account, proxy: ElrondProxy): - print_warning("Resume stake contract") + def resume(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = f"Resume stake contract" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 30000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "resume", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "resume", sc_args) - def pause(self, deployer: Account, proxy: ElrondProxy): - print_warning("Pause stake contract") + def pause(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = f"Pause stake contract" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 30000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "pause", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "pause", sc_args) - def start_produce_rewards(self, deployer: Account, proxy: ElrondProxy): - print_warning("Start producing rewards in stake contract") + def start_produce_rewards(self, deployer: Account, proxy: ProxyNetworkProvider): + function_purpose = f"Start producing rewards in stake contract" + logger.info(function_purpose) - network_config = proxy.get_network_config() gas_limit = 10000000 sc_args = [] - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - "startProduceRewards", sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "startProduceRewards", sc_args) - return tx_hash + def whitelist_contract(self, deployer: Account, proxy: ProxyNetworkProvider, contract_to_whitelist: str): + function_purpose = f"Whitelist contract in staking" + logger.info(function_purpose) - def whitelist_contract(self, deployer: Account, proxy: ElrondProxy, contract_to_whitelist: str): - print_warning("Whitelist contract in staking") - - network_config = proxy.get_network_config() gas_limit = 50000000 sc_args = [ - "0x" + Address(contract_to_whitelist).hex() + Address(contract_to_whitelist) ] endpoint_name = "addAddressToWhitelist" if self.version == StakingContractVersion.V1 \ else "addSCAddressToWhitelist" - tx = prepare_contract_call_tx(Address(self.address), deployer, network_config, gas_limit, - endpoint_name, sc_args) - tx_hash = send_contract_call_tx(tx, proxy) - deployer.nonce += 1 if tx_hash != "" else 0 - - return tx_hash + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), endpoint_name, sc_args) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = []): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = []): _ = self.start_produce_rewards(deployer, proxy) _ = self.resume(deployer, proxy) diff --git a/contracts/unstaker_contract.py b/contracts/unstaker_contract.py index 9d0cf47..562eb50 100644 --- a/contracts/unstaker_contract.py +++ b/contracts/unstaker_contract.py @@ -1,15 +1,17 @@ import sys import traceback -from arrows.stress.contracts.contract import load_code_as_hex from contracts.contract_identities import DEXContractInterface -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, \ - multi_esdt_endpoint_call, endpoint_call +from utils.logger import get_logger +from utils.utils_tx import endpoint_call, deploy from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning -from erdpy.accounts import Account, Address -from erdpy.contracts import CodeMetadata, SmartContract -from erdpy.proxy import ElrondProxy +from utils.utils_generic import print_test_step_fail, print_test_step_pass, log_unexpected_args +from utils.utils_chain import Account, WrapperAddress as Address +from multiversx_sdk_core import CodeMetadata +from multiversx_sdk_network_providers import ProxyNetworkProvider + + +logger = get_logger(__name__) class UnstakerContract(DEXContractInterface): @@ -26,84 +28,56 @@ def get_config_dict(self) -> dict: def load_config_dict(cls, config_dict: dict): return UnstakerContract(address=config_dict['address']) - def contract_deploy(self, deployer: Account, proxy: ElrondProxy, bytecode_path, args: list): + def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): """Expecting as args: type[int]: unbond epochs type[str]: energy factory address type[int]: fees burn percentage type[str]: fees collector address """ - print_warning("Deploy token unstake contract") + function_purpose = f"Deploy token unstake contract" + logger.info(function_purpose) - metadata = CodeMetadata(upgradeable=True, payable_by_sc=True, readable=True) - bytecode: str = load_code_as_hex(bytecode_path) - network_config = proxy.get_network_config() + metadata = CodeMetadata(upgradeable=True, payable_by_contract=True, readable=True) gas_limit = 200000000 - value = 0 - address = "" - tx_hash = "" if len(args) != 4: - print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") - return tx_hash, address + log_unexpected_args(function_purpose, args) + return "", "" arguments = args - # lock_fee_pairs = list(zip(args[4], args[5])) - # lock_options = [item for sublist in lock_fee_pairs for item in sublist] - # arguments.extend(lock_options) # lock_options - print(f"Arguments: {arguments}") - contract = SmartContract(bytecode=bytecode, metadata=metadata) - tx = contract.deploy(deployer, arguments, network_config.min_gas_price, gas_limit, value, - network_config.chain_id, network_config.min_tx_version) - - try: - response = proxy.send_transaction_and_wait_for_result(tx.to_dictionary()) - tx_hash = response.get_hash() - log_explorer_transaction(tx_hash, proxy.url) - - address = contract.address.bech32() - deployer.nonce += 1 - - except Exception as ex: - print_test_step_fail(f"Failed to send deploy transaction due to: {ex}") - traceback.print_exception(*sys.exc_info()) - return tx_hash, address - + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address - def set_energy_factory_address(self, deployer: Account, proxy: ElrondProxy, args: list): + def set_energy_factory_address(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: type[address]: energy factory address """ function_purpose = "set energy factory address" + logger.info(function_purpose) if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose} in token unstake contract. " - f"Args list not as expected.") + log_unexpected_args(function_purpose, args) return "" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 10000000, deployer, Address(self.address), - "setEnergyFactoryAddress", args) + return endpoint_call(proxy, 10000000, deployer, Address(self.address), "setEnergyFactoryAddress", args) - def claim_unlocked_tokens(self, deployer: Account, proxy: ElrondProxy, args: list): + def claim_unlocked_tokens(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: empty """ function_purpose = "claim unlocked tokens" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 20000000, deployer, Address(self.address), - "claimUnlockedTokens", []) + logger.info(function_purpose) + return endpoint_call(proxy, 20000000, deployer, Address(self.address), "claimUnlockedTokens", []) - def cancel_unbond(self, deployer: Account, proxy: ElrondProxy, args: list): + def cancel_unbond(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """ Expected as args: empty """ function_purpose = "cancel unbond" - print(f"Arguments: {args}") - return endpoint_call(function_purpose, proxy, 20000000, deployer, Address(self.address), - "cancelUnbond", args) + logger.info(function_purpose) + return endpoint_call(proxy, 20000000, deployer, Address(self.address), "cancelUnbond", args) - def contract_start(self, deployer: Account, proxy: ElrondProxy, args: list = None): + def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: list = None): pass def print_contract_info(self): diff --git a/events/event_generators.py b/events/event_generators.py index ee8f1e1..4449c99 100644 --- a/events/event_generators.py +++ b/events/event_generators.py @@ -65,7 +65,7 @@ def generate_add_liquidity_event(context: Context, user_account: Account, pair_c set_reserves_event = SetCorrectReservesEvent() context.observable.set_event(pair_contract, user_account, set_reserves_event, '') - txhash = pair_contract.addLiquidity(context.network_provider, user_account, event) + txhash = pair_contract.add_liquidity(context.network_provider, user_account, event) context.observable.set_event(pair_contract, user_account, event, txhash) except Exception as ex: @@ -87,7 +87,7 @@ def generate_add_initial_liquidity_event(context: Context, user_account: Account tokens[0], 2000, 1, tokens[1], 2000, 1 ) - pair_contract.addInitialLiquidity(context.network_provider, user_account, event) + pair_contract.add_initial_liquidity(context.network_provider, user_account, event) def generate_remove_liquidity_event(context: Context, user_account: Account, pair_contract: PairContract): @@ -126,7 +126,7 @@ def generate_remove_liquidity_event(context: Context, user_account: Account, pai set_reserves_event = SetCorrectReservesEvent() context.observable.set_event(pair_contract, user_account, set_reserves_event, '') - txhash = pair_contract.removeLiquidity(context.network_provider, user_account, event) + txhash = pair_contract.remove_liquidity(context.network_provider, user_account, event) context.observable.set_event(pair_contract, user_account, event, txhash) except Exception as ex: @@ -168,7 +168,7 @@ def generate_swap_fixed_input(context: Context, user_account: Account, pair_cont set_reserves_event = SetCorrectReservesEvent() context.observable.set_event(pair_contract, user_account, set_reserves_event, '') - txhash = pair_contract.swapFixedInput(context.network_provider, user_account, event) + txhash = pair_contract.swap_fixed_input(context.network_provider, user_account, event) context.observable.set_event(pair_contract, user_account, event, txhash) except Exception as ex: @@ -210,7 +210,7 @@ def generate_swap_fixed_output(context: Context, user_account: Account, pair_con set_reserves_event = SetCorrectReservesEvent() context.observable.set_event(pair_contract, user_account, set_reserves_event, '') - txhash = pair_contract.swapFixedOutput(context.network_provider, user_account, event) + txhash = pair_contract.swap_fixed_output(context.network_provider, user_account, event) context.observable.set_event(pair_contract, user_account, event, txhash) except Exception as ex: diff --git a/scenarios/scenario_dex_v2_all_in.py b/scenarios/scenario_dex_v2_all_in.py index bb382d4..07e9a1c 100644 --- a/scenarios/scenario_dex_v2_all_in.py +++ b/scenarios/scenario_dex_v2_all_in.py @@ -90,7 +90,7 @@ def add_initial_liquidity(context: Context): pair_contract.firstToken, nominated_amount(1000000), 1, pair_contract.secondToken, nominated_amount(1000000), 1 ) - pair_contract.addLiquidity(context.network_provider, context.deployer_account, event) + pair_contract.add_liquidity(context.network_provider, context.deployer_account, event) time.sleep(6) diff --git a/scenarios/scenario_fees_collector.py b/scenarios/scenario_fees_collector.py index bb382d4..07e9a1c 100644 --- a/scenarios/scenario_fees_collector.py +++ b/scenarios/scenario_fees_collector.py @@ -90,7 +90,7 @@ def add_initial_liquidity(context: Context): pair_contract.firstToken, nominated_amount(1000000), 1, pair_contract.secondToken, nominated_amount(1000000), 1 ) - pair_contract.addLiquidity(context.network_provider, context.deployer_account, event) + pair_contract.add_liquidity(context.network_provider, context.deployer_account, event) time.sleep(6) diff --git a/scenarios/scenario_swaps.py b/scenarios/scenario_swaps.py index f81338b..98049f2 100644 --- a/scenarios/scenario_swaps.py +++ b/scenarios/scenario_swaps.py @@ -90,7 +90,7 @@ def add_initial_liquidity(context: Context): pair_contract.firstToken, nominated_amount(1000000), 1, pair_contract.secondToken, nominated_amount(1000000), 1 ) - pair_contract.addLiquidity(context.network_provider, context.deployer_account, event) + pair_contract.add_liquidity(context.network_provider, context.deployer_account, event) time.sleep(6) diff --git a/scenarios/stress_many_add_remove_liquidity.py b/scenarios/stress_many_add_remove_liquidity.py index d9a0c6b..b674228 100644 --- a/scenarios/stress_many_add_remove_liquidity.py +++ b/scenarios/stress_many_add_remove_liquidity.py @@ -60,7 +60,7 @@ def create_remove_liquidity(pair: Address, caller: Account, token_lp: str, netwo transfers = [(token_lp, 1)] args = [taken_one, taken_two] gas_limit = 750000 - transaction = transfer_multi_esdt_and_execute(pair, caller, transfers, "removeLiquidity", args, gas_limit, network) + transaction = transfer_multi_esdt_and_execute(pair, caller, transfers, "remove_liquidity", args, gas_limit, network) return transaction diff --git a/utils/utils_chain.py b/utils/utils_chain.py index 98cc3fa..0f56a7a 100644 --- a/utils/utils_chain.py +++ b/utils/utils_chain.py @@ -15,9 +15,10 @@ from multiversx_sdk_network_providers import ProxyNetworkProvider from utils import utils_generic -from utils.utils_generic import logger +from utils.logger import get_logger -logger = logging.getLogger("accounts") + +logger = get_logger(__name__) class WrapperAddress(Address): diff --git a/utils/utils_generic.py b/utils/utils_generic.py index d6d6ace..81d0b34 100644 --- a/utils/utils_generic.py +++ b/utils/utils_generic.py @@ -6,7 +6,6 @@ import sys import tarfile import zipfile -from builtins import function import toml from enum import Enum diff --git a/utils/utils_tx.py b/utils/utils_tx.py index 8ef6492..ddad523 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -14,7 +14,8 @@ MultiESDTNFTTransferBuilder, ContractDeploymentBuilder, ContractUpgradeBuilder from utils.logger import get_logger from utils.utils_chain import Account, log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_warning, split_to_chunks, get_continue_confirmation +from utils.utils_generic import print_test_step_fail, print_warning, split_to_chunks, get_continue_confirmation, \ + log_unexpected_args TX_CACHE: Dict[str, dict] = {} logger = get_logger(__name__) @@ -356,17 +357,17 @@ def multi_esdt_endpoint_call(function_purpose: str, proxy: ProxyNetworkProvider, return tx_hash -def multi_esdt_transfer(function_purpose: str, proxy: ProxyNetworkProvider, gas: int, - user: Account, dest: Address, args: list): +def multi_esdt_transfer(proxy: ProxyNetworkProvider, gas: int, user: Account, dest: Address, args: list): """ Expected as args: type[ESDTToken...]: tokens list """ - print_warning(function_purpose) + logger.info(f"Sending multi esdt transfer to {dest}") + logger.debug(f"Args: {args}") network_config = proxy.get_network_config() # TODO: find solution to avoid this call tx_hash = "" if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_unexpected_args(f"send multi esdt transfer", args) return tx_hash tx = prepare_multiesdtnfttransfer_tx(dest, user, network_config, gas, args) @@ -376,12 +377,13 @@ def multi_esdt_transfer(function_purpose: str, proxy: ProxyNetworkProvider, gas: return tx_hash -def endpoint_call(function_purpose: str, proxy: ProxyNetworkProvider, gas: int, - user: Account, contract: Address, endpoint: str, args: list, value: str = "0"): +def endpoint_call(proxy: ProxyNetworkProvider, gas: int, user: Account, contract: Address, endpoint: str, args: list, + value: str = "0"): """ Expected as args: opt: type[str..]: endpoint arguments """ - print_warning(function_purpose) + logger.info(f"Calling {endpoint} at {contract.bech32()}") + logger.debug(f"Args: {args}") network_config = proxy.get_network_config() # TODO: find solution to avoid this call tx = prepare_contract_call_tx(contract, user, network_config, gas, endpoint, args, value) @@ -393,7 +395,8 @@ def endpoint_call(function_purpose: str, proxy: ProxyNetworkProvider, gas: int, def deploy(contract_label: str, proxy: ProxyNetworkProvider, gas: int, owner: Account, bytecode_path: Path, metadata: ICodeMetadata, args: list) -> (str, str): - print_warning(f"Deploy {contract_label} contract") + logger.info(f"Deploy {contract_label} contract") + logger.debug(f"Args: {args}") network_config = proxy.get_network_config() # TODO: find solution to avoid this call tx_hash, contract_address = "", "" @@ -412,9 +415,9 @@ def deploy(contract_label: str, proxy: ProxyNetworkProvider, gas: int, def upgrade_call(contract_label: str, proxy: ProxyNetworkProvider, gas: int, owner: Account, contract: Address, bytecode_path: Path, metadata: ICodeMetadata, args: list) -> str: - print_warning(f"Upgrade {contract_label} contract") + logger.info(f"Upgrade {contract_label} contract") + logger.debug(f"Args: {args}") network_config = proxy.get_network_config() # TODO: find solution to avoid this call - tx_hash = "" tx = prepare_upgrade_tx(owner, contract, network_config, gas, bytecode_path, metadata, args) tx_hash = send_contract_call_tx(tx, proxy) From 44b605951244ce4f871aea4fc0f679b0991d7e14 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Thu, 9 Mar 2023 15:06:10 +0200 Subject: [PATCH 06/26] fixes and adjustments --- config.py | 6 +- context.py | 2 +- contracts/dex_proxy_contract.py | 16 +- contracts/farm_contract.py | 10 +- contracts/fees_collector_contract.py | 4 +- contracts/locked_asset_contract.py | 20 +- contracts/metastaking_contract.py | 12 +- contracts/pair_contract.py | 12 +- contracts/price_discovery_contract.py | 6 +- contracts/proxy_deployer_contract.py | 4 +- contracts/router_contract.py | 4 +- contracts/simple_lock_contract.py | 8 +- contracts/simple_lock_energy_contract.py | 12 +- contracts/staking_contract.py | 8 +- contracts/unstaker_contract.py | 4 +- deploy/dex_structure.py | 164 +++++----- events/event_generators.py | 24 +- scenarios/scenario_dex_v2_all_in.py | 10 +- scenarios/scenario_fees_collector.py | 10 +- scenarios/scenario_simple_lock_energy.py | 6 +- scenarios/scenario_swaps.py | 10 +- scenarios/stress_create_positions.py | 4 +- tools/account_state.py | 14 +- tools/contracts_upgrader.py | 6 +- trackers/farm_economics_tracking.py | 292 +++++++++--------- trackers/metastaking_economics_tracking.py | 10 +- trackers/pair_economics_tracking.py | 120 +++---- .../price_discovery_economics_tracking.py | 124 ++++---- trackers/staking_economics_tracking.py | 122 ++++---- utils/contract_data_fetchers.py | 22 +- utils/logger.py | 28 +- utils/utils_chain.py | 77 +---- utils/utils_generic.py | 26 +- utils/utils_tx.py | 57 ++-- 34 files changed, 610 insertions(+), 644 deletions(-) diff --git a/config.py b/config.py index de5f252..6d610da 100644 --- a/config.py +++ b/config.py @@ -6,10 +6,10 @@ ZERO_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu" DEFAULT_WORKSPACE = Path(__file__).parent DEFAULT_ACCOUNTS = DEFAULT_WORKSPACE.absolute() / "wallets" / "C10.pem" -DEFAULT_OWNER = DEFAULT_WORKSPACE.absolute() / "wallets" / "devnet_wallet.pem" +DEFAULT_OWNER = DEFAULT_WORKSPACE.absolute() / "wallets" / "C1.pem" DEFAULT_ADMIN = DEFAULT_WORKSPACE.absolute() / "wallets" / "C1_1.pem" -DEFAULT_PROXY = "https://devnet-gateway.elrond.com" -DEFAULT_API = "https://devnet-api.elrond.com" +DEFAULT_PROXY = "https://devnet-gateway.multiversx.com" +DEFAULT_API = "https://devnet-api.multiversx.com" HISTORY_PROXY = "" DEFAULT_ISSUE_TOKEN_PRICE = 50000000000000000 # TODO: try to override this with testnet define to tidy code up diff --git a/context.py b/context.py index f04852c..8c378af 100644 --- a/context.py +++ b/context.py @@ -60,7 +60,7 @@ def __init__(self): # deploy closing self.deploy_structure.print_deployed_contracts() - self.observable = self.init_observers() + # self.observable = self.init_observers() def init_observers(self): observable = Observable() diff --git a/contracts/dex_proxy_contract.py b/contracts/dex_proxy_contract.py index 86150a6..58f2ebf 100644 --- a/contracts/dex_proxy_contract.py +++ b/contracts/dex_proxy_contract.py @@ -5,7 +5,7 @@ from utils.logger import get_logger from utils.utils_tx import deploy, upgrade_call, \ endpoint_call, multi_esdt_endpoint_call, ESDTToken -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, \ +from utils.utils_generic import log_step_fail, log_step_pass, log_substep, \ log_unexpected_args from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -200,7 +200,7 @@ def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, byteco return "", "" if len(self.locked_tokens) != len(args[0]): - print_test_step_fail(f"FAIL: Failed to deploy contract. " + log_step_fail(f"FAIL: Failed to deploy contract. " f"Mismatch between locked tokens and factory addresses.") return "", "" @@ -228,7 +228,7 @@ def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytec return "", "" if not no_init and len(self.locked_tokens) != len(args[0]): - print_test_step_fail(f"FAIL: Failed to upgrade contract. " + log_step_fail(f"FAIL: Failed to upgrade contract. " f"Mismatch between locked tokens and factory addresses.") return "", "" @@ -350,8 +350,8 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l pass def print_contract_info(self): - print_test_step_pass(f"Deployed proxy contract: {self.address}") - print_test_substep(f"Token: {self.token}") - print_test_substep(f"Locked tokens: {self.locked_tokens}") - print_test_substep(f"Proxy LP token: {self.proxy_lp_token}") - print_test_substep(f"Proxy Farm token: {self.proxy_farm_token}") + log_step_pass(f"Deployed proxy contract: {self.address}") + log_substep(f"Token: {self.token}") + log_substep(f"Locked tokens: {self.locked_tokens}") + log_substep(f"Proxy LP token: {self.proxy_lp_token}") + log_substep(f"Proxy Farm token: {self.proxy_farm_token}") diff --git a/contracts/farm_contract.py b/contracts/farm_contract.py index 0dfcce3..a71a41c 100644 --- a/contracts/farm_contract.py +++ b/contracts/farm_contract.py @@ -9,7 +9,7 @@ from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning, \ +from utils.utils_generic import log_step_fail, log_step_pass, log_substep, log_warning, \ log_unexpected_args from events.farm_events import (EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent, CompoundRewardsFarmEvent, MigratePositionFarmEvent) @@ -400,7 +400,7 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l _ = self.resume(deployer, proxy) def print_contract_info(self): - print_test_step_pass(f"Deployed farm contract: {self.address}") - print_test_substep(f"Farming token: {self.farmingToken}") - print_test_substep(f"Farmed token: {self.farmedToken}") - print_test_substep(f"Farm token: {self.farmToken}") + log_step_pass(f"Deployed farm contract: {self.address}") + log_substep(f"Farming token: {self.farmingToken}") + log_substep(f"Farmed token: {self.farmedToken}") + log_substep(f"Farm token: {self.farmToken}") diff --git a/contracts/fees_collector_contract.py b/contracts/fees_collector_contract.py index 3095321..683693f 100644 --- a/contracts/fees_collector_contract.py +++ b/contracts/fees_collector_contract.py @@ -4,7 +4,7 @@ from contracts.contract_identities import DEXContractInterface from utils.logger import get_logger from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, deploy, endpoint_call -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning, log_unexpected_args +from utils.utils_generic import log_step_fail, log_step_pass, log_warning, log_unexpected_args from utils.utils_chain import Account, WrapperAddress as Address, log_explorer_transaction from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -176,4 +176,4 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l pass def print_contract_info(self): - print_test_step_pass(f"Deployed fees collector contract: {self.address}") + log_step_pass(f"Deployed fees collector contract: {self.address}") diff --git a/contracts/locked_asset_contract.py b/contracts/locked_asset_contract.py index 09a5d9b..74561d3 100644 --- a/contracts/locked_asset_contract.py +++ b/contracts/locked_asset_contract.py @@ -3,7 +3,7 @@ from contracts.contract_identities import DEXContractInterface from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, deploy, upgrade_call, endpoint_call -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, log_unexpected_args +from utils.utils_generic import log_step_fail, log_step_pass, log_substep, log_unexpected_args from utils.utils_chain import Account, WrapperAddress as Address, log_explorer_transaction from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -41,12 +41,12 @@ def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, byteco arguments = [ self.unlocked_asset, - 93457, - 101137, - 108817, - 116497, - 124176, - 131856, + bytes.fromhex("000000000000016D11"), + bytes.fromhex("000000000000018B11"), + bytes.fromhex("00000000000001A911"), + bytes.fromhex("00000000000001C711"), + bytes.fromhex("00000000000001E510"), + bytes.fromhex("000000000000020310"), ] tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) @@ -159,6 +159,6 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l pass def print_contract_info(self): - print_test_step_pass(f"Deployed locked asset contract: {self.address}") - print_test_substep(f"Unlocked token: {self.unlocked_asset}") - print_test_substep(f"Locked token: {self.locked_asset}") + log_step_pass(f"Deployed locked asset contract: {self.address}") + log_substep(f"Unlocked token: {self.unlocked_asset}") + log_substep(f"Locked token: {self.locked_asset}") diff --git a/contracts/metastaking_contract.py b/contracts/metastaking_contract.py index 03f7bbe..79a18b8 100644 --- a/contracts/metastaking_contract.py +++ b/contracts/metastaking_contract.py @@ -11,7 +11,7 @@ from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning, \ +from utils.utils_generic import log_step_fail, log_step_pass, log_substep, log_warning, \ log_unexpected_args logger = get_logger(__name__) @@ -133,11 +133,11 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l pass def print_contract_info(self): - print_test_step_pass(f"Deployed metastaking contract: {self.address}") - print_test_substep(f"Staking token: {self.staking_token}") - print_test_substep(f"Stake address: {self.stake_address}") - print_test_substep(f"Farm address: {self.farm_address}") - print_test_substep(f"LP address: {self.lp_address}") + log_step_pass(f"Deployed metastaking contract: {self.address}") + log_substep(f"Staking token: {self.staking_token}") + log_substep(f"Stake address: {self.stake_address}") + log_substep(f"Farm address: {self.farm_address}") + log_substep(f"LP address: {self.lp_address}") def enter_metastake(self, network_provider: NetworkProviders, user: Account, event: EnterMetastakeEvent, initial: bool = False) -> str: diff --git a/contracts/pair_contract.py b/contracts/pair_contract.py index d1ebdc8..23f782f 100644 --- a/contracts/pair_contract.py +++ b/contracts/pair_contract.py @@ -4,7 +4,7 @@ from contracts.contract_identities import (DEXContractInterface, PairContractVersion) from utils.logger import get_logger from utils.utils_tx import NetworkProviders, endpoint_call, upgrade_call, deploy, ESDTToken, multi_esdt_endpoint_call -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, log_unexpected_args +from utils.utils_generic import log_step_fail, log_step_pass, log_substep, log_unexpected_args from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -177,7 +177,7 @@ def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, byteco gas_limit = 200000000 if len(args) < 5: - print_test_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") + log_step_fail(f"FAIL: Failed to deploy contract. Args list not as expected.") return "", "" arguments = [ @@ -399,7 +399,7 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l _ = self.resume(deployer, proxy) def print_contract_info(self): - print_test_step_pass(f"Deployed pair contract: {self.address}") - print_test_substep(f"First token: {self.firstToken}") - print_test_substep(f"Second token: {self.secondToken}") - print_test_substep(f"LP token: {self.lpToken}") + log_step_pass(f"Deployed pair contract: {self.address}") + log_substep(f"First token: {self.firstToken}") + log_substep(f"Second token: {self.secondToken}") + log_substep(f"LP token: {self.lpToken}") diff --git a/contracts/price_discovery_contract.py b/contracts/price_discovery_contract.py index a3913ac..b4d7220 100644 --- a/contracts/price_discovery_contract.py +++ b/contracts/price_discovery_contract.py @@ -9,7 +9,7 @@ from events.price_discovery_events import (DepositPDLiquidityEvent, WithdrawPDLiquidityEvent, RedeemPDLPTokensEvent) from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep, print_warning +from utils.utils_generic import log_step_fail, log_step_pass, log_substep, log_warning from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -190,5 +190,5 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l pass def print_contract_info(self): - print_test_step_pass(f"Deployed price discovery contract: {self.address}") - print_test_substep(f"Redeem token: {self.redeem_token}") + log_step_pass(f"Deployed price discovery contract: {self.address}") + log_substep(f"Redeem token: {self.redeem_token}") diff --git a/contracts/proxy_deployer_contract.py b/contracts/proxy_deployer_contract.py index fbed254..495a889 100644 --- a/contracts/proxy_deployer_contract.py +++ b/contracts/proxy_deployer_contract.py @@ -6,7 +6,7 @@ from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event, deploy, \ endpoint_call, get_deployed_address_from_tx from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning, log_unexpected_args +from utils.utils_generic import log_step_fail, log_step_pass, log_warning, log_unexpected_args from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -120,4 +120,4 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l pass def print_contract_info(self): - print_test_step_pass(f"Deployed proxy deployer contract: {self.address}") + log_step_pass(f"Deployed proxy deployer contract: {self.address}") diff --git a/contracts/router_contract.py b/contracts/router_contract.py index 7778842..8df72b7 100644 --- a/contracts/router_contract.py +++ b/contracts/router_contract.py @@ -2,7 +2,7 @@ from contracts.contract_identities import DEXContractInterface, RouterContractVersion from utils.logger import get_logger from utils.utils_tx import deploy, upgrade_call, get_deployed_address_from_tx, endpoint_call -from utils.utils_generic import print_test_step_pass, log_unexpected_args +from utils.utils_generic import log_step_pass, log_unexpected_args from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -218,4 +218,4 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l pass def print_contract_info(self): - print_test_step_pass(f"Deployed router contract: {self.address}") + log_step_pass(f"Deployed router contract: {self.address}") diff --git a/contracts/simple_lock_contract.py b/contracts/simple_lock_contract.py index 8fb8db7..bcaa9d0 100644 --- a/contracts/simple_lock_contract.py +++ b/contracts/simple_lock_contract.py @@ -2,7 +2,7 @@ from contracts.contract_identities import DEXContractInterface from utils.logger import get_logger from utils.utils_tx import deploy, endpoint_call -from utils.utils_generic import print_test_step_pass, print_test_substep, log_unexpected_args +from utils.utils_generic import log_step_pass, log_substep, log_unexpected_args from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -110,6 +110,6 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l pass def print_contract_info(self): - print_test_step_pass(f"Deployed simple lock contract: {self.address}") - print_test_substep(f"Locked token: {self.locked_token}") - print_test_substep(f"Locked LP token: {self.lp_proxy_token}") + log_step_pass(f"Deployed simple lock contract: {self.address}") + log_substep(f"Locked token: {self.locked_token}") + log_substep(f"Locked LP token: {self.lp_proxy_token}") diff --git a/contracts/simple_lock_energy_contract.py b/contracts/simple_lock_energy_contract.py index 67bc13d..2c89cae 100644 --- a/contracts/simple_lock_energy_contract.py +++ b/contracts/simple_lock_energy_contract.py @@ -1,7 +1,7 @@ from contracts.contract_identities import DEXContractInterface from utils.logger import get_logger from utils.utils_tx import multi_esdt_endpoint_call, endpoint_call, deploy, upgrade_call -from utils.utils_generic import print_test_step_pass, print_test_substep, log_unexpected_args +from utils.utils_generic import log_step_pass, log_substep, log_unexpected_args from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -450,8 +450,8 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l self.resume(deployer, proxy) def print_contract_info(self): - print_test_step_pass(f"Deployed simple lock energy contract: {self.address}") - print_test_substep(f"Base token: {self.base_token}") - print_test_substep(f"Locked token: {self.locked_token}") - print_test_substep(f"Locked LP token: {self.lp_proxy_token}") - print_test_substep(f"Locked Farm token: {self.farm_proxy_token}") + log_step_pass(f"Deployed simple lock energy contract: {self.address}") + log_substep(f"Base token: {self.base_token}") + log_substep(f"Locked token: {self.locked_token}") + log_substep(f"Locked LP token: {self.lp_proxy_token}") + log_substep(f"Locked Farm token: {self.farm_proxy_token}") diff --git a/contracts/staking_contract.py b/contracts/staking_contract.py index 40c21c5..d215875 100644 --- a/contracts/staking_contract.py +++ b/contracts/staking_contract.py @@ -5,7 +5,7 @@ from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider -from utils.utils_generic import print_test_step_pass, print_test_substep, log_unexpected_args +from utils.utils_generic import log_step_pass, log_substep, log_unexpected_args from events.farm_events import (EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent, CompoundRewardsFarmEvent) @@ -239,6 +239,6 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l _ = self.resume(deployer, proxy) def print_contract_info(self): - print_test_step_pass(f"Deployed staking contract: {self.address}") - print_test_substep(f"Staking token: {self.farming_token}") - print_test_substep(f"Stake token: {self.farm_token}") + log_step_pass(f"Deployed staking contract: {self.address}") + log_substep(f"Staking token: {self.farming_token}") + log_substep(f"Stake token: {self.farm_token}") diff --git a/contracts/unstaker_contract.py b/contracts/unstaker_contract.py index 562eb50..a9a57a5 100644 --- a/contracts/unstaker_contract.py +++ b/contracts/unstaker_contract.py @@ -5,7 +5,7 @@ from utils.logger import get_logger from utils.utils_tx import endpoint_call, deploy from utils.utils_chain import log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_test_step_pass, log_unexpected_args +from utils.utils_generic import log_step_fail, log_step_pass, log_unexpected_args from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -81,4 +81,4 @@ def contract_start(self, deployer: Account, proxy: ProxyNetworkProvider, args: l pass def print_contract_info(self): - print_test_step_pass(f"Deployed token unstake contract: {self.address}") + log_step_pass(f"Deployed token unstake contract: {self.address}") diff --git a/deploy/dex_structure.py b/deploy/dex_structure.py index 3480333..d00f6d1 100644 --- a/deploy/dex_structure.py +++ b/deploy/dex_structure.py @@ -27,8 +27,8 @@ from utils.utils_tx import NetworkProviders from utils.utils_chain import hex_to_string from utils.utils_chain import Account, WrapperAddress as Address -from utils.utils_generic import write_json_file, read_json_file, print_test_step_fail, print_test_step_pass, \ - print_warning +from utils.utils_generic import write_json_file, read_json_file, log_step_fail, log_step_pass, \ + log_warning from deploy import populate_deploy_lists @@ -53,7 +53,7 @@ def save_deployed_contracts(self): Path(config.DEFAULT_CONFIG_SAVE_PATH).mkdir(parents=True, exist_ok=True) write_json_file(filepath, dump) - print_test_step_pass(f"Saved deployed {self.label} contracts.") + log_step_pass(f"Saved deployed {self.label} contracts.") def get_saved_deployed_contracts(self) -> list: contracts_list = [] @@ -87,14 +87,14 @@ def load_deployed_contracts(self): contracts_list = self.get_saved_deployed_contracts() if len(self.deploy_structure_list) == len(contracts_list): self.deployed_contracts = contracts_list - print_test_step_pass(f"Loaded {len(contracts_list)} {self.label}.") + log_step_pass(f"Loaded {len(contracts_list)} {self.label}.") return - print_test_step_fail(f"No contracts fetched for: {self.label}; " + log_step_fail(f"No contracts fetched for: {self.label}; " f"Either no save available or mismatch between deploy structure and loaded contracts.") def print_deployed_contracts(self): - print_test_step_pass(f"{self.label}:") + log_step_pass(f"{self.label}:") for contract in self.deployed_contracts: contract.print_contract_info() @@ -191,7 +191,7 @@ def deploy_tokens(self, deployer_account: Account, network_provider: NetworkProv token_hashes.extend(hashes) for txhash in token_hashes: - network_provider.api.wait_for_tx_finalized(txhash) + network_provider.wait_for_tx_executed(txhash) time.sleep(40) @@ -214,22 +214,22 @@ def save_deployed_tokens(self): if self.tokens: filepath = config.DEFAULT_CONFIG_SAVE_PATH / "deployed_tokens.json" write_json_file(filepath, self.tokens) - print_test_step_pass("Saved deployed tokens.") + log_step_pass("Saved deployed tokens.") else: - print_test_step_fail("No tokens to save!") + log_step_fail("No tokens to save!") def get_saved_deployed_tokens(self) -> list: filepath = config.DEFAULT_CONFIG_SAVE_PATH / "deployed_tokens.json" retrieved_tokens = read_json_file(filepath) if retrieved_tokens and len(retrieved_tokens) == self.number_of_tokens: - print_test_step_pass(f"Loaded {len(retrieved_tokens)} tokens.") + log_step_pass(f"Loaded {len(retrieved_tokens)} tokens.") return retrieved_tokens elif retrieved_tokens and len(retrieved_tokens) >= self.number_of_tokens: - print_test_step_fail(f"Loaded {len(retrieved_tokens)} tokens instead of expected {self.number_of_tokens}.") + log_step_fail(f"Loaded {len(retrieved_tokens)} tokens instead of expected {self.number_of_tokens}.") return retrieved_tokens else: - print_test_step_fail("No tokens loaded!") + log_step_fail("No tokens loaded!") return [] def load_deployed_tokens(self) -> bool: @@ -245,7 +245,7 @@ def save_deployed_contracts(self): contracts.save_deployed_contracts() def print_deployed_contracts(self): - print_test_step_pass(f"Deployed contracts below:") + log_step_pass(f"Deployed contracts below:") for contracts in self.contracts.values(): contracts.print_deployed_contracts() print("") @@ -258,14 +258,14 @@ def deploy_structure(self, deployer_account: Account, network_provider: NetworkP if not clean_deploy_override and not contracts.deploy_clean: contracts.load_deployed_contracts() else: - print_test_step_pass(f"Starting setup process for {contract_label}:") + log_step_pass(f"Starting setup process for {contract_label}:") contracts.deploy_function(contract_label, deployer_account, network_provider) if len(contracts.deployed_contracts) > 0: contracts.print_deployed_contracts() self.contracts[contract_label] = contracts contracts.save_deployed_contracts() else: - print_warning(f"No contracts deployed for {contract_label}!") + log_warning(f"No contracts deployed for {contract_label}!") # should be run for fresh deployed contracts def start_deployed_contracts(self, deployer_account: Account, network_provider: NetworkProviders, @@ -321,7 +321,7 @@ def set_proxy_v2_in_pairs(self, deployer_account: Account, network_providers: Ne # Set proxy in pairs if len(pair_contracts.deploy_structure_list) != len(pair_contracts.deployed_contracts): - print_test_step_fail(f"Uneven length of pair deployed contracts! Skipping.") + log_step_fail(f"Uneven length of pair deployed contracts! Skipping.") return for index, config_pair in enumerate(pair_contracts.deploy_structure_list): if search_label in config_pair: @@ -332,7 +332,7 @@ def set_proxy_v2_in_pairs(self, deployer_account: Account, network_providers: Ne config_pair[search_label]) if not proxy_contract: - print_test_step_fail(f"Configured proxy not found for pair: {pair_contract.address}") + log_step_fail(f"Configured proxy not found for pair: {pair_contract.address}") tx_hash = proxy_contract.add_pair_to_intermediate(deployer_account, network_providers.proxy, pair_contract.address) @@ -367,7 +367,7 @@ def locked_asset_deploy(self, contracts_index: str, deployer_account: Account, n # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "locked asset"): return deployed_locked_asset_contract.address = contract_address - print_test_step_pass(f"Locked asset contract address: {contract_address}") + log_step_pass(f"Locked asset contract address: {contract_address}") # register locked token and save it tx_hash = deployed_locked_asset_contract.register_locked_asset_token(deployer_account, @@ -402,7 +402,7 @@ def proxy_deploy(self, contracts_index: str, deployer_account: Account, network_ elif contracts_index == config.PROXIES_V2: version = ProxyContractVersion.V2 else: - print_test_step_fail(f"Aborting deploy: Unsupported proxy label.") + log_step_fail(f"Aborting deploy: Unsupported proxy label.") return # deploy proxy contract @@ -414,7 +414,7 @@ def proxy_deploy(self, contracts_index: str, deployer_account: Account, network_ if version == ProxyContractVersion.V1 or ProxyContractVersion.V2: if 'locked_asset' not in config_proxy and version == ProxyContractVersion.V1: - print_test_step_fail(f"Aborting deploy: locked asset not configured.") + log_step_fail(f"Aborting deploy: locked asset not configured.") return locked_asset_contract = self.contracts[config.LOCKED_ASSETS].\ @@ -424,18 +424,18 @@ def proxy_deploy(self, contracts_index: str, deployer_account: Account, network_ locked_assets.append(locked_asset_contract.locked_asset) factory_addresses.append(locked_asset_contract.address) elif version == ProxyContractVersion.V1: - print_test_step_fail(f"Aborting deploy: locked asset contract not available.") + log_step_fail(f"Aborting deploy: locked asset contract not available.") return if version == ProxyContractVersion.V2: if 'energy_factory' not in config_proxy: - print_test_step_fail(f"Aborting deploy: energy factory not configured.") + log_step_fail(f"Aborting deploy: energy factory not configured.") return energy_contract = self.contracts[config.SIMPLE_LOCKS_ENERGY].\ get_deployed_contract_by_index(config_proxy["energy_factory"]) if asset and asset != energy_contract.base_token: - print_test_step_fail(f"Aborting deploy: Mismatch configuration in base tokens.") + log_step_fail(f"Aborting deploy: Mismatch configuration in base tokens.") return elif not asset: asset = energy_contract.base_token @@ -456,7 +456,7 @@ def proxy_deploy(self, contracts_index: str, deployer_account: Account, network_ # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "proxy"): return deployed_proxy_contract.address = contract_address - print_test_step_pass(f"Proxy contract address: {contract_address}") + log_step_pass(f"Proxy contract address: {contract_address}") # register proxy lp token and save it tx_hash = deployed_proxy_contract.register_proxy_lp_token(deployer_account, @@ -522,7 +522,7 @@ def simple_lock_deploy(self, contracts_index: str, deployer_account: Account, ne # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "simple lock"): return deployed_simple_lock_contract.address = contract_address - print_test_step_pass(f"Simple lock contract address: {contract_address}") + log_step_pass(f"Simple lock contract address: {contract_address}") # issue locked token and save it tx_hash = deployed_simple_lock_contract.issue_locked_token(deployer_account, @@ -554,7 +554,7 @@ def fees_collector_deploy(self, contracts_index: str, deployer_account: Account, contract_config['energy_factory'] ) else: - print_test_step_fail(f"Aborting deploy: Energy factory not configured! Contract will be dumped.") + log_step_fail(f"Aborting deploy: Energy factory not configured! Contract will be dumped.") return # deploy contract @@ -565,7 +565,7 @@ def fees_collector_deploy(self, contracts_index: str, deployer_account: Account, # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "fees collector"): return deployed_contract.address = contract_address - print_test_step_pass(f"Fees collector contract address: {contract_address}") + log_step_pass(f"Fees collector contract address: {contract_address}") # set energy factory in fees collector tx_hash = deployed_contract.set_energy_factory_address(deployer_account, network_providers.proxy, @@ -610,10 +610,10 @@ def simple_lock_energy_deploy(self, contracts_index: str, deployer_account: Acco locked_asset_factory = self.contracts[config.LOCKED_ASSETS].get_deployed_contract_by_index( contract_config['locked_asset_factory']) if locked_asset_factory is None: - print_test_step_fail(f"Aborting deploy: Locked asset factory contract not available! Contract will be dumped.") + log_step_fail(f"Aborting deploy: Locked asset factory contract not available! Contract will be dumped.") return else: - print_test_step_fail(f"Aborting deploy: Locked asset factory not configured! Contract will be dumped.") + log_step_fail(f"Aborting deploy: Locked asset factory not configured! Contract will be dumped.") return # deploy contract @@ -626,7 +626,7 @@ def simple_lock_energy_deploy(self, contracts_index: str, deployer_account: Acco # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "simple lock energy"): return deployed_contract.address = contract_address - print_test_step_pass(f"Simple lock energy contract address: {contract_address}") + log_step_pass(f"Simple lock energy contract address: {contract_address}") # issue locked token and save it tx_hash = deployed_contract.issue_locked_token(deployer_account, @@ -666,11 +666,11 @@ def token_unstake_deploy(self, contracts_index: str, deployer_account: Account, fees_collector = self.contracts[config.FEES_COLLECTORS].get_deployed_contract_by_index( contract_config["fees_collector"]) if fees_collector is None: - print_test_step_fail(f"Aborting deploy: Fees collector contract not available! " + log_step_fail(f"Aborting deploy: Fees collector contract not available! " f"Contract will be dumped.") return else: - print_test_step_fail( + log_step_fail( f"Aborting deploy: Fees collector not configured! Contract will be dumped.") return @@ -679,11 +679,11 @@ def token_unstake_deploy(self, contracts_index: str, deployer_account: Account, energy_factory = self.contracts[config.SIMPLE_LOCKS_ENERGY].get_deployed_contract_by_index( contract_config['energy_factory']) if energy_factory is None: - print_test_step_fail(f"Aborting deploy: Energy factory contract not available! " + log_step_fail(f"Aborting deploy: Energy factory contract not available! " f"Contract will be dumped.") return else: - print_test_step_fail(f"Aborting deploy: Energy factory not configured! Contract will be dumped.") + log_step_fail(f"Aborting deploy: Energy factory not configured! Contract will be dumped.") return # deploy contract @@ -695,7 +695,7 @@ def token_unstake_deploy(self, contracts_index: str, deployer_account: Account, # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "token unstake"): return deployed_contract.address = contract_address - print_test_step_pass(f"Token unstake contract address: {contract_address}") + log_step_pass(f"Token unstake contract address: {contract_address}") # Set special role on unlocked asset for burning base token tx_hash = self.esdt_contract.set_special_role_token(deployer_account, @@ -763,7 +763,7 @@ def router_deploy(self, contracts_index: str, deployer_account: Account, network if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "router"): return router_contract.address = contract_address - print_test_step_pass(f"Router contract address: {contract_address}") + log_step_pass(f"Router contract address: {contract_address}") deployed_contracts.append(router_contract) self.contracts[contracts_index].deployed_contracts = deployed_contracts @@ -787,7 +787,7 @@ def pool_deploy_from_router(self, contracts_index: str, deployer_account: Accoun router_contract = self.contracts[used_router_label].get_deployed_contract_by_index(0) if router_contract is None: - print_test_step_fail(f"Aborting deploy: Router contract not available! Contract will be dumped.") + log_step_fail(f"Aborting deploy: Router contract not available! Contract will be dumped.") return # deploy contract @@ -805,7 +805,7 @@ def pool_deploy_from_router(self, contracts_index: str, deployer_account: Accoun # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "pair via router"): return deployed_pair_contract.address = contract_address - print_test_step_pass(f"Pair contract address: {contract_address}") + log_step_pass(f"Pair contract address: {contract_address}") # issue LP token and save it tx_hash = deployed_pair_contract.issue_lp_token_via_router(deployer_account, network_providers.proxy, @@ -831,7 +831,7 @@ def pool_deploy_from_router(self, contracts_index: str, deployer_account: Accoun proxy_contract = self.contracts[config.PROXIES_V2].\ get_deployed_contract_by_index(config_pool["proxy_v2"]) if proxy_contract is None: - print_test_step_fail(f"Aborting setup: Proxy contract not available! Contract will be dumped.") + log_step_fail(f"Aborting setup: Proxy contract not available! Contract will be dumped.") return tx_hash = proxy_contract.add_pair_to_intermediate(deployer_account, network_providers.proxy, contract_address) @@ -856,7 +856,7 @@ def pool_deploy_from_router(self, contracts_index: str, deployer_account: Accoun deployed_simple_lock: Optional[SimpleLockContract] = None deployed_simple_lock = self.contracts[config.SIMPLE_LOCKS].get_deployed_contract_by_index(config_pool['simple_lock']) if deployed_simple_lock is None: - print_test_step_fail(f"Aborting setup: Simple lock contract not available! Contract will be dumped.") + log_step_fail(f"Aborting setup: Simple lock contract not available! Contract will be dumped.") return # add simple lock address in pair tx_hash = deployed_pair_contract.set_locking_sc_address(deployer_account, network_providers.proxy, @@ -874,10 +874,10 @@ def pool_deploy_from_router(self, contracts_index: str, deployer_account: Accoun fees_collector: Optional[FeesCollectorContract] = None fees_collector = self.contracts[config.FEES_COLLECTORS].get_deployed_contract_by_index(config_pool['fees_collector']) if fees_collector is None: - print_test_step_fail(f"Aborting setup: Fees collector contract not available! Contract will be dumped.") + log_step_fail(f"Aborting setup: Fees collector contract not available! Contract will be dumped.") return if 'fees_collector_cut' not in config_pool: - print_test_step_fail(f"Aborting setup: fees_collector_cut not available in config! Contract will be dumped.") + log_step_fail(f"Aborting setup: fees_collector_cut not available in config! Contract will be dumped.") return fees_cut = config_pool['fees_collector_cut'] @@ -910,7 +910,7 @@ def farm_deploy(self, contracts_index: str, deployer_account: Account, network_p if version == FarmContractVersion.V14Locked: if not self.contracts[config.LOCKED_ASSETS].deployed_contracts: - print_test_step_fail("Aborting deploy for farm locked. Locked asset contract not existing.") + log_step_fail("Aborting deploy for farm locked. Locked asset contract not existing.") return locked_asset_contract = self.contracts[config.LOCKED_ASSETS].deployed_contracts[0] locked_asset_address = locked_asset_contract.address @@ -925,7 +925,7 @@ def farm_deploy(self, contracts_index: str, deployer_account: Account, network_p farming_token = lp_contract.lpToken lp_address = lp_contract.address else: - print_test_step_fail(f'Aborting deploy: farming token/pool not configured!') + log_step_fail(f'Aborting deploy: farming token/pool not configured!') return # deploy contract @@ -941,7 +941,7 @@ def farm_deploy(self, contracts_index: str, deployer_account: Account, network_p # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "farm"): return deployed_farm_contract.address = contract_address - print_test_step_pass(f"Farm contract address: {contract_address}") + log_step_pass(f"Farm contract address: {contract_address}") # register farm token and save it tx_hash = deployed_farm_contract.register_farm_token(deployer_account, network_providers.proxy, farm_token) @@ -998,20 +998,20 @@ def proxy_deployer_deploy(self, contracts_index: str, deployer_account: Account, for contract_config in contract_structure.deploy_structure_list: # deploy template contract if "template" not in contract_config: - print_test_step_fail(f"Aborting deploy: template not configured") + log_step_fail(f"Aborting deploy: template not configured") return template_name = contract_config['template'] if template_name in self.contracts: contract_bytecode = self.contracts[contract_config['template']].bytecode else: - print_test_step_fail("Aborting deploy: Template for proxy deployer not valid.") + log_step_fail("Aborting deploy: Template for proxy deployer not valid.") return if template_name == config.FARMS_V2: version = FarmContractVersion.V2Boosted else: - print_test_step_fail(f"Aborting deploy: invalid template configured") + log_step_fail(f"Aborting deploy: invalid template configured") return template_contract = FarmContract( @@ -1035,7 +1035,7 @@ def proxy_deployer_deploy(self, contracts_index: str, deployer_account: Account, if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "proxy deployer"): return contract.address = contract_address - print_test_step_pass(f"Proxy deployer contract address: {contract_address}") + log_step_pass(f"Proxy deployer contract address: {contract_address}") deployed_contracts.append(contract) self.contracts[contracts_index].deployed_contracts = deployed_contracts @@ -1049,7 +1049,7 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account # deploy farm contract from proxy deployer # get deployer proxy contract if "deployer" not in contract_config: - print_test_step_fail(f"Aborting deploy: deployer not configured") + log_step_fail(f"Aborting deploy: deployer not configured") return deployer_contract: Optional[ProxyDeployerContract] = \ @@ -1057,7 +1057,7 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account contract_config['deployer']) if deployer_contract is None: - print_test_step_fail(f"Aborting deploy: deployer contract not available") + log_step_fail(f"Aborting deploy: deployer contract not available") # determine version version = None @@ -1066,7 +1066,7 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account # get lock factory if 'lock_factory' not in contract_config: - print_test_step_fail("Aborting deploy: Locked factory contract not existing!") + log_step_fail("Aborting deploy: Locked factory contract not existing!") return locking_contract: Optional[SimpleLockEnergyContract] = None locking_contract = self.contracts[config.SIMPLE_LOCKS_ENERGY].get_deployed_contract_by_index( @@ -1085,12 +1085,12 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account lp_contract = self.contracts[config.PAIRS_V2].get_deployed_contract_by_index( contract_config['farming_pool']) if lp_contract is None: - print_test_step_fail(f'Aborting deploy: farming pool v2 not existing!') + log_step_fail(f'Aborting deploy: farming pool v2 not existing!') return farming_token = lp_contract.lpToken lp_address = lp_contract.address else: - print_test_step_fail(f'Aborting deploy: farming token/pool not configured!') + log_step_fail(f'Aborting deploy: farming token/pool not configured!') return # deploy contract @@ -1105,7 +1105,7 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "boosted farm"): return deployed_contract.address = contract_address - print_test_step_pass(f"Farm contract address: {contract_address}") + log_step_pass(f"Farm contract address: {contract_address}") # register farm token and save it tx_hash = deployed_contract.register_farm_token(deployer_account, network_providers.proxy, @@ -1131,7 +1131,7 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account locking_contract.address]) if not network_providers.check_simple_tx_status(tx_hash, "set energy address in farm"): return else: - print_test_step_fail(f"Failed to set up energy contract in farm. Energy contract not available!") + log_step_fail(f"Failed to set up energy contract in farm. Energy contract not available!") return # Set locking contract @@ -1145,13 +1145,13 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account locking_contract.address]) if not network_providers.check_simple_tx_status(tx_hash, "set locking address in farm"): return else: - print_test_step_fail(f"Failed to set up locking contract in farm. Locking contract not available!") + log_step_fail(f"Failed to set up locking contract in farm. Locking contract not available!") return # Set lock epochs if 'lock_epochs' not in contract_config: lock_epochs = 1440 - print_test_step_fail(f"Rewards per block not configured! Setting default: {lock_epochs}") + log_step_fail(f"Rewards per block not configured! Setting default: {lock_epochs}") else: lock_epochs = contract_config['lock_epochs'] # tx_hash = deployed_contract.set_lock_epochs(deployer_account, network_providers.proxy, @@ -1166,7 +1166,7 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account # Set boosted yields rewards percentage if 'boosted_rewards' not in contract_config: boosted_rewards = 6000 - print_test_step_fail(f"Boosted yields rewards percentage configured! Setting default: {boosted_rewards}") + log_step_fail(f"Boosted yields rewards percentage configured! Setting default: {boosted_rewards}") else: boosted_rewards = contract_config['boosted_rewards'] tx_hash = deployed_contract.set_boosted_yields_rewards_percentage(deployer_account, network_providers.proxy, @@ -1180,7 +1180,7 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account "farm_const" not in contract_config or \ "min_energy" not in contract_config or \ "min_farm" not in contract_config: - print_test_step_fail(f"Aborting deploy: Boosted yields factors not configured!") + log_step_fail(f"Aborting deploy: Boosted yields factors not configured!") tx_hash = deployed_contract.set_boosted_yields_factors(deployer_account, network_providers.proxy, [contract_config['base_const'], contract_config['energy_const'], @@ -1193,7 +1193,7 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account # set rewards per block if 'rpb' not in contract_config: rpb = 10000 - print_test_step_fail(f"Rewards per block not configured! Setting default: {rpb}") + log_step_fail(f"Rewards per block not configured! Setting default: {rpb}") else: rpb = contract_config['rpb'] tx_hash = deployed_contract.set_rewards_per_block(deployer_account, network_providers.proxy, @@ -1202,7 +1202,7 @@ def farm_deploy_from_proxy_deployer(self, contracts_index: str, deployer_account # set penalty percent if 'penalty' not in contract_config: - print_test_step_fail(f"Penalty percent not configured! Setting default: 0") + log_step_fail(f"Penalty percent not configured! Setting default: 0") penalty = 0 else: penalty = contract_config['penalty'] @@ -1225,7 +1225,7 @@ def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, n for contract_config in contract_structure.deploy_structure_list: # get lock factory if 'lock_factory' not in contract_config: - print_test_step_fail("Aborting deploy: Locked factory contract not existing!") + log_step_fail("Aborting deploy: Locked factory contract not existing!") return locking_contract: Optional[SimpleLockEnergyContract] = None locking_contract = self.contracts[config.SIMPLE_LOCKS_ENERGY].get_deployed_contract_by_index( @@ -1244,12 +1244,12 @@ def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, n lp_contract = self.contracts[config.PAIRS_V2].get_deployed_contract_by_index( contract_config['farming_pool']) if lp_contract is None: - print_test_step_fail(f'Aborting deploy: farming pool v2 not existing!') + log_step_fail(f'Aborting deploy: farming pool v2 not existing!') return farming_token = lp_contract.lpToken lp_address = lp_contract.address else: - print_test_step_fail(f'Aborting deploy: farming token/pool not configured!') + log_step_fail(f'Aborting deploy: farming token/pool not configured!') return version = FarmContractVersion.V2Boosted @@ -1268,7 +1268,7 @@ def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, n # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "boosted farm"): return deployed_contract.address = contract_address - print_test_step_pass(f"Farm contract address: {contract_address}") + log_step_pass(f"Farm contract address: {contract_address}") # register farm token and save it tx_hash = deployed_contract.register_farm_token(deployer_account, network_providers.proxy, @@ -1289,7 +1289,7 @@ def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, n locking_contract.address) if not network_providers.check_simple_tx_status(tx_hash, "set energy address in farm"): return else: - print_test_step_fail(f"Failed to set up energy contract in farm. Energy contract not available!") + log_step_fail(f"Failed to set up energy contract in farm. Energy contract not available!") return # Set locking contract @@ -1298,13 +1298,13 @@ def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, n locking_contract.address) if not network_providers.check_simple_tx_status(tx_hash, "set locking address in farm"): return else: - print_test_step_fail(f"Failed to set up locking contract in farm. Locking contract not available!") + log_step_fail(f"Failed to set up locking contract in farm. Locking contract not available!") return # Set lock epochs if 'lock_epochs' not in contract_config: lock_epochs = 1440 - print_test_step_fail(f"Rewards per block not configured! Setting default: {lock_epochs}") + log_step_fail(f"Rewards per block not configured! Setting default: {lock_epochs}") else: lock_epochs = contract_config['lock_epochs'] @@ -1315,7 +1315,7 @@ def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, n # Set boosted yields rewards percentage if 'boosted_rewards' not in contract_config: boosted_rewards = 6000 - print_test_step_fail(f"Boosted yields rewards percentage configured! Setting default: {boosted_rewards}") + log_step_fail(f"Boosted yields rewards percentage configured! Setting default: {boosted_rewards}") else: boosted_rewards = contract_config['boosted_rewards'] tx_hash = deployed_contract.set_boosted_yields_rewards_percentage(deployer_account, network_providers.proxy, @@ -1329,7 +1329,7 @@ def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, n "farm_const" not in contract_config or \ "min_energy" not in contract_config or \ "min_farm" not in contract_config: - print_test_step_fail(f"Aborting deploy: Boosted yields factors not configured!") + log_step_fail(f"Aborting deploy: Boosted yields factors not configured!") tx_hash = deployed_contract.set_boosted_yields_factors(deployer_account, network_providers.proxy, [contract_config['base_const'], contract_config['energy_const'], @@ -1342,7 +1342,7 @@ def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, n # set rewards per block if 'rpb' not in contract_config: rpb = 10000 - print_test_step_fail(f"Rewards per block not configured! Setting default: {rpb}") + log_step_fail(f"Rewards per block not configured! Setting default: {rpb}") else: rpb = contract_config['rpb'] tx_hash = deployed_contract.set_rewards_per_block(deployer_account, network_providers.proxy, @@ -1351,7 +1351,7 @@ def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, n # set penalty percent if 'penalty' not in contract_config: - print_test_step_fail(f"Penalty percent not configured! Setting default: 0") + log_step_fail(f"Penalty percent not configured! Setting default: 0") penalty = 0 else: penalty = contract_config['penalty'] @@ -1361,7 +1361,7 @@ def farm_boosted_deploy(self, contracts_index: str, deployer_account: Account, n # set minimum farming epochs if 'min_farming_epochs' not in contract_config: - print_test_step_fail(f"Penalty percent not configured! Setting default: 7") + log_step_fail(f"Penalty percent not configured! Setting default: 7") min_epochs = 7 else: min_epochs = contract_config['min_farming_epochs'] @@ -1424,7 +1424,7 @@ def farm_community_deploy(self, contracts_index: str, deployer_account: Account, # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "farm"): return deployed_farm_contract.address = contract_address - print_test_step_pass(f"Farm contract address: {contract_address}") + log_step_pass(f"Farm contract address: {contract_address}") # register farm token and save it tx_hash = deployed_farm_contract.register_farm_token(deployer_account, network_providers.proxy, farm_token) @@ -1471,7 +1471,7 @@ def price_discovery_deploy(self, contracts_index: str, deployer_account: Account for config_pd in contract_structure.deploy_structure_list: config_pd_pool = self.contracts[config.PAIRS].deploy_structure_list[config_pd["pool"]] if not self.contracts[config.SIMPLE_LOCKS].deployed_contracts: - print_test_step_fail("Skipped deploy for price discovery. Simple lock contract not existing.") + log_step_fail("Skipped deploy for price discovery. Simple lock contract not existing.") return deployed_simple_lock: SimpleLockContract deployed_simple_lock = self.contracts[config.SIMPLE_LOCKS].deployed_contracts[0] @@ -1513,7 +1513,7 @@ def price_discovery_deploy(self, contracts_index: str, deployer_account: Account # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "price discovery"): return deployed_pd_contract.address = contract_address - print_test_step_pass(f"Price discovery contract address: {contract_address}") + log_step_pass(f"Price discovery contract address: {contract_address}") # issue redeem token tx_hash = deployed_pd_contract.issue_redeem_token(deployer_account, network_providers.proxy, redeem_token) @@ -1521,7 +1521,7 @@ def price_discovery_deploy(self, contracts_index: str, deployer_account: Account redeem_token_hex = PriceDiscoveryContractDataFetcher(Address(deployed_pd_contract.address), network_providers.proxy.url).get_data("getRedeemTokenId") if hex_to_string(redeem_token_hex) == "EGLD": - print_test_step_fail(f"FAIL: contract failed to set the issued token!") + log_step_fail(f"FAIL: contract failed to set the issued token!") return deployed_pd_contract.redeem_token = hex_to_string(redeem_token_hex) @@ -1565,7 +1565,7 @@ def staking_deploy(self, contracts_index: str, deployer_account: Account, networ # check for deployment success and save the deployed address if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "stake contract"): return deployed_staking_contract.address = contract_address - print_test_step_pass(f"Stake contract address: {contract_address}") + log_step_pass(f"Stake contract address: {contract_address}") # register farm token and save it tx_hash = deployed_staking_contract.register_farm_token(deployer_account, network_providers.proxy, @@ -1600,7 +1600,7 @@ def metastaking_deploy(self, contracts_index: str, deployer_account: Account, ne elif 'pool_v2' in config_metastaking: lp = self.contracts[config.PAIRS_V2].get_deployed_contract_by_index(config_metastaking['pool_v2']) else: - print_test_step_fail(f"Aborting deploy: no farm pool for metastaking deploy") + log_step_fail(f"Aborting deploy: no farm pool for metastaking deploy") return farm: Optional[FarmContract] = None if 'farm_unlocked' in config_metastaking: @@ -1613,7 +1613,7 @@ def metastaking_deploy(self, contracts_index: str, deployer_account: Account, ne farm = self.contracts[config.FARMS_V2].get_deployed_contract_by_index( config_metastaking['farm_boosted']) else: - print_test_step_fail(f"Aborting deploy: no farm configured for metastaking deploy") + log_step_fail(f"Aborting deploy: no farm configured for metastaking deploy") return staking: Optional[StakingContract] = None @@ -1640,7 +1640,7 @@ def metastaking_deploy(self, contracts_index: str, deployer_account: Account, ne if not network_providers.check_deploy_tx_status(tx_hash, contract_address, "metastake"): return deployed_metastaking_contract.address = contract_address - print_test_step_pass(f"Metastake contract address: {contract_address}") + log_step_pass(f"Metastake contract address: {contract_address}") # register metastake token and save it tx_hash = deployed_metastaking_contract.register_dual_yield_token(deployer_account, network_providers.proxy, diff --git a/events/event_generators.py b/events/event_generators.py index 4449c99..5b5cd41 100644 --- a/events/event_generators.py +++ b/events/event_generators.py @@ -25,7 +25,7 @@ from utils.utils_chain import (prevent_spam_crash_elrond_proxy_go, get_token_details_for_address, get_all_token_nonces_details_for_account, decode_merged_attributes, dec_to_padded_hex) -from utils.utils_generic import print_test_step_fail +from utils.utils_generic import log_step_fail from utils.utils_chain import Account, WrapperAddress as Address @@ -41,7 +41,7 @@ def generate_add_liquidity_event(context: Context, user_account: Account, pair_c _, amount_token_b, _ = get_token_details_for_address(tokens[1], user_account.address.bech32(), context.network_provider.proxy) if amount_token_a <= 0 or amount_token_b <= 0: - print_test_step_fail(f"Skipped add liquidity because needed tokens NOT found in account.") + log_step_fail(f"Skipped add liquidity because needed tokens NOT found in account.") return max_amount_a = int(amount_token_a * context.add_liquidity_max_amount) @@ -51,7 +51,7 @@ def generate_add_liquidity_event(context: Context, user_account: Account, pair_c max_amount_a]) if equivalent_amount_b <= 0 or equivalent_amount_b > amount_token_b: - print_test_step_fail(f'Minimum token equivalent amount not satisfied.') + log_step_fail(f'Minimum token equivalent amount not satisfied.') return amount_token_b_min = context.get_slippaged_below_value(equivalent_amount_b) @@ -156,7 +156,7 @@ def generate_swap_fixed_input(context: Context, user_account: Account, pair_cont amount_token_a_swapped]) if equivalent_amount_token_b <= 0: - print_test_step_fail(f'Minimum token equivalent amount not satisfied. Token amount: {equivalent_amount_token_b}') + log_step_fail(f'Minimum token equivalent amount not satisfied. Token amount: {equivalent_amount_token_b}') return amount_token_b_min = context.get_slippaged_below_value(equivalent_amount_token_b) @@ -237,7 +237,7 @@ def generateEnterFarmEvent(context: Context, userAccount: Account, farmContract: farmTkNonce, farmTkAmount, _ = get_token_details_for_address(farmToken, userAccount.address, context.network_provider.proxy) if farmingTkNonce == 0 and farmingTkAmount == 0: - print_test_step_fail(f"SKIPPED: No tokens found!") + log_step_fail(f"SKIPPED: No tokens found!") return initial = True if farmTkNonce == 0 else False @@ -285,7 +285,7 @@ def generateEnterStakingEvent(context: Context, user: Account, staking_contract: user.address, context.network_provider.proxy) if not staking_token_amount: - print_test_step_fail('SKIPPED enterStakingEvent: No tokens found!') + log_step_fail('SKIPPED enterStakingEvent: No tokens found!') return # set correct token balance in case it has been changed since the init of observers @@ -323,7 +323,7 @@ def generateEnterMetastakeEvent(context: Context, user: Account, metastake_contr context.network_provider.proxy) if staking_token_nonce == 0 and staking_token_amount == 0: - print_test_step_fail(f"SKIPPED: No tokens found!") + log_step_fail(f"SKIPPED: No tokens found!") return initial = True if metastake_token_nonce == 0 else False @@ -408,7 +408,7 @@ def generateUnstakeEvent(context: Context, user: Account, staking_contract: Stak user.address, context.network_provider.proxy) if not stake_token_nonce: - print_test_step_fail('SKIPPED unstakingEvent: No tokens to unstake!') + log_step_fail('SKIPPED unstakingEvent: No tokens to unstake!') return # set correct token balance in case it has been changed since the init of observers @@ -453,7 +453,7 @@ def generateExitMetastakeEvent(context: Context, user: Account, metastake_contra metastake_token, user.address, context.network_provider.proxy ) if metastake_token_nonce == 0: - print_test_step_fail(f"SKIPPED: No tokens found!") + log_step_fail(f"SKIPPED: No tokens found!") return # set correct token balance in case it has been changed since the init of observers @@ -571,7 +571,7 @@ def generateClaimMetastakeRewardsEvent(context: Context, user: Account, metastak context.network_provider.proxy ) if metastake_token_nonce == 0: - print_test_step_fail(f"SKIPPED: No tokens found!") + log_step_fail(f"SKIPPED: No tokens found!") return # set correct token balance in case it has been changed since the init of observers @@ -876,7 +876,7 @@ def generate_withdraw_pd_liquidity_event(context: Context, user_account: Account # TODO: find a smarter/more configurable method of choosing which token to use tokens = get_all_token_nonces_details_for_account(pd_contract.redeem_token, user_account.address, context.network_provider.proxy) if len(tokens) == 0: - print_test_step_fail(f"Generate withdraw price discovery liquidity failed! No redeem tokens available.") + log_step_fail(f"Generate withdraw price discovery liquidity failed! No redeem tokens available.") return random.shuffle(tokens) @@ -905,7 +905,7 @@ def generate_redeem_pd_liquidity_event(context: Context, user_account: Account, # TODO: find a smarter/more configurable method of choosing which token to use and how much tokens = get_all_token_nonces_details_for_account(pd_contract.redeem_token, user_account.address, context.network_provider.proxy) if len(tokens) == 0: - print_test_step_fail(f"Generate redeem price discovery liquidity failed! No redeem tokens available.") + log_step_fail(f"Generate redeem price discovery liquidity failed! No redeem tokens available.") return random.shuffle(tokens) diff --git a/scenarios/scenario_dex_v2_all_in.py b/scenarios/scenario_dex_v2_all_in.py index 07e9a1c..c2a6cca 100644 --- a/scenarios/scenario_dex_v2_all_in.py +++ b/scenarios/scenario_dex_v2_all_in.py @@ -24,7 +24,7 @@ from utils.utils_tx import ESDTToken from utils.utils_chain import nominated_amount, \ get_token_details_for_address, get_all_token_nonces_details_for_account -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_condition_assert, TestStepConditions +from utils.utils_generic import log_step_fail, log_step_pass, log_condition_assert, TestStepConditions from arrows.stress.send_token_from_minter import main as send_token_from_minter from arrows.stress.send_egld_from_minter import main as send_egld_from_minter from arrows.stress.shared import get_shard_of_address @@ -120,17 +120,17 @@ def scenarios(context: Context, threads: int, repeats: int): try: for future in concurrent.futures.as_completed(futures): finished_jobs += 1 - print_test_step_pass(f"Finished {finished_jobs} repeats.") + log_step_pass(f"Finished {finished_jobs} repeats.") futures.remove(future) # spawn a new job futures.append(executor.submit(scenarios_per_account, context, random.choice(accounts))) if future.exception() is not None: - print_test_step_fail(f"Thread failed: {future.exception()}") + log_step_fail(f"Thread failed: {future.exception()}") except Exception as ex: traceback.print_exception(*sys.exc_info()) - print_test_step_fail(f"Something failed: {ex}") + log_step_fail(f"Something failed: {ex}") else: - print_test_step_fail(f"Number of threads must be minimum 1!") + log_step_fail(f"Number of threads must be minimum 1!") return diff --git a/scenarios/scenario_fees_collector.py b/scenarios/scenario_fees_collector.py index 07e9a1c..c2a6cca 100644 --- a/scenarios/scenario_fees_collector.py +++ b/scenarios/scenario_fees_collector.py @@ -24,7 +24,7 @@ from utils.utils_tx import ESDTToken from utils.utils_chain import nominated_amount, \ get_token_details_for_address, get_all_token_nonces_details_for_account -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_condition_assert, TestStepConditions +from utils.utils_generic import log_step_fail, log_step_pass, log_condition_assert, TestStepConditions from arrows.stress.send_token_from_minter import main as send_token_from_minter from arrows.stress.send_egld_from_minter import main as send_egld_from_minter from arrows.stress.shared import get_shard_of_address @@ -120,17 +120,17 @@ def scenarios(context: Context, threads: int, repeats: int): try: for future in concurrent.futures.as_completed(futures): finished_jobs += 1 - print_test_step_pass(f"Finished {finished_jobs} repeats.") + log_step_pass(f"Finished {finished_jobs} repeats.") futures.remove(future) # spawn a new job futures.append(executor.submit(scenarios_per_account, context, random.choice(accounts))) if future.exception() is not None: - print_test_step_fail(f"Thread failed: {future.exception()}") + log_step_fail(f"Thread failed: {future.exception()}") except Exception as ex: traceback.print_exception(*sys.exc_info()) - print_test_step_fail(f"Something failed: {ex}") + log_step_fail(f"Something failed: {ex}") else: - print_test_step_fail(f"Number of threads must be minimum 1!") + log_step_fail(f"Number of threads must be minimum 1!") return diff --git a/scenarios/scenario_simple_lock_energy.py b/scenarios/scenario_simple_lock_energy.py index f8e553c..a423bb9 100644 --- a/scenarios/scenario_simple_lock_energy.py +++ b/scenarios/scenario_simple_lock_energy.py @@ -19,7 +19,7 @@ from utils.utils_tx import ESDTToken from utils.utils_chain import nominated_amount, \ get_token_details_for_address -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_condition_assert, TestStepConditions +from utils.utils_generic import log_step_fail, log_step_pass, log_condition_assert, TestStepConditions from arrows.stress.send_token_from_minter import main as send_token_from_minter from arrows.stress.shared import get_shard_of_address from erdpy.accounts import Account @@ -86,7 +86,7 @@ def scenarios(context: Context, threads: int, repeats: int): try: for _ in concurrent.futures.as_completed(futures): finished_jobs += 1 - print_test_step_pass(f"Finished {finished_jobs} repeats.") + log_step_pass(f"Finished {finished_jobs} repeats.") if repeats == 0 or repeats > finished_jobs: # spawn a new job @@ -94,7 +94,7 @@ def scenarios(context: Context, threads: int, repeats: int): except Exception as ex: pass else: - print_test_step_fail(f"Number of threads must be minimum 1!") + log_step_fail(f"Number of threads must be minimum 1!") return diff --git a/scenarios/scenario_swaps.py b/scenarios/scenario_swaps.py index 98049f2..adff585 100644 --- a/scenarios/scenario_swaps.py +++ b/scenarios/scenario_swaps.py @@ -24,7 +24,7 @@ from utils.utils_tx import ESDTToken from utils.utils_chain import nominated_amount, \ get_token_details_for_address, get_all_token_nonces_details_for_account -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_condition_assert, TestStepConditions +from utils.utils_generic import log_step_fail, log_step_pass, log_condition_assert, TestStepConditions from arrows.stress.send_token_from_minter import main as send_token_from_minter from arrows.stress.send_egld_from_minter import main as send_egld_from_minter from arrows.stress.shared import get_shard_of_address @@ -120,17 +120,17 @@ def scenarios(context: Context, threads: int, repeats: int): try: for future in concurrent.futures.as_completed(futures): finished_jobs += 1 - print_test_step_pass(f"Finished {finished_jobs} repeats.") + log_step_pass(f"Finished {finished_jobs} repeats.") futures.remove(future) # spawn a new job futures.append(executor.submit(scenarios_per_account, context, random.choice(accounts))) if future.exception() is not None: - print_test_step_fail(f"Thread failed: {future.exception()}") + log_step_fail(f"Thread failed: {future.exception()}") except Exception as ex: traceback.print_exception(*sys.exc_info()) - print_test_step_fail(f"Something failed: {ex}") + log_step_fail(f"Something failed: {ex}") else: - print_test_step_fail(f"Number of threads must be minimum 1!") + log_step_fail(f"Number of threads must be minimum 1!") return diff --git a/scenarios/stress_create_positions.py b/scenarios/stress_create_positions.py index 46ad500..95e6570 100644 --- a/scenarios/stress_create_positions.py +++ b/scenarios/stress_create_positions.py @@ -15,7 +15,7 @@ generateClaimMetastakeRewardsEvent, generateExitMetastakeEvent, generateExitFarmEvent) -from utils.utils_generic import print_test_step_pass +from utils.utils_generic import log_step_pass from arrows.stress.send_token_from_minter import main as send_token_from_minter from arrows.stress.shared import get_shard_of_address from erdpy.accounts import Account @@ -85,7 +85,7 @@ def stress(context: Context, threads: int, repeats: int): try: for _ in concurrent.futures.as_completed(futures): finished_jobs += 1 - print_test_step_pass(f"Finished {finished_jobs} repeats.") + log_step_pass(f"Finished {finished_jobs} repeats.") if repeats == 0 or repeats > finished_jobs: # spawn a new job diff --git a/tools/account_state.py b/tools/account_state.py index 94b37a1..5dfb89b 100644 --- a/tools/account_state.py +++ b/tools/account_state.py @@ -5,7 +5,7 @@ from typing import Dict, Any, Tuple, List -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_warning +from utils.utils_generic import log_step_fail, log_step_pass, log_warning from erdpy.proxy.http_facade import do_get @@ -81,7 +81,7 @@ def compare_keys(left_state: dict, right_state: dict) -> Tuple[bool, dict, dict, def report_key_files_compare(folder_path: str, left_prefix: str, right_prefix: str, verbose: bool = False): compare_count = 0 if not os.path.exists(folder_path): - print_test_step_fail(f"Given folder path doesn't exist.") + log_step_fail(f"Given folder path doesn't exist.") for file in os.listdir(folder_path): if f"{left_prefix}" not in file: @@ -101,21 +101,21 @@ def report_key_files_compare(folder_path: str, left_prefix: str, right_prefix: s right_state) if identical: - print_test_step_pass(f"\n{file} and {right_file} are identical.") + log_step_pass(f"\n{file} and {right_file} are identical.") else: - print_test_step_fail(f"\n{file} and {right_file} are not identical.") + log_step_fail(f"\n{file} and {right_file} are not identical.") if verbose: if keys_in_left: for key, value in keys_in_left.items(): - print_warning(f"Data only in {left_prefix}: {key}: {value}") + log_warning(f"Data only in {left_prefix}: {key}: {value}") print(f"Decoded key: {bytearray.fromhex(key).decode('iso-8859-1')}") if keys_in_right: for key, value in keys_in_right.items(): - print_warning(f"Data only in {right_prefix}: {key}: {value}") + log_warning(f"Data only in {right_prefix}: {key}: {value}") print(f"Decoded key: {bytearray.fromhex(key).decode('iso-8859-1')}") if common_keys_diff_values: for key, value in common_keys_diff_values.items(): - print_warning(f"Common key with different values: {key}: {value}") + log_warning(f"Common key with different values: {key}: {value}") print(f"Decoded key: {bytearray.fromhex(key).decode('iso-8859-1')}") compare_count += 1 diff --git a/tools/contracts_upgrader.py b/tools/contracts_upgrader.py index c034e60..d8a7715 100644 --- a/tools/contracts_upgrader.py +++ b/tools/contracts_upgrader.py @@ -24,7 +24,7 @@ StakingContractDataFetcher, FarmContractDataFetcher from utils.utils_tx import NetworkProviders from utils.utils_chain import base64_to_hex -from utils.utils_generic import print_test_step_fail +from utils.utils_generic import log_step_fail from tools import config_contracts_upgrader as config from erdpy.accounts import Address, Account from erdpy.proxy import ElrondProxy @@ -1015,7 +1015,7 @@ def fetch_contract_states(prefix: str, network_providers: NetworkProviders): get_account_keys_online(LOCKED_ASSET_FACTORY_CONTRACT, network_providers.proxy.url, with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) else: - print_test_step_fail(f"Locked asset factory address not available. No state saved for this!") + log_step_fail(f"Locked asset factory address not available. No state saved for this!") # get proxy dex state # filename = get_contract_save_name(PROXY_DEX_LABEL, PROXY_DEX_CONTRACT, prefix) @@ -1028,7 +1028,7 @@ def fetch_contract_states(prefix: str, network_providers: NetworkProviders): get_account_keys_online(ROUTER_CONTRACT, network_providers.proxy.url, with_save_in=str(OUTPUT_FOLDER / f"{filename}.json")) else: - print_test_step_fail(f"Router address not available. No state saved for this!") + log_step_fail(f"Router address not available. No state saved for this!") # get template state router_data_fetcher = RouterContractDataFetcher(Address(ROUTER_CONTRACT), network_providers.proxy.url) diff --git a/trackers/farm_economics_tracking.py b/trackers/farm_economics_tracking.py index 508d088..30a45e9 100644 --- a/trackers/farm_economics_tracking.py +++ b/trackers/farm_economics_tracking.py @@ -2,8 +2,7 @@ from utils.contract_data_fetchers import FarmContractDataFetcher, ChainDataFetcher from utils.utils_tx import NetworkProviders from erdpy.accounts import Account, Address -from utils.utils_chain import (DecodedTokenAttributes) -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep +from utils.utils_generic import log_step_fail, log_step_pass, log_substep from events.farm_events import (EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent, SetTokenBalanceEvent) from trackers.abstract_observer import Subscriber @@ -12,6 +11,21 @@ from contracts.contract_identities import FarmContractVersion +class DecodedTokenAttributes: + rewards_per_share: int + original_entering_epoch: int + entering_epoch: int + apr_multiplier: int + locked_rewards: bool + initial_farming_amount: int + compounded_rewards: int + current_farm_amount: int + + def __init__(self, attributes_hex: str, attr_version: FarmContractVersion = None): + # TODO: implement it using the new decoders + pass + + class FarmAccountEconomics(Subscriber): def __init__(self, address: Address, network_provider: NetworkProviders): @@ -36,24 +50,24 @@ def enter_farm(self, event: EnterFarmEvent) -> None: expected_farm_token_balance = int(old_farm_token_balance) + event.farming_tk_amount if old_farm_token_nonce >= new_farm_token_nonce: - print_test_step_fail(f'Farm token nonce did not increase for {self.address.bech32()}') - print_test_substep(f'Old farm token nonce: {old_farm_token_nonce}') - print_test_substep(f'New farm token nonce: {new_farm_token_nonce}') + log_step_fail(f'Farm token nonce did not increase for {self.address.bech32()}') + log_substep(f'Old farm token nonce: {old_farm_token_nonce}') + log_substep(f'New farm token nonce: {new_farm_token_nonce}') if new_farming_token_balance != expected_farming_token_balance: - print_test_step_fail(f'Farming token balance did not decrease for account {self.address.bech32()}') - print_test_substep(f'Old farming token amount: {old_farming_token_balance}') - print_test_substep(f'New farming token amount: {new_farming_token_balance}') - print_test_substep(f'Expected farming token amount: {expected_farming_token_balance}') + log_step_fail(f'Farming token balance did not decrease for account {self.address.bech32()}') + log_substep(f'Old farming token amount: {old_farming_token_balance}') + log_substep(f'New farming token amount: {new_farming_token_balance}') + log_substep(f'Expected farming token amount: {expected_farming_token_balance}') if farm_token is not None: if new_farm_token_balance != expected_farm_token_balance: - print_test_step_fail(f'Farm token balance did not increase for account {self.address.bech32()}') - print_test_substep(f'Old farm token amount: {old_farm_token_balance}') - print_test_substep(f'New farm token amount: {new_farm_token_balance}') - print_test_substep(f'Expected farm token amount: {expected_farm_token_balance}') + log_step_fail(f'Farm token balance did not increase for account {self.address.bech32()}') + log_substep(f'Old farm token amount: {old_farm_token_balance}') + log_substep(f'New farm token amount: {new_farm_token_balance}') + log_substep(f'Expected farm token amount: {expected_farm_token_balance}') - print_test_step_pass('Checked enter farm event economics for account') + log_step_pass('Checked enter farm event economics for account') def exit_farm(self, contract, event: ExitFarmEvent) -> None: farm_token = self.tokens.get(event.farm_token, None) @@ -76,23 +90,23 @@ def exit_farm(self, contract, event: ExitFarmEvent) -> None: new_farmed_token_balance = int(self.tokens[contract.farmedToken]['balance']) if new_farm_token_balance != expected_farm_token_balance: - print_test_step_fail(f'Farm token amount did not decrease for account {self.address.bech32()}') - print_test_substep(f'Old farm token amount: {old_farm_token_balance}') - print_test_substep(f'New farm token amount: {new_farm_token_balance}') - print_test_substep(f'Expected farm token amount: {expected_farm_token_balance}') + log_step_fail(f'Farm token amount did not decrease for account {self.address.bech32()}') + log_substep(f'Old farm token amount: {old_farm_token_balance}') + log_substep(f'New farm token amount: {new_farm_token_balance}') + log_substep(f'Expected farm token amount: {expected_farm_token_balance}') if new_farming_token_balance != expected_farming_token_balance: - print_test_step_fail(f'Farming token amount did not increase for account {self.address.bech32()}') - print_test_substep(f'Old farming token amount {old_farming_token_balance}') - print_test_substep(f'New farming token amount {new_farming_token_balance}') - print_test_substep(f'Expected farming token amount {expected_farming_token_balance}') + log_step_fail(f'Farming token amount did not increase for account {self.address.bech32()}') + log_substep(f'Old farming token amount {old_farming_token_balance}') + log_substep(f'New farming token amount {new_farming_token_balance}') + log_substep(f'Expected farming token amount {expected_farming_token_balance}') if old_farmed_token_balance >= new_farmed_token_balance: - print_test_step_fail(f'Farmed token amount did not increase for account {self.address.bech32()}') - print_test_substep(f'Old farmed token amount {old_farmed_token_balance}') - print_test_substep(f'New farmed token amount {new_farmed_token_balance}') + log_step_fail(f'Farmed token amount did not increase for account {self.address.bech32()}') + log_substep(f'Old farmed token amount {old_farmed_token_balance}') + log_substep(f'New farmed token amount {new_farmed_token_balance}') - print_test_step_pass('Checked exit farm event economics for account') + log_step_pass('Checked exit farm event economics for account') def claim_rewards(self, contract, event: ClaimRewardsFarmEvent) -> None: farmed_token = self.tokens.get(contract.farmedToken, None) @@ -104,17 +118,17 @@ def claim_rewards(self, contract, event: ClaimRewardsFarmEvent) -> None: expected_farmed_token_balance = int(old_farmed_token_balance) + event.amount if new_farmed_token_balance != expected_farmed_token_balance: - print_test_step_fail(f'Farmed token amount did not increase for account {self.address.bech32()}') - print_test_substep(f'Old farm token amount: {old_farmed_token_balance}') - print_test_substep(f'New farm token amount: {new_farmed_token_balance}') - print_test_substep(f'Expected farm token amount: {expected_farmed_token_balance}') + log_step_fail(f'Farmed token amount did not increase for account {self.address.bech32()}') + log_substep(f'Old farm token amount: {old_farmed_token_balance}') + log_substep(f'New farm token amount: {new_farmed_token_balance}') + log_substep(f'Expected farm token amount: {expected_farmed_token_balance}') - print_test_step_pass('Checked claim rewards event economics for account') + log_step_pass('Checked claim rewards event economics for account') def report_current_tokens(self): - print_test_step_pass(f'All tokens for account {self.address}') + log_step_pass(f'All tokens for account {self.address}') for key in self.tokens: - print_test_substep(f'{key}: {self.tokens[key]["balance"]}') + log_substep(f'{key}: {self.tokens[key]["balance"]}') def set_token_balance(self, event: SetTokenBalanceEvent): if event.token not in self.tokens: @@ -125,12 +139,12 @@ def set_token_balance(self, event: SetTokenBalanceEvent): self.tokens[event.token]['nonce'] = event.nonce self.tokens[event.token]['balance'] = event.balance - print_test_step_pass(f'Updated token balance for account {self.address.bech32()}') + log_step_pass(f'Updated token balance for account {self.address.bech32()}') def update(self, publisher: Observable): if publisher.user.address == self.address: if publisher.tx_hash: - self.network_provider.api.wait_for_tx_finalized(publisher.tx_hash) + self.network_provider.wait_for_tx_executed(publisher.tx_hash) if type(publisher.event) == EnterFarmEvent: self.enter_farm(publisher.event) elif type(publisher.event) == ExitFarmEvent: @@ -184,23 +198,23 @@ def check_invariant_properties(self): chain_division_safety_constant = self.farm_data_fetcher.get_data("getDivisionSafetyConstant") if self.rewards_per_share > new_rewards_per_share: - print_test_step_fail("TEST CHECK FAIL: Rewards per share decreased!") - print_test_substep(f"Old rewards per share: {self.rewards_per_share}") - print_test_substep(f"New rewards per share: {new_rewards_per_share}") + log_step_fail("TEST CHECK FAIL: Rewards per share decreased!") + log_substep(f"Old rewards per share: {self.rewards_per_share}") + log_substep(f"New rewards per share: {new_rewards_per_share}") if self.last_rewards_block_nonce > new_last_rewards_block_nonce: - print_test_step_fail("TEST CHECK FAIL: Last rewards block nonce decreased!") - print_test_substep(f"Old rewards block nonce: {self.last_rewards_block_nonce}") - print_test_substep(f"New rewards block nonce: {new_last_rewards_block_nonce}") + log_step_fail("TEST CHECK FAIL: Last rewards block nonce decreased!") + log_substep(f"Old rewards block nonce: {self.last_rewards_block_nonce}") + log_substep(f"New rewards block nonce: {new_last_rewards_block_nonce}") if self.rewards_per_block != chain_rewards_per_block: - print_test_step_fail("TEST CHECK FAIL: Rewards per block has changed!") - print_test_substep(f"Old rewards per block: {self.rewards_per_block}") - print_test_substep(f"New rewards per block: {chain_rewards_per_block}") + log_step_fail("TEST CHECK FAIL: Rewards per block has changed!") + log_substep(f"Old rewards per block: {self.rewards_per_block}") + log_substep(f"New rewards per block: {chain_rewards_per_block}") if self.division_safety_constant != chain_division_safety_constant: - print_test_step_fail("TEST CHECK FAIL: Division safety constant has changed!") - print_test_substep(f"Old division safety constant: {self.division_safety_constant}") - print_test_substep(f"New division safety constant: {chain_division_safety_constant}") + log_step_fail("TEST CHECK FAIL: Division safety constant has changed!") + log_substep(f"Old division safety constant: {self.division_safety_constant}") + log_substep(f"New division safety constant: {chain_division_safety_constant}") - print_test_step_pass("Checked invariant properties!") + log_step_pass("Checked invariant properties!") def check_enter_farm_properties(self): # track event dependent properties @@ -211,15 +225,15 @@ def check_enter_farm_properties(self): ENTER_FARM_REWARDS_RESERVE_FAIL = "TEST CHECK FAIL: Rewards reserve decreased!" if self.farm_token_supply >= new_farm_token_supply: - print_test_step_fail(ENTER_FARM_FARM_TK_SUPPLY_FAIL) - print_test_substep(f"Old Farm token supply: {self.farm_token_supply}") - print_test_substep(f"New Farm token supply: {new_farm_token_supply}") + log_step_fail(ENTER_FARM_FARM_TK_SUPPLY_FAIL) + log_substep(f"Old Farm token supply: {self.farm_token_supply}") + log_substep(f"New Farm token supply: {new_farm_token_supply}") if self.rewards_reserve >= new_rewards_reserve: - print_test_step_fail(ENTER_FARM_REWARDS_RESERVE_FAIL) - print_test_substep(f"Old Rewards reserve: {self.rewards_reserve}") - print_test_substep(f"New Rewards reserve: {new_rewards_reserve}") + log_step_fail(ENTER_FARM_REWARDS_RESERVE_FAIL) + log_substep(f"Old Rewards reserve: {self.rewards_reserve}") + log_substep(f"New Rewards reserve: {new_rewards_reserve}") - print_test_step_pass("Checked enter farm properties!") + log_step_pass("Checked enter farm properties!") def check_enter_farm_tx_data(self, event: EnterFarmEvent, txhash: str): new_contract_farm_token_supply = self.farm_data_fetcher.get_data("getFarmTokenSupply") @@ -241,47 +255,47 @@ def check_enter_farm_tx_data(self, event: EnterFarmEvent, txhash: str): new_exp_rewards_per_share = 0 def report_generic_fails(): - print_test_substep(f"Old Rewards reserve: {self.rewards_reserve}") - print_test_substep(f"New Rewards reserve in contract: {new_contract_rewards_reserve}") - print_test_substep(f"Expected Rewards reserve: {new_exp_rewards_reserve}") - print_test_substep(f"Aggregated rewards: {aggregated_rewards}") - print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") - print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") - print_test_substep(f"Rewards per block: {self.rewards_per_block}") - print_test_substep(f"TX hash: {txhash}") - print_test_substep(f"TX block nonce: {tx_block}") - print_test_substep(f"Last calculated rewards at block nonce:{self.last_block_calculated_rewards}") + log_substep(f"Old Rewards reserve: {self.rewards_reserve}") + log_substep(f"New Rewards reserve in contract: {new_contract_rewards_reserve}") + log_substep(f"Expected Rewards reserve: {new_exp_rewards_reserve}") + log_substep(f"Aggregated rewards: {aggregated_rewards}") + log_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + log_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + log_substep(f"Rewards per block: {self.rewards_per_block}") + log_substep(f"TX hash: {txhash}") + log_substep(f"TX block nonce: {tx_block}") + log_substep(f"Last calculated rewards at block nonce:{self.last_block_calculated_rewards}") # check for FARM TOKEN SUPPLY integrity if new_contract_farm_token_supply != new_exp_farm_token_supply: - print_test_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") - print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") - print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + log_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") + log_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + log_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") report_generic_fails() # check for REWARDS PER SHARE integrity if new_contract_rewards_per_share != new_exp_rewards_per_share: - print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") - print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") - print_test_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") - print_test_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") + log_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + log_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + log_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") + log_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") report_generic_fails() # check for REWARDS RESERVE integrity if new_contract_rewards_reserve != new_exp_rewards_reserve: - print_test_step_fail(f"TEST CHECK FAIL: Rewards reserve not as expected!") + log_step_fail(f"TEST CHECK FAIL: Rewards reserve not as expected!") report_generic_fails() # check for LAST REWARD BLOCK integrity if tx_block != new_last_rewards_block_nonce: - print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") - print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") - print_test_substep(f"Expected last reward block nonce: {tx_block}") + log_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + log_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + log_substep(f"Expected last reward block nonce: {tx_block}") # TODO: decide afterwards if this is a good place for this update, or whether it should be moved with the others self.last_block_calculated_rewards = tx_block - print_test_step_pass("Checked enter farm tx data!") + log_step_pass("Checked enter farm tx data!") def check_exit_farm_properties(self): # track event dependent properties @@ -290,9 +304,9 @@ def check_exit_farm_properties(self): EXIT_FARM_FARM_TK_SUPPLY_FAIL = "TEST CHECK FAIL: Farm token supply did not decrease!" if self.farm_token_supply <= new_farm_token_supply: - print_test_step_fail(f"{EXIT_FARM_FARM_TK_SUPPLY_FAIL}") - print_test_substep(f"Old Farm token supply: {self.farm_token_supply}") - print_test_substep(f"New Farm token supply: {new_farm_token_supply}") + log_step_fail(f"{EXIT_FARM_FARM_TK_SUPPLY_FAIL}") + log_substep(f"Old Farm token supply: {self.farm_token_supply}") + log_substep(f"New Farm token supply: {new_farm_token_supply}") """moved into exit farm check as it can differ from case to case based on side of exit position if self.rewards_reserve < new_rewards_reserve: # TODO: have to check whether rewards should be given and if it increased @@ -301,7 +315,7 @@ def check_exit_farm_properties(self): print_test_substep(f"New Rewards reserve: {new_rewards_reserve}") """ - print_test_step_pass(f"Checked exit farm properties!") + log_step_pass(f"Checked exit farm properties!") def check_exit_farm_tx_data(self, event: ExitFarmEvent, txhash: str): # TODO: check burned tokens if penalty applies @@ -329,51 +343,51 @@ def check_exit_farm_tx_data(self, event: ExitFarmEvent, txhash: str): # todo: clarify if rewards per share should not consider the new farm tokens def report_generic_fails(): - print_test_substep(f"Old Rewards reserve: {self.rewards_reserve}") - print_test_substep(f"New Rewards reserve in contract: {new_contract_rewards_reserve}") - print_test_substep(f"Expected Rewards reserve: {new_exp_rewards_reserve}") - print_test_substep(f"Aggregated rewards: {aggregated_rewards}") - print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") - print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") - print_test_substep(f"Rewards per block: {self.rewards_per_block}") - print_test_substep(f"TX hash: {txhash}") - print_test_substep(f"TX block nonce: {tx_block}") - print_test_substep(f"Last calculated rewards at block nonce:{self.last_block_calculated_rewards}") + log_substep(f"Old Rewards reserve: {self.rewards_reserve}") + log_substep(f"New Rewards reserve in contract: {new_contract_rewards_reserve}") + log_substep(f"Expected Rewards reserve: {new_exp_rewards_reserve}") + log_substep(f"Aggregated rewards: {aggregated_rewards}") + log_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + log_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + log_substep(f"Rewards per block: {self.rewards_per_block}") + log_substep(f"TX hash: {txhash}") + log_substep(f"TX block nonce: {tx_block}") + log_substep(f"Last calculated rewards at block nonce:{self.last_block_calculated_rewards}") # check for FARM TOKEN SUPPLY integrity if new_contract_farm_token_supply != new_exp_farm_token_supply: - print_test_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") - print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") - print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + log_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") + log_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + log_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") report_generic_fails() # check for REWARDS PER SHARE integrity if new_contract_rewards_per_share != new_exp_rewards_per_share: - print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") - print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") - print_test_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") - print_test_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") + log_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + log_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + log_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") + log_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") report_generic_fails() # check for REWARDS RESERVE integrity if new_contract_rewards_reserve != new_exp_rewards_reserve: - print_test_step_fail(f"TEST CHECK FAIL: Rewards reserve not as expected!") - print_test_substep(f"Rewards per share in contract: {new_contract_rewards_per_share}") - print_test_substep(f"Rewards per share in exit position: {decoded_attrs.rewards_per_share}") - print_test_substep(f"Exit position amount: {event.amount}") - print_test_substep(f"Expected rewards per position: {exp_rewards}") + log_step_fail(f"TEST CHECK FAIL: Rewards reserve not as expected!") + log_substep(f"Rewards per share in contract: {new_contract_rewards_per_share}") + log_substep(f"Rewards per share in exit position: {decoded_attrs.rewards_per_share}") + log_substep(f"Exit position amount: {event.amount}") + log_substep(f"Expected rewards per position: {exp_rewards}") report_generic_fails() # check for LAST REWARD BLOCK integrity if tx_block != new_last_rewards_block_nonce: - print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") - print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") - print_test_substep(f"Expected last reward block nonce: {tx_block}") + log_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + log_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + log_substep(f"Expected last reward block nonce: {tx_block}") # TODO: decide afterwards if this is a good place for this update, or whether it should be moved with the others self.last_block_calculated_rewards = tx_block - print_test_step_pass("Checked exit farm tx data!") + log_step_pass("Checked exit farm tx data!") def check_claim_rewards_properties(self): # track event dependent properties @@ -382,11 +396,11 @@ def check_claim_rewards_properties(self): CLAIM_REWARDS_FARM_TK_SUPPLY_FAIL = "TEST CHECK FAIL: Farm token supply modified!" if self.farm_token_supply != new_farm_token_supply: - print_test_step_fail(CLAIM_REWARDS_FARM_TK_SUPPLY_FAIL) - print_test_substep(f"Old Farm token supply: {self.farm_token_supply}") - print_test_substep(f"New Farm token supply: {new_farm_token_supply}") + log_step_fail(CLAIM_REWARDS_FARM_TK_SUPPLY_FAIL) + log_substep(f"Old Farm token supply: {self.farm_token_supply}") + log_substep(f"New Farm token supply: {new_farm_token_supply}") - print_test_step_pass("Checked claim rewards properties!") + log_step_pass("Checked claim rewards properties!") def check_claim_rewards_farm_tx_data(self, event: ClaimRewardsFarmEvent, txhash: str): # TODO: check burned tokens if penalty applies @@ -414,51 +428,51 @@ def check_claim_rewards_farm_tx_data(self, event: ClaimRewardsFarmEvent, txhash: # todo: clarify if rewards per share should not consider the new farm tokens def report_generic_fails(): - print_test_substep(f"Old Rewards reserve: {self.rewards_reserve}") - print_test_substep(f"New Rewards reserve in contract: {new_contract_rewards_reserve}") - print_test_substep(f"Expected Rewards reserve: {new_exp_rewards_reserve}") - print_test_substep(f"Aggregated rewards: {aggregated_rewards}") - print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") - print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") - print_test_substep(f"Rewards per block: {self.rewards_per_block}") - print_test_substep(f"TX hash: {txhash}") - print_test_substep(f"TX block nonce: {tx_block}") - print_test_substep(f"Last calculated rewards at block nonce:{self.last_block_calculated_rewards}") + log_substep(f"Old Rewards reserve: {self.rewards_reserve}") + log_substep(f"New Rewards reserve in contract: {new_contract_rewards_reserve}") + log_substep(f"Expected Rewards reserve: {new_exp_rewards_reserve}") + log_substep(f"Aggregated rewards: {aggregated_rewards}") + log_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + log_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + log_substep(f"Rewards per block: {self.rewards_per_block}") + log_substep(f"TX hash: {txhash}") + log_substep(f"TX block nonce: {tx_block}") + log_substep(f"Last calculated rewards at block nonce:{self.last_block_calculated_rewards}") # check for FARM TOKEN SUPPLY integrity if new_contract_farm_token_supply != new_exp_farm_token_supply: - print_test_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") - print_test_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") - print_test_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") + log_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") + log_substep(f"Farm token supply in contract: {new_contract_farm_token_supply}") + log_substep(f"Expected Farm token supply: {new_exp_farm_token_supply}") report_generic_fails() # check for REWARDS PER SHARE integrity if new_contract_rewards_per_share != new_exp_rewards_per_share: - print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") - print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") - print_test_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") - print_test_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") + log_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + log_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + log_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") + log_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") report_generic_fails() # check for REWARDS RESERVE integrity if new_contract_rewards_reserve != new_exp_rewards_reserve: - print_test_step_fail(f"TEST CHECK FAIL: Rewards reserve not as expected!") - print_test_substep(f"Rewards per share in contract: {new_contract_rewards_per_share}") - print_test_substep(f"Rewards per share in exit position: {decoded_attrs.rewards_per_share}") - print_test_substep(f"Exit position amount: {event.amount}") - print_test_substep(f"Expected rewards per position: {exp_rewards}") + log_step_fail(f"TEST CHECK FAIL: Rewards reserve not as expected!") + log_substep(f"Rewards per share in contract: {new_contract_rewards_per_share}") + log_substep(f"Rewards per share in exit position: {decoded_attrs.rewards_per_share}") + log_substep(f"Exit position amount: {event.amount}") + log_substep(f"Expected rewards per position: {exp_rewards}") report_generic_fails() # check for LAST REWARD BLOCK integrity if tx_block != new_last_rewards_block_nonce: - print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") - print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") - print_test_substep(f"Expected last reward block nonce: {tx_block}") + log_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + log_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + log_substep(f"Expected last reward block nonce: {tx_block}") # TODO: decide afterwards if this is a good place for this update, or whether it should be moved with the others self.last_block_calculated_rewards = tx_block - print_test_step_pass("Checked claim rewards tx data!") + log_step_pass("Checked claim rewards tx data!") def __enter_farm_event_for_account(self, account: Account, block: int, lp_staked: int): if account.address.bech32() not in self.accounts: @@ -483,7 +497,7 @@ def update_tracking_data(self): self.rewards_reserve = new_rewards_reserve self.division_safety_constant = new_division_safety_constant - print_test_step_pass("Updated farm tracking data!") + log_step_pass("Updated farm tracking data!") def enter_farm_event_tracking(self, account: Account, event: EnterFarmEvent, txhash: str, lock: int = 0): # TODO: replace error reporting with logger @@ -510,7 +524,7 @@ def claim_rewards_farm_event_tracking(self, account: Account, event: ExitFarmEve def update(self, publisher: Observable): if publisher.contract is not None: if str(self.contract_address) == publisher.contract.address: - self.network_provider.api.wait_for_tx_finalized(publisher.tx_hash) + self.network_provider.wait_for_tx_executed(publisher.tx_hash) if type(publisher.event) == EnterFarmEvent: self.enter_farm_event_tracking(publisher.user, publisher.event, publisher.tx_hash) elif type(publisher.event) == ExitFarmEvent: diff --git a/trackers/metastaking_economics_tracking.py b/trackers/metastaking_economics_tracking.py index ad652af..99b80a3 100644 --- a/trackers/metastaking_economics_tracking.py +++ b/trackers/metastaking_economics_tracking.py @@ -1,6 +1,6 @@ from erdpy.accounts import Address from utils.utils_tx import NetworkProviders -from utils.utils_generic import print_test_step_pass +from utils.utils_generic import log_step_pass from utils.contract_data_fetchers import MetaStakingContractDataFetcher, ChainDataFetcher from events.metastake_events import (EnterMetastakeEvent, ExitMetastakeEvent, @@ -141,19 +141,19 @@ def check_enter_metastaking(self, publisher: Observable): self.staking_tracker.check_invariant_properties() self.staking_tracker.check_enter_staking_properties() self.check_enter_metastaking_data(publisher) - print_test_step_pass('Checked enter metastaking event economics!') + log_step_pass('Checked enter metastaking event economics!') def check_exit_metastaking(self, publisher: Observable): self.staking_tracker.check_invariant_properties() self.staking_tracker.check_exit_staking_properties() self.check_exit_metastaking_data(publisher) - print_test_step_pass('Checked exit metastaking event economics!') + log_step_pass('Checked exit metastaking event economics!') def check_claim_rewards(self, publisher: Observable): self.staking_tracker.check_invariant_properties() self.staking_tracker.check_claim_rewards_properties() self.check_claim_rewards_data(publisher) - print_test_step_pass('Checked claim metastaking rewards event economics!') + log_step_pass('Checked claim metastaking rewards event economics!') def update_trackers_data(self): self.staking_tracker.update_data() @@ -164,7 +164,7 @@ def update(self, publisher: Observable): if publisher.contract is not None: if str(self.contract_address) == publisher.contract.address: if publisher.tx_hash: - self.network_provider.api.wait_for_tx_finalized(publisher.tx_hash) + self.network_provider.wait_for_tx_executed(publisher.tx_hash) if isinstance(publisher.event, SetCorrectReservesEvent): self.update_trackers_data() elif isinstance(publisher.event, EnterMetastakeEvent): diff --git a/trackers/pair_economics_tracking.py b/trackers/pair_economics_tracking.py index f117e54..e683eaf 100644 --- a/trackers/pair_economics_tracking.py +++ b/trackers/pair_economics_tracking.py @@ -3,7 +3,7 @@ from trackers.abstract_observer import Subscriber from trackers.concrete_observer import Observable from utils.contract_data_fetchers import PairContractDataFetcher -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep +from utils.utils_generic import log_step_fail, log_step_pass, log_substep from contracts.pair_contract import (AddLiquidityEvent, RemoveLiquidityEvent, SwapFixedInputEvent, @@ -59,23 +59,23 @@ def check_add_liquidity(self, event: AddLiquidityEvent): expected_second_token_reserve = old_second_token_reserve + event.amountAmin if old_first_token_reserve >= new_first_token_reserve: - print_test_step_fail(f'First token reserve did not increase for pair {self.contract_address.bech32()}') - print_test_substep(f'Old first token reserve: {old_first_token_reserve}') - print_test_substep(f'New first token reserve: {new_first_token_reserve}') - print_test_substep(f'Minimum first token reserve expected: {expected_first_token_reserve}') + log_step_fail(f'First token reserve did not increase for pair {self.contract_address.bech32()}') + log_substep(f'Old first token reserve: {old_first_token_reserve}') + log_substep(f'New first token reserve: {new_first_token_reserve}') + log_substep(f'Minimum first token reserve expected: {expected_first_token_reserve}') if old_second_token_reserve >= new_second_token_reserve: - print_test_step_fail(f'Second token reserve did not increase for pair {self.contract_address.bech32()}') - print_test_substep(f'Old second token reserve: {old_second_token_reserve}') - print_test_substep(f'New second token reserve: {new_second_token_reserve}') - print_test_substep(f'Minimum second token reserve expected: {expected_second_token_reserve}') + log_step_fail(f'Second token reserve did not increase for pair {self.contract_address.bech32()}') + log_substep(f'Old second token reserve: {old_second_token_reserve}') + log_substep(f'New second token reserve: {new_second_token_reserve}') + log_substep(f'Minimum second token reserve expected: {expected_second_token_reserve}') if old_total_supply >= new_total_supply: - print_test_step_fail(f'Total supply did not increase for pair {self.contract_address.bech32()}') - print_test_substep(f'Old supply: {old_total_supply}') - print_test_substep(f'New supply: {new_total_supply}') + log_step_fail(f'Total supply did not increase for pair {self.contract_address.bech32()}') + log_substep(f'Old supply: {old_total_supply}') + log_substep(f'New supply: {new_total_supply}') - print_test_step_pass('Checked addLiquidityEvent economics!') + log_step_pass('Checked addLiquidityEvent economics!') def check_remove_liquidity(self, event: RemoveLiquidityEvent): old_first_token_reserve = self.first_token_reserve @@ -96,23 +96,23 @@ def check_remove_liquidity(self, event: RemoveLiquidityEvent): expected_second_token_reserve = old_second_token_reserve - event.amountA if old_first_token_reserve <= new_first_token_reserve: - print_test_step_fail(f'First token reserve did not decrease for pair {self.contract_address.bech32()}') - print_test_substep(f'Old first token reserve: {old_first_token_reserve}') - print_test_substep(f'New first token reserve: {new_first_token_reserve}') - print_test_substep(f'Maximum first token reserve expected: {expected_first_token_reserve}') + log_step_fail(f'First token reserve did not decrease for pair {self.contract_address.bech32()}') + log_substep(f'Old first token reserve: {old_first_token_reserve}') + log_substep(f'New first token reserve: {new_first_token_reserve}') + log_substep(f'Maximum first token reserve expected: {expected_first_token_reserve}') if old_second_token_reserve <= new_second_token_reserve: - print_test_step_fail(f'Second token reserve did not decrease for pair {self.contract_address.bech32()}') - print_test_substep(f'Old second token reserve: {old_second_token_reserve}') - print_test_substep(f'New second token reserve: {new_second_token_reserve}') - print_test_substep(f'Maximum second token reserve expected: {expected_second_token_reserve}') + log_step_fail(f'Second token reserve did not decrease for pair {self.contract_address.bech32()}') + log_substep(f'Old second token reserve: {old_second_token_reserve}') + log_substep(f'New second token reserve: {new_second_token_reserve}') + log_substep(f'Maximum second token reserve expected: {expected_second_token_reserve}') if old_total_supply <= new_total_supply: - print_test_step_fail(f'Total supply did not decrease for pair {self.contract_address.bech32()}') - print_test_substep(f'Old supply: {old_total_supply}') - print_test_substep(f'New supply: {new_total_supply}') + log_step_fail(f'Total supply did not decrease for pair {self.contract_address.bech32()}') + log_substep(f'Old supply: {old_total_supply}') + log_substep(f'New supply: {new_total_supply}') - print_test_step_pass('Checked removeLiquidityEvent economics!') + log_step_pass('Checked removeLiquidityEvent economics!') def check_swap_fixed_input(self, event: SwapFixedInputEvent): old_first_token_reserve = self.first_token_reserve @@ -128,33 +128,33 @@ def check_swap_fixed_input(self, event: SwapFixedInputEvent): expected_second_token_reserve = old_second_token_reserve - event.amountBmin if old_first_token_reserve >= new_first_token_reserve: - print_test_step_fail(f'First token reserve did not increase for pair {self.contract_address.bech32()}') - print_test_substep(f'Old first token reserve: {old_first_token_reserve}') - print_test_substep(f'New first token reserve: {new_first_token_reserve}') - print_test_substep(f'Expected first token reserve: {expected_first_token_reserve}') + log_step_fail(f'First token reserve did not increase for pair {self.contract_address.bech32()}') + log_substep(f'Old first token reserve: {old_first_token_reserve}') + log_substep(f'New first token reserve: {new_first_token_reserve}') + log_substep(f'Expected first token reserve: {expected_first_token_reserve}') if old_second_token_reserve <= new_second_token_reserve: - print_test_step_fail(f'Second token reserve did not decrease for pair {self.contract_address.bech32()}') - print_test_substep(f'Old second token reserve: {old_second_token_reserve}') - print_test_substep(f'New second token reserve: {new_second_token_reserve}') - print_test_substep(f'Maximum expected second token reserve: {expected_second_token_reserve}') + log_step_fail(f'Second token reserve did not decrease for pair {self.contract_address.bech32()}') + log_substep(f'Old second token reserve: {old_second_token_reserve}') + log_substep(f'New second token reserve: {new_second_token_reserve}') + log_substep(f'Maximum expected second token reserve: {expected_second_token_reserve}') else: expected_first_token_reserve = old_first_token_reserve - event.amountBmin expected_second_token_reserve = old_second_token_reserve + event.amountA if old_first_token_reserve <= new_first_token_reserve: - print_test_step_fail(f'First token reserve did not decrease for pair {self.contract_address.bech32()}') - print_test_substep(f'Old first token reserve: {old_first_token_reserve}') - print_test_substep(f'New first token reserve: {new_first_token_reserve}') - print_test_substep(f'Expected first token reserve: {expected_first_token_reserve}') + log_step_fail(f'First token reserve did not decrease for pair {self.contract_address.bech32()}') + log_substep(f'Old first token reserve: {old_first_token_reserve}') + log_substep(f'New first token reserve: {new_first_token_reserve}') + log_substep(f'Expected first token reserve: {expected_first_token_reserve}') if old_second_token_reserve >= new_second_token_reserve: - print_test_step_fail(f'Second token reserve did not increase for pair {self.contract_address.bech32()}') - print_test_substep(f'Old second token reserve: {old_second_token_reserve}') - print_test_substep(f'New second token reserve: {new_second_token_reserve}') - print_test_substep(f'Maximum expected second token reserve: {expected_second_token_reserve}') + log_step_fail(f'Second token reserve did not increase for pair {self.contract_address.bech32()}') + log_substep(f'Old second token reserve: {old_second_token_reserve}') + log_substep(f'New second token reserve: {new_second_token_reserve}') + log_substep(f'Maximum expected second token reserve: {expected_second_token_reserve}') - print_test_step_pass(f'Checked swapFixedInputEvent economics') + log_step_pass(f'Checked swapFixedInputEvent economics') def check_swap_fixed_output(self, event: SwapFixedOutputEvent): old_first_token_reserve = self.first_token_reserve @@ -170,39 +170,39 @@ def check_swap_fixed_output(self, event: SwapFixedOutputEvent): expected_second_token_reserve = old_second_token_reserve - event.amountB if old_first_token_reserve >= new_first_token_reserve: - print_test_step_fail(f'First token reserve did not increase for pair {self.contract_address.bech32()}') - print_test_substep(f'Old first token reserve: {old_first_token_reserve}') - print_test_substep(f'New first token reserve: {new_first_token_reserve}') - print_test_substep(f'Maximum expected first token reserve: {expected_first_token_reserve}') + log_step_fail(f'First token reserve did not increase for pair {self.contract_address.bech32()}') + log_substep(f'Old first token reserve: {old_first_token_reserve}') + log_substep(f'New first token reserve: {new_first_token_reserve}') + log_substep(f'Maximum expected first token reserve: {expected_first_token_reserve}') if old_second_token_reserve <= new_second_token_reserve: - print_test_step_fail(f'Second token reserve did not decrease for pair {self.contract_address.bech32()}') - print_test_substep(f'Old second token reserve: {old_second_token_reserve}') - print_test_substep(f'New second token reserve: {new_second_token_reserve}') - print_test_substep(f'Expected second token reserve: {expected_second_token_reserve}') + log_step_fail(f'Second token reserve did not decrease for pair {self.contract_address.bech32()}') + log_substep(f'Old second token reserve: {old_second_token_reserve}') + log_substep(f'New second token reserve: {new_second_token_reserve}') + log_substep(f'Expected second token reserve: {expected_second_token_reserve}') else: expected_first_token_reserve = old_first_token_reserve - event.amountB expected_second_token_reserve = old_second_token_reserve + event.amountAmax if old_first_token_reserve <= new_first_token_reserve: - print_test_step_fail(f'First token reserve did not decrease for pair {self.contract_address.bech32()}') - print_test_substep(f'Old first token reserve: {old_first_token_reserve}') - print_test_substep(f'New first token reserve: {new_first_token_reserve}') - print_test_substep(f'Maximum expected first token reserve: {expected_first_token_reserve}') + log_step_fail(f'First token reserve did not decrease for pair {self.contract_address.bech32()}') + log_substep(f'Old first token reserve: {old_first_token_reserve}') + log_substep(f'New first token reserve: {new_first_token_reserve}') + log_substep(f'Maximum expected first token reserve: {expected_first_token_reserve}') if old_second_token_reserve >= new_second_token_reserve: - print_test_step_fail(f'Second token reserve did not increase for pair {self.contract_address.bech32()}') - print_test_substep(f'Old second token reserve: {old_second_token_reserve}') - print_test_substep(f'New second token reserve: {new_second_token_reserve}') - print_test_substep(f'Expected second token reserve: {expected_second_token_reserve}') + log_step_fail(f'Second token reserve did not increase for pair {self.contract_address.bech32()}') + log_substep(f'Old second token reserve: {old_second_token_reserve}') + log_substep(f'New second token reserve: {new_second_token_reserve}') + log_substep(f'Expected second token reserve: {expected_second_token_reserve}') - print_test_step_pass(f'Checked swapFixedOutputEvent economics') + log_step_pass(f'Checked swapFixedOutputEvent economics') def update(self, publisher: Observable): if publisher.contract is not None: if self.contract_address.bech32() == publisher.contract.address: if publisher.tx_hash: - self.network_provider.api.wait_for_tx_finalized(publisher.tx_hash) + self.network_provider.wait_for_tx_executed(publisher.tx_hash) if type(publisher.event) == AddLiquidityEvent: self.check_add_liquidity(publisher.event) elif type(publisher.event) == RemoveLiquidityEvent: diff --git a/trackers/price_discovery_economics_tracking.py b/trackers/price_discovery_economics_tracking.py index 0039c24..f34ba7f 100644 --- a/trackers/price_discovery_economics_tracking.py +++ b/trackers/price_discovery_economics_tracking.py @@ -5,7 +5,7 @@ WithdrawPDLiquidityEvent, RedeemPDLPTokensEvent) from contracts.contract_identities import PriceDiscoveryContractIdentity from utils.utils_chain import get_all_token_nonces_details_for_account, get_token_details_for_address -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep +from utils.utils_generic import log_step_fail, log_step_pass, log_substep from erdpy.accounts import Address from erdpy.proxy import ElrondProxy @@ -56,16 +56,16 @@ def __check_deposit_event(self, event: DepositPDLiquidityEvent): new_second_token_reserve = self.second_token_reserve + event.amount if chain_first_token_reserve != new_first_token_reserve: - print_test_step_fail("TEST CHECK FAIL: First token reserve not as expected!") - print_test_substep(f"Chain first token reserve: {chain_first_token_reserve}") - print_test_substep(f"Expected first token reserve: {new_first_token_reserve}") + log_step_fail("TEST CHECK FAIL: First token reserve not as expected!") + log_substep(f"Chain first token reserve: {chain_first_token_reserve}") + log_substep(f"Expected first token reserve: {new_first_token_reserve}") if chain_second_token_reserve != new_second_token_reserve: - print_test_step_fail("TEST CHECK FAIL: Second token reserve not as expected!") - print_test_substep(f"Chain second token reserve: {chain_second_token_reserve}") - print_test_substep(f"Expected second token reserve: {new_second_token_reserve}") + log_step_fail("TEST CHECK FAIL: Second token reserve not as expected!") + log_substep(f"Chain second token reserve: {chain_second_token_reserve}") + log_substep(f"Expected second token reserve: {new_second_token_reserve}") - print_test_step_pass("Checked deposit event data!") + log_step_pass("Checked deposit event data!") def __deposit_event_account_tracking(self, event: DepositPDLiquidityEvent, user_address: Address): if user_address.bech32() not in self.account_tracker.keys(): @@ -98,33 +98,33 @@ def __deposit_event_account_tracking(self, event: DepositPDLiquidityEvent, user_ if chain_token['nonce'] == self.pd_contract_identity.first_redeem_token_nonce: chain_first_redeem_tokens = int(chain_token['balance']) if chain_first_redeem_tokens != exp_first_redeem_tokens_owned: - print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") - print_test_substep(f"Chain first redeem tokens: {chain_first_redeem_tokens}") - print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + log_substep(f"Chain first redeem tokens: {chain_first_redeem_tokens}") + log_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") first_redeem_token_found = True if chain_token['nonce'] == self.pd_contract_identity.second_redeem_token_nonce: chain_second_redeem_tokens = int(chain_token['balance']) if chain_second_redeem_tokens != exp_second_redeem_tokens_owned: - print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") - print_test_substep(f"Chain second redeem tokens: {chain_second_redeem_tokens}") - print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + log_substep(f"Chain second redeem tokens: {chain_second_redeem_tokens}") + log_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") second_redeem_token_found = True if not first_redeem_token_found and exp_first_redeem_tokens_owned > 0: - print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") - print_test_substep(f"Chain first redeem tokens: 0") - print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + log_substep(f"Chain first redeem tokens: 0") + log_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") if not second_redeem_token_found and exp_second_redeem_tokens_owned > 0: - print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") - print_test_substep(f"Chain second redeem tokens: 0") - print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + log_substep(f"Chain second redeem tokens: 0") + log_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") # update redeem tokens data on account self.account_tracker[user_address.bech32()].first_redeem_tokens_owned = chain_first_redeem_tokens self.account_tracker[user_address.bech32()].second_redeem_tokens_owned = chain_second_redeem_tokens - print_test_step_pass("Tracked and checked deposit account data!") + log_step_pass("Tracked and checked deposit account data!") def deposit_event_tracking(self, event: DepositPDLiquidityEvent, user_address: Address, tx_hash: str): # TODO: check state based on tx_hash success @@ -138,7 +138,7 @@ def deposit_event_tracking(self, event: DepositPDLiquidityEvent, user_address: A self.second_token_reserve += event.amount self.second_redeem_tokens_reserve += event.amount - print_test_step_pass("Tracked deposit data!") + log_step_pass("Tracked deposit data!") def __check_withdraw_event(self, event: WithdrawPDLiquidityEvent): chain_first_token_reserve = self.contract_data_fetcher.get_token_reserve(self.pd_contract_identity.launched_token_id) @@ -152,16 +152,16 @@ def __check_withdraw_event(self, event: WithdrawPDLiquidityEvent): new_second_token_reserve = self.second_token_reserve - event.amount if chain_first_token_reserve != new_first_token_reserve: - print_test_step_fail("TEST CHECK FAIL: First token reserve not as expected!") - print_test_substep(f"Chain first token reserve: {chain_first_token_reserve}") - print_test_substep(f"Expected first token reserve: {new_first_token_reserve}") + log_step_fail("TEST CHECK FAIL: First token reserve not as expected!") + log_substep(f"Chain first token reserve: {chain_first_token_reserve}") + log_substep(f"Expected first token reserve: {new_first_token_reserve}") if chain_second_token_reserve != new_second_token_reserve: - print_test_step_fail("TEST CHECK FAIL: Second token reserve not as expected!") - print_test_substep(f"Chain second token reserve: {chain_second_token_reserve}") - print_test_substep(f"Expected second token reserve: {new_second_token_reserve}") + log_step_fail("TEST CHECK FAIL: Second token reserve not as expected!") + log_substep(f"Chain second token reserve: {chain_second_token_reserve}") + log_substep(f"Expected second token reserve: {new_second_token_reserve}") - print_test_step_pass("Checked withdraw event data!") + log_step_pass("Checked withdraw event data!") def __withdraw_event_account_tracking(self, event: WithdrawPDLiquidityEvent, user_address: Address): if user_address.bech32() not in self.account_tracker.keys(): @@ -194,33 +194,33 @@ def __withdraw_event_account_tracking(self, event: WithdrawPDLiquidityEvent, use if chain_token['nonce'] == self.pd_contract_identity.first_redeem_token_nonce: chain_first_redeem_tokens = int(chain_token['balance']) if chain_first_redeem_tokens != exp_first_redeem_tokens_owned: - print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") - print_test_substep(f"Chain first redeem tokens: {chain_first_redeem_tokens}") - print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + log_substep(f"Chain first redeem tokens: {chain_first_redeem_tokens}") + log_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") first_redeem_token_found = True if chain_token['nonce'] == self.pd_contract_identity.second_redeem_token_nonce: chain_second_redeem_tokens = int(chain_token['balance']) if chain_second_redeem_tokens != exp_second_redeem_tokens_owned: - print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") - print_test_substep(f"Chain second redeem tokens: {chain_second_redeem_tokens}") - print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + log_substep(f"Chain second redeem tokens: {chain_second_redeem_tokens}") + log_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") second_redeem_token_found = True if not first_redeem_token_found and exp_first_redeem_tokens_owned > 0: - print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") - print_test_substep(f"Chain first redeem tokens: 0") - print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + log_substep(f"Chain first redeem tokens: 0") + log_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") if not second_redeem_token_found and exp_second_redeem_tokens_owned > 0: - print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") - print_test_substep(f"Chain second redeem tokens: 0") - print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + log_substep(f"Chain second redeem tokens: 0") + log_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") # update redeem tokens data on account self.account_tracker[user_address.bech32()].first_redeem_tokens_owned = chain_first_redeem_tokens self.account_tracker[user_address.bech32()].second_redeem_tokens_owned = chain_second_redeem_tokens - print_test_step_pass("Tracked and checked withdraw account data!") + log_step_pass("Tracked and checked withdraw account data!") def withdraw_event_tracking(self, event: WithdrawPDLiquidityEvent, user_address: Address, tx_hash: str): # TODO: check state based on tx_hash success @@ -251,14 +251,14 @@ def __check_redeem_event(self, event: RedeemPDLPTokensEvent): self.__get_exp_lp_tokens_redeemed(event.amount, self.second_redeem_tokens_reserve) if chain_lp_tokens_reserve != exp_lp_tokens_reserve: - print_test_step_fail("TEST CHECK FAIL: LP tokens reserve not as expected!") - print_test_substep(f"Chain LP tokens reserve: {chain_lp_tokens_reserve}") - print_test_substep(f"Expected LP tokens reserve: {exp_lp_tokens_reserve}") + log_step_fail("TEST CHECK FAIL: LP tokens reserve not as expected!") + log_substep(f"Chain LP tokens reserve: {chain_lp_tokens_reserve}") + log_substep(f"Expected LP tokens reserve: {exp_lp_tokens_reserve}") # update LP tokens tracking data self.lp_tokens_reserve = chain_lp_tokens_reserve - print_test_step_pass("Checked redeem event data!") + log_step_pass("Checked redeem event data!") def __redeem_event_account_tracking(self, event: RedeemPDLPTokensEvent, user_address: Address): if user_address.bech32() not in self.account_tracker.keys(): @@ -297,39 +297,39 @@ def __redeem_event_account_tracking(self, event: RedeemPDLPTokensEvent, user_add if chain_token['nonce'] == self.pd_contract_identity.first_redeem_token_nonce: chain_first_redeem_tokens = int(chain_token['balance']) if chain_first_redeem_tokens != exp_first_redeem_tokens_owned: - print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") - print_test_substep(f"Chain first redeem tokens: {chain_first_redeem_tokens}") - print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + log_substep(f"Chain first redeem tokens: {chain_first_redeem_tokens}") + log_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") first_redeem_token_found = True if chain_token['nonce'] == self.pd_contract_identity.second_redeem_token_nonce: chain_second_redeem_tokens = int(chain_token['balance']) if chain_second_redeem_tokens != exp_second_redeem_tokens_owned: - print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") - print_test_substep(f"Chain second redeem tokens: {chain_second_redeem_tokens}") - print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + log_substep(f"Chain second redeem tokens: {chain_second_redeem_tokens}") + log_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") second_redeem_token_found = True if not first_redeem_token_found and exp_first_redeem_tokens_owned > 0: - print_test_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") - print_test_substep(f"Chain first redeem tokens: 0") - print_test_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: First redeem tokens on account not as expected!") + log_substep(f"Chain first redeem tokens: 0") + log_substep(f"Expected first redeem tokens: {exp_first_redeem_tokens_owned}") if not second_redeem_token_found and exp_second_redeem_tokens_owned > 0: - print_test_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") - print_test_substep(f"Chain second redeem tokens: 0") - print_test_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") + log_step_fail("TEST CHECK FAIL: Second redeem tokens on account not as expected!") + log_substep(f"Chain second redeem tokens: 0") + log_substep(f"Expected second redeem tokens: {exp_second_redeem_tokens_owned}") # check for lp tokens retrieved - the real deal if chain_lp_tokens_on_account != exp_lp_tokens_owned: - print_test_step_fail("TEST CHECK FAIL: LP tokens on account not as expected!") - print_test_substep(f"Chain LP tokens on account: {chain_lp_tokens_on_account}") - print_test_substep(f"Expected LP on account: {exp_lp_tokens_owned}") + log_step_fail("TEST CHECK FAIL: LP tokens on account not as expected!") + log_substep(f"Chain LP tokens on account: {chain_lp_tokens_on_account}") + log_substep(f"Expected LP on account: {exp_lp_tokens_owned}") # update redeem tokens data on account self.account_tracker[user_address.bech32()].first_redeem_tokens_owned = chain_first_redeem_tokens self.account_tracker[user_address.bech32()].second_redeem_tokens_owned = chain_second_redeem_tokens self.account_tracker[user_address.bech32()].lp_tokens_redeemed = chain_lp_tokens_on_account - print_test_step_pass("Tracked and checked redeem account data!") + log_step_pass("Tracked and checked redeem account data!") def redeem_event_tracking(self, event: RedeemPDLPTokensEvent, user_address: Address, tx_hash: str): # TODO: check state based on tx_hash success diff --git a/trackers/staking_economics_tracking.py b/trackers/staking_economics_tracking.py index b812ef2..5ccf791 100644 --- a/trackers/staking_economics_tracking.py +++ b/trackers/staking_economics_tracking.py @@ -4,7 +4,7 @@ from trackers.concrete_observer import Observable from events.farm_events import EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent from utils.contract_data_fetchers import StakingContractDataFetcher, ChainDataFetcher -from utils.utils_generic import print_test_step_fail, print_test_step_pass, print_test_substep +from utils.utils_generic import log_step_fail, log_step_pass, log_substep class StakingEconomics(Subscriber): @@ -54,32 +54,32 @@ def check_invariant_properties(self): chain_division_safety_constant = self.data_fetcher.get_data("getDivisionSafetyConstant") if self.rewards_per_share > new_rewards_per_share: - print_test_step_fail("TEST CHECK FAIL: Rewards per share decreased!") - print_test_substep(f"Old rewards per share: {self.rewards_per_share}") - print_test_substep(f"New rewards per share: {new_rewards_per_share}") + log_step_fail("TEST CHECK FAIL: Rewards per share decreased!") + log_substep(f"Old rewards per share: {self.rewards_per_share}") + log_substep(f"New rewards per share: {new_rewards_per_share}") if self.last_rewards_block_nonce > new_last_rewards_block_nonce: - print_test_step_fail("TEST CHECK FAIL: Last rewards block nonce decreased!") - print_test_substep(f"Old rewards block nonce: {self.last_rewards_block_nonce}") - print_test_substep(f"New rewards block nonce: {new_last_rewards_block_nonce}") + log_step_fail("TEST CHECK FAIL: Last rewards block nonce decreased!") + log_substep(f"Old rewards block nonce: {self.last_rewards_block_nonce}") + log_substep(f"New rewards block nonce: {new_last_rewards_block_nonce}") if self.rewards_per_block != chain_rewards_per_block: - print_test_step_fail("TEST CHECK FAIL: Rewards per block has changed!") - print_test_substep(f"Old rewards per block: {self.rewards_per_block}") - print_test_substep(f"New rewards per block: {chain_rewards_per_block}") + log_step_fail("TEST CHECK FAIL: Rewards per block has changed!") + log_substep(f"Old rewards per block: {self.rewards_per_block}") + log_substep(f"New rewards per block: {chain_rewards_per_block}") if self.division_safety_constant != chain_division_safety_constant: - print_test_step_fail("TEST CHECK FAIL: Division safety constant has changed!") - print_test_substep(f"Old division safety constant: {self.division_safety_constant}") - print_test_substep(f"New division safety constant: {chain_division_safety_constant}") + log_step_fail("TEST CHECK FAIL: Division safety constant has changed!") + log_substep(f"Old division safety constant: {self.division_safety_constant}") + log_substep(f"New division safety constant: {chain_division_safety_constant}") - print_test_step_pass("Checked invariant properties!") + log_step_pass("Checked invariant properties!") def check_enter_staking_properties(self): new_token_supply = self.data_fetcher.get_data("getFarmTokenSupply") if self.token_supply >= new_token_supply: - print_test_step_fail('Staking farm token supply did not increase') - print_test_substep(f"Old token supply: {self.token_supply}") - print_test_substep(f"New token supply: {new_token_supply}") + log_step_fail('Staking farm token supply did not increase') + log_substep(f"Old token supply: {self.token_supply}") + log_substep(f"New token supply: {new_token_supply}") - print_test_step_pass('Checked enter staking properties!') + log_step_pass('Checked enter staking properties!') def check_enter_staking_data(self, event: EnterFarmEvent, tx_hash: str): new_staking_token_supply = self.data_fetcher.get_data("getFarmTokenSupply") @@ -97,33 +97,33 @@ def check_enter_staking_data(self, event: EnterFarmEvent, tx_hash: str): new_exp_rewards_per_share = 0 if new_staking_token_supply != expected_token_supply: - print_test_step_fail('TEST CHECK FAIL: Staking token supply not as expected!') - print_test_substep(f"Old Staking token supply: {self.token_supply}") - print_test_substep(f"New Staking token supply: {new_staking_token_supply}") - print_test_substep(f"Expected Staking token supply: {expected_token_supply}") + log_step_fail('TEST CHECK FAIL: Staking token supply not as expected!') + log_substep(f"Old Staking token supply: {self.token_supply}") + log_substep(f"New Staking token supply: {new_staking_token_supply}") + log_substep(f"Expected Staking token supply: {expected_token_supply}") if new_contract_rewards_per_share != new_exp_rewards_per_share: - print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") - print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") - print_test_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") - print_test_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") + log_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + log_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + log_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") + log_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") if tx_block != new_last_rewards_block_nonce: - print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") - print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") - print_test_substep(f"Expected last reward block nonce: {tx_block}") + log_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + log_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + log_substep(f"Expected last reward block nonce: {tx_block}") self.last_block_calculated_rewards = tx_block - print_test_step_pass('Checked enter staking data!') + log_step_pass('Checked enter staking data!') def check_exit_staking_properties(self): new_token_supply = self.data_fetcher.get_data('getFarmTokenSupply') if self.token_supply <= new_token_supply: - print_test_step_fail('Staking farm token supply did not decrease') - print_test_substep(f"Old token supply: {self.token_supply}") - print_test_substep(f"New token supply: {new_token_supply}") + log_step_fail('Staking farm token supply did not decrease') + log_substep(f"Old token supply: {self.token_supply}") + log_substep(f"New token supply: {new_token_supply}") - print_test_step_pass('Checked exit staking properties') + log_step_pass('Checked exit staking properties') def check_exit_staking_data(self, event: ExitFarmEvent, tx_hash: str): new_token_supply = self.data_fetcher.get_data('getFarmTokenSupply') @@ -138,32 +138,32 @@ def check_exit_staking_data(self, event: ExitFarmEvent, tx_hash: str): (self.division_safety_constant * aggregated_rewards // self.token_supply) if new_token_supply != expected_token_supply: - print_test_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") - print_test_substep(f"Farm token supply in contract: {new_token_supply}") - print_test_substep(f"Expected Farm token supply: {expected_token_supply}") + log_step_fail(f"TEST CHECK FAIL: Farm token supply not as expected!") + log_substep(f"Farm token supply in contract: {new_token_supply}") + log_substep(f"Expected Farm token supply: {expected_token_supply}") if new_contract_rewards_per_share != expected_rewards_per_share: - print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") - print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") - print_test_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") - print_test_substep(f"Expected Rewards per share: {expected_rewards_per_share}") + log_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + log_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + log_substep(f"New Rewards per share in contract: {new_contract_rewards_per_share}") + log_substep(f"Expected Rewards per share: {expected_rewards_per_share}") if tx_block != new_last_rewards_block_nonce: - print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") - print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") - print_test_substep(f"Expected last reward block nonce: {tx_block}") + log_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + log_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + log_substep(f"Expected last reward block nonce: {tx_block}") self.last_block_calculated_rewards = tx_block - print_test_step_pass('Checked exit staking data') + log_step_pass('Checked exit staking data') def check_claim_rewards_properties(self): new_token_supply = self.data_fetcher.get_data('getFarmTokenSupply') if self.token_supply != new_token_supply: - print_test_step_fail('Token supply modified!') - print_test_substep(f"Old Farm token supply: {self.token_supply}") - print_test_substep(f"New Farm token supply: {new_token_supply}") + log_step_fail('Token supply modified!') + log_substep(f"Old Farm token supply: {self.token_supply}") + log_substep(f"New Farm token supply: {new_token_supply}") - print_test_step_pass("Checked claim rewards properties!") + log_step_pass("Checked claim rewards properties!") def check_claim_rewards_data(self, tx_hash): new_token_supply = self.data_fetcher.get_data('getFarmTokenSupply') @@ -176,23 +176,23 @@ def check_claim_rewards_data(self, tx_hash): (self.division_safety_constant * aggregated_rewards // self.token_supply) if new_token_supply != self.token_supply: - print_test_step_fail(f"TEST CHECK FAIL: Token supply not as expected!") - print_test_substep(f"Farm token supply in contract: {new_token_supply}") - print_test_substep(f"Expected Farm token supply: {self.token_supply}") + log_step_fail(f"TEST CHECK FAIL: Token supply not as expected!") + log_substep(f"Farm token supply in contract: {new_token_supply}") + log_substep(f"Expected Farm token supply: {self.token_supply}") if new_rewards_per_share != new_exp_rewards_per_share: - print_test_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") - print_test_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") - print_test_substep(f"New Rewards per share in contract: {new_rewards_per_share}") - print_test_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") + log_step_fail(f"TEST CHECK FAIL: Rewards per share not as expected!") + log_substep(f"Old Rewards per share in contract: {self.rewards_per_share}") + log_substep(f"New Rewards per share in contract: {new_rewards_per_share}") + log_substep(f"Expected Rewards per share: {new_exp_rewards_per_share}") if tx_block != new_last_rewards_block_nonce: - print_test_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") - print_test_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") - print_test_substep(f"Expected last reward block nonce: {tx_block}") + log_step_fail(f"TEST CHECK FAIL: Last rewards block nonce not as expected!") + log_substep(f"Last reward block nonce in contract: {new_last_rewards_block_nonce}") + log_substep(f"Expected last reward block nonce: {tx_block}") self.last_block_calculated_rewards = tx_block - print_test_step_pass("Checked claim staking rewards data!") + log_step_pass("Checked claim staking rewards data!") def enter_staking_event(self, event: EnterFarmEvent, tx_hash): self.check_invariant_properties() @@ -218,7 +218,7 @@ def claim_rewards_staking_event(self, tx_hash): def update(self, publisher: Observable): if publisher.contract is not None: if self.contract_address.bech32() == publisher.contract.address: - self.network_provider.api.wait_for_tx_finalized(publisher.tx_hash) + self.network_provider.wait_for_tx_executed(publisher.tx_hash) if type(publisher.event) == EnterFarmEvent: self.enter_staking_event(publisher.event, publisher.tx_hash) elif type(publisher.event) == ExitFarmEvent: diff --git a/utils/contract_data_fetchers.py b/utils/contract_data_fetchers.py index ec1039e..9b95266 100644 --- a/utils/contract_data_fetchers.py +++ b/utils/contract_data_fetchers.py @@ -3,10 +3,13 @@ from multiversx_sdk_core import Address, ContractQueryBuilder from multiversx_sdk_network_providers import ProxyNetworkProvider - +from utils.logger import get_logger from utils.utils_chain import base64_to_hex +logger = get_logger(__name__) + + class DataFetcher: def __init__(self, contract_address: Address, proxy_url: str): self.proxy = ProxyNetworkProvider(proxy_url) @@ -17,6 +20,7 @@ def get_data(self, view_name: str, attrs: list = []): if view_name in self.view_handler_map: return self.view_handler_map[view_name](view_name, attrs) else: + logger.error(f"View name not registered in {type(self).__name__}") raise ValueError(f"View name not registered in {type(self).__name__}") def _query_contract(self, view_name: str, attrs: list = []): @@ -33,10 +37,9 @@ def _get_int_view(self, view_name: str, attrs) -> int: result = self._query_contract(view_name, attrs) if result.return_data == '': return 0 - return int(base64_to_hex(result.return_data), base=16) + return int(base64_to_hex(result.return_data[0]), base=16) except Exception as ex: - print(f"Exception encountered on view name {view_name}: {ex}") - traceback.print_exception(*sys.exc_info()) + logger.exception(f"Exception encountered on view name {view_name}: {ex}") return -1 def _get_int_list_view(self, view_name: str, attrs) -> list: @@ -44,17 +47,15 @@ def _get_int_list_view(self, view_name: str, attrs) -> list: result = self._query_contract(view_name, attrs) return [int(base64_to_hex(elem), base=16) for elem in result.return_data] except Exception as ex: - print(f"Exception encountered on view name {view_name}: {ex}") - traceback.print_exception(*sys.exc_info()) + logger.exception(f"Exception encountered on view name {view_name}: {ex}") return [] def _get_hex_view(self, view_name: str, attrs) -> str: try: result = self._query_contract(view_name, attrs) - return base64_to_hex(result.return_data) + return base64_to_hex(result.return_data[0]) except Exception as ex: - print(f"Exception encountered on view name {view_name}: {ex}") - traceback.print_exception(*sys.exc_info()) + logger.exception(f"Exception encountered on view name {view_name}: {ex}") return "" def _get_hex_list_view(self, view_name: str, attrs) -> list: @@ -62,8 +63,7 @@ def _get_hex_list_view(self, view_name: str, attrs) -> list: result = self._query_contract(view_name, attrs) return [base64_to_hex(elem) for elem in result.return_data] except Exception as ex: - print(f"Exception encountered on view name {view_name}: {ex}") - traceback.print_exception(*sys.exc_info()) + logger.exception(f"Exception encountered on view name {view_name}: {ex}") return [] diff --git a/utils/logger.py b/utils/logger.py index fc331be..86362b6 100644 --- a/utils/logger.py +++ b/utils/logger.py @@ -1,20 +1,42 @@ import logging +class ScreenFormatter(logging.Formatter): + grey = "\x1b[38;20m" + yellow = "\033[93m" + green = '\033[92m' + red = "\x1b[31;20m" + bold_red = "\x1b[31;1m" + reset = "\x1b[0m" + format = "%(asctime)s - %(levelname)s - %(message)s" + + FORMATS = { + logging.DEBUG: grey + format + reset, + logging.INFO: green + format + reset, + logging.WARNING: yellow + format + reset, + logging.ERROR: red + format + reset, + logging.CRITICAL: bold_red + format + reset + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + + def get_logger(name: str) -> logging.Logger: logger = logging.getLogger(name) logger.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s : %(message)s") - # console handler console_handler = logging.StreamHandler() console_handler.setLevel(logging.DEBUG) - console_handler.setFormatter(formatter) + console_handler.setFormatter(ScreenFormatter()) # file handler file_handler = logging.FileHandler(f"trace.log") file_handler.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s : %(message)s") file_handler.setFormatter(formatter) logger.addHandler(console_handler) diff --git a/utils/utils_chain.py b/utils/utils_chain.py index 0f56a7a..8ec951c 100644 --- a/utils/utils_chain.py +++ b/utils/utils_chain.py @@ -1,14 +1,10 @@ import base64 import time -import logging from multiprocessing import Pool from os import path from pathlib import Path from typing import List, Dict, Any, Optional, Set, cast - from multiversx_sdk_network_providers.tokens import FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork - -from contracts.contract_identities import FarmContractVersion from multiversx_sdk_core import Address, Transaction from multiversx_sdk_core.interfaces import ISignature from multiversx_sdk_wallet import UserSigner, pem_format @@ -55,9 +51,8 @@ def __init__(self, self.address = Address.from_hex(self.signer.get_pubkey().hex(), "erd") def sync_nonce(self, proxy: ProxyNetworkProvider): - logger.info("Account.sync_nonce()") self.nonce = proxy.get_account(self.address).nonce - logger.info(f"Account.sync_nonce() done: {self.nonce}") + logger.debug(f"Account.sync_nonce() done: {self.nonce}") def sign_transaction(self, transaction: Transaction) -> ISignature: return self.signer.sign(transaction) @@ -295,76 +290,6 @@ def build_token_ticker(owner: Address, prefix: str = ""): return prefix, hex -class DecodedTokenAttributes: - rewards_per_share: int - original_entering_epoch: int - entering_epoch: int - apr_multiplier: int - locked_rewards: bool - initial_farming_amount: int - compounded_rewards: int - current_farm_amount: int - - def __init__(self, attributes_hex: str, attr_version: FarmContractVersion = None): - def slide_indexes(i, j, no_bytes: int): - index_f = j - index_l = j + (no_bytes * 2) - return index_f, index_l - - self.rewards_per_share = 0 - self.apr_multiplier = 0 - self.locked_rewards = False - self.current_farm_amount = 0 - self.compounded_rewards = 0 - self.initial_farming_amount = 0 - - # decode rewards per share BigUInt - index_first = 0 - index_last = 8 - payload_size = int(attributes_hex[index_first:index_last], 16) - if payload_size: - index_first, index_last = slide_indexes(index_first, index_last, payload_size) - self.rewards_per_share = int(attributes_hex[index_first:index_last], 16) - - # decode original entering epoch U64 - index_first, index_last = slide_indexes(index_first, index_last, 8) - self.entering_epoch = int(attributes_hex[index_first:index_last], 16) - - # decode entering epoch U64 - index_first, index_last = slide_indexes(index_first, index_last, 8) - self.original_entering_epoch = int(attributes_hex[index_first:index_last], 16) - - if attr_version == FarmContractVersion.V12: - # decode APR multiplier U8 - index_first, index_last = slide_indexes(index_first, index_last, 1) - self.apr_multiplier = int(attributes_hex[index_first:index_last], 16) - - # decode Locked Rewards U8 - index_first, index_last = slide_indexes(index_first, index_last, 1) - self.locked_rewards = bool(int(attributes_hex[index_first:index_last], 16)) - - # decode Initial Farming amount BigUInt - index_first, index_last = slide_indexes(index_first, index_last, 4) - payload_size = int(attributes_hex[index_first:index_last], 16) - if payload_size: - index_first, index_last = slide_indexes(index_first, index_last, payload_size) - self.initial_farming_amount = int(attributes_hex[index_first:index_last], 16) - - # decode Compounded Rewards BigUInt - index_first, index_last = slide_indexes(index_first, index_last, 4) - payload_size = int(attributes_hex[index_first:index_last], 16) - if payload_size: - index_first, index_last = slide_indexes(index_first, index_last, payload_size) - self.compounded_rewards = int(attributes_hex[index_first:index_last], 16) - - # decode Current Farm amount BigUInt - index_first, index_last = slide_indexes(index_first, index_last, 4) - payload_size = int(attributes_hex[index_first:index_last], 16) - if payload_size: - index_first, index_last = slide_indexes(index_first, index_last, payload_size) - self.current_farm_amount = int(attributes_hex[index_first:index_last], 16) - - def decode_merged_attributes(attributes_hex: str, decode_struct: dict) -> dict: def slide_indexes(j, no_bytes: int): index_f = j diff --git a/utils/utils_generic.py b/utils/utils_generic.py index 81d0b34..1a08ea6 100644 --- a/utils/utils_generic.py +++ b/utils/utils_generic.py @@ -286,21 +286,21 @@ def print_color(msg, color: PrintColors): print(f"{color.value}{msg}{PrintColors.ENDC.value}") -def print_test_step_fail(msg): - print_color(msg, PrintColors.FAIL) +def log_step_fail(msg): + logger.error(msg) -def print_test_step_pass(msg): - print_color(msg, PrintColors.PASS) +def log_step_pass(msg): + logger.info(msg) -def print_test_substep(msg): +def log_substep(msg): sub_step_print_header = " ├ " - print(f"{sub_step_print_header}{msg}") + logger.info(f"{sub_step_print_header}{msg}") -def print_warning(msg): - print_color(msg, PrintColors.WARNING) +def log_warning(msg): + logger.warning(msg) def log_unexpected_args(function_purpose: str, args: Any): @@ -308,12 +308,12 @@ def log_unexpected_args(function_purpose: str, args: Any): logger.debug(f"Unexpected arguments: {args}") -def print_condition_assert(conditions: Dict[bool, str]): +def log_condition_assert(conditions: Dict[bool, str]): for condition, message in conditions.items(): if condition: - print_test_step_pass(f"PASS: {message}") + log_step_pass(f"PASS: {message}") else: - print_test_step_fail(f"FAIL: {message}") + log_step_fail(f"FAIL: {message}") class TestStepCondition: @@ -334,6 +334,6 @@ def add_condition(self, condition: bool, message: str): def assert_conditions(self): for condition in self.conditions: if condition.condition: - print_test_step_pass(f"PASS: {condition.message}") + log_step_pass(f"PASS: {condition.message}") else: - print_test_step_fail(f"FAIL: {condition.message}") + log_step_fail(f"FAIL: {condition.message}") diff --git a/utils/utils_tx.py b/utils/utils_tx.py index ddad523..e38ac68 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -14,7 +14,7 @@ MultiESDTNFTTransferBuilder, ContractDeploymentBuilder, ContractUpgradeBuilder from utils.logger import get_logger from utils.utils_chain import Account, log_explorer_transaction -from utils.utils_generic import print_test_step_fail, print_warning, split_to_chunks, get_continue_confirmation, \ +from utils.utils_generic import log_step_fail, log_warning, split_to_chunks, get_continue_confirmation, \ log_unexpected_args TX_CACHE: Dict[str, dict] = {} @@ -67,20 +67,21 @@ def wait_for_tx_executed(self, tx_hash: str): time.sleep(2) # temporary fix for the api returning the wrong status while True: status = self.api.get_transaction_status(tx_hash) + logger.debug(f"Transaction {tx_hash} status: {status.status}") if status.is_executed(): break - time.sleep(self.network.round_duration) + time.sleep(self.network.round_duration // 1000) def check_deploy_tx_status(self, tx_hash: str, address: str, msg_label: str = "") -> bool: if not tx_hash: if msg_label: - print_test_step_fail(f"FAIL: no tx hash for {msg_label} contract deployment!") + log_step_fail(f"FAIL: no tx hash for {msg_label} contract deployment!") return False status = self.api.get_transaction_status(tx_hash) if status.is_failed() or address == "": if msg_label: - print_test_step_fail(f"FAIL: transaction for {msg_label} contract deployment failed " + log_step_fail(f"FAIL: transaction for {msg_label} contract deployment failed " f"or couldn't retrieve address!") return False return True @@ -88,22 +89,23 @@ def check_deploy_tx_status(self, tx_hash: str, address: str, msg_label: str = "" def check_complex_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: if not tx_hash: if msg_label: - print_test_step_fail(f"FAIL: no tx hash for {msg_label} transaction!") + log_step_fail(f"FAIL: no tx hash for {msg_label} transaction!") return False + logger.debug(f"Waiting for transaction {tx_hash} to be executed...") self.wait_for_tx_executed(tx_hash) return self.check_simple_tx_status(tx_hash, msg_label) def check_simple_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: if not tx_hash: if msg_label: - print_test_step_fail(f"FAIL: no tx hash for {msg_label} transaction!") + log_step_fail(f"FAIL: no tx hash for {msg_label} transaction!") return False results = self.api.get_transaction_status(tx_hash) if results.is_failed(): if msg_label: - print_test_step_fail(f"Transaction to {msg_label} failed!") + log_step_fail(f"Transaction to {msg_label} failed!") return False return True @@ -211,8 +213,6 @@ def prepare_deploy_tx(deployer: Account, network_config: NetworkConfig, tx.signature = deployer.sign_transaction(tx) logger.debug(f"Deploy arguments: {args}") - logger.debug(f"Transaction: {tx.to_dictionary()}") - logger.debug(f"Transaction data: {tx.data}") return tx @@ -237,8 +237,6 @@ def prepare_upgrade_tx(deployer: Account, contract_address: Address, network_con tx.signature = deployer.sign_transaction(tx) logger.debug(f"Upgrade arguments: {args}") - logger.debug(f"Transaction: {tx.to_dictionary()}") - logger.debug(f"Transaction data: {tx.data}") return tx @@ -262,6 +260,8 @@ def prepare_contract_call_tx(contract_address: Address, deployer: Account, tx = builder.build() tx.signature = deployer.sign_transaction(tx) + logger.debug(f"Contract call arguments: {args}") + return tx @@ -286,6 +286,8 @@ def prepare_multiesdtnfttransfer_to_endpoint_call_tx(contract_address: Address, tx = builder.build() tx.signature = user.sign_transaction(tx) + logger.debug(f"Contract call arguments: {endpoint_args}") + return tx @@ -314,7 +316,7 @@ def send_deploy_tx(tx: Transaction, proxy: ProxyNetworkProvider) -> str: tx_hash = proxy.send_transaction(tx) log_explorer_transaction(tx_hash, proxy.url) except Exception as ex: - print_test_step_fail(f"Failed to deploy due to: {ex}") + log_step_fail(f"Failed to deploy due to: {ex}") traceback.print_exception(*sys.exc_info()) tx_hash = "" @@ -327,7 +329,7 @@ def send_contract_call_tx(tx: Transaction, proxy: ProxyNetworkProvider) -> str: # TODO: check if needed to wait for tx to be processed log_explorer_transaction(tx_hash, proxy.url) except Exception as ex: - print_test_step_fail(f"Failed to send tx due to: {ex}") + log_step_fail(f"Failed to send tx due to: {ex}") traceback.print_exception(*sys.exc_info()) return "" @@ -340,12 +342,12 @@ def multi_esdt_endpoint_call(function_purpose: str, proxy: ProxyNetworkProvider, type[List[ESDTToken]]: tokens list opt: type[str..]: endpoint arguments """ - print_warning(function_purpose) + log_warning(function_purpose) network_config = proxy.get_network_config() # TODO: find solution to avoid this call tx_hash = "" if len(args) < 1: - print_test_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") + log_step_fail(f"FAIL: Failed to {function_purpose}. Args list not as expected.") return tx_hash ep_args = args[1:] if len(args) != 1 else [] @@ -361,7 +363,7 @@ def multi_esdt_transfer(proxy: ProxyNetworkProvider, gas: int, user: Account, de """ Expected as args: type[ESDTToken...]: tokens list """ - logger.info(f"Sending multi esdt transfer to {dest}") + logger.debug(f"Sending multi esdt transfer to {dest}") logger.debug(f"Args: {args}") network_config = proxy.get_network_config() # TODO: find solution to avoid this call tx_hash = "" @@ -382,7 +384,7 @@ def endpoint_call(proxy: ProxyNetworkProvider, gas: int, user: Account, contract """ Expected as args: opt: type[str..]: endpoint arguments """ - logger.info(f"Calling {endpoint} at {contract.bech32()}") + logger.debug(f"Calling {endpoint} at {contract.bech32()}") logger.debug(f"Args: {args}") network_config = proxy.get_network_config() # TODO: find solution to avoid this call @@ -395,8 +397,7 @@ def endpoint_call(proxy: ProxyNetworkProvider, gas: int, user: Account, contract def deploy(contract_label: str, proxy: ProxyNetworkProvider, gas: int, owner: Account, bytecode_path: Path, metadata: ICodeMetadata, args: list) -> (str, str): - logger.info(f"Deploy {contract_label} contract") - logger.debug(f"Args: {args}") + logger.debug(f"Deploy {contract_label}") network_config = proxy.get_network_config() # TODO: find solution to avoid this call tx_hash, contract_address = "", "" @@ -404,8 +405,14 @@ def deploy(contract_label: str, proxy: ProxyNetworkProvider, gas: int, tx_hash = send_deploy_tx(tx, proxy) if tx_hash: + time.sleep(2) while not proxy.get_transaction_status(tx_hash).is_executed(): time.sleep(2) + + if not proxy.get_transaction_status(tx_hash).is_successful(): + log_step_fail(f"Transaction to deploy {contract_label} contract failed.") + return tx_hash, contract_address + contract_address = get_deployed_address_from_tx(tx_hash, proxy) owner.nonce += 1 @@ -415,8 +422,7 @@ def deploy(contract_label: str, proxy: ProxyNetworkProvider, gas: int, def upgrade_call(contract_label: str, proxy: ProxyNetworkProvider, gas: int, owner: Account, contract: Address, bytecode_path: Path, metadata: ICodeMetadata, args: list) -> str: - logger.info(f"Upgrade {contract_label} contract") - logger.debug(f"Args: {args}") + logger.debug(f"Upgrade {contract_label} contract") network_config = proxy.get_network_config() # TODO: find solution to avoid this call tx = prepare_upgrade_tx(owner, contract, network_config, gas, bytecode_path, metadata, args) @@ -441,8 +447,7 @@ def get_deployed_address_from_tx(tx_hash: str, proxy: ProxyNetworkProvider) -> s tx = proxy.get_transaction(tx_hash) contract_address = get_deployed_address_from_event(tx) except Exception as ex: - print_test_step_fail(f"Failed to get contract address due to: {ex}") - traceback.print_exception(*sys.exc_info()) + logger.exception(f"Failed to get contract address due to: {ex}") contract_address = "" return contract_address @@ -452,17 +457,17 @@ def broadcast_transactions(transactions: List[Transaction], proxy: ProxyNetworkP chunk_size: int, sleep: int = 0, confirm_yes: bool = False): chunks = list(split_to_chunks(transactions, chunk_size)) - print(f"{len(transactions)} transactions have been prepared, in {len(chunks)} chunks of size {chunk_size}") + logger.debug(f"{len(transactions)} transactions have been prepared, in {len(chunks)} chunks of size {chunk_size}") get_continue_confirmation(confirm_yes) chunk_index = 0 hashes = [] for chunk in chunks: - print("... chunk", chunk_index, "out of", len(chunks)) + logger.debug(f"... chunk {chunk_index} out of {len(chunks)}") num_sent, sent_hashes = proxy.send_transactions(chunk) if len(chunk) != num_sent: - print(f"sent {num_sent} instead of {len(chunk)}") + logger.debug(f"sent {num_sent} instead of {len(chunk)}") chunk_index += 1 hashes.extend(sent_hashes) From b57c3643c1f3ebb5f057124a333225210a6a415e Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Tue, 28 Mar 2023 14:08:14 +0300 Subject: [PATCH 07/26] small fixes and adjustments --- .gitignore | 3 ++- contracts/simple_lock_energy_contract.py | 2 +- utils/contract_data_fetchers.py | 4 ++-- utils/contract_retrievers.py | 12 +++++++++++- utils/decoding_structures.py | 6 ++++++ utils/utils_tx.py | 22 +++++++++------------- 6 files changed, 31 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index f6720d0..b3eef99 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /venv* /.idea /.vscode -/logs/* \ No newline at end of file +/logs/* +/tools/notebooks/env.py \ No newline at end of file diff --git a/contracts/simple_lock_energy_contract.py b/contracts/simple_lock_energy_contract.py index 2c89cae..b883f1f 100644 --- a/contracts/simple_lock_energy_contract.py +++ b/contracts/simple_lock_energy_contract.py @@ -88,7 +88,7 @@ def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytec arguments = [ self.base_token, # base token id args[0], # legacy token id - args[1], # locked asset factory address + Address(args[1]), # locked asset factory address args[2] # min migrated token locking epochs ] diff --git a/utils/contract_data_fetchers.py b/utils/contract_data_fetchers.py index 9b95266..790b715 100644 --- a/utils/contract_data_fetchers.py +++ b/utils/contract_data_fetchers.py @@ -98,7 +98,7 @@ def __init__(self, contract_address: Address, proxy_url: str): "getPenaltyPercentage": self._get_int_list_view, "getFeesBurnPercentage": self._get_int_view, "getEnergyAmountForUser": self._get_int_view, - "getEnergyEntryForUser": self._get_int_view, + "getEnergyEntryForUser": self._get_hex_view, "getFeesCollectorAddress": self._get_hex_view, } @@ -112,7 +112,7 @@ def __init__(self, contract_address: Address, proxy_url: str): "getFarmProxyTokenId": self._get_hex_view, "getBaseAssetTokenId": self._get_hex_view, "getEnergyAmountForUser": self._get_int_view, - "getEnergyEntryForUser": self._get_int_view, + "getEnergyEntryForUser": self._get_hex_view, "getFeesBurnPercentage": self._get_int_view, "getPenaltyPercentage": self._get_hex_view, "getLockOptions": self._get_int_list_view, diff --git a/utils/contract_retrievers.py b/utils/contract_retrievers.py index 760abe2..46449f2 100644 --- a/utils/contract_retrievers.py +++ b/utils/contract_retrievers.py @@ -7,6 +7,7 @@ from contracts.fees_collector_contract import FeesCollectorContract from contracts.metastaking_contract import MetaStakingContract from contracts.pair_contract import PairContract +from contracts.locked_asset_contract import LockedAssetContract import config from contracts.router_contract import RouterContract from contracts.simple_lock_energy_contract import SimpleLockEnergyContract @@ -14,7 +15,7 @@ from contracts.unstaker_contract import UnstakerContract from utils.contract_data_fetchers import PairContractDataFetcher, RouterContractDataFetcher, \ FarmContractDataFetcher, SimpleLockEnergyContractDataFetcher, StakingContractDataFetcher, \ - MetaStakingContractDataFetcher, ProxyContractDataFetcher + MetaStakingContractDataFetcher, ProxyContractDataFetcher, LockedAssetContractDataFetcher from utils.utils_chain import hex_to_string, WrapperAddress as Address @@ -62,6 +63,15 @@ def retrieve_simple_lock_energy_by_address(address: str) -> Optional[SimpleLockE return contract +def retrieve_locked_asset_factory_by_address(address: str) -> Optional[LockedAssetContract]: + data_fetcher = LockedAssetContractDataFetcher(Address(address), config.DEFAULT_PROXY) + base_token = hex_to_string(data_fetcher.get_data("getAssetTokenId")) + locked_token = hex_to_string(data_fetcher.get_data("getLockedAssetTokenId")) + + contract = LockedAssetContract(locked_asset=locked_token, unlocked_asset=base_token, address=address) + return contract + + def retrieve_unstaker_by_address(address: str) -> Optional[UnstakerContract]: contract = UnstakerContract(address) return contract diff --git a/utils/decoding_structures.py b/utils/decoding_structures.py index e59a9d3..488c4e4 100644 --- a/utils/decoding_structures.py +++ b/utils/decoding_structures.py @@ -3,4 +3,10 @@ LOCK_OPTIONS = { 'lock_epochs': 'u64', 'penalty_start_percentage': 'u64' +} + +ENERGY_ENTRY = { + 'amount': 'biguint', + 'last_update_epoch': 'u64', + 'total_locked_tokens': 'biguint', } \ No newline at end of file diff --git a/utils/utils_tx.py b/utils/utils_tx.py index e38ac68..4cbd202 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -189,7 +189,7 @@ def _prep_args_for_addresses(args: List): new_args = [] for item in args: if type(item) is str and "erd" in item: - item = Address.from_bech32(item, "erd") + item = Address.from_bech32(item) new_args.append(item) return new_args @@ -199,6 +199,7 @@ def prepare_deploy_tx(deployer: Account, network_config: NetworkConfig, args: list = None) -> Transaction: config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) args = _prep_args_for_addresses(args) + logger.debug(f"Deploy arguments: {args}") builder = ContractDeploymentBuilder( config=config, owner=deployer.address, @@ -212,8 +213,6 @@ def prepare_deploy_tx(deployer: Account, network_config: NetworkConfig, tx = builder.build() tx.signature = deployer.sign_transaction(tx) - logger.debug(f"Deploy arguments: {args}") - return tx @@ -222,6 +221,7 @@ def prepare_upgrade_tx(deployer: Account, contract_address: Address, network_con args: list = None) -> Transaction: config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) args = _prep_args_for_addresses(args) + logger.debug(f"Upgrade arguments: {args}") builder = ContractUpgradeBuilder( config=config, contract=contract_address, @@ -236,8 +236,6 @@ def prepare_upgrade_tx(deployer: Account, contract_address: Address, network_con tx = builder.build() tx.signature = deployer.sign_transaction(tx) - logger.debug(f"Upgrade arguments: {args}") - return tx @@ -247,6 +245,7 @@ def prepare_contract_call_tx(contract_address: Address, deployer: Account, config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) args = _prep_args_for_addresses(args) + logger.debug(f"Contract call arguments: {args}") builder = ContractCallBuilder( config=config, contract=contract_address, @@ -260,8 +259,6 @@ def prepare_contract_call_tx(contract_address: Address, deployer: Account, tx = builder.build() tx.signature = deployer.sign_transaction(tx) - logger.debug(f"Contract call arguments: {args}") - return tx @@ -272,6 +269,7 @@ def prepare_multiesdtnfttransfer_to_endpoint_call_tx(contract_address: Address, config = DefaultTransactionBuildersConfiguration(chain_id=network_config.chain_id) payment_tokens = [token.to_token_payment() for token in tokens] endpoint_args = _prep_args_for_addresses(endpoint_args) + logger.debug(f"Contract call arguments: {endpoint_args}") builder = ContractCallBuilder( config=config, contract=contract_address, @@ -286,8 +284,6 @@ def prepare_multiesdtnfttransfer_to_endpoint_call_tx(contract_address: Address, tx = builder.build() tx.signature = user.sign_transaction(tx) - logger.debug(f"Contract call arguments: {endpoint_args}") - return tx @@ -396,12 +392,12 @@ def endpoint_call(proxy: ProxyNetworkProvider, gas: int, user: Account, contract def deploy(contract_label: str, proxy: ProxyNetworkProvider, gas: int, - owner: Account, bytecode_path: Path, metadata: ICodeMetadata, args: list) -> (str, str): + owner: Account, bytecode_path: str, metadata: ICodeMetadata, args: list) -> (str, str): logger.debug(f"Deploy {contract_label}") network_config = proxy.get_network_config() # TODO: find solution to avoid this call tx_hash, contract_address = "", "" - tx = prepare_deploy_tx(owner, network_config, gas, bytecode_path, metadata, args) + tx = prepare_deploy_tx(owner, network_config, gas, Path(bytecode_path), metadata, args) tx_hash = send_deploy_tx(tx, proxy) if tx_hash: @@ -420,12 +416,12 @@ def deploy(contract_label: str, proxy: ProxyNetworkProvider, gas: int, def upgrade_call(contract_label: str, proxy: ProxyNetworkProvider, gas: int, - owner: Account, contract: Address, bytecode_path: Path, metadata: ICodeMetadata, + owner: Account, contract: Address, bytecode_path: str, metadata: ICodeMetadata, args: list) -> str: logger.debug(f"Upgrade {contract_label} contract") network_config = proxy.get_network_config() # TODO: find solution to avoid this call - tx = prepare_upgrade_tx(owner, contract, network_config, gas, bytecode_path, metadata, args) + tx = prepare_upgrade_tx(owner, contract, network_config, gas, Path(bytecode_path), metadata, args) tx_hash = send_contract_call_tx(tx, proxy) owner.nonce += 1 if tx_hash != "" else 0 From b29cc5d28c73bae286f302ed5b2fc5099147403c Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Mon, 3 Apr 2023 17:07:41 +0300 Subject: [PATCH 08/26] fixes and adjustments --- contracts/dex_proxy_contract.py | 6 +++--- contracts/locked_asset_contract.py | 2 +- contracts/pair_contract.py | 2 +- deploy/dex_structure.py | 2 +- utils/utils_tx.py | 2 ++ 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/contracts/dex_proxy_contract.py b/contracts/dex_proxy_contract.py index 58f2ebf..129ea69 100644 --- a/contracts/dex_proxy_contract.py +++ b/contracts/dex_proxy_contract.py @@ -211,7 +211,7 @@ def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, byteco locked_tokens_args = list(sum(zip(self.locked_tokens, args[0]), ())) arguments.extend(locked_tokens_args) - tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, args) + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address @@ -264,7 +264,7 @@ def register_proxy_farm_token(self, deployer: Account, proxy: ProxyNetworkProvid sc_args = [ args[0], args[1], - "18" + 18 ] return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "registerProxyFarm", sc_args, value=config.DEFAULT_ISSUE_TOKEN_PRICE) @@ -286,7 +286,7 @@ def register_proxy_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider sc_args = [ args[0], args[1], - "18" + 18 ] return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "registerProxyPair", sc_args, value=config.DEFAULT_ISSUE_TOKEN_PRICE) diff --git a/contracts/locked_asset_contract.py b/contracts/locked_asset_contract.py index 74561d3..1b04576 100644 --- a/contracts/locked_asset_contract.py +++ b/contracts/locked_asset_contract.py @@ -128,7 +128,7 @@ def whitelist_contract(self, deployer: Account, proxy: ProxyNetworkProvider, con gas_limit = 100000000 sc_args = [ - "0x" + Address(contract_to_whitelist).hex() + Address(contract_to_whitelist) ] return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "whitelist", sc_args) diff --git a/contracts/pair_contract.py b/contracts/pair_contract.py index 23f782f..6495b32 100644 --- a/contracts/pair_contract.py +++ b/contracts/pair_contract.py @@ -193,7 +193,7 @@ def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, byteco if self.version == PairContractVersion.V2: arguments.extend(args[5:]) - tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, args) + tx_hash, address = deploy(type(self).__name__, proxy, gas_limit, deployer, bytecode_path, metadata, arguments) return tx_hash, address def contract_upgrade(self, deployer: Account, proxy: ProxyNetworkProvider, bytecode_path, args: list): diff --git a/deploy/dex_structure.py b/deploy/dex_structure.py index d00f6d1..cbb0e94 100644 --- a/deploy/dex_structure.py +++ b/deploy/dex_structure.py @@ -226,7 +226,7 @@ def get_saved_deployed_tokens(self) -> list: log_step_pass(f"Loaded {len(retrieved_tokens)} tokens.") return retrieved_tokens elif retrieved_tokens and len(retrieved_tokens) >= self.number_of_tokens: - log_step_fail(f"Loaded {len(retrieved_tokens)} tokens instead of expected {self.number_of_tokens}.") + log_warning(f"Loaded {len(retrieved_tokens)} tokens instead of expected {self.number_of_tokens}.") return retrieved_tokens else: log_step_fail("No tokens loaded!") diff --git a/utils/utils_tx.py b/utils/utils_tx.py index 4cbd202..9a70772 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -84,6 +84,7 @@ def check_deploy_tx_status(self, tx_hash: str, address: str, msg_label: str = "" log_step_fail(f"FAIL: transaction for {msg_label} contract deployment failed " f"or couldn't retrieve address!") return False + logger.debug(f"Transaction {tx_hash} status: {status.status}") return True def check_complex_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: @@ -107,6 +108,7 @@ def check_simple_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: if msg_label: log_step_fail(f"Transaction to {msg_label} failed!") return False + logger.debug(f"Transaction {tx_hash} status: {results.status}") return True def get_tx_operations(self, tx_hash: str) -> list: From 5e8a9524c5419545453054bdc4cb3e732a6f4e35 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Mon, 3 Apr 2023 18:25:36 +0300 Subject: [PATCH 09/26] usability improvements --- README.md | 24 +++++++++++++++++++++++- config.py | 51 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index d568cbb..2dcc1b3 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,26 @@ Create a virtual environment and install the dependencies: python3 -m venv ./.venv source ./.venv/bin/activate pip install -r ./requirements.txt --upgrade -``` \ No newline at end of file +``` + +### 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 +To deploy a clean exchange setup, 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 +or add to on top of an existing one. \ No newline at end of file diff --git a/config.py b/config.py index 6d610da..496e5e2 100644 --- a/config.py +++ b/config.py @@ -1,23 +1,24 @@ from pathlib import Path HOME = Path().home() - -TOKENS_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" -ZERO_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu" DEFAULT_WORKSPACE = Path(__file__).parent -DEFAULT_ACCOUNTS = DEFAULT_WORKSPACE.absolute() / "wallets" / "C10.pem" -DEFAULT_OWNER = DEFAULT_WORKSPACE.absolute() / "wallets" / "C1.pem" -DEFAULT_ADMIN = DEFAULT_WORKSPACE.absolute() / "wallets" / "C1_1.pem" -DEFAULT_PROXY = "https://devnet-gateway.multiversx.com" -DEFAULT_API = "https://devnet-api.multiversx.com" -HISTORY_PROXY = "" -DEFAULT_ISSUE_TOKEN_PRICE = 50000000000000000 # TODO: try to override this with testnet define to tidy code up -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... +# ------------ 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 setup LOCKED_ASSET_FACTORY_BYTECODE_PATH = Path().home() / "projects" / "dex" / "dex-v2" / "dexv2-rs" / "factory.wasm" @@ -42,6 +43,20 @@ 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" @@ -64,12 +79,6 @@ METASTAKINGS_V2 = "metastakings_v2" FEES_COLLECTORS = "fees_collectors" -DEFAULT_CONFIG_SAVE_PATH = DEFAULT_WORKSPACE.absolute() / "deploy" / "configs-testruns" -DEPLOY_STRUCTURE_JSON = DEFAULT_CONFIG_SAVE_PATH / "deploy_structure.json" - -CROSS_SHARD_DELAY = 60 -INTRA_SHARD_DELAY = 10 - def get_default_contracts_file(): return DEFAULT_WORKSPACE / "contracts.json" From c905da930ee0194daa594e27f8d815b53297e0e8 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Tue, 4 Apr 2023 12:27:15 +0300 Subject: [PATCH 10/26] comment update --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 496e5e2..c3d6431 100644 --- a/config.py +++ b/config.py @@ -20,7 +20,7 @@ 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 setup +# 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" From 9ff74085bd6211ce9195f7de9f568dd84f06868d Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Tue, 4 Apr 2023 12:44:14 +0300 Subject: [PATCH 11/26] readme update --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2dcc1b3..aea0262 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Features include: - additive deployment to existing setup - Interaction with DEX SCs via exposed endpoints - Data reading from DEX SCs via exposed views -- DEX SC operation trackers +- DEX SC operation trackers - Attributes decoding tool - Setup updater tool @@ -40,9 +40,20 @@ In the config.py file you should configure the following: - DEX contract binaries paths #### Deploy -To deploy a clean exchange setup, run: +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 -or add to on top of an existing one. \ No newline at end of file +(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 +``` \ No newline at end of file From 4a13699a755bc4a729db5c8f65cf91b8857384a0 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Tue, 4 Apr 2023 14:34:06 +0300 Subject: [PATCH 12/26] fixes --- contracts/staking_contract.py | 2 +- deploy/dex_structure.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/staking_contract.py b/contracts/staking_contract.py index d215875..13268ea 100644 --- a/contracts/staking_contract.py +++ b/contracts/staking_contract.py @@ -192,7 +192,7 @@ def topup_rewards(self, deployer: Account, proxy: ProxyNetworkProvider, rewards_ gas_limit = 50000000 - tokens = [ESDTToken(self.farm_token, 0, rewards_amount)] + tokens = [ESDTToken(self.farmed_token, 0, rewards_amount)] sc_args = [tokens] return multi_esdt_endpoint_call(function_purpose, proxy, gas_limit, deployer, Address(self.address), "topUpRewards", sc_args) diff --git a/deploy/dex_structure.py b/deploy/dex_structure.py index cbb0e94..920092f 100644 --- a/deploy/dex_structure.py +++ b/deploy/dex_structure.py @@ -118,16 +118,16 @@ def __init__(self): self.simple_lock_deploy, False), config.SIMPLE_LOCKS_ENERGY: ContractStructure(config.SIMPLE_LOCKS_ENERGY, SimpleLockEnergyContract, config.SIMPLE_LOCK_ENERGY_BYTECODE_PATH, - self.simple_lock_energy_deploy, True), + self.simple_lock_energy_deploy, False), config.FEES_COLLECTORS: ContractStructure(config.FEES_COLLECTORS, FeesCollectorContract, config.FEES_COLLECTOR_BYTECODE_PATH, - self.fees_collector_deploy, True), + self.fees_collector_deploy, False), config.UNSTAKERS: ContractStructure(config.UNSTAKERS, UnstakerContract, config.UNSTAKER_BYTECODE_PATH, - self.token_unstake_deploy, True), + self.token_unstake_deploy, False), config.PROXIES_V2: ContractStructure(config.PROXIES_V2, DexProxyContract, config.PROXY_V2_BYTECODE_PATH, - self.proxy_deploy, True), + self.proxy_deploy, False), config.ROUTER: ContractStructure(config.ROUTER, RouterContract, config.ROUTER_BYTECODE_PATH, self.router_deploy, False), @@ -154,7 +154,7 @@ def __init__(self): self.proxy_deployer_deploy, False), config.FARMS_V2: ContractStructure(config.FARMS_V2, FarmContract, config.FARM_V2_BYTECODE_PATH, - self.farm_boosted_deploy, True), # self.farm_deploy_from_proxy_deployer, True), + self.farm_boosted_deploy, False), # self.farm_deploy_from_proxy_deployer, True), config.PRICE_DISCOVERIES: ContractStructure(config.PRICE_DISCOVERIES, PriceDiscoveryContract, config.PRICE_DISCOVERY_BYTECODE_PATH, self.price_discovery_deploy, False), @@ -169,7 +169,7 @@ def __init__(self): self.metastaking_deploy, False), config.METASTAKINGS_V2: ContractStructure(config.METASTAKINGS_V2, MetaStakingContract, config.STAKING_PROXY_V2_BYTECODE_PATH, - self.metastaking_deploy, True) + self.metastaking_deploy, False) } # main entry method to deploy tokens (either deploy fresh ones or reuse existing ones) From 3ec866f12d50d4d2232de84f6b7e433fb8d280e8 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Tue, 4 Apr 2023 14:38:45 +0300 Subject: [PATCH 13/26] fixes --- deploy/dex_structure.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/dex_structure.py b/deploy/dex_structure.py index 920092f..fb00de2 100644 --- a/deploy/dex_structure.py +++ b/deploy/dex_structure.py @@ -725,7 +725,7 @@ def token_unstake_deploy(self, contracts_index: str, deployer_account: Account, "whitelist energy address in fees collector"): return tx_hash = fees_collector.add_known_tokens(deployer_account, network_providers.proxy, - [f"str:{energy_factory.locked_token}"]) + [energy_factory.locked_token]) if not network_providers.check_simple_tx_status(tx_hash, "whitelist locked token in fees collector"): return @@ -890,8 +890,8 @@ def pool_deploy_from_router(self, contracts_index: str, deployer_account: Accoun _ = fees_collector.add_known_contracts(deployer_account, network_providers.proxy, [contract_address]) _ = fees_collector.add_known_tokens(deployer_account, network_providers.proxy, - [f"str:{deployed_pair_contract.firstToken}", - f"str:{deployed_pair_contract.secondToken}"]) + [deployed_pair_contract.firstToken, + deployed_pair_contract.secondToken]) deployed_contracts.append(deployed_pair_contract) self.contracts[contracts_index].deployed_contracts = deployed_contracts From c52ab67a780a559789e2d77cc9c02ba55c84f41e Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Tue, 4 Apr 2023 14:44:58 +0300 Subject: [PATCH 14/26] fixes --- contracts/pair_contract.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/pair_contract.py b/contracts/pair_contract.py index 6495b32..69ea9c6 100644 --- a/contracts/pair_contract.py +++ b/contracts/pair_contract.py @@ -181,10 +181,10 @@ def contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, byteco return "", "" arguments = [ - "0x" + self.firstToken.encode("ascii").hex(), - "0x" + self.secondToken.encode("ascii").hex(), - "0x" + Address(args[0]).hex(), - "0x" + Address(args[1]).hex(), + self.firstToken, + self.secondToken, + Address(args[0]), + Address(args[1]), args[3], args[4], args[2] From 05fd07401517da3f6ee9e1cf576b57805909c8f5 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Tue, 4 Apr 2023 15:22:17 +0300 Subject: [PATCH 15/26] corrected endpoint name --- contracts/router_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/router_contract.py b/contracts/router_contract.py index 8df72b7..dd8b919 100644 --- a/contracts/router_contract.py +++ b/contracts/router_contract.py @@ -102,7 +102,7 @@ def pair_contract_deploy(self, deployer: Account, proxy: ProxyNetworkProvider, a if self.version == RouterContractVersion.V2: sc_args.extend(args[5:]) - tx_hash = endpoint_call(proxy, gas_limit, deployer, Address(self.address), "deployPair", sc_args) + tx_hash = endpoint_call(proxy, gas_limit, deployer, Address(self.address), "createPair", sc_args) # retrieve deployed contract address if tx_hash != "": From b43f8465268028b65d525691edb93e10cd4a7d89 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Tue, 4 Apr 2023 17:26:19 +0300 Subject: [PATCH 16/26] various fixes --- contracts/farm_contract.py | 12 +++++++----- contracts/router_contract.py | 6 +++--- deploy/dex_structure.py | 2 +- utils/utils_tx.py | 20 +++++++++++--------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/contracts/farm_contract.py b/contracts/farm_contract.py index a71a41c..7ce5627 100644 --- a/contracts/farm_contract.py +++ b/contracts/farm_contract.py @@ -1,6 +1,7 @@ import sys import traceback +import config from contracts.contract_identities import FarmContractVersion, DEXContractInterface from utils.logger import get_logger from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, NetworkProviders, ESDTToken, \ @@ -232,7 +233,8 @@ def register_farm_token(self, deployer: Account, proxy: ProxyNetworkProvider, ar logger.debug(f"Arguments: {sc_args}") - return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "registerFarmToken", sc_args) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "registerFarmToken", sc_args, + config.DEFAULT_ISSUE_TOKEN_PRICE) def set_local_roles_farm_token(self, deployer: Account, proxy: ProxyNetworkProvider): function_purpose = "Set local roles for farm token" @@ -251,7 +253,7 @@ def set_rewards_per_block(self, deployer: Account, proxy: ProxyNetworkProvider, rewards_amount ] logger.debug(f"Arguments: {sc_args}") - return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setRewardsPerBlock", sc_args) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setPerBlockRewardAmount", sc_args) def set_penalty_percent(self, deployer: Account, proxy: ProxyNetworkProvider, percent: int): function_purpose = "Set penalty percent in farm" @@ -262,7 +264,7 @@ def set_penalty_percent(self, deployer: Account, proxy: ProxyNetworkProvider, pe percent ] logger.debug(f"Arguments: {sc_args}") - return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setPenaltyPercent", sc_args) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "set_penalty_percent", sc_args) def set_minimum_farming_epochs(self, deployer: Account, proxy: ProxyNetworkProvider, epochs: int): function_purpose = "Set minimum farming epochs in farm" @@ -273,7 +275,7 @@ def set_minimum_farming_epochs(self, deployer: Account, proxy: ProxyNetworkProvi epochs ] logger.debug(f"Arguments: {sc_args}") - return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setMinimumFarmingEpochs", sc_args) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "set_minimum_farming_epochs", sc_args) def set_boosted_yields_factors(self, deployer: Account, proxy: ProxyNetworkProvider, args: list): """Only V2Boosted. @@ -328,7 +330,7 @@ def set_locking_address(self, deployer: Account, proxy: ProxyNetworkProvider, lo gas_limit = 70000000 sc_args = [locking_address] logger.debug(f"Arguments: {sc_args}") - return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLockingAddress", sc_args) + return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setLockingScAddress", sc_args) def set_lock_epochs(self, deployer: Account, proxy: ProxyNetworkProvider, lock_epochs: int): """Only V2Boosted. diff --git a/contracts/router_contract.py b/contracts/router_contract.py index dd8b919..65cdf8e 100644 --- a/contracts/router_contract.py +++ b/contracts/router_contract.py @@ -156,7 +156,7 @@ def issue_lp_token(self, deployer: Account, proxy: ProxyNetworkProvider, args: l gas_limit = 100000000 sc_args = [ - Address(args[0]).hex(), + Address(args[0]), args[1], args[2] ] @@ -188,8 +188,8 @@ def set_fee_on(self, deployer: Account, proxy: ProxyNetworkProvider, args: list) gas_limit = 100000000 sc_args = [ - Address(args[0]).hex(), - Address(args[1]).hex(), + Address(args[0]), + Address(args[1]), args[2] ] return endpoint_call(proxy, gas_limit, deployer, Address(self.address), "setFeeOn", sc_args) diff --git a/deploy/dex_structure.py b/deploy/dex_structure.py index fb00de2..33a0e05 100644 --- a/deploy/dex_structure.py +++ b/deploy/dex_structure.py @@ -819,7 +819,7 @@ def pool_deploy_from_router(self, contracts_index: str, deployer_account: Accoun tx_hash = deployed_pair_contract.set_lp_token_local_roles_via_router(deployer_account, network_providers.proxy, router_contract) - if not network_providers.check_simple_tx_status(tx_hash, "set lp token local roles via router"): return + if not network_providers.check_complex_tx_status(tx_hash, "set lp token local roles via router"): return # Set proxy if applicable if "proxy" in config_pool or "proxy_v2" in config_pool: diff --git a/utils/utils_tx.py b/utils/utils_tx.py index 9a70772..c92629e 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -64,7 +64,7 @@ def __init__(self, api: str, proxy: str): self.network = self.proxy.get_network_config() def wait_for_tx_executed(self, tx_hash: str): - time.sleep(2) # temporary fix for the api returning the wrong status + time.sleep(3) # temporary fix for the api returning the wrong status while True: status = self.api.get_transaction_status(tx_hash) logger.debug(f"Transaction {tx_hash} status: {status.status}") @@ -103,6 +103,7 @@ def check_simple_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: log_step_fail(f"FAIL: no tx hash for {msg_label} transaction!") return False + time.sleep(3) # temporary fix for the api returning wrong statuses results = self.api.get_transaction_status(tx_hash) if results.is_failed(): if msg_label: @@ -403,14 +404,6 @@ def deploy(contract_label: str, proxy: ProxyNetworkProvider, gas: int, tx_hash = send_deploy_tx(tx, proxy) if tx_hash: - time.sleep(2) - while not proxy.get_transaction_status(tx_hash).is_executed(): - time.sleep(2) - - if not proxy.get_transaction_status(tx_hash).is_successful(): - log_step_fail(f"Transaction to deploy {contract_label} contract failed.") - return tx_hash, contract_address - contract_address = get_deployed_address_from_tx(tx_hash, proxy) owner.nonce += 1 @@ -442,8 +435,17 @@ def get_deployed_address_from_event(tx_result: TransactionOnNetwork) -> str: def get_deployed_address_from_tx(tx_hash: str, proxy: ProxyNetworkProvider) -> str: try: + time.sleep(3) + while not proxy.get_transaction_status(tx_hash).is_executed(): + time.sleep(6) + + if not proxy.get_transaction_status(tx_hash).is_successful(): + logger.debug(f"Deploy transaction {tx_hash} failed.") + return "" + tx = proxy.get_transaction(tx_hash) contract_address = get_deployed_address_from_event(tx) + logger.debug(f"Deployed contract address: {contract_address}") except Exception as ex: logger.exception(f"Failed to get contract address due to: {ex}") contract_address = "" From a7af7124238d91260546a724c9056a24462b732b Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Tue, 4 Apr 2023 18:06:16 +0300 Subject: [PATCH 17/26] delay tweak --- utils/utils_tx.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/utils/utils_tx.py b/utils/utils_tx.py index c92629e..af0c5dd 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -20,6 +20,8 @@ TX_CACHE: Dict[str, dict] = {} logger = get_logger(__name__) +API_TX_DELAY = 4 + class ESDTToken: token_id: str @@ -64,7 +66,7 @@ def __init__(self, api: str, proxy: str): self.network = self.proxy.get_network_config() def wait_for_tx_executed(self, tx_hash: str): - time.sleep(3) # temporary fix for the api returning the wrong status + time.sleep(API_TX_DELAY) # temporary fix for the api returning the wrong status while True: status = self.api.get_transaction_status(tx_hash) logger.debug(f"Transaction {tx_hash} status: {status.status}") @@ -103,7 +105,7 @@ def check_simple_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: log_step_fail(f"FAIL: no tx hash for {msg_label} transaction!") return False - time.sleep(3) # temporary fix for the api returning wrong statuses + time.sleep(API_TX_DELAY) # temporary fix for the api returning wrong statuses results = self.api.get_transaction_status(tx_hash) if results.is_failed(): if msg_label: @@ -435,7 +437,7 @@ def get_deployed_address_from_event(tx_result: TransactionOnNetwork) -> str: def get_deployed_address_from_tx(tx_hash: str, proxy: ProxyNetworkProvider) -> str: try: - time.sleep(3) + time.sleep(API_TX_DELAY) while not proxy.get_transaction_status(tx_hash).is_executed(): time.sleep(6) From 7142068cf5728c1e2beb1cf06b53d8399333e4f5 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Wed, 5 Apr 2023 18:17:32 +0300 Subject: [PATCH 18/26] attempt to work around API status issue --- contracts/proxy_deployer_contract.py | 4 +- deploy/issue_tokens.py | 5 ++- deploy/sync_tokens.py | 5 ++- utils/utils_tx.py | 62 ++++++++++++++++++++-------- 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/contracts/proxy_deployer_contract.py b/contracts/proxy_deployer_contract.py index 495a889..b5046e7 100644 --- a/contracts/proxy_deployer_contract.py +++ b/contracts/proxy_deployer_contract.py @@ -3,9 +3,7 @@ from contracts.contract_identities import DEXContractInterface from utils.logger import get_logger -from utils.utils_tx import prepare_contract_call_tx, send_contract_call_tx, get_deployed_address_from_event, deploy, \ - endpoint_call, get_deployed_address_from_tx -from utils.utils_chain import log_explorer_transaction +from utils.utils_tx import deploy, endpoint_call, get_deployed_address_from_tx from utils.utils_generic import log_step_fail, log_step_pass, log_warning, log_unexpected_args from utils.utils_chain import Account, WrapperAddress as Address from multiversx_sdk_core import CodeMetadata diff --git a/deploy/issue_tokens.py b/deploy/issue_tokens.py index 6c028f9..ef5caf8 100644 --- a/deploy/issue_tokens.py +++ b/deploy/issue_tokens.py @@ -1,9 +1,9 @@ -import logging import sys from argparse import ArgumentParser from typing import List import config +from utils.logger import get_logger from utils.utils_chain import (Account, build_token_name, build_token_ticker) from multiversx_sdk_network_providers import ProxyNetworkProvider from multiversx_sdk_core import Address, TokenPayment, Transaction @@ -11,9 +11,10 @@ from utils.utils_tx import broadcast_transactions +logger = get_logger(__name__) + def main(cli_args: List[str]): - logging.basicConfig(level=logging.ERROR) parser = ArgumentParser() parser.add_argument("--proxy", default=config.DEFAULT_PROXY) diff --git a/deploy/sync_tokens.py b/deploy/sync_tokens.py index 13897d0..20732d4 100644 --- a/deploy/sync_tokens.py +++ b/deploy/sync_tokens.py @@ -1,4 +1,3 @@ -import logging import sys from argparse import ArgumentParser from multiprocessing.dummy import Pool @@ -6,14 +5,16 @@ import config from deploy.tokens_tracks import BunchOfTracks +from utils.logger import get_logger from utils.utils_chain import BunchOfAccounts from utils.utils_chain import WrapperAddress as Address from multiversx_sdk_network_providers.errors import GenericError from multiversx_sdk_network_providers import ProxyNetworkProvider +logger = get_logger(__name__) + def main(cli_args: List[str]): - logging.basicConfig(level=logging.ERROR) parser = ArgumentParser() parser.add_argument("--proxy", default=config.DEFAULT_PROXY) diff --git a/utils/utils_tx.py b/utils/utils_tx.py index af0c5dd..79e959f 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -2,13 +2,14 @@ import time import traceback from pathlib import Path -from typing import List, Dict, Any +from typing import List, Dict, Any, Union from multiversx_sdk_core import Transaction, TokenPayment, Address from multiversx_sdk_core.interfaces import ICodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider, ApiNetworkProvider from multiversx_sdk_network_providers.network_config import NetworkConfig from multiversx_sdk_network_providers.tokens import FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork +from multiversx_sdk_network_providers.transaction_events import TransactionEvent from multiversx_sdk_network_providers.transactions import TransactionOnNetwork from multiversx_sdk_core.transaction_builders import ContractCallBuilder, DefaultTransactionBuildersConfiguration, \ MultiESDTNFTTransferBuilder, ContractDeploymentBuilder, ContractUpgradeBuilder @@ -21,6 +22,7 @@ logger = get_logger(__name__) API_TX_DELAY = 4 +MAX_TX_FETCH_RETRIES = 50 // API_TX_DELAY class ESDTToken: @@ -425,34 +427,60 @@ def upgrade_call(contract_label: str, proxy: ProxyNetworkProvider, gas: int, return tx_hash -def get_deployed_address_from_event(tx_result: TransactionOnNetwork) -> str: - searched_event_id = "SCDeploy" - deploy_event = tx_result.logs.find_first_or_none_event(searched_event_id) - if deploy_event is None: - return "" - - address = deploy_event.address.bech32() - return address +def check_error_from_event(tx_result: TransactionOnNetwork) -> bool: + searched_event_id = ["signalError", "internalVMErrors"] + for event_id in searched_event_id: + error_event = tx_result.logs.find_first_or_none_event(event_id) + if error_event is not None: + return True + return False -def get_deployed_address_from_tx(tx_hash: str, proxy: ProxyNetworkProvider) -> str: +def get_event_from_tx(event_id: str, tx_hash: str, proxy: ProxyNetworkProvider) -> Union[TransactionEvent, None]: try: time.sleep(API_TX_DELAY) while not proxy.get_transaction_status(tx_hash).is_executed(): time.sleep(6) if not proxy.get_transaction_status(tx_hash).is_successful(): - logger.debug(f"Deploy transaction {tx_hash} failed.") - return "" + logger.debug(f"Transaction {tx_hash} failed.") + return None tx = proxy.get_transaction(tx_hash) - contract_address = get_deployed_address_from_event(tx) - logger.debug(f"Deployed contract address: {contract_address}") + event = tx.logs.find_first_or_none_event(event_id) + + # if event is still not available, but the transaction was successful, try fetching again until either event is + # available or transaction is failed + # timeout after MAX_TX_FETCH_RETRIES + attempt = 0 + while event is None and attempt < MAX_TX_FETCH_RETRIES: + attempt += 1 + if check_error_from_event(tx): + logger.debug(f"Transaction {tx_hash} failed.") + return None + time.sleep(API_TX_DELAY) + tx = proxy.get_transaction(tx_hash) + event = tx.logs.find_first_or_none_event(event_id) + except Exception as ex: - logger.exception(f"Failed to get contract address due to: {ex}") - contract_address = "" + logger.exception(f"Failed to get event due to: {ex}") + event = None + + return event + - return contract_address +def get_deployed_address_from_tx(tx_hash: str, proxy: ProxyNetworkProvider) -> str: + event = get_event_from_tx("SCDeploy", tx_hash, proxy) + if event is None: + return "" + return event.address.bech32() + + +def get_issued_token_from_tx(tx_hash: str, proxy: ProxyNetworkProvider) -> str: + event = get_event_from_tx("registerAndSetAllRoles", tx_hash, proxy) + if event is None: + return "" + return str(event.topics[0]) def broadcast_transactions(transactions: List[Transaction], proxy: ProxyNetworkProvider, From 6b79b5327eaa45e22e3d552b3f37624a8f86eb52 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Thu, 6 Apr 2023 14:39:23 +0300 Subject: [PATCH 19/26] quality of life improvements related to API work and cleanliness --- .gitignore | 6 +++++- config.py | 8 ++++---- deploy/issue_tokens.py | 1 - tools/config_contracts_upgrader.py | 4 ---- utils/logger.py | 3 ++- utils/utils_tx.py | 20 +++++++++++++++++--- 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index b3eef99..7a8900c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,8 @@ /.idea /.vscode /logs/* -/tools/notebooks/env.py \ No newline at end of file +/tools/notebooks/env.py +.venv +__pycache__ +/deploy/*/tokens.json +/deploy/*/deployed_*.json \ No newline at end of file diff --git a/config.py b/config.py index c3d6431..9f93183 100644 --- a/config.py +++ b/config.py @@ -80,9 +80,9 @@ FEES_COLLECTORS = "fees_collectors" -def get_default_contracts_file(): - return DEFAULT_WORKSPACE / "contracts.json" +def get_default_tokens_file(): + return DEFAULT_CONFIG_SAVE_PATH / "tokens.json" -def get_default_tokens_file(): - return DEFAULT_WORKSPACE / "tokens.json" +def get_default_log_file(): + return DEFAULT_WORKSPACE / "logs" / "trace.log" diff --git a/deploy/issue_tokens.py b/deploy/issue_tokens.py index ef5caf8..b271b8d 100644 --- a/deploy/issue_tokens.py +++ b/deploy/issue_tokens.py @@ -19,7 +19,6 @@ def main(cli_args: List[str]): parser = ArgumentParser() parser.add_argument("--proxy", default=config.DEFAULT_PROXY) parser.add_argument("--account", default=config.DEFAULT_OWNER) - parser.add_argument("--contracts", default=config.get_default_contracts_file()) parser.add_argument("--sleep-between-chunks", type=int, default=5) parser.add_argument("--chunk-size", type=int, default=400) parser.add_argument("--from-shard") diff --git a/tools/config_contracts_upgrader.py b/tools/config_contracts_upgrader.py index 0c5760c..9592db0 100644 --- a/tools/config_contracts_upgrader.py +++ b/tools/config_contracts_upgrader.py @@ -71,10 +71,6 @@ INTRA_SHARD_DELAY = 10 -def get_default_contracts_file(): - return DEFAULT_WORKSPACE / "contracts.json" - - def get_default_tokens_file(): return DEFAULT_WORKSPACE / "tokens.json" diff --git a/utils/logger.py b/utils/logger.py index 86362b6..94e29fe 100644 --- a/utils/logger.py +++ b/utils/logger.py @@ -1,4 +1,5 @@ import logging +import config class ScreenFormatter(logging.Formatter): @@ -34,7 +35,7 @@ def get_logger(name: str) -> logging.Logger: console_handler.setFormatter(ScreenFormatter()) # file handler - file_handler = logging.FileHandler(f"trace.log") + file_handler = logging.FileHandler(config.get_default_log_file()) file_handler.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s : %(message)s") file_handler.setFormatter(formatter) diff --git a/utils/utils_tx.py b/utils/utils_tx.py index 79e959f..859d4f1 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -6,7 +6,7 @@ from multiversx_sdk_core import Transaction, TokenPayment, Address from multiversx_sdk_core.interfaces import ICodeMetadata -from multiversx_sdk_network_providers import ProxyNetworkProvider, ApiNetworkProvider +from multiversx_sdk_network_providers import ProxyNetworkProvider, ApiNetworkProvider, GenericError from multiversx_sdk_network_providers.network_config import NetworkConfig from multiversx_sdk_network_providers.tokens import FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork from multiversx_sdk_network_providers.transaction_events import TransactionEvent @@ -21,7 +21,8 @@ TX_CACHE: Dict[str, dict] = {} logger = get_logger(__name__) -API_TX_DELAY = 4 +API_TX_DELAY = 2 +API_LONG_TX_DELAY = 6 MAX_TX_FETCH_RETRIES = 50 // API_TX_DELAY @@ -98,6 +99,7 @@ def check_complex_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: return False logger.debug(f"Waiting for transaction {tx_hash} to be executed...") + time.sleep(API_LONG_TX_DELAY - API_TX_DELAY) # temporary fix for the api returning the wrong status self.wait_for_tx_executed(tx_hash) return self.check_simple_tx_status(tx_hash, msg_label) @@ -107,8 +109,20 @@ def check_simple_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: log_step_fail(f"FAIL: no tx hash for {msg_label} transaction!") return False + results = None time.sleep(API_TX_DELAY) # temporary fix for the api returning wrong statuses - results = self.api.get_transaction_status(tx_hash) + try: + results = self.api.get_transaction_status(tx_hash) + except GenericError as e: + if '404' in e.data: + # api didn't index the transaction yet, try again after a delay + logger.debug(f"Transaction {tx_hash} not indexed yet, trying again in {API_TX_DELAY} seconds...") + time.sleep(API_TX_DELAY) + results = self.api.get_transaction_status(tx_hash) + + if results is None: + log_step_fail(f"FAIL: couldn't retrieve transaction {tx_hash} status!") + if results.is_failed(): if msg_label: log_step_fail(f"Transaction to {msg_label} failed!") From 7210b898d81828ebb54af5ec11156af51a993f9d Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Thu, 6 Apr 2023 15:01:12 +0300 Subject: [PATCH 20/26] logger create path if not existing --- utils/logger.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/logger.py b/utils/logger.py index 94e29fe..6b8e3c5 100644 --- a/utils/logger.py +++ b/utils/logger.py @@ -35,6 +35,9 @@ def get_logger(name: str) -> logging.Logger: console_handler.setFormatter(ScreenFormatter()) # file handler + logs_path = config.get_default_log_file() + if not logs_path.exists(): + logs_path.parent.mkdir(parents=True, exist_ok=True) file_handler = logging.FileHandler(config.get_default_log_file()) file_handler.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s : %(message)s") From 6ef9c28952ed2473a8b51dbc7c8b40dbda4326ca Mon Sep 17 00:00:00 2001 From: Lucas Willems Date: Thu, 6 Apr 2023 15:20:40 +0200 Subject: [PATCH 21/26] Attempt to remove erdpy and arrows deps --- .../AutomaticTests/ElasticIndexer.py | 83 ++++++++++++++ .../AutomaticTests/ProxyExtension.py | 107 ++++++++++++++++++ ported_arrows/AutomaticTests/__init__.py | 0 ported_arrows/__init__.py | 0 ported_arrows/stress/__init__.py | 0 .../stress/contracts/transaction_builder.py | 88 ++++++++++++++ ported_arrows/stress/send_egld_from_minter.py | 56 +++++++++ .../stress/send_token_from_minter.py | 56 +++++++++ ported_arrows/stress/shared.py | 35 ++++++ requirements.txt | 3 +- scenarios/pair_admin_owner_check.py | 6 +- scenarios/pair_v2_check_fee.py | 6 +- scenarios/scenario_dex_v2_all_in.py | 8 +- scenarios/scenario_fees_collector.py | 8 +- scenarios/scenario_simple_lock_energy.py | 7 +- scenarios/scenario_swaps.py | 8 +- scenarios/stress_claim_metastaking.py | 14 +-- scenarios/stress_create_positions.py | 10 +- scenarios/stress_many_add_remove_liquidity.py | 15 +-- scenarios/stress_many_swaps.py | 15 +-- scenarios/stress_scenario.py | 2 +- tools/account_state.py | 9 +- tools/contracts_upgrader.py | 20 ++-- tools/manual_issue_token.py | 14 +-- tools/safeprice_monitor.py | 6 +- trackers/concrete_observer.py | 4 +- trackers/farm_economics_tracking.py | 2 +- trackers/metastaking_economics_tracking.py | 2 +- trackers/pair_economics_tracking.py | 2 +- .../price_discovery_economics_tracking.py | 6 +- trackers/simple_lock_energy_tracking.py | 2 +- trackers/staking_economics_tracking.py | 2 +- utils/utils_chain.py | 5 +- utils/utils_tx.py | 4 +- 34 files changed, 515 insertions(+), 90 deletions(-) create mode 100644 ported_arrows/AutomaticTests/ElasticIndexer.py create mode 100644 ported_arrows/AutomaticTests/ProxyExtension.py create mode 100644 ported_arrows/AutomaticTests/__init__.py create mode 100644 ported_arrows/__init__.py create mode 100644 ported_arrows/stress/__init__.py create mode 100644 ported_arrows/stress/contracts/transaction_builder.py create mode 100644 ported_arrows/stress/send_egld_from_minter.py create mode 100644 ported_arrows/stress/send_token_from_minter.py create mode 100644 ported_arrows/stress/shared.py diff --git a/ported_arrows/AutomaticTests/ElasticIndexer.py b/ported_arrows/AutomaticTests/ElasticIndexer.py new file mode 100644 index 0000000..0b6dab9 --- /dev/null +++ b/ported_arrows/AutomaticTests/ElasticIndexer.py @@ -0,0 +1,83 @@ +import time + +import requests + + +class ElasticIndexer: + url: str + + def __init__(self, url: str): + self.url = url + + def fetch_tx_status(self, hash_str: str): + url = f"{self.url}/transactions/{hash_str}" + headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'} + request_error_codes = [404, 408, 429, 500, 504] + try: + response = requests.get(url, headers=headers) + response.raise_for_status() + parsed = response.json() + return parsed + except Exception as ex: + if response.status_code in request_error_codes: + time.sleep(1) + response = requests.get(url, headers=headers) + response.raise_for_status() + return response.json() + else: + print(f'Exception occurred: {ex}') + + def is_tx_finalized(self, hash_str: str) -> bool: + response = self.fetch_tx_status(hash_str) + if ('status' in response and response['status'] == 'success' and 'pendingResults' not in response) \ + or ('status' in response and response['status'] == 'fail'): + return True + return False + + def wait_for_tx_finalized(self, hash_str: str): + num_seconds_timeout = 60 + time.sleep(2) + for _ in range(0, num_seconds_timeout): + if self.is_tx_finalized(hash_str): + return self.fetch_tx_status(hash_str) + time.sleep(2) + + def get_no_transactions_per_account(self, address: str): + url = f"{self.url}/accounts/{address}/transactions/count" + headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'} + response = requests.get(url, headers=headers) + response.raise_for_status() + parsed = response.json() + return parsed + + def get_transactions_per_account(self, address: str): + url = f"{self.url}/accounts/{address}/transactions" + headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'} + response = requests.get(url, headers=headers) + response.raise_for_status() + parsed = response.json() + return parsed + + def get_address_details(self, address: str): + url = f"{self.url}/accounts/{address}" + headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'} + response = requests.get(url, headers=headers) + response.raise_for_status() + parsed = response.json() + return parsed + + def get_esdt_data(self, token_id: str): + url = f"{self.url}/tokens/{token_id}" + headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'} + response = requests.get(url, headers=headers) + response.raise_for_status() + parsed = response.json() + return parsed + + def get_nft_data(self, token_id: str): + url = f"{self.url}/nfts/{token_id}" + headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'} + response = requests.get(url, headers=headers) + response.raise_for_status() + parsed = response.json() + return parsed diff --git a/ported_arrows/AutomaticTests/ProxyExtension.py b/ported_arrows/AutomaticTests/ProxyExtension.py new file mode 100644 index 0000000..e81e6f3 --- /dev/null +++ b/ported_arrows/AutomaticTests/ProxyExtension.py @@ -0,0 +1,107 @@ +from typing import List, Dict, Any + +import time +import requests + +from multiversx_sdk_cli import errors +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider +from multiversx_sdk_core.constants import METACHAIN_ID + +class NetworkStatusOnShard: + def __init__(self, data: Dict[str, Any]) -> None: + self.current_round = data.get("erd_current_round", 0) + self.current_epoch = data.get("erd_epoch_number", 0) + self.current_nonce = data.get("erd_nonce", 0) + + +class ProxyExtension(ProxyNetworkProvider): + def do_query_with_caller(self, sc_address: str, caller_adress: str, function_name: str) -> List[str]: + payload = { + "scAddress": sc_address, + "caller": caller_adress, + "funcName": function_name + } + + result = self.do_post('/vm-values/query', payload) + return_data = result['data'] + value = return_data['returnData'] + return value + + def do_post(self, endpoint, payload): + url = self.url + endpoint + try: + response = requests.post(url, json=payload) + response.raise_for_status() + parsed = response.json() + return self.get_data(parsed, url) + except requests.HTTPError as err: + error_data = self._extract_error_from_response(err.response) + raise Exception(url, error_data) + except requests.ConnectionError as err: + raise Exception(url, err) + except Exception as err: + raise Exception(url, err) + + def get_network_status(self, shard_id) -> NetworkStatusOnShard: + if shard_id == "metachain": + metrics = self._get_network_status(METACHAIN_ID) + else: + metrics = self._get_network_status(shard_id) + result = NetworkStatusOnShard(metrics) + return result + + def wait_for_epoch(self, target_epoch, idle_time=30): + status = self.get_network_status(0) + while status.current_epoch < target_epoch: + status = self.get_network_status(0) + time.sleep(idle_time) + + def wait_for_nonce_in_shard(self, shard_id: int, target_nonce: int, idle_time=6): + status = self.get_network_status(shard_id) + while status.current_nonce < target_nonce: + status = self.get_network_status(shard_id) + time.sleep(idle_time) + + def wait_epochs(self, num_epochs, idle_time=30): + status = self.get_network_status(0) + next_epoch = status.current_epoch + num_epochs + while status.current_epoch < next_epoch: + status = self.get_network_status(0) + time.sleep(idle_time) + + def get_round(self): + status = self.get_network_status(0) + return status.current_round + + def get_heartbeats_info(self) -> List[dict]: + heartbeat_info = [] + proxy = ProxyNetworkProvider(self.url) + node_heartbeat_list = proxy.do_get_generic("node/heartbeatstatus") + heartbeats = node_heartbeat_list['heartbeats'] + for node_heartbeat in heartbeats: + heartbeat_info.append(node_heartbeat) + return heartbeat_info + + def get_validators_statistics_raw(self) -> List[dict]: + proxy = ProxyNetworkProvider(self.url) + response = proxy.do_get_generic("validator/statistics") + statistics = response['statistics'] + return statistics + + + @staticmethod + def get_data(parsed, url): + err = parsed.get("error") + code = parsed.get("code") + + if not err and code == "successful": + return parsed.get("data", dict()) + + raise errors.ProxyRequestError(url, f"code:{code}, error: {err}") + + @staticmethod + def _extract_error_from_response(response): + try: + return response.json() + except Exception: + return response.text diff --git a/ported_arrows/AutomaticTests/__init__.py b/ported_arrows/AutomaticTests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ported_arrows/__init__.py b/ported_arrows/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ported_arrows/stress/__init__.py b/ported_arrows/stress/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ported_arrows/stress/contracts/transaction_builder.py b/ported_arrows/stress/contracts/transaction_builder.py new file mode 100644 index 0000000..a8715a2 --- /dev/null +++ b/ported_arrows/stress/contracts/transaction_builder.py @@ -0,0 +1,88 @@ + +from typing import Any, List, Tuple + +from utils.utils_chain import Account, Address +from utils.utils_tx import Transaction +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider + + +class ArgLengths: + def __init__(self, lengths: List[int]): + self.lengths = lengths + + def as_bytes(self): + result = bytearray() + + for length in self.lengths: + result += length.to_bytes(4, byteorder="little") + + return bytes(result) + + def as_hex(self): + result = self.as_bytes().hex() + return result + + +class RawHex: + def __init__(self, raw: str): + self.raw = raw + + def as_hex(self): + return self.raw + + +def transfer_multi_esdt_and_execute(contract_address: Address, caller: Account, transfers: List[Tuple[str, int]], function: str, args: List[Any], execute_gas_limit: int, network_config: ProxyNetworkProvider): + tx_data_transfer = multi_esdt_transfer_data(contract_address, transfers) + tx_data_execute = string_as_arg(function) + + for arg in args: + tx_data_execute += f"@{any_as_arg(arg)}" + + tx = Transaction() + tx.nonce = caller.nonce + tx.sender = caller.address.bech32() + tx.data = f"{tx_data_transfer}@{tx_data_execute}" + tx.receiver = caller.address.bech32() + tx.gasPrice = network_config.min_gas_price + tx.gasLimit = 250000 * len(transfers) + 1000000 + 50000 + 1500 * len(tx.data) + execute_gas_limit + tx.chainID = network_config.chain_id + tx.version = network_config.min_tx_version + tx.sign(caller) + + return tx + + +def multi_esdt_transfer_data(receiver: Address, transfers: List[Tuple[str, int]]): + tx_data = f"MultiESDTNFTTransfer@{receiver.hex()}@{number_as_arg(len(transfers))}" + for token_id, value in transfers: + tx_data += f"@{token_id_as_arg(token_id)}@00@{number_as_arg(value)}" + + return tx_data + + +def any_as_arg(value: Any): + if isinstance(value, str): + return string_as_arg(value) + if isinstance(value, int): + return number_as_arg(value) + if isinstance(value, Address): + return value.hex() + if isinstance(value, ArgLengths): + return value.as_hex() + if isinstance(value, RawHex): + return value.as_hex() + raise Exception(f"Unknown type: value = {value}.") + + +def token_id_as_arg(value: str): + return string_as_arg(value) + + +def string_as_arg(value: str): + return value.encode('ascii').hex() + + +def number_as_arg(value: int): + result = format(value, "x") + result = "0" + result if len(result) % 2 else result + return result diff --git a/ported_arrows/stress/send_egld_from_minter.py b/ported_arrows/stress/send_egld_from_minter.py new file mode 100644 index 0000000..65c7d55 --- /dev/null +++ b/ported_arrows/stress/send_egld_from_minter.py @@ -0,0 +1,56 @@ +import logging +import sys +from argparse import ArgumentParser +from pathlib import Path +from typing import List + +from utils.utils_chain import BunchOfAccounts +from utils.utils_tx import broadcast_transactions +from multiversx_sdk_cli.accounts import Account +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider +from multiversx_sdk_cli.transactions import Transaction + + +def main(cli_args: List[str]): + logging.basicConfig(level=logging.ERROR) + + parser = ArgumentParser() + parser.add_argument("--proxy", required=True) + parser.add_argument("--accounts", required=True) + parser.add_argument("--minter", required=True) + parser.add_argument("--value-atoms", type=int, required=True) + args = parser.parse_args(cli_args) + + proxy = ProxyNetworkProvider(args.proxy) + network = proxy.get_network_config() + accounts = BunchOfAccounts.load_accounts_from_files([Path(args.accounts)]) + minter = Account(pem_file=Path(args.minter)) + minter.sync_nonce(proxy) + + print("Minter", minter.address, "nonce", minter.nonce) + + transactions: List[Transaction] = [] + + for account in accounts.get_all(): + if account.address.bech32() == minter.address.bech32(): + continue + + transaction = Transaction() + transaction.nonce = minter.nonce + transaction.sender = minter.address.bech32() + transaction.receiver = account.address.bech32() + transaction.value = str(args.value_atoms) + transaction.gasPrice = network.min_gas_price + transaction.gasLimit = 50000 + transaction.chainID = network.chain_id + transaction.version = network.min_transaction_version + transaction.sign(minter) + minter.nonce += 1 + + transactions.append(transaction) + + broadcast_transactions(transactions, proxy, 10, confirm_yes=True) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/ported_arrows/stress/send_token_from_minter.py b/ported_arrows/stress/send_token_from_minter.py new file mode 100644 index 0000000..45bb1e6 --- /dev/null +++ b/ported_arrows/stress/send_token_from_minter.py @@ -0,0 +1,56 @@ +import logging +import sys +from argparse import ArgumentParser +from pathlib import Path +from typing import List + +from ported_arrows.stress.contracts.transaction_builder import (number_as_arg, + token_id_as_arg) +from utils.utils_tx import broadcast_transactions +from utils.utils_chain import BunchOfAccounts +from multiversx_sdk_cli.accounts import Account +from multiversx_sdk_network_providers import ProxyNetworkProvider +from multiversx_sdk_cli.transactions import Transaction + + +def main(cli_args: List[str]): + logging.basicConfig(level=logging.ERROR) + + parser = ArgumentParser() + parser.add_argument("--proxy", required=True) + parser.add_argument("--accounts", required=True) + parser.add_argument("--minter", required=True) + parser.add_argument("--token", required=True) + parser.add_argument("--amount-atoms", type=int, required=True) + args = parser.parse_args(cli_args) + + proxy = ProxyNetworkProvider(args.proxy) + network = proxy.get_network_config() + accounts = BunchOfAccounts.load_accounts_from_files([Path(args.accounts)]) + minter = Account(pem_file=Path(args.minter)) + minter.sync_nonce(proxy) + + transactions: List[Transaction] = [] + + for account in accounts.get_all(): + + transaction = Transaction() + transaction.nonce = minter.nonce + transaction.sender = minter.address.bech32() + transaction.receiver = account.address.bech32() + transaction.data = f"ESDTTransfer@{token_id_as_arg(args.token)}@{number_as_arg(args.amount_atoms)}" + transaction.value = "0" + transaction.gasPrice = network.min_gas_price + transaction.gasLimit = 250000 + 50000 + 1500 * len(transaction.data) + transaction.chainID = network.chain_id + transaction.version = network.min_transaction_version + transaction.sign(minter) + minter.nonce += 1 + + transactions.append(transaction) + + broadcast_transactions(transactions, proxy, 10, confirm_yes=True) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/ported_arrows/stress/shared.py b/ported_arrows/stress/shared.py new file mode 100644 index 0000000..8c2e166 --- /dev/null +++ b/ported_arrows/stress/shared.py @@ -0,0 +1,35 @@ +from multiversx_sdk_cli.accounts import Address +from multiversx_sdk_core.constants import METACHAIN_ID + + +def get_shard_of_address(address: Address) -> int: + pub_key = address.pubkey() + num_shards = 3 + mask_high = int("11", 2) + mask_low = int("01", 2) + + last_byte_of_pub_key = pub_key[31] + + if is_address_of_metachain(address): + return METACHAIN_ID + + shard = last_byte_of_pub_key & mask_high + if shard > num_shards - 1: + shard = last_byte_of_pub_key & mask_low + + return shard + + +def is_address_of_metachain(address: Address): + pub_key = address.pubkey() + + metachain_prefix = bytearray([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + pub_key_prefix = pub_key[0:len(metachain_prefix)] + if pub_key_prefix == metachain_prefix: + return True + + zero_address = bytearray(32) + if pub_key == zero_address: + return True + + return False diff --git a/requirements.txt b/requirements.txt index a4f23a2..f4f7f8a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ ipykernel -multiversx-sdk-core~=0.3.0 -multiversx-sdk-wallet~=0.4.2 +multiversx-sdk-cli~=6.1.3 multiversx-sdk-network-providers~=0.6.7 toml debugpy \ No newline at end of file diff --git a/scenarios/pair_admin_owner_check.py b/scenarios/pair_admin_owner_check.py index fadf0c8..92a8c17 100644 --- a/scenarios/pair_admin_owner_check.py +++ b/scenarios/pair_admin_owner_check.py @@ -8,9 +8,9 @@ from events.event_generators import ( generate_add_initial_liquidity_event, generate_add_liquidity_event, generate_swap_fixed_input) -from arrows.stress.send_token_from_minter import main as send_token_from_minter -from arrows.stress.shared import get_shard_of_address -from erdpy.accounts import Account +from ported_arrows.stress.send_token_from_minter import main as send_token_from_minter +from ported_arrows.stress.shared import get_shard_of_address +from multiversx_sdk_cli.accounts import Account def main(cli_args: List[str]): diff --git a/scenarios/pair_v2_check_fee.py b/scenarios/pair_v2_check_fee.py index 8d6a80e..eb58597 100644 --- a/scenarios/pair_v2_check_fee.py +++ b/scenarios/pair_v2_check_fee.py @@ -10,9 +10,9 @@ generate_swap_fixed_input) from utils.contract_data_fetchers import ( FeeCollectorContractDataFetcher, PairContractDataFetcher) -from arrows.stress.send_token_from_minter import main as send_token_from_minter -from arrows.stress.shared import get_shard_of_address -from erdpy.accounts import Account, Address +from ported_arrows.stress.send_token_from_minter import main as send_token_from_minter +from ported_arrows.stress.shared import get_shard_of_address +from multiversx_sdk_cli.accounts import Account, Address def main(cli_args: List[str]): diff --git a/scenarios/scenario_dex_v2_all_in.py b/scenarios/scenario_dex_v2_all_in.py index c2a6cca..6ce3272 100644 --- a/scenarios/scenario_dex_v2_all_in.py +++ b/scenarios/scenario_dex_v2_all_in.py @@ -25,10 +25,10 @@ from utils.utils_chain import nominated_amount, \ get_token_details_for_address, get_all_token_nonces_details_for_account from utils.utils_generic import log_step_fail, log_step_pass, log_condition_assert, TestStepConditions -from arrows.stress.send_token_from_minter import main as send_token_from_minter -from arrows.stress.send_egld_from_minter import main as send_egld_from_minter -from arrows.stress.shared import get_shard_of_address -from erdpy.accounts import Account, Address +from ported_arrows.stress.send_token_from_minter import main as send_token_from_minter +from ported_arrows.stress.send_egld_from_minter import main as send_egld_from_minter +from ported_arrows.stress.shared import get_shard_of_address +from multiversx_sdk_cli.accounts import Account, Address def main(cli_args: List[str]): diff --git a/scenarios/scenario_fees_collector.py b/scenarios/scenario_fees_collector.py index c2a6cca..6ce3272 100644 --- a/scenarios/scenario_fees_collector.py +++ b/scenarios/scenario_fees_collector.py @@ -25,10 +25,10 @@ from utils.utils_chain import nominated_amount, \ get_token_details_for_address, get_all_token_nonces_details_for_account from utils.utils_generic import log_step_fail, log_step_pass, log_condition_assert, TestStepConditions -from arrows.stress.send_token_from_minter import main as send_token_from_minter -from arrows.stress.send_egld_from_minter import main as send_egld_from_minter -from arrows.stress.shared import get_shard_of_address -from erdpy.accounts import Account, Address +from ported_arrows.stress.send_token_from_minter import main as send_token_from_minter +from ported_arrows.stress.send_egld_from_minter import main as send_egld_from_minter +from ported_arrows.stress.shared import get_shard_of_address +from multiversx_sdk_cli.accounts import Account, Address def main(cli_args: List[str]): diff --git a/scenarios/scenario_simple_lock_energy.py b/scenarios/scenario_simple_lock_energy.py index a423bb9..9dd58ad 100644 --- a/scenarios/scenario_simple_lock_energy.py +++ b/scenarios/scenario_simple_lock_energy.py @@ -2,7 +2,6 @@ import random import sys import time -import pytest from itertools import count from typing import List from argparse import ArgumentParser @@ -20,9 +19,9 @@ from utils.utils_chain import nominated_amount, \ get_token_details_for_address from utils.utils_generic import log_step_fail, log_step_pass, log_condition_assert, TestStepConditions -from arrows.stress.send_token_from_minter import main as send_token_from_minter -from arrows.stress.shared import get_shard_of_address -from erdpy.accounts import Account +from ported_arrows.stress.send_token_from_minter import main as send_token_from_minter +from ported_arrows.stress.shared import get_shard_of_address +from multiversx_sdk_cli.accounts import Account def main(cli_args: List[str]): diff --git a/scenarios/scenario_swaps.py b/scenarios/scenario_swaps.py index adff585..cc262ac 100644 --- a/scenarios/scenario_swaps.py +++ b/scenarios/scenario_swaps.py @@ -25,10 +25,10 @@ from utils.utils_chain import nominated_amount, \ get_token_details_for_address, get_all_token_nonces_details_for_account from utils.utils_generic import log_step_fail, log_step_pass, log_condition_assert, TestStepConditions -from arrows.stress.send_token_from_minter import main as send_token_from_minter -from arrows.stress.send_egld_from_minter import main as send_egld_from_minter -from arrows.stress.shared import get_shard_of_address -from erdpy.accounts import Account, Address +from ported_arrows.stress.send_token_from_minter import main as send_token_from_minter +from ported_arrows.stress.send_egld_from_minter import main as send_egld_from_minter +from ported_arrows.stress.shared import get_shard_of_address +from multiversx_sdk_cli.accounts import Account, Address def main(cli_args: List[str]): diff --git a/scenarios/stress_claim_metastaking.py b/scenarios/stress_claim_metastaking.py index 9343cea..c56b8ba 100644 --- a/scenarios/stress_claim_metastaking.py +++ b/scenarios/stress_claim_metastaking.py @@ -2,12 +2,12 @@ import time from argparse import ArgumentParser from typing import List -from arrows.stress.contracts.transaction_builder import number_as_arg, string_as_arg -from arrows.stress.shared import broadcast_transactions -from erdpy.accounts import Account, Address -from erdpy.proxy.core import ElrondProxy -from erdpy.proxy.messages import NetworkConfig -from erdpy.transactions import Transaction +from ported_arrows.stress.contracts.transaction_builder import number_as_arg, string_as_arg +from utils.utils_tx import broadcast_transactions +from multiversx_sdk_cli.accounts import Account, Address +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider +from multiversx_sdk_network_providers.network_config import NetworkConfig +from multiversx_sdk_cli.transactions import Transaction def claim_metastaking_rewards(caller: Account, contract_addr: Address, number_of_tokens: int, token_identifier: str, @@ -40,7 +40,7 @@ def main(cli_args: List[str]): parser.add_argument('--contract-address', required=True) args = parser.parse_args(cli_args) - proxy = ElrondProxy(args.proxy) + proxy = ProxyNetworkProvider(args.proxy) network = proxy.get_network_config() account = Account(pem_file=args.account) account.sync_nonce(proxy) diff --git a/scenarios/stress_create_positions.py b/scenarios/stress_create_positions.py index 95e6570..45b7fdd 100644 --- a/scenarios/stress_create_positions.py +++ b/scenarios/stress_create_positions.py @@ -16,9 +16,9 @@ generateExitMetastakeEvent, generateExitFarmEvent) from utils.utils_generic import log_step_pass -from arrows.stress.send_token_from_minter import main as send_token_from_minter -from arrows.stress.shared import get_shard_of_address -from erdpy.accounts import Account +from ported_arrows.stress.send_token_from_minter import main as send_token_from_minter +from ported_arrows.stress.shared import get_shard_of_address +from multiversx_sdk_cli.accounts import Account def main(cli_args: List[str]): @@ -43,9 +43,9 @@ def main(cli_args: List[str]): def create_nonce_file(context: Context): - context.accounts.sync_nonces(context.proxy) + context.accounts.sync_nonces(context.network_provider.proxy) context.accounts.store_nonces(context.nonces_file) - context.deployer_account.sync_nonce(context.proxy) + context.deployer_account.sync_nonce(context.network_provider.proxy) def send_tokens(context: Context): diff --git a/scenarios/stress_many_add_remove_liquidity.py b/scenarios/stress_many_add_remove_liquidity.py index b674228..acdd9f3 100644 --- a/scenarios/stress_many_add_remove_liquidity.py +++ b/scenarios/stress_many_add_remove_liquidity.py @@ -5,13 +5,14 @@ from pathlib import Path from typing import List -from arrows.stress.contracts.transaction_builder import \ +from ported_arrows.stress.contracts.transaction_builder import \ transfer_multi_esdt_and_execute -from arrows.stress.shared import BunchOfAccounts, broadcast_transactions -from erdpy.accounts import Account, Address -from erdpy.proxy.core import ElrondProxy -from erdpy.proxy.messages import NetworkConfig -from erdpy.transactions import Transaction +from utils.utils_tx import broadcast_transactions +from utils.utils_chain import BunchOfAccounts +from multiversx_sdk_cli.accounts import Account, Address +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider +from multiversx_sdk_network_providers.network_config import NetworkConfig +from multiversx_sdk_cli.transactions import Transaction def main(cli_args: List[str]): @@ -24,7 +25,7 @@ def main(cli_args: List[str]): parser.add_argument("--pair", required=True) args = parser.parse_args(cli_args) - proxy = ElrondProxy(args.proxy) + proxy = ProxyNetworkProvider(args.proxy) network = proxy.get_network_config() pair = Address(args.pair) accounts = BunchOfAccounts.load_accounts_from_files([Path(args.accounts)]) diff --git a/scenarios/stress_many_swaps.py b/scenarios/stress_many_swaps.py index 4825d2b..a9bc4d1 100644 --- a/scenarios/stress_many_swaps.py +++ b/scenarios/stress_many_swaps.py @@ -5,14 +5,15 @@ from pathlib import Path from typing import List -from arrows.stress.contracts.transaction_builder import (number_as_arg, +from ported_arrows.stress.contracts.transaction_builder import (number_as_arg, string_as_arg, token_id_as_arg) -from arrows.stress.shared import BunchOfAccounts, broadcast_transactions -from erdpy.accounts import Account, Address -from erdpy.proxy.core import ElrondProxy -from erdpy.proxy.messages import NetworkConfig -from erdpy.transactions import Transaction +from utils.utils_tx import broadcast_transactions +from utils.utils_chain import BunchOfAccounts +from multiversx_sdk_cli.accounts import Account, Address +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider +from multiversx_sdk_network_providers.network_config import NetworkConfig +from multiversx_sdk_cli.transactions import Transaction def main(cli_args: List[str]): @@ -24,7 +25,7 @@ def main(cli_args: List[str]): parser.add_argument("--pair", required=True) args = parser.parse_args(cli_args) - proxy = ElrondProxy(args.proxy) + proxy = ProxyNetworkProvider(args.proxy) network = proxy.get_network_config() pair = Address(args.pair) accounts = BunchOfAccounts.load_accounts_from_files([Path(args.accounts)]) diff --git a/scenarios/stress_scenario.py b/scenarios/stress_scenario.py index a55ad1d..1f609c3 100644 --- a/scenarios/stress_scenario.py +++ b/scenarios/stress_scenario.py @@ -20,7 +20,7 @@ generateRandomCompoundRewardsProxyEvent, generateEnterFarmEvent, generateExitFarmEvent, generateClaimRewardsEvent, generateEnterFarmv12Event, generate_add_initial_liquidity_event) -from erdpy.accounts import Account +from multiversx_sdk_cli.accounts import Account def main(): diff --git a/tools/account_state.py b/tools/account_state.py index 5dfb89b..88135a1 100644 --- a/tools/account_state.py +++ b/tools/account_state.py @@ -6,7 +6,7 @@ from typing import Dict, Any, Tuple, List from utils.utils_generic import log_step_fail, log_step_pass, log_warning -from erdpy.proxy.http_facade import do_get +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider def main(cli_args: List[str]): @@ -22,11 +22,12 @@ def main(cli_args: List[str]): def get_account_keys_online(address: str, proxy_url: str, block_number: int = 0, with_save_in: str = "") -> Dict[str, Any]: if block_number == 0: - url = f"{proxy_url}/address/{address}/keys" + resource_url = f"address/{address}/keys" else: - url = f"{proxy_url}/address/{address}/keys?blockNonce={block_number}" + resource_url = f"address/{address}/keys?blockNonce={block_number}" - response = do_get(url) + proxy = ProxyNetworkProvider(proxy_url) + response = proxy.do_get_generic(resource_url) keys = response.get("pairs", dict()) if keys and with_save_in: diff --git a/tools/contracts_upgrader.py b/tools/contracts_upgrader.py index d8a7715..2b3566d 100644 --- a/tools/contracts_upgrader.py +++ b/tools/contracts_upgrader.py @@ -7,8 +7,8 @@ from argparse import ArgumentParser from typing import List -from arrows.AutomaticTests.ElasticIndexer import ElasticIndexer -from arrows.AutomaticTests.ProxyExtension import ProxyExtension +from ported_arrows.AutomaticTests.ElasticIndexer import ElasticIndexer +from ported_arrows.AutomaticTests.ProxyExtension import ProxyExtension from contracts.contract_identities import RouterContractVersion, PairContractVersion, \ StakingContractVersion, ProxyContractVersion, FarmContractVersion from contracts.dex_proxy_contract import DexProxyContract @@ -26,8 +26,8 @@ from utils.utils_chain import base64_to_hex from utils.utils_generic import log_step_fail from tools import config_contracts_upgrader as config -from erdpy.accounts import Address, Account -from erdpy.proxy import ElrondProxy +from multiversx_sdk_cli.accounts import Address, Account +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider from pathlib import Path PROXY = config.DEFAULT_PROXY @@ -96,7 +96,7 @@ def main(cli_args: List[str]): args = parser.parse_args(cli_args) api = ElasticIndexer(API) - proxy = ElrondProxy(PROXY) + proxy = ProxyNetworkProvider(PROXY) extended_proxy = ProxyExtension(PROXY) network_providers = NetworkProviders(api, proxy, extended_proxy) @@ -203,7 +203,7 @@ def save_wasm(code_data_hex: str, code_hash: str): print(f"Created wasm binary in: {output_file}") -def fetch_and_save_contracts(contract_addresses: list, contract_label: str, save_path: Path, proxy: ElrondProxy): +def fetch_and_save_contracts(contract_addresses: list, contract_label: str, save_path: Path, proxy: ProxyNetworkProvider): pairs_data = {} for address in contract_addresses: contract_addr = Address(address) @@ -223,13 +223,13 @@ def fetch_and_save_contracts(contract_addresses: list, contract_label: str, save print(f"Dumped {contract_label} data in {save_path}") -def fetch_and_save_pairs_from_chain(proxy: ElrondProxy): +def fetch_and_save_pairs_from_chain(proxy: ProxyNetworkProvider): router_data_fetcher = RouterContractDataFetcher(Address(ROUTER_CONTRACT), PROXY) registered_pairs = router_data_fetcher.get_data("getAllPairsManagedAddresses") fetch_and_save_contracts(registered_pairs, PAIRS_LABEL, OUTPUT_PAIR_CONTRACTS_FILE, proxy) -def fetch_and_save_farms_from_chain(proxy: ElrondProxy): +def fetch_and_save_farms_from_chain(proxy: ProxyNetworkProvider): farmsv13 = get_farm_addresses_from_chain("v1.3") farmsv13locked = get_farm_addresses_locked_from_chain() farmsv12 = get_farm_addresses_from_chain("v1.2") @@ -238,12 +238,12 @@ def fetch_and_save_farms_from_chain(proxy: ElrondProxy): fetch_and_save_contracts(farmsv12, FARMSV12_LABEL, OUTPUT_FARMV12_CONTRACTS_FILE, proxy) -def fetch_and_save_stakings_from_chain(proxy: ElrondProxy): +def fetch_and_save_stakings_from_chain(proxy: ProxyNetworkProvider): stakings = get_staking_addresses_from_chain() fetch_and_save_contracts(stakings, STAKINGS_LABEL, OUTPUT_STAKING_CONTRACTS_FILE, proxy) -def fetch_and_save_metastakings_from_chain(proxy: ElrondProxy): +def fetch_and_save_metastakings_from_chain(proxy: ProxyNetworkProvider): metastakings = get_metastaking_addresses_from_chain() fetch_and_save_contracts(metastakings, METASTAKINGS_LABEL, OUTPUT_METASTAKING_CONTRACTS_FILE, proxy) diff --git a/tools/manual_issue_token.py b/tools/manual_issue_token.py index 36e2037..54f11b3 100644 --- a/tools/manual_issue_token.py +++ b/tools/manual_issue_token.py @@ -4,13 +4,11 @@ from typing import List import config -from arrows.stress.esdtnft.shared import (build_token_name, build_token_ticker, - load_contracts, make_call_arg_ascii, - make_call_arg_pubkey) -from arrows.stress.shared import BunchOfAccounts, broadcast_transactions -from erdpy.contracts import SmartContract -from erdpy.proxy.core import ElrondProxy -from erdpy.transactions import Transaction +from utils.utils_tx import broadcast_transactions +from utils.utils_chain import BunchOfAccounts +from multiversx_sdk_cli.contracts import SmartContract +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider +from multiversx_sdk_cli.transactions import Transaction def main(cli_args: List[str]): @@ -32,7 +30,7 @@ def main(cli_args: List[str]): args = parser.parse_args(cli_args) - proxy = ElrondProxy(args.proxy) + proxy = ProxyNetworkProvider(args.proxy) network = proxy.get_network_config() bunch_of_accounts = BunchOfAccounts.load_accounts_from_files([args.accounts]) diff --git a/tools/safeprice_monitor.py b/tools/safeprice_monitor.py index 7e05acb..7a904fd 100644 --- a/tools/safeprice_monitor.py +++ b/tools/safeprice_monitor.py @@ -4,8 +4,8 @@ from typing import List from utils.contract_data_fetchers import PairContractDataFetcher -from erdpy.accounts import Address -from erdpy.proxy.core import ElrondProxy +from multiversx_sdk_cli.accounts import Address +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider from utils.utils_chain import decode_merged_attributes PROXY = "https://gateway.elrond.com" @@ -21,7 +21,7 @@ def main(cli_args: List[str]): contract_data_fetcher = PairContractDataFetcher(Address(PAIR_ADDRESS), PROXY) - proxy = ElrondProxy(PROXY) + proxy = ProxyNetworkProvider(PROXY) esdt_token_payment_schema = { 'token_type': 'u8', diff --git a/trackers/concrete_observer.py b/trackers/concrete_observer.py index e0922be..7642736 100644 --- a/trackers/concrete_observer.py +++ b/trackers/concrete_observer.py @@ -1,7 +1,7 @@ from typing import List, Any from .abstract_observer import Publisher, Subscriber -from ..contracts.contract_identities import DEXContractInterface -from erdpy.accounts import Account +from contracts.contract_identities import DEXContractInterface +from multiversx_sdk_cli.accounts import Account class Observable(Publisher): diff --git a/trackers/farm_economics_tracking.py b/trackers/farm_economics_tracking.py index 30a45e9..b370c0d 100644 --- a/trackers/farm_economics_tracking.py +++ b/trackers/farm_economics_tracking.py @@ -1,7 +1,7 @@ from typing import Dict from utils.contract_data_fetchers import FarmContractDataFetcher, ChainDataFetcher from utils.utils_tx import NetworkProviders -from erdpy.accounts import Account, Address +from multiversx_sdk_cli.accounts import Account, Address from utils.utils_generic import log_step_fail, log_step_pass, log_substep from events.farm_events import (EnterFarmEvent, ExitFarmEvent, ClaimRewardsFarmEvent, SetTokenBalanceEvent) diff --git a/trackers/metastaking_economics_tracking.py b/trackers/metastaking_economics_tracking.py index 99b80a3..fcd5c2f 100644 --- a/trackers/metastaking_economics_tracking.py +++ b/trackers/metastaking_economics_tracking.py @@ -1,4 +1,4 @@ -from erdpy.accounts import Address +from multiversx_sdk_cli.accounts import Address from utils.utils_tx import NetworkProviders from utils.utils_generic import log_step_pass from utils.contract_data_fetchers import MetaStakingContractDataFetcher, ChainDataFetcher diff --git a/trackers/pair_economics_tracking.py b/trackers/pair_economics_tracking.py index e683eaf..9950018 100644 --- a/trackers/pair_economics_tracking.py +++ b/trackers/pair_economics_tracking.py @@ -1,4 +1,4 @@ -from erdpy.accounts import Address +from multiversx_sdk_cli.accounts import Address from utils.utils_tx import NetworkProviders from trackers.abstract_observer import Subscriber from trackers.concrete_observer import Observable diff --git a/trackers/price_discovery_economics_tracking.py b/trackers/price_discovery_economics_tracking.py index f34ba7f..2bfe5cf 100644 --- a/trackers/price_discovery_economics_tracking.py +++ b/trackers/price_discovery_economics_tracking.py @@ -6,8 +6,8 @@ from contracts.contract_identities import PriceDiscoveryContractIdentity from utils.utils_chain import get_all_token_nonces_details_for_account, get_token_details_for_address from utils.utils_generic import log_step_fail, log_step_pass, log_substep -from erdpy.accounts import Address -from erdpy.proxy import ElrondProxy +from multiversx_sdk_cli.accounts import Address +from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider class PriceDiscoveryAccountEconomics: @@ -27,7 +27,7 @@ def __init__(self, address: Address): class PriceDiscoveryEconomics: def __init__(self, contract_identity: PriceDiscoveryContractIdentity, proxy_url: str): - self.proxy = ElrondProxy(proxy_url) + self.proxy = ProxyNetworkProvider(proxy_url) self.pd_contract_identity = contract_identity self.contract_data_fetcher = PriceDiscoveryContractDataFetcher(Address(contract_identity.address), proxy_url) diff --git a/trackers/simple_lock_energy_tracking.py b/trackers/simple_lock_energy_tracking.py index b7a58f4..1b23bea 100644 --- a/trackers/simple_lock_energy_tracking.py +++ b/trackers/simple_lock_energy_tracking.py @@ -2,7 +2,7 @@ from utils.contract_data_fetchers import SimpleLockEnergyContractDataFetcher from utils.utils_tx import ESDTToken, NetworkProviders from utils.utils_chain import get_token_details_for_address, decode_merged_attributes, base64_to_hex -from erdpy.accounts import Account, Address +from multiversx_sdk_cli.accounts import Address class SimpleLockEnergyTokenAttributes: diff --git a/trackers/staking_economics_tracking.py b/trackers/staking_economics_tracking.py index 5ccf791..8373f7a 100644 --- a/trackers/staking_economics_tracking.py +++ b/trackers/staking_economics_tracking.py @@ -1,4 +1,4 @@ -from erdpy.accounts import Address +from multiversx_sdk_cli.accounts import Address from utils.utils_tx import NetworkProviders from trackers.abstract_observer import Subscriber from trackers.concrete_observer import Observable diff --git a/utils/utils_chain.py b/utils/utils_chain.py index 8ec951c..a4b1879 100644 --- a/utils/utils_chain.py +++ b/utils/utils_chain.py @@ -7,7 +7,8 @@ from multiversx_sdk_network_providers.tokens import FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork from multiversx_sdk_core import Address, Transaction from multiversx_sdk_core.interfaces import ISignature -from multiversx_sdk_wallet import UserSigner, pem_format +from multiversx_sdk_wallet.user_signer import UserSigner +from multiversx_sdk_wallet.pem_entry import PemEntry from multiversx_sdk_network_providers import ProxyNetworkProvider from utils import utils_generic @@ -68,7 +69,7 @@ def load_accounts_from_files(cls, files: List[Path]): for file in files: # Assume multi-account PEM files. - pem_entries = len(pem_format.parse_all(file)) + pem_entries = len(PemEntry.from_text_all(file.read_text())) for index in range(pem_entries): account = Account(pem_file=str(file), pem_index=index) loaded.append(account) diff --git a/utils/utils_tx.py b/utils/utils_tx.py index 859d4f1..bd491ea 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -2,7 +2,7 @@ import time import traceback from pathlib import Path -from typing import List, Dict, Any, Union +from typing import List, Dict, Union, Tuple from multiversx_sdk_core import Transaction, TokenPayment, Address from multiversx_sdk_core.interfaces import ICodeMetadata @@ -413,7 +413,7 @@ def endpoint_call(proxy: ProxyNetworkProvider, gas: int, user: Account, contract def deploy(contract_label: str, proxy: ProxyNetworkProvider, gas: int, - owner: Account, bytecode_path: str, metadata: ICodeMetadata, args: list) -> (str, str): + owner: Account, bytecode_path: str, metadata: ICodeMetadata, args: list) -> Tuple[str, str]: logger.debug(f"Deploy {contract_label}") network_config = proxy.get_network_config() # TODO: find solution to avoid this call tx_hash, contract_address = "", "" From f3a5cef85172e68362478e6e330741aee28e0b18 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Thu, 6 Apr 2023 17:34:05 +0300 Subject: [PATCH 22/26] robustness improvement for waiting on long transactions --- utils/utils_tx.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/utils_tx.py b/utils/utils_tx.py index 859d4f1..39195f8 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -101,6 +101,10 @@ def check_complex_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: logger.debug(f"Waiting for transaction {tx_hash} to be executed...") time.sleep(API_LONG_TX_DELAY - API_TX_DELAY) # temporary fix for the api returning the wrong status self.wait_for_tx_executed(tx_hash) + if self.check_simple_tx_status(tx_hash, msg_label): + # most likely a false positive, try again + time.sleep(API_LONG_TX_DELAY - API_TX_DELAY) + self.wait_for_tx_executed(tx_hash) return self.check_simple_tx_status(tx_hash, msg_label) def check_simple_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: @@ -359,7 +363,7 @@ def multi_esdt_endpoint_call(function_purpose: str, proxy: ProxyNetworkProvider, type[List[ESDTToken]]: tokens list opt: type[str..]: endpoint arguments """ - log_warning(function_purpose) + logger.debug(function_purpose) network_config = proxy.get_network_config() # TODO: find solution to avoid this call tx_hash = "" From a8910aef157df0c7e0faf96a15a70c1431b4e6bc Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Thu, 6 Apr 2023 19:20:03 +0300 Subject: [PATCH 23/26] robustness improvement for waiting on long transactions --- deploy/dex_structure.py | 2 +- utils/utils_tx.py | 86 +++++++++++++++++++++++++---------------- 2 files changed, 53 insertions(+), 35 deletions(-) diff --git a/deploy/dex_structure.py b/deploy/dex_structure.py index 33a0e05..1f64b37 100644 --- a/deploy/dex_structure.py +++ b/deploy/dex_structure.py @@ -191,7 +191,7 @@ def deploy_tokens(self, deployer_account: Account, network_provider: NetworkProv token_hashes.extend(hashes) for txhash in token_hashes: - network_provider.wait_for_tx_executed(txhash) + network_provider.check_complex_tx_status(txhash) time.sleep(40) diff --git a/utils/utils_tx.py b/utils/utils_tx.py index 39195f8..222872a 100644 --- a/utils/utils_tx.py +++ b/utils/utils_tx.py @@ -1,6 +1,7 @@ import sys import time import traceback +from os import read from pathlib import Path from typing import List, Dict, Any, Union @@ -10,6 +11,7 @@ from multiversx_sdk_network_providers.network_config import NetworkConfig from multiversx_sdk_network_providers.tokens import FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork from multiversx_sdk_network_providers.transaction_events import TransactionEvent +from multiversx_sdk_network_providers.transaction_status import TransactionStatus from multiversx_sdk_network_providers.transactions import TransactionOnNetwork from multiversx_sdk_core.transaction_builders import ContractCallBuilder, DefaultTransactionBuildersConfiguration, \ MultiESDTNFTTransferBuilder, ContractDeploymentBuilder, ContractUpgradeBuilder @@ -21,8 +23,9 @@ TX_CACHE: Dict[str, dict] = {} logger = get_logger(__name__) -API_TX_DELAY = 2 +API_TX_DELAY = 3 API_LONG_TX_DELAY = 6 +API_TX_STATUS_REFETCH_DELAY = 2 MAX_TX_FETCH_RETRIES = 50 // API_TX_DELAY @@ -68,14 +71,30 @@ def __init__(self, api: str, proxy: str): self.proxy = ProxyNetworkProvider(proxy) self.network = self.proxy.get_network_config() - def wait_for_tx_executed(self, tx_hash: str): - time.sleep(API_TX_DELAY) # temporary fix for the api returning the wrong status - while True: + def _get_initial_tx_status(self, tx_hash: str) -> Union[None, TransactionStatus]: + # due to API data propagation delays, some transactions may not be indexed yet at the time of the request + results = None + try: + results = self.api.get_transaction_status(tx_hash) + except GenericError as e: + if '404' in e.data: + # api didn't index the transaction yet, try again after a delay + logger.debug(f"Transaction {tx_hash} not indexed yet, " + f"trying again in {API_TX_STATUS_REFETCH_DELAY} seconds...") + time.sleep(API_TX_STATUS_REFETCH_DELAY) + results = self.api.get_transaction_status(tx_hash) + return results + + def wait_for_tx_executed(self, tx_hash: str) -> Union[None, TransactionStatus]: + status = self._get_initial_tx_status(tx_hash) + if status is None: + log_step_fail(f"Wait failed. Transaction {tx_hash} not found!") + return None + while not status.is_executed(): + time.sleep(API_TX_STATUS_REFETCH_DELAY) status = self.api.get_transaction_status(tx_hash) logger.debug(f"Transaction {tx_hash} status: {status.status}") - if status.is_executed(): - break - time.sleep(self.network.round_duration // 1000) + return status def check_deploy_tx_status(self, tx_hash: str, address: str, msg_label: str = "") -> bool: if not tx_hash: @@ -83,8 +102,8 @@ def check_deploy_tx_status(self, tx_hash: str, address: str, msg_label: str = "" log_step_fail(f"FAIL: no tx hash for {msg_label} contract deployment!") return False - status = self.api.get_transaction_status(tx_hash) - if status.is_failed() or address == "": + status = self.wait_for_tx_executed(tx_hash) + if status is None or status.is_failed() or address == "": if msg_label: log_step_fail(f"FAIL: transaction for {msg_label} contract deployment failed " f"or couldn't retrieve address!") @@ -99,13 +118,28 @@ def check_complex_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: return False logger.debug(f"Waiting for transaction {tx_hash} to be executed...") - time.sleep(API_LONG_TX_DELAY - API_TX_DELAY) # temporary fix for the api returning the wrong status - self.wait_for_tx_executed(tx_hash) - if self.check_simple_tx_status(tx_hash, msg_label): - # most likely a false positive, try again - time.sleep(API_LONG_TX_DELAY - API_TX_DELAY) - self.wait_for_tx_executed(tx_hash) - return self.check_simple_tx_status(tx_hash, msg_label) + start_time = time.time() + + # temporary fix for the api returning the wrong status + # start by avoiding an early false success followed by pending (usually occurring in the first 2 rounds) + time.sleep(API_LONG_TX_DELAY) + status = self.check_simple_tx_status(tx_hash, msg_label) + if status: + ready_time = time.time() + if ready_time - start_time < 2 * self.network.round_duration // 1000: + # most likely a false positive, wait again + logger.debug(f"TX status most likely false success, making sure...") + time.sleep(API_LONG_TX_DELAY) + status = self.wait_for_tx_executed(tx_hash) + + # we need to check for false success again, + # because the api may return a fake success at the end followed by fail + if status: + logger.debug(f"Making sure tx status is not false success...") + time.sleep(API_LONG_TX_DELAY) + status = self.check_simple_tx_status(tx_hash, msg_label) + + return status def check_simple_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: if not tx_hash: @@ -113,19 +147,10 @@ def check_simple_tx_status(self, tx_hash: str, msg_label: str = "") -> bool: log_step_fail(f"FAIL: no tx hash for {msg_label} transaction!") return False - results = None - time.sleep(API_TX_DELAY) # temporary fix for the api returning wrong statuses - try: - results = self.api.get_transaction_status(tx_hash) - except GenericError as e: - if '404' in e.data: - # api didn't index the transaction yet, try again after a delay - logger.debug(f"Transaction {tx_hash} not indexed yet, trying again in {API_TX_DELAY} seconds...") - time.sleep(API_TX_DELAY) - results = self.api.get_transaction_status(tx_hash) - + results = self.wait_for_tx_executed(tx_hash) if results is None: log_step_fail(f"FAIL: couldn't retrieve transaction {tx_hash} status!") + return False if results.is_failed(): if msg_label: @@ -494,13 +519,6 @@ def get_deployed_address_from_tx(tx_hash: str, proxy: ProxyNetworkProvider) -> s return event.address.bech32() -def get_issued_token_from_tx(tx_hash: str, proxy: ProxyNetworkProvider) -> str: - event = get_event_from_tx("registerAndSetAllRoles", tx_hash, proxy) - if event is None: - return "" - return str(event.topics[0]) - - def broadcast_transactions(transactions: List[Transaction], proxy: ProxyNetworkProvider, chunk_size: int, sleep: int = 0, confirm_yes: bool = False): chunks = list(split_to_chunks(transactions, chunk_size)) From 676eb35c636750a065d838ea53f58d9c1e7fa183 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Fri, 7 Apr 2023 17:49:57 +0300 Subject: [PATCH 24/26] fixes for scenarios porting issues --- context.py | 18 +++++++-------- contracts/simple_lock_energy_contract.py | 15 +++++++++++- deploy/dex_structure.py | 10 +++++++- events/event_generators.py | 6 ++--- scenarios/scenario_dex_v2_all_in.py | 29 ++++++++++++------------ scenarios/stress_create_positions.py | 6 ++--- utils/contract_data_fetchers.py | 2 +- utils/utils_chain.py | 6 ++--- 8 files changed, 55 insertions(+), 37 deletions(-) diff --git a/context.py b/context.py index 8c378af..34f392a 100644 --- a/context.py +++ b/context.py @@ -60,38 +60,38 @@ def __init__(self): # deploy closing self.deploy_structure.print_deployed_contracts() - # self.observable = self.init_observers() + self.observable = Observable() + # self.init_observers() # call should be parameterized so that observers can be disabled programmatically def init_observers(self): - observable = Observable() 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) - observable.subscribe(observer) + 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) - observable.subscribe(observer) + self.observable.subscribe(observer) for acc in self.accounts.get_all(): account_observer = FarmAccountEconomics(acc.address, self.network_provider) - observable.subscribe(account_observer) + 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) - observable.subscribe(observer) + 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) - observable.subscribe(observer) + self.observable.subscribe(observer) metastaking_contracts = self.deploy_structure.contracts[config.METASTAKINGS].deployed_contracts for contract in metastaking_contracts: @@ -100,9 +100,7 @@ def init_observers(self): 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) - observable.subscribe(observer) - - return observable + self.observable.subscribe(observer) def get_slippaged_below_value(self, value: int): return value - int(value * self.pair_slippage) diff --git a/contracts/simple_lock_energy_contract.py b/contracts/simple_lock_energy_contract.py index b883f1f..9985ced 100644 --- a/contracts/simple_lock_energy_contract.py +++ b/contracts/simple_lock_energy_contract.py @@ -1,8 +1,10 @@ from contracts.contract_identities import DEXContractInterface +from utils.contract_data_fetchers import SimpleLockEnergyContractDataFetcher +from utils import decoding_structures from utils.logger import get_logger from utils.utils_tx import multi_esdt_endpoint_call, endpoint_call, deploy, upgrade_call from utils.utils_generic import log_step_pass, log_substep, log_unexpected_args -from utils.utils_chain import Account, WrapperAddress as Address +from utils.utils_chain import Account, WrapperAddress as Address, decode_merged_attributes from multiversx_sdk_core import CodeMetadata from multiversx_sdk_network_providers import ProxyNetworkProvider @@ -455,3 +457,14 @@ def print_contract_info(self): log_substep(f"Locked token: {self.locked_token}") log_substep(f"Locked LP token: {self.lp_proxy_token}") log_substep(f"Locked Farm token: {self.farm_proxy_token}") + + def get_lock_options(self, proxy: ProxyNetworkProvider): + data_fetcher = SimpleLockEnergyContractDataFetcher(Address(self.address), proxy.url) + raw_result = data_fetcher.get_data("getLockOptions") + if not raw_result: + return [] + decoded_results = [] + for entry in raw_result: + decoded_entry = decode_merged_attributes(entry, decoding_structures.LOCK_OPTIONS) + decoded_results.append(decoded_entry) + return decoded_results diff --git a/deploy/dex_structure.py b/deploy/dex_structure.py index 1f64b37..58608c8 100644 --- a/deploy/dex_structure.py +++ b/deploy/dex_structure.py @@ -281,7 +281,7 @@ def start_deployed_contracts(self, deployer_account: Account, network_provider: def global_start_setups(self, deployer_account: Account, network_provider: NetworkProviders, clean_deploy_override: bool): self.set_transfer_role_locked_token(deployer_account, network_provider, clean_deploy_override) - # self.set_proxy_v2_in_pairs(deployer_account, network_provider, clean_deploy_override) + # self.set_proxy_v2_in_pairs(deployer_account, network_provider, clean_deploy_override) # used only when not done implicitly def set_transfer_role_locked_token(self, deployer_account: Account, network_provider: NetworkProviders, clean_deploy_override: bool): @@ -307,6 +307,14 @@ def set_transfer_role_locked_token(self, deployer_account: Account, network_prov if not network_provider.check_complex_tx_status(tx_hash, "set transfer role for locked asset on contracts"): return + # set transfer role for self + if self.contracts[config.SIMPLE_LOCKS_ENERGY].deploy_clean or clean_deploy_override: + tx_hash = energy_factory.set_transfer_role_locked_token(deployer_account, network_provider.proxy, + []) + if not network_provider.check_complex_tx_status(tx_hash, + "set transfer role for locked asset on factory"): + return + def set_proxy_v2_in_pairs(self, deployer_account: Account, network_providers: NetworkProviders, clean_deploy_override: bool): search_label = "proxy_v2" diff --git a/events/event_generators.py b/events/event_generators.py index 5b5cd41..cb89bcc 100644 --- a/events/event_generators.py +++ b/events/event_generators.py @@ -47,7 +47,7 @@ def generate_add_liquidity_event(context: Context, user_account: Account, pair_c max_amount_a = int(amount_token_a * context.add_liquidity_max_amount) # should do a try except block on get equivalent equivalent_amount_b = contract_data_fetcher.get_data("getEquivalent", - ["0x" + tokens[0].encode('utf-8').hex(), + [tokens[0], max_amount_a]) if equivalent_amount_b <= 0 or equivalent_amount_b > amount_token_b: @@ -152,7 +152,7 @@ def generate_swap_fixed_input(context: Context, user_account: Account, pair_cont int(amount_token_a * context.swap_max_tokens_to_spend)) equivalent_amount_token_b = contract_data_fetcher.get_data("getAmountOut", - ["0x"+tokens[0].encode('utf-8').hex(), + [tokens[0], amount_token_a_swapped]) if equivalent_amount_token_b <= 0: @@ -200,7 +200,7 @@ def generate_swap_fixed_output(context: Context, user_account: Account, pair_con # TODO: switch to getAmountIn equivalent_amount_token_b = contract_data_fetcher.get_data("getAmountOut", - ["0x"+tokens[0].encode('utf-8').hex(), + [tokens[0], amount_token_a_max]) # TODO: apply slippage on token A event = SwapFixedOutputEvent( diff --git a/scenarios/scenario_dex_v2_all_in.py b/scenarios/scenario_dex_v2_all_in.py index 6ce3272..48b227a 100644 --- a/scenarios/scenario_dex_v2_all_in.py +++ b/scenarios/scenario_dex_v2_all_in.py @@ -4,8 +4,6 @@ import time import traceback -import pytest -from itertools import count from typing import List from argparse import ArgumentParser @@ -22,20 +20,20 @@ generateExitMetastakeEvent, generate_swap_fixed_input) from utils.contract_data_fetchers import PairContractDataFetcher, SimpleLockEnergyContractDataFetcher from utils.utils_tx import ESDTToken -from utils.utils_chain import nominated_amount, \ +from utils.utils_chain import Account, nominated_amount, \ get_token_details_for_address, get_all_token_nonces_details_for_account from utils.utils_generic import log_step_fail, log_step_pass, log_condition_assert, TestStepConditions from ported_arrows.stress.send_token_from_minter import main as send_token_from_minter from ported_arrows.stress.send_egld_from_minter import main as send_egld_from_minter from ported_arrows.stress.shared import get_shard_of_address -from multiversx_sdk_cli.accounts import Account, Address +from multiversx_sdk_cli.accounts import Address def main(cli_args: List[str]): parser = ArgumentParser() parser.add_argument("--threads", required=False, default="2") # number of concurrent threads to execute operations parser.add_argument("--repeats", required=False, default="0") # number of total operations to execute; 0 - infinite - parser.add_argument("--skip-minting", action="store_true", default=False) + parser.add_argument("--skip-minting", action="store_true", default=True) args = parser.parse_args(cli_args) context = Context() @@ -48,14 +46,14 @@ def main(cli_args: List[str]): create_nonce_file(context) - # stress generator for adding liquidity, enter farm, enter metastaking, claim metastaking, exit metastaking + # stress generator scenarios(context, int(args.threads), int(args.repeats)) def create_nonce_file(context: Context): - context.accounts.sync_nonces(context.proxy) + context.accounts.sync_nonces(context.network_provider.proxy) context.accounts.store_nonces(context.nonces_file) - context.deployer_account.sync_nonce(context.proxy) + context.deployer_account.sync_nonce(context.network_provider.proxy) def send_tokens(context: Context): @@ -137,8 +135,8 @@ def scenarios(context: Context, threads: int, repeats: int): def scenarios_per_account(context: Context, account: Account): min_time = 10 max_time = 30 - deployer_shard = get_shard_of_address(context.deployer_account.address) - sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + deployer_shard = context.deployer_account.address.get_shard() + sleep_time = config.CROSS_SHARD_DELAY if account.address.get_shard() is not deployer_shard \ else 6 account.sync_nonce(context.network_provider.proxy) @@ -151,18 +149,19 @@ def scenarios_per_account(context: Context, account: Account): simple_lock_energy_contract: SimpleLockEnergyContract simple_lock_energy_contract = context.get_contracts(config.SIMPLE_LOCKS_ENERGY)[0] - simple_lock_energy_data_fetcher = SimpleLockEnergyContractDataFetcher(Address(simple_lock_energy_contract.address), - context.network_provider.proxy.url) - lock_options = simple_lock_energy_data_fetcher.get_data('getLockOptions') + lock_options = simple_lock_energy_contract.get_lock_options(context.network_provider.proxy) + lock_periods = [] + for entry in lock_options: + lock_periods.append(entry['lock_epochs']) # lock tokens amount = random.randint(1, 10) - lock_period = random.choice(lock_options) + lock_period = random.choice(lock_periods) token = ESDTToken(simple_lock_energy_contract.base_token, 0, nominated_amount(amount)) args = [[token], lock_period] _ = simple_lock_energy_contract.lock_tokens(account, context.network_provider.proxy, args) print(f"User: {account.address.bech32()}") - print(f"Locked {amount} tokens for {lock_period} epochs.") + print(f"Locked {nominated_amount(amount)} tokens for {lock_period} epochs.") time.sleep(sleep_time) # swap tokens diff --git a/scenarios/stress_create_positions.py b/scenarios/stress_create_positions.py index 45b7fdd..1a31516 100644 --- a/scenarios/stress_create_positions.py +++ b/scenarios/stress_create_positions.py @@ -18,7 +18,7 @@ from utils.utils_generic import log_step_pass from ported_arrows.stress.send_token_from_minter import main as send_token_from_minter from ported_arrows.stress.shared import get_shard_of_address -from multiversx_sdk_cli.accounts import Account +from utils.utils_chain import Account def main(cli_args: List[str]): @@ -97,8 +97,8 @@ def stress(context: Context, threads: int, repeats: int): def stress_per_account(context: Context, account: Account): min_time = 2 max_time = 10 - deployer_shard = get_shard_of_address(context.deployer_account.address) - sleep_time = config.CROSS_SHARD_DELAY if get_shard_of_address(account.address) is not deployer_shard \ + deployer_shard = context.deployer_account.address.get_shard() + sleep_time = config.CROSS_SHARD_DELAY if account.address.get_shard() is not deployer_shard \ else config.INTRA_SHARD_DELAY account.sync_nonce(context.network_provider.proxy) diff --git a/utils/contract_data_fetchers.py b/utils/contract_data_fetchers.py index 790b715..368a388 100644 --- a/utils/contract_data_fetchers.py +++ b/utils/contract_data_fetchers.py @@ -115,7 +115,7 @@ def __init__(self, contract_address: Address, proxy_url: str): "getEnergyEntryForUser": self._get_hex_view, "getFeesBurnPercentage": self._get_int_view, "getPenaltyPercentage": self._get_hex_view, - "getLockOptions": self._get_int_list_view, + "getLockOptions": self._get_hex_list_view, } diff --git a/utils/utils_chain.py b/utils/utils_chain.py index a4b1879..beb2635 100644 --- a/utils/utils_chain.py +++ b/utils/utils_chain.py @@ -1,6 +1,6 @@ import base64 import time -from multiprocessing import Pool +from multiprocessing.dummy import Pool from os import path from pathlib import Path from typing import List, Dict, Any, Optional, Set, cast @@ -102,14 +102,14 @@ def get_in_shard(self, shard: int) -> List[Account]: return [account for account in self.accounts if account.address.get_shard() == shard] def sync_nonces(self, proxy: ProxyNetworkProvider): - print("Sync nonces for", len(self.accounts), "accounts") + logger.debug(f"Sync nonces for {len(self.accounts)} accounts") def sync_nonce(account: Account): account.sync_nonce(proxy) Pool(100).map(sync_nonce, self.accounts) - print("Done") + logger.debug("Done") def store_nonces(self, file: str): # We load the previously stored data in order to display a nice delta (for debugging purposes) From d5e8a19d17d15b10025cdb6a7ba99869c2f1e0f2 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Mon, 10 Apr 2023 15:09:44 +0300 Subject: [PATCH 25/26] decoded views in contract --- contracts/simple_lock_energy_contract.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/contracts/simple_lock_energy_contract.py b/contracts/simple_lock_energy_contract.py index 9985ced..b89d9ad 100644 --- a/contracts/simple_lock_energy_contract.py +++ b/contracts/simple_lock_energy_contract.py @@ -1,3 +1,5 @@ +from typing import Dict, List, Any + from contracts.contract_identities import DEXContractInterface from utils.contract_data_fetchers import SimpleLockEnergyContractDataFetcher from utils import decoding_structures @@ -458,7 +460,7 @@ def print_contract_info(self): log_substep(f"Locked LP token: {self.lp_proxy_token}") log_substep(f"Locked Farm token: {self.farm_proxy_token}") - def get_lock_options(self, proxy: ProxyNetworkProvider): + def get_lock_options(self, proxy: ProxyNetworkProvider) -> List[Dict[str, Any]]: data_fetcher = SimpleLockEnergyContractDataFetcher(Address(self.address), proxy.url) raw_result = data_fetcher.get_data("getLockOptions") if not raw_result: @@ -468,3 +470,12 @@ def get_lock_options(self, proxy: ProxyNetworkProvider): decoded_entry = decode_merged_attributes(entry, decoding_structures.LOCK_OPTIONS) decoded_results.append(decoded_entry) return decoded_results + + def get_energy_for_user(self, proxy: ProxyNetworkProvider, user_address: str) -> Dict[str, Any]: + data_fetcher = SimpleLockEnergyContractDataFetcher(Address(self.address), proxy.url) + raw_results = data_fetcher.get_data('getEnergyEntryForUser', [Address(user_address).serialize()]) + if not raw_results: + return "" + energy_entry_user = decode_merged_attributes(raw_results, decoding_structures.ENERGY_ENTRY) + + return energy_entry_user From fc3dc2fa1dc8c24c0c2a9743d65396d731a3c0d6 Mon Sep 17 00:00:00 2001 From: Ovidiu Olteanu Date: Mon, 10 Apr 2023 15:10:23 +0300 Subject: [PATCH 26/26] decoded views in contract --- contracts/simple_lock_energy_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/simple_lock_energy_contract.py b/contracts/simple_lock_energy_contract.py index b89d9ad..db79f5d 100644 --- a/contracts/simple_lock_energy_contract.py +++ b/contracts/simple_lock_energy_contract.py @@ -475,7 +475,7 @@ def get_energy_for_user(self, proxy: ProxyNetworkProvider, user_address: str) -> data_fetcher = SimpleLockEnergyContractDataFetcher(Address(self.address), proxy.url) raw_results = data_fetcher.get_data('getEnergyEntryForUser', [Address(user_address).serialize()]) if not raw_results: - return "" + return {} energy_entry_user = decode_merged_attributes(raw_results, decoding_structures.ENERGY_ENTRY) return energy_entry_user