From 85eee8ccc01add2b5976ff2736da10649690cc43 Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Thu, 13 Jun 2024 01:24:35 +0400 Subject: [PATCH 1/4] feat: decode and add calls to multisend from calldata --- ape_safe/multisend.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/ape_safe/multisend.py b/ape_safe/multisend.py index 2cbcd68..96b91b2 100644 --- a/ape_safe/multisend.py +++ b/ape_safe/multisend.py @@ -1,6 +1,9 @@ +from io import BytesIO + +from ape import convert from ape.api import ReceiptAPI, TransactionAPI from ape.contracts.base import ContractInstance, ContractTransactionHandler -from ape.types import ContractType, HexBytes +from ape.types import AddressType, ContractType, HexBytes from ape.utils import ManagerAccessMixin, cached_property from eth_abi.packed import encode_packed @@ -253,3 +256,21 @@ def as_transaction(self, **txn_kwargs) -> TransactionAPI: data=self.handler.encode_input(b"".join(self.encoded_calls)), **txn_kwargs, ) + + def add_from_calldata(self, calldata: bytes): + _, args = self.contract.decode_input(calldata) + buffer = BytesIO(args["transactions"]) + while buffer.tell() < len(args["transactions"]): + operation = int.from_bytes(buffer.read(1), "big") + target = convert(buffer.read(20), AddressType) + value = int.from_bytes(buffer.read(32), "big") + length = int.from_bytes(buffer.read(32), "big") + data = HexBytes(buffer.read(length)) + self.calls.append( + { + "operation": operation, + "target": target, + "value": value, + "callData": data, + } + ) From 6e52692bd5db66dd0075a413618962102ed3ab4a Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Thu, 13 Jun 2024 01:25:01 +0400 Subject: [PATCH 2/4] test: decoded multisend encodes as calldata --- tests/functional/test_multisend.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/functional/test_multisend.py b/tests/functional/test_multisend.py index 9d4712b..d683f38 100644 --- a/tests/functional/test_multisend.py +++ b/tests/functional/test_multisend.py @@ -21,3 +21,11 @@ def test_no_operation(safe, token, vault, multisend): multisend.add(vault.transfer, safe, amount) with pytest.raises(SafeLogicError, match="Safe transaction failed"): multisend(sender=safe, operation=0) + + +def test_decode_multisend(multisend): + calldata = bytes.fromhex( + "8d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000016b00527e80008d212e2891c737ba8a2768a7337d7fd200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f0080878000000000000000000000000584bffc5f51ccae39ad69f1c399743620e619c2b00da18f789a1d9ad33e891253660fcf1332d236b2900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024e74b981b000000000000000000000000584bffc5f51ccae39ad69f1c399743620e619c2b0027b5739e22ad9033bcbf192059122d163b60349d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247a55036500000000000000000000000000000000000000000000000000002a1b324b8f68000000000000000000000000000000000000000000" + ) + multisend.add_from_calldata(calldata) + assert multisend.handler.encode_input(b"".join(multisend.encoded_calls)) == calldata From ff9a6b644d37bca7bdc7d3de7917cd8d3d2d0efa Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Thu, 13 Jun 2024 01:26:22 +0400 Subject: [PATCH 3/4] lint: happy bot --- tests/functional/test_multisend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/test_multisend.py b/tests/functional/test_multisend.py index d683f38..30fd84e 100644 --- a/tests/functional/test_multisend.py +++ b/tests/functional/test_multisend.py @@ -25,7 +25,7 @@ def test_no_operation(safe, token, vault, multisend): def test_decode_multisend(multisend): calldata = bytes.fromhex( - "8d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000016b00527e80008d212e2891c737ba8a2768a7337d7fd200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f0080878000000000000000000000000584bffc5f51ccae39ad69f1c399743620e619c2b00da18f789a1d9ad33e891253660fcf1332d236b2900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024e74b981b000000000000000000000000584bffc5f51ccae39ad69f1c399743620e619c2b0027b5739e22ad9033bcbf192059122d163b60349d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247a55036500000000000000000000000000000000000000000000000000002a1b324b8f68000000000000000000000000000000000000000000" + "8d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000016b00527e80008d212e2891c737ba8a2768a7337d7fd200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f0080878000000000000000000000000584bffc5f51ccae39ad69f1c399743620e619c2b00da18f789a1d9ad33e891253660fcf1332d236b2900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024e74b981b000000000000000000000000584bffc5f51ccae39ad69f1c399743620e619c2b0027b5739e22ad9033bcbf192059122d163b60349d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247a55036500000000000000000000000000000000000000000000000000002a1b324b8f68000000000000000000000000000000000000000000" # noqa: E501 ) multisend.add_from_calldata(calldata) assert multisend.handler.encode_input(b"".join(multisend.encoded_calls)) == calldata From 626211faf08b61d1d3bbde5b55a57da58391a26d Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Thu, 13 Jun 2024 01:58:42 +0400 Subject: [PATCH 4/4] docs: rename multicall to multisend, fix wrong example, decode docs --- ape_safe/multisend.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/ape_safe/multisend.py b/ape_safe/multisend.py index 96b91b2..43572b9 100644 --- a/ape_safe/multisend.py +++ b/ape_safe/multisend.py @@ -115,24 +115,24 @@ class MultiSend(ManagerAccessMixin): def __init__(self) -> None: """ - Initialize a new Multicall session object. By default, there are no calls to make. + Initialize a new MultiSend session object. By default, there are no calls to make. """ self.calls: list[dict] = [] @classmethod def inject(cls): """ - Create the multicall module contract on-chain, so we can use it. + Create the multisend module contract on-chain, so we can use it. Must use a provider that supports ``debug_setCode``. Usage example:: - from ape_ethereum import multicall + from ape_safe.multisend import MultiSend @pytest.fixture(scope="session") - def use_multicall(): - # NOTE: use this fixture any test where you want to use a multicall - multicall.BaseMulticall.deploy() + def multisend(): + MultiSend.inject() + return MultiSend() """ active_provider = cls.network_manager.active_provider assert active_provider, "Must be connected to an active network to deploy" @@ -169,7 +169,7 @@ def add( value=0, ) -> "MultiSend": """ - Adds a call to the Multicall session object. + Append a call to the MultiSend session object. Raises: :class:`InvalidOption`: If one of the kwarg modifiers is not able to be used. @@ -221,11 +221,11 @@ def encoded_calls(self): def __call__(self, **txn_kwargs) -> ReceiptAPI: """ - Execute the Multicall transaction. The transaction will broadcast again every time + Execute the MultiSend transaction. The transaction will broadcast again every time the ``Transaction`` object is called. Raises: - :class:`UnsupportedChain`: If there is not an instance of Multicall3 deployed + :class:`UnsupportedChain`: If there is not an instance of MultiSend deployed on the current chain at the expected address. Args: @@ -241,7 +241,7 @@ def __call__(self, **txn_kwargs) -> ReceiptAPI: def as_transaction(self, **txn_kwargs) -> TransactionAPI: """ - Encode the Multicall transaction as a ``TransactionAPI`` object, but do not execute it. + Encode the MultiSend transaction as a ``TransactionAPI`` object, but do not execute it. Returns: :class:`~ape.api.transactions.TransactionAPI` @@ -258,6 +258,12 @@ def as_transaction(self, **txn_kwargs) -> TransactionAPI: ) def add_from_calldata(self, calldata: bytes): + """ + Decode all calls from a multisend calldata and add them to this MultiSend. + + Args: + calldata: Calldata encoding the MultiSend.multiSend call + """ _, args = self.contract.decode_input(calldata) buffer = BytesIO(args["transactions"]) while buffer.tell() < len(args["transactions"]):