Skip to content
Open
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
7 changes: 7 additions & 0 deletions .github/configs/feature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ monad:
# (triggered by tarball output) fails to proceed on no tests processed
# in 1st phase (exit code 5)
fill-params: --suppress-no-test-exit-code -m blockchain_test --from=MONAD_EIGHT --until=MONAD_NEXT --chain-id=143 -k "not eip4844 and not eip7002 and not eip7251 and not eip7685 and not eip6110 and not eip7594 and not eip7918 and not eip7610 and not eip7934 and not invalid_header"

mip3:
evm-type: develop
# --suppress-no-test-exit-code works around a problem where multi-phase fill
# (triggered by tarball output) fails to proceed on no tests processed
# in 1st phase (exit code 5)
fill-params: --suppress-no-test-exit-code -m blockchain_test --from=MONAD_EIGHT --until=MONAD_NEXT --chain-id=143 -k "not eip4844 and not eip7002 and not eip7251 and not eip7685 and not eip6110 and not eip7594 and not eip7918 and not eip7610 and not eip7934 and not invalid_header"
24 changes: 24 additions & 0 deletions packages/testing/src/execution_testing/forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3400,6 +3400,30 @@ def transaction_gas_limit_cap(
block_number=block_number, timestamp=timestamp
)

@classmethod
def memory_expansion_gas_calculator(
cls, *, block_number: int = 0, timestamp: int = 0
) -> MemoryExpansionGasCalculator:
"""
Return callable that calculates the gas cost of memory expansion for
the fork.
"""
del block_number, timestamp
memory_words_per_gas = 2

def fn(*, new_bytes: int, previous_bytes: int = 0) -> int:
if new_bytes <= previous_bytes:
return 0
new_words = ceiling_division(new_bytes, 32)
previous_words = ceiling_division(previous_bytes, 32)

def c(w: int) -> int:
return w // memory_words_per_gas

return c(new_words) - c(previous_words)

return fn


class BPO1(Osaka, bpo_fork=True):
"""Mainnet BPO1 fork - Blob Parameter Only fork 1."""
Expand Down
26 changes: 25 additions & 1 deletion src/ethereum/forks/monad_next/vm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,31 @@ class Message:
disable_create_opcodes: bool


@dataclass
class EvmMemory:
"""
Memory of the EVM.
"""

data: bytearray
high_watermark_bytes: int

def __len__(self) -> int:
"""Return the length of the memory data."""
return len(self.data)

def hex(self) -> str:
"""Return the hex string of the memory data."""
return self.data.hex()


@dataclass
class Evm:
"""The internal state of the virtual machine."""

pc: Uint
stack: List[U256]
memory: bytearray
memory: EvmMemory
code: Bytes
gas_left: Uint
valid_jump_destinations: Set[Uint]
Expand Down Expand Up @@ -177,6 +195,9 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None:
evm.accessed_addresses.update(child_evm.accessed_addresses)
evm.accessed_storage_keys.update(child_evm.accessed_storage_keys)

# NOTE: absence of `evm.memory`, in particular of its high watermark
# is intended for memory to deallocate on call frame exit.


def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None:
"""
Expand All @@ -191,3 +212,6 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None:

"""
evm.gas_left += child_evm.gas_left

# NOTE: absence of `evm.memory`, in particular of its high watermark
# is intended for memory to deallocate on call frame exit.
51 changes: 38 additions & 13 deletions src/ethereum/forks/monad_next/vm/gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from ..blocks import Header
from ..transactions import BlobTransaction, Transaction
from . import Evm
from . import Evm, EvmMemory
from .exceptions import OutOfGasError

GAS_JUMPDEST = Uint(1)
Expand Down Expand Up @@ -81,6 +81,9 @@
MIN_BLOB_GASPRICE = Uint(1)
BLOB_BASE_FEE_UPDATE_FRACTION = Uint(5007716)

MAX_TX_MEMORY_USAGE = 8 * 1024 * 1024
MEMORY_WORDS_PER_GAS = Uint(2)

GAS_BLS_G1_ADD = Uint(375)
GAS_BLS_G1_MUL = Uint(12000)
GAS_BLS_G1_MAP = Uint(5500)
Expand Down Expand Up @@ -160,25 +163,20 @@ def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint:

"""
size_in_words = ceil32(size_in_bytes) // Uint(32)
linear_cost = size_in_words * GAS_MEMORY
quadratic_cost = size_in_words ** Uint(2) // Uint(512)
total_gas_cost = linear_cost + quadratic_cost
try:
return total_gas_cost
except ValueError as e:
raise OutOfGasError from e
total_gas_cost = size_in_words // MEMORY_WORDS_PER_GAS
return total_gas_cost


def calculate_gas_extend_memory(
memory: bytearray, extensions: List[Tuple[U256, U256]]
memory: EvmMemory, extensions: List[Tuple[U256, U256]]
) -> ExtendMemory:
"""
Calculates the gas amount to extend memory.

Parameters
----------
memory :
Memory contents of the EVM.
Memory object of the EVM.
extensions:
List of extensions to be made to the memory.
Consists of a tuple of start position and size.
Expand All @@ -190,7 +188,7 @@ def calculate_gas_extend_memory(
"""
size_to_extend = Uint(0)
to_be_paid = Uint(0)
current_size = Uint(len(memory))
current_size = Uint(len(memory.data))
for start_position, size in extensions:
if size == 0:
continue
Expand All @@ -209,6 +207,33 @@ def calculate_gas_extend_memory(
return ExtendMemory(to_be_paid, size_to_extend)


def update_memory_high_watermark(
evm: Evm, extend_memory: ExtendMemory
) -> None:
"""
Update the memory high watermark and check it doesn't exceed
MAX_TX_MEMORY_USAGE. The high watermark is intended to not be
propagated from child EVM call frame to its parent, as the memory
is respectively deallocated on exit.

Parameters
----------
evm :
The EVM object.
extend_memory :
The memory extension info from calculate_gas_extend_memory.

Raises
------
OutOfGasError
If the new memory size would exceed MAX_TX_MEMORY_USAGE.

"""
evm.memory.high_watermark_bytes += int(extend_memory.expand_by)
if evm.memory.high_watermark_bytes > MAX_TX_MEMORY_USAGE:
raise OutOfGasError


def calculate_message_call_gas(
value: U256,
gas: Uint,
Expand Down Expand Up @@ -245,11 +270,11 @@ def calculate_message_call_gas(
"""
call_stipend = Uint(0) if value == 0 else call_stipend
if gas_left < extra_gas + memory_cost:
return MessageCallGas(gas + extra_gas, gas + call_stipend)
return MessageCallGas(gas, gas + call_stipend)

gas = min(gas, max_message_call_gas(gas_left - memory_cost - extra_gas))

return MessageCallGas(gas + extra_gas, gas + call_stipend)
return MessageCallGas(gas, gas + call_stipend)


def max_message_call_gas(gas: Uint) -> Uint:
Expand Down
13 changes: 9 additions & 4 deletions src/ethereum/forks/monad_next/vm/instructions/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
calculate_blob_gas_price,
calculate_gas_extend_memory,
charge_gas,
update_memory_high_watermark,
)
from ..stack import pop, push

Expand Down Expand Up @@ -236,9 +237,10 @@ def calldatacopy(evm: Evm) -> None:
evm.memory, [(memory_start_index, size)]
)
charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost)
update_memory_high_watermark(evm, extend_memory)

# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
evm.memory.data += b"\x00" * extend_memory.expand_by
value = buffer_read(evm.message.data, data_start_index, size)
memory_write(evm.memory, memory_start_index, value)

Expand Down Expand Up @@ -294,9 +296,10 @@ def codecopy(evm: Evm) -> None:
evm.memory, [(memory_start_index, size)]
)
charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost)
update_memory_high_watermark(evm, extend_memory)

# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
evm.memory.data += b"\x00" * extend_memory.expand_by
value = buffer_read(evm.code, code_start_index, size)
memory_write(evm.memory, memory_start_index, value)

Expand Down Expand Up @@ -389,9 +392,10 @@ def extcodecopy(evm: Evm) -> None:
access_gas_cost = GAS_COLD_ACCOUNT_ACCESS

charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost)
update_memory_high_watermark(evm, extend_memory)

# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
evm.memory.data += b"\x00" * extend_memory.expand_by
code = get_account(evm.message.block_env.state, address).code

value = buffer_read(code, code_start_index, size)
Expand Down Expand Up @@ -446,10 +450,11 @@ def returndatacopy(evm: Evm) -> None:
evm.memory, [(memory_start_index, size)]
)
charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost)
update_memory_high_watermark(evm, extend_memory)
if Uint(return_data_start_position) + Uint(size) > ulen(evm.return_data):
raise OutOfBoundsRead

evm.memory += b"\x00" * extend_memory.expand_by
evm.memory.data += b"\x00" * extend_memory.expand_by
value = evm.return_data[
return_data_start_position : return_data_start_position + size
]
Expand Down
4 changes: 3 additions & 1 deletion src/ethereum/forks/monad_next/vm/instructions/keccak.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
GAS_KECCAK256_WORD,
calculate_gas_extend_memory,
charge_gas,
update_memory_high_watermark,
)
from ..memory import memory_read_bytes
from ..stack import pop, push
Expand Down Expand Up @@ -51,9 +52,10 @@ def keccak(evm: Evm) -> None:
evm.memory, [(memory_start_index, size)]
)
charge_gas(evm, GAS_KECCAK256 + word_gas_cost + extend_memory.cost)
update_memory_high_watermark(evm, extend_memory)

# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
evm.memory.data += b"\x00" * extend_memory.expand_by
data = memory_read_bytes(evm.memory, memory_start_index, size)
hashed = keccak256(data)

Expand Down
4 changes: 3 additions & 1 deletion src/ethereum/forks/monad_next/vm/instructions/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
GAS_LOG_TOPIC,
calculate_gas_extend_memory,
charge_gas,
update_memory_high_watermark,
)
from ..memory import memory_read_bytes
from ..stack import pop
Expand Down Expand Up @@ -64,9 +65,10 @@ def log_n(evm: Evm, num_topics: int) -> None:
+ GAS_LOG_TOPIC * Uint(num_topics)
+ extend_memory.cost,
)
update_memory_high_watermark(evm, extend_memory)

# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
evm.memory.data += b"\x00" * extend_memory.expand_by
if evm.message.is_static:
raise WriteInStaticContext
log_entry = Log(
Expand Down
15 changes: 10 additions & 5 deletions src/ethereum/forks/monad_next/vm/instructions/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
GAS_VERY_LOW,
calculate_gas_extend_memory,
charge_gas,
update_memory_high_watermark,
)
from ..memory import memory_read_bytes, memory_write
from ..stack import pop, push
Expand Down Expand Up @@ -50,9 +51,10 @@ def mstore(evm: Evm) -> None:
)

charge_gas(evm, GAS_VERY_LOW + extend_memory.cost)
update_memory_high_watermark(evm, extend_memory)

# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
evm.memory.data += b"\x00" * extend_memory.expand_by
memory_write(evm.memory, start_position, value)

# PROGRAM COUNTER
Expand Down Expand Up @@ -81,9 +83,10 @@ def mstore8(evm: Evm) -> None:
)

charge_gas(evm, GAS_VERY_LOW + extend_memory.cost)
update_memory_high_watermark(evm, extend_memory)

# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
evm.memory.data += b"\x00" * extend_memory.expand_by
normalized_bytes_value = Bytes([value & U256(0xFF)])
memory_write(evm.memory, start_position, normalized_bytes_value)

Expand All @@ -109,9 +112,10 @@ def mload(evm: Evm) -> None:
evm.memory, [(start_position, U256(32))]
)
charge_gas(evm, GAS_VERY_LOW + extend_memory.cost)
update_memory_high_watermark(evm, extend_memory)

# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
evm.memory.data += b"\x00" * extend_memory.expand_by
value = U256.from_be_bytes(
memory_read_bytes(evm.memory, start_position, U256(32))
)
Expand All @@ -138,7 +142,7 @@ def msize(evm: Evm) -> None:
charge_gas(evm, GAS_BASE)

# OPERATION
push(evm.stack, U256(len(evm.memory)))
push(evm.stack, U256(len(evm.memory.data)))

# PROGRAM COUNTER
evm.pc += Uint(1)
Expand Down Expand Up @@ -167,9 +171,10 @@ def mcopy(evm: Evm) -> None:
evm.memory, [(source, length), (destination, length)]
)
charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost)
update_memory_high_watermark(evm, extend_memory)

# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
evm.memory.data += b"\x00" * extend_memory.expand_by
value = memory_read_bytes(evm.memory, source, length)
memory_write(evm.memory, destination, value)

Expand Down
Loading