From 224de479b91a498b6f5a622c88aceb2d3be8d63a Mon Sep 17 00:00:00 2001 From: Nerolation Date: Thu, 20 Jun 2024 09:26:02 +0200 Subject: [PATCH 1/8] Add EIP-7623 --- src/ethereum/prague/fork.py | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index f0c772fd21..9d6efce483 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -49,8 +49,8 @@ TX_ACCESS_LIST_STORAGE_KEY_COST, TX_BASE_COST, TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, + LEGACY_CALLDATA_TOKEN_COST, + COST_FLOOR_PER_CALLDATA_TOKEN, AccessListTransaction, BlobTransaction, FeeMarketTransaction, @@ -395,7 +395,7 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ - if calculate_intrinsic_cost(tx) > tx.gas: + if calculate_intrinsic_cost(tx)[0] > tx.gas: raise InvalidBlock if tx.nonce >= 2**64 - 1: raise InvalidBlock @@ -921,7 +921,8 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price - gas = tx.gas - calculate_intrinsic_cost(tx) + intrinsic_gas, tokens_in_calldata = calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = ( @@ -965,6 +966,15 @@ def process_transaction( output = process_message_call(message, env) gas_used = tx.gas - output.gas_left + + floor = Uint(tokens_in_calldata * COST_FLOOR_PER_CALLDATA_TOKEN + TX_BASE_COST) + + legacy_cost = Uint(gas_used - tokens_in_calldata * (COST_FLOOR_PER_CALLDATA_TOKEN - LEGACY_TOKEN_COST)) + + if legacy_cost < floor: + output.gas_left -= floor - gas_used + gas_used = floor + gas_refund = min(gas_used // 5, output.refund_counter) gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price @@ -1001,7 +1011,7 @@ def process_transaction( return total_gas_used, output.logs, output.error -def calculate_intrinsic_cost(tx: Transaction) -> Uint: +def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: """ Calculates the gas that is charged before execution is started. @@ -1023,14 +1033,19 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: ------- verified : `ethereum.base_types.Uint` The intrinsic cost of the transaction. + tokens_in_calldata : `ethereum.base_types.Uint` + The eip-7623 calldata tokens used by the transaction. """ data_cost = 0 + zerobytes = 0 for byte in tx.data: if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO + zerobytes += 1 + + tokens_in_calldata = zerobytes + (len(tx.data) - zerobytes) * 4 + + data_cost = tokens_in_calldata * COST_FLOOR_PER_CALLDATA_TOKEN if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) @@ -1055,9 +1070,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if isinstance(tx, SetCodeTransaction): auth_cost += PER_EMPTY_ACCOUNT_COST * len(tx.authorizations) - return Uint( - TX_BASE_COST + data_cost + create_cost + access_list_cost + auth_cost - ) + return Uint(TX_BASE_COST + + data_cost + + create_cost + access_list_cost + auth_cost), tokens_in_calldata def recover_sender(chain_id: U64, tx: Transaction) -> Address: From a90556045c6d51e1b540f01dc453645f950db132 Mon Sep 17 00:00:00 2001 From: Nerolation Date: Tue, 3 Sep 2024 09:12:51 +0200 Subject: [PATCH 2/8] Fix refund logic: eip-7623 --- src/ethereum/prague/fork.py | 27 ++++++++++++++++++--------- src/ethereum/prague/transactions.py | 4 ++-- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 9d6efce483..1baa3b32a2 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -49,8 +49,8 @@ TX_ACCESS_LIST_STORAGE_KEY_COST, TX_BASE_COST, TX_CREATE_COST, - LEGACY_CALLDATA_TOKEN_COST, - COST_FLOOR_PER_CALLDATA_TOKEN, + STANDARD_CALLDATA_TOKEN_COST, + FLOOR_CALLDATA_COST, AccessListTransaction, BlobTransaction, FeeMarketTransaction, @@ -965,14 +965,24 @@ def process_transaction( output = process_message_call(message, env) + # For EIP-7623 gas_used includes the intrinsic costs with the floor price for calldata gas_used = tx.gas - output.gas_left - floor = Uint(tokens_in_calldata * COST_FLOOR_PER_CALLDATA_TOKEN + TX_BASE_COST) - - legacy_cost = Uint(gas_used - tokens_in_calldata * (COST_FLOOR_PER_CALLDATA_TOKEN - LEGACY_TOKEN_COST)) + # EIP-7623 floor price (note: no EVM costs) + floor = Uint(tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST) + + # EIP-7623 second part of max() condition with standard cost. Gas_used must be reduced by the + # by the difference between the floor and the standard cost. + standard_cost = Uint(gas_used - tokens_in_calldata * (FLOOR_CALLDATA_COST - STANDARD_CALLDATA_TOKEN_COST)) - if legacy_cost < floor: - output.gas_left -= floor - gas_used + # For EIP-7623 transactions where the standard cost (with evm costs) > floor pay standard cost + if standard_cost > floor: + gas_used = standard_cost + output.gas_left += tx.gas - gas_used + # For EIP-7623 transactions where the standard cost (with evm costs) < floor pay floor + else: + # Refund costs for EVM execution + output.gas_left += floor - gas_used gas_used = floor gas_refund = min(gas_used // 5, output.refund_counter) @@ -1045,7 +1055,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: tokens_in_calldata = zerobytes + (len(tx.data) - zerobytes) * 4 - data_cost = tokens_in_calldata * COST_FLOOR_PER_CALLDATA_TOKEN + data_cost = tokens_in_calldata * FLOOR_CALLDATA_COST if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) @@ -1074,7 +1084,6 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: data_cost + create_cost + access_list_cost + auth_cost), tokens_in_calldata - def recover_sender(chain_id: U64, tx: Transaction) -> Address: """ Extracts the sender address from a transaction. diff --git a/src/ethereum/prague/transactions.py b/src/ethereum/prague/transactions.py index a6ca805b96..5778cc3a5d 100644 --- a/src/ethereum/prague/transactions.py +++ b/src/ethereum/prague/transactions.py @@ -21,8 +21,8 @@ from .fork_types import Address, Authorization, VersionedHash TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 +FLOOR_CALLDATA_COST = 10 +STANDARD_CALLDATA_TOKEN_COST = 4 TX_CREATE_COST = 32000 TX_ACCESS_LIST_ADDRESS_COST = 2400 TX_ACCESS_LIST_STORAGE_KEY_COST = 1900 From e71862eec1621c21355f5bd4fae366cc1dd7dba4 Mon Sep 17 00:00:00 2001 From: Nerolation Date: Tue, 1 Oct 2024 16:26:43 +0200 Subject: [PATCH 3/8] EIP-7623: Change logic of mechanism --- src/ethereum/prague/fork.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 1baa3b32a2..99a56280ab 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -922,7 +922,12 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price intrinsic_gas, tokens_in_calldata = calculate_intrinsic_cost(tx) - gas = tx.gas - intrinsic_gas + + if intrinsic_gas > tx.gas: + raise InvalidBlock + + delta_eip7623 = tokens_in_calldata * (FLOOR_CALLDATA_COST - STANDARD_CALLDATA_TOKEN_COST) + gas = tx.gas - intrinsic_gas + delta_eip7623 increment_nonce(env.state, sender) sender_balance_after_gas_fee = ( @@ -970,21 +975,13 @@ def process_transaction( # EIP-7623 floor price (note: no EVM costs) floor = Uint(tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST) - - # EIP-7623 second part of max() condition with standard cost. Gas_used must be reduced by the - # by the difference between the floor and the standard cost. - standard_cost = Uint(gas_used - tokens_in_calldata * (FLOOR_CALLDATA_COST - STANDARD_CALLDATA_TOKEN_COST)) - - # For EIP-7623 transactions where the standard cost (with evm costs) > floor pay standard cost - if standard_cost > floor: - gas_used = standard_cost - output.gas_left += tx.gas - gas_used - # For EIP-7623 transactions where the standard cost (with evm costs) < floor pay floor - else: - # Refund costs for EVM execution - output.gas_left += floor - gas_used + + # Transactions with less gas used than the floor pay at the floor cost. + if gas_used < floor: gas_used = floor - + + output.gas_left = tx.gas - gas_used + gas_refund = min(gas_used // 5, output.refund_counter) gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price From a40fd72bd231bb004894d3f6a42c267473d497b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= <51536394+nerolation@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:49:16 +0200 Subject: [PATCH 4/8] Update src/ethereum/prague/fork.py Co-authored-by: Tanishq Jasoria --- src/ethereum/prague/fork.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 99a56280ab..90b0b65b45 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -923,11 +923,11 @@ def process_transaction( intrinsic_gas, tokens_in_calldata = calculate_intrinsic_cost(tx) - if intrinsic_gas > tx.gas: + floor = Uint(tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST) + if floor > tx.gas: raise InvalidBlock - delta_eip7623 = tokens_in_calldata * (FLOOR_CALLDATA_COST - STANDARD_CALLDATA_TOKEN_COST) - gas = tx.gas - intrinsic_gas + delta_eip7623 + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = ( From 2eff817afa85be370e3bc2a1fb6928482b63209e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= <51536394+nerolation@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:49:38 +0200 Subject: [PATCH 5/8] Update src/ethereum/prague/fork.py Co-authored-by: Tanishq Jasoria --- src/ethereum/prague/fork.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 90b0b65b45..8048d8f230 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -974,7 +974,6 @@ def process_transaction( gas_used = tx.gas - output.gas_left # EIP-7623 floor price (note: no EVM costs) - floor = Uint(tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST) # Transactions with less gas used than the floor pay at the floor cost. if gas_used < floor: From 5ee972549cde798b3832ca8cfe0d37eb93778894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= <51536394+nerolation@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:49:48 +0200 Subject: [PATCH 6/8] Update src/ethereum/prague/fork.py Co-authored-by: Tanishq Jasoria --- src/ethereum/prague/fork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 8048d8f230..419d35c1d2 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -1051,7 +1051,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: tokens_in_calldata = zerobytes + (len(tx.data) - zerobytes) * 4 - data_cost = tokens_in_calldata * FLOOR_CALLDATA_COST + data_cost = tokens_in_calldata * STANDARD_CALLDATA_TOKEN_COST if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) From a6f5239390eb2674facded950e2082bd64e0e4b2 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Wed, 2 Oct 2024 09:22:27 -0400 Subject: [PATCH 7/8] Format, typecheck, etc. --- src/ethereum/prague/fork.py | 41 ++++++++++++++++++++++--------------- whitelist.txt | 4 +++- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 419d35c1d2..33db13710c 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -45,12 +45,12 @@ state_root, ) from .transactions import ( + FLOOR_CALLDATA_COST, + STANDARD_CALLDATA_TOKEN_COST, TX_ACCESS_LIST_ADDRESS_COST, TX_ACCESS_LIST_STORAGE_KEY_COST, TX_BASE_COST, TX_CREATE_COST, - STANDARD_CALLDATA_TOKEN_COST, - FLOOR_CALLDATA_COST, AccessListTransaction, BlobTransaction, FeeMarketTransaction, @@ -922,11 +922,11 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price intrinsic_gas, tokens_in_calldata = calculate_intrinsic_cost(tx) - + floor = Uint(tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST) if floor > tx.gas: raise InvalidBlock - + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) @@ -970,17 +970,18 @@ def process_transaction( output = process_message_call(message, env) - # For EIP-7623 gas_used includes the intrinsic costs with the floor price for calldata + # For EIP-7623 gas_used includes the intrinsic costs with the floor price + # for calldata. gas_used = tx.gas - output.gas_left - + # EIP-7623 floor price (note: no EVM costs) - + # Transactions with less gas used than the floor pay at the floor cost. if gas_used < floor: gas_used = floor - + output.gas_left = tx.gas - gas_used - + gas_refund = min(gas_used // 5, output.refund_counter) gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price @@ -1044,13 +1045,13 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: """ data_cost = 0 - zerobytes = 0 + zero_bytes = 0 for byte in tx.data: if byte == 0: - zerobytes += 1 + zero_bytes += 1 + + tokens_in_calldata = zero_bytes + (len(tx.data) - zero_bytes) * 4 - tokens_in_calldata = zerobytes + (len(tx.data) - zerobytes) * 4 - data_cost = tokens_in_calldata * STANDARD_CALLDATA_TOKEN_COST if tx.to == Bytes0(b""): @@ -1076,9 +1077,17 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: if isinstance(tx, SetCodeTransaction): auth_cost += PER_EMPTY_ACCOUNT_COST * len(tx.authorizations) - return Uint(TX_BASE_COST + - data_cost + - create_cost + access_list_cost + auth_cost), tokens_in_calldata + return ( + Uint( + TX_BASE_COST + + data_cost + + create_cost + + access_list_cost + + auth_cost + ), + Uint(tokens_in_calldata), + ) + def recover_sender(chain_id: U64, tx: Transaction) -> Address: """ diff --git a/whitelist.txt b/whitelist.txt index f309ebbb8f..ccc117c05d 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -12,12 +12,14 @@ Bytes64 Bytes8 Bytes256 Bytes0 +calldata copyreg copytree coinbase coincurve crypto E501 +EIP encodings endian eth @@ -459,4 +461,4 @@ exponentiate monomial impl -eoa \ No newline at end of file +eoa From 851e13045fea3d5c633e88b121b0adba8831ccda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= <51536394+nerolation@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:09:19 +0200 Subject: [PATCH 8/8] Apply suggestions from code review lgtm Co-authored-by: Roman Krasiuk --- src/ethereum/prague/fork.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 33db13710c..ae4999ad16 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -395,7 +395,9 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ - if calculate_intrinsic_cost(tx)[0] > tx.gas: + intrinsic_gas, tokens_in_calldata = calculate_intrinsic_cost(tx) + floor = Uint(tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST) + if max(intrinsic_gas, floor) > tx.gas: raise InvalidBlock if tx.nonce >= 2**64 - 1: raise InvalidBlock @@ -923,10 +925,6 @@ def process_transaction( intrinsic_gas, tokens_in_calldata = calculate_intrinsic_cost(tx) - floor = Uint(tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST) - if floor > tx.gas: - raise InvalidBlock - gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender)