ethereum / py-evm

A Python implementation of the Ethereum Virtual Machine
https://py-evm.readthedocs.io/en/latest/
MIT License
2.22k stars 637 forks source link

AttributeError when calling opcode `SELFDESTRUCT` in py-evm #2176

Closed Alleysira closed 2 months ago

Alleysira commented 2 months ago

What happened?

Hi, guys. I'm fuzzing the py-evm py-evm 0.8.0b1 with other EVM implementations and I found that when calling opcode 0xFF SELFDESTRUCT , the funtion apply_computation() at eth/vm/computation.py will raise an AttributeError as AttributeError: 'function' object has no attribute 'mnemonic'. The corresponding line of python codes is opcode_fn = computation.get_opcode_fn(opcode). I will give an json file with relevant trace of opcodes, maybe this will be helpful. And the simple solidity contract I executed is:

contract ForceEth {
    constructor() payable {}

    receive() external payable {}

    function forceEth(address to) public {
        selfdestruct(payable(to));
    }
}

The parameter I used to call the contract is 0xb349cb320000000000000000000000004dead63c2002b5f7997626feaca7f7ed0ba6ccf0. I'd like to know whether this is sort of bug. If you need further information, feel free to reach out. Thanks for your time!

Code that produced the error

from eth import constants
from eth.db.atomic import AtomicDB
from eth import constants
from eth.chains.base import MiningChain
from eth_utils import (to_wei, decode_hex,  to_canonical_address,)
from eth.vm.forks.shanghai import ShanghaiVM
from eth_typing import Address
from eth_keys import keys
from eth.tools.transaction import new_transaction
from cytoolz import assoc
import argparse

def parse_args():
    """
    Parse input arguments
    """
    parser = argparse.ArgumentParser(description='Test a transaction')

    # contract runtime bytecode  $ solc xxx.sol --bin-runtime
    parser.add_argument('--data', dest='data', default='', type=str)
    # function signature bytecode
    parser.add_argument('--sig', dest='signature', default='', type=str)

    args = parser.parse_args()
    return args

def funded_address_initial_balance():
    return to_wei(0xffff, 'ether')

def base_genesis_state(funded_address, funded_address_initial_balance):
    return {
        funded_address: {
            'balance': funded_address_initial_balance,
            'nonce': 0,
            'code': b'',
            'storage': {},
        }
    }

def funded_address_private_key():
    return keys.PrivateKey(
        decode_hex('0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8')
    )

def genesis_state(base_genesis_state,simple_contract_address, bytecode):
    # base_genesis_state is a dict, simple_contract_address is key, {b,n,c,s} is value :)
    result = assoc(
        base_genesis_state,
        simple_contract_address,
        {
            'balance': 0,
            'nonce': 0,
            'code': decode_hex(bytecode),  # contract bytecode
            'storage': {},
        },
    )
    return result

GENESIS_PARAMS = {
     'coinbase': Address(0x000000000000000000000000000000000000abcd.to_bytes(20,'big')),
     'transaction_root': constants.BLANK_ROOT_HASH,
     'receipt_root': constants.BLANK_ROOT_HASH,
     'difficulty': 0,
     'gas_limit': 0xffffff,
     'timestamp': 0,
     'extra_data': constants.GENESIS_EXTRA_DATA,
     'nonce': b'\x00' * 8
}

def main():
    args = parse_args()

    init_address = to_canonical_address("8888f1f195afa192cfee860698584c030f4c9db1")

    base_state = base_genesis_state(init_address, funded_address_initial_balance())

    simple_contract_address = to_canonical_address("0x692a70d2e424a56d2c6c27aa97d1a86395877b3a")

    klass = MiningChain.configure(
        __name__='MyTestChain',
        vm_configuration=(
            (constants.GENESIS_BLOCK_NUMBER,ShanghaiVM),
        )
    )

    SENDER = to_canonical_address("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")
    SENDER_PRIVATE_KEY = funded_address_private_key()

    GENESIS_STATE = genesis_state(base_state, simple_contract_address,args.data)
    chain = klass.from_genesis(AtomicDB(), GENESIS_PARAMS, GENESIS_STATE)

    call_txn = new_transaction(
        vm = chain.get_vm(),
        from_ = to_canonical_address("0x1c7cd2d37ffd63856a5bd56a9af1643f2bcf545f"),
        to = simple_contract_address,
        gas=0xffffff,
        data=decode_hex(args.signature),
    )
    result_bytes = chain.get_transaction_result(call_txn, chain.get_canonical_head())

if __name__ == '__main__':
    main()

Full error output

Traceback (most recent call last):
  File "/home/alleysira/EVMFuzzer/benchmarkEVMs/py-evm/runBytecode.py", line 109, in <module>
    main()
  File "/home/alleysira/EVMFuzzer/benchmarkEVMs/py-evm/runBytecode.py", line 105, in main
    result_bytes = chain.get_transaction_result(call_txn, chain.get_canonical_head())
  File "/home/alleysira/EVMFuzzer/env/lib/python3.8/site-packages/eth/chains/base.py", line 479, in get_transaction_result
    computation = state.costless_execute_transaction(transaction)
  File "/home/alleysira/EVMFuzzer/env/lib/python3.8/site-packages/eth/vm/state.py", line 283, in costless_execute_transaction
    return self.apply_transaction(free_transaction)
  File "/home/alleysira/EVMFuzzer/env/lib/python3.8/site-packages/eth/vm/forks/frontier/state.py", line 219, in apply_transaction
    return executor(transaction)
  File "/home/alleysira/EVMFuzzer/env/lib/python3.8/site-packages/eth/vm/state.py", line 330, in __call__
    computation = self.build_computation(message, transaction)
  File "/home/alleysira/EVMFuzzer/env/lib/python3.8/site-packages/eth/vm/forks/berlin/state.py", line 37, in build_computation
    return super().build_computation(message, transaction)
  File "/home/alleysira/EVMFuzzer/env/lib/python3.8/site-packages/eth/vm/forks/frontier/state.py", line 147, in build_computation
    computation = self.vm_state.computation_class.apply_message(
  File "/home/alleysira/EVMFuzzer/env/lib/python3.8/site-packages/eth/vm/forks/frontier/computation.py", line 87, in apply_message
    computation = cls.apply_computation(
  File "/home/alleysira/EVMFuzzer/env/lib/python3.8/site-packages/eth/vm/computation.py", line 354, in apply_computation
    opcode_fn.mnemonic,
AttributeError: 'function' object has no attribute 'mnemonic'

Fill this section in if you know how this could or should be fixed

I noticed that you guys changed the way to get opcode_fn to the following commands, don't know whether this problem will still happen.

            for opcode in computation.code:
                try:
                    opcode_fn = opcode_lookup[opcode]
                except KeyError:
                    opcode_fn = InvalidOpcode(opcode)

py-evm Version

0.8.0b1

Python Version

Python 3.8.10

Operating System

linux

Output from pip-freeze

attrs==19.3.0
Automat==0.8.0
blinker==1.4
cached-property==1.5.2
certifi==2019.11.28
chardet==3.0.4
Click==7.0
cloud-init==23.3.1
colorama==0.4.3
command-not-found==0.3
configobj==5.0.6
constantly==15.1.0
contourpy==1.1.1
cryptography==2.8
cycler==0.12.1
cytoolz==0.12.2
dbus-python==1.2.16
distlib==0.3.7
distro==1.4.0
distro-info==0.23+ubuntu1.1
entrypoints==0.3
eth-bloom==2.0.0
eth-hash==0.5.2
eth-keys==0.4.0
eth-typing==3.4.0
eth-utils==2.2.1
eth_abi==5.1.0
exceptiongroup==1.1.3
filelock==3.12.4
fonttools==4.44.0
hexbytes==0.3.1
httplib2==0.14.0
hyperlink==19.0.0
idna==2.8
importlib-metadata==1.5.0
importlib-resources==6.1.0
incremental==16.10.1
iniconfig==2.0.0
Jinja2==2.10.1
jsonpatch==1.22
jsonpointer==2.0
jsonschema==3.2.0
keyring==18.0.1
kiwisolver==1.4.5
language-selector==0.1
launchpadlib==1.10.13
lazr.restfulclient==0.14.2
lazr.uri==1.0.3
logzero==1.7.0
lru-dict==1.2.0
MarkupSafe==1.1.0
matplotlib==3.7.3
more-itertools==4.2.0
mypy-extensions==1.0.0
netifaces==0.10.4
numpy==1.24.4
oauthlib==3.1.0
packaging==23.1
parsimonious==0.10.0
pexpect==4.6.0
Pillow==10.1.0
platformdirs==3.10.0
pluggy==1.3.0
py-ecc==6.0.0
py-evm==0.8.0b1
pyasn1==0.4.2
pyasn1-modules==0.2.1
pycryptodome==3.19.0
pyethash==0.1.27
Pygments==2.3.1
PyGObject==3.36.0
PyHamcrest==1.9.0
PyJWT==1.7.1
pymacaroons==0.13.0
PyNaCl==1.3.0
pyOpenSSL==19.0.0
pyparsing==3.1.1
pyrsistent==0.15.5
pyserial==3.4
pytest==7.4.2
python-apt==2.0.1+ubuntu0.20.4.1
python-dateutil==2.8.2
python-debian==0.1.36+ubuntu1.1
PyYAML==5.3.1
regex==2024.4.16
requests==2.22.0
requests-unixsocket==0.2.0
rlp==3.0.0
SecretStorage==2.3.1
service-identity==18.1.0
simplejson==3.16.0
six==1.16.0
solc-select==1.0.4
sortedcontainers==2.4.0
sos==4.5.6
ssh-import-id==5.10
systemd-python==234
tomli==2.0.1
toolz==0.12.0
trie==2.1.1
Twisted==18.9.0
ubuntu-advantage-tools==8001
ufw==0.36
unattended-upgrades==0.1
urllib3==1.25.8
virtualenv==20.24.5
wadllib==1.3.3
zipp==3.17.0
zope.interface==4.7.1
fselmo commented 2 months ago

Hey @Alleysira, can you please start testing with the latest version of the library? This was only relevant when debug logging. It was indeed a "bug" but it has since been fixed.