Skip to content
23 changes: 20 additions & 3 deletions cmd/ethrex/build_l2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ pub fn download_script() {
&Path::new("../../crates/l2/contracts/src/l2/FeeTokenRegistry.sol"),
"FeeTokenRegistry",
),
(
&Path::new("../../crates/l2/contracts/src/l2/FeeTokenPricer.sol"),
"FeeTokenPricer",
),
];
for (path, name) in l2_contracts {
compile_contract_to_bytecode(
Expand Down Expand Up @@ -277,8 +281,8 @@ fn decode_to_bytecode(input_file_path: &Path, output_file_path: &Path) {

use ethrex_l2_sdk::{
COMMON_BRIDGE_L2_ADDRESS, CREATE2DEPLOYER_ADDRESS, DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS,
FEE_TOKEN_REGISTRY_ADDRESS, L2_TO_L1_MESSENGER_ADDRESS, SAFE_SINGLETON_FACTORY_ADDRESS,
address_to_word, get_erc1967_slot,
FEE_TOKEN_PRICER_ADDRESS, FEE_TOKEN_REGISTRY_ADDRESS, L2_TO_L1_MESSENGER_ADDRESS,
SAFE_SINGLETON_FACTORY_ADDRESS, address_to_word, get_erc1967_slot,
};

#[allow(clippy::enum_variant_names)]
Expand Down Expand Up @@ -341,6 +345,12 @@ fn fee_token_registry_runtime(out_dir: &Path) -> Vec<u8> {
fs::read(path).expect("Failed to read bytecode file")
}

/// Bytecode of the FeeTokenPricer contract.
fn fee_token_pricer_runtime(out_dir: &Path) -> Vec<u8> {
let path = out_dir.join("contracts/solc_out/FeeTokenPricer.bytecode");
fs::read(path).expect("Failed to read bytecode file")
}

/// Bytecode of the Create2Deployer contract.
fn create2deployer_runtime(out_dir: &Path) -> Vec<u8> {
let path = out_dir.join("contracts/solc_out/Create2Deployer.bytecode");
Expand Down Expand Up @@ -451,7 +461,14 @@ pub fn update_genesis_file(
out_dir,
)?;

for address in 0xff00..0xfffb {
add_with_proxy(
&mut genesis,
FEE_TOKEN_PRICER_ADDRESS,
fee_token_pricer_runtime(out_dir),
out_dir,
)?;

for address in 0xff00..0xfffa {
add_placeholder_proxy(&mut genesis, Address::from_low_u64_be(address), out_dir)?;
}

Expand Down
16 changes: 16 additions & 0 deletions crates/l2/contracts/src/l2/FeeTokenPricer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.29;
import "./interfaces/IFeeTokenPricer.sol";

contract FeeTokenPricer is IFeeTokenPricer {
address internal constant FEE_TOKEN =
0xb7E811662Fa10ac068aeE115AC2e682821630535;

/// @inheritdoc IFeeTokenPricer
function getFeeTokenRatio(
address fee_token
) external view override returns (uint256) {
require(fee_token == FEE_TOKEN, "The fee token does not match with the one set for the chain");
return 2;
}
}
11 changes: 11 additions & 0 deletions crates/l2/contracts/src/l2/interfaces/IFeeTokenPricer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.29;

/// @title IFeeTokenPricer
/// @notice Interface for a contract that provides pricing information for fee tokens.
interface IFeeTokenPricer {
/// @notice Returns the ratio of fee token (implements FeeToken) to ETH in wei.
/// @param feeToken The address of the fee token.
/// @return ratio The amount of fee token (in its smallest unit) equivalent to 1 wei.
function getFeeTokenRatio(address feeToken) external view returns (uint256);
}
29 changes: 29 additions & 0 deletions crates/l2/sdk/src/sdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ pub const FEE_TOKEN_REGISTRY_ADDRESS: Address = H160([
0x00, 0x00, 0xff, 0xfc,
]);

// 0x000000000000000000000000000000000000fffb
pub const FEE_TOKEN_PRICER_ADDRESS: Address = H160([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xfb,
]);

// 0xee110000000000000000000000000000000011ff
pub const ADDRESS_ALIASING: Address = H160([
0xee, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
Expand Down Expand Up @@ -1114,3 +1120,26 @@ async fn _call_bytes32_variable(

Ok(arr)
}

pub async fn get_fee_token_ratio(
fee_token: &Address,
client: &EthClient,
) -> Result<u64, EthClientError> {
let values = vec![Value::Address(*fee_token)];
let calldata = encode_calldata("getFeeTokenRatio(address)", &values)?;

let ratio = client
.call(
FEE_TOKEN_PRICER_ADDRESS,
calldata.into(),
Default::default(),
)
.await?;

let ratio: u64 = ratio
.trim_start_matches("0x")
.parse::<u64>()
.map_err(|e| EthClientError::Custom(format!("Failed to parse ratio to u64: {}", e)))?;

Ok(ratio)
}
16 changes: 10 additions & 6 deletions crates/l2/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ use ethrex_l2_sdk::{
wait_for_transaction_receipt,
};
use ethrex_l2_sdk::{
build_generic_tx, get_last_verified_batch, send_generic_transaction, wait_for_message_proof,
build_generic_tx, get_fee_token_ratio, get_last_verified_batch, send_generic_transaction,
wait_for_message_proof,
};
use ethrex_rpc::{
clients::eth::{EthClient, Overrides},
Expand Down Expand Up @@ -2118,13 +2119,16 @@ async fn test_fee_token(

let transfer_fees =
get_fees_details_l2(&transfer_receipt, &l2_client, get_fee_token_diff_size()).await?;
let fee_token_ratio = get_fee_token_ratio(&fee_token_address, &l2_client)
.await
.unwrap();

let sender_fee_token_spent = sender_token_balance_before_transfer
.checked_sub(sender_token_balance_after_transfer)
.expect("Sender fee token balance increased unexpectedly");
assert_eq!(
sender_fee_token_spent,
U256::from(transfer_fees.total()),
U256::from(transfer_fees.total()) * fee_token_ratio,
"{test}: Sender fee token spend mismatch"
);

Expand All @@ -2135,7 +2139,7 @@ async fn test_fee_token(
.expect("Coinbase fee token balance decreased");
assert_eq!(
coinbase_delta,
U256::from(transfer_fees.priority_fees),
U256::from(transfer_fees.priority_fees) * fee_token_ratio,
"{test}: Priority fee mismatch"
);

Expand All @@ -2150,7 +2154,7 @@ async fn test_fee_token(
.expect("Base fee vault balance decreased");
assert_eq!(
base_fee_vault_delta,
U256::from(transfer_fees.base_fees),
U256::from(transfer_fees.base_fees) * fee_token_ratio,
"{test}: Base fee vault mismatch"
);
}
Expand All @@ -2170,7 +2174,7 @@ async fn test_fee_token(
.expect("Operator fee vault balance decreased");
assert_eq!(
operator_fee_vault_delta,
U256::from(transfer_fees.operator_fees),
U256::from(transfer_fees.operator_fees) * fee_token_ratio,
"{test}: Operator fee vault mismatch"
);

Expand All @@ -2179,7 +2183,7 @@ async fn test_fee_token(
.expect("L1 fee vault balance decreased");
assert_eq!(
l1_fee_vault_delta,
U256::from(transfer_fees.l1_fees),
U256::from(transfer_fees.l1_fees) * fee_token_ratio,
"{test}: L1 fee vault mismatch"
);

Expand Down
43 changes: 43 additions & 0 deletions crates/vm/backends/levm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use crate::system_contracts::{
};
use crate::{EvmError, ExecutionResult};
use bytes::Bytes;
use ethrex_common::H160;
use ethrex_common::types::TxType;
use ethrex_common::types::fee_config::FeeConfig;
use ethrex_common::{
Address, U256,
Expand All @@ -31,6 +33,12 @@ use ethrex_levm::{
use std::cmp::min;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::Sender;
// getFeeTokenRatio(address feeToken) external view override returns (uint256)
const GET_FEE_TOKEN_RATIO_SELECTOR: [u8; 4] = [0xc6, 0xab, 0x85, 0xd8];
pub const FEE_TOKEN_RATIO_ADDRESS: Address = H160([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xfb,
]);

/// The struct implements the following functions:
/// [LEVM::execute_block]
Expand Down Expand Up @@ -171,6 +179,13 @@ impl LEVM {
block_header.base_fee_per_gas.unwrap_or_default(),
&vm_type,
)?;
let fee_ratio = if let Some(fee_token) = tx.fee_token()
&& tx.tx_type() == TxType::FeeToken
{
get_fee_token_ratio(block_header, db, vm_type, chain_config, fee_token)?
} else {
None
};

let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
let env = Environment {
Expand All @@ -195,6 +210,7 @@ impl LEVM {
difficulty: block_header.difficulty,
is_privileged: matches!(tx, Transaction::PrivilegedL2Transaction(_)),
fee_token: tx.fee_token(),
fee_ratio,
};

Ok(env)
Expand Down Expand Up @@ -434,6 +450,32 @@ impl LEVM {
}
}

fn get_fee_token_ratio(
block_header: &BlockHeader,
db: &mut GeneralizedDatabase,
vm_type: VMType,
chain_config: ethrex_common::types::ChainConfig,
fee_token: ethrex_common::H160,
) -> Result<Option<U256>, EvmError> {
let mut data = GET_FEE_TOKEN_RATIO_SELECTOR.to_vec();
data.extend_from_slice(&[0u8; 12]);
data.extend_from_slice(fee_token.as_bytes());
let tx_query = EIP1559Transaction {
to: TxKind::Call(FEE_TOKEN_RATIO_ADDRESS),
chain_id: chain_config.chain_id,
data: Bytes::from(data),
gas_limit: 1_000_000,
..Default::default()
};
let execution_result = LEVM::simulate_tx_from_generic(
&GenericTransaction::from(tx_query),
block_header,
&mut db.clone(),
vm_type,
)?;
Ok(Some(U256::from_big_endian(&execution_result.output())))
}

pub fn generic_system_contract_levm(
block_header: &BlockHeader,
calldata: Bytes,
Expand Down Expand Up @@ -694,6 +736,7 @@ fn env_from_generic(
difficulty: header.difficulty,
is_privileged: false,
fee_token: tx.fee_token,
fee_ratio: None,
})
}

Expand Down
1 change: 1 addition & 0 deletions crates/vm/levm/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub struct Environment {
pub block_gas_limit: u64,
pub is_privileged: bool,
pub fee_token: Option<Address>,
pub fee_ratio: Option<U256>,
}

/// This struct holds special configuration variables specific to the
Expand Down
45 changes: 37 additions & 8 deletions crates/vm/levm/src/hooks/l2_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,31 +139,53 @@ fn finalize_non_privileged_execution(

default_hook::delete_self_destruct_accounts(vm)?;

let fee_token_ratio = vm.env.fee_ratio.map(|ratio| ratio.as_u64()).unwrap_or(1u64);

if let Some(l1_fee_config) = fee_config.l1_fee_config {
pay_to_l1_fee_vault(vm, l1_gas, l1_fee_config, use_fee_token)?;
pay_to_l1_fee_vault(
vm,
l1_gas.saturating_mul(fee_token_ratio),
l1_fee_config,
use_fee_token,
)?;
}

if use_fee_token {
refund_sender_fee_token(vm, ctx_result, gas_refunded, total_gas)?;
refund_sender_fee_token(vm, ctx_result, gas_refunded, total_gas, fee_token_ratio)?;
} else {
default_hook::refund_sender(vm, ctx_result, gas_refunded, total_gas)?;
}

pay_coinbase_l2(
vm,
actual_gas_used,
actual_gas_used.saturating_mul(fee_token_ratio),
&fee_config.operator_fee_config,
use_fee_token,
)?;

if let Some(base_fee_vault) = fee_config.base_fee_vault {
pay_base_fee_vault(vm, actual_gas_used, base_fee_vault, use_fee_token)?;
pay_base_fee_vault(
vm,
actual_gas_used.saturating_mul(fee_token_ratio),
base_fee_vault,
use_fee_token,
)?;
} else if use_fee_token {
pay_base_fee_vault(vm, actual_gas_used, Address::zero(), use_fee_token)?;
pay_base_fee_vault(
vm,
actual_gas_used.saturating_mul(fee_token_ratio),
Address::zero(),
use_fee_token,
)?;
}

if let Some(operator_fee_config) = fee_config.operator_fee_config {
pay_operator_fee(vm, actual_gas_used, operator_fee_config, use_fee_token)?;
pay_operator_fee(
vm,
actual_gas_used.saturating_mul(fee_token_ratio),
operator_fee_config,
use_fee_token,
)?;
}

ctx_result.gas_used = total_gas;
Expand Down Expand Up @@ -403,6 +425,8 @@ fn prepare_execution_fee_token(vm: &mut VM<'_>) -> Result<(), crate::errors::VME
));
}

let fee_token_ratio = vm.env.fee_ratio.unwrap_or(U256::one());

let sender_address = vm.env.origin;
let sender_info = vm.db.get_account(sender_address)?.info.clone();

Expand All @@ -429,7 +453,7 @@ fn prepare_execution_fee_token(vm: &mut VM<'_>) -> Result<(), crate::errors::VME
// NOT CHECKED: the blob price does not matter, fee token transactions do not support blobs

// (3) INSUFFICIENT_ACCOUNT_FUNDS
deduct_caller_fee_token(vm, gaslimit_price_product)?;
deduct_caller_fee_token(vm, gaslimit_price_product.saturating_mul(fee_token_ratio))?;

// (4) INSUFFICIENT_MAX_FEE_PER_GAS
default_hook::validate_sufficient_max_fee_per_gas(vm)?;
Expand Down Expand Up @@ -638,6 +662,7 @@ fn refund_sender_fee_token(
ctx_result: &mut ContextResult,
refunded_gas: u64,
actual_gas_used: u64,
fee_token_ratio: u64,
) -> Result<(), VMError> {
// c. Update gas used and refunded.
ctx_result.gas_used = actual_gas_used;
Expand All @@ -657,7 +682,11 @@ fn refund_sender_fee_token(
.ok_or(InternalError::Overflow)?;
let sender_address = vm.env.origin;

pay_fee_token(vm, sender_address, erc20_return_amount)?;
pay_fee_token(
vm,
sender_address,
erc20_return_amount.saturating_mul(fee_token_ratio.into()),
)?;

Ok(())
}
Expand Down
7 changes: 7 additions & 0 deletions fixtures/genesis/l2.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
{
"alloc": {
"0x000000000000000000000000000000000000effb": {
"balance": "0x0",
"code": "0x60806040526004361015610013575b6100f2565b61001d5f3561002c565b63c6ab85d80361000e576100bd565b60e01c90565b60405190565b5f80fd5b5f80fd5b60018060a01b031690565b61005490610040565b90565b6100608161004b565b0361006757565b5f80fd5b9050359061007882610057565b565b9060208282031261009357610090915f0161006b565b90565b61003c565b90565b6100a490610098565b9052565b91906100bb905f6020850194019061009b565b565b346100ed576100e96100d86100d336600461007a565b6101e5565b6100e0610032565b918291826100a8565b0390f35b610038565b5f80fd5b5f90565b73b7e811662fa10ac068aee115ac2e68282163053590565b60209181520190565b60207f6820746865206f6e652073657420666f722074686520636861696e0000000000917f5468652066656520746f6b656e20646f6573206e6f74206d61746368207769745f8201520152565b610175603b604092610112565b61017e8161011b565b0190565b6101979060208101905f818303910152610168565b90565b156101a157565b6101a9610032565b62461bcd60e51b8152806101bf60048201610182565b0390fd5b90565b90565b6101dd6101d86101e2926101c3565b6101c6565b610098565b90565b610211906101f16100f6565b5061020b6102056102006100fa565b61004b565b9161004b565b1461019a565b61021b60026101c9565b9056",
"nonce": "0x1",
"storage": {}
},
"0x000000000000000000000000000000000000effc": {
"balance": "0x0",
"code": "0x60806040526004361015610013575b6100f4565b61001d5f3561002c565b6316ad82d70361000e576100bf565b60e01c90565b60405190565b5f80fd5b5f80fd5b60018060a01b031690565b61005490610040565b90565b6100608161004b565b0361006757565b5f80fd5b9050359061007882610057565b565b9060208282031261009357610090915f0161006b565b90565b61003c565b151590565b6100a690610098565b9052565b91906100bd905f6020850194019061009d565b565b346100ef576100eb6100da6100d536600461007a565b610114565b6100e2610032565b918291826100aa565b0390f35b610038565b5f80fd5b5f90565b73b7e811662fa10ac068aee115ac2e68282163053590565b61011c6100f8565b5061013661013061012b6100fc565b61004b565b9161004b565b149056",
Expand Down Expand Up @@ -2037,6 +2043,7 @@
"code": "0x608060405261000c61000e565b005b610016610040565b565b60018060a01b031690565b61002c90610018565b90565b63ffffffff60e01b1690565b5f0190565b3361005a61005461004f6100c2565b610023565b91610023565b145f146100b35763ffffffff60e01b5f351661008561007f63278f794360e11b61002f565b9161002f565b14155f146100a9575f6334ad5dbb60e21b8152806100a56004820161003b565b0390fd5b6100b16102d9565b565b6100d6565b5f90565b61f00090565b6100ca6100b8565b506100d36100bc565b90565b6100de610318565b61032c565b90565b90565b90565b6101006100fb610105926100e3565b6100e9565b6100e6565b90565b60405190565b5f80fd5b5f80fd5b90939293848311610136578411610131576001820201920390565b610112565b61010e565b91565b5f80fd5b5f80fd5b61014f90610018565b90565b61015b81610146565b0361016257565b5f80fd5b9050359061017382610152565b565b5f80fd5b5f80fd5b601f801991011690565b634e487b7160e01b5f52604160045260245ffd5b906101a59061017d565b810190811067ffffffffffffffff8211176101bf57604052565b610187565b906101d76101d0610108565b928361019b565b565b67ffffffffffffffff81116101f7576101f360209161017d565b0190565b610187565b90825f939282370152565b9092919261021c610217826101d9565b6101c4565b9381855260208501908284011161023857610236926101fc565b565b610179565b9080601f8301121561025b5781602061025893359101610207565b90565b610175565b9190916040818403126102a057610279835f8301610166565b92602082013567ffffffffffffffff811161029b57610298920161023d565b90565b610142565b61013e565b6102b96102b46102be92610018565b6100e9565b610018565b90565b6102ca906102a5565b90565b6102d6906102c1565b90565b61031661031161030a6103026102fc5f366102f460046100ec565b908092610116565b9061013b565b810190610260565b91906102cd565b610379565b565b6103206100b8565b50610329610486565b90565b5f8091368280378136915af43d5f803e5f14610346573d5ff35b3d5ffd5b610353906102c1565b90565b5190565b90565b61037161036c6103769261035a565b6100e9565b6100e6565b90565b906103838261050c565b816103ae7fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b9161034a565b906103b7610108565b806103c18161003b565b0390a26103cd81610356565b6103df6103d95f61035d565b916100e6565b115f146103f3576103ef916105dc565b505b565b50506103fd610561565b6103f1565b90565b90565b5f1b90565b61042161041c61042692610402565b610408565b610405565b90565b6104527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61040d565b90565b5f1c90565b60018060a01b031690565b61047161047691610455565b61045a565b90565b6104839054610465565b90565b61048e6100b8565b506104a95f6104a361049e610429565b61060b565b01610479565b90565b6104b590610023565b9052565b91906104cc905f602085019401906104ac565b565b906104df60018060a01b0391610408565b9181191691161790565b90565b906105016104fc6105089261034a565b6104e9565b82546104ce565b9055565b803b61052061051a5f61035d565b916100e6565b1461054257610540905f61053a610535610429565b61060b565b016104ec565b565b61055d905f918291634c9c8ce360e01b8352600483016104b9565b0390fd5b3461057461056e5f61035d565b916100e6565b1161057b57565b5f63b398979f60e01b8152806105936004820161003b565b0390fd5b606090565b906105ae6105a9836101d9565b6101c4565b918252565b3d5f146105ce576105c33d61059c565b903d5f602084013e5b565b6105d6610597565b906105cc565b5f80610608936105ea610597565b508390602081019051915af4906105ff6105b3565b90919091610613565b90565b90565b151590565b9061062790610620610597565b501561060e565b5f146106335750610697565b61063c82610356565b61064e6106485f61035d565b916100e6565b148061067c575b61065d575090565b610678905f918291639996b31560e01b8352600483016104b9565b0390fd5b50803b61069161068b5f61035d565b916100e6565b14610655565b6106a081610356565b6106b26106ac5f61035d565b916100e6565b115f146106c157602081519101fd5b5f63d6bda27560e01b8152806106d96004820161003b565b0390fd",
"nonce": "0x1",
"storage": {
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0xeffb",
"0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0xf000"
}
},
Expand Down
Loading
Loading