Skip to content

Commit 4c49f44

Browse files
authored
feat(MultiSend): add .add_from_receipt method (#94)
* feat(MultiSend): add `.add_from_receipt` method Add method to append calls from transaction receipts to a MultiSend batch * style: remove empty whitespace Added usage example for add_from_receipt method. * test(MultiSend): add a test demonstrating use of `.add_from_receipt` * docs(MultiSend): add section on simulating side effects, using new feat
1 parent 91956b4 commit 4c49f44

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

ape_safe/multisend.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,32 @@ def add(
157157
)
158158
return self
159159

160+
def add_from_receipt(self, receipt: "ReceiptAPI") -> "MultiSend":
161+
"""
162+
Append a call to the MultiSend session object from a receipt.
163+
Especially useful for more complex simulations.
164+
165+
Usage::
166+
167+
with networks.fork():
168+
receipt = contract.method(*args, sender=safe.address)
169+
assert contract.viewMethod() == ...
170+
batch.add_from_receipt(receipt)
171+
172+
batch(...)
173+
174+
Args:
175+
receipt: :class:`~ape.api.ReceiptAPI` The receipt object to pull information from for the call to add.
176+
"""
177+
self.calls.append(
178+
{
179+
"target": receipt.receiver,
180+
"value": receipt.value,
181+
"callData": receipt.data,
182+
}
183+
)
184+
return self
185+
160186
def _validate_safe_tx(self, safe_tx: "SafeTx") -> None:
161187
required_value = sum(call["value"] for call in self.calls)
162188
if required_value > safe_tx.value:

docs/userguides/multisend.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,41 @@ signatures = safe.add_signatures(safe_tx)
6666
receipt = safe.submit_safe_tx(safe_tx)
6767
```
6868

69+
## Simulating Side Effects in Scripts
70+
71+
Typically, it is useful to validate your multisend scripts through the use of simulating their
72+
side effects via a "forked" network context.
73+
We have provided a feature `batch.add_from_receipt` which allows you to add calls directly from
74+
the results of simulated network calls (executing as the Safe itself), which reduces human error.
75+
76+
```python
77+
# Create MultiSend transaction outside the fork context (important!)
78+
# NOTE: You should be connected to a live "public" network when executing this
79+
batch = safe.create_batch()
80+
81+
# Enter a "simulated" context as a "fork" of the public network you want to propose to
82+
with networks.fork():
83+
# NOTE: We must use `sender=safe.address` to skip ape-safe's local signer processing
84+
# NOTE: You typically must provide an eth balance to `safe.address`, or it will fail
85+
# due to no funds to pay for gas in order to perform the transaction.
86+
receipt = dai.approve(vault, amount, sender=safe.address)
87+
# NOTE: You can now test "side effects" of the transaction here.
88+
assert dai.allowance(safe, vault) == amount
89+
# This will add the call as a step in the MultiSend batch
90+
batch.add_from_receipt(receipt)
91+
92+
# ...add as many steps as you would like to the batch while inside this forked context.
93+
receipt = vault.deposit(amount, sender=safe.address)
94+
assert vault.balanceOf(safe) == amount
95+
batch.add_from_receipt(receipt)
96+
97+
# After exiting the forked context, submit it as a real transaction on-chain
98+
batch(submitter=me)
99+
100+
# Or, propose to the API
101+
safe_tx = batch.propose(submitter=me)
102+
```
103+
69104
## Decoding Existing MultiSend Transactions
70105

71106
You can decode and inspect existing MultiSend transactions:

tests/functional/test_multisend.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,35 @@ def test_default_operation(safe, deployer, token, vault, mode):
3030
assert receipt.txn_hash
3131

3232

33+
def test_using_multisend_from_receipts(chain, safe, deployer, token, vault):
34+
amount = 100
35+
token.DEBUG_mint(safe, amount, sender=deployer)
36+
37+
assert vault.asset() == token
38+
batch = safe.create_batch()
39+
40+
with chain.isolate():
41+
# HACK: Contract address must have balance to send txns directly
42+
safe.balance += int(1e18)
43+
44+
# NOTE: Execute these transactions in an simulated context
45+
receipt = token.approve(vault, amount, sender=safe.address)
46+
assert token.allowance(safe, vault) == amount
47+
batch.add_from_receipt(receipt)
48+
49+
receipt = vault.deposit(amount, safe, sender=safe.address)
50+
assert vault.balanceOf(safe) == amount
51+
batch.add_from_receipt(receipt)
52+
53+
# NOTE: Context was reverted, so no side-effects exist
54+
assert token.allowance(safe, vault) == 0
55+
assert vault.balanceOf(safe) == 0
56+
57+
batch(submitter=deployer)
58+
59+
assert vault.balanceOf(safe) == amount
60+
61+
3362
def test_decode_multisend(new_multisend):
3463
calldata = bytes.fromhex(
3564
"8d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000016b00527e80008d212e2891c737ba8a2768a7337d7fd200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f0080878000000000000000000000000584bffc5f51ccae39ad69f1c399743620e619c2b00da18f789a1d9ad33e891253660fcf1332d236b2900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024e74b981b000000000000000000000000584bffc5f51ccae39ad69f1c399743620e619c2b0027b5739e22ad9033bcbf192059122d163b60349d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247a55036500000000000000000000000000000000000000000000000000002a1b324b8f68000000000000000000000000000000000000000000" # noqa: E501

0 commit comments

Comments
 (0)