Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect Safe contract addresses using configured RPC #200

Merged
merged 1 commit into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions safe_cli/operators/safe_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ens import ENS
from eth_account import Account
from eth_account.signers.local import LocalAccount
from eth_typing import ChecksumAddress
from eth_utils import ValidationError
from hexbytes import HexBytes
from packaging import version as semantic_version
Expand All @@ -28,7 +29,11 @@
from safe_cli.api.relay_service_api import RelayServiceApi
from safe_cli.api.transaction_service_api import TransactionServiceApi
from safe_cli.ethereum_hd_wallet import get_account_from_words
from safe_cli.safe_addresses import LAST_DEFAULT_CALLBACK_HANDLER, LAST_SAFE_CONTRACT
from safe_cli.safe_addresses import (
get_default_fallback_handler_address,
get_safe_contract_address,
get_safe_l2_contract_address,
)
from safe_cli.utils import get_erc_20_list, yes_or_no_question


Expand Down Expand Up @@ -201,6 +206,23 @@ def __init__(self, address: str, node_url: str):
True # Require all signatures to be present to send a tx
)

@cached_property
def last_default_fallback_handler_address(self) -> ChecksumAddress:
"""
:return: Address for last version of default fallback handler contract
"""
return get_default_fallback_handler_address(self.ethereum_client)

@cached_property
def last_safe_contract_address_address(self) -> ChecksumAddress:
"""
:return: Last version of the Safe Contract. Use events version for every network but mainnet
"""
if self.network == EthereumNetwork.MAINNET:
return get_safe_contract_address(self.ethereum_client)
else:
return get_safe_l2_contract_address(self.ethereum_client)

@cached_property
def ens_domain(self) -> Optional[str]:
# FIXME After web3.py fixes the middleware copy
Expand All @@ -218,11 +240,11 @@ def is_version_updated(self) -> bool:
:return: True if Safe Master Copy is updated, False otherwise
"""

if self._safe_cli_info.master_copy == LAST_SAFE_CONTRACT:
if self._safe_cli_info.master_copy == self.last_safe_contract_address:
return True
else: # Check versions, maybe safe-cli addresses were not updated
safe_contract = get_safe_contract(
self.ethereum_client.w3, LAST_SAFE_CONTRACT
self.ethereum_client.w3, self.last_safe_contract_address
)
try:
safe_contract_version = safe_contract.functions.VERSION().call()
Expand Down Expand Up @@ -515,7 +537,10 @@ def update_version(self) -> Optional[bool]:
if self.is_version_updated():
raise SafeAlreadyUpdatedException()

addresses = (LAST_SAFE_CONTRACT, LAST_DEFAULT_CALLBACK_HANDLER)
addresses = (
self.last_safe_contract_address,
self.last_default_fallback_handler_address,
)
if not all(
self.ethereum_client.is_contract(contract) for contract in addresses
):
Expand All @@ -529,10 +554,10 @@ def update_version(self) -> Optional[bool]:
MultiSendTx(MultiSendOperation.CALL, self.address, 0, data)
for data in (
self.safe_contract_1_1_0.functions.changeMasterCopy(
LAST_SAFE_CONTRACT
self.last_safe_contract_address
).build_transaction(tx_params)["data"],
self.safe_contract_1_1_0.functions.setFallbackHandler(
LAST_DEFAULT_CALLBACK_HANDLER
self.last_default_fallback_handler_address
).build_transaction(tx_params)["data"],
)
]
Expand All @@ -542,8 +567,10 @@ def update_version(self) -> Optional[bool]:
if self.prepare_and_execute_safe_transaction(
multisend.address, 0, multisend_data, operation=SafeOperation.DELEGATE_CALL
):
self.safe_cli_info.master_copy = LAST_SAFE_CONTRACT
self.safe_cli_info.fallback_handler = LAST_DEFAULT_CALLBACK_HANDLER
self.safe_cli_info.master_copy = self.last_safe_contract_address
self.safe_cli_info.fallback_handler = (
self.last_default_fallback_handler_address
)
self.safe_cli_info.version = self.safe.retrieve_version()

def change_threshold(self, threshold: int):
Expand Down
94 changes: 87 additions & 7 deletions safe_cli/safe_addresses.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,87 @@
# https://github.com/gnosis/safe-deployments/tree/main/src/assets/v1.3.0
LAST_SAFE_CONTRACT = "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"
LAST_SAFE_L2_CONTRACT = "0x3E5c63644E683549055b9Be8653de26E0B4CD36E"
LAST_DEFAULT_CALLBACK_HANDLER = "0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4"
LAST_PROXY_FACTORY_CONTRACT = "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2"
LAST_MULTISEND_CONTRACT = "0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761"
LAST_MULTISEND_CALL_ONLY_CONTRACT = "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D"
"""
Get the correct addresses for the contracts by testing the deployment addresses using the RPC
Currently using Safe v1.3.0
https://github.com/gnosis/safe-deployments/tree/main/src/assets/v1.3.0
"""
from eth_typing import ChecksumAddress

from gnosis.eth import EthereumClient


def _get_valid_contract(
ethereum_client: EthereumClient, addresses: ChecksumAddress
) -> ChecksumAddress:
"""
:param ethereum_client:
:param addresses:
:return: First valid contract found in blockchain
"""

for address in addresses:
if ethereum_client.is_contract(address):
return address
raise ValueError(f"Network ${ethereum_client.get_network()} is not supported")


def get_safe_contract_address(ethereum_client: EthereumClient) -> ChecksumAddress:
return _get_valid_contract(
ethereum_client,
[
"0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552",
"0x69f4D1788e39c87893C980c06EdF4b7f686e2938",
],
)


def get_safe_l2_contract_address(ethereum_client: EthereumClient) -> ChecksumAddress:
return _get_valid_contract(
ethereum_client,
[
"0x3E5c63644E683549055b9Be8653de26E0B4CD36E",
"0xfb1bffC9d739B8D520DaF37dF666da4C687191EA",
],
)


def get_default_fallback_handler_address(
ethereum_client: EthereumClient,
) -> ChecksumAddress:
return _get_valid_contract(
ethereum_client,
[
"0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4",
"0x017062a1dE2FE6b99BE3d9d37841FeD19F573804",
],
)


def get_proxy_factory_address(ethereum_client: EthereumClient) -> ChecksumAddress:
return _get_valid_contract(
ethereum_client,
[
"0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2",
"0xC22834581EbC8527d974F8a1c97E1bEA4EF910BC",
],
)


def get_last_multisend_address(ethereum_client: EthereumClient) -> ChecksumAddress:
return _get_valid_contract(
ethereum_client,
[
"0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761",
"0x998739BFdAAdde7C933B942a68053933098f9EDa",
],
)


def get_last_multisend_call_only_address(
ethereum_client: EthereumClient,
) -> ChecksumAddress:
return _get_valid_contract(
ethereum_client,
[
"0x40A2aCCbd92BCA938b02010E17A5b8929b49130D"
"0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B"
],
)
41 changes: 23 additions & 18 deletions safe_cli/safe_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@

from safe_cli.prompt_parser import check_ethereum_address
from safe_cli.safe_addresses import (
LAST_DEFAULT_CALLBACK_HANDLER,
LAST_PROXY_FACTORY_CONTRACT,
LAST_SAFE_CONTRACT,
LAST_SAFE_L2_CONTRACT,
get_default_fallback_handler_address,
get_proxy_factory_address,
get_safe_contract_address,
get_safe_l2_contract_address,
)
from safe_cli.utils import yes_or_no_question

Expand Down Expand Up @@ -77,14 +77,14 @@ def setup_argument_parser():
parser.add_argument(
"--proxy-factory",
help="Use a custom proxy factory",
default=LAST_PROXY_FACTORY_CONTRACT,
default=None,
type=check_ethereum_address,
)
parser.add_argument(
"--callback-handler",
help="Use a custom fallback handler. It is not required for Safe Master Copies "
"with version < 1.1.0",
default=LAST_DEFAULT_CALLBACK_HANDLER,
default=None,
type=check_ethereum_address,
)
parser.add_argument(
Expand All @@ -98,8 +98,8 @@ def setup_argument_parser():
)

parser.add_argument(
"--l2",
help="Use L2 deployment of the Safe instead of the regular one. Recommended for every network but mainnet",
"--without-events",
help="Use non events deployment of the Safe instead of the regular one. Recommended for mainnet to save gas costs when using the Safe",
default=False,
action="store_true",
)
Expand All @@ -108,9 +108,7 @@ def setup_argument_parser():

def main(*args, **kwargs):
parser = setup_argument_parser()
print_formatted_text(
pyfiglet.figlet_format("Gnosis Safe Creator")
) # Print fancy text
print_formatted_text(pyfiglet.figlet_format("Safe Creator")) # Print fancy text
args = parser.parse_args()
node_url: URI = args.node_url
account: LocalAccount = Account.from_key(args.private_key)
Expand All @@ -129,14 +127,21 @@ def main(*args, **kwargs):
)
sys.exit(1)

safe_contract_address = args.safe_contract or (
LAST_SAFE_L2_CONTRACT if args.l2 else LAST_SAFE_CONTRACT
)
proxy_factory_address = args.proxy_factory
fallback_handler = args.callback_handler
ethereum_client = EthereumClient(node_url)
ethereum_network = ethereum_client.get_network()

safe_contract_address = args.safe_contract or (
get_safe_contract_address(ethereum_client)
if args.without_events
else get_safe_l2_contract_address(ethereum_client)
)
proxy_factory_address = args.proxy_factory or get_proxy_factory_address(
ethereum_client
)
fallback_handler = args.callback_handler or get_default_fallback_handler_address(
ethereum_client
)

if not ethereum_client.is_contract(safe_contract_address):
print_formatted_text(
f"Safe contract address {safe_contract_address} "
Expand Down Expand Up @@ -182,8 +187,8 @@ def main(*args, **kwargs):
f"Creating new Safe with owners={owners} threshold={threshold} salt-nonce={salt_nonce}"
)
print_formatted_text(
f"Proxy factory={proxy_factory_address} safe-master-copy={safe_contract_address} and "
f"fallback-handler={fallback_handler}"
f"Safe-master-copy={safe_contract_address}\nFallback-handler={fallback_handler}\n"
f"Proxy factory={proxy_factory_address}"
)
if yes_or_no_question("Do you want to continue?"):
safe_contract = get_safe_V1_3_0_contract(
Expand Down