From 8d823dc13ef5f869b02ca0700ba45be29bbc11a6 Mon Sep 17 00:00:00 2001 From: voith Date: Wed, 15 Sep 2021 17:32:19 +0530 Subject: [PATCH] fixed log tests that were failing because gas calculations would result in U256 to overflow --- src/ethereum/frontier/vm/gas.py | 14 ++- src/ethereum/frontier/vm/instructions/log.py | 26 ++++-- src/ethereum/utils/safe_arithmetic.py | 93 ++++++++++++++++++++ tests/frontier/test_state_transition.py | 25 +++--- 4 files changed, 136 insertions(+), 22 deletions(-) create mode 100644 src/ethereum/utils/safe_arithmetic.py diff --git a/src/ethereum/frontier/vm/gas.py b/src/ethereum/frontier/vm/gas.py index 9fbb9f96e5b..eed8b5eae30 100644 --- a/src/ethereum/frontier/vm/gas.py +++ b/src/ethereum/frontier/vm/gas.py @@ -15,6 +15,7 @@ from ethereum.frontier.eth_types import Address from ethereum.frontier.state import State, account_exists from ethereum.utils.numeric import ceil32 +from ethereum.utils.safe_arithmetic import u256_safe_add from .error import OutOfGasError @@ -96,7 +97,10 @@ def calculate_memory_gas_cost(size_in_bytes: Uint) -> U256: linear_cost = size_in_words * GAS_MEMORY quadratic_cost = size_in_words ** 2 // 512 total_gas_cost = linear_cost + quadratic_cost - return U256(total_gas_cost) + try: + return U256(total_gas_cost) + except ValueError: + raise OutOfGasError def calculate_gas_extend_memory( @@ -159,7 +163,13 @@ def calculate_call_gas_cost( _account_exists = account_exists(state, to) create_gas_cost = U256(0) if _account_exists else GAS_NEW_ACCOUNT transfer_gas_cost = U256(0) if value == 0 else GAS_CALL_VALUE - return GAS_CALL + gas + create_gas_cost + transfer_gas_cost + return u256_safe_add( + GAS_CALL, + gas, + create_gas_cost, + transfer_gas_cost, + exception_type=OutOfGasError, + ) def calculate_message_call_gas_stipend(value: U256) -> U256: diff --git a/src/ethereum/frontier/vm/instructions/log.py b/src/ethereum/frontier/vm/instructions/log.py index d922880b46f..b5d45bdf7d6 100644 --- a/src/ethereum/frontier/vm/instructions/log.py +++ b/src/ethereum/frontier/vm/instructions/log.py @@ -13,7 +13,9 @@ """ from functools import partial -from ethereum.base_types import Uint +from ethereum.base_types import U256, Uint +from ethereum.frontier.vm.error import OutOfGasError +from ethereum.utils.safe_arithmetic import u256_safe_add, u256_safe_multiply from ...eth_types import Log from .. import Evm @@ -28,7 +30,7 @@ from ..stack import pop -def log_n(evm: Evm, num_topics: int) -> None: +def log_n(evm: Evm, num_topics: U256) -> None: """ Appends a log entry, having `num_topics` topics, to the evm logs. @@ -52,11 +54,21 @@ def log_n(evm: Evm, num_topics: int) -> None: memory_start_index = Uint(pop(evm.stack)) size = pop(evm.stack) - gas_cost = ( - GAS_LOG - + (GAS_LOG_DATA * size) - + (GAS_LOG_TOPIC * num_topics) - + calculate_gas_extend_memory(evm.memory, memory_start_index, size) + gas_cost_log_data = u256_safe_multiply( + GAS_LOG_DATA, size, exception_type=OutOfGasError + ) + gas_cost_log_topic = u256_safe_multiply( + GAS_LOG_TOPIC, num_topics, exception_type=OutOfGasError + ) + gas_cost_memory_extend = calculate_gas_extend_memory( + evm.memory, memory_start_index, size + ) + gas_cost = u256_safe_add( + GAS_LOG, + gas_cost_log_data, + gas_cost_log_topic, + gas_cost_memory_extend, + exception_type=OutOfGasError, ) evm.gas_left = subtract_gas(evm.gas_left, gas_cost) diff --git a/src/ethereum/utils/safe_arithmetic.py b/src/ethereum/utils/safe_arithmetic.py new file mode 100644 index 00000000000..d1fde8500bc --- /dev/null +++ b/src/ethereum/utils/safe_arithmetic.py @@ -0,0 +1,93 @@ +""" +Safe Arithmetic for U256 Integer Type +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +..contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Safe arithmetic utility functions for U256 integer type. +""" +from typing import Optional, Type + +from ethereum.base_types import U256 + + +def u256_safe_add( + *numbers: U256, exception_type: Optional[Type[BaseException]] = None +) -> U256: + """ + Adds together the given sequence of numbers. If the total sum of the + numbers exceeds `U256.MAX_VALUE` then an exception is raised. + If `exception_type` = None then the exception raised defaults to the one + raised by `U256` when `U256.value > U256.MAX_VALUE` + else `exception_type` is raised. + + Parameters + ---------- + numbers : + The sequence of numbers that need to be added together. + + exception_type: + The exception that needs to be raised if the sum of the `numbers` + exceeds `U256.MAX_VALUE`. + + Returns + ------- + result : `ethereum.base_types.U256` + The sum of the given sequence of numbers if the total is less than + `U256.MAX_VALUE` else an exception is raised. + If `exception_type` = None then the exception raised defaults to the + one raised by `U256` when `U256.value > U256.MAX_VALUE` + else `exception_type` is raised. + """ + try: + return U256(sum(numbers)) + except ValueError as e: + if exception_type: + raise exception_type from e + else: + raise e + + +def u256_safe_multiply( + *numbers: U256, exception_type: Optional[Type[BaseException]] = None +) -> U256: + """ + Multiplies together the given sequence of numbers. If the net product of + the numbers exceeds `U256.MAX_VALUE` then an exception is raised. + If `exception_type` = None then the exception raised defaults to the one + raised by `U256` when `U256.value > U256.MAX_VALUE` else + `exception_type` is raised. + + Parameters + ---------- + numbers : + The sequence of numbers that need to be multiplies together. + + exception_type: + The exception that needs to be raised if the sum of the `numbers` + exceeds `U256.MAX_VALUE`. + + Returns + ------- + result : `ethereum.base_types.U256` + The multiplication product of the given sequence of numbers if the + net product is less than `U256.MAX_VALUE` else an exception is raised. + If `exception_type` = None then the exception raised defaults to the + one raised by `U256` when `U256.value > U256.MAX_VALUE` + else `exception_type` is raised. + """ + result = numbers[0] + try: + for number in numbers[1:]: + result *= number + return result + except ValueError as e: + if exception_type: + raise exception_type from e + else: + raise e diff --git a/tests/frontier/test_state_transition.py b/tests/frontier/test_state_transition.py index 7c73d563285..17e8438f06e 100644 --- a/tests/frontier/test_state_transition.py +++ b/tests/frontier/test_state_transition.py @@ -93,19 +93,18 @@ def test_transaction_init(test_file: str) -> None: "log4_nonEmptyMem_d0g0v0.json", "log4_nonEmptyMem_logMemSize1_d0g0v0.json", "log4_nonEmptyMem_logMemSize1_logMemStart31_d0g0v0.json", - # FIXME: The tests below are failing because gas calculation is causing - # U256 to overflow. These tests are supposed to fail with OOG exception - # "log0_logMemStartTooHigh_d0g0v0.json", - # "log0_logMemsizeTooHigh_d0g0v0.json", - # "log1_logMemStartTooHigh_d0g0v0.json", - # "log1_logMemsizeTooHigh_d0g0v0.json", - # "log2_logMemStartTooHigh_d0g0v0.json", - # "log2_logMemsizeTooHigh_d0g0v0.json", - # "log3_logMemStartTooHigh_d0g0v0.json", - # "log3_logMemsizeTooHigh_d0g0v0.json", - # "log4_logMemStartTooHigh_d0g0v0.json", - # "log4_logMemsizeTooHigh_d0g0v0.json", - # "logInOOG_Call_d0g0v0.json", + "log0_logMemStartTooHigh_d0g0v0.json", + "log0_logMemsizeTooHigh_d0g0v0.json", + "log1_logMemStartTooHigh_d0g0v0.json", + "log1_logMemsizeTooHigh_d0g0v0.json", + "log2_logMemStartTooHigh_d0g0v0.json", + "log2_logMemsizeTooHigh_d0g0v0.json", + "log3_logMemStartTooHigh_d0g0v0.json", + "log3_logMemsizeTooHigh_d0g0v0.json", + "log4_logMemStartTooHigh_d0g0v0.json", + "log4_logMemsizeTooHigh_d0g0v0.json", + # FIXME: The receipt root doesn't match for the test below + # "logInOOG_Call_d0g0v0.json", ], ) def test_log_operations(test_file: str) -> None: