From ff6365c2e2d2239dd3e1c3802f8148ba5473d885 Mon Sep 17 00:00:00 2001 From: Cesar Date: Tue, 9 Apr 2024 19:55:26 +0200 Subject: [PATCH 1/5] chore: build script init --- autotx/agents/ExampleAgent.py | 2 +- autotx/agents/ResearchTokensAgent.py | 38 +++++++-------- autotx/agents/SendTokensAgent.py | 8 ++-- autotx/agents/SwapTokensAgent.py | 4 +- autotx/autotx_agent.py | 4 +- autotx/cli.py | 26 +++++------ autotx/patch.py | 46 ------------------- .../token/test_tokens_regression.py | 2 - autotx/tests/agents/test_analyze_prompt.py | 3 -- autotx/tests/agents/token/send/test_send.py | 2 - autotx/tests/agents/token/test_swap.py | 2 - .../tests/agents/token/test_swap_and_send.py | 2 - autotx/utils/configuration.py | 6 +-- autotx/utils/ethereum/SafeManager.py | 32 ++++++------- autotx/utils/ethereum/agent_account.py | 15 ++++-- autotx/utils/ethereum/cache.py | 9 ++-- autotx/utils/ethereum/eth_address.py | 3 +- .../utils/ethereum/helpers/get_dev_account.py | 3 +- autotx/utils/ethereum/send_eth.py | 4 +- autotx/utils/io_silent.py | 2 +- pyproject.toml | 23 ++++++++++ 21 files changed, 105 insertions(+), 131 deletions(-) delete mode 100644 autotx/patch.py diff --git a/autotx/agents/ExampleAgent.py b/autotx/agents/ExampleAgent.py index 6ad6fe44..2b35aaba 100644 --- a/autotx/agents/ExampleAgent.py +++ b/autotx/agents/ExampleAgent.py @@ -17,7 +17,7 @@ class ExampleTool(AutoTxTool): """ ) - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[float, str], str]: def run( amount: Annotated[float, "Amount of something."], receiver: Annotated[str, "The receiver of something."] diff --git a/autotx/agents/ResearchTokensAgent.py b/autotx/agents/ResearchTokensAgent.py index 9e2d6be4..2d088d4d 100644 --- a/autotx/agents/ResearchTokensAgent.py +++ b/autotx/agents/ResearchTokensAgent.py @@ -1,7 +1,7 @@ import json from textwrap import dedent -from typing import Annotated, Callable, List, Optional, Union +from typing import Annotated, Any, Callable, Optional, Type, Union, cast from web3 import Web3 from autotx.AutoTx import AutoTx from gnosis.eth import EthereumNetworkNotSupported as ChainIdNotSupported @@ -21,14 +21,14 @@ ChainId.GNOSIS: "xdai", } -def get_coingecko(): +def get_coingecko() -> Any: return CoinGeckoDemoClient(api_key=COINGECKO_API_KEY) def get_tokens_and_filter_per_network( network_name: str, -) -> dict[str, Union[str, dict[str, str]]]: - network = ChainId[network_name] +) -> list[dict[str, Union[str, dict[str, str]]]]: + network = ChainId[network_name] # type: ignore coingecko_network_key = COINGECKO_NETWORKS_TO_SUPPORTED_NETWORKS_MAP.get(network) if coingecko_network_key == None: raise ChainIdNotSupported(f"Network {network_name} not supported") @@ -40,11 +40,12 @@ def get_tokens_and_filter_per_network( if coingecko_network_key in token["platforms"] ] -def filter_token_list_by_network(tokens: list[dict[str, str]], network_name: str): +def filter_token_list_by_network(tokens: list[dict[str, str]], network_name: str) -> list[dict[str, Union[str, dict[str, str]]]]: tokens_in_category_map = {category["id"]: category for category in tokens} filtered_tokens_map = { token["id"]: token for token in get_tokens_and_filter_per_network(network_name) } + return [ { **tokens_in_category_map[token["id"]], @@ -55,16 +56,17 @@ def filter_token_list_by_network(tokens: list[dict[str, str]], network_name: str ] def add_tokens_address_if_not_in_registry( - tokens_in_category: list[dict[str, str]], - tokens: list[str, str], + tokens_in_category: list[dict[str, Union[str, dict[str, str]]]], + tokens: dict[str, str], current_network: str, -): +) -> None: for token_with_address in tokens_in_category: - token_symbol = token_with_address["symbol"].lower() + token_symbol = cast(str, token_with_address["symbol"]).lower() token_not_added = token_symbol not in tokens if token_not_added: + platforms = cast(dict[str, str], token_with_address["platforms"]) tokens[token_symbol] = Web3.to_checksum_address( - token_with_address["platforms"][current_network] + platforms[current_network] ) class GetTokenInformationTool(AutoTxTool): @@ -75,7 +77,7 @@ class GetTokenInformationTool(AutoTxTool): """ ) - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[str], str]: def run( token_id: Annotated[str, "ID of token"] ) -> str: @@ -130,7 +132,7 @@ class SearchTokenTool(AutoTxTool): name: str = "search_token" description: str = "Search token based on its symbol. It will return the ID of tokens with the largest market cap" - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[str, bool], str]: def run( token_symbol: Annotated[str, "Symbol of token to search"], retrieve_duplicate: Annotated[bool, "Set to true to retrieve all instances of tokens sharing the same symbol, indicating potential duplicates. By default, it is False, meaning only a single, most relevant token is retrieved unless duplication is explicitly requested."] @@ -151,7 +153,7 @@ class GetAvailableCategoriesTool(AutoTxTool): name: str = "get_available_categories" description: str = "Retrieve all available category ids of tokens" - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[], str]: def run() -> str: print("Fetching available token categories") @@ -164,7 +166,7 @@ class GetTokensBasedOnCategoryTool(AutoTxTool): name: str = "get_tokens_based_on_category" description: str = "Retrieve all tokens with their respective information (symbol, market cap, price change percentages and total traded volume in the last 24 hours) from a given category" - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[str, str, int, str, Optional[str]], str]: def run( category: Annotated[str, "Category to retrieve tokens"], sort_by: Annotated[str, "Sort tokens by field. It can be: 'volume_desc' | 'volume_asc' | 'market_cap_desc' | 'market_cap_asc'. 'market_cap_desc' is the default"], @@ -197,11 +199,11 @@ def run( autotx.network.chain_id ) asked_network = COINGECKO_NETWORKS_TO_SUPPORTED_NETWORKS_MAP.get( - ChainId[network_name] + ChainId[network_name] # type: ignore ) if current_network == asked_network: add_tokens_address_if_not_in_registry( - tokens_in_category, autotx.network.tokens, current_network + tokens_in_category, autotx.network.tokens, cast(str, current_network) ) interval = ( @@ -228,10 +230,10 @@ class GetExchangesWhereTokenCanBeTradedTool(AutoTxTool): name: str = "get_exchanges_where_token_can_be_traded" description: str = "Retrieve exchanges where token can be traded" - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[str], list[str]]: def run( token_id: Annotated[str, "ID of token"] - ) -> List[str]: + ) -> list[str]: print(f"Fetching exchanges where token ({token_id}) can be traded") tickers = get_coingecko().coins.get_tickers(id=token_id)["tickers"] diff --git a/autotx/agents/SendTokensAgent.py b/autotx/agents/SendTokensAgent.py index bf7cf96d..742b1f75 100644 --- a/autotx/agents/SendTokensAgent.py +++ b/autotx/agents/SendTokensAgent.py @@ -21,7 +21,7 @@ class TransferETHTool(AutoTxTool): """ ) - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[float, str], str]: def run( amount: Annotated[float, "Amount given by the user to transfer. The function will take care of converting the amount to needed decimals."], receiver: Annotated[str, "The receiver's address or ENS domain"] @@ -51,7 +51,7 @@ class TransferERC20Tool(AutoTxTool): """ ) - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[float, str, str], str]: def run( amount: Annotated[float, "Amount given by the user to transfer. The function will take care of converting the amount to needed decimals."], receiver: Annotated[str, "The receiver's address or ENS domain"], @@ -82,7 +82,7 @@ class GetETHBalanceTool(AutoTxTool): """ ) - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[str], float]: def run( owner: Annotated[str, "The owner's address or ENS domain"] ) -> float: @@ -106,7 +106,7 @@ class GetERC20BalanceTool(AutoTxTool): """ ) - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[str, str], float]: def run( token: Annotated[str, "Token symbol of erc20"], owner: Annotated[str, "The token owner's address or ENS domain"] diff --git a/autotx/agents/SwapTokensAgent.py b/autotx/agents/SwapTokensAgent.py index c2550987..ff8a4545 100644 --- a/autotx/agents/SwapTokensAgent.py +++ b/autotx/agents/SwapTokensAgent.py @@ -7,7 +7,7 @@ from autotx.utils.ethereum.uniswap.swap import SUPPORTED_UNISWAP_V3_NETWORKS, build_swap_transaction from gnosis.eth import EthereumNetworkNotSupported as ChainIdNotSupported -def get_tokens_address(token_in: str, token_out: str, network_info: NetworkInfo): +def get_tokens_address(token_in: str, token_out: str, network_info: NetworkInfo) -> tuple[str, str]: token_in = token_in.lower() token_out = token_out.lower() @@ -32,7 +32,7 @@ class SwapTool(AutoTxTool): """ ) - def build_tool(self, autotx: AutoTx) -> Callable: + def build_tool(self, autotx: AutoTx) -> Callable[[str, str], str]: def run( token_to_sell: Annotated[str, "Token to sell. E.g. '10 USDC' or just 'USDC'"], token_to_buy: Annotated[str, "Token to buy. E.g. '10 USDC' or just 'USDC'"], diff --git a/autotx/autotx_agent.py b/autotx/autotx_agent.py index debcb2bd..2995d14b 100644 --- a/autotx/autotx_agent.py +++ b/autotx/autotx_agent.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, Optional, TYPE_CHECKING +from typing import Any, Callable, Dict, Optional, TYPE_CHECKING, Self import autogen if TYPE_CHECKING: from autotx.autotx_tool import AutoTxTool @@ -10,7 +10,7 @@ class AutoTxAgent(): tools: list['AutoTxTool'] tool_descriptions: list[str] - def __init__(self): + def __init__(self) -> Self: self.tool_descriptions = [ f"{tool.name}: {tool.description}" for tool in self.tools ] diff --git a/autotx/cli.py b/autotx/cli.py index 6d2acb2e..100db43b 100644 --- a/autotx/cli.py +++ b/autotx/cli.py @@ -1,34 +1,34 @@ +from typing import cast from dotenv import load_dotenv +import click + +load_dotenv() from autotx.agents.ResearchTokensAgent import ResearchTokensAgent from autotx.agents.SendTokensAgent import SendTokensAgent from autotx.agents.SwapTokensAgent import SwapTokensAgent from autotx.utils.ethereum import get_eth_balance -load_dotenv() -import click from autotx.utils.constants import COINGECKO_API_KEY, OPENAI_API_KEY, OPENAI_MODEL_NAME from autotx.utils.ethereum.networks import NetworkInfo from autotx.utils.ethereum.helpers.get_dev_account import get_dev_account from autotx.AutoTx import AutoTx -from autotx.patch import patch_langchain from autotx.utils.ethereum.agent_account import get_agent_account, create_agent_account, delete_agent_account from autotx.utils.ethereum.SafeManager import SafeManager from autotx.utils.ethereum.send_eth import send_eth from autotx.utils.ethereum.helpers.show_address_balances import show_address_balances from autotx.utils.configuration import get_configuration -patch_langchain() @click.group() -def main(): +def main() -> None: pass @main.command() @click.argument('prompt', required=False) @click.option("-n", "--non-interactive", is_flag=True, help="Non-interactive mode (will not expect further user input or approval)") @click.option("-v", "--verbose", is_flag=True, help="Verbose mode") -def run(prompt: str | None, non_interactive: bool, verbose: bool): +def run(prompt: str | None, non_interactive: bool, verbose: bool) -> None: if prompt == None: prompt = click.prompt("What do you want to do?") @@ -85,7 +85,7 @@ def run(prompt: str | None, non_interactive: bool, verbose: bool): None, get_llm_config=get_llm_config ) - autotx.run(prompt, non_interactive, silent=not verbose) + autotx.run(cast(str, prompt), non_interactive, silent=not verbose) if not smart_account_addr: print("=" * 50) @@ -94,18 +94,16 @@ def run(prompt: str | None, non_interactive: bool, verbose: bool): print("=" * 50) @main.group() -def agent(): +def agent() -> None: pass @agent.command(name="address") -def agent_address(): +def agent_address() -> None: print_agent_address() -def print_agent_address(): - acc = get_agent_account() - - if acc == None: - acc = create_agent_account() +def print_agent_address() -> None: + agent_account = get_agent_account() + acc = agent_account if agent_account else create_agent_account() print(f"Agent address: {acc.address}") diff --git a/autotx/patch.py b/autotx/patch.py deleted file mode 100644 index 54c3d94f..00000000 --- a/autotx/patch.py +++ /dev/null @@ -1,46 +0,0 @@ -def patch_langchain(): - default_repr = __builtins__["repr"] - - def safe_repr(obj, seen=None): - if seen is None: - seen = set() - - obj_id = id(obj) - if obj_id in seen: - raise RecursionError("Found a recursive reference") - - seen.add(obj_id) - result = [] - - if isinstance(obj, dict): - result.append("{") - for key, value in obj.items(): - result.append(f"{safe_repr(key, seen=seen)}: {safe_repr(value, seen=seen)}, ") - result.append("}") - elif isinstance(obj, list): - result.append("[") - result.extend(f"{safe_repr(item, seen=seen)}, " for item in obj) - result.append("]") - elif isinstance(obj, tuple): - result.append("(") - result.extend(f"{safe_repr(item, seen=seen)}, " for item in obj) - result.append(")") - elif isinstance(obj, set): - result.append("{") - result.extend(f"{safe_repr(item, seen=seen)}, " for item in obj) - result.append("}") - else: - # Handle general class instances - if hasattr(obj, "__dict__"): - result.append(f"<{obj.__class__.__name__}: ") - for key, value in obj.__dict__.items(): - result.append(f"{key}={safe_repr(value, seen=seen)}, ") - result.append(">") - else: - # Fallback for objects without __dict__ or non-container types - result.append(default_repr(obj)) - - seen.remove(obj_id) - return ''.join(result) - - __builtins__["repr"] = safe_repr diff --git a/autotx/tests/agents/regression/token/test_tokens_regression.py b/autotx/tests/agents/regression/token/test_tokens_regression.py index 9e8b2c1b..546019f8 100644 --- a/autotx/tests/agents/regression/token/test_tokens_regression.py +++ b/autotx/tests/agents/regression/token/test_tokens_regression.py @@ -1,10 +1,8 @@ import pytest -from autotx.patch import patch_langchain from autotx.utils.ethereum import get_erc20_balance, load_w3 from autotx.utils.ethereum.networks import NetworkInfo from autotx.utils.ethereum.eth_address import ETHAddress -patch_langchain() @pytest.mark.skip() def test_auto_tx_send_erc20(configuration, auto_tx, usdc, test_accounts): diff --git a/autotx/tests/agents/test_analyze_prompt.py b/autotx/tests/agents/test_analyze_prompt.py index b5d93ed5..8599a51e 100644 --- a/autotx/tests/agents/test_analyze_prompt.py +++ b/autotx/tests/agents/test_analyze_prompt.py @@ -1,9 +1,6 @@ -from autotx.patch import patch_langchain from autotx.utils.agent.build_goal import DefineGoalResponse, analyze_user_prompt from autotx.utils.ethereum.helpers.get_dev_account import get_dev_account -patch_langchain() - def test_with_missing_amount(auto_tx): response = analyze_prompt("Send ETH to 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1", auto_tx) assert response.type == "missing_info" diff --git a/autotx/tests/agents/token/send/test_send.py b/autotx/tests/agents/token/send/test_send.py index 17d1472e..e5cd7f05 100644 --- a/autotx/tests/agents/token/send/test_send.py +++ b/autotx/tests/agents/token/send/test_send.py @@ -1,8 +1,6 @@ -from autotx.patch import patch_langchain from autotx.utils.ethereum import get_erc20_balance from autotx.utils.ethereum.get_eth_balance import get_eth_balance -patch_langchain() def test_auto_tx_send_eth(configuration, auto_tx, test_accounts): (_, _, client, _) = configuration diff --git a/autotx/tests/agents/token/test_swap.py b/autotx/tests/agents/token/test_swap.py index ed84edbd..344da7ed 100644 --- a/autotx/tests/agents/token/test_swap.py +++ b/autotx/tests/agents/token/test_swap.py @@ -1,9 +1,7 @@ -from autotx.patch import patch_langchain from autotx.utils.ethereum import load_w3 from autotx.utils.ethereum.networks import NetworkInfo from autotx.utils.ethereum.eth_address import ETHAddress -patch_langchain() def test_auto_tx_swap_with_non_default_token(configuration, auto_tx): (_, _, _, manager) = configuration diff --git a/autotx/tests/agents/token/test_swap_and_send.py b/autotx/tests/agents/token/test_swap_and_send.py index a523e6bb..bf8f18ce 100644 --- a/autotx/tests/agents/token/test_swap_and_send.py +++ b/autotx/tests/agents/token/test_swap_and_send.py @@ -1,9 +1,7 @@ -from autotx.patch import patch_langchain from autotx.utils.ethereum import get_erc20_balance, load_w3 from autotx.utils.ethereum.networks import NetworkInfo from autotx.utils.ethereum.eth_address import ETHAddress -patch_langchain() def test_auto_tx_swap_and_send_simple(configuration, auto_tx): (_, _, client, manager) = configuration diff --git a/autotx/utils/configuration.py b/autotx/utils/configuration.py index 81d668c1..ebeabd83 100644 --- a/autotx/utils/configuration.py +++ b/autotx/utils/configuration.py @@ -3,7 +3,7 @@ from autotx.get_env_vars import get_env_vars from gnosis.eth import EthereumClient from eth_typing import URI -from eth_account import Account +from eth_account.signers.local import LocalAccount from web3 import Web3, HTTPProvider from autotx.utils.ethereum.agent_account import get_or_create_agent_account @@ -12,7 +12,7 @@ smart_account_addr = get_env_vars() -def get_configuration(): +def get_configuration() -> tuple[ETHAddress, LocalAccount, EthereumClient]: w3 = Web3(HTTPProvider(FORK_RPC_URL)) for i in range(16): if w3.is_connected(): @@ -22,7 +22,7 @@ def get_configuration(): sleep(0.5) client = EthereumClient(URI(FORK_RPC_URL)) - agent: Account = get_or_create_agent_account() + agent = get_or_create_agent_account() smart_account = ETHAddress(smart_account_addr, client.w3) if smart_account_addr else None diff --git a/autotx/utils/ethereum/SafeManager.py b/autotx/utils/ethereum/SafeManager.py index 2460f97f..48f884d9 100644 --- a/autotx/utils/ethereum/SafeManager.py +++ b/autotx/utils/ethereum/SafeManager.py @@ -1,9 +1,18 @@ -from logging import getLogger import sys from typing import Optional import re +import logging from web3 import Web3 +from gnosis.eth import EthereumClient +from gnosis.eth.constants import NULL_ADDRESS +from gnosis.eth.multicall import Multicall +from gnosis.safe import Safe, SafeOperation, SafeTx +from gnosis.safe.multi_send import MultiSend, MultiSendOperation, MultiSendTx +from web3.types import TxParams +from gnosis.safe.api import TransactionServiceApi +from gnosis.safe.api.base_api import SafeAPIException +from eth_account.signers.local import LocalAccount from autotx.utils.ethereum.get_eth_balance import get_eth_balance from autotx.utils.PreparedTx import PreparedTx @@ -15,16 +24,7 @@ from .deploy_multicall import deploy_multicall from .get_erc20_balance import get_erc20_balance from .constants import MULTI_SEND_ADDRESS, GAS_PRICE_MULTIPLIER -from eth_account import Account -from gnosis.eth import EthereumClient -from gnosis.eth.constants import NULL_ADDRESS -from gnosis.eth.multicall import Multicall -from gnosis.safe import Safe, SafeOperation, SafeTx -from gnosis.safe.multi_send import MultiSend, MultiSendOperation, MultiSendTx -from web3.types import TxParams -from gnosis.safe.api import TransactionServiceApi -from gnosis.safe.api.base_api import SafeAPIException -import logging + # Disable safe warning logs logging.getLogger('gnosis.safe.safe').setLevel(logging.CRITICAL + 1) @@ -36,13 +36,13 @@ class SafeManager: multisend: MultiSend | None = None safe_nonce: int | None = None gas_multiplier: float | None = GAS_PRICE_MULTIPLIER - dev_account: Account | None = None + dev_account: LocalAccount | None = None address: ETHAddress def __init__( self, client: EthereumClient, - agent: Account, + agent: LocalAccount, safe: Safe ): self.client = client @@ -58,8 +58,8 @@ def __init__( def deploy_safe( cls, client: EthereumClient, - dev_account: Account, - agent: Account, + dev_account: LocalAccount, + agent: LocalAccount, owners: list[str], threshold: int ) -> 'SafeManager': @@ -80,7 +80,7 @@ def connect( cls, client: EthereumClient, safe_address: ETHAddress, - agent: Account, + agent: LocalAccount, ) -> 'SafeManager': safe = Safe(Web3.to_checksum_address(safe_address.hex), client) diff --git a/autotx/utils/ethereum/agent_account.py b/autotx/utils/ethereum/agent_account.py index 54387701..c0dc3aa8 100644 --- a/autotx/utils/ethereum/agent_account.py +++ b/autotx/utils/ethereum/agent_account.py @@ -1,9 +1,11 @@ from eth_account import Account +from eth_account.signers.local import LocalAccount from autotx.utils.ethereum.cache import cache AGENT_PK_FILE_NAME = "agent.pk.txt" -def get_agent_account() -> Account | None: + +def get_agent_account() -> LocalAccount | None: try: account = cache.read(AGENT_PK_FILE_NAME) if account: @@ -13,17 +15,20 @@ def get_agent_account() -> Account | None: except Exception as e: print(f"An error occurred while reading the agent account: {e}") raise - -def create_agent_account() -> Account: + + +def create_agent_account() -> LocalAccount: agent_account: str = Account.create().key.hex() cache.write(AGENT_PK_FILE_NAME, agent_account) return Account.from_key(agent_account) -def get_or_create_agent_account() -> Account: + +def get_or_create_agent_account() -> LocalAccount: agent = get_agent_account() if agent: return agent return create_agent_account() + def delete_agent_account(): - cache.remove(AGENT_PK_FILE_NAME) \ No newline at end of file + cache.remove(AGENT_PK_FILE_NAME) diff --git a/autotx/utils/ethereum/cache.py b/autotx/utils/ethereum/cache.py index a5541547..f21b78c0 100644 --- a/autotx/utils/ethereum/cache.py +++ b/autotx/utils/ethereum/cache.py @@ -2,10 +2,11 @@ from typing import Optional class Cache: - folder: str = None + folder: str = ".cache" def __init__(self, folder: Optional[str] = ".cache"): - self.folder = folder + if folder: + self.folder = folder os.makedirs(folder, exist_ok=True) def read(self, file_name: str) -> str | None: @@ -19,11 +20,11 @@ def read(self, file_name: str) -> str | None: print(f"An error occurred while reading {file_name}: {e}") raise - def write(self, file_name: str, data: str): + def write(self, file_name: str, data: str) -> None: with open(os.path.join(self.folder, file_name), "w") as f: f.write(data) - def remove(self, file_name: str): + def remove(self, file_name: str) -> None: try: os.remove(os.path.join(self.folder, file_name)) except FileNotFoundError: diff --git a/autotx/utils/ethereum/eth_address.py b/autotx/utils/ethereum/eth_address.py index 066429f6..5167e992 100644 --- a/autotx/utils/ethereum/eth_address.py +++ b/autotx/utils/ethereum/eth_address.py @@ -1,7 +1,8 @@ +from eth_typing import ChecksumAddress from web3 import Web3 class ETHAddress: - hex: str + hex: ChecksumAddress ens_domain: str | None def __init__(self, hex_or_ens: str, web3: Web3): diff --git a/autotx/utils/ethereum/helpers/get_dev_account.py b/autotx/utils/ethereum/helpers/get_dev_account.py index 73a24008..657066e2 100644 --- a/autotx/utils/ethereum/helpers/get_dev_account.py +++ b/autotx/utils/ethereum/helpers/get_dev_account.py @@ -1,4 +1,5 @@ from eth_account import Account +from eth_account.signers.local import LocalAccount -def get_dev_account() -> Account: +def get_dev_account() -> LocalAccount: return Account.from_key("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") \ No newline at end of file diff --git a/autotx/utils/ethereum/send_eth.py b/autotx/utils/ethereum/send_eth.py index ee07103b..cd53aaaa 100644 --- a/autotx/utils/ethereum/send_eth.py +++ b/autotx/utils/ethereum/send_eth.py @@ -1,4 +1,4 @@ -from eth_account import Account +from eth_account.signers.local import LocalAccount from eth_typing import Address from web3 import Web3 from web3.types import TxReceipt @@ -8,7 +8,7 @@ from .constants import GAS_PRICE_MULTIPLIER -def send_eth(account: Account, to: ETHAddress, value: float, web3: Web3) -> tuple[str, TxReceipt]: +def send_eth(account: LocalAccount, to: ETHAddress, value: float, web3: Web3) -> tuple[str, TxReceipt]: bytes_address: Address = Address(bytes.fromhex(account.address[2:])) nonce = web3.eth.get_transaction_count(bytes_address) diff --git a/autotx/utils/io_silent.py b/autotx/utils/io_silent.py index aa83d75d..327a8ac1 100644 --- a/autotx/utils/io_silent.py +++ b/autotx/utils/io_silent.py @@ -1,7 +1,7 @@ from typing import Any from autogen.io import IOConsole -class IOSilent(IOConsole): +class IOSilent(IOConsole): # type: ignore """A console input/output stream.""" def print(self, *objects: Any, sep: str = " ", end: str = "\n", flush: bool = False) -> None: diff --git a/pyproject.toml b/pyproject.toml index d4946479..73969b4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,3 +34,26 @@ load_tokens = "autotx.load_tokens:run" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + +[tool.mypy] + +strict = true +ignore_missing_imports = true +install_types = true +non_interactive = true + +plugins = [ + "pydantic.mypy" +] + +# from https://blog.wolt.com/engineering/2021/09/30/professional-grade-mypy-configuration/ +disallow_untyped_defs = true +no_implicit_optional = true +check_untyped_defs = true +warn_return_any = true +show_error_codes = true +warn_unused_ignores = true + +disallow_incomplete_defs = true +disallow_untyped_decorators = true +disallow_any_unimported = true \ No newline at end of file From 634f52fda6a2397bfeb77272ae2b868c03350838 Mon Sep 17 00:00:00 2001 From: Cesar Date: Tue, 9 Apr 2024 22:09:39 +0200 Subject: [PATCH 2/5] chore: safe logic green in mypy --- autotx/AutoTx.py | 8 +-- autotx/agents/ResearchTokensAgent.py | 2 +- autotx/autotx_agent.py | 6 +-- autotx/autotx_tool.py | 6 +-- autotx/cli.py | 6 +-- autotx/utils/ethereum/SafeManager.py | 53 ++++++++++++------- autotx/utils/ethereum/agent_account.py | 10 ++-- autotx/utils/ethereum/cache.py | 6 +-- autotx/utils/ethereum/cached_safe_address.py | 4 +- autotx/utils/ethereum/config.py | 16 ------ autotx/utils/ethereum/deploy_multicall.py | 6 +-- .../ethereum/deploy_safe_with_create2.py | 22 ++++---- autotx/utils/ethereum/eth_address.py | 4 +- .../utils/ethereum/helpers/get_dev_account.py | 9 +++- .../ethereum/helpers/show_address_balances.py | 20 +++---- autotx/utils/ethereum/is_valid_safe.py | 3 +- autotx/utils/ethereum/send_tx.py | 6 +-- autotx/utils/ethereum/transfer_erc20.py | 7 +-- autotx/utils/io_silent.py | 2 +- pyproject.toml | 8 ++- 20 files changed, 111 insertions(+), 93 deletions(-) delete mode 100644 autotx/utils/ethereum/config.py diff --git a/autotx/AutoTx.py b/autotx/AutoTx.py index 1c58e1fa..04be048c 100644 --- a/autotx/AutoTx.py +++ b/autotx/AutoTx.py @@ -4,13 +4,14 @@ from autogen import UserProxyAgent, AssistantAgent, GroupChat, GroupChatManager from termcolor import cprint from typing import Optional -from autogen.io import IOStream +from autogen.io import IOStream, IOConsole from autotx.autotx_agent import AutoTxAgent from autotx.utils.PreparedTx import PreparedTx from autotx.utils.agent.build_goal import build_goal from autotx.utils.ethereum import SafeManager from autotx.utils.ethereum.networks import NetworkInfo -from autotx.utils.io_silent import IOConsole, IOSilent +from autotx.utils.io_silent import IOSilent + @dataclass(kw_only=True) class Config: @@ -22,7 +23,6 @@ class AutoTx: transactions: list[PreparedTx] = [] network: NetworkInfo get_llm_config: Callable[[], Optional[Dict[str, Any]]] - user_proxy: UserProxyAgent agents: list[AutoTxAgent] def __init__( @@ -36,7 +36,7 @@ def __init__( self.config = config self.agents = agents - def run(self, prompt: str, non_interactive: bool, silent: bool = False): + def run(self, prompt: str, non_interactive: bool, silent: bool = False) -> None: print("Running AutoTx with the following prompt: ", prompt) user_proxy = UserProxyAgent( diff --git a/autotx/agents/ResearchTokensAgent.py b/autotx/agents/ResearchTokensAgent.py index 2d088d4d..b35b8ea7 100644 --- a/autotx/agents/ResearchTokensAgent.py +++ b/autotx/agents/ResearchTokensAgent.py @@ -21,7 +21,7 @@ ChainId.GNOSIS: "xdai", } -def get_coingecko() -> Any: +def get_coingecko() -> CoinGeckoDemoClient: return CoinGeckoDemoClient(api_key=COINGECKO_API_KEY) diff --git a/autotx/autotx_agent.py b/autotx/autotx_agent.py index 2995d14b..01db1650 100644 --- a/autotx/autotx_agent.py +++ b/autotx/autotx_agent.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, Optional, TYPE_CHECKING, Self +from typing import Any, Callable, Dict, Optional, TYPE_CHECKING import autogen if TYPE_CHECKING: from autotx.autotx_tool import AutoTxTool @@ -10,7 +10,7 @@ class AutoTxAgent(): tools: list['AutoTxTool'] tool_descriptions: list[str] - def __init__(self) -> Self: + def __init__(self) -> None: self.tool_descriptions = [ f"{tool.name}: {tool.description}" for tool in self.tools ] @@ -20,7 +20,7 @@ def build_autogen_agent(self, autotx: 'AutoTx', user_proxy: autogen.UserProxyAge if isinstance(self.system_message, str): system_message = self.system_message else: - get_system_message = self.system_message.__func__ + get_system_message = self.system_message.__func__ # type: ignore system_message = get_system_message(autotx) agent = autogen.AssistantAgent( diff --git a/autotx/autotx_tool.py b/autotx/autotx_tool.py index 8a0ed66b..ea1f8fa0 100644 --- a/autotx/autotx_tool.py +++ b/autotx/autotx_tool.py @@ -1,4 +1,4 @@ -from typing import Callable, TYPE_CHECKING +from typing import Any, Callable, TYPE_CHECKING from autogen import Agent, UserProxyAgent if TYPE_CHECKING: from autotx.AutoTx import AutoTx @@ -7,12 +7,12 @@ class AutoTxTool: name: str description: str - def register_tool(self, autotx: 'AutoTx', agent: Agent, user_proxy: UserProxyAgent): + def register_tool(self, autotx: 'AutoTx', agent: Agent, user_proxy: UserProxyAgent) -> None: tool = self.build_tool(autotx) # Register the tool signature with the assistant agent. agent.register_for_llm(name=self.name, description=self.description)(tool) # Register the tool function with the user proxy agent. user_proxy.register_for_execution(name=self.name)(tool) - def build_tool(self, autotx: 'AutoTx') -> Callable: + def build_tool(self, autotx: 'AutoTx') -> Callable[..., Any]: raise NotImplementedError \ No newline at end of file diff --git a/autotx/cli.py b/autotx/cli.py index 100db43b..83b55554 100644 --- a/autotx/cli.py +++ b/autotx/cli.py @@ -13,7 +13,7 @@ from autotx.utils.ethereum.networks import NetworkInfo from autotx.utils.ethereum.helpers.get_dev_account import get_dev_account from autotx.AutoTx import AutoTx -from autotx.utils.ethereum.agent_account import get_agent_account, create_agent_account, delete_agent_account +from autotx.utils.ethereum.agent_account import get_or_create_agent_account from autotx.utils.ethereum.SafeManager import SafeManager from autotx.utils.ethereum.send_eth import send_eth from autotx.utils.ethereum.helpers.show_address_balances import show_address_balances @@ -102,9 +102,7 @@ def agent_address() -> None: print_agent_address() def print_agent_address() -> None: - agent_account = get_agent_account() - acc = agent_account if agent_account else create_agent_account() - + acc = get_or_create_agent_account() print(f"Agent address: {acc.address}") if __name__ == "__main__": diff --git a/autotx/utils/ethereum/SafeManager.py b/autotx/utils/ethereum/SafeManager.py index 48f884d9..6a11d0dc 100644 --- a/autotx/utils/ethereum/SafeManager.py +++ b/autotx/utils/ethereum/SafeManager.py @@ -1,15 +1,17 @@ import sys -from typing import Optional +from typing import Optional, cast import re import logging +from eth_typing import ChecksumAddress +from hexbytes import HexBytes from web3 import Web3 from gnosis.eth import EthereumClient from gnosis.eth.constants import NULL_ADDRESS from gnosis.eth.multicall import Multicall from gnosis.safe import Safe, SafeOperation, SafeTx from gnosis.safe.multi_send import MultiSend, MultiSendOperation, MultiSendTx -from web3.types import TxParams +from web3.types import TxParams, TxReceipt from gnosis.safe.api import TransactionServiceApi from gnosis.safe.api.base_api import SafeAPIException from eth_account.signers.local import LocalAccount @@ -37,7 +39,10 @@ class SafeManager: safe_nonce: int | None = None gas_multiplier: float | None = GAS_PRICE_MULTIPLIER dev_account: LocalAccount | None = None + network: ChainId | None = None + transaction_service_url: str | None = None address: ETHAddress + use_tx_service: bool def __init__( self, @@ -71,7 +76,7 @@ def deploy_safe( manager = cls(client, agent, Safe(Web3.to_checksum_address(safe_address), client)) manager.dev_account = dev_account - manager.multisend = MultiSend(client, address=MULTI_SEND_ADDRESS) + manager.multisend = MultiSend(client, address=Web3.to_checksum_address(MULTI_SEND_ADDRESS)) return manager @@ -86,41 +91,45 @@ def connect( manager = cls(client, agent, safe) - manager.multisend = MultiSend(client, address=MULTI_SEND_ADDRESS) + manager.multisend = MultiSend(client, address=Web3.to_checksum_address(MULTI_SEND_ADDRESS)) return manager - def connect_tx_service(self, network: ChainId, transaction_service_url: str): + def connect_tx_service(self, network: ChainId, transaction_service_url: str) -> None: self.use_tx_service = True self.network = network self.transaction_service_url = transaction_service_url - def disconnect_tx_service(self): + def disconnect_tx_service(self) -> None: self.use_tx_service = False self.network = None self.transaction_service_url = None - def connect_multisend(self, address: str): + def connect_multisend(self, address: ChecksumAddress) -> None: self.multisend = MultiSend(self.client, address=address) - def connect_multicall(self, address: ETHAddress): + def connect_multicall(self, address: ETHAddress) -> None: self.client.multicall = Multicall(self.client, address.hex) - def deploy_multicall(self): + def deploy_multicall(self) -> None: if not self.dev_account: raise ValueError("Dev account not set. This function should not be called in production.") multicall_addr = deploy_multicall(self.client, self.dev_account) self.connect_multicall(multicall_addr) def build_multisend_tx(self, txs: list[TxParams], safe_nonce: Optional[int] = None) -> SafeTx: + if not self.multisend: + raise Exception("No multisend contract address has been set to SafeManager") + multisend_txs = [ - MultiSendTx(MultiSendOperation.CALL, tx["to"], tx["value"], tx["data"]) + MultiSendTx(MultiSendOperation.CALL, str(tx["to"]), tx["value"], tx["data"]) for tx in txs ] safe_multisend_data = self.multisend.build_tx_data(multisend_txs) + safe_tx = self.safe.build_multisig_tx( - to=self.multisend.address, + to=str(self.multisend.address), value=sum(tx["value"] for tx in txs), data=safe_multisend_data, operation=SafeOperation.DELEGATE_CALL.value, @@ -133,9 +142,9 @@ def build_tx(self, tx: TxParams, safe_nonce: Optional[int] = None) -> SafeTx: safe_tx = SafeTx( self.client, self.address.hex, - tx["to"], + str(tx["to"]), tx["value"], - tx["data"], + cast(bytes, tx["data"]), 0, 0, 0, @@ -149,7 +158,7 @@ def build_tx(self, tx: TxParams, safe_nonce: Optional[int] = None) -> SafeTx: return safe_tx - def execute_tx(self, tx: TxParams, safe_nonce: Optional[int] = None): + def execute_tx(self, tx: TxParams, safe_nonce: Optional[int] = None) -> HexBytes: if not self.dev_account: raise ValueError("Dev account not set. This function should not be called in production.") @@ -175,7 +184,7 @@ def execute_tx(self, tx: TxParams, safe_nonce: Optional[int] = None): raise Exception("Unknown error executing transaction", e) - def execute_multisend_tx(self, txs: list[TxParams], safe_nonce: Optional[int] = None): + def execute_multisend_tx(self, txs: list[TxParams], safe_nonce: Optional[int] = None) -> HexBytes: if not self.dev_account: raise ValueError("Dev account not set. This function should not be called in production.") @@ -191,7 +200,10 @@ def execute_multisend_tx(self, txs: list[TxParams], safe_nonce: Optional[int] = return tx_hash - def post_transaction(self, tx: TxParams, safe_nonce: Optional[int] = None): + def post_transaction(self, tx: TxParams, safe_nonce: Optional[int] = None) -> None: + if not self.network: + raise Exception("Network not defined for transaction service") + try: ts_api = TransactionServiceApi( self.network, ethereum_client=self.client, base_url=self.transaction_service_url @@ -205,7 +217,10 @@ def post_transaction(self, tx: TxParams, safe_nonce: Optional[int] = None): if "is not an owner or delegate" in str(e): sys.exit(f"Agent with address {self.agent.address} is not a signer of the safe with address {self.address.hex}. Please add it and try again") - def post_multisend_transaction(self, txs: list[TxParams], safe_nonce: Optional[int] = None): + def post_multisend_transaction(self, txs: list[TxParams], safe_nonce: Optional[int] = None) -> None: + if not self.network: + raise Exception("Network not defined for transaction service") + ts_api = TransactionServiceApi( self.network, ethereum_client=self.client, base_url=self.transaction_service_url ) @@ -281,7 +296,7 @@ def send_tx_batch(self, txs: list[PreparedTx], require_approval: bool, safe_nonc return True - def send_empty_tx(self, safe_nonce: Optional[int] = None): + def send_empty_tx(self, safe_nonce: Optional[int] = None) -> str | None: tx: TxParams = { "to": self.address.hex, "value": self.web3.to_wei(0, "ether"), @@ -291,7 +306,7 @@ def send_empty_tx(self, safe_nonce: Optional[int] = None): return self.send_tx(tx, safe_nonce) - def wait(self, tx_hash: str): + def wait(self, tx_hash: HexBytes) -> TxReceipt: return self.web3.eth.wait_for_transaction_receipt(tx_hash) def balance_of(self, token_address: ETHAddress | None = None) -> float: diff --git a/autotx/utils/ethereum/agent_account.py b/autotx/utils/ethereum/agent_account.py index c0dc3aa8..5f25e398 100644 --- a/autotx/utils/ethereum/agent_account.py +++ b/autotx/utils/ethereum/agent_account.py @@ -1,3 +1,4 @@ +from typing import Optional, cast from eth_account import Account from eth_account.signers.local import LocalAccount from autotx.utils.ethereum.cache import cache @@ -5,11 +6,12 @@ AGENT_PK_FILE_NAME = "agent.pk.txt" -def get_agent_account() -> LocalAccount | None: +def get_agent_account() -> Optional[LocalAccount]: try: account = cache.read(AGENT_PK_FILE_NAME) if account: - return Account.from_key(account) + return cast(LocalAccount, Account.from_key(account)) + return None except FileNotFoundError: return None except Exception as e: @@ -20,7 +22,7 @@ def get_agent_account() -> LocalAccount | None: def create_agent_account() -> LocalAccount: agent_account: str = Account.create().key.hex() cache.write(AGENT_PK_FILE_NAME, agent_account) - return Account.from_key(agent_account) + return cast(LocalAccount, Account.from_key(agent_account)) def get_or_create_agent_account() -> LocalAccount: @@ -30,5 +32,5 @@ def get_or_create_agent_account() -> LocalAccount: return create_agent_account() -def delete_agent_account(): +def delete_agent_account() -> None: cache.remove(AGENT_PK_FILE_NAME) diff --git a/autotx/utils/ethereum/cache.py b/autotx/utils/ethereum/cache.py index f21b78c0..e4a32022 100644 --- a/autotx/utils/ethereum/cache.py +++ b/autotx/utils/ethereum/cache.py @@ -4,10 +4,10 @@ class Cache: folder: str = ".cache" - def __init__(self, folder: Optional[str] = ".cache"): + def __init__(self, folder: Optional[str]): if folder: self.folder = folder - os.makedirs(folder, exist_ok=True) + os.makedirs(self.folder, exist_ok=True) def read(self, file_name: str) -> str | None: try: @@ -33,4 +33,4 @@ def remove(self, file_name: str) -> None: print(f"An error occurred while deleting {file_name}: {e}") raise -cache = Cache() +cache = Cache(folder=None) diff --git a/autotx/utils/ethereum/cached_safe_address.py b/autotx/utils/ethereum/cached_safe_address.py index ffc15fe7..73661928 100644 --- a/autotx/utils/ethereum/cached_safe_address.py +++ b/autotx/utils/ethereum/cached_safe_address.py @@ -8,8 +8,8 @@ def get_cached_safe_address() -> str | None: except Exception as e: return None -def save_cached_safe_address(safe_address: str): +def save_cached_safe_address(safe_address: str) -> None: cache.write(SAFE_ADDRESS_FILE_NAME, safe_address) -def delete_cached_safe_address(): +def delete_cached_safe_address() -> None: cache.remove(SAFE_ADDRESS_FILE_NAME) diff --git a/autotx/utils/ethereum/config.py b/autotx/utils/ethereum/config.py deleted file mode 100644 index d9e69145..00000000 --- a/autotx/utils/ethereum/config.py +++ /dev/null @@ -1,16 +0,0 @@ -import json -from typing import Dict -from pydantic import RootModel - -class ContractsAddresses(RootModel): - root: Dict[str, Dict[str, str]] - - def __getitem__(self, attr): - return self.root.get(attr) - - def items(self): - return self.root.items() - -contracts_config: ContractsAddresses = ContractsAddresses.model_validate( - json.loads(open("autotx/config/addresses.json", "r").read()) -) diff --git a/autotx/utils/ethereum/deploy_multicall.py b/autotx/utils/ethereum/deploy_multicall.py index 1862f536..b84eb440 100644 --- a/autotx/utils/ethereum/deploy_multicall.py +++ b/autotx/utils/ethereum/deploy_multicall.py @@ -1,20 +1,20 @@ import os -from eth_account import Account from gnosis.eth import EthereumClient from gnosis.eth.multicall import Multicall +from eth_account.signers.local import LocalAccount from autotx.utils.ethereum.eth_address import ETHAddress from .cache import cache -def deploy_multicall(client: EthereumClient, account: Account) -> ETHAddress: +def deploy_multicall(client: EthereumClient, account: LocalAccount) -> ETHAddress: if os.getenv("MULTICALL_ADDRESS"): return os.getenv("MULTICALL_ADDRESS") multicall_address = deploy(client, account) cache.write("multicall-address.txt", multicall_address) return ETHAddress(multicall_address, client.w3) -def deploy(client: EthereumClient, account: Account) -> str: +def deploy(client: EthereumClient, account: LocalAccount) -> str: tx = Multicall.deploy_contract(client, account) if not tx.contract_address: diff --git a/autotx/utils/ethereum/deploy_safe_with_create2.py b/autotx/utils/ethereum/deploy_safe_with_create2.py index 3ac7546f..29da3dd6 100644 --- a/autotx/utils/ethereum/deploy_safe_with_create2.py +++ b/autotx/utils/ethereum/deploy_safe_with_create2.py @@ -1,13 +1,17 @@ import random -from eth_account import Account +from eth_account.signers.local import LocalAccount +from eth_typing import ChecksumAddress, HexStr from gnosis.eth import EthereumClient from gnosis.safe import ProxyFactory from gnosis.safe.safe_create2_tx import SafeCreate2TxBuilder +from hexbytes import HexBytes +from web3 import Web3 +from web3.types import Wei from .send_tx import send_tx from .constants import GAS_PRICE_MULTIPLIER, MASTER_COPY_ADDRESS, PROXY_FACTORY_ADDRESS -def deploy_safe_with_create2(client: EthereumClient, account: Account, signers: list[str], threshold: int) -> str: +def deploy_safe_with_create2(client: EthereumClient, account: LocalAccount, signers: list[str], threshold: int) -> ChecksumAddress: w3 = client.w3 salt_nonce = generate_salt_nonce() @@ -22,10 +26,10 @@ def deploy_safe_with_create2(client: EthereumClient, account: Account, signers: owners=signers, threshold=threshold, ) - safe_address = builder.calculate_create2_address(setup_data, salt_nonce) + safe_address: ChecksumAddress = builder.calculate_create2_address(setup_data, salt_nonce) # Check if safe is already deployed - if w3.eth.get_code(safe_address) != w3.to_bytes(hexstr="0x"): + if w3.eth.get_code(safe_address) != w3.to_bytes(hexstr=HexStr("0x")): print("Safe already deployed", safe_address) return safe_address @@ -39,21 +43,21 @@ def deploy_safe_with_create2(client: EthereumClient, account: Account, signers: if safe_address != safe_creation_tx.safe_address: raise ValueError("Create2 address mismatch") - tx_hash = send_tx( + send_tx( w3, { "to": safe_creation_tx.safe_address, "value": safe_creation_tx.payment, - "gasPrice": int(w3.eth.gas_price * GAS_PRICE_MULTIPLIER) + "gasPrice": Wei(int(w3.eth.gas_price * GAS_PRICE_MULTIPLIER)) }, account=account, ) - proxy_factory = ProxyFactory(PROXY_FACTORY_ADDRESS, client) + proxy_factory = ProxyFactory(Web3.to_checksum_address(PROXY_FACTORY_ADDRESS), client) ethereum_tx_sent = proxy_factory.deploy_proxy_contract_with_nonce( account, - MASTER_COPY_ADDRESS, + Web3.to_checksum_address(MASTER_COPY_ADDRESS), safe_creation_tx.safe_setup_data, salt_nonce, safe_creation_tx.gas, @@ -61,7 +65,7 @@ def deploy_safe_with_create2(client: EthereumClient, account: Account, signers: ) print("Deploying safe address: ", safe_address, ", tx: ", ethereum_tx_sent.tx_hash.hex()) - tx_receipt = w3.eth.wait_for_transaction_receipt(ethereum_tx_sent.tx_hash) + tx_receipt = w3.eth.wait_for_transaction_receipt(HexBytes(ethereum_tx_sent.tx_hash)) if tx_receipt["status"] != 1: raise ValueError("Transaction failed") diff --git a/autotx/utils/ethereum/eth_address.py b/autotx/utils/ethereum/eth_address.py index 5167e992..35e61050 100644 --- a/autotx/utils/ethereum/eth_address.py +++ b/autotx/utils/ethereum/eth_address.py @@ -1,13 +1,13 @@ from eth_typing import ChecksumAddress from web3 import Web3 - +from web3._utils.empty import Empty class ETHAddress: hex: ChecksumAddress ens_domain: str | None def __init__(self, hex_or_ens: str, web3: Web3): if hex_or_ens.endswith(".eth"): - self.hex = web3.ens.address(hex_or_ens) + self.hex = web3.ens.address(hex_or_ens) # type: ignore self.ens_domain = hex_or_ens elif Web3.is_address(hex_or_ens): self.hex = Web3.to_checksum_address(hex_or_ens) diff --git a/autotx/utils/ethereum/helpers/get_dev_account.py b/autotx/utils/ethereum/helpers/get_dev_account.py index 657066e2..33bedafc 100644 --- a/autotx/utils/ethereum/helpers/get_dev_account.py +++ b/autotx/utils/ethereum/helpers/get_dev_account.py @@ -1,5 +1,12 @@ +from typing import cast from eth_account import Account from eth_account.signers.local import LocalAccount + def get_dev_account() -> LocalAccount: - return Account.from_key("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") \ No newline at end of file + return cast( + LocalAccount, + Account.from_key( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + ), + ) diff --git a/autotx/utils/ethereum/helpers/show_address_balances.py b/autotx/utils/ethereum/helpers/show_address_balances.py index 2fb5a2e4..fc5a1855 100644 --- a/autotx/utils/ethereum/helpers/show_address_balances.py +++ b/autotx/utils/ethereum/helpers/show_address_balances.py @@ -5,16 +5,18 @@ from autotx.utils.ethereum.networks import SUPPORTED_NETWORKS_CONFIGURATION_MAP, ChainId from autotx.utils.ethereum.eth_address import ETHAddress -def show_address_balances(web3: Web3, network: ChainId, address: ETHAddress): +def show_address_balances(web3: Web3, network: ChainId, address: ETHAddress) -> None: eth_balance = get_eth_balance(web3, address) print(f"ETH balance: {eth_balance}") - tokens = SUPPORTED_NETWORKS_CONFIGURATION_MAP.get(network).default_tokens - for token in tokens: - if tokens[token] == NATIVE_TOKEN_ADDRESS: - continue - token_address = ETHAddress(tokens[token], web3) - balance = get_erc20_balance(web3, token_address, address) + current_network = SUPPORTED_NETWORKS_CONFIGURATION_MAP.get(network) + + if current_network: + for token in current_network.default_tokens: + if current_network.default_tokens[token] == NATIVE_TOKEN_ADDRESS: + continue + token_address = ETHAddress(current_network.default_tokens[token], web3) + balance = get_erc20_balance(web3, token_address, address) - if balance > 0: - print(f"{token.upper()} balance: {balance}") + if balance > 0: + print(f"{token.upper()} balance: {balance}") diff --git a/autotx/utils/ethereum/is_valid_safe.py b/autotx/utils/ethereum/is_valid_safe.py index 895cf481..bee0ba1b 100644 --- a/autotx/utils/ethereum/is_valid_safe.py +++ b/autotx/utils/ethereum/is_valid_safe.py @@ -1,3 +1,4 @@ +from eth_typing import HexStr from gnosis.eth import EthereumClient from web3 import Web3 from gnosis.safe import Safe @@ -8,7 +9,7 @@ def is_valid_safe(client: EthereumClient, safe_address: ETHAddress) -> bool: w3 = client.w3 - if w3.eth.get_code(Web3.to_checksum_address(safe_address.hex)) != w3.to_bytes(hexstr="0x"): + if w3.eth.get_code(Web3.to_checksum_address(safe_address.hex)) != w3.to_bytes(hexstr=HexStr("0x")): safe = Safe(Web3.to_checksum_address(safe_address.hex), client) master_copy_address = safe.retrieve_master_copy_address() return master_copy_address in [MASTER_COPY_ADDRESS, MASTER_COPY_L2_ADDRESS] diff --git a/autotx/utils/ethereum/send_tx.py b/autotx/utils/ethereum/send_tx.py index 07ae2117..71b87573 100644 --- a/autotx/utils/ethereum/send_tx.py +++ b/autotx/utils/ethereum/send_tx.py @@ -1,8 +1,8 @@ -from eth_account import Account +from eth_account.signers.local import LocalAccount from web3 import Web3 from web3.types import TxParams -def send_tx(w3: Web3, tx: TxParams, account: Account) -> bytes: +def send_tx(w3: Web3, tx: TxParams, account: LocalAccount) -> bytes: tx["from"] = account.address if "nonce" not in tx: tx["nonce"] = w3.eth.get_transaction_count( @@ -15,7 +15,7 @@ def send_tx(w3: Web3, tx: TxParams, account: Account) -> bytes: if "gas" not in tx: tx["gas"] = w3.eth.estimate_gas(tx) - signed_tx = account.sign_transaction(tx) + signed_tx = account.sign_transaction(tx) # type: ignore tx_hash = w3.eth.send_raw_transaction(bytes(signed_tx.rawTransaction)) print("Send TX: ", tx_hash.hex()) tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) diff --git a/autotx/utils/ethereum/transfer_erc20.py b/autotx/utils/ethereum/transfer_erc20.py index fabb33ca..69403aa7 100644 --- a/autotx/utils/ethereum/transfer_erc20.py +++ b/autotx/utils/ethereum/transfer_erc20.py @@ -1,6 +1,7 @@ -from eth_account import Account +from eth_account.signers.local import LocalAccount +from hexbytes import HexBytes from web3 import Web3 -from web3.middleware import construct_sign_and_send_raw_middleware +from web3.middleware.signing import construct_sign_and_send_raw_middleware from autotx.utils.ethereum.eth_address import ETHAddress @@ -8,7 +9,7 @@ from .erc20_abi import ERC20_ABI -def transfer_erc20(web3: Web3, token_address: ETHAddress, from_account: Account, to: ETHAddress, value: float): +def transfer_erc20(web3: Web3, token_address: ETHAddress, from_account: LocalAccount, to: ETHAddress, value: float) -> HexBytes: account_middleware = construct_sign_and_send_raw_middleware(from_account) web3.middleware_onion.add(account_middleware) diff --git a/autotx/utils/io_silent.py b/autotx/utils/io_silent.py index 327a8ac1..dbc2c392 100644 --- a/autotx/utils/io_silent.py +++ b/autotx/utils/io_silent.py @@ -8,4 +8,4 @@ def print(self, *objects: Any, sep: str = " ", end: str = "\n", flush: bool = Fa pass # Pass all args to the base class def input(self, prompt: str = "", *, password: bool = False) -> str: - return super().input(prompt, password=password) + return super().input(prompt, password=password) # type: ignore diff --git a/pyproject.toml b/pyproject.toml index 73969b4c..dd546c53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,11 @@ build-backend = "poetry.core.masonry.api" [tool.mypy] strict = true + +exclude = [ + "autotx/tests/", +] + ignore_missing_imports = true install_types = true non_interactive = true @@ -55,5 +60,4 @@ show_error_codes = true warn_unused_ignores = true disallow_incomplete_defs = true -disallow_untyped_decorators = true -disallow_any_unimported = true \ No newline at end of file +disallow_untyped_decorators = true \ No newline at end of file From 3bc1dff5aca7d46030464844bc683630f122b873 Mon Sep 17 00:00:00 2001 From: Cesar Date: Tue, 9 Apr 2024 22:45:04 +0200 Subject: [PATCH 3/5] chore: agents tools helpers green in mypy --- autotx/agents/SendTokensAgent.py | 4 ++- autotx/load_tokens.py | 4 +-- autotx/utils/configuration.py | 2 +- autotx/utils/ethereum/build_approve_erc20.py | 2 +- autotx/utils/ethereum/build_transfer_erc20.py | 13 +++++++-- autotx/utils/ethereum/deploy_multicall.py | 8 +++-- autotx/utils/ethereum/get_erc20_balance.py | 6 ++-- autotx/utils/ethereum/networks.py | 10 +++---- autotx/utils/ethereum/send_eth.py | 9 +++--- autotx/utils/ethereum/transfer_erc20.py | 21 +++++++++++--- autotx/utils/ethereum/uniswap/swap.py | 29 ++++++++++--------- 11 files changed, 66 insertions(+), 42 deletions(-) diff --git a/autotx/agents/SendTokensAgent.py b/autotx/agents/SendTokensAgent.py index 742b1f75..1718205b 100644 --- a/autotx/agents/SendTokensAgent.py +++ b/autotx/agents/SendTokensAgent.py @@ -1,5 +1,7 @@ from textwrap import dedent from typing import Annotated, Callable + +from web3 import Web3 from autotx.AutoTx import AutoTx from autotx.autotx_agent import AutoTxAgent from autotx.autotx_tool import AutoTxTool @@ -57,7 +59,7 @@ def run( receiver: Annotated[str, "The receiver's address or ENS domain"], token: Annotated[str, "Symbol of token to transfer"] ) -> str: - token_address = autotx.network.tokens[token.lower()] + token_address = Web3.to_checksum_address(autotx.network.tokens[token.lower()]) web3 = load_w3() receiver_addr = ETHAddress(receiver, web3) diff --git a/autotx/load_tokens.py b/autotx/load_tokens.py index 0632ed90..60f93441 100644 --- a/autotx/load_tokens.py +++ b/autotx/load_tokens.py @@ -20,7 +20,7 @@ TOKENS_LIST = [KLEROS_TOKENS_LIST, *COINGECKO_TOKENS_LISTS] -def fetch_tokens_list(): +def fetch_tokens_list() -> None: loaded_tokens: list[dict[str, Union[str, int]]] = [] for token_list_url in TOKENS_LIST: @@ -42,5 +42,5 @@ def fetch_tokens_list(): f.write(content) -def run(): +def run() -> None: fetch_tokens_list() diff --git a/autotx/utils/configuration.py b/autotx/utils/configuration.py index ebeabd83..27b95ef3 100644 --- a/autotx/utils/configuration.py +++ b/autotx/utils/configuration.py @@ -12,7 +12,7 @@ smart_account_addr = get_env_vars() -def get_configuration() -> tuple[ETHAddress, LocalAccount, EthereumClient]: +def get_configuration() -> tuple[ETHAddress | None, LocalAccount, EthereumClient]: w3 = Web3(HTTPProvider(FORK_RPC_URL)) for i in range(16): if w3.is_connected(): diff --git a/autotx/utils/ethereum/build_approve_erc20.py b/autotx/utils/ethereum/build_approve_erc20.py index ff4f0d05..d99f4e37 100644 --- a/autotx/utils/ethereum/build_approve_erc20.py +++ b/autotx/utils/ethereum/build_approve_erc20.py @@ -3,7 +3,7 @@ from autotx.utils.ethereum.eth_address import ETHAddress from autotx.utils.ethereum.erc20_abi import ERC20_ABI -def build_approve_erc20(web3: Web3, token_address: ETHAddress, spender: ETHAddress, value: float): +def build_approve_erc20(web3: Web3, token_address: ETHAddress, spender: ETHAddress, value: float) -> TxParams: erc20 = web3.eth.contract(address=token_address.hex, abi=ERC20_ABI) decimals = erc20.functions.decimals().call() diff --git a/autotx/utils/ethereum/build_transfer_erc20.py b/autotx/utils/ethereum/build_transfer_erc20.py index a1858e18..26480600 100644 --- a/autotx/utils/ethereum/build_transfer_erc20.py +++ b/autotx/utils/ethereum/build_transfer_erc20.py @@ -1,17 +1,24 @@ +from eth_typing import ChecksumAddress from web3 import Web3 -from web3.types import TxParams +from web3.types import TxParams, Wei from autotx.utils.ethereum.eth_address import ETHAddress from .constants import GAS_PRICE_MULTIPLIER from .erc20_abi import ERC20_ABI -def build_transfer_erc20(web3: Web3, token_address: str, to: ETHAddress, value: float): + +def build_transfer_erc20( + web3: Web3, token_address: ChecksumAddress, to: ETHAddress, value: float +) -> TxParams: erc20 = web3.eth.contract(address=token_address, abi=ERC20_ABI) decimals = erc20.functions.decimals().call() tx: TxParams = erc20.functions.transfer( to.hex, int(value * 10**decimals) ).build_transaction( - {"gas": None, "gasPrice": web3.eth.gas_price * GAS_PRICE_MULTIPLIER} + { + "gas": None, # type: ignore + "gasPrice": Wei(int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER)) + } ) return tx diff --git a/autotx/utils/ethereum/deploy_multicall.py b/autotx/utils/ethereum/deploy_multicall.py index b84eb440..dd63a300 100644 --- a/autotx/utils/ethereum/deploy_multicall.py +++ b/autotx/utils/ethereum/deploy_multicall.py @@ -2,14 +2,16 @@ from gnosis.eth import EthereumClient from gnosis.eth.multicall import Multicall from eth_account.signers.local import LocalAccount +from hexbytes import HexBytes from autotx.utils.ethereum.eth_address import ETHAddress from .cache import cache def deploy_multicall(client: EthereumClient, account: LocalAccount) -> ETHAddress: - if os.getenv("MULTICALL_ADDRESS"): - return os.getenv("MULTICALL_ADDRESS") + multicall_address = os.getenv("MULTICALL_ADDRESS") + if multicall_address: + return ETHAddress(multicall_address, client.w3) multicall_address = deploy(client, account) cache.write("multicall-address.txt", multicall_address) return ETHAddress(multicall_address, client.w3) @@ -20,7 +22,7 @@ def deploy(client: EthereumClient, account: LocalAccount) -> str: if not tx.contract_address: raise ValueError("Multicall contract address is not set") - client.w3.eth.wait_for_transaction_receipt(tx.tx_hash) + client.w3.eth.wait_for_transaction_receipt(HexBytes(tx.tx_hash)) print("Deployed Multicall to: ", tx.contract_address) diff --git a/autotx/utils/ethereum/get_erc20_balance.py b/autotx/utils/ethereum/get_erc20_balance.py index f3c9afee..8ec2edca 100644 --- a/autotx/utils/ethereum/get_erc20_balance.py +++ b/autotx/utils/ethereum/get_erc20_balance.py @@ -5,6 +5,6 @@ def get_erc20_balance(web3: Web3, token_address: ETHAddress, account: ETHAddress) -> float: erc20 = web3.eth.contract(address=token_address.hex, abi=ERC20_ABI) - decimals = erc20.functions.decimals().call() - - return erc20.functions.balanceOf(account.hex).call() / 10 ** decimals + decimals: int = erc20.functions.decimals().call() + balance: int = erc20.functions.balanceOf(account.hex).call() + return balance / 10 ** decimals # type: ignore diff --git a/autotx/utils/ethereum/networks.py b/autotx/utils/ethereum/networks.py index 37bb8ac6..f5c317fa 100644 --- a/autotx/utils/ethereum/networks.py +++ b/autotx/utils/ethereum/networks.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Union +from typing import Union, cast from gnosis.eth import EthereumNetwork from web3 import Web3 @@ -20,15 +20,15 @@ def __init__( self.chain_id = ChainId(chain_id) config = SUPPORTED_NETWORKS_CONFIGURATION_MAP.get(self.chain_id) - if config == None: + if not config: raise Exception(f"Chain ID {chain_id} is not supported") self.transaction_service_url = config.transaction_service_url - self.tokens = self.fetch_tokens_for_chain(chain_id) | config.default_tokens + self.tokens = self.fetch_tokens_for_chain(chain_id) or config.default_tokens - def fetch_tokens_for_chain(self, chain_id: int) -> list[dict[str, Union[str, int]]]: + def fetch_tokens_for_chain(self, chain_id: int) -> dict[str, str]: return { - token["symbol"].lower(): Web3.to_checksum_address(token["address"]) + cast(str, token["symbol"]).lower(): Web3.to_checksum_address(cast(str, token["address"])) for token in token_list if token["chainId"] == chain_id } diff --git a/autotx/utils/ethereum/send_eth.py b/autotx/utils/ethereum/send_eth.py index cd53aaaa..dca94086 100644 --- a/autotx/utils/ethereum/send_eth.py +++ b/autotx/utils/ethereum/send_eth.py @@ -1,8 +1,7 @@ from eth_account.signers.local import LocalAccount from eth_typing import Address from web3 import Web3 -from web3.types import TxReceipt -from web3.types import TxParams +from web3.types import TxParams, TxReceipt, Wei from autotx.utils.ethereum.eth_address import ETHAddress @@ -16,8 +15,8 @@ def send_eth(account: LocalAccount, to: ETHAddress, value: float, web3: Web3) -> tx: TxParams = { 'from': account.address, 'to': to.hex, - 'value': int(value * 10 ** 18), - 'gasPrice': int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER), + 'value': Wei(int(value * 10 ** 18)), + 'gasPrice': Wei(int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER)), 'nonce': nonce, 'chainId': web3.eth.chain_id } @@ -25,7 +24,7 @@ def send_eth(account: LocalAccount, to: ETHAddress, value: float, web3: Web3) -> gas = web3.eth.estimate_gas(tx) tx.update({'gas': gas}) - signed_tx = account.sign_transaction(tx) + signed_tx = account.sign_transaction(tx) # type: ignore web3.eth.send_raw_transaction(signed_tx.rawTransaction) diff --git a/autotx/utils/ethereum/transfer_erc20.py b/autotx/utils/ethereum/transfer_erc20.py index 69403aa7..a09085ba 100644 --- a/autotx/utils/ethereum/transfer_erc20.py +++ b/autotx/utils/ethereum/transfer_erc20.py @@ -1,6 +1,7 @@ from eth_account.signers.local import LocalAccount from hexbytes import HexBytes from web3 import Web3 +from web3.types import Wei from web3.middleware.signing import construct_sign_and_send_raw_middleware from autotx.utils.ethereum.eth_address import ETHAddress @@ -9,15 +10,27 @@ from .erc20_abi import ERC20_ABI -def transfer_erc20(web3: Web3, token_address: ETHAddress, from_account: LocalAccount, to: ETHAddress, value: float) -> HexBytes: + +def transfer_erc20( + web3: Web3, + token_address: ETHAddress, + from_account: LocalAccount, + to: ETHAddress, + value: float, +) -> HexBytes: account_middleware = construct_sign_and_send_raw_middleware(from_account) web3.middleware_onion.add(account_middleware) erc20 = web3.eth.contract(address=token_address.hex, abi=ERC20_ABI) decimals = erc20.functions.decimals().call() - tx_hash = erc20.functions.transfer(to.hex, int(value * 10 ** decimals)).transact({"from": from_account.address, "gasPrice": int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER)}) + tx_hash = erc20.functions.transfer(to.hex, int(value * 10**decimals)).transact( + { + "from": from_account.address, + "gasPrice": Wei(int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER)), + } + ) - web3.middleware_onion.remove(account_middleware) + web3.middleware_onion.remove(account_middleware) # type: ignore - return tx_hash \ No newline at end of file + return tx_hash diff --git a/autotx/utils/ethereum/uniswap/swap.py b/autotx/utils/ethereum/uniswap/swap.py index 00573a10..4b97f889 100644 --- a/autotx/utils/ethereum/uniswap/swap.py +++ b/autotx/utils/ethereum/uniswap/swap.py @@ -1,8 +1,9 @@ from decimal import Decimal +from typing import Literal, Union from gnosis.eth import EthereumClient from gnosis.eth.oracles.uniswap_v3 import UniswapV3Oracle import requests -from web3 import Web3 +from web3.types import Wei from autotx.utils.PreparedTx import PreparedTx from autotx.utils.ethereum.constants import GAS_PRICE_MULTIPLIER, NATIVE_TOKEN_ADDRESS @@ -29,7 +30,7 @@ def get_swap_information( token_out_decimals: int, price: float, exact_input: bool, -): +) -> tuple[int, int, Union[Literal["exactInputSingle"] | Literal["exactOutputSingle"]]]: if exact_input: amount_compared_with_token = amount * price minimum_amount_out = int(Decimal(amount_compared_with_token) * 10**token_out_decimals) @@ -109,11 +110,11 @@ def build_swap_transaction( token_out_is_native = token_out_address == NATIVE_TOKEN_ADDRESS token_in = web3.eth.contract( - address=uniswap.weth_address if token_in_is_native else token_in_address, + address=uniswap.weth_address if token_in_is_native else web3.to_checksum_address(token_in_address), abi=WETH_ABI if token_in_address == uniswap.weth_address else ERC20_ABI, ) token_out = web3.eth.contract( - address=uniswap.weth_address if token_out_is_native else token_out_address, + address=uniswap.weth_address if token_out_is_native else web3.to_checksum_address(token_out_address), abi=WETH_ABI if token_out_address == uniswap.weth_address else ERC20_ABI, ) @@ -125,9 +126,9 @@ def build_swap_transaction( token_out.functions.deposit().build_transaction( { "from": _from, - "gasPrice": int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER), - "gas": None, - "value": int(Decimal(amount) * 10**18), + "gasPrice": Wei(int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER)), + "gas": None, # type: ignore + "value": Wei(int(Decimal(amount) * 10**18)), } ), ) @@ -141,8 +142,8 @@ def build_swap_transaction( token_out.functions.withdraw(int(Decimal(amount) * 10**18)).build_transaction( { "from": _from, - "gasPrice": int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER), - "gas": None, + "gasPrice": Wei(int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER)), + "gas": None, # type: ignore } ), ) @@ -168,7 +169,7 @@ def build_swap_transaction( ).build_transaction( { "from": _from, - "gasPrice": int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER), + "gasPrice": Wei(int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER)), } ) transactions.append( @@ -180,7 +181,7 @@ def build_swap_transaction( subgraph_url = SUPPORTED_UNISWAP_V3_NETWORKS[NetworkInfo(web3.eth.chain_id).chain_id] fee = get_best_fee_tier(token_in.address, token_out.address, subgraph_url) - swap_transaction = uniswap.router.functions[method]( + swap_transaction = uniswap.router.functions[method]( # type: ignore ( token_in.address, token_out.address, @@ -194,7 +195,7 @@ def build_swap_transaction( { "value": amount_in if token_in_is_native else 0, "gas": None, - "gasPrice": int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER), + "gasPrice": Wei(int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER)), } ) transactions.append( @@ -208,8 +209,8 @@ def build_swap_transaction( tx = token_out.functions.withdraw(amount_out).build_transaction( { "from": _from, - "gasPrice": int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER), - "gas": None, + "gasPrice": Wei(int(web3.eth.gas_price * GAS_PRICE_MULTIPLIER)), + "gas": None, # type: ignore } ) transactions.append(PreparedTx(f"Convert WETH to ETH", tx)) From 3b2c64ef96ba65e3e9d871fa8b9db7930b018e7f Mon Sep 17 00:00:00 2001 From: Cesar Date: Tue, 9 Apr 2024 22:51:29 +0200 Subject: [PATCH 4/5] chore: mypy green without big refactor --- autotx/chain_fork.py | 4 ++-- pyproject.toml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/autotx/chain_fork.py b/autotx/chain_fork.py index 4a64a1d6..8f668e94 100644 --- a/autotx/chain_fork.py +++ b/autotx/chain_fork.py @@ -4,7 +4,7 @@ container_name = "autotx_chain_fork" -def start(): +def start() -> None: delete_cached_safe_address() build = subprocess.run( @@ -32,5 +32,5 @@ def start(): check=True, ) -def stop(): +def stop() -> None: subprocess.run(["docker", "container", "rm", container_name, "-f"], check=True) diff --git a/pyproject.toml b/pyproject.toml index dd546c53..cfaa975a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,8 @@ strict = true exclude = [ "autotx/tests/", + "benchmarks.py", + "autotx/utils/agent/build_goal.py" ] ignore_missing_imports = true From 6a3f563fe6fe74748cd6e693a464027efc00c336 Mon Sep 17 00:00:00 2001 From: Cesar Date: Tue, 9 Apr 2024 23:24:56 +0200 Subject: [PATCH 5/5] chore: create build script in gh action --- .github/workflows/ci.yaml | 28 ++++++++++++++++++++++++++++ autotx/build_check.py | 10 ++++++++++ pyproject.toml | 3 ++- 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yaml create mode 100644 autotx/build_check.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..07ceff76 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,28 @@ +name: ci + +on: + push: + branches: + - main + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: "3.10" + + - name: Install Poetry + run: curl -sSL https://install.python-poetry.org | python3 - + + - name: Install dependencies + run: poetry install + + - name: Check types + run: poetry run build-check \ No newline at end of file diff --git a/autotx/build_check.py b/autotx/build_check.py new file mode 100644 index 00000000..b59c9a1f --- /dev/null +++ b/autotx/build_check.py @@ -0,0 +1,10 @@ +import subprocess +import sys + +def run() -> None: + # Run mypy check + result = subprocess.run(["mypy", "."], capture_output=True) + print(result.stdout.decode()) + if result.returncode != 0: + print("Type checking failed") + sys.exit(1) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index cfaa975a..8bb17468 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,8 @@ start-fork = "autotx.chain_fork:start" stop-fork = "autotx.chain_fork:stop" ask = "autotx.cli:run" agent = "autotx.cli:agent" -load_tokens = "autotx.load_tokens:run" +load-tokens = "autotx.load_tokens:run" +build-check = "autotx.build_check:run" [build-system] requires = ["poetry-core"]