Skip to content

Commit

Permalink
Support v1.4.1 contracts (#266)
Browse files Browse the repository at this point in the history
- Refactor tests
  • Loading branch information
Uxio0 authored Oct 13, 2023
1 parent 84be526 commit 688bc16
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 217 deletions.
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ prompt_toolkit==3.0.39
pyfiglet==1.0.0
pygments==2.16.1
requests==2.31.0
safe-eth-py==5.8.0
safe-eth-py==6.0.0b2
tabulate==0.9.0
web3==6.10.0
1 change: 0 additions & 1 deletion safe_cli/operators/safe_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,6 @@ def change_guard(self, guard: str) -> bool:
return True

def change_master_copy(self, new_master_copy: str) -> bool:
# TODO Check that master copy is valid
if new_master_copy == self.safe_cli_info.master_copy:
raise SameMasterCopyException(new_master_copy)
else:
Expand Down
38 changes: 25 additions & 13 deletions safe_cli/safe_addresses.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Get the correct addresses for the contracts by testing the deployment addresses using the RPC
Currently using Safe v1.3.0
Currently using Safe v1.4.1 when available, and 1.3.0 as fallback as they are compatible
https://github.com/gnosis/safe-deployments/tree/main/src/assets/v1.4.1
https://github.com/gnosis/safe-deployments/tree/main/src/assets/v1.3.0
"""
from eth_typing import ChecksumAddress
Expand All @@ -27,8 +28,9 @@ def get_safe_contract_address(ethereum_client: EthereumClient) -> ChecksumAddres
return _get_valid_contract(
ethereum_client,
[
"0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552",
"0x69f4D1788e39c87893C980c06EdF4b7f686e2938",
"0x41675C099F32341bf84BFc5382aF534df5C7461a", # v1.4.1
"0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552", # v1.3.0
"0x69f4D1788e39c87893C980c06EdF4b7f686e2938", # v1.3.0
],
)

Expand All @@ -37,8 +39,10 @@ def get_safe_l2_contract_address(ethereum_client: EthereumClient) -> ChecksumAdd
return _get_valid_contract(
ethereum_client,
[
"0x3E5c63644E683549055b9Be8653de26E0B4CD36E",
"0xfb1bffC9d739B8D520DaF37dF666da4C687191EA",
"0x29fcB43b46531BcA003ddC8FCB67FFE91900C762", # v1.4.1
"0x3E5c63644E683549055b9Be8653de26E0B4CD36E", # v1.3.0
"0xfb1bffC9d739B8D520DaF37dF666da4C687191EA", # v1.3.0
"0x1727c2c531cf966f902E5927b98490fDFb3b2b70", # v1.3.0 zkSync
],
)

Expand All @@ -49,8 +53,10 @@ def get_default_fallback_handler_address(
return _get_valid_contract(
ethereum_client,
[
"0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4",
"0x017062a1dE2FE6b99BE3d9d37841FeD19F573804",
"0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99", # v1.4.1
"0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4", # v1.3.0
"0x017062a1dE2FE6b99BE3d9d37841FeD19F573804", # v1.3.0
"0x2f870a80647BbC554F3a0EBD093f11B4d2a7492A", # v1.3.0 zkSync
],
)

Expand All @@ -59,8 +65,10 @@ def get_proxy_factory_address(ethereum_client: EthereumClient) -> ChecksumAddres
return _get_valid_contract(
ethereum_client,
[
"0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2",
"0xC22834581EbC8527d974F8a1c97E1bEA4EF910BC",
"0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", # v1.4.1
"0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", # v1.3.0
"0xC22834581EbC8527d974F8a1c97E1bEA4EF910BC", # v1.3.0
"0xDAec33641865E4651fB43181C6DB6f7232Ee91c2", # v1.3.0 zkSync
],
)

Expand All @@ -69,8 +77,10 @@ def get_last_multisend_address(ethereum_client: EthereumClient) -> ChecksumAddre
return _get_valid_contract(
ethereum_client,
[
"0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761",
"0x998739BFdAAdde7C933B942a68053933098f9EDa",
"0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526", # v1.4.1
"0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761", # v1.3.0
"0x998739BFdAAdde7C933B942a68053933098f9EDa", # v1.3.0
"0x0dFcccB95225ffB03c6FBB2559B530C2B7C8A912", # v1.3.0 zkSync
],
)

Expand All @@ -81,7 +91,9 @@ def get_last_multisend_call_only_address(
return _get_valid_contract(
ethereum_client,
[
"0x40A2aCCbd92BCA938b02010E17A5b8929b49130D"
"0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B"
"0x40A2aCCbd92BCA938b02010E17A5b8929b49130D", # v1.4.1
"0x40A2aCCbd92BCA938b02010E17A5b8929b49130D", # v1.3.0
"0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B", # v1.3.0
"0xf220D3b4DFb23C4ade8C88E526C1353AbAcbC38F", # v1.3.0 zkSync
],
)
14 changes: 7 additions & 7 deletions safe_cli/safe_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

from gnosis.eth import EthereumClient
from gnosis.eth.constants import NULL_ADDRESS
from gnosis.eth.contracts import get_safe_V1_3_0_contract
from gnosis.safe import ProxyFactory
from gnosis.eth.contracts import get_safe_V1_4_1_contract
from gnosis.safe import ProxyFactory, Safe

from safe_cli.prompt_parser import check_ethereum_address
from safe_cli.safe_addresses import (
Expand Down Expand Up @@ -91,9 +91,7 @@ def setup_argument_parser():
"--salt-nonce",
help="Use a custom nonce for the deployment. Same nonce with same deployment configuration will "
"lead to the same Safe address ",
default=secrets.SystemRandom().randint(
0, 2**256 - 1
), # TODO Add support for CPK
default=secrets.randbits(256),
type=int,
)

Expand Down Expand Up @@ -186,12 +184,14 @@ def main(*args, **kwargs):
print_formatted_text(
f"Creating new Safe with owners={owners} threshold={threshold} salt-nonce={salt_nonce}"
)
safe_version = Safe(safe_contract_address, ethereum_client).retrieve_version()
print_formatted_text(
f"Safe-master-copy={safe_contract_address}\nFallback-handler={fallback_handler}\n"
f"Safe-master-copy={safe_contract_address} version={safe_version}\n"
f"Fallback-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(
safe_contract = get_safe_V1_4_1_contract(
ethereum_client.w3, safe_contract_address
)
safe_creation_tx_data = HexBytes(
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"pyfiglet>=0.8",
"pygments>=2",
"requests>=2",
"safe-eth-py>=5.0.1",
"safe-eth-py>=6",
"tabulate>=0.8",
],
packages=setuptools.find_packages(),
Expand All @@ -39,6 +39,7 @@
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
Expand Down
184 changes: 17 additions & 167 deletions tests/safe_cli_test_case_mixin.py
Original file line number Diff line number Diff line change
@@ -1,185 +1,35 @@
import os
from typing import List, Optional

from eth_account import Account
from eth_typing import ChecksumAddress
from hexbytes import HexBytes

from gnosis.eth import EthereumClient
from gnosis.eth.constants import NULL_ADDRESS
from gnosis.eth.contracts import get_safe_contract, get_safe_V1_3_0_contract
from gnosis.safe import ProxyFactory, Safe
from gnosis.safe.safe_create2_tx import SafeCreate2Tx
from gnosis.safe.tests.utils import generate_salt_nonce
from gnosis.safe.tests.safe_test_case import SafeTestCaseMixin

from safe_cli.operators.safe_operator import SafeOperator


class SafeCliTestCaseMixin:
ETHEREUM_NODE_URL: str = os.environ.get(
"ETHEREUM_NODE_URL", "http://localhost:8545"
)
ETHEREUM_ACCOUNT_KEY: str = "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" # Ganache #0

@classmethod
def setUpClass(cls) -> None:
cls.ethereum_node_url = cls.ETHEREUM_NODE_URL
cls.ethereum_client = EthereumClient(cls.ethereum_node_url)
cls.w3 = cls.ethereum_client.w3
cls.ethereum_test_account = Account.from_key(cls.ETHEREUM_ACCOUNT_KEY)
cls.safe_contract_V1_3_0_address = Safe.deploy_master_contract_v1_3_0(
cls.ethereum_client, cls.ethereum_test_account
).contract_address
cls.safe_contract_address = Safe.deploy_master_contract_v1_1_1(
cls.ethereum_client, cls.ethereum_test_account
).contract_address
cls.safe_old_contract_address = Safe.deploy_master_contract_v1_0_0(
cls.ethereum_client, cls.ethereum_test_account
).contract_address
cls.proxy_factory = ProxyFactory(
ProxyFactory.deploy_proxy_factory_contract(
cls.ethereum_client, cls.ethereum_test_account
).contract_address,
cls.ethereum_client,
)

def build_test_safe(
self,
number_owners: int = 3,
threshold: Optional[int] = None,
owners: Optional[List[str]] = None,
fallback_handler: Optional[str] = None,
) -> SafeCreate2Tx:
salt_nonce = generate_salt_nonce()
owners = (
owners
if owners
else [Account.create().address for _ in range(number_owners)]
)
threshold = threshold if threshold else len(owners) - 1

gas_price = self.ethereum_client.w3.eth.gas_price
return Safe.build_safe_create2_tx(
self.ethereum_client,
self.safe_contract_address,
self.proxy_factory.address,
salt_nonce,
owners,
threshold,
fallback_handler=fallback_handler,
gas_price=gas_price,
payment_token=None,
fixed_creation_cost=0,
)

def deploy_test_safe(
self,
number_owners: int = 3,
threshold: Optional[int] = None,
owners: Optional[List[str]] = None,
initial_funding_wei: int = 0,
fallback_handler: Optional[str] = None,
) -> SafeCreate2Tx:
owners = (
owners
if owners
else [Account.create().address for _ in range(number_owners)]
)
if not threshold:
threshold = len(owners) - 1 if len(owners) > 1 else 1
safe_creation_tx = self.build_test_safe(
threshold=threshold, owners=owners, fallback_handler=fallback_handler
)
funder_account = self.ethereum_test_account

ethereum_tx_sent = self.proxy_factory.deploy_proxy_contract_with_nonce(
funder_account,
self.safe_contract_address,
safe_creation_tx.safe_setup_data,
safe_creation_tx.salt_nonce,
)

safe_address = ethereum_tx_sent.contract_address
if initial_funding_wei:
self.send_ether(safe_address, initial_funding_wei)

safe_instance = get_safe_contract(self.w3, safe_address)

self.assertEqual(safe_instance.functions.getThreshold().call(), threshold)
self.assertEqual(safe_instance.functions.getOwners().call(), owners)
self.assertEqual(safe_address, safe_creation_tx.safe_address)
return safe_creation_tx

def deploy_test_safe_v1_3_0(
self,
number_owners: int = 3,
threshold: Optional[int] = None,
owners: Optional[List[ChecksumAddress]] = None,
initial_funding_wei: int = 0,
fallback_handler: ChecksumAddress = NULL_ADDRESS,
) -> Safe:
owners = (
owners
if owners
else [Account.create().address for _ in range(number_owners)]
)
if not threshold:
threshold = len(owners) - 1 if len(owners) > 1 else 1
empty_parameters = {"gas": 1, "gasPrice": 1}
to = NULL_ADDRESS
data = b""
payment_token = NULL_ADDRESS
payment = 0
payment_receiver = NULL_ADDRESS
initializer = HexBytes(
get_safe_V1_3_0_contract(self.w3, address=NULL_ADDRESS)
.functions.setup(
owners,
threshold,
to,
data,
fallback_handler,
payment_token,
payment,
payment_receiver,
)
.build_transaction(empty_parameters)["data"]
)
ethereum_tx_sent = self.proxy_factory.deploy_proxy_contract(
self.ethereum_test_account,
self.safe_contract_V1_3_0_address,
initializer=initializer,
)
safe = Safe(ethereum_tx_sent.contract_address, self.ethereum_client)
if initial_funding_wei:
self.send_ether(safe.address, initial_funding_wei)

self.assertEqual(safe.retrieve_threshold(), threshold)
self.assertCountEqual(safe.retrieve_owners(), owners)

return safe

def setup_operator(self, number_owners: int = 1, version="1.1.1") -> SafeOperator:
class SafeCliTestCaseMixin(SafeTestCaseMixin):
def setup_operator(self, number_owners: int = 1, version="1.4.1") -> SafeOperator:
assert number_owners >= 1, "Number of owners cannot be less than 1!"
if version == "1.1.1":
safe_address = self.deploy_test_safe(
if version == "1.0.0":
safe = self.deploy_test_safe_v1_0_0(
owners=[self.ethereum_test_account.address]
)
elif version == "1.1.1":
safe = self.deploy_test_safe_v1_1_1(
owners=[self.ethereum_test_account.address]
).safe_address
)
elif version == "1.3.0":
safe_address = self.deploy_test_safe_v1_3_0(
safe = self.deploy_test_safe_v1_3_0(
owners=[self.ethereum_test_account.address]
)
elif version == "1.4.1":
safe = self.deploy_test_safe_v1_4_1(
owners=[self.ethereum_test_account.address]
).address
)
else:
raise ValueError(f"{version} not supported")
safe_operator = SafeOperator(safe_address, self.ethereum_node_url)
safe_operator = SafeOperator(safe.address, self.ethereum_node_url)
safe_operator.load_cli_owners([self.ethereum_test_account.key.hex()])
for _ in range(number_owners - 1):
account = Account.create()
safe_operator.add_owner(account.address)
safe_operator.load_cli_owners([account.key.hex()])
return safe_operator

def send_ether(self, to: str, value: int) -> bytes:
return self.w3.eth.send_transaction(
{"to": to, "value": value, "from": self.ethereum_test_account.address}
)
2 changes: 1 addition & 1 deletion tests/test_safe_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_safe_cli_happy_path(self):
owners=account_addresses,
threshold=2,
initial_funding_wei=self.w3.to_wei(1, "ether"),
).safe_address
).address
safe = Safe(safe_address, self.ethereum_client)
safe_operator = SafeOperator(safe_address, self.ethereum_node_url)
prompt_parser = PromptParser(safe_operator)
Expand Down
Loading

0 comments on commit 688bc16

Please sign in to comment.