Skip to content
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
14 changes: 12 additions & 2 deletions src/ethereum/frontier/vm/gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand Down
26 changes: 19 additions & 7 deletions src/ethereum/frontier/vm/instructions/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand All @@ -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)

Expand Down
93 changes: 93 additions & 0 deletions src/ethereum/utils/safe_arithmetic.py
Original file line number Diff line number Diff line change
@@ -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
25 changes: 12 additions & 13 deletions tests/frontier/test_state_transition.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down