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

feat: support all versions of Safe #70

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
22 changes: 22 additions & 0 deletions ape-config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# NOTE: For testing only
contracts_folder: tests/contracts

plugins:
Expand All @@ -12,7 +13,28 @@ ethereum:
local:
default_provider: foundry

# NOTE: Build before release w/ `ape pm compile`
dependencies:
- name: safe-contracts
github: safe-global/safe-smart-account
version: v1.1.1
config_override:
compile:
exclude:
- mocks
- proxies
- interfaces

dependencies:
- name: openzeppelin
github: OpenZeppelin/openzeppelin-contracts
version: 2.2.0

solidity:
version: 0.5.14
import_remapping:
- "@openzeppelin/contracts=openzeppelin/v2.2.0"

- name: safe-contracts
github: safe-global/safe-smart-account
version: v1.3.0
Expand Down
33 changes: 29 additions & 4 deletions ape_safe/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ape.api import AccountAPI, AccountContainerAPI, ReceiptAPI, TransactionAPI
from ape.api.networks import ForkedNetworkAPI
from ape.cli import select_account
from ape.contracts import ContractCall
from ape.exceptions import ContractNotFoundError, ProviderNotConnectedError
from ape.logging import logger
from ape.managers.accounts import AccountManager, TestAccountManager
Expand All @@ -16,6 +17,8 @@
from eip712.common import create_safe_tx_def
from eth_utils import keccak, to_bytes, to_int
from ethpm_types import ContractType
from ethpm_types.abi import ABIType, MethodABI
from packaging.version import Version

from ape_safe.client import (
BaseSafeClient,
Expand Down Expand Up @@ -276,11 +279,31 @@ def client(self) -> BaseSafeClient:
return SafeClient(address=self.address, chain_id=self.provider.chain_id)

@property
def version(self) -> str:
def version(self) -> Version:
try:
return self.client.safe_details.version.replace("+L2", "")
# NOTE: Shortcut w/ Safe API (if available)
version = self.client.safe_details.version.replace("+L2", "")

except Exception:
return self.contract.VERSION()
VERSION_ABI = MethodABI(
name="VERSION",
type="function",
stateMutability="view",
outputs=[ABIType(type="string")],
)
# NOTE: We want to make a direct call, so we can use this for loading
# the proper verison of the Safe protocol w/ `.contract`
version = ContractCall(VERSION_ABI, address=self.address)()
if not isinstance(version, str):
raise ContractNotFoundError(
self.address,
bool(self.provider.network.explorer),
f"{self.provider.network.ecosystem.name}:"
f"{self.provider.network.name}:"
f"{self.provider.name}",
)

return Version(version)

@property
def signers(self) -> list[AddressType]:
Expand All @@ -307,6 +330,7 @@ def next_nonce(self) -> int:
try:
return self.client.get_next_nonce()
except Exception:
# NOTE: `.nonce` conflicts with `AccountAPI.nonce`, so use `._view_methods_`
return self.contract._view_methods_["nonce"]()

@property
Expand All @@ -326,12 +350,13 @@ def new_nonce(self):
return self.next_nonce

def sign_message(self, msg: Any, **signer_options) -> Optional[MessageSignature]:
# TODO: Support signing via https://eips.ethereum.org/EIPS/eip-1271 in Ape
raise NotImplementedError("Safe accounts do not support message signing!")

@property
def safe_tx_def(self) -> type[SafeTx]:
return create_safe_tx_def(
version=self.version,
version=str(self.version),
contract_address=self.address,
chain_id=self.provider.chain_id,
)
Expand Down
11 changes: 7 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
TESTS_DIR = Path(__file__).parent.absolute()

# TODO: Test more versions.
VERSIONS = ("1.3.0",)
VERSIONS = ("1.3.0", "1.1.1")


@pytest.fixture(scope="session", autouse=True)
Expand All @@ -30,9 +30,12 @@ def config(project):
# Ensure we don't persist any .ape data.
with create_tempdir() as path:
# First, copy in Safe contracts so we don't download each time.
dest = path / "dest"
shutil.copytree(cfg.DATA_FOLDER, dest)
cfg.DATA_FOLDER = dest
src = cfg.DATA_FOLDER / "packages"
dest = path / "packages"
# NOTE: Only copy specific safe-global contracts (for local dev)
for subfolder in src.glob("*/safe-global*"):
shutil.copytree(subfolder, dest / subfolder.parent.name / subfolder.name)
cfg.DATA_FOLDER = path
yield cfg


Expand Down
Loading