diff --git a/gnosis/eth/ethereum_client.py b/gnosis/eth/ethereum_client.py index a38f2004a..4a1a0b70c 100644 --- a/gnosis/eth/ethereum_client.py +++ b/gnosis/eth/ethereum_client.py @@ -73,6 +73,7 @@ from .exceptions import ( BatchCallFunctionFailed, ChainIdIsRequired, + ContractAlreadyDeployed, FromAddressNotFound, GasLimitExceeded, InsufficientFunds, @@ -1488,7 +1489,7 @@ def deploy_and_initialize_contract( SAFE_SINGLETON_FACTORY_ADDRESS, salt, data ) if self.is_contract(contract_address): - raise ValueError( + raise ContractAlreadyDeployed( f"Contract {contract_address} already deployed" ) diff --git a/gnosis/eth/exceptions.py b/gnosis/eth/exceptions.py index b4ed22ad8..97728d94a 100644 --- a/gnosis/eth/exceptions.py +++ b/gnosis/eth/exceptions.py @@ -54,6 +54,10 @@ class TransactionGasPriceTooLow(EthereumClientException): pass +class ContractAlreadyDeployed(EthereumClientException): + pass + + class InvalidERC20Info(EthereumClientException): pass diff --git a/gnosis/eth/tests/ethereum_test_case.py b/gnosis/eth/tests/ethereum_test_case.py index abb97f31f..20ec0912f 100644 --- a/gnosis/eth/tests/ethereum_test_case.py +++ b/gnosis/eth/tests/ethereum_test_case.py @@ -30,7 +30,7 @@ class EthereumTestCaseMixin: def setUpClass(cls): super().setUpClass() - cls.ethereum_test_account = cls.get_ethereum_test_account(cls) + cls.ethereum_test_account = cls.get_ethereum_test_account() # Caching ethereum_client to prevent initializing again cls.ethereum_client = _cached_data["ethereum_client"] @@ -45,7 +45,8 @@ def setUpClass(cls): cls.multicall = cls.ethereum_client.multicall assert cls.multicall, "Multicall must be defined" - def get_ethereum_test_account(self) -> LocalAccount: + @classmethod + def get_ethereum_test_account(cls) -> LocalAccount: try: from django.conf import settings diff --git a/gnosis/safe/tests/safe_test_case.py b/gnosis/safe/tests/safe_test_case.py index 042bcb242..9af3777b8 100644 --- a/gnosis/safe/tests/safe_test_case.py +++ b/gnosis/safe/tests/safe_test_case.py @@ -1,6 +1,6 @@ import logging import os -from typing import List, Optional +from typing import Dict, List, Optional from eth_account import Account from eth_typing import ChecksumAddress @@ -36,9 +36,6 @@ logger = logging.getLogger(__name__) -_contract_addresses = {} - - class SafeTestCaseMixin(EthereumTestCaseMixin): compatibility_fallback_handler: Contract multi_send: MultiSend @@ -65,6 +62,8 @@ class SafeTestCaseMixin(EthereumTestCaseMixin): "multi_send": MultiSend.deploy_contract, } + contract_addresses: Dict[str, ChecksumAddress] = {} + @property def safe_contract(self): """ @@ -76,55 +75,56 @@ def safe_contract(self): def setUpClass(cls): super().setUpClass() - cls.deploy_safe_singleton_factory(cls) + cls.deploy_safe_singleton_factory() - if not _contract_addresses: + if not cls.contract_addresses: # First time mixin is called, deploy Safe contracts for key, function in cls.contract_deployers.items(): - _contract_addresses[key] = function( + cls.contract_addresses[key] = function( cls.ethereum_client, cls.ethereum_test_account ).contract_address - cls.configure_django_settings(cls) - cls.configure_envvars(cls) + cls.configure_django_settings() + cls.configure_envvars() cls.compatibility_fallback_handler = ( get_compatibility_fallback_handler_contract( - cls.w3, _contract_addresses["compatibility_fallback_handler"] + cls.w3, cls.contract_addresses["compatibility_fallback_handler"] ) ) cls.simulate_tx_accessor_V1_4_1 = get_simulate_tx_accessor_V1_4_1_contract( - cls.w3, _contract_addresses["simulate_tx_accessor_V1_4_1"] + cls.w3, cls.contract_addresses["simulate_tx_accessor_V1_4_1"] ) cls.safe_contract_V1_4_1 = get_safe_V1_4_1_contract( - cls.w3, _contract_addresses["safe_V1_4_1"] + cls.w3, cls.contract_addresses["safe_V1_4_1"] ) cls.safe_contract_V1_3_0 = get_safe_V1_3_0_contract( - cls.w3, _contract_addresses["safe_V1_3_0"] + cls.w3, cls.contract_addresses["safe_V1_3_0"] ) cls.safe_contract_V1_1_1 = get_safe_V1_1_1_contract( - cls.w3, _contract_addresses["safe_V1_1_1"] + cls.w3, cls.contract_addresses["safe_V1_1_1"] ) cls.safe_contract_V1_0_0 = get_safe_V1_0_0_contract( - cls.w3, _contract_addresses["safe_V1_0_0"] + cls.w3, cls.contract_addresses["safe_V1_0_0"] ) cls.safe_contract_V0_0_1 = get_safe_V1_0_0_contract( - cls.w3, _contract_addresses["safe_V0_0_1"] + cls.w3, cls.contract_addresses["safe_V0_0_1"] ) cls.proxy_factory_contract = get_proxy_factory_contract( - cls.w3, _contract_addresses["proxy_factory"] + cls.w3, cls.contract_addresses["proxy_factory"] ) cls.proxy_factory = ProxyFactory( cls.proxy_factory_contract.address, cls.ethereum_client ) cls.multi_send_contract = get_multi_send_contract( - cls.w3, _contract_addresses["multi_send"] + cls.w3, cls.contract_addresses["multi_send"] ) cls.multi_send = MultiSend( cls.ethereum_client, address=cls.multi_send_contract.address ) - def configure_django_settings(self): + @classmethod + def configure_django_settings(cls): """ Configure settings for django based applications @@ -134,18 +134,30 @@ def configure_django_settings(self): try: from django.conf import settings - settings.SAFE_CONTRACT_ADDRESS = _contract_addresses["safe_V1_4_1"] - settings.SAFE_DEFAULT_CALLBACK_HANDLER = _contract_addresses[ + settings.SAFE_CONTRACT_ADDRESS = cls.contract_addresses["safe_V1_4_1"] + settings.SAFE_DEFAULT_CALLBACK_HANDLER = cls.contract_addresses[ "compatibility_fallback_handler" ] - settings.SAFE_MULTISEND_ADDRESS = _contract_addresses["multi_send"] - settings.SAFE_PROXY_FACTORY_ADDRESS = _contract_addresses["proxy_factory"] - settings.SAFE_V0_0_1_CONTRACT_ADDRESS = _contract_addresses["safe_V0_0_1"] - settings.SAFE_V1_0_0_CONTRACT_ADDRESS = _contract_addresses["safe_V1_0_0"] - settings.SAFE_V1_1_1_CONTRACT_ADDRESS = _contract_addresses["safe_V1_1_1"] - settings.SAFE_V1_3_0_CONTRACT_ADDRESS = _contract_addresses["safe_V1_3_0"] - settings.SAFE_V1_4_1_CONTRACT_ADDRESS = _contract_addresses["safe_V1_4_1"] - settings.SAFE_SIMULATE_TX_ACCESSOR = _contract_addresses[ + settings.SAFE_MULTISEND_ADDRESS = cls.contract_addresses["multi_send"] + settings.SAFE_PROXY_FACTORY_ADDRESS = cls.contract_addresses[ + "proxy_factory" + ] + settings.SAFE_V0_0_1_CONTRACT_ADDRESS = cls.contract_addresses[ + "safe_V0_0_1" + ] + settings.SAFE_V1_0_0_CONTRACT_ADDRESS = cls.contract_addresses[ + "safe_V1_0_0" + ] + settings.SAFE_V1_1_1_CONTRACT_ADDRESS = cls.contract_addresses[ + "safe_V1_1_1" + ] + settings.SAFE_V1_3_0_CONTRACT_ADDRESS = cls.contract_addresses[ + "safe_V1_3_0" + ] + settings.SAFE_V1_4_1_CONTRACT_ADDRESS = cls.contract_addresses[ + "safe_V1_4_1" + ] + settings.SAFE_SIMULATE_TX_ACCESSOR = cls.contract_addresses[ "simulate_tx_accessor_V1_4_1" ] settings.SAFE_VALID_CONTRACT_ADDRESSES = { @@ -159,17 +171,19 @@ def configure_django_settings(self): except ModuleNotFoundError: logger.info("Django library is not installed") - def configure_envvars(self): + @classmethod + def configure_envvars(cls): """ Configure environment variables :return: """ - os.environ["SAFE_SIMULATE_TX_ACCESSOR_ADDRESS"] = _contract_addresses[ + os.environ["SAFE_SIMULATE_TX_ACCESSOR_ADDRESS"] = cls.contract_addresses[ "simulate_tx_accessor_V1_4_1" ] - def deploy_safe_singleton_factory(self) -> bool: + @classmethod + def deploy_safe_singleton_factory(cls) -> bool: """ Deploy Safe Singleton Factory for deterministic deployments and speeding up tests, due to being able to check quickly if contracts are deployed in their expected addresses @@ -180,19 +194,19 @@ def deploy_safe_singleton_factory(self) -> bool: :return: `True` if deployed, `False` otherwise """ - if self.ethereum_client.is_contract(SAFE_SINGLETON_FACTORY_ADDRESS): + if cls.ethereum_client.is_contract(SAFE_SINGLETON_FACTORY_ADDRESS): return False raw_tx = HexBytes( "0xf8a78085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3820a96a0460c6ea9b8f791e5d9e67fbf2c70aba92bf88591c39ac3747ea1bedc2ef1750ca04b08a4b5cea15a56276513da7a0c0b34f16e89811d5dd911efba5f8625a921cc" ) send_tx( - self.w3, + cls.w3, {"to": SAFE_SINGLETON_FACTORY_DEPLOYER_ADDRESS, "value": 10000000000000000}, - self.ethereum_test_account, + cls.ethereum_test_account, ) - tx_hash = self.ethereum_client.send_raw_transaction(raw_tx) - tx_receipt = self.ethereum_client.get_transaction_receipt(tx_hash, timeout=30) + tx_hash = cls.ethereum_client.send_raw_transaction(raw_tx) + tx_receipt = cls.ethereum_client.get_transaction_receipt(tx_hash, timeout=30) assert tx_receipt["status"] == 1 return True