-
Notifications
You must be signed in to change notification settings - Fork 8
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: add delegates #41
base: main
Are you sure you want to change the base?
Conversation
880928f
to
1f62100
Compare
d28f656
to
74baeec
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty great PR! I had some nits.
Is there anything particular you want me to verify? I can take it for a spin soon.
for delegates in self.client.get_delegates().values(): | ||
yield from delegates | ||
|
||
def propose_safe_tx( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the propose
cli method could probably be refactored to utilize this now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looked into this, but seems more complicated than I'm willing to deal with now
Think I will try to refactor that CLI method later
If you can try adding and removing a delegate, I've been doing it and it works for me but curious on the experience (which may improve with ApeWorX/ape#1966) |
Thoughts from testing:
but I am wondering if we would wanto enrich those values, like if they are known accounst, use aliases here? btw I would fix the second and fourth items and make tickets for the first and third items |
Related to those, on both add / remove, if you slightly mispell the alias, it just comes back as None and tell you to use a keyfile account again and i was confused for a bit. i think most of the annoyance stems from the submitter callback |
ohhh , one more thing i just noticed. When i do:
for a safe with no delegates, it outputs:
feedback: |
Yes I want to do this too. There are some ways to deploy a safe at the same address on all networks, I have done this for one of our safes but not the other. But still need to detect if it is deployed on the network you wish to use |
I am working on a PR for core that'll bypass this issue, and then I'll make it accept a wider range of inputs so you can be more specific with it |
Agree, should really just be INFO |
@fubuloubu the signing features we wanted are in Core now, we should update this and get this out. |
74baeec
to
7439a4b
Compare
|
|
2, 3, and 4 solved with 2148cf0, I made issues for the 1st one |
6a7537d
to
3710d25
Compare
@@ -373,6 +373,60 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) | |||
} | |||
return self.safe_tx_def(**safe_tx) | |||
|
|||
def all_delegates(self) -> Iterator[AddressType]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generators are bad ux in interactive sessions.
change to delegates
property and make it return a list or a dict of address->label
moreover, since delegates is a client feature, it should only live in SafeClient
exclusively, so might as well remove it from SafeAccount
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can have a method for the iterator such as get_all_delegates()
and have the delegates
property use the iterator to make a dict
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my other point is delegates is not a feature of a safe wallet, but rather a feature of a safe backend api, this it should not be in SafeAccount
at all, but rather in SafeClient
.
it's completely off-chain and only affects the backend accepting txs from non-owners.
@@ -185,6 +188,53 @@ def estimate_gas_cost( | |||
gas = result.get("safeTxGas") | |||
return int(HexBytes(gas).hex(), 16) | |||
|
|||
def get_delegates(self) -> Dict[AddressType, List[AddressType]]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
labels got lost along the way, they are quite useful imo. the current anonymous structure is not immediately clear. maybe it should be a dict of delegates to their metadata like label and delegator. currently delegator is the key, but it's not the primary info here. or it could be a list[dataclass] where str() casts to address.
|
||
return delegates | ||
|
||
def add_delegate(self, delegate: AddressType, label: str, delegator: AccountAPI): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of delegator
we can say owner
, should be less confusing.
Co-authored-by: antazoey <[email protected]>
5e99cd1
to
318f4e9
Compare
""" | ||
Add a delegate for a signer in a Safe | ||
""" | ||
delegate = cli_ctx.conversion_manager.convert(delegate, AddressType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could be part of the argument:
@click.argument("delegate", callback=lambda ctx, _, val: ctx.obj.conversion_manager.convert(delegate, AddressType))
@delegates.command(cls=ConnectedProviderCommand) | ||
@safe_cli_ctx() | ||
@safe_option | ||
@click.argument("delegate", type=ChecksumAddress) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type=ChecksumAddress
is a bit wrong, since this implicitly handles ENS strings and such.
@delegates.command(cls=ConnectedProviderCommand) | ||
@safe_cli_ctx() | ||
@safe_option | ||
@click.argument("delegate", type=ChecksumAddress) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here re: the type is incorrect (since ENS domains work because we are converting them?)
this may just all be better with the callback validating the value into an address beforehand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(and line 60)
@@ -373,6 +373,60 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) | |||
} | |||
return self.safe_tx_def(**safe_tx) | |||
|
|||
def all_delegates(self) -> Iterator[AddressType]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can have a method for the iterator such as get_all_delegates()
and have the delegates
property use the iterator to make a dict
safe_tx: SafeTx, | ||
submitter: Union[AccountAPI, AddressType, str, None] = None, | ||
sigs_by_signer: Optional[dict[AddressType, MessageSignature]] = None, | ||
contractTransactionHash: Optional[SafeTxID] = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a note for why this has to be camelCase would be helpful
@@ -186,6 +189,53 @@ def estimate_gas_cost( | |||
gas = result.get("safeTxGas") | |||
return int(HexBytes(gas).hex(), 16) | |||
|
|||
def get_delegates(self) -> Dict[AddressType, List[AddressType]]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def get_delegates(self) -> Dict[AddressType, List[AddressType]]: | |
def get_delegates(self) -> dict[AddressType, list[AddressType]]: |
data = response.json() | ||
|
||
for delegate_info in map(DelegateInfo.model_validate, data.get("results", [])): | ||
if delegate_info.delegator not in delegates: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
idea: we can use defaultdict
again here
"delegate": delegate, | ||
"delegator": delegator.address, | ||
"label": label, | ||
"signature": sig.encode_rsv().hex(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: when we move to web3.py 7.0 series, this will not longer have a 0x prefix. I have been trying to use different ways of going to hex-str now in preparation for this
"delegator": delegator.address, | ||
"signature": sig.encode_rsv().hex(), | ||
} | ||
self._delete(f"delegates/{delegate}", json=payload) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: trailing slashes are important i think for all of Safe's APIs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true, the api states it needs a trailing slash https://safe-transaction-mainnet.safe.global/
@@ -15,6 +17,21 @@ | |||
TESTS_DIR = Path(__file__).parent.absolute() | |||
|
|||
|
|||
@pytest.fixture(autouse=True) | |||
def fix_eip712_signing(monkeypatch): | |||
# TODO: `ape_test.TestAccount.sign_message` doesn't support `EIP712Message` yet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it does now!
What I did
Adds support for delegates to the Client classes (including MockClient), the CLI (via
ape safe delegates ...
), as well as to the main accounts class (SafeAccount.propose_safe_tx
andSafeAccount.propose
)fixes: #16
How I did it
How to verify it
Checklist
- [ ] Documentation has been updated