Skip to content

Commit

Permalink
Refactor proxyFactory and safe (#597)
Browse files Browse the repository at this point in the history
* Add ContractCommon class

* Refactor proxy factory

* Refactor Safe class

* Apply suggestions from code review

Co-authored-by: Uxío <[email protected]>

* Remove gas increment

* Add get_proxy_factory_fn

* Move contract_common to contracts

* Revert deploy_contract_with_deploy_function

* Fix SafeCreate2TxBuilder

* Refactor ProxyFactory

* Test every check_proxy_code in a subTest

* Refactor Safe.py

* Add version property to every Safe class

* Remove Creation V1. It was used on the Safe Relay when CREATE2 did not exist

* Remove deploy from ContractCommon, use EthereumClient deployandinitialize

* Refactor retrieve_all_info

* Fix Safe retrieve modules

* Move abis to abis folder

* Rename `deploy_safe_master_copy` and `deploy_proxy_factory_contract` to `deploy_contract`

* Improve docs and add typing for contract methods

* Update types and docs

---------

Co-authored-by: Uxío <[email protected]>
  • Loading branch information
moisses89 and Uxio0 authored Sep 12, 2023
1 parent 4482698 commit bd1f1cd
Show file tree
Hide file tree
Showing 43 changed files with 1,039 additions and 1,871 deletions.
149 changes: 105 additions & 44 deletions gnosis/eth/contracts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flake8: noqa F401
"""
Safe Addresses. Should be the same for every chain except for the ones with `chainId` protection. Check:
https://github.com/safe-global/safe-deployments/tree/main/src/assets
Expand All @@ -23,7 +24,7 @@
import json
import os
import sys
from typing import Any, Dict, Optional
from typing import Any, Callable, Dict, Optional

from eth_typing import ChecksumAddress
from hexbytes import HexBytes
Expand All @@ -32,19 +33,8 @@

from gnosis.util import cache


def load_contract_interface(file_name):
return _load_json_file(_abi_file_path(file_name))


def _abi_file_path(file):
return os.path.abspath(os.path.join(os.path.dirname(__file__), file))


def _load_json_file(path):
with open(path) as f:
return json.load(f)

from .abis.multicall import multicall_v3_abi, multicall_v3_bytecode
from .contract_base import ContractBase

current_module = sys.modules[__name__]
contracts = {
Expand All @@ -60,7 +50,7 @@ def _load_json_file(path):
"delegate_constructor_proxy": "DelegateConstructorProxy.json",
"multi_send": "MultiSend.json",
"paying_proxy": "PayingProxy.json",
"proxy_factory": "ProxyFactory_V1_3_0.json",
"proxy_factory_V1_3_0": "ProxyFactory_V1_3_0.json",
"proxy_factory_V1_1_1": "ProxyFactory_V1_1_1.json",
"proxy_factory_V1_0_0": "ProxyFactory_V1_0_0.json",
"proxy": "Proxy_V1_1_1.json",
Expand All @@ -74,24 +64,51 @@ def _load_json_file(path):
}


def generate_contract_fn(contract: Dict[str, Any]):
def load_contract_interface(file_name: str) -> Dict[str, Any]:
"""
Dynamically generate functions to work with the contracts
:param file_name:
:return: Get parsed JSON to ABI with the relative filename to this file path
"""
return _load_json_file(_abi_file_path(file_name))


def _abi_file_path(file_name: str) -> str:
"""
:param file_name:
:return: Full path to the provided ``file_name``
"""
return os.path.abspath(os.path.join(os.path.dirname(__file__), "abis", file_name))


def _load_json_file(path) -> Dict[str, Any]:
"""
:param path:
:return: Parsed json for the provided file
"""
with open(path) as f:
return json.load(f)


def generate_contract_fn(
contract: Dict[str, Any]
) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
"""
Dynamically generate a function to build a Web3 Contract for the provided contract ABI
:param contract:
:return:
:return: function that will return a Web3 Contract from an ABI
"""

def fn(w3: Web3, address: Optional[ChecksumAddress] = None):
def fn(w3: Web3, address: Optional[ChecksumAddress] = None) -> Contract:
return w3.eth.contract(
address=address, abi=contract["abi"], bytecode=contract.get("bytecode")
)

return fn


# Anotate functions that will be generated later with `setattr` so typing does not complains
def get_safe_contract(w3: Web3, address: Optional[str] = None) -> Contract:
# Anotate functions that will be generated later with `setattr` so typing does not complain
def get_safe_contract(w3: Web3, address: Optional[ChecksumAddress] = None) -> Contract:
"""
:param w3:
:param address:
Expand All @@ -100,110 +117,154 @@ def get_safe_contract(w3: Web3, address: Optional[str] = None) -> Contract:
return get_safe_V1_3_0_contract(w3, address=address)


def get_safe_V1_3_0_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_safe_V1_3_0_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_safe_V1_1_1_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_safe_V1_1_1_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_safe_V1_0_0_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_safe_V1_0_0_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_safe_V0_0_1_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_safe_V0_0_1_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_compatibility_fallback_handler_V1_3_0_contract(
w3: Web3, address: Optional[str] = None
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_erc20_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_erc20_contract(w3: Web3, address: Optional[ChecksumAddress] = None) -> Contract:
pass


def get_erc721_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_erc721_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_erc1155_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_erc1155_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_example_erc20_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_example_erc20_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_delegate_constructor_proxy_contract(
w3: Web3, address: Optional[str] = None
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_multi_send_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_multi_send_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_paying_proxy_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_paying_proxy_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_proxy_factory_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_proxy_factory_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
return get_proxy_factory_V1_3_0_contract(w3, address)


def get_proxy_factory_V1_3_0_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_proxy_factory_V1_1_1_contract(
w3: Web3, address: Optional[str] = None
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_proxy_factory_V1_0_0_contract(
w3: Web3, address: Optional[str] = None
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_proxy_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_proxy_contract(w3: Web3, address: Optional[ChecksumAddress] = None) -> Contract:
pass


def get_uniswap_exchange_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_uniswap_exchange_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_uniswap_factory_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_uniswap_factory_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_uniswap_v2_factory_contract(
w3: Web3, address: Optional[str] = None
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_uniswap_v2_pair_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_uniswap_v2_pair_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_uniswap_v2_router_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_uniswap_v2_router_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_kyber_network_proxy_contract(
w3: Web3, address: Optional[str] = None
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_cpk_factory_contract(w3: Web3, address: Optional[str] = None) -> Contract:
def get_cpk_factory_contract(
w3: Web3, address: Optional[ChecksumAddress] = None
) -> Contract:
pass


def get_multicall_v3_contract(w3: Web3, address: Optional[ChecksumAddress] = None):
return w3.eth.contract(
address=address,
abi=multicall_v3_abi,
bytecode=multicall_v3_bytecode,
)


@cache
def get_proxy_1_3_0_deployed_bytecode() -> bytes:
return HexBytes(load_contract_interface("Proxy_V1_3_0.json")["deployedBytecode"])
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
30 changes: 30 additions & 0 deletions gnosis/eth/contracts/contract_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from abc import ABCMeta, abstractmethod
from functools import cached_property
from logging import getLogger
from typing import Callable, Optional

from eth_typing import ChecksumAddress
from web3 import Web3
from web3.contract import Contract

logger = getLogger(__name__)


class ContractBase(metaclass=ABCMeta):
def __init__(
self, address: ChecksumAddress, ethereum_client: "EthereumClient" # noqa F821
):
self.address = address
self.ethereum_client = ethereum_client
self.w3 = ethereum_client.w3

@abstractmethod
def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
"""
:return: Contract function to get the proper contract
"""
raise NotImplementedError

@cached_property
def contract(self) -> Contract:
return self.get_contract_fn()(self.ethereum_client.w3, self.address)
Loading

0 comments on commit bd1f1cd

Please sign in to comment.