From d2eb0c4ef8ae2d0247cb428678be39b799da662d Mon Sep 17 00:00:00 2001 From: aldenhu Date: Sun, 10 Dec 2023 19:10:35 +0000 Subject: [PATCH] ephemeral storage fee --- aptos-move/aptos-gas-meter/src/traits.rs | 39 ++-- .../aptos-gas-profiling/src/profiler.rs | 42 +++-- .../src/gas_schedule/transaction.rs | 53 +++++- aptos-move/aptos-gas-schedule/src/ver.rs | 2 + .../src/components/feature_flags.rs | 3 + aptos-move/aptos-vm-types/src/storage/mod.rs | 13 +- .../src/storage/space_pricing.rs | 170 ++++++++++++++++-- aptos-move/aptos-vm-types/src/tests/utils.rs | 2 +- aptos-move/aptos-vm/src/aptos_vm.rs | 4 +- aptos-move/aptos-vm/src/gas.rs | 5 +- .../src/move_vm_ext/write_op_converter.rs | 4 +- .../src/proptest_types/types.rs | 2 +- .../src/tests/per_category_gas_limits.rs | 4 +- .../src/tests/state_metadata.rs | 6 +- .../src/tests/storage_refund.rs | 2 +- .../framework/move-stdlib/doc/features.md | 11 ++ .../move-stdlib/sources/configs/features.move | 3 + types/src/on_chain_config/aptos_features.rs | 7 + types/src/state_store/state_value.rs | 65 ++++++- 19 files changed, 362 insertions(+), 75 deletions(-) diff --git a/aptos-move/aptos-gas-meter/src/traits.rs b/aptos-move/aptos-gas-meter/src/traits.rs index 3068b3b939d9a..720b0a2be8ea3 100644 --- a/aptos-move/aptos-gas-meter/src/traits.rs +++ b/aptos-move/aptos-gas-meter/src/traits.rs @@ -142,17 +142,23 @@ pub trait AptosGasMeter: MoveGasMeter { let pricing = self.disk_space_pricing(); let params = &self.vm_gas_params().txn; - // Calculate the storage fees. - let mut write_fee = Fee::new(0); - let mut total_refund = Fee::new(0); + // Write set + let mut writeset_charge_and_refund = ChargeAndRefund::zero(); for (key, op_size, metadata_opt) in change_set.write_set_iter_mut() { - let ChargeAndRefund { charge, refund } = - pricing.charge_refund_write_op(params, key, &op_size, metadata_opt); - total_refund += refund; - - write_fee += charge; + writeset_charge_and_refund.combine(pricing.charge_refund_write_op( + params, + key, + &op_size, + metadata_opt, + )); } + let ChargeAndRefund { + non_discountable, + discountable, + refund, + } = writeset_charge_and_refund; + // Events let event_fee = change_set .events() .iter() @@ -160,16 +166,25 @@ pub trait AptosGasMeter: MoveGasMeter { acc + pricing.storage_fee_per_event(params, event) }); let event_discount = pricing.storage_discount_for_events(params, event_fee); - let event_net_fee = event_fee + let net_event_fee = event_fee .checked_sub(event_discount) - .expect("discount should always be less than or equal to total amount"); + .expect("event discount should always be less than or equal to total amount"); + + // Txn let txn_fee = pricing.storage_fee_for_transaction_storage(params, txn_size); - let fee = write_fee + event_net_fee + txn_fee; + // Ephemeral fee discount + let total_discountable = discountable + net_event_fee + txn_fee; + let discount = pricing.ephemeral_storage_fee_discount(params, total_discountable); + let net_ephemeral = total_discountable + .checked_sub(discount) + .expect("ephemeral fee discount should always be less than or equal to total amount"); + + let fee = non_discountable + net_ephemeral; self.charge_storage_fee(fee, gas_unit_price) .map_err(|err| err.finish(Location::Undefined))?; - Ok(total_refund) + Ok(refund) } // Below are getters reexported from the gas algebra. diff --git a/aptos-move/aptos-gas-profiling/src/profiler.rs b/aptos-move/aptos-gas-profiling/src/profiler.rs index cd87cd74b601f..b5aa8c9a191df 100644 --- a/aptos-move/aptos-gas-profiling/src/profiler.rs +++ b/aptos-move/aptos-gas-profiling/src/profiler.rs @@ -523,23 +523,26 @@ where let pricing = self.disk_space_pricing(); let params = &self.vm_gas_params().txn; - // Writes - let mut write_fee = Fee::new(0); + // Write set let mut write_set_storage = vec![]; - let mut total_refund = Fee::new(0); + let mut writeset_charge_and_refund = ChargeAndRefund::zero(); for (key, op_size, metadata_opt) in change_set.write_set_iter_mut() { - let ChargeAndRefund { charge, refund } = + let charge_and_refund = pricing.charge_refund_write_op(params, key, &op_size, metadata_opt); - write_fee += charge; - total_refund += refund; write_set_storage.push(WriteStorage { key: key.clone(), op_type: write_op_type(&op_size), - cost: charge, - refund, + cost: charge_and_refund.non_discountable + charge_and_refund.discountable, + refund: charge_and_refund.refund, }); + writeset_charge_and_refund.combine(charge_and_refund); } + let ChargeAndRefund { + non_discountable, + discountable, + refund, + } = writeset_charge_and_refund; // Events let mut event_fee = Fee::new(0); @@ -553,16 +556,24 @@ where event_fee += fee; } let event_discount = pricing.storage_discount_for_events(params, event_fee); - let event_fee_with_discount = event_fee + let net_event_fee = event_fee .checked_sub(event_discount) .expect("discount should always be less than or equal to total amount"); // Txn let txn_fee = pricing.storage_fee_for_transaction_storage(params, txn_size); + // Ephemeral fee discount + let total_discountable = discountable + net_event_fee + txn_fee; + let discount = pricing.ephemeral_storage_fee_discount(params, total_discountable); + let net_ephemeral = total_discountable + .checked_sub(discount) + .expect("ephemeral fee discount should always be less than or equal to total amount"); + let fee = non_discountable + net_ephemeral; + self.storage_fees = Some(StorageFees { - total: write_fee + event_fee + txn_fee, - total_refund, + total: fee, + total_refund: refund, write_set_storage, events: event_fees, @@ -570,13 +581,10 @@ where txn_storage: txn_fee, }); - self.charge_storage_fee( - write_fee + event_fee_with_discount + txn_fee, - gas_unit_price, - ) - .map_err(|err| err.finish(Location::Undefined))?; + self.charge_storage_fee(fee, gas_unit_price) + .map_err(|err| err.finish(Location::Undefined))?; - Ok(total_refund) + Ok(refund) } fn charge_intrinsic_gas_for_transaction(&mut self, txn_size: NumBytes) -> VMResult<()> { diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs index fd8f088dbf1f6..bc051ddc41a4b 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs @@ -124,10 +124,15 @@ crate::gas_schedule::macros::define_gas_parameters!( 1024, // 1KB free per state write ], [ - free_event_bytes_quota: NumBytes, - { 7.. => "free_event_bytes_quota" }, + legacy_free_event_bytes_quota: NumBytes, + { 7..=11 => "free_event_bytes_quota", 12.. => "legacy_free_event_bytes_quota" }, 1024, // 1KB free event bytes per transaction ], + [ + ephemeral_storage_fee_discount_per_transaction: Fee, + { 12.. => "ephemeral_storage_fee_discount_per_transaction" }, + 50000, // FIXME(aldenhu): tune -- for example, 4k of ephemeral bytes, roughly 50k octas + ], [ max_bytes_per_write_op: NumBytes, { 5.. => "max_bytes_per_write_op" }, @@ -154,24 +159,54 @@ crate::gas_schedule::macros::define_gas_parameters!( 8192, ], [ - storage_fee_per_state_slot_create: FeePerSlot, - { 7.. => "storage_fee_per_state_slot_create" }, + legacy_storage_fee_per_state_slot_create: FeePerSlot, + { 7..=11 => "storage_fee_per_state_slot_create", 12 => "legacy_storage_fee_per_state_slot_create" }, 50000, ], [ - storage_fee_per_excess_state_byte: FeePerByte, - { 7.. => "storage_fee_per_excess_state_byte" }, + storage_fee_per_state_slot_refundable: FeePerSlot, + { 12.. => "storage_fee_per_state_slot_refundable" }, + 50000, // FIXME(aldenhu): tune + ], + [ + legacy_storage_fee_per_excess_state_byte: FeePerByte, + { 7..=11 => "storage_fee_per_excess_state_byte", 12 => "legacy_storage_fee_per_excess_state_byte" }, 50, ], + [ + storage_fee_per_state_byte_refundable: FeePerByte, + { 12.. => "storage_fee_per_state_byte_refundable" }, + 50, // FIXME(aldenhu): tune + ], + [ + storage_fee_per_write_op: FeePerSlot, + { 12.. => "storage_fee_per_write_op" }, + 10000, // FIMXME(aldenhu): tune -- calculate based on the mainnet tree height + ], + [ + storage_fee_per_write_set_byte: FeePerByte, + { 12.. => "storage_fee_per_write_set_byte" }, + 10, // FIMXME(aldenhu): tune + ], + [ + legacy_storage_fee_per_event_byte: FeePerByte, + { 7..=11 => "storage_fee_per_event_byte", 12 => "legacy_storage_fee_per_event_byte" }, + 20, + ], [ storage_fee_per_event_byte: FeePerByte, - { 7.. => "storage_fee_per_event_byte" }, + { 12 => "storage_fee_per_event_byte" }, + 10, // FIXME(aldenhu): tune + ], + [ + legacy_storage_fee_per_transaction_byte: FeePerByte, + { 7..=11 => "storage_fee_per_transaction_byte", 12 => "legacy_storage_fee_per_transaction_byte" }, 20, ], [ storage_fee_per_transaction_byte: FeePerByte, - { 7.. => "storage_fee_per_transaction_byte" }, - 20, + { 12 => "storage_fee_per_transaction_byte" }, + 10, // FIXME(aldenhu): tune ], [ max_execution_gas: InternalGas, diff --git a/aptos-move/aptos-gas-schedule/src/ver.rs b/aptos-move/aptos-gas-schedule/src/ver.rs index 1937d8acb8edd..2d2da95c84a4a 100644 --- a/aptos-move/aptos-gas-schedule/src/ver.rs +++ b/aptos-move/aptos-gas-schedule/src/ver.rs @@ -11,6 +11,8 @@ /// - V12 /// - Making resource group charge on first read independent of BTreeMap serialization. /// - Added BN254 operations. +/// - IO gas change: 1. read bytes charged at 4KB intervals; 2. ignore free_write_bytes_quota +/// - Support "ephemeral" storage fee scheme, gated by FeatrueFlag::EPHEMERAL_STORAGE_FEE /// - V11 /// - Ristretto255 natives (point cloning & double-scalar multiplication) and Bulletproofs natives /// - Hard limit on the number of write ops per transaction diff --git a/aptos-move/aptos-release-builder/src/components/feature_flags.rs b/aptos-move/aptos-release-builder/src/components/feature_flags.rs index 8b522629e3ab3..4578df0418f09 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -94,6 +94,7 @@ pub enum FeatureFlag { ResourceGroupsChargeAsSizeSum, CommissionChangeDelegationPool, BN254Structures, + EphemeralStorageFee, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -246,6 +247,7 @@ impl From for AptosFeatureFlag { AptosFeatureFlag::COMMISSION_CHANGE_DELEGATION_POOL }, FeatureFlag::BN254Structures => AptosFeatureFlag::BN254_STRUCTURES, + FeatureFlag::EphemeralStorageFee => AptosFeatureFlag::EPHEMERAL_STORAGE_FEE, } } } @@ -321,6 +323,7 @@ impl From for FeatureFlag { FeatureFlag::CommissionChangeDelegationPool }, AptosFeatureFlag::BN254_STRUCTURES => FeatureFlag::BN254Structures, + AptosFeatureFlag::EPHEMERAL_STORAGE_FEE => FeatureFlag::EphemeralStorageFee, } } } diff --git a/aptos-move/aptos-vm-types/src/storage/mod.rs b/aptos-move/aptos-vm-types/src/storage/mod.rs index d70336f052be4..3901857927fe2 100644 --- a/aptos-move/aptos-vm-types/src/storage/mod.rs +++ b/aptos-move/aptos-vm-types/src/storage/mod.rs @@ -7,7 +7,7 @@ use crate::storage::{ space_pricing::DiskSpacePricing, }; use aptos_gas_schedule::{AptosGasParameters, LATEST_GAS_FEATURE_VERSION}; -use aptos_types::on_chain_config::ConfigStorage; +use aptos_types::on_chain_config::{ConfigStorage, Features}; use move_core_types::gas_algebra::NumBytes; use std::fmt::Debug; @@ -24,13 +24,14 @@ pub struct StorageGasParameters { impl StorageGasParameters { pub fn new( - feature_version: u64, + gas_feature_version: u64, + features: &Features, gas_params: &AptosGasParameters, config_storage: &impl ConfigStorage, ) -> Self { - let io_pricing = IoPricing::new(feature_version, gas_params, config_storage); - let space_pricing = DiskSpacePricing::v1(); - let change_set_configs = ChangeSetConfigs::new(feature_version, gas_params); + let io_pricing = IoPricing::new(gas_feature_version, gas_params, config_storage); + let space_pricing = DiskSpacePricing::new(gas_feature_version, features); + let change_set_configs = ChangeSetConfigs::new(gas_feature_version, gas_params); Self { io_pricing, @@ -45,7 +46,7 @@ impl StorageGasParameters { feature_version: LATEST_GAS_FEATURE_VERSION, legacy_free_write_bytes_quota: free_write_bytes_quota, }), - space_pricing: DiskSpacePricing::v1(), + space_pricing: DiskSpacePricing::latest(), change_set_configs: ChangeSetConfigs::unlimited_at_gas_feature_version( LATEST_GAS_FEATURE_VERSION, ), diff --git a/aptos-move/aptos-vm-types/src/storage/space_pricing.rs b/aptos-move/aptos-vm-types/src/storage/space_pricing.rs index 02e305a4102ec..c6fc92693f0ae 100644 --- a/aptos-move/aptos-vm-types/src/storage/space_pricing.rs +++ b/aptos-move/aptos-vm-types/src/storage/space_pricing.rs @@ -5,6 +5,7 @@ use aptos_gas_algebra::{Fee, NumSlots}; use aptos_gas_schedule::TransactionGasParameters; use aptos_types::{ contract_event::ContractEvent, + on_chain_config::Features, state_store::{state_key::StateKey, state_value::StateValueMetadata}, write_set::WriteOpSize, }; @@ -12,19 +13,56 @@ use move_core_types::gas_algebra::NumBytes; use std::fmt::Debug; pub struct ChargeAndRefund { - pub charge: Fee, + // The amount not subject to the per txn discount, including all DiskSpacePricingV1 charges + // and the refundable portion of DiskSpacePricingV2 charges (state slot and state bytes charges). + pub non_discountable: Fee, + // The amount subject to the per txn discounts, i.e. the "ephemeral bytes" charges by + // DiskSpacePricingV2. + pub discountable: Fee, pub refund: Fee, } +impl ChargeAndRefund { + pub fn zero() -> Self { + Self { + non_discountable: 0.into(), + discountable: 0.into(), + refund: 0.into(), + } + } + + pub fn combine(&mut self, other: Self) { + let Self { + non_discountable, + discountable, + refund, + } = other; + + self.non_discountable += non_discountable; + self.discountable += discountable; + self.refund += refund; + } +} + #[derive(Clone, Debug)] pub enum DiskSpacePricing { /// With per state slot free write quota V1, + /// With per txn ephemeral storage fee discount + V2, } impl DiskSpacePricing { - pub fn v1() -> Self { - Self::V1 + pub fn new(gas_feature_version: u64, features: &Features) -> Self { + if gas_feature_version >= 12 && features.is_ephemeral_storage_fee_enabled() { + Self::V2 + } else { + Self::V1 + } + } + + pub fn latest() -> Self { + Self::V2 } /// Calculates the storage fee for a state slot allocation. @@ -37,6 +75,7 @@ impl DiskSpacePricing { ) -> ChargeAndRefund { match self { Self::V1 => Self::charge_refund_write_op_v1(params, key, op_size, metadata), + Self::V2 => Self::charge_refund_write_op_v2(params, key, op_size, metadata), } } @@ -47,11 +86,16 @@ impl DiskSpacePricing { event: &ContractEvent, ) -> Fee { match self { - Self::V1 => NumBytes::new(event.size() as u64) * params.storage_fee_per_event_byte, + Self::V1 => { + NumBytes::new(event.size() as u64) * params.legacy_storage_fee_per_event_byte + }, + Self::V2 => NumBytes::new(event.size() as u64) * params.storage_fee_per_event_byte, } } /// Calculates the discount applied to the event storage fees, based on a free quota. + /// + /// This is specific to DiskSpacePricingV1, and applicable to only event bytes. pub fn storage_discount_for_events( &self, params: &TransactionGasParameters, @@ -60,8 +104,9 @@ impl DiskSpacePricing { match self { Self::V1 => std::cmp::min( total_cost, - params.free_event_bytes_quota * params.storage_fee_per_event_byte, + params.legacy_free_event_bytes_quota * params.legacy_storage_fee_per_event_byte, ), + Self::V2 => 0.into(), } } @@ -76,8 +121,26 @@ impl DiskSpacePricing { txn_size .checked_sub(params.large_transaction_cutoff) .unwrap_or(NumBytes::zero()) - * params.storage_fee_per_transaction_byte + * params.legacy_storage_fee_per_transaction_byte }, + Self::V2 => txn_size * params.storage_fee_per_transaction_byte, + } + } + + /// Calculates the discount applied to the total of ephemeral storage fees, based on a free quota. + /// + /// This is specific to DiskSpacePricingV2, where the per state slot free write quota is removed. + pub fn ephemeral_storage_fee_discount( + &self, + params: &TransactionGasParameters, + total_ephemeral_fee: Fee, + ) -> Fee { + match self { + DiskSpacePricing::V1 => 0.into(), + DiskSpacePricing::V2 => std::cmp::min( + total_ephemeral_fee, + params.ephemeral_storage_fee_discount_per_transaction, + ), } } @@ -103,37 +166,116 @@ impl DiskSpacePricing { match op_size { Creation { write_len } => { - let slot_fee = params.storage_fee_per_state_slot_create * NumSlots::new(1); + let slot_fee = params.legacy_storage_fee_per_state_slot_create * NumSlots::new(1); let bytes_fee = Self::discounted_write_op_size_for_v1(params, key, *write_len) - * params.storage_fee_per_excess_state_byte; + * params.legacy_storage_fee_per_excess_state_byte; if let Some(m) = metadata { - m.set_deposit(slot_fee.into()) + m.set_slot_deposit(slot_fee.into()) } ChargeAndRefund { - charge: slot_fee + bytes_fee, + non_discountable: slot_fee + bytes_fee, + discountable: 0.into(), refund: 0.into(), } }, Modification { write_len } => { let bytes_fee = Self::discounted_write_op_size_for_v1(params, key, *write_len) - * params.storage_fee_per_excess_state_byte; + * params.legacy_storage_fee_per_excess_state_byte; + + ChargeAndRefund { + non_discountable: bytes_fee, + discountable: 0.into(), + refund: 0.into(), + } + }, + Deletion => { + let refund = match metadata { + None => 0, + Some(m) => m.total_deposit(), + } + .into(); ChargeAndRefund { - charge: bytes_fee, + non_discountable: 0.into(), + discountable: 0.into(), + refund, + } + }, + } + } + + fn charge_refund_write_op_v2( + params: &TransactionGasParameters, + key: &StateKey, + op_size: &WriteOpSize, + metadata: Option<&mut StateValueMetadata>, + ) -> ChargeAndRefund { + use WriteOpSize::*; + + // ephemeral storage fee + let write_op_fee = params.storage_fee_per_write_op * NumSlots::new(1); + let num_bytes = + NumBytes::new(key.size() as u64) + NumBytes::new(op_size.write_len().unwrap_or(0)); + let write_op_bytes_fee = params.storage_fee_per_write_set_byte * num_bytes; + let discountable = write_op_fee + write_op_bytes_fee; + + match op_size { + Creation { .. } => { + // permanent storage fee + let slot_deposit = params.storage_fee_per_state_slot_refundable * NumSlots::new(1); + let bytes_deposit = num_bytes * params.storage_fee_per_state_byte_refundable; + + if let Some(m) = metadata { + m.set_deposits(slot_deposit.into(), bytes_deposit.into()) + } else { + // FIXME(aldenhu): this shouldn't happen + } + + ChargeAndRefund { + non_discountable: slot_deposit + bytes_deposit, + discountable, refund: 0.into(), } }, + Modification { write_len } => { + // change of slot size or per byte price can result in a charge or refund of permanent bytes fee + let num_bytes = NumBytes::new(key.size() as u64) + NumBytes::new(*write_len); + let target_bytes_deposit = num_bytes * params.storage_fee_per_state_byte_refundable; + let old_bytes_deposit = metadata.as_ref().map_or(0, |m| m.bytes_deposit()).into(); + let (state_bytes_charge, state_bytes_refund) = if target_bytes_deposit + > old_bytes_deposit + { + let bytes_deposit = + target_bytes_deposit.checked_sub(old_bytes_deposit).unwrap(); + (bytes_deposit, 0.into()) + } else { + let bytes_refund = old_bytes_deposit.checked_sub(target_bytes_deposit).unwrap(); + (0.into(), bytes_refund) + }; + + // FIXME(aldenhu): upgrade to new format automatically, otherwise old slots will be heavily punished whenever modified. + if let Some(m) = metadata { + m.set_bytes_deposit(target_bytes_deposit.into()) + } + + ChargeAndRefund { + non_discountable: state_bytes_charge, + discountable, + refund: state_bytes_refund, + } + }, Deletion => { let refund = match metadata { None => 0, - Some(m) => m.deposit(), + Some(m) => m.total_deposit(), } .into(); ChargeAndRefund { - charge: 0.into(), + non_discountable: 0.into(), + discountable, refund, } }, diff --git a/aptos-move/aptos-vm-types/src/tests/utils.rs b/aptos-move/aptos-vm-types/src/tests/utils.rs index 1e985c9d1dabf..0fbb68c20786c 100644 --- a/aptos-move/aptos-vm-types/src/tests/utils.rs +++ b/aptos-move/aptos-vm-types/src/tests/utils.rs @@ -59,7 +59,7 @@ macro_rules! as_bytes { pub(crate) use as_bytes; pub(crate) fn raw_metadata(v: u64) -> StateValueMetadata { - StateValueMetadata::new(v, &CurrentTimeMicroseconds { microseconds: v }) + StateValueMetadata::new_v0(v, &CurrentTimeMicroseconds { microseconds: v }) } pub(crate) fn mock_create(k: impl ToString, v: u128) -> (StateKey, WriteOp) { diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index 0022aae5bdaf4..fd35f28d8d296 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -173,14 +173,14 @@ impl AptosVM { pub fn new(resolver: &impl AptosMoveResolver) -> Self { let _timer = TIMER.timer_with(&["AptosVM::new"]); + let features = Features::fetch_config(resolver).unwrap_or_default(); let ( gas_params, storage_gas_params, native_gas_params, misc_gas_params, gas_feature_version, - ) = get_gas_parameters(resolver); - let features = Features::fetch_config(resolver).unwrap_or_default(); + ) = get_gas_parameters(&features, resolver); // If no chain ID is in storage, we assume we are in a testing environment and use ChainId::TESTING let chain_id = ChainId::fetch_config(resolver).unwrap_or_else(ChainId::test); diff --git a/aptos-move/aptos-vm/src/gas.rs b/aptos-move/aptos-vm/src/gas.rs index 1e99bba5b631f..9c0911b82428b 100644 --- a/aptos-move/aptos-vm/src/gas.rs +++ b/aptos-move/aptos-vm/src/gas.rs @@ -8,7 +8,7 @@ use aptos_gas_schedule::{ }; use aptos_logger::{enabled, Level}; use aptos_types::on_chain_config::{ - ApprovedExecutionHashes, ConfigStorage, GasSchedule, GasScheduleV2, OnChainConfig, + ApprovedExecutionHashes, ConfigStorage, Features, GasSchedule, GasScheduleV2, OnChainConfig, }; use aptos_vm_logging::{log_schema::AdapterLogSchema, speculative_log, speculative_warn}; use aptos_vm_types::storage::{io_pricing::IoPricing, StorageGasParameters}; @@ -43,6 +43,7 @@ pub(crate) fn get_gas_config_from_storage( } pub(crate) fn get_gas_parameters( + features: &Features, config_storage: &impl ConfigStorage, ) -> ( Result, @@ -56,7 +57,7 @@ pub(crate) fn get_gas_parameters( let storage_gas_params = match &mut gas_params { Ok(gas_params) => { let storage_gas_params = - StorageGasParameters::new(gas_feature_version, gas_params, config_storage); + StorageGasParameters::new(gas_feature_version, features, gas_params, config_storage); // Overwrite table io gas parameters with global io pricing. let g = &mut gas_params.natives.table; diff --git a/aptos-move/aptos-vm/src/move_vm_ext/write_op_converter.rs b/aptos-move/aptos-vm/src/move_vm_ext/write_op_converter.rs index da08af9f31887..823c40df58ee2 100644 --- a/aptos-move/aptos-vm/src/move_vm_ext/write_op_converter.rs +++ b/aptos-move/aptos-vm/src/move_vm_ext/write_op_converter.rs @@ -154,7 +154,7 @@ impl<'r> WriteOpConverter<'r> { if let Some(current_time) = CurrentTimeMicroseconds::fetch_config(remote) { // The deposit on the metadata is a placeholder (0), it will be updated later when // storage fee is charged. - new_slot_metadata = Some(StateValueMetadata::new(0, ¤t_time)); + new_slot_metadata = Some(StateValueMetadata::new_placeholder(¤t_time)); } } @@ -396,7 +396,7 @@ mod tests { }; fn raw_metadata(v: u64) -> StateValueMetadata { - StateValueMetadata::new(v, &CurrentTimeMicroseconds { microseconds: v }) + StateValueMetadata::new_v0(v, &CurrentTimeMicroseconds { microseconds: v }) } // TODO: Can re-use some of these testing definitions with aptos-vm-types. diff --git a/aptos-move/block-executor/src/proptest_types/types.rs b/aptos-move/block-executor/src/proptest_types/types.rs index 1a4e876c8f6b3..8a0f3d0fcc662 100644 --- a/aptos-move/block-executor/src/proptest_types/types.rs +++ b/aptos-move/block-executor/src/proptest_types/types.rs @@ -971,7 +971,7 @@ where } pub(crate) fn raw_metadata(v: u64) -> StateValueMetadataKind { - Some(StateValueMetadata::new(v, &CurrentTimeMicroseconds { + Some(StateValueMetadata::new_v0(v, &CurrentTimeMicroseconds { microseconds: v, })) } diff --git a/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs b/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs index 56a5269bb0542..3e2c8c6b7ef55 100644 --- a/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs +++ b/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs @@ -107,7 +107,7 @@ fn storage_limit_reached_by_new_bytes() { // Modify the gas schedule. h.modify_gas_schedule(|gas_params| { // Make other aspects of the gas schedule irrelevant by setting byte fee super high. - gas_params.vm.txn.storage_fee_per_excess_state_byte = 1_000_000.into(); + gas_params.vm.txn.legacy_storage_fee_per_excess_state_byte = 1_000_000.into(); gas_params.vm.txn.storage_io_per_state_byte_write = 1.into(); // Allow 10 value bytes charged at most. gas_params.vm.txn.max_storage_fee = 11_000_000.into(); @@ -150,7 +150,7 @@ fn out_of_gas_while_charging_storage_fee() { // Modify the gas schedule. h.modify_gas_schedule(|gas_params| { // Make other aspects of the gas schedule irrelevant by setting per state byte storage fee super high. - gas_params.vm.txn.storage_fee_per_excess_state_byte = 1_000_000.into(); + gas_params.vm.txn.legacy_storage_fee_per_excess_state_byte = 1_000_000.into(); // Make sure we don't hit storage fee limit gas_params.vm.txn.max_storage_fee = 100_000_000.into(); // Bump max gas allowed diff --git a/aptos-move/e2e-move-tests/src/tests/state_metadata.rs b/aptos-move/e2e-move-tests/src/tests/state_metadata.rs index 28488a7745a75..7f94932043a69 100644 --- a/aptos-move/e2e-move-tests/src/tests/state_metadata.rs +++ b/aptos-move/e2e-move-tests/src/tests/state_metadata.rs @@ -52,14 +52,14 @@ fn test_metadata_tracking() { .unwrap() .vm .txn - .storage_fee_per_state_slot_create + .legacy_storage_fee_per_state_slot_create .into(); assert!(slot_fee > 0); // Observe that metadata is tracked for address3 resources assert_eq!( harness.read_resource_metadata(&address3, coin_store.clone()), - Some(Some(StateValueMetadata::new(slot_fee, ×tamp,))), + Some(Some(StateValueMetadata::new_v0(slot_fee, ×tamp,))), ); // Bump the timestamp and modify the resources, observe that metadata doesn't change. @@ -78,6 +78,6 @@ fn test_metadata_tracking() { ); assert_eq!( harness.read_resource_metadata(&address3, coin_store), - Some(Some(StateValueMetadata::new(slot_fee, ×tamp))), + Some(Some(StateValueMetadata::new_v0(slot_fee, ×tamp))), ); } diff --git a/aptos-move/e2e-move-tests/src/tests/storage_refund.rs b/aptos-move/e2e-move-tests/src/tests/storage_refund.rs index 141096bedfe64..e92836aaea1a7 100644 --- a/aptos-move/e2e-move-tests/src/tests/storage_refund.rs +++ b/aptos-move/e2e-move-tests/src/tests/storage_refund.rs @@ -96,7 +96,7 @@ fn read_slot_fee_from_gas_schedule(h: &MoveHarness) -> u64 { .unwrap() .vm .txn - .storage_fee_per_state_slot_create + .legacy_storage_fee_per_state_slot_create .into(); assert!(slot_fee > 0); assert!(slot_fee > LEEWAY * 10); diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md index 2556a8acbd7a7..b0d9ccbcff737 100644 --- a/aptos-move/framework/move-stdlib/doc/features.md +++ b/aptos-move/framework/move-stdlib/doc/features.md @@ -345,6 +345,17 @@ The provided signer has not a framework address. + + +Whether use the new way of charging storage fees where the per write free quota is removed and all bytes are +charged at a lower rate on modification. + + +
const EPHEMERAL_STORAGE_FEE: u64 = 44;
+
+ + + diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index 98f775a759cc0..b8679732130bb 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -331,6 +331,9 @@ module std::features { public fun bn254_structures_enabled(): bool acquires Features { is_enabled(BN254_STRUCTURES) } + /// Whether use the new way of charging storage fees where the per write free quota is removed and all bytes are + /// charged at a lower rate on modification. + const EPHEMERAL_STORAGE_FEE: u64 = 44; // ============================================================================================ // Feature Flag Implementation diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index f06b38eced5f4..73b24a2feaba6 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -51,6 +51,7 @@ pub enum FeatureFlag { RESOURCE_GROUPS_CHARGE_AS_SIZE_SUM = 41, COMMISSION_CHANGE_DELEGATION_POOL = 42, BN254_STRUCTURES = 43, + EPHEMERAL_STORAGE_FEE = 44, } /// Representation of features on chain as a bitset. @@ -75,6 +76,8 @@ impl Default for Features { features.enable(SIGNATURE_CHECKER_V2_SCRIPT_FIX); features.enable(AGGREGATOR_V2_API); features.enable(BN254_STRUCTURES); + features.enable(EPHEMERAL_STORAGE_FEE); + features } } @@ -144,4 +147,8 @@ impl Features { pub fn is_resource_group_charge_as_size_sum_enabled(&self) -> bool { self.is_enabled(FeatureFlag::RESOURCE_GROUPS_CHARGE_AS_SIZE_SUM) } + + pub fn is_ephemeral_storage_fee_enabled(&self) -> bool { + self.is_enabled(FeatureFlag::EPHEMERAL_STORAGE_FEE) + } } diff --git a/types/src/state_store/state_value.rs b/types/src/state_store/state_value.rs index 629c46315327e..33698366cf57c 100644 --- a/types/src/state_store/state_value.rs +++ b/types/src/state_store/state_value.rs @@ -33,28 +33,87 @@ pub enum StateValueMetadata { deposit: u64, creation_time_usecs: u64, }, + V1 { + slot_deposit: u64, + bytes_deposit: u64, + creation_time_usecs: u64, + }, } // To avoid nested options when fetching a resource and its metadata. pub type StateValueMetadataKind = Option; impl StateValueMetadata { - pub fn new(deposit: u64, creation_time_usecs: &CurrentTimeMicroseconds) -> Self { + // FIXME(aldenhu): update tests and remove + pub fn new_v0(deposit: u64, creation_time_usecs: &CurrentTimeMicroseconds) -> Self { Self::V0 { deposit, creation_time_usecs: creation_time_usecs.microseconds, } } - pub fn deposit(&self) -> u64 { + pub fn new_placeholder(creation_time_usecs: &CurrentTimeMicroseconds) -> Self { + Self::new_v0(0, creation_time_usecs) + } + + pub fn creation_time_usecs(&self) -> u64 { + match self { + StateValueMetadata::V0 { + creation_time_usecs, + .. + } + | StateValueMetadata::V1 { + creation_time_usecs, + .. + } => *creation_time_usecs, + } + } + + pub fn slot_deposit(&self) -> u64 { match self { StateValueMetadata::V0 { deposit, .. } => *deposit, + StateValueMetadata::V1 { slot_deposit, .. } => *slot_deposit, + } + } + + pub fn bytes_deposit(&self) -> u64 { + match self { + StateValueMetadata::V0 { .. } => 0, + StateValueMetadata::V1 { bytes_deposit, .. } => *bytes_deposit, } } - pub fn set_deposit(&mut self, amount: u64) { + pub fn total_deposit(&self) -> u64 { + self.slot_deposit() + self.bytes_deposit() + } + + pub fn set_slot_deposit(&mut self, amount: u64) { match self { StateValueMetadata::V0 { deposit, .. } => *deposit = amount, + StateValueMetadata::V1 { slot_deposit, .. } => *slot_deposit = amount, + } + } + + pub fn set_bytes_deposit(&mut self, amount: u64) { + let creation_time_usecs = self.creation_time_usecs(); + let slot_deposit = match self { + StateValueMetadata::V0 { deposit, .. } => *deposit, + StateValueMetadata::V1 { slot_deposit, .. } => *slot_deposit, + }; + + *self = Self::V1 { + slot_deposit, + bytes_deposit: amount, + creation_time_usecs, + } + } + + pub fn set_deposits(&mut self, slot_deposit: u64, bytes_deposit: u64) { + let creation_time_usecs = self.creation_time_usecs(); + *self = Self::V1 { + slot_deposit, + bytes_deposit, + creation_time_usecs, } } }