use crate::{
constants::{POST_OSAKA_GAS_LIMIT_CAP, TX_MAX_GAS_LIMIT_AMSTERDAM},
db::gen_db::GeneralizedDatabase,
errors::{ContextResult, ExecutionReport, InternalError, TxValidationError, VMError},
hooks::{DefaultHook, default_hook, hook::Hook},
opcodes::Opcode,
tracing::LevmCallTracer,
vm::{VM, VMType},
};
use bytes::Bytes;
use ethrex_common::{
Address, H160, H256, U256,
constants::GAS_PER_BLOB,
types::{
Code, EIP1559Transaction, Fork, Transaction, TxKind,
{
SAFE_BYTES_PER_BLOB,
fee_config::{FeeConfig, L1FeeConfig, OperatorFeeConfig},
},
},
};
use ethrex_rlp::encode::RLPEncode;
pub const COMMON_BRIDGE_L2_ADDRESS: Address = H160([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xff,
]);
pub const FEE_TOKEN_REGISTRY_ADDRESS: Address = H160([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xfc,
]);
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,
]);
const LOCK_FEE_SELECTOR: [u8; 4] = [0x89, 0x9c, 0x86, 0xe2];
const PAY_FEE_SELECTOR: [u8; 4] = [0x72, 0x74, 0x6e, 0xaf];
const IS_FEE_TOKEN_SELECTOR: [u8; 4] = [0x16, 0xad, 0x82, 0xd7];
const FEE_TOKEN_RATIO_SELECTOR: [u8; 4] = [0xc6, 0xab, 0x85, 0xd8];
const SIMULATION_GAS_LIMIT: u64 = 21000 * 100;
const SIMULATION_MAX_FEE: u64 = 100;
pub struct L2Hook {
pub fee_config: FeeConfig,
cached_fee_token_ratio: Option<U256>,
}
impl L2Hook {
pub fn new(fee_config: FeeConfig) -> Self {
Self {
fee_config,
cached_fee_token_ratio: None,
}
}
}
impl Hook for L2Hook {
fn prepare_execution(&mut self, vm: &mut VM<'_>) -> Result<(), crate::errors::VMError> {
if vm.env.is_privileged {
return prepare_execution_privileged(vm);
} else if vm.env.fee_token.is_some() {
let ratio = prepare_execution_fee_token(vm)?;
self.cached_fee_token_ratio = Some(ratio);
} else {
DefaultHook.prepare_execution(vm)?;
}
validate_sufficient_max_fee_per_gas_l2(vm, &self.fee_config.operator_fee_config)?;
reserve_l1_gas(vm, &self.fee_config.l1_fee_config)?;
Ok(())
}
fn finalize_execution(
&mut self,
vm: &mut VM<'_>,
ctx_result: &mut ContextResult,
) -> Result<(), crate::errors::VMError> {
if vm.env.is_privileged {
if !ctx_result.is_success() && vm.env.origin != COMMON_BRIDGE_L2_ADDRESS {
default_hook::undo_value_transfer(vm)?;
}
default_hook::delete_self_destruct_accounts(vm)?;
} else {
finalize_non_privileged_execution(
vm,
ctx_result,
&self.fee_config,
vm.env.fee_token.is_some(),
self.cached_fee_token_ratio,
)?;
}
Ok(())
}
}
fn finalize_non_privileged_execution(
vm: &mut VM<'_>,
ctx_result: &mut ContextResult,
fee_config: &FeeConfig,
use_fee_token: bool,
cached_fee_token_ratio: Option<U256>,
) -> Result<(), crate::errors::VMError> {
if !ctx_result.is_success() {
default_hook::undo_value_transfer(vm)?;
}
let l1_gas = calculate_l1_fee_gas(vm, &fee_config.l1_fee_config)?;
let execution_gas_pre_refund = ctx_result
.gas_used
.checked_sub(l1_gas)
.ok_or(InternalError::Underflow)?;
let gas_refunded: u64 = vm
.substate
.refunded_gas
.min(execution_gas_pre_refund / default_hook::MAX_REFUND_QUOTIENT);
let execution_gas =
default_hook::compute_actual_gas_used(vm, gas_refunded, execution_gas_pre_refund)?;
let actual_gas_used = execution_gas
.checked_add(l1_gas)
.ok_or(InternalError::Overflow)?;
let total_gas_pre_refund = ctx_result.gas_used;
let execution_backup = std::mem::take(&mut vm.current_call_frame.call_frame_backup);
let fee_token_ratio: u64 = match (cached_fee_token_ratio, use_fee_token) {
(Some(cached), _) => {
cached.try_into().map_err(|_| {
VMError::Internal(InternalError::Custom(
"Failed to convert fee token ratio".to_owned(),
))
})?
}
(None, true) => {
return Err(VMError::Internal(InternalError::Custom(
"use_fee_token is true but fee_token_ratio was not cached".to_owned(),
)));
}
(None, false) => 1u64,
};
let result = apply_finalize_mutations(
vm,
ctx_result,
fee_config,
use_fee_token,
fee_token_ratio,
l1_gas,
gas_refunded,
execution_gas,
actual_gas_used,
total_gas_pre_refund,
);
if let Err(e) = result {
vm.restore_cache_state()?;
return Err(e);
}
vm.current_call_frame
.call_frame_backup
.extend(execution_backup);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn apply_finalize_mutations(
vm: &mut VM<'_>,
ctx_result: &mut ContextResult,
fee_config: &FeeConfig,
use_fee_token: bool,
fee_token_ratio: u64,
l1_gas: u64,
gas_refunded: u64,
execution_gas: u64,
actual_gas_used: u64,
total_gas_pre_refund: u64,
) -> Result<(), crate::errors::VMError> {
default_hook::delete_self_destruct_accounts(vm)?;
if let Some(l1_fee_config) = fee_config.l1_fee_config {
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,
actual_gas_used,
total_gas_pre_refund,
fee_token_ratio,
)?;
} else {
default_hook::refund_sender(vm, ctx_result, gas_refunded, actual_gas_used)?;
}
pay_coinbase_l2(
vm,
execution_gas.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,
execution_gas.saturating_mul(fee_token_ratio),
base_fee_vault,
use_fee_token,
)?;
} else if use_fee_token {
pay_base_fee_vault(
vm,
execution_gas.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,
execution_gas.saturating_mul(fee_token_ratio),
operator_fee_config,
use_fee_token,
)?;
}
Ok(())
}
fn validate_sufficient_max_fee_per_gas_l2(
vm: &VM<'_>,
operator_fee_config: &Option<OperatorFeeConfig>,
) -> Result<(), TxValidationError> {
let Some(fee_config) = operator_fee_config else {
return Ok(());
};
let total_fee = vm
.env
.base_fee_per_gas
.checked_add(fee_config.operator_fee_per_gas.into())
.ok_or(TxValidationError::InsufficientMaxFeePerGas)?;
if vm.env.tx_max_fee_per_gas.unwrap_or(vm.env.gas_price) < total_fee {
return Err(TxValidationError::InsufficientMaxFeePerGas);
}
Ok(())
}
fn reserve_l1_gas(vm: &mut VM<'_>, l1_fee_config: &Option<L1FeeConfig>) -> Result<(), VMError> {
let l1_gas = calculate_l1_fee_gas(vm, l1_fee_config)?;
if vm.env.config.fork >= Fork::Prague {
let floor = vm.get_min_gas_used()?;
let floor_plus_l1 = floor.checked_add(l1_gas).ok_or(InternalError::Overflow)?;
if vm.env.gas_limit < floor_plus_l1 {
return Err(TxValidationError::IntrinsicGasTooLow.into());
}
}
vm.current_call_frame
.increase_consumed_gas(l1_gas)
.map_err(|_| TxValidationError::IntrinsicGasTooLow)?;
Ok(())
}
fn pay_coinbase_l2(
vm: &mut VM<'_>,
gas_to_pay: u64,
operator_fee_config: &Option<OperatorFeeConfig>,
use_fee_token: bool,
) -> Result<(), crate::errors::VMError> {
if operator_fee_config.is_none() && !use_fee_token {
return default_hook::pay_coinbase(vm, gas_to_pay);
}
let priority_fee_per_gas = compute_priority_fee_per_gas(vm, operator_fee_config)?;
let coinbase_fee = U256::from(gas_to_pay)
.checked_mul(priority_fee_per_gas)
.ok_or(InternalError::Overflow)?;
if let Some(recorder) = vm.db.bal_recorder.as_mut() {
recorder.record_touched_address(vm.env.coinbase);
}
if !coinbase_fee.is_zero() {
if use_fee_token {
pay_fee_token(vm, vm.env.coinbase, coinbase_fee)?;
} else {
vm.increase_account_balance(vm.env.coinbase, coinbase_fee)?;
}
}
Ok(())
}
fn compute_priority_fee_per_gas(
vm: &VM<'_>,
operator_fee_config: &Option<OperatorFeeConfig>,
) -> Result<U256, InternalError> {
let priority_fee = vm
.env
.gas_price
.checked_sub(vm.env.base_fee_per_gas)
.ok_or(InternalError::Underflow)?;
if let Some(fee_config) = operator_fee_config {
priority_fee
.checked_sub(U256::from(fee_config.operator_fee_per_gas))
.ok_or(InternalError::Underflow)
} else {
Ok(priority_fee)
}
}
fn pay_base_fee_vault(
vm: &mut VM<'_>,
gas_to_pay: u64,
base_fee_vault: Address,
use_fee_token: bool,
) -> Result<(), crate::errors::VMError> {
let base_fee = U256::from(gas_to_pay)
.checked_mul(vm.env.base_fee_per_gas)
.ok_or(InternalError::Overflow)?;
if use_fee_token {
pay_fee_token(vm, base_fee_vault, base_fee)?;
} else {
vm.increase_account_balance(base_fee_vault, base_fee)?;
}
Ok(())
}
fn pay_operator_fee(
vm: &mut VM<'_>,
gas_to_pay: u64,
operator_fee_config: OperatorFeeConfig,
use_fee_token: bool,
) -> Result<(), crate::errors::VMError> {
let operator_fee = U256::from(gas_to_pay)
.checked_mul(U256::from(operator_fee_config.operator_fee_per_gas))
.ok_or(InternalError::Overflow)?;
if use_fee_token {
pay_fee_token(vm, operator_fee_config.operator_fee_vault, operator_fee)?;
} else {
vm.increase_account_balance(operator_fee_config.operator_fee_vault, operator_fee)?;
}
Ok(())
}
fn prepare_execution_privileged(vm: &mut VM<'_>) -> Result<(), crate::errors::VMError> {
let sender_address = vm.env.origin;
let sender_balance = vm.db.get_account(sender_address)?.info.balance;
let mut tx_should_fail = false;
if sender_address != COMMON_BRIDGE_L2_ADDRESS {
let value = vm.current_call_frame.msg_value;
if value > sender_balance {
tx_should_fail = true;
}
}
let intrinsic_failed = match vm.get_intrinsic_gas() {
Ok(intrinsic) => vm.add_intrinsic_gas(&intrinsic).is_err(),
Err(_) => true,
};
if intrinsic_failed {
tx_should_fail = true;
}
default_hook::validate_gas_allowance(vm)?;
if tx_should_fail {
vm.current_call_frame.msg_value = U256::zero();
vm.current_call_frame
.set_code(Code::from_bytecode_unchecked(
vec![Opcode::INVALID.into()].into(),
H256::zero(),
))?;
return Ok(());
}
if sender_address != COMMON_BRIDGE_L2_ADDRESS {
let value = vm.current_call_frame.msg_value;
vm.decrease_account_balance(sender_address, value)
.map_err(|_| {
InternalError::Custom("Insufficient funds in privileged transaction".to_string())
})?;
}
default_hook::transfer_value(vm)?;
default_hook::set_bytecode_and_code_address(vm)
}
fn prepare_execution_fee_token(vm: &mut VM<'_>) -> Result<U256, crate::errors::VMError> {
let fee_token = vm
.env
.fee_token
.ok_or(VMError::Internal(InternalError::Custom(
"Fee token address not provided".to_owned(),
)))?;
let (execution_result, _) = simulate_common_bridge_call(
vm,
FEE_TOKEN_REGISTRY_ADDRESS,
encode_is_fee_token_call(fee_token),
)?;
if !execution_result.is_success() {
return Err(VMError::TxValidation(
TxValidationError::InsufficientAccountFunds,
));
}
if execution_result.output.len() != 32
|| execution_result.output.get(31).is_none_or(|&b| b == 0)
{
return Err(VMError::TxValidation(
TxValidationError::InsufficientAccountFunds,
));
}
let fee_token_ratio = get_fee_token_ratio(vm, fee_token)?;
let sender_address = vm.env.origin;
let sender_info = vm.db.get_account(sender_address)?.info.clone();
let intrinsic = vm.get_intrinsic_gas()?;
if vm.env.config.fork >= Fork::Prague {
default_hook::validate_min_gas_limit(vm, &intrinsic)?;
if vm.env.config.fork < Fork::Amsterdam && vm.tx.gas_limit() > TX_MAX_GAS_LIMIT_AMSTERDAM {
return Err(VMError::TxValidation(
TxValidationError::TxMaxGasLimitExceeded {
tx_hash: vm.tx.hash(),
tx_gas_limit: vm.tx.gas_limit(),
},
));
}
if vm.env.config.fork >= Fork::Osaka
&& vm.env.config.fork < Fork::Amsterdam
&& vm.tx.gas_limit() > POST_OSAKA_GAS_LIMIT_CAP
{
return Err(VMError::TxValidation(
TxValidationError::TxMaxGasLimitExceeded {
tx_hash: vm.tx.hash(),
tx_gas_limit: vm.tx.gas_limit(),
},
));
}
}
let gaslimit_price_product = vm
.env
.gas_price
.checked_mul(vm.env.gas_limit.into())
.ok_or(TxValidationError::GasLimitPriceProductOverflow)?;
deduct_caller_fee_token(vm, gaslimit_price_product.saturating_mul(fee_token_ratio))?;
default_hook::validate_sufficient_max_fee_per_gas(vm)?;
if vm.is_create()? {
default_hook::validate_init_code_size(vm)?;
}
vm.add_intrinsic_gas(&intrinsic)?;
vm.increment_account_nonce(sender_address)
.map_err(|_| TxValidationError::NonceIsMax)?;
if sender_info.nonce != vm.env.tx_nonce {
return Err(TxValidationError::NonceMismatch {
expected: sender_info.nonce,
actual: vm.env.tx_nonce,
}
.into());
}
if let (Some(tx_max_priority_fee), Some(tx_max_fee_per_gas)) = (
vm.env.tx_max_priority_fee_per_gas,
vm.env.tx_max_fee_per_gas,
) && tx_max_priority_fee > tx_max_fee_per_gas
{
return Err(TxValidationError::PriorityGreaterThanMaxFeePerGas {
priority_fee: tx_max_priority_fee,
max_fee_per_gas: tx_max_fee_per_gas,
}
.into());
}
let code = vm.db.get_code(sender_info.code_hash)?;
default_hook::validate_sender(sender_address, code.code())?;
default_hook::validate_gas_allowance(vm)?;
default_hook::transfer_value(vm)?;
default_hook::set_bytecode_and_code_address(vm)?;
Ok(fee_token_ratio)
}
pub fn deduct_caller_fee_token(
vm: &mut VM<'_>,
gas_limit_price_product: U256,
) -> Result<(), VMError> {
let sender_address = vm.env.origin;
let value = vm.current_call_frame.msg_value;
vm.decrease_account_balance(sender_address, value)
.map_err(|_| TxValidationError::InsufficientAccountFunds)?;
lock_fee_token(vm, sender_address, gas_limit_price_product)?;
Ok(())
}
fn encode_fee_token_call(selector: [u8; 4], address: Address, amount: U256) -> Bytes {
let mut data = Vec::with_capacity(4 + 32 + 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&[0u8; 12]);
data.extend_from_slice(&address.0);
data.extend_from_slice(&amount.to_big_endian());
data.into()
}
fn encode_is_fee_token_call(token: Address) -> Bytes {
let mut data = Vec::with_capacity(4 + 32);
data.extend_from_slice(&IS_FEE_TOKEN_SELECTOR);
data.extend_from_slice(&[0u8; 12]);
data.extend_from_slice(&token.0);
data.into()
}
fn encode_fee_token_ratio_call(token: Address) -> Bytes {
let mut data = Vec::with_capacity(4 + 32);
data.extend_from_slice(&FEE_TOKEN_RATIO_SELECTOR);
data.extend_from_slice(&[0u8; 12]);
data.extend_from_slice(&token.0);
data.into()
}
fn lock_fee_token(vm: &mut VM<'_>, payer: Address, amount: U256) -> Result<(), VMError> {
transfer_fee_token(vm, encode_fee_token_call(LOCK_FEE_SELECTOR, payer, amount))
}
fn pay_fee_token(vm: &mut VM<'_>, receiver: Address, amount: U256) -> Result<(), VMError> {
transfer_fee_token(
vm,
encode_fee_token_call(PAY_FEE_SELECTOR, receiver, amount),
)
}
fn transfer_fee_token(vm: &mut VM<'_>, data: Bytes) -> Result<(), VMError> {
let fee_token = vm
.env
.fee_token
.ok_or(VMError::Internal(InternalError::Custom(
"No fee token address provided, this is a bug".to_owned(),
)))?;
let (execution_result, mut db_clone) = simulate_common_bridge_call(vm, fee_token, data)?;
if !execution_result.is_success() {
return Err(VMError::TxValidation(
TxValidationError::InsufficientAccountFunds,
));
}
let new_storage = db_clone.get_account(fee_token)?.storage.clone();
let current_storage = vm.db.get_account(fee_token)?.storage.clone();
for (key, new_value) in &new_storage {
let old_value = current_storage.get(key).copied().unwrap_or_default();
if old_value != *new_value {
vm.backup_storage_slot(fee_token, *key, old_value)?;
}
}
for (key, &old_value) in ¤t_storage {
if !new_storage.contains_key(key) {
vm.backup_storage_slot(fee_token, *key, old_value)?;
}
}
vm.db.get_account_mut(fee_token)?.storage = new_storage;
let initial_state_fee_token = db_clone
.initial_accounts_state
.get(&fee_token)
.cloned()
.ok_or(VMError::Internal(InternalError::Custom(
"No initial state found for fee token".to_owned(),
)))?;
vm.db
.initial_accounts_state
.insert(fee_token, initial_state_fee_token);
Ok(())
}
fn simulate_common_bridge_call(
vm: &VM<'_>,
to: Address,
data: Bytes,
) -> Result<(ExecutionReport, GeneralizedDatabase), VMError> {
let mut db_clone = vm.db.clone(); let origin = COMMON_BRIDGE_L2_ADDRESS; let nonce = db_clone.get_account(origin)?.info.nonce;
let simulation_tx = EIP1559Transaction {
chain_id: u64::try_from(vm.env.chain_id).map_err(|_| {
VMError::Internal(InternalError::Custom("chain_id overflows u64".to_string()))
})?,
nonce,
max_priority_fee_per_gas: SIMULATION_MAX_FEE,
max_fee_per_gas: SIMULATION_MAX_FEE,
gas_limit: SIMULATION_GAS_LIMIT,
to: TxKind::Call(to),
value: U256::zero(),
data,
..Default::default()
};
let tx = Transaction::EIP1559Transaction(simulation_tx);
let mut env_clone = vm.env.clone();
env_clone.base_fee_per_gas = U256::zero();
env_clone.block_excess_blob_gas = None;
env_clone.gas_price = U256::zero();
env_clone.origin = origin;
env_clone.fee_token = None;
env_clone.gas_limit = SIMULATION_GAS_LIMIT;
let mut new_vm = VM::new(
env_clone,
&mut db_clone,
&tx,
LevmCallTracer::disabled(),
VMType::L2(Default::default()),
vm.crypto,
)?;
new_vm.hooks = vec![];
default_hook::set_bytecode_and_code_address(&mut new_vm)?;
let execution_result = new_vm.execute()?;
Ok((execution_result, db_clone))
}
fn refund_sender_fee_token(
vm: &mut VM<'_>,
ctx_result: &mut ContextResult,
refunded_gas: u64,
gas_spent: u64,
gas_used_pre_refund: u64,
fee_token_ratio: u64,
) -> Result<(), VMError> {
vm.substate.refunded_gas = refunded_gas;
if vm.env.config.fork >= Fork::Amsterdam {
ctx_result.gas_used = gas_used_pre_refund;
ctx_result.gas_spent = gas_spent;
} else {
ctx_result.gas_used = gas_spent;
ctx_result.gas_spent = gas_spent;
}
let gas_to_return = vm
.env
.gas_limit
.checked_sub(gas_spent)
.ok_or(InternalError::Underflow)?;
let erc20_return_amount = vm
.env
.gas_price
.checked_mul(U256::from(gas_to_return))
.ok_or(InternalError::Overflow)?;
let sender_address = vm.env.origin;
pay_fee_token(
vm,
sender_address,
erc20_return_amount.saturating_mul(fee_token_ratio.into()),
)?;
Ok(())
}
fn calculate_l1_fee(
fee_config: &L1FeeConfig,
transaction_size: usize,
) -> Result<U256, crate::errors::VMError> {
let l1_fee_per_blob: U256 = fee_config
.l1_fee_per_blob_gas
.checked_mul(GAS_PER_BLOB.into())
.ok_or(InternalError::Overflow)?
.into();
let l1_fee_per_blob_byte = l1_fee_per_blob
.checked_div(U256::from(SAFE_BYTES_PER_BLOB))
.ok_or(InternalError::DivisionByZero)?;
let l1_fee = l1_fee_per_blob_byte
.checked_mul(U256::from(transaction_size))
.ok_or(InternalError::Overflow)?;
Ok(l1_fee)
}
fn calculate_l1_fee_gas(
vm: &VM<'_>,
l1_fee_config: &Option<L1FeeConfig>,
) -> Result<u64, crate::errors::VMError> {
let Some(fee_config) = l1_fee_config else {
return Ok(0);
};
let tx_size = vm.tx.length();
let l1_fee = calculate_l1_fee(fee_config, tx_size)?;
let mut l1_fee_gas = l1_fee
.checked_div(vm.env.gas_price)
.ok_or(InternalError::DivisionByZero)?;
if l1_fee_gas == U256::zero() && l1_fee > U256::zero() {
l1_fee_gas = U256::one();
}
Ok(l1_fee_gas.try_into().map_err(|_| InternalError::Overflow)?)
}
fn pay_to_l1_fee_vault(
vm: &mut VM<'_>,
gas_to_pay: u64,
l1_fee_config: L1FeeConfig,
use_fee_token: bool,
) -> Result<(), crate::errors::VMError> {
let l1_fee = U256::from(gas_to_pay)
.checked_mul(vm.env.gas_price)
.ok_or(InternalError::Overflow)?;
if use_fee_token {
pay_fee_token(vm, l1_fee_config.l1_fee_vault, l1_fee)?;
} else {
vm.increase_account_balance(l1_fee_config.l1_fee_vault, l1_fee)
.map_err(|_| TxValidationError::InsufficientAccountFunds)?;
}
Ok(())
}
fn get_fee_token_ratio(vm: &mut VM<'_>, fee_token: H160) -> Result<U256, VMError> {
let fee_token_ratio = simulate_common_bridge_call(
vm,
FEE_TOKEN_RATIO_ADDRESS,
encode_fee_token_ratio_call(fee_token),
)?
.0;
if !fee_token_ratio.is_success() || fee_token_ratio.output.len() != 32 {
return Err(VMError::Internal(InternalError::Custom(
"Failed to get fee token ratio".to_owned(),
)));
}
Ok(U256::from_big_endian(
fee_token_ratio
.output
.get(0..32)
.ok_or(InternalError::Custom(
"Failed to parse fee token ratio".to_owned(),
))?,
))
}