diff --git a/contracts/dex_proxy_contract.py b/contracts/dex_proxy_contract.py index ab33e4e..b685213 100644 --- a/contracts/dex_proxy_contract.py +++ b/contracts/dex_proxy_contract.py @@ -54,11 +54,12 @@ def __init__(self, farmContract: FarmContract, class DexProxyExitFarmEvent: - def __init__(self, farmContract: FarmContract, token: str, nonce: int, amount): + def __init__(self, farmContract: FarmContract, token: str, nonce: int, amount, original_caller: str = ""): self.farmContract = farmContract self.token = token self.nonce = nonce self.amount = amount + self.original_caller = original_caller class DexProxyClaimRewardsEvent: @@ -181,8 +182,7 @@ def exit_farm_proxy(self, user: Account, proxy: ProxyNetworkProvider, event: Dex sc_args = [ tokens, - Address(event.farmContract.address), - event.amount + Address(event.farmContract.address) ] return multi_esdt_endpoint_call(function_purpose, proxy, gas_limit, user, Address(self.address), "exitFarmProxy", sc_args) diff --git a/contracts/farm_contract.py b/contracts/farm_contract.py index 0ca9421..39bb129 100644 --- a/contracts/farm_contract.py +++ b/contracts/farm_contract.py @@ -63,24 +63,19 @@ def has_proxy(self) -> bool: return True return False - def enterFarm(self, network_provider: NetworkProviders, user: Account, event: EnterFarmEvent, lock: int = 0, initial: bool = False) -> str: + def enterFarm(self, network_provider: NetworkProviders, user: Account, event: EnterFarmEvent) -> str: # 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: - enterFarmFn = "enterFarmAndLockRewards" - elif lock == 0: - enterFarmFn = "enterFarm" - logger.info(f"Calling {enterFarmFn} endpoint...") gas_limit = 50000000 tokens = [ESDTToken(event.farming_tk, event.farming_tk_nonce, event.farming_tk_amount)] - if not initial: + if event.farm_tk_amount > 0: tokens.append(ESDTToken(event.farm_tk, event.farm_tk_nonce, event.farm_tk_amount)) sc_args = [tokens] diff --git a/events/event_generators.py b/events/event_generators.py index 0a19fda..b2baec1 100644 --- a/events/event_generators.py +++ b/events/event_generators.py @@ -233,8 +233,7 @@ def generate_random_swap_fixed_output(context: Context): 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;""" +def generateEnterFarmEvent(context: Context, userAccount: Account, farmContract: FarmContract): logger.info(f"Attempt generateEnterFarmEvent for {userAccount.address.bech32()} on {farmContract.address}") tx_hash = "" try: @@ -248,8 +247,6 @@ def generateEnterFarmEvent(context: Context, userAccount: Account, farmContract: logger.warning(f"SKIPPED: No {farming_token} found for {userAccount.address.bech32()}!") 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, '') @@ -265,7 +262,7 @@ def generateEnterFarmEvent(context: Context, userAccount: Account, farmContract: event_log.set_generic_event_data(event, userAccount.address.bech32(), farmContract) event_log.set_pre_event_data(context.network_provider.proxy) - tx_hash = farmContract.enterFarm(context.network_provider, userAccount, event, lockRewards, initial) + tx_hash = farmContract.enterFarm(context.network_provider, userAccount, event) context.observable.set_event(farmContract, userAccount, event, tx_hash) # post-event logging @@ -742,7 +739,7 @@ def generateRemoveLiquidityProxyEvent(context: Context): context.dexProxyContract.removeLiquidityProxy(context, userAccount, event) -def generateEnterFarmProxyEvent(context: Context, user_account: Account, farm_contract: FarmContract, lock_rewards: int = 0): +def generateEnterFarmProxyEvent(context: Context, user_account: Account, farm_contract: FarmContract): try: farm_token = farm_contract.proxyContract.farm_token @@ -764,7 +761,7 @@ def generateEnterFarmProxyEvent(context: Context, user_account: Account, farm_co 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) + context.dexProxyContract.enterFarmProxy(context, user_account, event, initial_enter_farm) except Exception as ex: logger.error("Exception encountered:", ex) diff --git a/tools/notebooks/boosted-farm.ipynb b/tools/notebooks/boosted-farm.ipynb index 03f3aa0..f404240 100644 --- a/tools/notebooks/boosted-farm.ipynb +++ b/tools/notebooks/boosted-farm.ipynb @@ -341,8 +341,7 @@ "event = EnterFarmEvent(farm_contract.farmingToken, 0, farming_tk_balance // 10,\n", " farm_contract.farmToken, farm_tk_nonce, farm_tk_balance)\n", "\n", - "initial_enter: bool = False if farm_tk_nonce else True\n", - "tx_hash = farm_contract.enterFarm(context.network_provider, user_account, event, initial=initial_enter)" + "tx_hash = farm_contract.enterFarm(context.network_provider, user_account, event)" ] }, { diff --git a/tools/notebooks/boosted-staking.ipynb b/tools/notebooks/boosted-staking.ipynb index 3cbead6..5ebbba6 100644 --- a/tools/notebooks/boosted-staking.ipynb +++ b/tools/notebooks/boosted-staking.ipynb @@ -224,7 +224,7 @@ "metadata": {}, "outputs": [], "source": [ - "event = EnterFarmEvent(staking_contract.farming_token, 0, 1000000000000000000, \"\", 0, 0)\n", + "event = EnterFarmEvent(staking_contract.farming_token, 0, 1000000000000000000, \"\", 0, 0, False, False)\n", "txhash = staking_contract.stake_farm(context.network_provider, depositer, event, True)" ] }, diff --git a/tools/runner.py b/tools/runner.py index 40fdb7a..1368908 100644 --- a/tools/runner.py +++ b/tools/runner.py @@ -138,6 +138,9 @@ def fetch_all_contracts_states(prefix: str): farm_v13_addresses = farm_runner.get_all_farm_v13_addresses() fetch_contracts_states(prefix, network_providers, farm_v13_addresses, farm_runner.FARMSV13_LABEL) - + # get farm v2 states + farm_v2_addresses = farm_runner.get_all_farm_v2_addresses() + fetch_contracts_states(prefix, network_providers, farm_v2_addresses, farm_runner.FARMSV2_LABEL) + if __name__ == '__main__': main(sys.argv[1:]) diff --git a/tools/runners/farm_runner.py b/tools/runners/farm_runner.py index 41d15c0..b5eba75 100644 --- a/tools/runners/farm_runner.py +++ b/tools/runners/farm_runner.py @@ -1,22 +1,32 @@ from argparse import ArgumentParser +from concurrent.futures import ThreadPoolExecutor import json import os +from time import sleep from typing import Any -from multiversx_sdk import Address +from events.event_generators import get_lp_from_metastake_token_attributes +from multiversx_sdk import Address, ContractCallBuilder, \ + DefaultTransactionBuildersConfiguration from config import GRAPHQL +from context import Context from contracts.contract_identities import FarmContractVersion from contracts.farm_contract import FarmContract +from contracts.simple_lock_contract import SimpleLockContract +from events.farm_events import EnterFarmEvent, ExitFarmEvent from tools.common import API, OUTPUT_FOLDER, OUTPUT_PAUSE_STATES, \ PROXY, fetch_and_save_contracts, fetch_new_and_compare_contract_states, \ - get_owner, get_saved_contract_addresses, get_user_continue, run_graphql_query, fetch_contracts_states -from tools.runners.common_runner import add_upgrade_all_command, add_upgrade_command -from utils.contract_data_fetchers import FarmContractDataFetcher + get_owner, get_saved_contract_addresses, get_user_continue, rule_of_three, run_graphql_query, fetch_contracts_states +from tools.runners import metastaking_runner +from tools.runners.common_runner import add_generate_transaction_command, add_upgrade_all_command, add_upgrade_command, fund_shadowfork_accounts, get_acounts_with_token, get_default_signature, read_accounts_from_json, sync_account_nonce +from tools.runners.metastaking_runner import generate_unstake_farm_tokens_transaction +from utils.contract_data_fetchers import FarmContractDataFetcher, SimpleLockContractDataFetcher from utils.contract_retrievers import retrieve_farm_by_address -from utils.utils_tx import NetworkProviders +from utils.utils_chain import Account, WrapperAddress, base64_to_hex, get_all_token_nonces_details_for_account, hex_to_string +from utils.utils_generic import split_to_chunks +from utils.utils_tx import ESDTToken, NetworkProviders import config - FARMSV13_LABEL = "farmsv13" FARMSV12_LABEL = "farmsv12" FARMSV2_LABEL = "farmsv2" @@ -46,8 +56,15 @@ def setup_parser(subparsers: ArgumentParser) -> ArgumentParser: command_parser = contract_group.add_parser('resume-all', help='resume all contracts command') command_parser.set_defaults(func=resume_farm_contracts) - return group_parser + transactions_parser = subgroup_parser.add_parser('generate-transactions', help='farms transactions commands') + + transactions_group = transactions_parser.add_subparsers() + add_generate_transaction_command(transactions_group, generate_unstake_farm_tokens_transaction, 'unstakeFarmTokens', 'exit farm tokens command') + add_generate_transaction_command(transactions_group, generate_stake_farm_tokens_transaction, 'stakeFarmTokens', 'enter farm tokens command') + add_generate_transaction_command(transactions_group, generate_exit_farm_locked_transaction, 'exitFarmLocked', 'exit simple lock command') + + return group_parser def fetch_and_save_farms_from_chain(): """Fetch and save farms from chain""" @@ -340,7 +357,7 @@ def upgrade_farmv2_contract(args: Any): return tx_hash = contract.contract_upgrade(dex_owner, network_providers.proxy, - config.FARM_V2_BYTECODE_PATH, + config.FARM_V3_BYTECODE_PATH, [], True) if not network_providers.check_complex_tx_status(tx_hash, f"upgrade farm v2 contract: {farm_address}"): @@ -467,7 +484,153 @@ def remove_penalty_farms(): return count += 1 +def generate_unstake_farm_tokens_transaction(args: Any): + context = Context() + farm_contracts = context.get_contracts(config.FARMS_V2) + + """Generate unstake farm tokens transaction""" + farm_address = args.address + exported_accounts_path = args.accounts_export + if not exported_accounts_path: + print("Missing required arguments!") + return + + if not farm_address and not args.all: + print("Missing required arguments!") + return + + default_account = Account(None, config.DEFAULT_OWNER) + network_providers = NetworkProviders(API, PROXY) + chain_id = network_providers.proxy.get_network_config().chain_id + config_tx = DefaultTransactionBuildersConfiguration(chain_id=chain_id) + signature = get_default_signature() + proxy = network_providers.proxy + default_account.sync_nonce(proxy) + + exported_accounts = read_accounts_from_json(exported_accounts_path) + fund_shadowfork_accounts(exported_accounts) + sleep(30) + + farm_contract = FarmContract.load_contract_by_address(farm_address, FarmContractVersion.V2Boosted) + accounts_with_token = get_acounts_with_token(exported_accounts, farm_contract.farmToken) + + transactions = [] + accounts_index = 1 + with ThreadPoolExecutor(max_workers=500) as executor: + exported_accounts = list(executor.map(sync_account_nonce, exported_accounts)) + + for account_with_token in accounts_with_token: + account = Account(account_with_token.address, config.DEFAULT_OWNER) + account.address = WrapperAddress.from_bech32(account_with_token.address) + account.sync_nonce(proxy) + tokens = [token for token in account_with_token.account_tokens_supply if token.token_name == farm_contract.farmToken ] + for token in tokens: + event = ExitFarmEvent(token.token_name, int(token.supply), int(token.token_nonce_hex, 16), '') + payment_tokens = [ESDTToken(token.token_name, int(token.token_nonce_hex, 16), int(token.supply)).to_token_payment()] + + if not account.address.is_smart_contract(): + builder = ContractCallBuilder( + config=config_tx, + contract=Address.new_from_bech32(farm_address), + function_name="exitFarm", + caller=account.address, + call_arguments=[1, 1, event.amount], + value=0, + gas_limit=75000000, + nonce=account.nonce, + esdt_transfers=payment_tokens + ) + tx = builder.build() + tx.signature = signature + + transactions.append(tx) + account.nonce += 1 + + index = exported_accounts.index(account_with_token) + exported_accounts[index].nonce = account.nonce + accounts_index += 1 + + transactions_chunks = split_to_chunks(transactions, 100) + for chunk in transactions_chunks: + network_providers.proxy.send_transactions(chunk) + +def generate_stake_farm_tokens_transaction(args: Any): + """Generate unstake farm tokens transaction""" + + farm_address = args.address + exported_accounts_path = args.accounts_export + + network_providers = NetworkProviders(API, PROXY) + proxy = network_providers.proxy + + if not farm_address or not exported_accounts_path: + print("Missing required arguments!") + return + + print(f"Generate stake farm tokens transaction for metastaking contract {farm_address}") + + network_providers = NetworkProviders(API, PROXY) + farm_contract = FarmContract.load_contract_by_address(farm_address, FarmContractVersion.V2Boosted) + + exported_accounts = read_accounts_from_json(exported_accounts_path) + accounts_with_token = get_acounts_with_token(exported_accounts, farm_contract.farmToken) + + for account_with_token in accounts_with_token: + account = Account(account_with_token.address, config.DEFAULT_OWNER) + account.address = WrapperAddress.from_bech32(account_with_token.address) + account.sync_nonce(network_providers.proxy) + tokens = [token for token in account_with_token.account_tokens_supply if token.token_name == farm_contract.farmToken] + for token in tokens: + event = EnterFarmEvent(token.token_name, int(token.supply), int(token.token_nonce_hex, 16), '') + farm_contract.enterFarm( + proxy, + account, + event + ) + +def generate_exit_farm_locked_transaction(args: Any): + """Generate exit farm locked tokens transaction""" + farm_address = args.address + exported_accounts_path = args.accounts_export + + if not farm_address or not exported_accounts_path: + print("Missing required arguments!") + return + + print(f"Generate unstake farm tokens transaction for contract {farm_address}") + network_providers = NetworkProviders(API, PROXY) + proxy = network_providers.proxy + + locked_token_hex = SimpleLockContractDataFetcher(Address(farm_address), + proxy.url).get_data("getLockedTokenId") + locked_lp_token_hex = SimpleLockContractDataFetcher(Address(farm_address), + proxy.url).get_data("getLpProxyTokenId") + locked_farm_token_hex = SimpleLockContractDataFetcher(Address(farm_address), + proxy.url).get_data("getFarmProxyTokenId") + LOCKED_TOKEN = hex_to_string(locked_token_hex) + LOCKED_LP_TOKEN = hex_to_string(locked_lp_token_hex) + LOCKED_FARM_TOKEN = hex_to_string(locked_farm_token_hex) + + + farm_contract = SimpleLockContract(LOCKED_TOKEN, LOCKED_LP_TOKEN, LOCKED_FARM_TOKEN, farm_address) + + exported_accounts = read_accounts_from_json(exported_accounts_path) + accounts_with_token = get_acounts_with_token(exported_accounts, farm_contract.farm_proxy_token) + + for account_with_token in accounts_with_token: + account = Account(account_with_token.address, config.DEFAULT_OWNER) + account.address = WrapperAddress.from_bech32(account_with_token.address) + account.sync_nonce(network_providers.proxy) + tokens = [token for token in account_with_token.account_tokens_supply if token.token_name == farm_contract.farm_proxy_token ] + + for token in tokens: + if(account.address.to_bech32() != ""): + farm_contract.exit_farm_locked_token( + account, + proxy, + [[ESDTToken(farm_contract.farm_proxy_token, int(token.token_nonce_hex, 16), int(token.supply))]] + ) def get_farm_addresses_from_chain(version: str) -> list: """ diff --git a/tools/runners/metastaking_runner.py b/tools/runners/metastaking_runner.py index 16ab14a..777eb45 100644 --- a/tools/runners/metastaking_runner.py +++ b/tools/runners/metastaking_runner.py @@ -44,6 +44,7 @@ def setup_parser(subparsers: ArgumentParser) -> ArgumentParser: add_generate_transaction_command(transactions_group, generate_unstake_farm_tokens_transaction, 'unstakeFarmTokens', 'unstake farm tokens command') add_generate_transaction_command(transactions_group, generate_stake_farm_tokens_transaction, 'stakeFarmTokens', 'stake farm tokens command') + return group_parser def fetch_and_save_metastakings_from_chain(): """Fetch metastaking contracts from chain""" diff --git a/tools/runners/proxy_runner.py b/tools/runners/proxy_runner.py index 8003b71..266f35e 100644 --- a/tools/runners/proxy_runner.py +++ b/tools/runners/proxy_runner.py @@ -1,9 +1,11 @@ from argparse import ArgumentParser from context import Context -from contracts.contract_identities import ProxyContractVersion -from contracts.dex_proxy_contract import DexProxyContract +from contracts.contract_identities import FarmContractVersion, ProxyContractVersion +from contracts.dex_proxy_contract import DexProxyContract, DexProxyExitFarmEvent +from contracts.farm_contract import FarmContract from tools.common import API, PROXY, fetch_contracts_states, fetch_new_and_compare_contract_states, get_owner, get_user_continue -from tools.runners.common_runner import add_upgrade_command +from tools.runners.common_runner import add_generate_transaction_command, add_upgrade_command, get_acounts_with_token, read_accounts_from_json +from utils.utils_chain import Account, WrapperAddress, get_token_details_for_address from utils.utils_tx import NetworkProviders import config @@ -18,6 +20,10 @@ def setup_parser(subparsers: ArgumentParser) -> ArgumentParser: contract_group = contract_parser.add_subparsers() add_upgrade_command(contract_group, upgrade_proxy_dex_contracts) + transaction_parser = subgroup_parser.add_parser('generate-transactions', help='proxy dex transaction commands') + transactions_group = transaction_parser.add_subparsers() + add_generate_transaction_command(transactions_group, exit_proxy, 'exitFarmProxy', 'exit farm proxy command') + return group_parser @@ -51,3 +57,28 @@ def upgrade_proxy_dex_contracts(compare_states: bool = False): if compare_states: fetch_new_and_compare_contract_states("proxy_dex", proxy_dex_contract.address, context.network_provider) + +def exit_proxy(args: any): + farm_address = args.address + exported_accounts_path = args.accounts_export + + context = Context() + farm_contract = DexProxyContract.load_contract_by_address(farm_address) + network_providers = NetworkProviders(API, PROXY) + + farm_contract: FarmContract + farm_contract = context.get_contracts(config.FARMS_V2)[0] + proxy_contract: DexProxyContract + proxy_contract = context.get_contracts(config.PROXIES_V2)[0] + + exported_accounts = read_accounts_from_json(exported_accounts_path) + accounts_with_token = get_acounts_with_token(exported_accounts, proxy_contract.proxy_farm_token) + + for account_with_token in accounts_with_token: + account = Account(account_with_token.address, config.DEFAULT_OWNER) + account.address = WrapperAddress.from_bech32(account_with_token.address) + account.sync_nonce(network_providers.proxy) + tokens = [token for token in account_with_token.account_tokens_supply if token.token_name == proxy_contract.proxy_farm_token ] + for token in tokens: + event = DexProxyExitFarmEvent(farm_contract, proxy_contract.proxy_farm_token, int(token.token_nonce_hex,16), int(token.supply) ) + proxy_contract.exit_farm_proxy(account, context.network_provider.proxy, event) \ No newline at end of file