Skip to content

Commit 827cbc9

Browse files
antazoeyfubuloubu
andauthored
fix: handle user decline on transaction signing, fix install from source, and use ruff (#91)
* fix: handle user decline * docs: show how to install from source * fix: safe fixes * refactor(Account): remove `SignatureError`; streamline logic --------- Co-authored-by: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com>
1 parent 1ef8b1d commit 827cbc9

18 files changed

Lines changed: 232 additions & 82 deletions

File tree

.github/workflows/test.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ jobs:
2626
python -m pip install --upgrade pip
2727
pip install .[lint]
2828
29-
- name: Run Black
30-
run: black --check .
29+
- name: Run Ruff Lint
30+
run: ruff check .
3131

32-
- name: Run isort
33-
run: isort --check-only .
32+
- name: Run Ruff Format
33+
run: ruff format --check .
3434

35-
- name: Run flake8
36-
run: flake8 .
35+
- name: Run mdformat
36+
run: mdformat . --check
3737

3838
type-check:
3939
runs-on: ubuntu-latest

.pre-commit-config.yaml

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,21 @@ repos:
44
hooks:
55
- id: check-yaml
66

7-
- repo: https://github.com/PyCQA/isort
8-
rev: 5.13.2
7+
- repo: https://github.com/astral-sh/ruff-pre-commit
8+
rev: v0.12.0 # Latest ruff version
99
hooks:
10-
- id: isort
11-
12-
- repo: https://github.com/psf/black
13-
rev: 24.10.0
14-
15-
hooks:
16-
- id: black
17-
name: black
18-
19-
- repo: https://github.com/pycqa/flake8
20-
rev: 7.1.1
21-
hooks:
22-
- id: flake8
23-
additional_dependencies: [flake8-breakpoint, flake8-print, flake8-pydantic, flake8-type-checking]
10+
- id: ruff
11+
args: [--fix, --exit-non-zero-on-fix]
12+
- id: ruff-format
2413

2514
- repo: https://github.com/pre-commit/mirrors-mypy
26-
rev: v1.13.0
15+
rev: v1.16.1
2716
hooks:
2817
- id: mypy
29-
additional_dependencies: [types-requests, types-setuptools, pydantic]
18+
additional_dependencies: [types-setuptools, types-requests, pydantic]
3019

3120
- repo: https://github.com/executablebooks/mdformat
32-
rev: 0.7.19
21+
rev: 0.7.22
3322
hooks:
3423
- id: mdformat
3524
additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject]

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ You can install the latest release via [`pip`](https://pypi.org/project/pip/):
3333
$ pip install ape-safe
3434
```
3535

36+
You can also install from source:
37+
38+
```shell
39+
# Clone the source code.
40+
git clone git@github.com:ApeWorX/ape-safe.git
41+
cd ape-safe
42+
43+
# Build the Safe manifests.
44+
ape pm compile
45+
ape run build
46+
47+
# Install.
48+
pip install .
49+
```
50+
3651
### via `setuptools`
3752

3853
You can clone the repository and use [`setuptools`](https://github.com/pypa/setuptools) for the most up-to-date version:

ape_safe/_cli/click_ext.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,18 @@ def _get_execute_callback(cls, ctx, param, val, name: str = "execute"):
135135
return False
136136

137137
raise BadOptionUsage(
138-
f"--{name}", f"`--{name}` value '{val}` not a boolean or account identifier."
138+
f"--{name}",
139+
f"`--{name}` value '{val}` not a boolean or account identifier.",
139140
)
140141

141142

142143
callback_factory = CallbackFactory()
143144
safe_option = click.option("--safe", callback=callback_factory.safe_callback)
144145
safe_argument = click.argument("safe", callback=callback_factory.safe_callback)
145146
submitter_option = click.option(
146-
"--submitter", help="Account to execute", callback=callback_factory.submitter_callback
147+
"--submitter",
148+
help="Account to execute",
149+
callback=callback_factory.submitter_callback,
147150
)
148151
sender_option = click.option("--sender", callback=callback_factory.sender_callback)
149152
execute_option = click.option("--execute", callback=callback_factory.execute_callback)

ape_safe/_cli/pending.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ def propose(cli_ctx, ecosystem, safe, data, gas_price, value, receiver, nonce, s
162162
while new_tx is None and timeout > 0:
163163
new_tx = next(
164164
safe.client.get_transactions(
165-
starting_nonce=safe.next_nonce, confirmed=False, filter_by_ids=[safe_tx_hash]
165+
starting_nonce=safe.next_nonce,
166+
confirmed=False,
167+
filter_by_ids=[safe_tx_hash],
166168
),
167169
None,
168170
)
@@ -290,7 +292,7 @@ def reject(cli_ctx: SafeCliContext, safe, txn_ids, execute):
290292
"""
291293
from ape.api import AccountAPI
292294

293-
submit = False if execute in (False, None) else True
295+
submit = execute not in (False, None)
294296
submitter = execute if isinstance(execute, AccountAPI) else None
295297
if submitter is None and submit:
296298
submitter = safe.select_signer(for_="submitter")
@@ -315,7 +317,11 @@ def reject(cli_ctx: SafeCliContext, safe, txn_ids, execute):
315317
elif click.confirm(f"{txn}\nCancel Transaction?"):
316318
try:
317319
safe.transfer(
318-
safe, 0, nonce=txn.nonce, submit_transaction=submit, submitter=submitter
320+
safe,
321+
0,
322+
nonce=txn.nonce,
323+
submit_transaction=submit,
324+
submitter=submitter,
319325
)
320326
except SignatureError:
321327
# These are expected because of how the plugin works

ape_safe/accounts.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@
2121
from ethpm_types.abi import ABIType, MethodABI
2222
from packaging.version import Version
2323

24-
from .client import BaseSafeClient, MockSafeClient, SafeClient, SafeTx, SafeTxConfirmation, SafeTxID
24+
from .client import (
25+
BaseSafeClient,
26+
MockSafeClient,
27+
SafeClient,
28+
SafeTx,
29+
SafeTxConfirmation,
30+
SafeTxID,
31+
)
2532
from .config import SafeConfig
2633
from .exceptions import (
2734
ApeSafeError,
@@ -494,9 +501,10 @@ def propose(
494501

495502
def pending_transactions(self) -> Iterator[tuple[SafeTx, list[SafeTxConfirmation]]]:
496503
for executed_tx in self.client.get_transactions(confirmed=False):
497-
yield self.create_safe_tx(
498-
**executed_tx.model_dump(mode="json", by_alias=True)
499-
), executed_tx.confirmations
504+
yield (
505+
self.create_safe_tx(**executed_tx.model_dump(mode="json", by_alias=True)),
506+
executed_tx.confirmations,
507+
)
500508

501509
@property
502510
def local_signers(self) -> list[AccountAPI]:
@@ -849,13 +857,13 @@ def skip_signer(signer: AccountAPI):
849857
sigs_by_signer,
850858
**gas_args,
851859
nonce=submitter_account.nonce,
860+
# NOTE: Because of `ape_ethereum.transactions.BaseTransaction.serialize_transaction`
861+
# doing a recovered signer check, and we have to make sure the address matches
862+
# the recovered address of the signed transaction.
863+
sender=submitter_account.address,
852864
)
853-
txn = submitter_account.sign_transaction(exec_transaction, **signer_options)
854-
# NOTE: Because of `ape_ethereum.transactions.BaseTransaction.serialize_transaction`
855-
# doing a recovered signer check, and we gotta make sure the address matches
856-
# the recovered address of the signed transaction.
857-
txn.sender = submitter_account.address
858-
return txn
865+
# NOTE: If `submitter` does not sign, this returns `None` which raises downstream
866+
return submitter_account.sign_transaction(exec_transaction, **signer_options)
859867

860868
elif submit:
861869
# NOTE: User wanted to submit transaction, but we can't, so don't publish to API

ape_safe/client/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,10 @@ def post_transaction(
160160
b"",
161161
)
162162
)
163-
post_dict: dict = {"signature": signature.hex() if signature else None, "origin": ORIGIN}
163+
post_dict: dict = {
164+
"signature": signature.hex() if signature else None,
165+
"origin": ORIGIN,
166+
}
164167

165168
for key, value in tx_data.model_dump(by_alias=True, mode="json").items():
166169
if isinstance(value, HexBytes):

ape_safe/client/mock.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def __init__(self, contract: "ContractInstance"):
3030
self.contract = contract
3131
self.transactions: dict[SafeTxID, SafeApiTxData] = {}
3232
self.transactions_by_nonce: dict[int, list[SafeTxID]] = {}
33-
self.delegates: dict["AddressType", list["AddressType"]] = {}
33+
self.delegates: dict[AddressType, list[AddressType]] = {}
3434

3535
@property
3636
def safe_details(self) -> SafeDetails:
@@ -90,7 +90,10 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati
9090
yield from safe_tx_data.confirmations
9191

9292
def post_transaction(
93-
self, safe_tx: SafeTx, signatures: dict["AddressType", "MessageSignature"], **kwargs
93+
self,
94+
safe_tx: SafeTx,
95+
signatures: dict["AddressType", "MessageSignature"],
96+
**kwargs,
9497
):
9598
owners = self.safe_details.owners
9699

ape_safe/factory.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import secrets
2-
from functools import cache
3-
from typing import TYPE_CHECKING, Iterable, Optional, Union
2+
from collections.abc import Iterable
3+
from typing import TYPE_CHECKING, Optional, Union
44

55
from ape.types import AddressType
66
from ape.utils import ZERO_ADDRESS, ManagerAccessMixin
77
from packaging.version import Version
88

9-
from ape_safe.packages import MANIFESTS_BY_VERSION, PackageType, get_factory, get_singleton
9+
from ape_safe.packages import (
10+
MANIFESTS_BY_VERSION,
11+
PackageType,
12+
get_factory,
13+
get_singleton,
14+
)
1015

1116
if TYPE_CHECKING:
1217
from ape.api import AccountAPI, BaseAddress
@@ -22,7 +27,6 @@ def inject(cls, version: Version, deployer: "AccountAPI"):
2227
cls._singleton[version] = deployer.deploy(PackageType.SINGLETON(version))
2328
cls._factory[version] = deployer.deploy(PackageType.PROXY_FACTORY(version))
2429

25-
@cache
2630
def get_factory(self, version: Version) -> "ContractInstance":
2731
if injected_factory := self._factory.get(version):
2832
return injected_factory
@@ -33,7 +37,6 @@ def get_factory(self, version: Version) -> "ContractInstance":
3337
def contract(self) -> "ContractInstance":
3438
return self.get_factory(max(MANIFESTS_BY_VERSION))
3539

36-
@cache
3740
def get_singleton(self, version: Version) -> "ContractInstance":
3841
if injected_singleton := self._singleton.get(version):
3942
return injected_singleton

ape_safe/multisend.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,18 @@ class MultiSend(ManagerAccessMixin):
5252
def __init__(
5353
self,
5454
safe: Optional[SafeAccount] = None,
55-
version: Union[Version, str] = max(MANIFESTS_BY_VERSION),
55+
version: Union[Version, str, None] = None,
5656
) -> None:
5757
"""
5858
Initialize a new MultiSend session object. By default, there are no calls to make.
5959
"""
60+
version = version or max(MANIFESTS_BY_VERSION)
6061
self.calls: list[dict] = []
6162
self.safe = safe
6263
self.version = version if isinstance(version, Version) else Version(version)
6364

6465
@classmethod
65-
def inject(cls, version: Union[Version, str] = max(MANIFESTS_BY_VERSION)):
66+
def inject(cls, version: Union[Version, str, None] = None):
6667
"""
6768
Create the multisend module contract on-chain, so we can use it.
6869
Must use a provider that supports ``debug_setCode``.
@@ -71,11 +72,13 @@ def inject(cls, version: Union[Version, str] = max(MANIFESTS_BY_VERSION)):
7172
7273
from ape_safe.multisend import MultiSend
7374
75+
7476
@pytest.fixture(scope="session")
7577
def multisend():
7678
MultiSend.inject()
7779
return MultiSend()
7880
"""
81+
version = version or max(MANIFESTS_BY_VERSION)
7982
active_provider = cls.network_manager.active_provider
8083
assert active_provider, "Must be connected to an active network to deploy"
8184
MultiSend = PackageType.MULTISEND(version)

0 commit comments

Comments
 (0)