diff --git a/sui_core/src/authority.rs b/sui_core/src/authority.rs index d2f7a41727952..3693bd6ad8e04 100644 --- a/sui_core/src/authority.rs +++ b/sui_core/src/authority.rs @@ -127,7 +127,8 @@ impl AuthorityState { object_id: gas_payment_id, })?; gas::check_gas_balance(&gas_object, gas_budget)?; - let gas_status = gas::start_gas_metering(gas_budget)?; + // TODO: Pass in real computation gas unit price and storage gas unit price. + let gas_status = gas::start_gas_metering(gas_budget, 1, 1)?; Ok((gas_object, gas_status)) } diff --git a/sui_core/src/authority/temporary_store.rs b/sui_core/src/authority/temporary_store.rs index 7d1b1c19203f1..03f8f81f3431e 100644 --- a/sui_core/src/authority/temporary_store.rs +++ b/sui_core/src/authority/temporary_store.rs @@ -118,32 +118,63 @@ impl AuthorityTemporaryStore { /// the gas object hasn't been mutated yet. Passing in `gas_object_size` so that we can also charge /// for the gas object mutation in advance. pub fn charge_gas_for_storage_changes( - &self, + &mut self, gas_status: &mut SuiGasStatus, - gas_object_size: usize, + gas_object: &mut Object, ) -> SuiResult { - for (object_id, (_object_ref, object)) in &self.written { - // Objects in written can be either mutation or creation. - // We figure it out by looking them up in `self.objects`. - let object_size = object.object_size_for_gas_metering(); - let old_object_size = if let Some(old_obj) = self.objects.get(object_id) { - old_obj.object_size_for_gas_metering() - } else { - 0 - }; - gas_status.charge_storage_mutation(old_object_size, object_size)?; + let mut objects_to_update = vec![]; + // Also charge gas for mutating the gas object in advance. + let gas_object_size = gas_object.object_size_for_gas_metering(); + gas_object.storage_rebate = gas_status.charge_storage_mutation( + gas_object_size, + gas_object_size, + gas_object.storage_rebate, + )?; + objects_to_update.push(gas_object.clone()); + + for (object_id, (_object_ref, object)) in &mut self.written { + let (old_object_size, storage_rebate) = + if let Some(old_object) = self.objects.get(object_id) { + ( + old_object.object_size_for_gas_metering(), + old_object.storage_rebate, + ) + } else { + (0, 0) + }; + let new_storage_rebate = gas_status.charge_storage_mutation( + old_object_size, + object.object_size_for_gas_metering(), + storage_rebate, + )?; + if !object.is_read_only() { + // We don't need to set storage rebate for immutable objects, as they will + // never be deleted. + object.storage_rebate = new_storage_rebate; + objects_to_update.push(object.clone()); + } } + for object_id in self.deleted.keys() { // If an object is in `self.deleted`, and also in `self.objects`, we give storage rebate. // Otherwise if an object is in `self.deleted` but not in `self.objects`, it means this // object was unwrapped and then deleted. The rebate would have been provided already when // mutating the object that wrapped this object. - if let Some(old_obj) = self.objects.get(object_id) { - gas_status.charge_storage_mutation(old_obj.object_size_for_gas_metering(), 0)?; + if let Some(old_object) = self.objects.get(object_id) { + gas_status.charge_storage_mutation( + old_object.object_size_for_gas_metering(), + 0, + old_object.storage_rebate, + )?; } } - // Also charge gas for mutating the gas object in advance. - gas_status.charge_storage_mutation(gas_object_size, gas_object_size)?; + + // Write all objects at the end only if all previous gas charges succeeded. + // This avoids polluting the temporary store state if this function failed. + for object in objects_to_update { + self.write_object(object); + } + Ok(()) } diff --git a/sui_core/src/execution_engine.rs b/sui_core/src/execution_engine.rs index 4279ae2569b77..1a5730906793a 100644 --- a/sui_core/src/execution_engine.rs +++ b/sui_core/src/execution_engine.rs @@ -132,15 +132,18 @@ fn execute_transaction( temporary_store.reset(); } temporary_store.ensure_active_inputs_mutated(); - if let Err(err) = temporary_store - .charge_gas_for_storage_changes(&mut gas_status, gas_object.object_size_for_gas_metering()) + if let Err(err) = + temporary_store.charge_gas_for_storage_changes(&mut gas_status, &mut gas_object) { result = Err(err); + // No need to roll back the temporary store here since `charge_gas_for_storage_changes` + // will not modify `temporary_store` if it failed. } let cost_summary = gas_status.summary(result.is_ok()); let gas_used = cost_summary.gas_used(); - gas::deduct_gas(&mut gas_object, gas_used); + let gas_rebate = cost_summary.storage_rebate; + gas::deduct_gas(&mut gas_object, gas_used, gas_rebate); temporary_store.write_object(gas_object); // TODO: Return cost_summary so that the detailed summary exists in TransactionEffects for diff --git a/sui_core/src/gateway_state.rs b/sui_core/src/gateway_state.rs index cbf572f132203..92649fd527db7 100644 --- a/sui_core/src/gateway_state.rs +++ b/sui_core/src/gateway_state.rs @@ -222,7 +222,8 @@ where ) -> SuiResult<(Object, SuiGasStatus<'_>)> { let gas_object = self.get_object(&gas_payment_id).await?; gas::check_gas_balance(&gas_object, gas_budget)?; - let gas_status = gas::start_gas_metering(gas_budget)?; + // TODO: Pass in real computation gas unit price and storage gas unit price. + let gas_status = gas::start_gas_metering(gas_budget, 1, 1)?; Ok((gas_object, gas_status)) } diff --git a/sui_core/src/unit_tests/authority_tests.rs b/sui_core/src/unit_tests/authority_tests.rs index 1bbdc3d3d5230..0338fccac1cb6 100644 --- a/sui_core/src/unit_tests/authority_tests.rs +++ b/sui_core/src/unit_tests/authority_tests.rs @@ -1416,15 +1416,8 @@ async fn shared_object() { use sui_types::object::MoveObject; let content = GasCoin::new(shared_object_id, SequenceNumber::new(), 10); - let data = Data::Move(MoveObject::new( - /* type */ GasCoin::type_(), - content.to_bcs_bytes(), - )); - Object { - data, - owner: Owner::SharedMutable, - previous_transaction: TransactionDigest::genesis(), - } + let obj = MoveObject::new(/* type */ GasCoin::type_(), content.to_bcs_bytes()); + Object::new_move(obj, Owner::SharedMutable, TransactionDigest::genesis()) }; let authority = init_state_with_objects(vec![gas_object, shared_object]).await; diff --git a/sui_core/src/unit_tests/consensus_tests.rs b/sui_core/src/unit_tests/consensus_tests.rs index 68927e476d6c5..4f5bca827278f 100644 --- a/sui_core/src/unit_tests/consensus_tests.rs +++ b/sui_core/src/unit_tests/consensus_tests.rs @@ -17,7 +17,7 @@ use sui_types::gas_coin::GasCoin; use sui_types::messages::{ CertifiedTransaction, SignatureAggregator, Transaction, TransactionData, }; -use sui_types::object::{Data, MoveObject, Object, Owner}; +use sui_types::object::{MoveObject, Object, Owner}; use sui_types::serialize::serialize_cert; use test_utils::sequencer::Sequencer; @@ -47,15 +47,8 @@ fn test_shared_object() -> Object { let seed = "0x6666666666666660"; let shared_object_id = ObjectID::from_hex_literal(seed).unwrap(); let content = GasCoin::new(shared_object_id, SequenceNumber::new(), 10); - let data = Data::Move(MoveObject::new( - /* type */ GasCoin::type_(), - content.to_bcs_bytes(), - )); - Object { - data, - owner: Owner::SharedMutable, - previous_transaction: TransactionDigest::genesis(), - } + let obj = MoveObject::new(/* type */ GasCoin::type_(), content.to_bcs_bytes()); + Object::new_move(obj, Owner::SharedMutable, TransactionDigest::genesis()) } /// Fixture: a few test certificates containing a shared object. diff --git a/sui_core/src/unit_tests/gas_tests.rs b/sui_core/src/unit_tests/gas_tests.rs index 539e294b17186..12669b3e305f7 100644 --- a/sui_core/src/unit_tests/gas_tests.rs +++ b/sui_core/src/unit_tests/gas_tests.rs @@ -82,7 +82,8 @@ async fn test_native_transfer_sufficient_gas() -> SuiResult { let effects = result.response.unwrap().signed_effects.unwrap().effects; let gas_cost = effects.status.gas_cost_summary(); assert!(gas_cost.computation_cost > *MIN_GAS_BUDGET); - assert_eq!(gas_cost.storage_cost, 0); + assert!(gas_cost.storage_cost > 0); + // Removing genesis object does not have rebate. assert_eq!(gas_cost.storage_rebate, 0); let object = result @@ -103,22 +104,14 @@ async fn test_native_transfer_sufficient_gas() -> SuiResult { // Mimic the process of gas charging, to check that we are charging // exactly what we should be charging. - let mut gas_status = SuiGasStatus::new_with_budget(*MAX_GAS_BUDGET); + let mut gas_status = SuiGasStatus::new_with_budget(*MAX_GAS_BUDGET, 1, 1); gas_status.charge_min_tx_gas()?; + let obj_size = object.object_size_for_gas_metering(); + let gas_size = gas_object.object_size_for_gas_metering(); - // Both the object to be transferred and the gas object will be read - // from the store. Hence we need to charge for 2 reads. - gas_status.charge_storage_read( - object.object_size_for_gas_metering() + gas_object.object_size_for_gas_metering(), - )?; - gas_status.charge_storage_mutation( - object.object_size_for_gas_metering(), - object.object_size_for_gas_metering(), - )?; - gas_status.charge_storage_mutation( - gas_object.object_size_for_gas_metering(), - gas_object.object_size_for_gas_metering(), - )?; + gas_status.charge_storage_read(obj_size + gas_size)?; + gas_status.charge_storage_mutation(obj_size, obj_size, 0)?; + gas_status.charge_storage_mutation(gas_size, gas_size, 0)?; assert_eq!(gas_cost, &gas_status.summary(true)); Ok(()) } @@ -158,8 +151,8 @@ async fn test_native_transfer_insufficient_gas_execution() { let budget = total_gas - 1; let result = execute_transfer(budget, budget, true).await; let effects = result.response.unwrap().signed_effects.unwrap().effects; - // The gas balance should be drained. - assert_eq!(effects.status.gas_cost_summary().gas_used(), budget); + // We won't drain the entire budget because we don't charge for storage if tx failed. + assert!(effects.status.gas_cost_summary().gas_used() < budget); let gas_object = result .authority_state .get_object(&result.gas_object_id) @@ -167,11 +160,14 @@ async fn test_native_transfer_insufficient_gas_execution() { .unwrap() .unwrap(); let gas_coin = GasCoin::try_from(&gas_object).unwrap(); - assert_eq!(gas_coin.value(), 0); + assert_eq!( + gas_coin.value(), + budget - effects.status.gas_cost_summary().gas_used() + ); assert_eq!( effects.status.unwrap_err().1, SuiError::InsufficientGas { - error: "Ran out of gas while deducting computation cost".to_owned() + error: "Ran out of gas while deducting storage cost".to_owned() } ); } @@ -221,7 +217,7 @@ async fn test_publish_gas() -> SuiResult { }; // Mimic the gas charge behavior and cross check the result with above. - let mut gas_status = SuiGasStatus::new_with_budget(*MAX_GAS_BUDGET); + let mut gas_status = SuiGasStatus::new_with_budget(*MAX_GAS_BUDGET, 1, 1); gas_status.charge_min_tx_gas()?; gas_status.charge_storage_read( genesis_objects @@ -231,12 +227,13 @@ async fn test_publish_gas() -> SuiResult { )?; gas_status.charge_storage_read(gas_object.object_size_for_gas_metering())?; gas_status.charge_publish_package(publish_bytes.iter().map(|v| v.len()).sum())?; - gas_status.charge_storage_mutation(0, package.object_size_for_gas_metering())?; + gas_status.charge_storage_mutation(0, package.object_size_for_gas_metering(), 0)?; // Remember the gas used so far. We will use this to create another failure case latter. let gas_used_after_package_creation = gas_status.summary(true).gas_used(); gas_status.charge_storage_mutation( gas_object.object_size_for_gas_metering(), gas_object.object_size_for_gas_metering(), + 0, )?; assert_eq!(gas_cost, &gas_status.summary(true)); @@ -261,16 +258,14 @@ async fn test_publish_gas() -> SuiResult { assert_eq!( err, SuiError::InsufficientGas { - error: "Ran out of gas while deducting computation cost".to_owned() + error: "Ran out of gas while deducting storage cost".to_owned() } ); // Make sure that we are not charging storage cost at failure. assert_eq!(gas_cost.storage_cost, 0); // Upon failure, we should only be charging the expected computation cost. - // Since we failed when trying to charge the last piece of computation cost, - // the total cost will be DELTA less since it's not enough. - assert_eq!(gas_cost.gas_used(), computation_cost - DELTA); + assert_eq!(gas_cost.gas_used(), computation_cost); let gas_object = authority_state.get_object(&gas_object_id).await?.unwrap(); let expected_gas_balance = expected_gas_balance - gas_cost.gas_used(); @@ -352,7 +347,7 @@ async fn test_move_call_gas() -> SuiResult { ); // Mimic the gas charge behavior and cross check the result with above. - let mut gas_status = SuiGasStatus::new_with_budget(GAS_VALUE_FOR_TESTING); + let mut gas_status = SuiGasStatus::new_with_budget(GAS_VALUE_FOR_TESTING, 1, 1); gas_status.charge_min_tx_gas()?; let package_object = authority_state .get_object(&package_object_ref.0) @@ -370,22 +365,53 @@ async fn test_move_call_gas() -> SuiResult { .get_object(&effects.created[0].0 .0) .await? .unwrap(); - gas_status.charge_storage_mutation(0, created_object.object_size_for_gas_metering())?; + gas_status.charge_storage_mutation(0, created_object.object_size_for_gas_metering(), 0)?; gas_status.charge_storage_mutation( gas_object.object_size_for_gas_metering(), gas_object.object_size_for_gas_metering(), + 0, )?; let new_cost = gas_status.summary(true); assert_eq!(gas_cost.computation_cost, new_cost.computation_cost,); assert_eq!(gas_cost.storage_cost, new_cost.storage_cost); + // This is the total amount of storage cost paid. We will use this + // to check if we get back the same amount of rebate latter. + let prev_storage_cost = gas_cost.storage_cost; + + // Execute object deletion, and make sure we have storage rebate. + let data = TransactionData::new_move_call( + sender, + package_object_ref, + module.clone(), + ident_str!("delete").to_owned(), + vec![], + gas_object.compute_object_reference(), + vec![created_object_ref], + vec![], + vec![], + expected_gas_balance, + ); + let signature = Signature::new(&data, &sender_key); + let transaction = Transaction::new(data, signature); + let response = send_and_confirm_transaction(&authority_state, transaction).await?; + let effects = response.signed_effects.unwrap().effects; + assert!(effects.status.is_ok()); + let gas_cost = effects.status.gas_cost_summary(); + // storage_cost should be less than rebate because for object deletion, we only + // rebate without charging. + assert!(gas_cost.storage_cost > 0 && gas_cost.storage_cost < gas_cost.storage_rebate); + // Check that we have storage rebate that's the same as previous cost. + assert_eq!(gas_cost.storage_rebate, prev_storage_cost); + let expected_gas_balance = expected_gas_balance - gas_cost.gas_used() + gas_cost.storage_rebate; // Create a transaction with gas budget that should run out during Move VM execution. + let gas_object = authority_state.get_object(&gas_object_id).await?.unwrap(); let budget = gas_used_before_vm_exec + 1; let data = TransactionData::new_move_call( sender, package_object_ref, - module.clone(), + module, function, Vec::new(), gas_object.compute_object_reference(), @@ -409,34 +435,28 @@ async fn test_move_call_gas() -> SuiResult { } ); let gas_object = authority_state.get_object(&gas_object_id).await?.unwrap(); - let expected_gas_balance = expected_gas_balance - gas_cost.gas_used(); + let expected_gas_balance = expected_gas_balance - gas_cost.gas_used() + gas_cost.storage_rebate; assert_eq!( GasCoin::try_from(&gas_object)?.value(), expected_gas_balance, ); + Ok(()) +} - // Execute object deletion, and make sure we have storage rebate. - let data = TransactionData::new_move_call( - sender, - package_object_ref, - module, - ident_str!("delete").to_owned(), - vec![], - gas_object.compute_object_reference(), - vec![created_object_ref], - vec![], - vec![], - expected_gas_balance, - ); - let signature = Signature::new(&data, &sender_key); - let transaction = Transaction::new(data, signature); - let response = send_and_confirm_transaction(&authority_state, transaction).await?; - let effects = response.signed_effects.unwrap().effects; - assert!(effects.status.is_ok()); - let gas_cost = effects.status.gas_cost_summary(); - assert_eq!(gas_cost.storage_cost, 0); - // Check that we have storage rebate after deletion. - assert!(gas_cost.storage_rebate > 0); +#[tokio::test] +async fn test_storage_gas_unit_price() -> SuiResult { + let mut gas_status1 = SuiGasStatus::new_with_budget(*MAX_GAS_BUDGET, 1, 1); + gas_status1.charge_storage_mutation(100, 200, 5)?; + let gas_cost1 = gas_status1.summary(true); + let mut gas_status2 = SuiGasStatus::new_with_budget(*MAX_GAS_BUDGET, 1, 3); + gas_status2.charge_storage_mutation(100, 200, 5)?; + let gas_cost2 = gas_status2.summary(true); + // Computation unit price is the same, hence computation cost should be the same. + assert_eq!(gas_cost1.computation_cost, gas_cost2.computation_cost); + // Storage unit prices is 3X, so will be the storage cost. + assert_eq!(gas_cost1.storage_cost * 3, gas_cost2.storage_cost); + // Storage rebate should not be affected by the price. + assert_eq!(gas_cost1.storage_rebate, gas_cost2.storage_rebate); Ok(()) } diff --git a/sui_core/tests/staged/sui.yaml b/sui_core/tests/staged/sui.yaml index a25238679aa70..93c5657fe6205 100644 --- a/sui_core/tests/staged/sui.yaml +++ b/sui_core/tests/staged/sui.yaml @@ -273,6 +273,7 @@ Object: TYPENAME: Owner - previous_transaction: TYPENAME: TransactionDigest + - storage_rebate: U64 ObjectDigest: NEWTYPESTRUCT: BYTES ObjectFormatOptions: diff --git a/sui_programmability/adapter/src/unit_tests/adapter_tests.rs b/sui_programmability/adapter/src/unit_tests/adapter_tests.rs index 1329db1dc9219..335f053bc89bc 100644 --- a/sui_programmability/adapter/src/unit_tests/adapter_tests.rs +++ b/sui_programmability/adapter/src/unit_tests/adapter_tests.rs @@ -204,7 +204,7 @@ fn call( type_args, object_args, pure_args, - &mut SuiGasStatus::new_with_budget(gas_budget), + &mut SuiGasStatus::new_with_budget(gas_budget, 1, 1), &mut TxContext::random_for_testing_only(), ) } diff --git a/sui_types/src/gas.rs b/sui_types/src/gas.rs index 9674672f070d1..4f8f494d3446c 100644 --- a/sui_types/src/gas.rs +++ b/sui_types/src/gas.rs @@ -8,7 +8,7 @@ use crate::{ object::Object, }; use move_core_types::gas_schedule::{ - AbstractMemorySize, GasAlgebra, GasCarrier, GasUnits, InternalGasUnits, + AbstractMemorySize, GasAlgebra, GasCarrier, GasPrice, GasUnits, InternalGasUnits, }; use move_vm_types::gas_schedule::{GasStatus, INITIAL_COST_SCHEDULE}; use once_cell::sync::Lazy; @@ -102,32 +102,52 @@ static INIT_SUI_COST_TABLE: Lazy = Lazy::new(|| SuiCostTable { }); pub static MAX_GAS_BUDGET: Lazy = - Lazy::new(|| u64::MAX / INITIAL_COST_SCHEDULE.gas_constants.gas_unit_scaling_factor); + Lazy::new(|| to_external(InternalGasUnits::new(u64::MAX)).get()); -pub static MIN_GAS_BUDGET: Lazy = Lazy::new(|| { +pub static MIN_GAS_BUDGET: Lazy = + Lazy::new(|| to_external(INIT_SUI_COST_TABLE.min_transaction_cost.0).get()); + +fn to_external(internal_units: InternalGasUnits) -> GasUnits { let consts = &INITIAL_COST_SCHEDULE.gas_constants; - consts - .to_external_units(INIT_SUI_COST_TABLE.min_transaction_cost.0) - .get() -}); + consts.to_external_units(internal_units) +} + +fn to_internal(external_units: GasUnits) -> InternalGasUnits { + let consts = &INITIAL_COST_SCHEDULE.gas_constants; + consts.to_internal_units(external_units) +} pub struct SuiGasStatus<'a> { gas_status: GasStatus<'a>, - init_budget: u64, - storage_cost: InternalGasUnits, - storage_rebate: InternalGasUnits, + init_budget: GasUnits, + computation_gas_unit_price: GasPrice, + storage_gas_unit_price: GasPrice, + /// storage_cost is the total storage gas units charged so far, due to writes into storage. + /// It will be multiplied by the storage gas unit price in the end to obtain the Sui cost. + storage_cost: GasUnits, + /// storage_rebate is the total storage rebate (in Sui) accumulated in this transaction. + /// It's directly coming from each mutated object's storage rebate field, which + /// was the storage cost paid when the object was last mutated. It is not affected + /// by the current storage gas unit price. + storage_rebate: GasCarrier, } impl<'a> SuiGasStatus<'a> { - pub fn new_with_budget(gas_budget: u64) -> SuiGasStatus<'a> { + pub fn new_with_budget( + gas_budget: u64, + computation_gas_unit_price: GasCarrier, + storage_gas_unit_price: GasCarrier, + ) -> SuiGasStatus<'a> { Self::new( GasStatus::new(&INITIAL_COST_SCHEDULE, GasUnits::new(gas_budget)), gas_budget, + computation_gas_unit_price, + storage_gas_unit_price, ) } pub fn new_unmetered() -> SuiGasStatus<'a> { - Self::new(GasStatus::new_unmetered(), 0) + Self::new(GasStatus::new_unmetered(), 0, 0, 0) } pub fn get_move_gas_status(&mut self) -> &mut GasStatus<'a> { @@ -156,7 +176,12 @@ impl<'a> SuiGasStatus<'a> { self.deduct_computation_cost(&cost) } - pub fn charge_storage_mutation(&mut self, old_size: usize, new_size: usize) -> SuiResult { + pub fn charge_storage_mutation( + &mut self, + old_size: usize, + new_size: usize, + storage_rebate: GasCarrier, + ) -> SuiResult { // Computation cost of a mutation is charged based on the sum of the old and new size. // This is because to update an object in the store, we have to erase the old one and // write a new one. @@ -165,26 +190,12 @@ impl<'a> SuiGasStatus<'a> { .with_size(old_size + new_size); self.deduct_computation_cost(&cost)?; - /// TODO: For rebates, what we want is to keep track of most recent storage - /// cost (in Sui) for each object. whenever we mutate an object, we always rebate the old - /// cost, and recharge based on the current storage gas price. - use std::cmp::Ordering; - match new_size.cmp(&old_size) { - Ordering::Greater => { - let cost = INIT_SUI_COST_TABLE - .storage_per_byte_cost - .with_size(new_size - old_size); - self.deduct_storage_cost(&cost) - } - Ordering::Less => { - self.rebate_storage_deletion(old_size - new_size); - Ok(()) - } - Ordering::Equal => { - // Do nothing about storage cost if old_size == new_size. - Ok(()) - } - } + self.storage_rebate += storage_rebate; + + let storage_cost = INIT_SUI_COST_TABLE + .storage_per_byte_cost + .with_size(new_size); + self.deduct_storage_cost(&storage_cost) } /// This function is only called during testing, where we need to mock @@ -198,34 +209,40 @@ impl<'a> SuiGasStatus<'a> { /// We use initial budget, combined with remaining gas and storage cost to derive /// computation cost. pub fn summary(&self, succeeded: bool) -> GasCostSummary { - let consts = &INITIAL_COST_SCHEDULE.gas_constants; - let remaining_gas = self.gas_status.remaining_gas().get(); - let storage_cost = consts.to_external_units(self.storage_cost).get(); - let computation_cost = self.init_budget - remaining_gas - storage_cost; - let storage_rebate = consts.to_external_units(self.storage_rebate).get(); + let remaining_gas = self.gas_status.remaining_gas(); + let storage_cost = self.storage_cost; + let computation_cost = self.init_budget.sub(remaining_gas).sub(storage_cost); + let computation_cost_in_sui = computation_cost.mul(self.computation_gas_unit_price).get(); if succeeded { GasCostSummary { - computation_cost, - storage_cost, - storage_rebate, + computation_cost: computation_cost_in_sui, + storage_cost: storage_cost.mul(self.storage_gas_unit_price).get(), + storage_rebate: self.storage_rebate, } } else { // If execution failed, no storage creation/deletion will materialize in the store. // Hence they should be 0. GasCostSummary { - computation_cost, + computation_cost: computation_cost_in_sui, storage_cost: 0, storage_rebate: 0, } } } - fn new(move_gas_status: GasStatus<'a>, gas_budget: u64) -> SuiGasStatus<'a> { + fn new( + move_gas_status: GasStatus<'a>, + gas_budget: u64, + computation_gas_unit_price: GasCarrier, + storage_gas_unit_price: u64, + ) -> SuiGasStatus<'a> { SuiGasStatus { gas_status: move_gas_status, - init_budget: gas_budget, - storage_cost: InternalGasUnits::new(0), - storage_rebate: InternalGasUnits::new(0), + init_budget: GasUnits::new(gas_budget), + computation_gas_unit_price: GasPrice::new(computation_gas_unit_price), + storage_gas_unit_price: GasPrice::new(storage_gas_unit_price), + storage_cost: GasUnits::new(0), + storage_rebate: 0, } } @@ -239,28 +256,24 @@ impl<'a> SuiGasStatus<'a> { } } - fn deduct_storage_cost(&mut self, cost: &StorageCost) -> SuiResult { + fn deduct_storage_cost(&mut self, cost: &StorageCost) -> SuiResult { + let ext_cost = to_external(cost.0); + let charge_amount = to_internal(ext_cost); let remaining_gas = self.gas_status.remaining_gas(); - if self.gas_status.deduct_gas(cost.0).is_err() { + if self.gas_status.deduct_gas(charge_amount).is_err() { debug_assert_eq!(self.gas_status.remaining_gas().get(), 0); - self.storage_cost = self.storage_cost.add( - INITIAL_COST_SCHEDULE - .gas_constants - .to_internal_units(remaining_gas), - ); + // Even when we run out of gas, we still keep track of the storage_cost change, + // so that at the end, we could still use it to accurately derive the + // computation cost. + self.storage_cost = self.storage_cost.add(remaining_gas); Err(SuiError::InsufficientGas { error: "Ran out of gas while deducting storage cost".to_owned(), }) } else { - self.storage_cost = self.storage_cost.add(cost.0); - Ok(()) + self.storage_cost = self.storage_cost.add(ext_cost); + Ok(ext_cost.mul(self.storage_gas_unit_price).get()) } } - - fn rebate_storage_deletion(&mut self, size: usize) { - let rebate = INIT_SUI_COST_TABLE.storage_per_byte_cost.with_size(size); - self.storage_rebate = self.storage_rebate.add(rebate.0); - } } /// Check whether the given gas_object and gas_budget is legit: @@ -293,8 +306,16 @@ pub fn check_gas_balance(gas_object: &Object, gas_budget: u64) -> SuiResult { } /// Create a new gas status with the given `gas_budget`, and charge the transaction flat fee. -pub fn start_gas_metering(gas_budget: u64) -> SuiResult> { - let mut gas_status = SuiGasStatus::new_with_budget(gas_budget); +pub fn start_gas_metering( + gas_budget: u64, + computation_gas_unit_price: u64, + storage_gas_unit_price: u64, +) -> SuiResult> { + let mut gas_status = SuiGasStatus::new_with_budget( + gas_budget, + computation_gas_unit_price, + storage_gas_unit_price, + ); // Charge the flat transaction fee. gas_status.charge_min_tx_gas()?; Ok(gas_status) @@ -303,12 +324,25 @@ pub fn start_gas_metering(gas_budget: u64) -> SuiResult> { /// Subtract the gas balance of \p gas_object by \p amount. /// This function should never fail, since we checked that the budget is always /// less than balance, and the amount is capped at the budget. -pub fn deduct_gas(gas_object: &mut Object, amount: u64) { +pub fn deduct_gas(gas_object: &mut Object, deduct_amount: u64, rebate_amount: u64) { + // The object must be a gas coin as we have checked in transaction handle phase. + let gas_coin = GasCoin::try_from(&*gas_object).unwrap(); + let balance = gas_coin.value(); + debug_assert!(balance >= deduct_amount); + let new_gas_coin = GasCoin::new( + *gas_coin.id(), + gas_object.version(), + balance + rebate_amount - deduct_amount, + ); + let move_object = gas_object.data.try_as_move_mut().unwrap(); + move_object.update_contents(bcs::to_bytes(&new_gas_coin).unwrap()); +} + +pub fn refund_gas(gas_object: &mut Object, amount: u64) { // The object must be a gas coin as we have checked in transaction handle phase. let gas_coin = GasCoin::try_from(&*gas_object).unwrap(); let balance = gas_coin.value(); - debug_assert!(balance >= amount); - let new_gas_coin = GasCoin::new(*gas_coin.id(), gas_object.version(), balance - amount); + let new_gas_coin = GasCoin::new(*gas_coin.id(), gas_object.version(), balance + amount); let move_object = gas_object.data.try_as_move_mut().unwrap(); move_object.update_contents(bcs::to_bytes(&new_gas_coin).unwrap()); } diff --git a/sui_types/src/object.rs b/sui_types/src/object.rs index 18169cf2fff4d..717c9679ec991 100644 --- a/sui_types/src/object.rs +++ b/sui_types/src/object.rs @@ -336,6 +336,10 @@ pub struct Object { pub owner: Owner, /// The digest of the transaction that created or last mutated this object pub previous_transaction: TransactionDigest, + /// The amount of SUI we would rebate if this object gets deleted. + /// This number is re-calculated each time the object is mutated based on + /// the present storage gas price. + pub storage_rebate: u64, } impl BcsSignable for Object {} @@ -347,6 +351,7 @@ impl Object { data: Data::Move(o), owner, previous_transaction, + storage_rebate: 0, } } @@ -358,6 +363,7 @@ impl Object { data: Data::Package(MovePackage::from(&modules)), owner: Owner::SharedImmutable, previous_transaction, + storage_rebate: 0, } } @@ -419,7 +425,7 @@ impl Object { /// we also don't want to serialize the object just to get the size. /// This approximation should be good enough for gas metering. pub fn object_size_for_gas_metering(&self) -> usize { - let meta_data_size = size_of::() + size_of::(); + let meta_data_size = size_of::() + size_of::() + size_of::(); let data_size = match &self.data { Data::Move(m) => m.object_size_for_gas_metering(), Data::Package(p) => p @@ -450,6 +456,7 @@ impl Object { owner: Owner::SharedImmutable, data, previous_transaction: TransactionDigest::genesis(), + storage_rebate: 0, } } @@ -467,6 +474,7 @@ impl Object { owner: Owner::AddressOwner(owner), data, previous_transaction: TransactionDigest::genesis(), + storage_rebate: 0, } } @@ -492,6 +500,7 @@ impl Object { owner: Owner::AddressOwner(owner), data, previous_transaction: TransactionDigest::genesis(), + storage_rebate: 0, } }