Skip to content

Commit 042593b

Browse files
authored
feat(MultiSend): add direct calls (#108)
* fix(MultiSend): no reason to verify sum of value sent for Safe * feat(Multisend): use `.add_call` to send direct call; refactor use * fix(MultiSend): use better default argument
1 parent 6da204a commit 042593b

File tree

2 files changed

+53
-28
lines changed

2 files changed

+53
-28
lines changed

ape_safe/exceptions.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,6 @@ class MulticallException(ApeSafeException):
126126
pass
127127

128128

129-
class ValueRequired(MulticallException):
130-
def __init__(self, amount: int):
131-
super().__init__(f"This transaction must send at least '{amount / 1e18}' ether.")
132-
133-
134129
class UnsupportedChainError(MulticallException):
135130
def __init__(self):
136131
super().__init__("Multicall not supported on this chain.")

ape_safe/multisend.py

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@
1010
from ape_safe.client.types import OperationType, SafeTxID
1111

1212
from .accounts import SafeAccount, get_signatures
13-
from .exceptions import UnsupportedChainError, ValueRequired
13+
from .exceptions import UnsupportedChainError
1414
from .packages import MANIFESTS_BY_VERSION, PackageType, get_multisend
1515

1616
if TYPE_CHECKING:
1717
from ape.api import ReceiptAPI, TransactionAPI
18-
from ape.contracts.base import ContractInstance, ContractTransactionHandler
18+
from ape.contracts.base import (
19+
ContractInstance,
20+
ContractMethodHandler,
21+
ContractTransactionHandler,
22+
)
1923
from eip712.common import SafeTx
2024

2125

@@ -128,35 +132,63 @@ def contract(self) -> "ContractInstance":
128132
def handler(self) -> "ContractTransactionHandler":
129133
return self.contract.multiSend
130134

131-
def add(
135+
def add_call(
132136
self,
133-
call,
134-
*args,
137+
target: AddressType | str,
138+
data: HexBytes = "",
135139
value: int = 0,
136140
) -> "MultiSend":
137141
"""
138-
Append a call to the MultiSend session object.
142+
Append a raw call to the MultiSend session object.
139143
140144
Raises:
141145
:class:`InvalidOption`: If one of the kwarg modifiers is not able to be used.
142146
143147
Args:
144-
call: :class:`ContractMethodHandler` The method to call.
145-
*args: The arguments to invoke the method with.
148+
target: The address to send the message to.
149+
data: The data to send to the target. Defaults to empty.
146150
value: int The amount of ether to forward with the call. Defaults to 0.
147151
"""
148152
if value < 0:
149153
raise ValueError("`value=` must be positive.")
150154

151155
self.calls.append(
152156
{
153-
"target": call.contract.address,
157+
"target": self.conversion_manager.convert(target, AddressType),
154158
"value": value,
155-
"callData": call.encode_input(*args),
159+
"callData": data,
156160
}
157161
)
158162
return self
159163

164+
def add(
165+
self,
166+
call: "ContractMethodHandler",
167+
*args,
168+
value: int = 0,
169+
) -> "MultiSend":
170+
"""
171+
Append a call to the MultiSend session object.
172+
173+
Usage::
174+
175+
batch.add(contract.method1) # Method with no args
176+
batch.add(contract.method2, *args) # Method with args
177+
batch.add(contract.method3, *args, value=...) # Payable method
178+
179+
batch(...)
180+
181+
Raises:
182+
:class:`InvalidOption`: If one of the kwarg modifiers is not able to be used.
183+
184+
Args:
185+
call: :class:`ContractMethodHandler` The method to call.
186+
*args: The arguments to invoke the method with.
187+
value: int The amount of ether to forward with the call. Defaults to 0.
188+
"""
189+
190+
return self.add_call(call.contract, data=call.encode_input(*args), value=value)
191+
160192
def add_from_receipt(self, receipt: "ReceiptAPI") -> "MultiSend":
161193
"""
162194
Append a call to the MultiSend session object from a receipt.
@@ -169,24 +201,14 @@ def add_from_receipt(self, receipt: "ReceiptAPI") -> "MultiSend":
169201
assert contract.viewMethod() == ...
170202
batch.add_from_receipt(receipt)
171203
204+
# NOTE: `receipt` is no longer on the chain (only in fork)
172205
batch(...)
173206
174207
Args:
175208
receipt: :class:`~ape.api.ReceiptAPI` The receipt object to pull information from for the call to add.
176209
"""
177-
self.calls.append(
178-
{
179-
"target": receipt.receiver,
180-
"value": receipt.value,
181-
"callData": receipt.data,
182-
}
183-
)
184-
return self
185210

186-
def _validate_safe_tx(self, safe_tx: "SafeTx") -> None:
187-
required_value = sum(call["value"] for call in self.calls)
188-
if required_value > safe_tx.value:
189-
raise ValueRequired(required_value)
211+
return self.add_call(receipt.receiver, data=receipt.data, value=receipt.value)
190212

191213
@property
192214
def encoded_calls(self):
@@ -227,7 +249,6 @@ def propose(
227249
submitter = safe_tx_kwargs.pop("submitter", None)
228250
sigs_by_signer = safe_tx_kwargs.pop("sigs_by_signer", None)
229251
safe_tx = self.as_safe_tx(safe, **safe_tx_kwargs)
230-
self._validate_safe_tx(safe_tx)
231252
return safe.propose_safe_tx(
232253
safe_tx,
233254
submitter=submitter,
@@ -302,6 +323,15 @@ def add_from_calldata(self, calldata: bytes) -> "MultiSend":
302323
303324
Args:
304325
calldata: Calldata encoding the ``MultiSend.multiSend`` call.
326+
327+
Usage::
328+
329+
# NOTE: Previously submitted MultiSend SafeTx
330+
receipt = chain.get_receipt("0x...")
331+
batch.add_from_calldata(receipt.data)
332+
333+
# NOTE: `batch` now has all of the same calls in it as the MultiSend in `receipt` did
334+
batch(...)
305335
"""
306336
_, args = self.contract.decode_input(calldata)
307337
buffer = BytesIO(args["transactions"])

0 commit comments

Comments
 (0)