Skip to content
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: multisend contract detection #58

Merged
merged 9 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ape_safe/manifests/multisend.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"contractTypes":{"MultiSendCallOnly":{"abi":[{"inputs":[{"internalType":"bytes","name":"transactions","type":"bytes"}],"name":"multiSend","outputs":[],"stateMutability":"payable","type":"function"}],"contractName":"MultiSendCallOnly","methodIdentifiers":{"multiSend(bytes)":"0x8d80ff0a"},"runtimeBytecode":{"bytecode":"0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100dc6004803603602081101561003957600080fd5b810190808035906020019064010000000081111561005657600080fd5b82018360208201111561006857600080fd5b8035906020019184600183028401116401000000008311171561008a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506100de565b005b805160205b8181101561015f578083015160f81c6001820184015160601c60158301850151603584018601516055850187016000856000811461012857600181146101385761013d565b6000808585888a5af1915061013d565b600080fd5b50600081141561014c57600080fd5b82605501870196505050505050506100e3565b50505056fea264697066735822122035246402746c96964495cae5b36461fd44dfb89f8e6cf6f6b8d60c0aa89f414864736f6c63430007060033"}}},"manifest":"ethpm/3","name":"multisend","version":"v1.3.0"}
108 changes: 19 additions & 89 deletions ape_safe/multisend.py
Original file line number Diff line number Diff line change
@@ -1,86 +1,24 @@
from importlib.resources import files
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 AddressType, ContractType, HexBytes
from ape.types import AddressType, HexBytes
from ape.utils import ManagerAccessMixin, cached_property
from eth_abi.packed import encode_packed
from ethpm_types import PackageManifest

from ape_safe.exceptions import UnsupportedChainError, ValueRequired


# NOTE: Function name is constant-like because it is used to assemble a constant
# TODO: Do this better
def MULTISEND_CODE(address) -> HexBytes:
return HexBytes(
"0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100dc600480360"
"3602081101561003957600080fd5b810190808035906020019064010000000081111561005657600080fd5b82"
"018360208201111561006857600080fd5b8035906020019184600183028401116401000000008311171561008"
"a57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383"
"80828437600081840152601f19601f8201169050808301925050505050505091929192905050506100de565b0"
f"05b7f000000000000000000000000{address[2:].lower()}73ffffffffffffffffffffffffffffffffffff"
"ffff163073ffffffffffffffffffffffffffffffffffffffff161415610183576040517f08c379a0000000000"
"00000000000000000000000000000000000000000000000815260040180806020018281038252603081526020"
"01806102106030913960400191505060405180910390fd5b805160205b8181101561020a578083015160f81c6"
"001820184015160601c6015830185015160358401860151605585018701600085600081146101cd5760018114"
"6101dd576101e8565b6000808585888a5af191506101e8565b6000808585895af491505b5060008114156101f"
"757600080fd5b8260550187019650505050505050610188565b50505056fe4d756c746953656e642073686f75"
"6c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca26469706673582212205"
"c784303626eec02b71940b551976170b500a8a36cc5adcbeb2c19751a76d05464736f6c63430007060033"
)


DEFAULT_ADDRESS = "0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761"
DEPLOYMENT_ADDRESS = {
10: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
25: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
28: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
61: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
63: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
69: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
82: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
83: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
106: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
111: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
288: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
322: "0x6367360366E4c898488091ac315834B779d8f561",
338: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
420: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
588: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
595: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
599: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
686: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
787: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
1001: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
1088: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
1294: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
7700: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
8217: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
10000: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
10001: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
42220: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
43114: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
54211: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
71401: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
71402: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
11155111: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
1666600000: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
1666700000: "0x998739BFdAAdde7C933B942a68053933098f9EDa",
}
MULTISEND_CONTRACT_TYPE = {
"contractName": "MultiSend",
"abi": [
{"inputs": [], "stateMutability": "nonpayable", "type": "constructor"},
{
"inputs": [{"internalType": "bytes", "name": "transactions", "type": "bytes"}],
"name": "multiSend",
"outputs": [],
"stateMutability": "payable",
"type": "function",
},
],
}
MULTISEND_CALL_ONLY_ADDRESSES = (
"0x40A2aCCbd92BCA938b02010E17A5b8929b49130D", # MultiSend Call Only v1.3.0
"0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B", # MultiSend Call Only v1.3.0 (EIP-155)
)
MULTISEND_CALL_ONLY_MANIFEST = PackageManifest.model_validate_json(
files("ape_safe").joinpath("manifests/multisend.json").read_text()
)
MULTISEND_CALL_ONLY = MULTISEND_CALL_ONLY_MANIFEST.contract_types["MultiSendCallOnly"] # type: ignore # noqa: E501


class MultiSend(ManagerAccessMixin):
Expand Down Expand Up @@ -138,24 +76,18 @@ def multisend():
assert active_provider, "Must be connected to an active network to deploy"

active_provider.set_code(
DEFAULT_ADDRESS,
MULTISEND_CODE(DEFAULT_ADDRESS),
MULTISEND_CALL_ONLY_ADDRESSES[0], MULTISEND_CALL_ONLY.get_runtime_bytecode()
)

@cached_property
def contract(self) -> ContractInstance:
multisend_address = DEPLOYMENT_ADDRESS.get(self.provider.chain_id, DEFAULT_ADDRESS)

# All versions have this ABI
contract = self.chain_manager.contracts.instance_at(
multisend_address,
contract_type=ContractType.model_validate(MULTISEND_CONTRACT_TYPE),
)

if contract.code != MULTISEND_CODE(multisend_address):
raise UnsupportedChainError()
for address in MULTISEND_CALL_ONLY_ADDRESSES:
if self.provider.get_code(address) == MULTISEND_CALL_ONLY.get_runtime_bytecode():
return self.chain_manager.contracts.instance_at(
address, contract_type=MULTISEND_CALL_ONLY
)

return contract
raise UnsupportedChainError()

@property
def handler(self) -> ContractTransactionHandler:
Expand All @@ -165,7 +97,6 @@ def add(
self,
call,
*args,
delegatecall=False,
value=0,
) -> "MultiSend":
"""
Expand All @@ -177,12 +108,11 @@ def add(
Args:
call: :class:`ContractMethodHandler` The method to call.
*args: The arguments to invoke the method with.
delegatecall: bool Whether the call should be processed using delegatecall.
value: int The amount of ether to forward with the call.
"""
self.calls.append(
{
"operation": int(delegatecall),
"operation": 0,
"target": call.contract.address,
"value": value or 0,
"callData": call.encode_input(*args),
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
zip_safe=False,
keywords="ethereum",
packages=find_packages(exclude=["tests", "tests.*"]),
package_data={"ape_safe": ["py.typed"]},
package_data={"ape_safe": ["py.typed", "manifests/*"]},
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
Expand Down
13 changes: 0 additions & 13 deletions tests/functional/test_multisend.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import pytest

from ape_safe.exceptions import SafeLogicError


def test_asset(vault, token):
assert vault.asset() == token

Expand All @@ -15,14 +10,6 @@ def test_default_operation(safe, token, vault, multisend):
assert receipt.txn_hash


def test_no_operation(safe, token, vault, multisend):
amount = token.balanceOf(safe)
multisend.add(token.approve, vault, 123)
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" # noqa: E501
Expand Down
Loading