diff --git a/test/functional/feature_evm_contract_env_vars.py b/test/functional/feature_evm_contract_env_vars.py index 3641a8c974..ed86c13026 100755 --- a/test/functional/feature_evm_contract_env_vars.py +++ b/test/functional/feature_evm_contract_env_vars.py @@ -78,7 +78,7 @@ def should_create_contract(self): ) node.generate(1) - abi, bytecode = EVMContract.from_file( + abi, bytecode, _ = EVMContract.from_file( "GlobalVariable.sol", "GlobalVariable" ).compile() compiled = node.w3.eth.contract(abi=abi, bytecode=bytecode) diff --git a/test/functional/feature_evm_contracts.py b/test/functional/feature_evm_contracts.py index 312c89ae30..23d91148f6 100755 --- a/test/functional/feature_evm_contracts.py +++ b/test/functional/feature_evm_contracts.py @@ -9,6 +9,8 @@ from test_framework.test_framework import DefiTestFramework from test_framework.evm_contract import EVMContract from test_framework.evm_key_pair import EvmKeyPair +from test_framework.test_node import TestNode +from test_framework.util import assert_raises_web3_error class EVMTest(DefiTestFramework): @@ -60,11 +62,43 @@ def setup(self): ) self.nodes[0].generate(1) - def should_create_contract(self): - node = self.nodes[0] - self.evm_key_pair = EvmKeyPair.from_node(node) + def generate_contract(self, node: TestNode, num_functions: int, contract_name: str): + contract_start = f""" + pragma solidity ^0.8.0; + contract {contract_name} {{ + """ + function_template = ( + lambda index: f""" + function func{index}() public pure returns(uint256) {{ + return {index}; + }}""" + ) + contract_end = "\n}" + + list_sig = [] + contract_body = "" + + for i in range(0, num_functions): + func_sig = f"func${i}()" + sig_hash = self.node.w3.keccak(text=func_sig)[:4] + if sig_hash in list_sig: + continue + list_sig.append(sig_hash) + contract_body += function_template(i) + + utf8_source_code = contract_start + contract_body + contract_end + + abi, bytecode, runtime_bytecode = EVMContract.from_str( + utf8_source_code, contract_name + ).compile() + compiled_contract = self.node.w3.eth.contract(abi=abi, bytecode=bytecode) + + return compiled_contract, runtime_bytecode + + def should_deploy_contract_less_than_1KB(self): + self.evm_key_pair = EvmKeyPair.from_node(self.node) - node.transferdomain( + self.node.transferdomain( [ { "src": {"address": self.address, "amount": "50@DFI", "domain": 2}, @@ -76,120 +110,257 @@ def should_create_contract(self): } ] ) - node.generate(1) + self.node.generate(1) - abi, bytecode = EVMContract.from_file("SimpleStorage.sol", "Test").compile() - compiled = node.w3.eth.contract(abi=abi, bytecode=bytecode) + abi, bytecode, _ = EVMContract.from_file("SimpleStorage.sol", "Test").compile() + compiled = self.node.w3.eth.contract(abi=abi, bytecode=bytecode) tx = compiled.constructor().build_transaction( { - "chainId": node.w3.eth.chain_id, - "nonce": node.w3.eth.get_transaction_count(self.evm_key_pair.address), + "chainId": self.node.w3.eth.chain_id, + "nonce": self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ), "maxFeePerGas": 10_000_000_000, "maxPriorityFeePerGas": 1_500_000_000, "gas": 1_000_000, } ) - signed = node.w3.eth.account.sign_transaction(tx, self.evm_key_pair.privkey) - hash = node.w3.eth.send_raw_transaction(signed.rawTransaction) + signed = self.node.w3.eth.account.sign_transaction( + tx, self.evm_key_pair.privkey + ) + hash = self.node.w3.eth.send_raw_transaction(signed.rawTransaction) - node.generate(1) + self.node.generate(1) - receipt = node.w3.eth.wait_for_transaction_receipt(hash) - self.contract = node.w3.eth.contract( + receipt = self.node.w3.eth.wait_for_transaction_receipt(hash) + self.contract = self.node.w3.eth.contract( address=receipt["contractAddress"], abi=abi ) + size_of_runtime_bytecode = len(self.node.w3.eth.get_code(self.contract.address)) + assert_equal(size_of_runtime_bytecode, 323) def should_contract_get_set(self): # set variable - node = self.nodes[0] tx = self.contract.functions.store(10).build_transaction( { - "chainId": node.w3.eth.chain_id, - "nonce": node.w3.eth.get_transaction_count(self.evm_key_pair.address), + "chainId": self.node.w3.eth.chain_id, + "nonce": self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ), "gasPrice": 10_000_000_000, } ) - signed = node.w3.eth.account.sign_transaction(tx, self.evm_key_pair.privkey) - hash = node.w3.eth.send_raw_transaction(signed.rawTransaction) + signed = self.node.w3.eth.account.sign_transaction( + tx, self.evm_key_pair.privkey + ) + hash = self.node.w3.eth.send_raw_transaction(signed.rawTransaction) - node.generate(1) + self.node.generate(1) - node.w3.eth.wait_for_transaction_receipt(hash) + self.node.w3.eth.wait_for_transaction_receipt(hash) # get variable assert_equal(self.contract.functions.retrieve().call(), 10) def failed_tx_should_increment_nonce(self): - node = self.nodes[0] - - abi, bytecode = EVMContract.from_file("Reverter.sol", "Reverter").compile() - compiled = node.w3.eth.contract(abi=abi, bytecode=bytecode) + abi, bytecode, _ = EVMContract.from_file("Reverter.sol", "Reverter").compile() + compiled = self.node.w3.eth.contract(abi=abi, bytecode=bytecode) tx = compiled.constructor().build_transaction( { - "chainId": node.w3.eth.chain_id, - "nonce": node.w3.eth.get_transaction_count(self.evm_key_pair.address), + "chainId": self.node.w3.eth.chain_id, + "nonce": self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ), "maxFeePerGas": 10_000_000_000, "maxPriorityFeePerGas": 1_500_000_000, "gas": 1_000_000, } ) - signed = node.w3.eth.account.sign_transaction(tx, self.evm_key_pair.privkey) - hash = node.w3.eth.send_raw_transaction(signed.rawTransaction) + signed = self.node.w3.eth.account.sign_transaction( + tx, self.evm_key_pair.privkey + ) + hash = self.node.w3.eth.send_raw_transaction(signed.rawTransaction) - node.generate(1) + self.node.generate(1) - receipt = node.w3.eth.wait_for_transaction_receipt(hash) - contract = node.w3.eth.contract(address=receipt["contractAddress"], abi=abi) + receipt = self.node.w3.eth.wait_for_transaction_receipt(hash) + contract = self.node.w3.eth.contract( + address=receipt["contractAddress"], abi=abi + ) # for successful TX - before_tx_count = node.w3.eth.get_transaction_count(self.evm_key_pair.address) + before_tx_count = self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ) tx = contract.functions.trySuccess().build_transaction( { - "chainId": node.w3.eth.chain_id, - "nonce": node.w3.eth.get_transaction_count(self.evm_key_pair.address), + "chainId": self.node.w3.eth.chain_id, + "nonce": self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ), "gasPrice": 10_000_000_000, } ) - signed = node.w3.eth.account.sign_transaction(tx, self.evm_key_pair.privkey) - hash = node.w3.eth.send_raw_transaction(signed.rawTransaction) - node.generate(1) - node.w3.eth.wait_for_transaction_receipt(hash) + signed = self.node.w3.eth.account.sign_transaction( + tx, self.evm_key_pair.privkey + ) + hash = self.node.w3.eth.send_raw_transaction(signed.rawTransaction) + self.node.generate(1) + self.node.w3.eth.wait_for_transaction_receipt(hash) - after_tx_count = node.w3.eth.get_transaction_count(self.evm_key_pair.address) + after_tx_count = self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ) assert_equal(before_tx_count + 1, after_tx_count) # for failed TX - before_tx_count = node.w3.eth.get_transaction_count(self.evm_key_pair.address) + before_tx_count = self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ) tx = contract.functions.tryRevert().build_transaction( { - "chainId": node.w3.eth.chain_id, - "nonce": node.w3.eth.get_transaction_count(self.evm_key_pair.address), + "chainId": self.node.w3.eth.chain_id, + "nonce": self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ), "gasPrice": 10_000_000_000, } ) - signed = node.w3.eth.account.sign_transaction(tx, self.evm_key_pair.privkey) - hash = node.w3.eth.send_raw_transaction(signed.rawTransaction) - node.generate(1) - node.w3.eth.wait_for_transaction_receipt(hash) + signed = self.node.w3.eth.account.sign_transaction( + tx, self.evm_key_pair.privkey + ) + hash = self.node.w3.eth.send_raw_transaction(signed.rawTransaction) + self.node.generate(1) + self.node.w3.eth.wait_for_transaction_receipt(hash) - after_tx_count = node.w3.eth.get_transaction_count(self.evm_key_pair.address) + after_tx_count = self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ) assert_equal(before_tx_count + 1, after_tx_count) + def should_deploy_contract_with_different_sizes(self): + test_data = [ + (128, "ContractSize1KBTo10KB", 6901), + (256, "ContractSize10KBTo19KB", 13685), + (400, "ContractSize20KBTo29KB", 21140), + ] + for iteration, contract_name, expected_runtime_bytecode_size in test_data: + compiled_contract, compiler_runtime_bytecode = self.generate_contract( + self.node, iteration, contract_name + ) + + tx = compiled_contract.constructor().build_transaction( + { + "chainId": self.node.w3.eth.chain_id, + "nonce": self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ), + "maxFeePerGas": 10_000_000_000, + "maxPriorityFeePerGas": 1_500_000_000, + } + ) + signed = self.node.w3.eth.account.sign_transaction( + tx, self.evm_key_pair.privkey + ) + hash = self.node.w3.eth.send_raw_transaction(signed.rawTransaction) + + self.node.generate(1) + receipt = self.node.w3.eth.wait_for_transaction_receipt(hash) + runtime_bytecode = self.node.w3.eth.get_code(receipt["contractAddress"]) + size_of_runtime_bytecode = len(runtime_bytecode) + assert_equal(receipt["status"], 1) + assert_equal(size_of_runtime_bytecode, expected_runtime_bytecode_size) + # sanity check for the equality between the runtime bytecode generated by the compiler + # and the runtime code deployed + assert_equal(compiler_runtime_bytecode, runtime_bytecode.hex()[2:]) + + # EIP 170, contract size is limited to 24576 bytes + # this test deploys a smart contract with an estimated size larger than this number + def fail_deploy_contract_extremely_large_runtime_code(self): + compiled_contract, compiler_runtime_bytecode = self.generate_contract( + self.node, 2**9 - 1, "ContractLargeRunTimeCode" + ) + assert_equal(len(compiler_runtime_bytecode) / 2, 27458) + + tx = compiled_contract.constructor().build_transaction( + { + "chainId": self.node.w3.eth.chain_id, + "nonce": self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ), + "maxFeePerGas": 10_000_000_000, + "maxPriorityFeePerGas": 1_500_000_000, + } + ) + signed = self.node.w3.eth.account.sign_transaction( + tx, self.evm_key_pair.privkey + ) + hash = self.node.w3.eth.send_raw_transaction(signed.rawTransaction) + + self.node.generate(1) + + receipt = self.node.w3.eth.wait_for_transaction_receipt(hash) + size_of_runtime_bytecode = len( + self.node.w3.eth.get_code(receipt["contractAddress"]) + ) + assert_equal(receipt["status"], 0) + assert_equal(size_of_runtime_bytecode, 0) + + # EIP 3860, contract initcode is limited up till 49152 bytes + # This test takes in a contract with init code of 243542 bytes + # However, because the current implementation of DMC limits the size of EVM transaction to 32768 bytes + # the error returned is evm tx size too large + def fail_deploy_contract_extremely_large_init_code(self): + compiled_contract, _ = self.generate_contract( + self.node, 2**12 - 1, "ContractLargeInitCode" + ) + + tx = compiled_contract.constructor().build_transaction( + { + "chainId": self.node.w3.eth.chain_id, + "nonce": self.node.w3.eth.get_transaction_count( + self.evm_key_pair.address + ), + "maxFeePerGas": 10_000_000_000, + "maxPriorityFeePerGas": 1_500_000_000, + } + ) + # to check the init code is larger than 49152 + assert_equal((len(tx["data"]) - 2) / 2, 243542) + signed = self.node.w3.eth.account.sign_transaction( + tx, self.evm_key_pair.privkey + ) + + assert_raises_web3_error( + -32001, + "Test EvmTxTx execution failed:\nevm tx size too large", + self.node.w3.eth.send_raw_transaction, + signed.rawTransaction, + ) + def run_test(self): self.setup() - self.should_create_contract() + self.node = self.nodes[0] + + self.should_deploy_contract_less_than_1KB() self.should_contract_get_set() self.failed_tx_should_increment_nonce() + self.should_deploy_contract_with_different_sizes() + + self.fail_deploy_contract_extremely_large_runtime_code() + + self.fail_deploy_contract_extremely_large_init_code() + if __name__ == "__main__": EVMTest().main() diff --git a/test/functional/feature_evm_logs.py b/test/functional/feature_evm_logs.py index f1a742301b..c4531991fb 100755 --- a/test/functional/feature_evm_logs.py +++ b/test/functional/feature_evm_logs.py @@ -80,7 +80,7 @@ def should_create_contract(self): node.generate(1) self.contract = EVMContract.from_file("Events.sol", "TestEvents") - abi, bytecode = self.contract.compile() + abi, bytecode, _ = self.contract.compile() compiled = node.w3.eth.contract(abi=abi, bytecode=bytecode) self.abi = abi self.event_abi = compiled._find_matching_event_abi("NumberStored") diff --git a/test/functional/feature_evm_miner.py b/test/functional/feature_evm_miner.py index 1c215e3a9c..8940b5fa0a 100644 --- a/test/functional/feature_evm_miner.py +++ b/test/functional/feature_evm_miner.py @@ -107,7 +107,7 @@ def rollback_and_clear_mempool(self): def mempool_block_limit(self): self.rollback_and_clear_mempool() - abi, bytecode = EVMContract.from_file("Loop.sol", "Loop").compile() + abi, bytecode, _ = EVMContract.from_file("Loop.sol", "Loop").compile() compiled = self.nodes[0].w3.eth.contract(abi=abi, bytecode=bytecode) tx = compiled.constructor().build_transaction( { @@ -281,7 +281,7 @@ def test_for_fee_mismatch_between_block_and_queue(self): ) assert_equal(before_balance, Decimal("100")) - abi, bytecode = EVMContract.from_file( + abi, bytecode, _ = EVMContract.from_file( "StateChange.sol", "StateChange" ).compile() compiled = self.nodes[0].w3.eth.contract(abi=abi, bytecode=bytecode) diff --git a/test/functional/test_framework/evm_contract.py b/test/functional/test_framework/evm_contract.py index a64f5806c0..34edd7df4e 100644 --- a/test/functional/test_framework/evm_contract.py +++ b/test/functional/test_framework/evm_contract.py @@ -30,6 +30,10 @@ def from_file(file_name: str, contract_name: str): ) as file: return EVMContract(file.read(), file_name, contract_name) + @staticmethod + def from_str(sourceCode: str, contract_name: str): + return EVMContract(sourceCode, f"{contract_name}.sol", contract_name) + def compile(self) -> (List[Dict], str): compiled_sol = compile_standard( { @@ -37,12 +41,7 @@ def compile(self) -> (List[Dict], str): "sources": {self.file_name: {"content": self.code}}, "settings": { "outputSelection": { - "*": { - "*": [ - "abi", - "evm.bytecode", - ] - } + "*": {"*": ["abi", "evm.bytecode", "evm.deployedBytecode"]} } }, }, @@ -52,5 +51,6 @@ def compile(self) -> (List[Dict], str): data = compiled_sol["contracts"][self.file_name][self.contract_name] abi = data["abi"] bytecode = data["evm"]["bytecode"]["object"] + deployedBytecode = data["evm"]["deployedBytecode"]["object"] - return abi, bytecode + return abi, bytecode, deployedBytecode diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 1fc4e0d6d7..cb33f5a3c7 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -62,6 +62,16 @@ def assert_raises(exc, fun, *args, **kwds): assert_raises_message(exc, None, fun, *args, **kwds) +def assert_raises_web3_error(code, message, fun, *args, **kwargs): + try: + fun(*args, **kwargs) + except ValueError as e: + assert_equal(e.args[0]["code"], code) + + if message not in e.args[0]["message"]: + raise AssertionError("Expected substring not found:" + e.args[0]["message"]) + + def assert_raises_message(exc, message, fun, *args, **kwds): try: fun(*args, **kwds)