diff --git a/pyproject.toml b/pyproject.toml index 17badefa3..76bcb9037 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,6 +133,7 @@ markers = [ "UniswapV2Factory", "Utils", "Safe", + "EF_TEST", ] [tool.isort] diff --git a/src/kakarot/library.cairo b/src/kakarot/library.cairo index ba8be1328..aa6856506 100644 --- a/src/kakarot/library.cairo +++ b/src/kakarot/library.cairo @@ -360,12 +360,6 @@ namespace Kakarot { assert success = TRUE; } - // TODO: add check that target contract does not exist or has empty bytecode - if (data_len == 0) { - let (return_data) = alloc(); - return (0, return_data); - } - if (to == 0) { with_attr error_message("Kakarot: value should be 0 when deploying a contract") { assert value = 0; diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index fc7d48362..815699a49 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -46,13 +46,45 @@ async def kakarot( return kakarot -@pytest_asyncio.fixture(scope="session") -async def addresses( +@pytest.fixture(scope="session") +def deploy_eoa( starknet, kakarot, externally_owned_account_class, fund_evm_address, deployer_address, +): + async def _factory(private_key=None): + if private_key is None: + private_key = generate_random_private_key() + + evm_address = private_key.public_key.to_checksum_address() + + # pre fund account so that fees can be paid back to deployer + await fund_evm_address(int(evm_address, 16)) + + eoa_deploy_tx = await kakarot.deploy_externally_owned_account( + int(evm_address, 16) + ).execute(caller_address=deployer_address) + + return Wallet( + address=evm_address, + private_key=private_key, + starknet_contract=StarknetContract( + starknet.state, + externally_owned_account_class.abi, + eoa_deploy_tx.call_info.internal_calls[0].contract_address, + eoa_deploy_tx, + ), + starknet_address=eoa_deploy_tx.call_info.internal_calls[0].contract_address, + ) + + return _factory + + +@pytest_asyncio.fixture(scope="session") +async def addresses( + deploy_eoa, ) -> List[Wallet]: """ Returns a list of addresses to be used in tests. @@ -73,30 +105,7 @@ async def addresses( wallets = [] for private_key in private_keys: - evm_address = private_key.public_key.to_checksum_address() - - # pre fund account so that fees can be paid back to deployer - await fund_evm_address(int(evm_address, 16)) - - eoa_deploy_tx = await kakarot.deploy_externally_owned_account( - int(evm_address, 16) - ).execute(caller_address=deployer_address) - - wallets.append( - Wallet( - address=evm_address, - private_key=private_key, - starknet_contract=StarknetContract( - starknet.state, - externally_owned_account_class.abi, - eoa_deploy_tx.call_info.internal_calls[0].contract_address, - eoa_deploy_tx, - ), - starknet_address=eoa_deploy_tx.call_info.internal_calls[ - 0 - ].contract_address, - ) - ) + wallets.append(await deploy_eoa(private_key)) return wallets diff --git a/tests/integration/solidity_contracts/EFTests/test_sha3.py b/tests/integration/solidity_contracts/EFTests/test_sha3.py new file mode 100644 index 000000000..b0a36c8f8 --- /dev/null +++ b/tests/integration/solidity_contracts/EFTests/test_sha3.py @@ -0,0 +1,42 @@ +import logging + +import pytest +from starkware.starknet.testing.contract import StarknetContract + +from tests.utils.helpers import hex_string_to_bytes_array + +logger = logging.getLogger() + + +@pytest.mark.asyncio +@pytest.mark.EF_TEST +class TestSha3: + @pytest.mark.skip( + "TODO: need to fix when return_data is shorter than retSize in CallHelper.finalize_calling_context" + ) + async def test_sha3_d0g0v0_Shanghai( + self, + owner, + create_account_with_bytecode, + kakarot: StarknetContract, + ): + called_contract = await create_account_with_bytecode("0x600060002060005500") + caller_contract = await create_account_with_bytecode( + "0x604060206010600f6000600435610100016001600003f100" + ) + + res = await kakarot.eth_send_transaction( + origin=int(owner.address, 16), + to=int(caller_contract.evm_contract_address, 16), + gas_limit=1_000_000, + gas_price=0, + value=0, + data=hex_string_to_bytes_array( + # In the original EF test, the called contract is supposed to be set in genesis + # at address 0x000000000000000000000000000000000000010{i} while the payload of + # the tx uses {i}, hence we sub 0x100 to the real deployed called_address + f"0x693c6139{int(called_contract.evm_contract_address, 16) - 0x100:064x}" + ), + ).execute(caller_address=owner.starknet_address) + sha3 = called_contract.storage(0).call() + assert res == sha3 diff --git a/tests/integration/solidity_contracts/PlainOpcodes/test_plain_opcodes.py b/tests/integration/solidity_contracts/PlainOpcodes/test_plain_opcodes.py index 018bfbb21..3a39e0151 100644 --- a/tests/integration/solidity_contracts/PlainOpcodes/test_plain_opcodes.py +++ b/tests/integration/solidity_contracts/PlainOpcodes/test_plain_opcodes.py @@ -178,7 +178,7 @@ async def test_should_create_counters( evm_addresses = await plain_opcodes.create( bytecode=counter.constructor().data_in_transaction, count=count, - caller_address=plain_opcodes_deployer.starknet_address, + caller_address=plain_opcodes_deployer, ) assert len(evm_addresses) == count for evm_address in evm_addresses: diff --git a/tests/integration/solidity_contracts/conftest.py b/tests/integration/solidity_contracts/conftest.py index 96d3f11f8..95fa05fd3 100644 --- a/tests/integration/solidity_contracts/conftest.py +++ b/tests/integration/solidity_contracts/conftest.py @@ -5,11 +5,11 @@ calculate_contract_address_from_hash, ) from starkware.starknet.testing.contract import StarknetContract +from starkware.starknet.testing.contract_utils import gather_deprecated_compiled_class from web3 import Web3 from tests.utils.contracts import get_contract, use_kakarot_backend -from tests.utils.helpers import hex_string_to_bytes_array -from tests.utils.reporting import traceit +from tests.utils.helpers import generate_random_private_key, hex_string_to_bytes_array logger = logging.getLogger() @@ -64,7 +64,48 @@ def _factory( @pytest.fixture(scope="package") -def deploy_solidity_contract(kakarot, get_solidity_contract): +def deploy_bytecode(kakarot, deploy_eoa): + """ + Fixture to deploy a bytecode in kakarot. Returns the EVM address of the deployed contract, + the corresponding starknet address and the starknet transaction. + """ + + async def _factory(bytecode: str, caller_eoa=None): + """ + This factory is what is actually returned by pytest when requesting the `deploy_bytecode` + fixture. + """ + if caller_eoa is None: + caller_eoa = await deploy_eoa( + generate_random_private_key(int(bytecode, 16)) + ) + + tx = await kakarot.eth_send_transaction( + origin=int(caller_eoa.address, 16), + to=0, + gas_limit=1_000_000, + gas_price=0, + value=0, + data=hex_string_to_bytes_array(bytecode), + ).execute(caller_address=caller_eoa.starknet_address) + + deploy_event = [ + e + for e in tx.main_call_events + if type(e).__name__ == "evm_contract_deployed" + ][0] + + starknet_contract_address = deploy_event.starknet_contract_address + evm_contract_address = Web3.to_checksum_address( + f"{deploy_event.evm_contract_address:040x}" + ) + return evm_contract_address, starknet_contract_address, tx + + return _factory + + +@pytest.fixture(scope="package") +def deploy_solidity_contract(deploy_bytecode, get_solidity_contract): """ Fixture to deploy a solidity contract in kakarot. The returned contract is a modified web3.contract instance with an added `contract_account` attribute that return the actual @@ -83,33 +124,10 @@ async def _factory(contract_app, contract_name, *args, **kwargs): is required and filtered out before calling the constructor. """ contract = get_contract(contract_app, contract_name) - if "caller_eoa" not in kwargs: - raise ValueError( - "caller_eoa needs to be given in kwargs for deploying the contract" - ) - caller_eoa = kwargs["caller_eoa"] - del kwargs["caller_eoa"] - deploy_bytecode = hex_string_to_bytes_array( - contract.constructor(*args, **kwargs).data_in_transaction - ) - with traceit.context(contract_name): - tx = await kakarot.eth_send_transaction( - origin=int(caller_eoa.address, 16), - to=0, - gas_limit=1_000_000, - gas_price=0, - value=0, - data=deploy_bytecode, - ).execute(caller_address=caller_eoa.starknet_address) - - deploy_event = [ - e - for e in tx.main_call_events - if type(e).__name__ == "evm_contract_deployed" - ][0] - starknet_contract_address = deploy_event.starknet_contract_address - evm_contract_address = Web3.to_checksum_address( - f"{deploy_event.evm_contract_address:040x}" + caller_eoa = kwargs.pop("caller_eoa", None) + evm_contract_address, starknet_contract_address, tx = await deploy_bytecode( + contract.constructor(*args, **kwargs).data_in_transaction, + caller_eoa, ) return get_solidity_contract( contract_app, @@ -120,3 +138,47 @@ async def _factory(contract_app, contract_name, *args, **kwargs): ) return _factory + + +@pytest.fixture(scope="package") +def create_account_with_bytecode(starknet, kakarot, deploy_bytecode, deploy_eoa): + """ + Fixture to create a solidity contract in kakarot without running the bytecode. + The given bytecode is directly stored into the account, similarly to what is done + in a genesis config. + + Returns the corresponding starknet contract with the extra evm_contract_address attribute. + """ + + async def _factory(bytecode: str, caller_eoa=None): + """ + This factory is what is actually returned by pytest when requesting the `create_account_with_bytecode` + fixture. + """ + if caller_eoa is None: + caller_eoa = await deploy_eoa( + generate_random_private_key(int(bytecode, 16)) + ) + + evm_contract_address, starknet_contract_address, _ = await deploy_bytecode( + "", + caller_eoa, + ) + contract_class = gather_deprecated_compiled_class( + source="./src/kakarot/accounts/contract/contract_account.cairo", + cairo_path=["src"], + disable_hint_validation=True, + ) + contract = StarknetContract( + starknet.state, + contract_class.abi, + starknet_contract_address, + None, + ) + await contract.write_bytecode(hex_string_to_bytes_array(bytecode)).execute( + caller_address=kakarot.contract_address + ) + setattr(contract, "evm_contract_address", evm_contract_address) + return contract + + return _factory